Nevar pievienot vairāk kā 25 tēmas Tēmai ir jāsākas ar burtu vai ciparu, tā var saturēt domu zīmes ('-') un var būt līdz 35 simboliem gara.
 
 
 

5601 rinda
146 KiB

  1. /*!
  2. * svg.js - A lightweight library for manipulating and animating SVG.
  3. * @version 2.7.1
  4. * https://svgdotjs.github.io/
  5. *
  6. * @copyright Wout Fierens <wout@mick-wout.com>
  7. * @license MIT
  8. *
  9. * BUILT: Fri Nov 30 2018 10:01:55 GMT+0100 (GMT+01:00)
  10. */;
  11. (function(root, factory) {
  12. /* istanbul ignore next */
  13. if (typeof define === 'function' && define.amd) {
  14. define(function(){
  15. return factory(root, root.document)
  16. })
  17. } else if (typeof exports === 'object') {
  18. module.exports = root.document ? factory(root, root.document) : function(w){ return factory(w, w.document) }
  19. } else {
  20. root.SVG = factory(root, root.document)
  21. }
  22. }(typeof window !== "undefined" ? window : this, function(window, document) {
  23. // Find global reference - uses 'this' by default when available,
  24. // falls back to 'window' otherwise (for bundlers like Webpack)
  25. var globalRef = (typeof this !== "undefined") ? this : window;
  26. // The main wrapping element
  27. var SVG = globalRef.SVG = function(element) {
  28. if (SVG.supported) {
  29. element = new SVG.Doc(element)
  30. if(!SVG.parser.draw)
  31. SVG.prepare()
  32. return element
  33. }
  34. }
  35. // Default namespaces
  36. SVG.ns = 'http://www.w3.org/2000/svg'
  37. SVG.xmlns = 'http://www.w3.org/2000/xmlns/'
  38. SVG.xlink = 'http://www.w3.org/1999/xlink'
  39. SVG.svgjs = 'http://svgjs.com/svgjs'
  40. // Svg support test
  41. SVG.supported = (function() {
  42. return !! document.createElementNS &&
  43. !! document.createElementNS(SVG.ns,'svg').createSVGRect
  44. })()
  45. // Don't bother to continue if SVG is not supported
  46. if (!SVG.supported) return false
  47. // Element id sequence
  48. SVG.did = 1000
  49. // Get next named element id
  50. SVG.eid = function(name) {
  51. return 'Svgjs' + capitalize(name) + (SVG.did++)
  52. }
  53. // Method for element creation
  54. SVG.create = function(name) {
  55. // create element
  56. var element = document.createElementNS(this.ns, name)
  57. // apply unique id
  58. element.setAttribute('id', this.eid(name))
  59. return element
  60. }
  61. // Method for extending objects
  62. SVG.extend = function() {
  63. var modules, methods, key, i
  64. // Get list of modules
  65. modules = [].slice.call(arguments)
  66. // Get object with extensions
  67. methods = modules.pop()
  68. for (i = modules.length - 1; i >= 0; i--)
  69. if (modules[i])
  70. for (key in methods)
  71. modules[i].prototype[key] = methods[key]
  72. // Make sure SVG.Set inherits any newly added methods
  73. if (SVG.Set && SVG.Set.inherit)
  74. SVG.Set.inherit()
  75. }
  76. // Invent new element
  77. SVG.invent = function(config) {
  78. // Create element initializer
  79. var initializer = typeof config.create == 'function' ?
  80. config.create :
  81. function() {
  82. this.constructor.call(this, SVG.create(config.create))
  83. }
  84. // Inherit prototype
  85. if (config.inherit)
  86. initializer.prototype = new config.inherit
  87. // Extend with methods
  88. if (config.extend)
  89. SVG.extend(initializer, config.extend)
  90. // Attach construct method to parent
  91. if (config.construct)
  92. SVG.extend(config.parent || SVG.Container, config.construct)
  93. return initializer
  94. }
  95. // Adopt existing svg elements
  96. SVG.adopt = function(node) {
  97. // check for presence of node
  98. if (!node) return null
  99. // make sure a node isn't already adopted
  100. if (node.instance) return node.instance
  101. // initialize variables
  102. var element
  103. // adopt with element-specific settings
  104. if (node.nodeName == 'svg')
  105. element = node.parentNode instanceof window.SVGElement ? new SVG.Nested : new SVG.Doc
  106. else if (node.nodeName == 'linearGradient')
  107. element = new SVG.Gradient('linear')
  108. else if (node.nodeName == 'radialGradient')
  109. element = new SVG.Gradient('radial')
  110. else if (SVG[capitalize(node.nodeName)])
  111. element = new SVG[capitalize(node.nodeName)]
  112. else
  113. element = new SVG.Element(node)
  114. // ensure references
  115. element.type = node.nodeName
  116. element.node = node
  117. node.instance = element
  118. // SVG.Class specific preparations
  119. if (element instanceof SVG.Doc)
  120. element.namespace().defs()
  121. // pull svgjs data from the dom (getAttributeNS doesn't work in html5)
  122. element.setData(JSON.parse(node.getAttribute('svgjs:data')) || {})
  123. return element
  124. }
  125. // Initialize parsing element
  126. SVG.prepare = function() {
  127. // Select document body and create invisible svg element
  128. var body = document.getElementsByTagName('body')[0]
  129. , draw = (body ? new SVG.Doc(body) : SVG.adopt(document.documentElement).nested()).size(2, 0)
  130. // Create parser object
  131. SVG.parser = {
  132. body: body || document.documentElement
  133. , draw: draw.style('opacity:0;position:absolute;left:-100%;top:-100%;overflow:hidden').attr('focusable', 'false').node
  134. , poly: draw.polyline().node
  135. , path: draw.path().node
  136. , native: SVG.create('svg')
  137. }
  138. }
  139. SVG.parser = {
  140. native: SVG.create('svg')
  141. }
  142. document.addEventListener('DOMContentLoaded', function() {
  143. if(!SVG.parser.draw)
  144. SVG.prepare()
  145. }, false)
  146. // Storage for regular expressions
  147. SVG.regex = {
  148. // Parse unit value
  149. numberAndUnit: /^([+-]?(\d+(\.\d*)?|\.\d+)(e[+-]?\d+)?)([a-z%]*)$/i
  150. // Parse hex value
  151. , hex: /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i
  152. // Parse rgb value
  153. , rgb: /rgb\((\d+),(\d+),(\d+)\)/
  154. // Parse reference id
  155. , reference: /#([a-z0-9\-_]+)/i
  156. // splits a transformation chain
  157. , transforms: /\)\s*,?\s*/
  158. // Whitespace
  159. , whitespace: /\s/g
  160. // Test hex value
  161. , isHex: /^#[a-f0-9]{3,6}$/i
  162. // Test rgb value
  163. , isRgb: /^rgb\(/
  164. // Test css declaration
  165. , isCss: /[^:]+:[^;]+;?/
  166. // Test for blank string
  167. , isBlank: /^(\s+)?$/
  168. // Test for numeric string
  169. , isNumber: /^[+-]?(\d+(\.\d*)?|\.\d+)(e[+-]?\d+)?$/i
  170. // Test for percent value
  171. , isPercent: /^-?[\d\.]+%$/
  172. // Test for image url
  173. , isImage: /\.(jpg|jpeg|png|gif|svg)(\?[^=]+.*)?/i
  174. // split at whitespace and comma
  175. , delimiter: /[\s,]+/
  176. // The following regex are used to parse the d attribute of a path
  177. // Matches all hyphens which are not after an exponent
  178. , hyphen: /([^e])\-/gi
  179. // Replaces and tests for all path letters
  180. , pathLetters: /[MLHVCSQTAZ]/gi
  181. // yes we need this one, too
  182. , isPathLetter: /[MLHVCSQTAZ]/i
  183. // matches 0.154.23.45
  184. , numbersWithDots: /((\d?\.\d+(?:e[+-]?\d+)?)((?:\.\d+(?:e[+-]?\d+)?)+))+/gi
  185. // matches .
  186. , dots: /\./g
  187. }
  188. SVG.utils = {
  189. // Map function
  190. map: function(array, block) {
  191. var i
  192. , il = array.length
  193. , result = []
  194. for (i = 0; i < il; i++)
  195. result.push(block(array[i]))
  196. return result
  197. }
  198. // Filter function
  199. , filter: function(array, block) {
  200. var i
  201. , il = array.length
  202. , result = []
  203. for (i = 0; i < il; i++)
  204. if (block(array[i]))
  205. result.push(array[i])
  206. return result
  207. }
  208. // Degrees to radians
  209. , radians: function(d) {
  210. return d % 360 * Math.PI / 180
  211. }
  212. // Radians to degrees
  213. , degrees: function(r) {
  214. return r * 180 / Math.PI % 360
  215. }
  216. , filterSVGElements: function(nodes) {
  217. return this.filter( nodes, function(el) { return el instanceof window.SVGElement })
  218. }
  219. }
  220. SVG.defaults = {
  221. // Default attribute values
  222. attrs: {
  223. // fill and stroke
  224. 'fill-opacity': 1
  225. , 'stroke-opacity': 1
  226. , 'stroke-width': 0
  227. , 'stroke-linejoin': 'miter'
  228. , 'stroke-linecap': 'butt'
  229. , fill: '#000000'
  230. , stroke: '#000000'
  231. , opacity: 1
  232. // position
  233. , x: 0
  234. , y: 0
  235. , cx: 0
  236. , cy: 0
  237. // size
  238. , width: 0
  239. , height: 0
  240. // radius
  241. , r: 0
  242. , rx: 0
  243. , ry: 0
  244. // gradient
  245. , offset: 0
  246. , 'stop-opacity': 1
  247. , 'stop-color': '#000000'
  248. // text
  249. , 'font-size': 16
  250. , 'font-family': 'Helvetica, Arial, sans-serif'
  251. , 'text-anchor': 'start'
  252. }
  253. }
  254. // Module for color convertions
  255. SVG.Color = function(color) {
  256. var match
  257. // initialize defaults
  258. this.r = 0
  259. this.g = 0
  260. this.b = 0
  261. if(!color) return
  262. // parse color
  263. if (typeof color === 'string') {
  264. if (SVG.regex.isRgb.test(color)) {
  265. // get rgb values
  266. match = SVG.regex.rgb.exec(color.replace(SVG.regex.whitespace,''))
  267. // parse numeric values
  268. this.r = parseInt(match[1])
  269. this.g = parseInt(match[2])
  270. this.b = parseInt(match[3])
  271. } else if (SVG.regex.isHex.test(color)) {
  272. // get hex values
  273. match = SVG.regex.hex.exec(fullHex(color))
  274. // parse numeric values
  275. this.r = parseInt(match[1], 16)
  276. this.g = parseInt(match[2], 16)
  277. this.b = parseInt(match[3], 16)
  278. }
  279. } else if (typeof color === 'object') {
  280. this.r = color.r
  281. this.g = color.g
  282. this.b = color.b
  283. }
  284. }
  285. SVG.extend(SVG.Color, {
  286. // Default to hex conversion
  287. toString: function() {
  288. return this.toHex()
  289. }
  290. // Build hex value
  291. , toHex: function() {
  292. return '#'
  293. + compToHex(this.r)
  294. + compToHex(this.g)
  295. + compToHex(this.b)
  296. }
  297. // Build rgb value
  298. , toRgb: function() {
  299. return 'rgb(' + [this.r, this.g, this.b].join() + ')'
  300. }
  301. // Calculate true brightness
  302. , brightness: function() {
  303. return (this.r / 255 * 0.30)
  304. + (this.g / 255 * 0.59)
  305. + (this.b / 255 * 0.11)
  306. }
  307. // Make color morphable
  308. , morph: function(color) {
  309. this.destination = new SVG.Color(color)
  310. return this
  311. }
  312. // Get morphed color at given position
  313. , at: function(pos) {
  314. // make sure a destination is defined
  315. if (!this.destination) return this
  316. // normalise pos
  317. pos = pos < 0 ? 0 : pos > 1 ? 1 : pos
  318. // generate morphed color
  319. return new SVG.Color({
  320. r: ~~(this.r + (this.destination.r - this.r) * pos)
  321. , g: ~~(this.g + (this.destination.g - this.g) * pos)
  322. , b: ~~(this.b + (this.destination.b - this.b) * pos)
  323. })
  324. }
  325. })
  326. // Testers
  327. // Test if given value is a color string
  328. SVG.Color.test = function(color) {
  329. color += ''
  330. return SVG.regex.isHex.test(color)
  331. || SVG.regex.isRgb.test(color)
  332. }
  333. // Test if given value is a rgb object
  334. SVG.Color.isRgb = function(color) {
  335. return color && typeof color.r == 'number'
  336. && typeof color.g == 'number'
  337. && typeof color.b == 'number'
  338. }
  339. // Test if given value is a color
  340. SVG.Color.isColor = function(color) {
  341. return SVG.Color.isRgb(color) || SVG.Color.test(color)
  342. }
  343. // Module for array conversion
  344. SVG.Array = function(array, fallback) {
  345. array = (array || []).valueOf()
  346. // if array is empty and fallback is provided, use fallback
  347. if (array.length == 0 && fallback)
  348. array = fallback.valueOf()
  349. // parse array
  350. this.value = this.parse(array)
  351. }
  352. SVG.extend(SVG.Array, {
  353. // Make array morphable
  354. morph: function(array) {
  355. this.destination = this.parse(array)
  356. // normalize length of arrays
  357. if (this.value.length != this.destination.length) {
  358. var lastValue = this.value[this.value.length - 1]
  359. , lastDestination = this.destination[this.destination.length - 1]
  360. while(this.value.length > this.destination.length)
  361. this.destination.push(lastDestination)
  362. while(this.value.length < this.destination.length)
  363. this.value.push(lastValue)
  364. }
  365. return this
  366. }
  367. // Clean up any duplicate points
  368. , settle: function() {
  369. // find all unique values
  370. for (var i = 0, il = this.value.length, seen = []; i < il; i++)
  371. if (seen.indexOf(this.value[i]) == -1)
  372. seen.push(this.value[i])
  373. // set new value
  374. return this.value = seen
  375. }
  376. // Get morphed array at given position
  377. , at: function(pos) {
  378. // make sure a destination is defined
  379. if (!this.destination) return this
  380. // generate morphed array
  381. for (var i = 0, il = this.value.length, array = []; i < il; i++)
  382. array.push(this.value[i] + (this.destination[i] - this.value[i]) * pos)
  383. return new SVG.Array(array)
  384. }
  385. // Convert array to string
  386. , toString: function() {
  387. return this.value.join(' ')
  388. }
  389. // Real value
  390. , valueOf: function() {
  391. return this.value
  392. }
  393. // Parse whitespace separated string
  394. , parse: function(array) {
  395. array = array.valueOf()
  396. // if already is an array, no need to parse it
  397. if (Array.isArray(array)) return array
  398. return this.split(array)
  399. }
  400. // Strip unnecessary whitespace
  401. , split: function(string) {
  402. return string.trim().split(SVG.regex.delimiter).map(parseFloat)
  403. }
  404. // Reverse array
  405. , reverse: function() {
  406. this.value.reverse()
  407. return this
  408. }
  409. , clone: function() {
  410. var clone = new this.constructor()
  411. clone.value = array_clone(this.value)
  412. return clone
  413. }
  414. })
  415. // Poly points array
  416. SVG.PointArray = function(array, fallback) {
  417. SVG.Array.call(this, array, fallback || [[0,0]])
  418. }
  419. // Inherit from SVG.Array
  420. SVG.PointArray.prototype = new SVG.Array
  421. SVG.PointArray.prototype.constructor = SVG.PointArray
  422. SVG.extend(SVG.PointArray, {
  423. // Convert array to string
  424. toString: function() {
  425. // convert to a poly point string
  426. for (var i = 0, il = this.value.length, array = []; i < il; i++)
  427. array.push(this.value[i].join(','))
  428. return array.join(' ')
  429. }
  430. // Convert array to line object
  431. , toLine: function() {
  432. return {
  433. x1: this.value[0][0]
  434. , y1: this.value[0][1]
  435. , x2: this.value[1][0]
  436. , y2: this.value[1][1]
  437. }
  438. }
  439. // Get morphed array at given position
  440. , at: function(pos) {
  441. // make sure a destination is defined
  442. if (!this.destination) return this
  443. // generate morphed point string
  444. for (var i = 0, il = this.value.length, array = []; i < il; i++)
  445. array.push([
  446. this.value[i][0] + (this.destination[i][0] - this.value[i][0]) * pos
  447. , this.value[i][1] + (this.destination[i][1] - this.value[i][1]) * pos
  448. ])
  449. return new SVG.PointArray(array)
  450. }
  451. // Parse point string and flat array
  452. , parse: function(array) {
  453. var points = []
  454. array = array.valueOf()
  455. // if it is an array
  456. if (Array.isArray(array)) {
  457. // and it is not flat, there is no need to parse it
  458. if(Array.isArray(array[0])) {
  459. // make sure to use a clone
  460. return array.map(function (el) { return el.slice() })
  461. } else if (array[0].x != null){
  462. // allow point objects to be passed
  463. return array.map(function (el) { return [el.x, el.y] })
  464. }
  465. } else { // Else, it is considered as a string
  466. // parse points
  467. array = array.trim().split(SVG.regex.delimiter).map(parseFloat)
  468. }
  469. // validate points - https://svgwg.org/svg2-draft/shapes.html#DataTypePoints
  470. // Odd number of coordinates is an error. In such cases, drop the last odd coordinate.
  471. if (array.length % 2 !== 0) array.pop()
  472. // wrap points in two-tuples and parse points as floats
  473. for(var i = 0, len = array.length; i < len; i = i + 2)
  474. points.push([ array[i], array[i+1] ])
  475. return points
  476. }
  477. // Move point string
  478. , move: function(x, y) {
  479. var box = this.bbox()
  480. // get relative offset
  481. x -= box.x
  482. y -= box.y
  483. // move every point
  484. if (!isNaN(x) && !isNaN(y))
  485. for (var i = this.value.length - 1; i >= 0; i--)
  486. this.value[i] = [this.value[i][0] + x, this.value[i][1] + y]
  487. return this
  488. }
  489. // Resize poly string
  490. , size: function(width, height) {
  491. var i, box = this.bbox()
  492. // recalculate position of all points according to new size
  493. for (i = this.value.length - 1; i >= 0; i--) {
  494. if(box.width) this.value[i][0] = ((this.value[i][0] - box.x) * width) / box.width + box.x
  495. if(box.height) this.value[i][1] = ((this.value[i][1] - box.y) * height) / box.height + box.y
  496. }
  497. return this
  498. }
  499. // Get bounding box of points
  500. , bbox: function() {
  501. SVG.parser.poly.setAttribute('points', this.toString())
  502. return SVG.parser.poly.getBBox()
  503. }
  504. })
  505. var pathHandlers = {
  506. M: function(c, p, p0) {
  507. p.x = p0.x = c[0]
  508. p.y = p0.y = c[1]
  509. return ['M', p.x, p.y]
  510. },
  511. L: function(c, p) {
  512. p.x = c[0]
  513. p.y = c[1]
  514. return ['L', c[0], c[1]]
  515. },
  516. H: function(c, p) {
  517. p.x = c[0]
  518. return ['H', c[0]]
  519. },
  520. V: function(c, p) {
  521. p.y = c[0]
  522. return ['V', c[0]]
  523. },
  524. C: function(c, p) {
  525. p.x = c[4]
  526. p.y = c[5]
  527. return ['C', c[0], c[1], c[2], c[3], c[4], c[5]]
  528. },
  529. S: function(c, p) {
  530. p.x = c[2]
  531. p.y = c[3]
  532. return ['S', c[0], c[1], c[2], c[3]]
  533. },
  534. Q: function(c, p) {
  535. p.x = c[2]
  536. p.y = c[3]
  537. return ['Q', c[0], c[1], c[2], c[3]]
  538. },
  539. T: function(c, p) {
  540. p.x = c[0]
  541. p.y = c[1]
  542. return ['T', c[0], c[1]]
  543. },
  544. Z: function(c, p, p0) {
  545. p.x = p0.x
  546. p.y = p0.y
  547. return ['Z']
  548. },
  549. A: function(c, p) {
  550. p.x = c[5]
  551. p.y = c[6]
  552. return ['A', c[0], c[1], c[2], c[3], c[4], c[5], c[6]]
  553. }
  554. }
  555. var mlhvqtcsa = 'mlhvqtcsaz'.split('')
  556. for(var i = 0, il = mlhvqtcsa.length; i < il; ++i){
  557. pathHandlers[mlhvqtcsa[i]] = (function(i){
  558. return function(c, p, p0) {
  559. if(i == 'H') c[0] = c[0] + p.x
  560. else if(i == 'V') c[0] = c[0] + p.y
  561. else if(i == 'A'){
  562. c[5] = c[5] + p.x,
  563. c[6] = c[6] + p.y
  564. }
  565. else
  566. for(var j = 0, jl = c.length; j < jl; ++j) {
  567. c[j] = c[j] + (j%2 ? p.y : p.x)
  568. }
  569. return pathHandlers[i](c, p, p0)
  570. }
  571. })(mlhvqtcsa[i].toUpperCase())
  572. }
  573. // Path points array
  574. SVG.PathArray = function(array, fallback) {
  575. SVG.Array.call(this, array, fallback || [['M', 0, 0]])
  576. }
  577. // Inherit from SVG.Array
  578. SVG.PathArray.prototype = new SVG.Array
  579. SVG.PathArray.prototype.constructor = SVG.PathArray
  580. SVG.extend(SVG.PathArray, {
  581. // Convert array to string
  582. toString: function() {
  583. return arrayToString(this.value)
  584. }
  585. // Move path string
  586. , move: function(x, y) {
  587. // get bounding box of current situation
  588. var box = this.bbox()
  589. // get relative offset
  590. x -= box.x
  591. y -= box.y
  592. if (!isNaN(x) && !isNaN(y)) {
  593. // move every point
  594. for (var l, i = this.value.length - 1; i >= 0; i--) {
  595. l = this.value[i][0]
  596. if (l == 'M' || l == 'L' || l == 'T') {
  597. this.value[i][1] += x
  598. this.value[i][2] += y
  599. } else if (l == 'H') {
  600. this.value[i][1] += x
  601. } else if (l == 'V') {
  602. this.value[i][1] += y
  603. } else if (l == 'C' || l == 'S' || l == 'Q') {
  604. this.value[i][1] += x
  605. this.value[i][2] += y
  606. this.value[i][3] += x
  607. this.value[i][4] += y
  608. if (l == 'C') {
  609. this.value[i][5] += x
  610. this.value[i][6] += y
  611. }
  612. } else if (l == 'A') {
  613. this.value[i][6] += x
  614. this.value[i][7] += y
  615. }
  616. }
  617. }
  618. return this
  619. }
  620. // Resize path string
  621. , size: function(width, height) {
  622. // get bounding box of current situation
  623. var i, l, box = this.bbox()
  624. // recalculate position of all points according to new size
  625. for (i = this.value.length - 1; i >= 0; i--) {
  626. l = this.value[i][0]
  627. if (l == 'M' || l == 'L' || l == 'T') {
  628. this.value[i][1] = ((this.value[i][1] - box.x) * width) / box.width + box.x
  629. this.value[i][2] = ((this.value[i][2] - box.y) * height) / box.height + box.y
  630. } else if (l == 'H') {
  631. this.value[i][1] = ((this.value[i][1] - box.x) * width) / box.width + box.x
  632. } else if (l == 'V') {
  633. this.value[i][1] = ((this.value[i][1] - box.y) * height) / box.height + box.y
  634. } else if (l == 'C' || l == 'S' || l == 'Q') {
  635. this.value[i][1] = ((this.value[i][1] - box.x) * width) / box.width + box.x
  636. this.value[i][2] = ((this.value[i][2] - box.y) * height) / box.height + box.y
  637. this.value[i][3] = ((this.value[i][3] - box.x) * width) / box.width + box.x
  638. this.value[i][4] = ((this.value[i][4] - box.y) * height) / box.height + box.y
  639. if (l == 'C') {
  640. this.value[i][5] = ((this.value[i][5] - box.x) * width) / box.width + box.x
  641. this.value[i][6] = ((this.value[i][6] - box.y) * height) / box.height + box.y
  642. }
  643. } else if (l == 'A') {
  644. // resize radii
  645. this.value[i][1] = (this.value[i][1] * width) / box.width
  646. this.value[i][2] = (this.value[i][2] * height) / box.height
  647. // move position values
  648. this.value[i][6] = ((this.value[i][6] - box.x) * width) / box.width + box.x
  649. this.value[i][7] = ((this.value[i][7] - box.y) * height) / box.height + box.y
  650. }
  651. }
  652. return this
  653. }
  654. // Test if the passed path array use the same path data commands as this path array
  655. , equalCommands: function(pathArray) {
  656. var i, il, equalCommands
  657. pathArray = new SVG.PathArray(pathArray)
  658. equalCommands = this.value.length === pathArray.value.length
  659. for(i = 0, il = this.value.length; equalCommands && i < il; i++) {
  660. equalCommands = this.value[i][0] === pathArray.value[i][0]
  661. }
  662. return equalCommands
  663. }
  664. // Make path array morphable
  665. , morph: function(pathArray) {
  666. pathArray = new SVG.PathArray(pathArray)
  667. if(this.equalCommands(pathArray)) {
  668. this.destination = pathArray
  669. } else {
  670. this.destination = null
  671. }
  672. return this
  673. }
  674. // Get morphed path array at given position
  675. , at: function(pos) {
  676. // make sure a destination is defined
  677. if (!this.destination) return this
  678. var sourceArray = this.value
  679. , destinationArray = this.destination.value
  680. , array = [], pathArray = new SVG.PathArray()
  681. , i, il, j, jl
  682. // Animate has specified in the SVG spec
  683. // See: https://www.w3.org/TR/SVG11/paths.html#PathElement
  684. for (i = 0, il = sourceArray.length; i < il; i++) {
  685. array[i] = [sourceArray[i][0]]
  686. for(j = 1, jl = sourceArray[i].length; j < jl; j++) {
  687. array[i][j] = sourceArray[i][j] + (destinationArray[i][j] - sourceArray[i][j]) * pos
  688. }
  689. // For the two flags of the elliptical arc command, the SVG spec say:
  690. // Flags and booleans are interpolated as fractions between zero and one, with any non-zero value considered to be a value of one/true
  691. // Elliptical arc command as an array followed by corresponding indexes:
  692. // ['A', rx, ry, x-axis-rotation, large-arc-flag, sweep-flag, x, y]
  693. // 0 1 2 3 4 5 6 7
  694. if(array[i][0] === 'A') {
  695. array[i][4] = +(array[i][4] != 0)
  696. array[i][5] = +(array[i][5] != 0)
  697. }
  698. }
  699. // Directly modify the value of a path array, this is done this way for performance
  700. pathArray.value = array
  701. return pathArray
  702. }
  703. // Absolutize and parse path to array
  704. , parse: function(array) {
  705. // if it's already a patharray, no need to parse it
  706. if (array instanceof SVG.PathArray) return array.valueOf()
  707. // prepare for parsing
  708. var i, x0, y0, s, seg, arr
  709. , x = 0
  710. , y = 0
  711. , paramCnt = { 'M':2, 'L':2, 'H':1, 'V':1, 'C':6, 'S':4, 'Q':4, 'T':2, 'A':7, 'Z':0 }
  712. if(typeof array == 'string'){
  713. array = array
  714. .replace(SVG.regex.numbersWithDots, pathRegReplace) // convert 45.123.123 to 45.123 .123
  715. .replace(SVG.regex.pathLetters, ' $& ') // put some room between letters and numbers
  716. .replace(SVG.regex.hyphen, '$1 -') // add space before hyphen
  717. .trim() // trim
  718. .split(SVG.regex.delimiter) // split into array
  719. }else{
  720. array = array.reduce(function(prev, curr){
  721. return [].concat.call(prev, curr)
  722. }, [])
  723. }
  724. // array now is an array containing all parts of a path e.g. ['M', '0', '0', 'L', '30', '30' ...]
  725. var arr = []
  726. , p = new SVG.Point()
  727. , p0 = new SVG.Point()
  728. , index = 0
  729. , len = array.length
  730. do{
  731. // Test if we have a path letter
  732. if(SVG.regex.isPathLetter.test(array[index])){
  733. s = array[index]
  734. ++index
  735. // If last letter was a move command and we got no new, it defaults to [L]ine
  736. }else if(s == 'M'){
  737. s = 'L'
  738. }else if(s == 'm'){
  739. s = 'l'
  740. }
  741. arr.push(pathHandlers[s].call(null,
  742. array.slice(index, (index = index + paramCnt[s.toUpperCase()])).map(parseFloat),
  743. p, p0
  744. )
  745. )
  746. }while(len > index)
  747. return arr
  748. }
  749. // Get bounding box of path
  750. , bbox: function() {
  751. SVG.parser.path.setAttribute('d', this.toString())
  752. return SVG.parser.path.getBBox()
  753. }
  754. })
  755. // Module for unit convertions
  756. SVG.Number = SVG.invent({
  757. // Initialize
  758. create: function(value, unit) {
  759. // initialize defaults
  760. this.value = 0
  761. this.unit = unit || ''
  762. // parse value
  763. if (typeof value === 'number') {
  764. // ensure a valid numeric value
  765. this.value = isNaN(value) ? 0 : !isFinite(value) ? (value < 0 ? -3.4e+38 : +3.4e+38) : value
  766. } else if (typeof value === 'string') {
  767. unit = value.match(SVG.regex.numberAndUnit)
  768. if (unit) {
  769. // make value numeric
  770. this.value = parseFloat(unit[1])
  771. // normalize
  772. if (unit[5] == '%')
  773. this.value /= 100
  774. else if (unit[5] == 's')
  775. this.value *= 1000
  776. // store unit
  777. this.unit = unit[5]
  778. }
  779. } else {
  780. if (value instanceof SVG.Number) {
  781. this.value = value.valueOf()
  782. this.unit = value.unit
  783. }
  784. }
  785. }
  786. // Add methods
  787. , extend: {
  788. // Stringalize
  789. toString: function() {
  790. return (
  791. this.unit == '%' ?
  792. ~~(this.value * 1e8) / 1e6:
  793. this.unit == 's' ?
  794. this.value / 1e3 :
  795. this.value
  796. ) + this.unit
  797. }
  798. , toJSON: function() {
  799. return this.toString()
  800. }
  801. , // Convert to primitive
  802. valueOf: function() {
  803. return this.value
  804. }
  805. // Add number
  806. , plus: function(number) {
  807. number = new SVG.Number(number)
  808. return new SVG.Number(this + number, this.unit || number.unit)
  809. }
  810. // Subtract number
  811. , minus: function(number) {
  812. number = new SVG.Number(number)
  813. return new SVG.Number(this - number, this.unit || number.unit)
  814. }
  815. // Multiply number
  816. , times: function(number) {
  817. number = new SVG.Number(number)
  818. return new SVG.Number(this * number, this.unit || number.unit)
  819. }
  820. // Divide number
  821. , divide: function(number) {
  822. number = new SVG.Number(number)
  823. return new SVG.Number(this / number, this.unit || number.unit)
  824. }
  825. // Convert to different unit
  826. , to: function(unit) {
  827. var number = new SVG.Number(this)
  828. if (typeof unit === 'string')
  829. number.unit = unit
  830. return number
  831. }
  832. // Make number morphable
  833. , morph: function(number) {
  834. this.destination = new SVG.Number(number)
  835. if(number.relative) {
  836. this.destination.value += this.value
  837. }
  838. return this
  839. }
  840. // Get morphed number at given position
  841. , at: function(pos) {
  842. // Make sure a destination is defined
  843. if (!this.destination) return this
  844. // Generate new morphed number
  845. return new SVG.Number(this.destination)
  846. .minus(this)
  847. .times(pos)
  848. .plus(this)
  849. }
  850. }
  851. })
  852. SVG.Element = SVG.invent({
  853. // Initialize node
  854. create: function(node) {
  855. // make stroke value accessible dynamically
  856. this._stroke = SVG.defaults.attrs.stroke
  857. this._event = null
  858. this._events = {}
  859. // initialize data object
  860. this.dom = {}
  861. // create circular reference
  862. if (this.node = node) {
  863. this.type = node.nodeName
  864. this.node.instance = this
  865. this._events = node._events || {}
  866. // store current attribute value
  867. this._stroke = node.getAttribute('stroke') || this._stroke
  868. }
  869. }
  870. // Add class methods
  871. , extend: {
  872. // Move over x-axis
  873. x: function(x) {
  874. return this.attr('x', x)
  875. }
  876. // Move over y-axis
  877. , y: function(y) {
  878. return this.attr('y', y)
  879. }
  880. // Move by center over x-axis
  881. , cx: function(x) {
  882. return x == null ? this.x() + this.width() / 2 : this.x(x - this.width() / 2)
  883. }
  884. // Move by center over y-axis
  885. , cy: function(y) {
  886. return y == null ? this.y() + this.height() / 2 : this.y(y - this.height() / 2)
  887. }
  888. // Move element to given x and y values
  889. , move: function(x, y) {
  890. return this.x(x).y(y)
  891. }
  892. // Move element by its center
  893. , center: function(x, y) {
  894. return this.cx(x).cy(y)
  895. }
  896. // Set width of element
  897. , width: function(width) {
  898. return this.attr('width', width)
  899. }
  900. // Set height of element
  901. , height: function(height) {
  902. return this.attr('height', height)
  903. }
  904. // Set element size to given width and height
  905. , size: function(width, height) {
  906. var p = proportionalSize(this, width, height)
  907. return this
  908. .width(new SVG.Number(p.width))
  909. .height(new SVG.Number(p.height))
  910. }
  911. // Clone element
  912. , clone: function(parent) {
  913. // write dom data to the dom so the clone can pickup the data
  914. this.writeDataToDom()
  915. // clone element and assign new id
  916. var clone = assignNewId(this.node.cloneNode(true))
  917. // insert the clone in the given parent or after myself
  918. if(parent) parent.add(clone)
  919. else this.after(clone)
  920. return clone
  921. }
  922. // Remove element
  923. , remove: function() {
  924. if (this.parent())
  925. this.parent().removeElement(this)
  926. return this
  927. }
  928. // Replace element
  929. , replace: function(element) {
  930. this.after(element).remove()
  931. return element
  932. }
  933. // Add element to given container and return self
  934. , addTo: function(parent) {
  935. return parent.put(this)
  936. }
  937. // Add element to given container and return container
  938. , putIn: function(parent) {
  939. return parent.add(this)
  940. }
  941. // Get / set id
  942. , id: function(id) {
  943. return this.attr('id', id)
  944. }
  945. // Checks whether the given point inside the bounding box of the element
  946. , inside: function(x, y) {
  947. var box = this.bbox()
  948. return x > box.x
  949. && y > box.y
  950. && x < box.x + box.width
  951. && y < box.y + box.height
  952. }
  953. // Show element
  954. , show: function() {
  955. return this.style('display', '')
  956. }
  957. // Hide element
  958. , hide: function() {
  959. return this.style('display', 'none')
  960. }
  961. // Is element visible?
  962. , visible: function() {
  963. return this.style('display') != 'none'
  964. }
  965. // Return id on string conversion
  966. , toString: function() {
  967. return this.attr('id')
  968. }
  969. // Return array of classes on the node
  970. , classes: function() {
  971. var attr = this.attr('class')
  972. return attr == null ? [] : attr.trim().split(SVG.regex.delimiter)
  973. }
  974. // Return true if class exists on the node, false otherwise
  975. , hasClass: function(name) {
  976. return this.classes().indexOf(name) != -1
  977. }
  978. // Add class to the node
  979. , addClass: function(name) {
  980. if (!this.hasClass(name)) {
  981. var array = this.classes()
  982. array.push(name)
  983. this.attr('class', array.join(' '))
  984. }
  985. return this
  986. }
  987. // Remove class from the node
  988. , removeClass: function(name) {
  989. if (this.hasClass(name)) {
  990. this.attr('class', this.classes().filter(function(c) {
  991. return c != name
  992. }).join(' '))
  993. }
  994. return this
  995. }
  996. // Toggle the presence of a class on the node
  997. , toggleClass: function(name) {
  998. return this.hasClass(name) ? this.removeClass(name) : this.addClass(name)
  999. }
  1000. // Get referenced element form attribute value
  1001. , reference: function(attr) {
  1002. return SVG.get(this.attr(attr))
  1003. }
  1004. // Returns the parent element instance
  1005. , parent: function(type) {
  1006. var parent = this
  1007. // check for parent
  1008. if(!parent.node.parentNode) return null
  1009. // get parent element
  1010. parent = SVG.adopt(parent.node.parentNode)
  1011. if(!type) return parent
  1012. // loop trough ancestors if type is given
  1013. while(parent && parent.node instanceof window.SVGElement){
  1014. if(typeof type === 'string' ? parent.matches(type) : parent instanceof type) return parent
  1015. if(!parent.node.parentNode || parent.node.parentNode.nodeName == '#document' || parent.node.parentNode.nodeName == '#document-fragment') return null // #759, #720
  1016. parent = SVG.adopt(parent.node.parentNode)
  1017. }
  1018. }
  1019. // Get parent document
  1020. , doc: function() {
  1021. return this instanceof SVG.Doc ? this : this.parent(SVG.Doc)
  1022. }
  1023. // return array of all ancestors of given type up to the root svg
  1024. , parents: function(type) {
  1025. var parents = [], parent = this
  1026. do{
  1027. parent = parent.parent(type)
  1028. if(!parent || !parent.node) break
  1029. parents.push(parent)
  1030. } while(parent.parent)
  1031. return parents
  1032. }
  1033. // matches the element vs a css selector
  1034. , matches: function(selector){
  1035. return matches(this.node, selector)
  1036. }
  1037. // Returns the svg node to call native svg methods on it
  1038. , native: function() {
  1039. return this.node
  1040. }
  1041. // Import raw svg
  1042. , svg: function(svg) {
  1043. // create temporary holder
  1044. var well = document.createElement('svg')
  1045. // act as a setter if svg is given
  1046. if (svg && this instanceof SVG.Parent) {
  1047. // dump raw svg
  1048. well.innerHTML = '<svg>' + svg.replace(/\n/, '').replace(/<([\w:-]+)([^<]+?)\/>/g, '<$1$2></$1>') + '</svg>'
  1049. // transplant nodes
  1050. for (var i = 0, il = well.firstChild.childNodes.length; i < il; i++)
  1051. this.node.appendChild(well.firstChild.firstChild)
  1052. // otherwise act as a getter
  1053. } else {
  1054. // create a wrapping svg element in case of partial content
  1055. well.appendChild(svg = document.createElement('svg'))
  1056. // write svgjs data to the dom
  1057. this.writeDataToDom()
  1058. // insert a copy of this node
  1059. svg.appendChild(this.node.cloneNode(true))
  1060. // return target element
  1061. return well.innerHTML.replace(/^<svg>/, '').replace(/<\/svg>$/, '')
  1062. }
  1063. return this
  1064. }
  1065. // write svgjs data to the dom
  1066. , writeDataToDom: function() {
  1067. // dump variables recursively
  1068. if(this.each || this.lines){
  1069. var fn = this.each ? this : this.lines();
  1070. fn.each(function(){
  1071. this.writeDataToDom()
  1072. })
  1073. }
  1074. // remove previously set data
  1075. this.node.removeAttribute('svgjs:data')
  1076. if(Object.keys(this.dom).length)
  1077. this.node.setAttribute('svgjs:data', JSON.stringify(this.dom)) // see #428
  1078. return this
  1079. }
  1080. // set given data to the elements data property
  1081. , setData: function(o){
  1082. this.dom = o
  1083. return this
  1084. }
  1085. , is: function(obj){
  1086. return is(this, obj)
  1087. }
  1088. }
  1089. })
  1090. SVG.easing = {
  1091. '-': function(pos){return pos}
  1092. , '<>':function(pos){return -Math.cos(pos * Math.PI) / 2 + 0.5}
  1093. , '>': function(pos){return Math.sin(pos * Math.PI / 2)}
  1094. , '<': function(pos){return -Math.cos(pos * Math.PI / 2) + 1}
  1095. }
  1096. SVG.morph = function(pos){
  1097. return function(from, to) {
  1098. return new SVG.MorphObj(from, to).at(pos)
  1099. }
  1100. }
  1101. SVG.Situation = SVG.invent({
  1102. create: function(o){
  1103. this.init = false
  1104. this.reversed = false
  1105. this.reversing = false
  1106. this.duration = new SVG.Number(o.duration).valueOf()
  1107. this.delay = new SVG.Number(o.delay).valueOf()
  1108. this.start = +new Date() + this.delay
  1109. this.finish = this.start + this.duration
  1110. this.ease = o.ease
  1111. // this.loop is incremented from 0 to this.loops
  1112. // it is also incremented when in an infinite loop (when this.loops is true)
  1113. this.loop = 0
  1114. this.loops = false
  1115. this.animations = {
  1116. // functionToCall: [list of morphable objects]
  1117. // e.g. move: [SVG.Number, SVG.Number]
  1118. }
  1119. this.attrs = {
  1120. // holds all attributes which are not represented from a function svg.js provides
  1121. // e.g. someAttr: SVG.Number
  1122. }
  1123. this.styles = {
  1124. // holds all styles which should be animated
  1125. // e.g. fill-color: SVG.Color
  1126. }
  1127. this.transforms = [
  1128. // holds all transformations as transformation objects
  1129. // e.g. [SVG.Rotate, SVG.Translate, SVG.Matrix]
  1130. ]
  1131. this.once = {
  1132. // functions to fire at a specific position
  1133. // e.g. "0.5": function foo(){}
  1134. }
  1135. }
  1136. })
  1137. SVG.FX = SVG.invent({
  1138. create: function(element) {
  1139. this._target = element
  1140. this.situations = []
  1141. this.active = false
  1142. this.situation = null
  1143. this.paused = false
  1144. this.lastPos = 0
  1145. this.pos = 0
  1146. // The absolute position of an animation is its position in the context of its complete duration (including delay and loops)
  1147. // When performing a delay, absPos is below 0 and when performing a loop, its value is above 1
  1148. this.absPos = 0
  1149. this._speed = 1
  1150. }
  1151. , extend: {
  1152. /**
  1153. * sets or returns the target of this animation
  1154. * @param o object || number In case of Object it holds all parameters. In case of number its the duration of the animation
  1155. * @param ease function || string Function which should be used for easing or easing keyword
  1156. * @param delay Number indicating the delay before the animation starts
  1157. * @return target || this
  1158. */
  1159. animate: function(o, ease, delay){
  1160. if(typeof o == 'object'){
  1161. ease = o.ease
  1162. delay = o.delay
  1163. o = o.duration
  1164. }
  1165. var situation = new SVG.Situation({
  1166. duration: o || 1000,
  1167. delay: delay || 0,
  1168. ease: SVG.easing[ease || '-'] || ease
  1169. })
  1170. this.queue(situation)
  1171. return this
  1172. }
  1173. /**
  1174. * sets a delay before the next element of the queue is called
  1175. * @param delay Duration of delay in milliseconds
  1176. * @return this.target()
  1177. */
  1178. , delay: function(delay){
  1179. // The delay is performed by an empty situation with its duration
  1180. // attribute set to the duration of the delay
  1181. var situation = new SVG.Situation({
  1182. duration: delay,
  1183. delay: 0,
  1184. ease: SVG.easing['-']
  1185. })
  1186. return this.queue(situation)
  1187. }
  1188. /**
  1189. * sets or returns the target of this animation
  1190. * @param null || target SVG.Element which should be set as new target
  1191. * @return target || this
  1192. */
  1193. , target: function(target){
  1194. if(target && target instanceof SVG.Element){
  1195. this._target = target
  1196. return this
  1197. }
  1198. return this._target
  1199. }
  1200. // returns the absolute position at a given time
  1201. , timeToAbsPos: function(timestamp){
  1202. return (timestamp - this.situation.start) / (this.situation.duration/this._speed)
  1203. }
  1204. // returns the timestamp from a given absolute positon
  1205. , absPosToTime: function(absPos){
  1206. return this.situation.duration/this._speed * absPos + this.situation.start
  1207. }
  1208. // starts the animationloop
  1209. , startAnimFrame: function(){
  1210. this.stopAnimFrame()
  1211. this.animationFrame = window.requestAnimationFrame(function(){ this.step() }.bind(this))
  1212. }
  1213. // cancels the animationframe
  1214. , stopAnimFrame: function(){
  1215. window.cancelAnimationFrame(this.animationFrame)
  1216. }
  1217. // kicks off the animation - only does something when the queue is currently not active and at least one situation is set
  1218. , start: function(){
  1219. // dont start if already started
  1220. if(!this.active && this.situation){
  1221. this.active = true
  1222. this.startCurrent()
  1223. }
  1224. return this
  1225. }
  1226. // start the current situation
  1227. , startCurrent: function(){
  1228. this.situation.start = +new Date + this.situation.delay/this._speed
  1229. this.situation.finish = this.situation.start + this.situation.duration/this._speed
  1230. return this.initAnimations().step()
  1231. }
  1232. /**
  1233. * adds a function / Situation to the animation queue
  1234. * @param fn function / situation to add
  1235. * @return this
  1236. */
  1237. , queue: function(fn){
  1238. if(typeof fn == 'function' || fn instanceof SVG.Situation)
  1239. this.situations.push(fn)
  1240. if(!this.situation) this.situation = this.situations.shift()
  1241. return this
  1242. }
  1243. /**
  1244. * pulls next element from the queue and execute it
  1245. * @return this
  1246. */
  1247. , dequeue: function(){
  1248. // stop current animation
  1249. this.stop()
  1250. // get next animation from queue
  1251. this.situation = this.situations.shift()
  1252. if(this.situation){
  1253. if(this.situation instanceof SVG.Situation) {
  1254. this.start()
  1255. } else {
  1256. // If it is not a SVG.Situation, then it is a function, we execute it
  1257. this.situation.call(this)
  1258. }
  1259. }
  1260. return this
  1261. }
  1262. // updates all animations to the current state of the element
  1263. // this is important when one property could be changed from another property
  1264. , initAnimations: function() {
  1265. var i, j, source
  1266. var s = this.situation
  1267. if(s.init) return this
  1268. for(i in s.animations){
  1269. source = this.target()[i]()
  1270. if(!Array.isArray(source)) {
  1271. source = [source]
  1272. }
  1273. if(!Array.isArray(s.animations[i])) {
  1274. s.animations[i] = [s.animations[i]]
  1275. }
  1276. //if(s.animations[i].length > source.length) {
  1277. // source.concat = source.concat(s.animations[i].slice(source.length, s.animations[i].length))
  1278. //}
  1279. for(j = source.length; j--;) {
  1280. // The condition is because some methods return a normal number instead
  1281. // of a SVG.Number
  1282. if(s.animations[i][j] instanceof SVG.Number)
  1283. source[j] = new SVG.Number(source[j])
  1284. s.animations[i][j] = source[j].morph(s.animations[i][j])
  1285. }
  1286. }
  1287. for(i in s.attrs){
  1288. s.attrs[i] = new SVG.MorphObj(this.target().attr(i), s.attrs[i])
  1289. }
  1290. for(i in s.styles){
  1291. s.styles[i] = new SVG.MorphObj(this.target().style(i), s.styles[i])
  1292. }
  1293. s.initialTransformation = this.target().matrixify()
  1294. s.init = true
  1295. return this
  1296. }
  1297. , clearQueue: function(){
  1298. this.situations = []
  1299. return this
  1300. }
  1301. , clearCurrent: function(){
  1302. this.situation = null
  1303. return this
  1304. }
  1305. /** stops the animation immediately
  1306. * @param jumpToEnd A Boolean indicating whether to complete the current animation immediately.
  1307. * @param clearQueue A Boolean indicating whether to remove queued animation as well.
  1308. * @return this
  1309. */
  1310. , stop: function(jumpToEnd, clearQueue){
  1311. var active = this.active
  1312. this.active = false
  1313. if(clearQueue){
  1314. this.clearQueue()
  1315. }
  1316. if(jumpToEnd && this.situation){
  1317. // initialize the situation if it was not
  1318. !active && this.startCurrent()
  1319. this.atEnd()
  1320. }
  1321. this.stopAnimFrame()
  1322. return this.clearCurrent()
  1323. }
  1324. /** resets the element to the state where the current element has started
  1325. * @return this
  1326. */
  1327. , reset: function(){
  1328. if(this.situation){
  1329. var temp = this.situation
  1330. this.stop()
  1331. this.situation = temp
  1332. this.atStart()
  1333. }
  1334. return this
  1335. }
  1336. // Stop the currently-running animation, remove all queued animations, and complete all animations for the element.
  1337. , finish: function(){
  1338. this.stop(true, false)
  1339. while(this.dequeue().situation && this.stop(true, false));
  1340. this.clearQueue().clearCurrent()
  1341. return this
  1342. }
  1343. // set the internal animation pointer at the start position, before any loops, and updates the visualisation
  1344. , atStart: function() {
  1345. return this.at(0, true)
  1346. }
  1347. // set the internal animation pointer at the end position, after all the loops, and updates the visualisation
  1348. , atEnd: function() {
  1349. if (this.situation.loops === true) {
  1350. // If in a infinite loop, we end the current iteration
  1351. this.situation.loops = this.situation.loop + 1
  1352. }
  1353. if(typeof this.situation.loops == 'number') {
  1354. // If performing a finite number of loops, we go after all the loops
  1355. return this.at(this.situation.loops, true)
  1356. } else {
  1357. // If no loops, we just go at the end
  1358. return this.at(1, true)
  1359. }
  1360. }
  1361. // set the internal animation pointer to the specified position and updates the visualisation
  1362. // if isAbsPos is true, pos is treated as an absolute position
  1363. , at: function(pos, isAbsPos){
  1364. var durDivSpd = this.situation.duration/this._speed
  1365. this.absPos = pos
  1366. // If pos is not an absolute position, we convert it into one
  1367. if (!isAbsPos) {
  1368. if (this.situation.reversed) this.absPos = 1 - this.absPos
  1369. this.absPos += this.situation.loop
  1370. }
  1371. this.situation.start = +new Date - this.absPos * durDivSpd
  1372. this.situation.finish = this.situation.start + durDivSpd
  1373. return this.step(true)
  1374. }
  1375. /**
  1376. * sets or returns the speed of the animations
  1377. * @param speed null || Number The new speed of the animations
  1378. * @return Number || this
  1379. */
  1380. , speed: function(speed){
  1381. if (speed === 0) return this.pause()
  1382. if (speed) {
  1383. this._speed = speed
  1384. // We use an absolute position here so that speed can affect the delay before the animation
  1385. return this.at(this.absPos, true)
  1386. } else return this._speed
  1387. }
  1388. // Make loopable
  1389. , loop: function(times, reverse) {
  1390. var c = this.last()
  1391. // store total loops
  1392. c.loops = (times != null) ? times : true
  1393. c.loop = 0
  1394. if(reverse) c.reversing = true
  1395. return this
  1396. }
  1397. // pauses the animation
  1398. , pause: function(){
  1399. this.paused = true
  1400. this.stopAnimFrame()
  1401. return this
  1402. }
  1403. // unpause the animation
  1404. , play: function(){
  1405. if(!this.paused) return this
  1406. this.paused = false
  1407. // We use an absolute position here so that the delay before the animation can be paused
  1408. return this.at(this.absPos, true)
  1409. }
  1410. /**
  1411. * toggle or set the direction of the animation
  1412. * true sets direction to backwards while false sets it to forwards
  1413. * @param reversed Boolean indicating whether to reverse the animation or not (default: toggle the reverse status)
  1414. * @return this
  1415. */
  1416. , reverse: function(reversed){
  1417. var c = this.last()
  1418. if(typeof reversed == 'undefined') c.reversed = !c.reversed
  1419. else c.reversed = reversed
  1420. return this
  1421. }
  1422. /**
  1423. * returns a float from 0-1 indicating the progress of the current animation
  1424. * @param eased Boolean indicating whether the returned position should be eased or not
  1425. * @return number
  1426. */
  1427. , progress: function(easeIt){
  1428. return easeIt ? this.situation.ease(this.pos) : this.pos
  1429. }
  1430. /**
  1431. * adds a callback function which is called when the current animation is finished
  1432. * @param fn Function which should be executed as callback
  1433. * @return number
  1434. */
  1435. , after: function(fn){
  1436. var c = this.last()
  1437. , wrapper = function wrapper(e){
  1438. if(e.detail.situation == c){
  1439. fn.call(this, c)
  1440. this.off('finished.fx', wrapper) // prevent memory leak
  1441. }
  1442. }
  1443. this.target().on('finished.fx', wrapper)
  1444. return this._callStart()
  1445. }
  1446. // adds a callback which is called whenever one animation step is performed
  1447. , during: function(fn){
  1448. var c = this.last()
  1449. , wrapper = function(e){
  1450. if(e.detail.situation == c){
  1451. fn.call(this, e.detail.pos, SVG.morph(e.detail.pos), e.detail.eased, c)
  1452. }
  1453. }
  1454. // see above
  1455. this.target().off('during.fx', wrapper).on('during.fx', wrapper)
  1456. this.after(function(){
  1457. this.off('during.fx', wrapper)
  1458. })
  1459. return this._callStart()
  1460. }
  1461. // calls after ALL animations in the queue are finished
  1462. , afterAll: function(fn){
  1463. var wrapper = function wrapper(e){
  1464. fn.call(this)
  1465. this.off('allfinished.fx', wrapper)
  1466. }
  1467. // see above
  1468. this.target().off('allfinished.fx', wrapper).on('allfinished.fx', wrapper)
  1469. return this._callStart()
  1470. }
  1471. // calls on every animation step for all animations
  1472. , duringAll: function(fn){
  1473. var wrapper = function(e){
  1474. fn.call(this, e.detail.pos, SVG.morph(e.detail.pos), e.detail.eased, e.detail.situation)
  1475. }
  1476. this.target().off('during.fx', wrapper).on('during.fx', wrapper)
  1477. this.afterAll(function(){
  1478. this.off('during.fx', wrapper)
  1479. })
  1480. return this._callStart()
  1481. }
  1482. , last: function(){
  1483. return this.situations.length ? this.situations[this.situations.length-1] : this.situation
  1484. }
  1485. // adds one property to the animations
  1486. , add: function(method, args, type){
  1487. this.last()[type || 'animations'][method] = args
  1488. return this._callStart()
  1489. }
  1490. /** perform one step of the animation
  1491. * @param ignoreTime Boolean indicating whether to ignore time and use position directly or recalculate position based on time
  1492. * @return this
  1493. */
  1494. , step: function(ignoreTime){
  1495. // convert current time to an absolute position
  1496. if(!ignoreTime) this.absPos = this.timeToAbsPos(+new Date)
  1497. // This part convert an absolute position to a position
  1498. if(this.situation.loops !== false) {
  1499. var absPos, absPosInt, lastLoop
  1500. // If the absolute position is below 0, we just treat it as if it was 0
  1501. absPos = Math.max(this.absPos, 0)
  1502. absPosInt = Math.floor(absPos)
  1503. if(this.situation.loops === true || absPosInt < this.situation.loops) {
  1504. this.pos = absPos - absPosInt
  1505. lastLoop = this.situation.loop
  1506. this.situation.loop = absPosInt
  1507. } else {
  1508. this.absPos = this.situation.loops
  1509. this.pos = 1
  1510. // The -1 here is because we don't want to toggle reversed when all the loops have been completed
  1511. lastLoop = this.situation.loop - 1
  1512. this.situation.loop = this.situation.loops
  1513. }
  1514. if(this.situation.reversing) {
  1515. // Toggle reversed if an odd number of loops as occured since the last call of step
  1516. this.situation.reversed = this.situation.reversed != Boolean((this.situation.loop - lastLoop) % 2)
  1517. }
  1518. } else {
  1519. // If there are no loop, the absolute position must not be above 1
  1520. this.absPos = Math.min(this.absPos, 1)
  1521. this.pos = this.absPos
  1522. }
  1523. // while the absolute position can be below 0, the position must not be below 0
  1524. if(this.pos < 0) this.pos = 0
  1525. if(this.situation.reversed) this.pos = 1 - this.pos
  1526. // apply easing
  1527. var eased = this.situation.ease(this.pos)
  1528. // call once-callbacks
  1529. for(var i in this.situation.once){
  1530. if(i > this.lastPos && i <= eased){
  1531. this.situation.once[i].call(this.target(), this.pos, eased)
  1532. delete this.situation.once[i]
  1533. }
  1534. }
  1535. // fire during callback with position, eased position and current situation as parameter
  1536. if(this.active) this.target().fire('during', {pos: this.pos, eased: eased, fx: this, situation: this.situation})
  1537. // the user may call stop or finish in the during callback
  1538. // so make sure that we still have a valid situation
  1539. if(!this.situation){
  1540. return this
  1541. }
  1542. // apply the actual animation to every property
  1543. this.eachAt()
  1544. // do final code when situation is finished
  1545. if((this.pos == 1 && !this.situation.reversed) || (this.situation.reversed && this.pos == 0)){
  1546. // stop animation callback
  1547. this.stopAnimFrame()
  1548. // fire finished callback with current situation as parameter
  1549. this.target().fire('finished', {fx:this, situation: this.situation})
  1550. if(!this.situations.length){
  1551. this.target().fire('allfinished')
  1552. // Recheck the length since the user may call animate in the afterAll callback
  1553. if(!this.situations.length){
  1554. this.target().off('.fx') // there shouldnt be any binding left, but to make sure...
  1555. this.active = false
  1556. }
  1557. }
  1558. // start next animation
  1559. if(this.active) this.dequeue()
  1560. else this.clearCurrent()
  1561. }else if(!this.paused && this.active){
  1562. // we continue animating when we are not at the end
  1563. this.startAnimFrame()
  1564. }
  1565. // save last eased position for once callback triggering
  1566. this.lastPos = eased
  1567. return this
  1568. }
  1569. // calculates the step for every property and calls block with it
  1570. , eachAt: function(){
  1571. var i, len, at, self = this, target = this.target(), s = this.situation
  1572. // apply animations which can be called trough a method
  1573. for(i in s.animations){
  1574. at = [].concat(s.animations[i]).map(function(el){
  1575. return typeof el !== 'string' && el.at ? el.at(s.ease(self.pos), self.pos) : el
  1576. })
  1577. target[i].apply(target, at)
  1578. }
  1579. // apply animation which has to be applied with attr()
  1580. for(i in s.attrs){
  1581. at = [i].concat(s.attrs[i]).map(function(el){
  1582. return typeof el !== 'string' && el.at ? el.at(s.ease(self.pos), self.pos) : el
  1583. })
  1584. target.attr.apply(target, at)
  1585. }
  1586. // apply animation which has to be applied with style()
  1587. for(i in s.styles){
  1588. at = [i].concat(s.styles[i]).map(function(el){
  1589. return typeof el !== 'string' && el.at ? el.at(s.ease(self.pos), self.pos) : el
  1590. })
  1591. target.style.apply(target, at)
  1592. }
  1593. // animate initialTransformation which has to be chained
  1594. if(s.transforms.length){
  1595. // get initial initialTransformation
  1596. at = s.initialTransformation
  1597. for(i = 0, len = s.transforms.length; i < len; i++){
  1598. // get next transformation in chain
  1599. var a = s.transforms[i]
  1600. // multiply matrix directly
  1601. if(a instanceof SVG.Matrix){
  1602. if(a.relative){
  1603. at = at.multiply(new SVG.Matrix().morph(a).at(s.ease(this.pos)))
  1604. }else{
  1605. at = at.morph(a).at(s.ease(this.pos))
  1606. }
  1607. continue
  1608. }
  1609. // when transformation is absolute we have to reset the needed transformation first
  1610. if(!a.relative)
  1611. a.undo(at.extract())
  1612. // and reapply it after
  1613. at = at.multiply(a.at(s.ease(this.pos)))
  1614. }
  1615. // set new matrix on element
  1616. target.matrix(at)
  1617. }
  1618. return this
  1619. }
  1620. // adds an once-callback which is called at a specific position and never again
  1621. , once: function(pos, fn, isEased){
  1622. var c = this.last()
  1623. if(!isEased) pos = c.ease(pos)
  1624. c.once[pos] = fn
  1625. return this
  1626. }
  1627. , _callStart: function() {
  1628. setTimeout(function(){this.start()}.bind(this), 0)
  1629. return this
  1630. }
  1631. }
  1632. , parent: SVG.Element
  1633. // Add method to parent elements
  1634. , construct: {
  1635. // Get fx module or create a new one, then animate with given duration and ease
  1636. animate: function(o, ease, delay) {
  1637. return (this.fx || (this.fx = new SVG.FX(this))).animate(o, ease, delay)
  1638. }
  1639. , delay: function(delay){
  1640. return (this.fx || (this.fx = new SVG.FX(this))).delay(delay)
  1641. }
  1642. , stop: function(jumpToEnd, clearQueue) {
  1643. if (this.fx)
  1644. this.fx.stop(jumpToEnd, clearQueue)
  1645. return this
  1646. }
  1647. , finish: function() {
  1648. if (this.fx)
  1649. this.fx.finish()
  1650. return this
  1651. }
  1652. // Pause current animation
  1653. , pause: function() {
  1654. if (this.fx)
  1655. this.fx.pause()
  1656. return this
  1657. }
  1658. // Play paused current animation
  1659. , play: function() {
  1660. if (this.fx)
  1661. this.fx.play()
  1662. return this
  1663. }
  1664. // Set/Get the speed of the animations
  1665. , speed: function(speed) {
  1666. if (this.fx)
  1667. if (speed == null)
  1668. return this.fx.speed()
  1669. else
  1670. this.fx.speed(speed)
  1671. return this
  1672. }
  1673. }
  1674. })
  1675. // MorphObj is used whenever no morphable object is given
  1676. SVG.MorphObj = SVG.invent({
  1677. create: function(from, to){
  1678. // prepare color for morphing
  1679. if(SVG.Color.isColor(to)) return new SVG.Color(from).morph(to)
  1680. // check if we have a list of values
  1681. if(SVG.regex.delimiter.test(from)) {
  1682. // prepare path for morphing
  1683. if(SVG.regex.pathLetters.test(from)) return new SVG.PathArray(from).morph(to)
  1684. // prepare value list for morphing
  1685. else return new SVG.Array(from).morph(to)
  1686. }
  1687. // prepare number for morphing
  1688. if(SVG.regex.numberAndUnit.test(to)) return new SVG.Number(from).morph(to)
  1689. // prepare for plain morphing
  1690. this.value = from
  1691. this.destination = to
  1692. }
  1693. , extend: {
  1694. at: function(pos, real){
  1695. return real < 1 ? this.value : this.destination
  1696. },
  1697. valueOf: function(){
  1698. return this.value
  1699. }
  1700. }
  1701. })
  1702. SVG.extend(SVG.FX, {
  1703. // Add animatable attributes
  1704. attr: function(a, v, relative) {
  1705. // apply attributes individually
  1706. if (typeof a == 'object') {
  1707. for (var key in a)
  1708. this.attr(key, a[key])
  1709. } else {
  1710. this.add(a, v, 'attrs')
  1711. }
  1712. return this
  1713. }
  1714. // Add animatable styles
  1715. , style: function(s, v) {
  1716. if (typeof s == 'object')
  1717. for (var key in s)
  1718. this.style(key, s[key])
  1719. else
  1720. this.add(s, v, 'styles')
  1721. return this
  1722. }
  1723. // Animatable x-axis
  1724. , x: function(x, relative) {
  1725. if(this.target() instanceof SVG.G){
  1726. this.transform({x:x}, relative)
  1727. return this
  1728. }
  1729. var num = new SVG.Number(x)
  1730. num.relative = relative
  1731. return this.add('x', num)
  1732. }
  1733. // Animatable y-axis
  1734. , y: function(y, relative) {
  1735. if(this.target() instanceof SVG.G){
  1736. this.transform({y:y}, relative)
  1737. return this
  1738. }
  1739. var num = new SVG.Number(y)
  1740. num.relative = relative
  1741. return this.add('y', num)
  1742. }
  1743. // Animatable center x-axis
  1744. , cx: function(x) {
  1745. return this.add('cx', new SVG.Number(x))
  1746. }
  1747. // Animatable center y-axis
  1748. , cy: function(y) {
  1749. return this.add('cy', new SVG.Number(y))
  1750. }
  1751. // Add animatable move
  1752. , move: function(x, y) {
  1753. return this.x(x).y(y)
  1754. }
  1755. // Add animatable center
  1756. , center: function(x, y) {
  1757. return this.cx(x).cy(y)
  1758. }
  1759. // Add animatable size
  1760. , size: function(width, height) {
  1761. if (this.target() instanceof SVG.Text) {
  1762. // animate font size for Text elements
  1763. this.attr('font-size', width)
  1764. } else {
  1765. // animate bbox based size for all other elements
  1766. var box
  1767. if(!width || !height){
  1768. box = this.target().bbox()
  1769. }
  1770. if(!width){
  1771. width = box.width / box.height * height
  1772. }
  1773. if(!height){
  1774. height = box.height / box.width * width
  1775. }
  1776. this.add('width' , new SVG.Number(width))
  1777. .add('height', new SVG.Number(height))
  1778. }
  1779. return this
  1780. }
  1781. // Add animatable width
  1782. , width: function(width) {
  1783. return this.add('width', new SVG.Number(width))
  1784. }
  1785. // Add animatable height
  1786. , height: function(height) {
  1787. return this.add('height', new SVG.Number(height))
  1788. }
  1789. // Add animatable plot
  1790. , plot: function(a, b, c, d) {
  1791. // Lines can be plotted with 4 arguments
  1792. if(arguments.length == 4) {
  1793. return this.plot([a, b, c, d])
  1794. }
  1795. return this.add('plot', new (this.target().morphArray)(a))
  1796. }
  1797. // Add leading method
  1798. , leading: function(value) {
  1799. return this.target().leading ?
  1800. this.add('leading', new SVG.Number(value)) :
  1801. this
  1802. }
  1803. // Add animatable viewbox
  1804. , viewbox: function(x, y, width, height) {
  1805. if (this.target() instanceof SVG.Container) {
  1806. this.add('viewbox', new SVG.ViewBox(x, y, width, height))
  1807. }
  1808. return this
  1809. }
  1810. , update: function(o) {
  1811. if (this.target() instanceof SVG.Stop) {
  1812. if (typeof o == 'number' || o instanceof SVG.Number) {
  1813. return this.update({
  1814. offset: arguments[0]
  1815. , color: arguments[1]
  1816. , opacity: arguments[2]
  1817. })
  1818. }
  1819. if (o.opacity != null) this.attr('stop-opacity', o.opacity)
  1820. if (o.color != null) this.attr('stop-color', o.color)
  1821. if (o.offset != null) this.attr('offset', o.offset)
  1822. }
  1823. return this
  1824. }
  1825. })
  1826. SVG.Box = SVG.invent({
  1827. create: function(x, y, width, height) {
  1828. if (typeof x == 'object' && !(x instanceof SVG.Element)) {
  1829. // chromes getBoundingClientRect has no x and y property
  1830. return SVG.Box.call(this, x.left != null ? x.left : x.x , x.top != null ? x.top : x.y, x.width, x.height)
  1831. } else if (arguments.length == 4) {
  1832. this.x = x
  1833. this.y = y
  1834. this.width = width
  1835. this.height = height
  1836. }
  1837. // add center, right, bottom...
  1838. fullBox(this)
  1839. }
  1840. , extend: {
  1841. // Merge rect box with another, return a new instance
  1842. merge: function(box) {
  1843. var b = new this.constructor()
  1844. // merge boxes
  1845. b.x = Math.min(this.x, box.x)
  1846. b.y = Math.min(this.y, box.y)
  1847. b.width = Math.max(this.x + this.width, box.x + box.width) - b.x
  1848. b.height = Math.max(this.y + this.height, box.y + box.height) - b.y
  1849. return fullBox(b)
  1850. }
  1851. , transform: function(m) {
  1852. var xMin = Infinity, xMax = -Infinity, yMin = Infinity, yMax = -Infinity, p, bbox
  1853. var pts = [
  1854. new SVG.Point(this.x, this.y),
  1855. new SVG.Point(this.x2, this.y),
  1856. new SVG.Point(this.x, this.y2),
  1857. new SVG.Point(this.x2, this.y2)
  1858. ]
  1859. pts.forEach(function(p) {
  1860. p = p.transform(m)
  1861. xMin = Math.min(xMin,p.x)
  1862. xMax = Math.max(xMax,p.x)
  1863. yMin = Math.min(yMin,p.y)
  1864. yMax = Math.max(yMax,p.y)
  1865. })
  1866. bbox = new this.constructor()
  1867. bbox.x = xMin
  1868. bbox.width = xMax-xMin
  1869. bbox.y = yMin
  1870. bbox.height = yMax-yMin
  1871. fullBox(bbox)
  1872. return bbox
  1873. }
  1874. }
  1875. })
  1876. SVG.BBox = SVG.invent({
  1877. // Initialize
  1878. create: function(element) {
  1879. SVG.Box.apply(this, [].slice.call(arguments))
  1880. // get values if element is given
  1881. if (element instanceof SVG.Element) {
  1882. var box
  1883. // yes this is ugly, but Firefox can be a pain when it comes to elements that are not yet rendered
  1884. try {
  1885. if (!document.documentElement.contains){
  1886. // This is IE - it does not support contains() for top-level SVGs
  1887. var topParent = element.node
  1888. while (topParent.parentNode){
  1889. topParent = topParent.parentNode
  1890. }
  1891. if (topParent != document) throw new Exception('Element not in the dom')
  1892. } else {
  1893. // the element is NOT in the dom, throw error
  1894. if(!document.documentElement.contains(element.node)) throw new Exception('Element not in the dom')
  1895. }
  1896. // find native bbox
  1897. box = element.node.getBBox()
  1898. } catch(e) {
  1899. if(element instanceof SVG.Shape){
  1900. var clone = element.clone(SVG.parser.draw.instance).show()
  1901. box = clone.node.getBBox()
  1902. clone.remove()
  1903. }else{
  1904. box = {
  1905. x: element.node.clientLeft
  1906. , y: element.node.clientTop
  1907. , width: element.node.clientWidth
  1908. , height: element.node.clientHeight
  1909. }
  1910. }
  1911. }
  1912. SVG.Box.call(this, box)
  1913. }
  1914. }
  1915. // Define ancestor
  1916. , inherit: SVG.Box
  1917. // Define Parent
  1918. , parent: SVG.Element
  1919. // Constructor
  1920. , construct: {
  1921. // Get bounding box
  1922. bbox: function() {
  1923. return new SVG.BBox(this)
  1924. }
  1925. }
  1926. })
  1927. SVG.BBox.prototype.constructor = SVG.BBox
  1928. SVG.extend(SVG.Element, {
  1929. tbox: function(){
  1930. console.warn('Use of TBox is deprecated and mapped to RBox. Use .rbox() instead.')
  1931. return this.rbox(this.doc())
  1932. }
  1933. })
  1934. SVG.RBox = SVG.invent({
  1935. // Initialize
  1936. create: function(element) {
  1937. SVG.Box.apply(this, [].slice.call(arguments))
  1938. if (element instanceof SVG.Element) {
  1939. SVG.Box.call(this, element.node.getBoundingClientRect())
  1940. }
  1941. }
  1942. , inherit: SVG.Box
  1943. // define Parent
  1944. , parent: SVG.Element
  1945. , extend: {
  1946. addOffset: function() {
  1947. // offset by window scroll position, because getBoundingClientRect changes when window is scrolled
  1948. this.x += window.pageXOffset
  1949. this.y += window.pageYOffset
  1950. return this
  1951. }
  1952. }
  1953. // Constructor
  1954. , construct: {
  1955. // Get rect box
  1956. rbox: function(el) {
  1957. if (el) return new SVG.RBox(this).transform(el.screenCTM().inverse())
  1958. return new SVG.RBox(this).addOffset()
  1959. }
  1960. }
  1961. })
  1962. SVG.RBox.prototype.constructor = SVG.RBox
  1963. SVG.Matrix = SVG.invent({
  1964. // Initialize
  1965. create: function(source) {
  1966. var i, base = arrayToMatrix([1, 0, 0, 1, 0, 0])
  1967. // ensure source as object
  1968. source = source instanceof SVG.Element ?
  1969. source.matrixify() :
  1970. typeof source === 'string' ?
  1971. arrayToMatrix(source.split(SVG.regex.delimiter).map(parseFloat)) :
  1972. arguments.length == 6 ?
  1973. arrayToMatrix([].slice.call(arguments)) :
  1974. Array.isArray(source) ?
  1975. arrayToMatrix(source) :
  1976. typeof source === 'object' ?
  1977. source : base
  1978. // merge source
  1979. for (i = abcdef.length - 1; i >= 0; --i)
  1980. this[abcdef[i]] = source[abcdef[i]] != null ?
  1981. source[abcdef[i]] : base[abcdef[i]]
  1982. }
  1983. // Add methods
  1984. , extend: {
  1985. // Extract individual transformations
  1986. extract: function() {
  1987. // find delta transform points
  1988. var px = deltaTransformPoint(this, 0, 1)
  1989. , py = deltaTransformPoint(this, 1, 0)
  1990. , skewX = 180 / Math.PI * Math.atan2(px.y, px.x) - 90
  1991. return {
  1992. // translation
  1993. x: this.e
  1994. , y: this.f
  1995. , transformedX:(this.e * Math.cos(skewX * Math.PI / 180) + this.f * Math.sin(skewX * Math.PI / 180)) / Math.sqrt(this.a * this.a + this.b * this.b)
  1996. , transformedY:(this.f * Math.cos(skewX * Math.PI / 180) + this.e * Math.sin(-skewX * Math.PI / 180)) / Math.sqrt(this.c * this.c + this.d * this.d)
  1997. // skew
  1998. , skewX: -skewX
  1999. , skewY: 180 / Math.PI * Math.atan2(py.y, py.x)
  2000. // scale
  2001. , scaleX: Math.sqrt(this.a * this.a + this.b * this.b)
  2002. , scaleY: Math.sqrt(this.c * this.c + this.d * this.d)
  2003. // rotation
  2004. , rotation: skewX
  2005. , a: this.a
  2006. , b: this.b
  2007. , c: this.c
  2008. , d: this.d
  2009. , e: this.e
  2010. , f: this.f
  2011. , matrix: new SVG.Matrix(this)
  2012. }
  2013. }
  2014. // Clone matrix
  2015. , clone: function() {
  2016. return new SVG.Matrix(this)
  2017. }
  2018. // Morph one matrix into another
  2019. , morph: function(matrix) {
  2020. // store new destination
  2021. this.destination = new SVG.Matrix(matrix)
  2022. return this
  2023. }
  2024. // Get morphed matrix at a given position
  2025. , at: function(pos) {
  2026. // make sure a destination is defined
  2027. if (!this.destination) return this
  2028. // calculate morphed matrix at a given position
  2029. var matrix = new SVG.Matrix({
  2030. a: this.a + (this.destination.a - this.a) * pos
  2031. , b: this.b + (this.destination.b - this.b) * pos
  2032. , c: this.c + (this.destination.c - this.c) * pos
  2033. , d: this.d + (this.destination.d - this.d) * pos
  2034. , e: this.e + (this.destination.e - this.e) * pos
  2035. , f: this.f + (this.destination.f - this.f) * pos
  2036. })
  2037. return matrix
  2038. }
  2039. // Multiplies by given matrix
  2040. , multiply: function(matrix) {
  2041. return new SVG.Matrix(this.native().multiply(parseMatrix(matrix).native()))
  2042. }
  2043. // Inverses matrix
  2044. , inverse: function() {
  2045. return new SVG.Matrix(this.native().inverse())
  2046. }
  2047. // Translate matrix
  2048. , translate: function(x, y) {
  2049. return new SVG.Matrix(this.native().translate(x || 0, y || 0))
  2050. }
  2051. // Scale matrix
  2052. , scale: function(x, y, cx, cy) {
  2053. // support uniformal scale
  2054. if (arguments.length == 1) {
  2055. y = x
  2056. } else if (arguments.length == 3) {
  2057. cy = cx
  2058. cx = y
  2059. y = x
  2060. }
  2061. return this.around(cx, cy, new SVG.Matrix(x, 0, 0, y, 0, 0))
  2062. }
  2063. // Rotate matrix
  2064. , rotate: function(r, cx, cy) {
  2065. // convert degrees to radians
  2066. r = SVG.utils.radians(r)
  2067. return this.around(cx, cy, new SVG.Matrix(Math.cos(r), Math.sin(r), -Math.sin(r), Math.cos(r), 0, 0))
  2068. }
  2069. // Flip matrix on x or y, at a given offset
  2070. , flip: function(a, o) {
  2071. return a == 'x' ?
  2072. this.scale(-1, 1, o, 0) :
  2073. a == 'y' ?
  2074. this.scale(1, -1, 0, o) :
  2075. this.scale(-1, -1, a, o != null ? o : a)
  2076. }
  2077. // Skew
  2078. , skew: function(x, y, cx, cy) {
  2079. // support uniformal skew
  2080. if (arguments.length == 1) {
  2081. y = x
  2082. } else if (arguments.length == 3) {
  2083. cy = cx
  2084. cx = y
  2085. y = x
  2086. }
  2087. // convert degrees to radians
  2088. x = SVG.utils.radians(x)
  2089. y = SVG.utils.radians(y)
  2090. return this.around(cx, cy, new SVG.Matrix(1, Math.tan(y), Math.tan(x), 1, 0, 0))
  2091. }
  2092. // SkewX
  2093. , skewX: function(x, cx, cy) {
  2094. return this.skew(x, 0, cx, cy)
  2095. }
  2096. // SkewY
  2097. , skewY: function(y, cx, cy) {
  2098. return this.skew(0, y, cx, cy)
  2099. }
  2100. // Transform around a center point
  2101. , around: function(cx, cy, matrix) {
  2102. return this
  2103. .multiply(new SVG.Matrix(1, 0, 0, 1, cx || 0, cy || 0))
  2104. .multiply(matrix)
  2105. .multiply(new SVG.Matrix(1, 0, 0, 1, -cx || 0, -cy || 0))
  2106. }
  2107. // Convert to native SVGMatrix
  2108. , native: function() {
  2109. // create new matrix
  2110. var matrix = SVG.parser.native.createSVGMatrix()
  2111. // update with current values
  2112. for (var i = abcdef.length - 1; i >= 0; i--)
  2113. matrix[abcdef[i]] = this[abcdef[i]]
  2114. return matrix
  2115. }
  2116. // Convert matrix to string
  2117. , toString: function() {
  2118. // Construct the matrix directly, avoid values that are too small
  2119. return 'matrix(' + float32String(this.a) + ',' + float32String(this.b)
  2120. + ',' + float32String(this.c) + ',' + float32String(this.d)
  2121. + ',' + float32String(this.e) + ',' + float32String(this.f)
  2122. + ')'
  2123. }
  2124. }
  2125. // Define parent
  2126. , parent: SVG.Element
  2127. // Add parent method
  2128. , construct: {
  2129. // Get current matrix
  2130. ctm: function() {
  2131. return new SVG.Matrix(this.node.getCTM())
  2132. },
  2133. // Get current screen matrix
  2134. screenCTM: function() {
  2135. /* https://bugzilla.mozilla.org/show_bug.cgi?id=1344537
  2136. This is needed because FF does not return the transformation matrix
  2137. for the inner coordinate system when getScreenCTM() is called on nested svgs.
  2138. However all other Browsers do that */
  2139. if(this instanceof SVG.Nested) {
  2140. var rect = this.rect(1,1)
  2141. var m = rect.node.getScreenCTM()
  2142. rect.remove()
  2143. return new SVG.Matrix(m)
  2144. }
  2145. return new SVG.Matrix(this.node.getScreenCTM())
  2146. }
  2147. }
  2148. })
  2149. SVG.Point = SVG.invent({
  2150. // Initialize
  2151. create: function(x,y) {
  2152. var i, source, base = {x:0, y:0}
  2153. // ensure source as object
  2154. source = Array.isArray(x) ?
  2155. {x:x[0], y:x[1]} :
  2156. typeof x === 'object' ?
  2157. {x:x.x, y:x.y} :
  2158. x != null ?
  2159. {x:x, y:(y != null ? y : x)} : base // If y has no value, then x is used has its value
  2160. // merge source
  2161. this.x = source.x
  2162. this.y = source.y
  2163. }
  2164. // Add methods
  2165. , extend: {
  2166. // Clone point
  2167. clone: function() {
  2168. return new SVG.Point(this)
  2169. }
  2170. // Morph one point into another
  2171. , morph: function(x, y) {
  2172. // store new destination
  2173. this.destination = new SVG.Point(x, y)
  2174. return this
  2175. }
  2176. // Get morphed point at a given position
  2177. , at: function(pos) {
  2178. // make sure a destination is defined
  2179. if (!this.destination) return this
  2180. // calculate morphed matrix at a given position
  2181. var point = new SVG.Point({
  2182. x: this.x + (this.destination.x - this.x) * pos
  2183. , y: this.y + (this.destination.y - this.y) * pos
  2184. })
  2185. return point
  2186. }
  2187. // Convert to native SVGPoint
  2188. , native: function() {
  2189. // create new point
  2190. var point = SVG.parser.native.createSVGPoint()
  2191. // update with current values
  2192. point.x = this.x
  2193. point.y = this.y
  2194. return point
  2195. }
  2196. // transform point with matrix
  2197. , transform: function(matrix) {
  2198. return new SVG.Point(this.native().matrixTransform(matrix.native()))
  2199. }
  2200. }
  2201. })
  2202. SVG.extend(SVG.Element, {
  2203. // Get point
  2204. point: function(x, y) {
  2205. return new SVG.Point(x,y).transform(this.screenCTM().inverse());
  2206. }
  2207. })
  2208. SVG.extend(SVG.Element, {
  2209. // Set svg element attribute
  2210. attr: function(a, v, n) {
  2211. // act as full getter
  2212. if (a == null) {
  2213. // get an object of attributes
  2214. a = {}
  2215. v = this.node.attributes
  2216. for (n = v.length - 1; n >= 0; n--)
  2217. a[v[n].nodeName] = SVG.regex.isNumber.test(v[n].nodeValue) ? parseFloat(v[n].nodeValue) : v[n].nodeValue
  2218. return a
  2219. } else if (typeof a == 'object') {
  2220. // apply every attribute individually if an object is passed
  2221. for (v in a) this.attr(v, a[v])
  2222. } else if (v === null) {
  2223. // remove value
  2224. this.node.removeAttribute(a)
  2225. } else if (v == null) {
  2226. // act as a getter if the first and only argument is not an object
  2227. v = this.node.getAttribute(a)
  2228. return v == null ?
  2229. SVG.defaults.attrs[a] :
  2230. SVG.regex.isNumber.test(v) ?
  2231. parseFloat(v) : v
  2232. } else {
  2233. // BUG FIX: some browsers will render a stroke if a color is given even though stroke width is 0
  2234. if (a == 'stroke-width')
  2235. this.attr('stroke', parseFloat(v) > 0 ? this._stroke : null)
  2236. else if (a == 'stroke')
  2237. this._stroke = v
  2238. // convert image fill and stroke to patterns
  2239. if (a == 'fill' || a == 'stroke') {
  2240. if (SVG.regex.isImage.test(v))
  2241. v = this.doc().defs().image(v, 0, 0)
  2242. if (v instanceof SVG.Image)
  2243. v = this.doc().defs().pattern(0, 0, function() {
  2244. this.add(v)
  2245. })
  2246. }
  2247. // ensure correct numeric values (also accepts NaN and Infinity)
  2248. if (typeof v === 'number')
  2249. v = new SVG.Number(v)
  2250. // ensure full hex color
  2251. else if (SVG.Color.isColor(v))
  2252. v = new SVG.Color(v)
  2253. // parse array values
  2254. else if (Array.isArray(v))
  2255. v = new SVG.Array(v)
  2256. // if the passed attribute is leading...
  2257. if (a == 'leading') {
  2258. // ... call the leading method instead
  2259. if (this.leading)
  2260. this.leading(v)
  2261. } else {
  2262. // set given attribute on node
  2263. typeof n === 'string' ?
  2264. this.node.setAttributeNS(n, a, v.toString()) :
  2265. this.node.setAttribute(a, v.toString())
  2266. }
  2267. // rebuild if required
  2268. if (this.rebuild && (a == 'font-size' || a == 'x'))
  2269. this.rebuild(a, v)
  2270. }
  2271. return this
  2272. }
  2273. })
  2274. SVG.extend(SVG.Element, {
  2275. // Add transformations
  2276. transform: function(o, relative) {
  2277. // get target in case of the fx module, otherwise reference this
  2278. var target = this
  2279. , matrix, bbox
  2280. // act as a getter
  2281. if (typeof o !== 'object') {
  2282. // get current matrix
  2283. matrix = new SVG.Matrix(target).extract()
  2284. return typeof o === 'string' ? matrix[o] : matrix
  2285. }
  2286. // get current matrix
  2287. matrix = new SVG.Matrix(target)
  2288. // ensure relative flag
  2289. relative = !!relative || !!o.relative
  2290. // act on matrix
  2291. if (o.a != null) {
  2292. matrix = relative ?
  2293. // relative
  2294. matrix.multiply(new SVG.Matrix(o)) :
  2295. // absolute
  2296. new SVG.Matrix(o)
  2297. // act on rotation
  2298. } else if (o.rotation != null) {
  2299. // ensure centre point
  2300. ensureCentre(o, target)
  2301. // apply transformation
  2302. matrix = relative ?
  2303. // relative
  2304. matrix.rotate(o.rotation, o.cx, o.cy) :
  2305. // absolute
  2306. matrix.rotate(o.rotation - matrix.extract().rotation, o.cx, o.cy)
  2307. // act on scale
  2308. } else if (o.scale != null || o.scaleX != null || o.scaleY != null) {
  2309. // ensure centre point
  2310. ensureCentre(o, target)
  2311. // ensure scale values on both axes
  2312. o.scaleX = o.scale != null ? o.scale : o.scaleX != null ? o.scaleX : 1
  2313. o.scaleY = o.scale != null ? o.scale : o.scaleY != null ? o.scaleY : 1
  2314. if (!relative) {
  2315. // absolute; multiply inversed values
  2316. var e = matrix.extract()
  2317. o.scaleX = o.scaleX * 1 / e.scaleX
  2318. o.scaleY = o.scaleY * 1 / e.scaleY
  2319. }
  2320. matrix = matrix.scale(o.scaleX, o.scaleY, o.cx, o.cy)
  2321. // act on skew
  2322. } else if (o.skew != null || o.skewX != null || o.skewY != null) {
  2323. // ensure centre point
  2324. ensureCentre(o, target)
  2325. // ensure skew values on both axes
  2326. o.skewX = o.skew != null ? o.skew : o.skewX != null ? o.skewX : 0
  2327. o.skewY = o.skew != null ? o.skew : o.skewY != null ? o.skewY : 0
  2328. if (!relative) {
  2329. // absolute; reset skew values
  2330. var e = matrix.extract()
  2331. matrix = matrix.multiply(new SVG.Matrix().skew(e.skewX, e.skewY, o.cx, o.cy).inverse())
  2332. }
  2333. matrix = matrix.skew(o.skewX, o.skewY, o.cx, o.cy)
  2334. // act on flip
  2335. } else if (o.flip) {
  2336. if(o.flip == 'x' || o.flip == 'y') {
  2337. o.offset = o.offset == null ? target.bbox()['c' + o.flip] : o.offset
  2338. } else {
  2339. if(o.offset == null) {
  2340. bbox = target.bbox()
  2341. o.flip = bbox.cx
  2342. o.offset = bbox.cy
  2343. } else {
  2344. o.flip = o.offset
  2345. }
  2346. }
  2347. matrix = new SVG.Matrix().flip(o.flip, o.offset)
  2348. // act on translate
  2349. } else if (o.x != null || o.y != null) {
  2350. if (relative) {
  2351. // relative
  2352. matrix = matrix.translate(o.x, o.y)
  2353. } else {
  2354. // absolute
  2355. if (o.x != null) matrix.e = o.x
  2356. if (o.y != null) matrix.f = o.y
  2357. }
  2358. }
  2359. return this.attr('transform', matrix)
  2360. }
  2361. })
  2362. SVG.extend(SVG.FX, {
  2363. transform: function(o, relative) {
  2364. // get target in case of the fx module, otherwise reference this
  2365. var target = this.target()
  2366. , matrix, bbox
  2367. // act as a getter
  2368. if (typeof o !== 'object') {
  2369. // get current matrix
  2370. matrix = new SVG.Matrix(target).extract()
  2371. return typeof o === 'string' ? matrix[o] : matrix
  2372. }
  2373. // ensure relative flag
  2374. relative = !!relative || !!o.relative
  2375. // act on matrix
  2376. if (o.a != null) {
  2377. matrix = new SVG.Matrix(o)
  2378. // act on rotation
  2379. } else if (o.rotation != null) {
  2380. // ensure centre point
  2381. ensureCentre(o, target)
  2382. // apply transformation
  2383. matrix = new SVG.Rotate(o.rotation, o.cx, o.cy)
  2384. // act on scale
  2385. } else if (o.scale != null || o.scaleX != null || o.scaleY != null) {
  2386. // ensure centre point
  2387. ensureCentre(o, target)
  2388. // ensure scale values on both axes
  2389. o.scaleX = o.scale != null ? o.scale : o.scaleX != null ? o.scaleX : 1
  2390. o.scaleY = o.scale != null ? o.scale : o.scaleY != null ? o.scaleY : 1
  2391. matrix = new SVG.Scale(o.scaleX, o.scaleY, o.cx, o.cy)
  2392. // act on skew
  2393. } else if (o.skewX != null || o.skewY != null) {
  2394. // ensure centre point
  2395. ensureCentre(o, target)
  2396. // ensure skew values on both axes
  2397. o.skewX = o.skewX != null ? o.skewX : 0
  2398. o.skewY = o.skewY != null ? o.skewY : 0
  2399. matrix = new SVG.Skew(o.skewX, o.skewY, o.cx, o.cy)
  2400. // act on flip
  2401. } else if (o.flip) {
  2402. if(o.flip == 'x' || o.flip == 'y') {
  2403. o.offset = o.offset == null ? target.bbox()['c' + o.flip] : o.offset
  2404. } else {
  2405. if(o.offset == null) {
  2406. bbox = target.bbox()
  2407. o.flip = bbox.cx
  2408. o.offset = bbox.cy
  2409. } else {
  2410. o.flip = o.offset
  2411. }
  2412. }
  2413. matrix = new SVG.Matrix().flip(o.flip, o.offset)
  2414. // act on translate
  2415. } else if (o.x != null || o.y != null) {
  2416. matrix = new SVG.Translate(o.x, o.y)
  2417. }
  2418. if(!matrix) return this
  2419. matrix.relative = relative
  2420. this.last().transforms.push(matrix)
  2421. return this._callStart()
  2422. }
  2423. })
  2424. SVG.extend(SVG.Element, {
  2425. // Reset all transformations
  2426. untransform: function() {
  2427. return this.attr('transform', null)
  2428. },
  2429. // merge the whole transformation chain into one matrix and returns it
  2430. matrixify: function() {
  2431. var matrix = (this.attr('transform') || '')
  2432. // split transformations
  2433. .split(SVG.regex.transforms).slice(0,-1).map(function(str){
  2434. // generate key => value pairs
  2435. var kv = str.trim().split('(')
  2436. return [kv[0], kv[1].split(SVG.regex.delimiter).map(function(str){ return parseFloat(str) })]
  2437. })
  2438. // merge every transformation into one matrix
  2439. .reduce(function(matrix, transform){
  2440. if(transform[0] == 'matrix') return matrix.multiply(arrayToMatrix(transform[1]))
  2441. return matrix[transform[0]].apply(matrix, transform[1])
  2442. }, new SVG.Matrix())
  2443. return matrix
  2444. },
  2445. // add an element to another parent without changing the visual representation on the screen
  2446. toParent: function(parent) {
  2447. if(this == parent) return this
  2448. var ctm = this.screenCTM()
  2449. var pCtm = parent.screenCTM().inverse()
  2450. this.addTo(parent).untransform().transform(pCtm.multiply(ctm))
  2451. return this
  2452. },
  2453. // same as above with parent equals root-svg
  2454. toDoc: function() {
  2455. return this.toParent(this.doc())
  2456. }
  2457. })
  2458. SVG.Transformation = SVG.invent({
  2459. create: function(source, inversed){
  2460. if(arguments.length > 1 && typeof inversed != 'boolean'){
  2461. return this.constructor.call(this, [].slice.call(arguments))
  2462. }
  2463. if(Array.isArray(source)){
  2464. for(var i = 0, len = this.arguments.length; i < len; ++i){
  2465. this[this.arguments[i]] = source[i]
  2466. }
  2467. } else if(typeof source == 'object'){
  2468. for(var i = 0, len = this.arguments.length; i < len; ++i){
  2469. this[this.arguments[i]] = source[this.arguments[i]]
  2470. }
  2471. }
  2472. this.inversed = false
  2473. if(inversed === true){
  2474. this.inversed = true
  2475. }
  2476. }
  2477. , extend: {
  2478. arguments: []
  2479. , method: ''
  2480. , at: function(pos){
  2481. var params = []
  2482. for(var i = 0, len = this.arguments.length; i < len; ++i){
  2483. params.push(this[this.arguments[i]])
  2484. }
  2485. var m = this._undo || new SVG.Matrix()
  2486. m = new SVG.Matrix().morph(SVG.Matrix.prototype[this.method].apply(m, params)).at(pos)
  2487. return this.inversed ? m.inverse() : m
  2488. }
  2489. , undo: function(o){
  2490. for(var i = 0, len = this.arguments.length; i < len; ++i){
  2491. o[this.arguments[i]] = typeof this[this.arguments[i]] == 'undefined' ? 0 : o[this.arguments[i]]
  2492. }
  2493. // The method SVG.Matrix.extract which was used before calling this
  2494. // method to obtain a value for the parameter o doesn't return a cx and
  2495. // a cy so we use the ones that were provided to this object at its creation
  2496. o.cx = this.cx
  2497. o.cy = this.cy
  2498. this._undo = new SVG[capitalize(this.method)](o, true).at(1)
  2499. return this
  2500. }
  2501. }
  2502. })
  2503. SVG.Translate = SVG.invent({
  2504. parent: SVG.Matrix
  2505. , inherit: SVG.Transformation
  2506. , create: function(source, inversed){
  2507. this.constructor.apply(this, [].slice.call(arguments))
  2508. }
  2509. , extend: {
  2510. arguments: ['transformedX', 'transformedY']
  2511. , method: 'translate'
  2512. }
  2513. })
  2514. SVG.Rotate = SVG.invent({
  2515. parent: SVG.Matrix
  2516. , inherit: SVG.Transformation
  2517. , create: function(source, inversed){
  2518. this.constructor.apply(this, [].slice.call(arguments))
  2519. }
  2520. , extend: {
  2521. arguments: ['rotation', 'cx', 'cy']
  2522. , method: 'rotate'
  2523. , at: function(pos){
  2524. var m = new SVG.Matrix().rotate(new SVG.Number().morph(this.rotation - (this._undo ? this._undo.rotation : 0)).at(pos), this.cx, this.cy)
  2525. return this.inversed ? m.inverse() : m
  2526. }
  2527. , undo: function(o){
  2528. this._undo = o
  2529. return this
  2530. }
  2531. }
  2532. })
  2533. SVG.Scale = SVG.invent({
  2534. parent: SVG.Matrix
  2535. , inherit: SVG.Transformation
  2536. , create: function(source, inversed){
  2537. this.constructor.apply(this, [].slice.call(arguments))
  2538. }
  2539. , extend: {
  2540. arguments: ['scaleX', 'scaleY', 'cx', 'cy']
  2541. , method: 'scale'
  2542. }
  2543. })
  2544. SVG.Skew = SVG.invent({
  2545. parent: SVG.Matrix
  2546. , inherit: SVG.Transformation
  2547. , create: function(source, inversed){
  2548. this.constructor.apply(this, [].slice.call(arguments))
  2549. }
  2550. , extend: {
  2551. arguments: ['skewX', 'skewY', 'cx', 'cy']
  2552. , method: 'skew'
  2553. }
  2554. })
  2555. SVG.extend(SVG.Element, {
  2556. // Dynamic style generator
  2557. style: function(s, v) {
  2558. if (arguments.length == 0) {
  2559. // get full style
  2560. return this.node.style.cssText || ''
  2561. } else if (arguments.length < 2) {
  2562. // apply every style individually if an object is passed
  2563. if (typeof s == 'object') {
  2564. for (v in s) this.style(v, s[v])
  2565. } else if (SVG.regex.isCss.test(s)) {
  2566. // parse css string
  2567. s = s.split(/\s*;\s*/)
  2568. // filter out suffix ; and stuff like ;;
  2569. .filter(function(e) { return !!e })
  2570. .map(function(e){ return e.split(/\s*:\s*/) })
  2571. // apply every definition individually
  2572. while (v = s.pop()) {
  2573. this.style(v[0], v[1])
  2574. }
  2575. } else {
  2576. // act as a getter if the first and only argument is not an object
  2577. return this.node.style[camelCase(s)]
  2578. }
  2579. } else {
  2580. this.node.style[camelCase(s)] = v === null || SVG.regex.isBlank.test(v) ? '' : v
  2581. }
  2582. return this
  2583. }
  2584. })
  2585. SVG.Parent = SVG.invent({
  2586. // Initialize node
  2587. create: function(element) {
  2588. this.constructor.call(this, element)
  2589. }
  2590. // Inherit from
  2591. , inherit: SVG.Element
  2592. // Add class methods
  2593. , extend: {
  2594. // Returns all child elements
  2595. children: function() {
  2596. return SVG.utils.map(SVG.utils.filterSVGElements(this.node.childNodes), function(node) {
  2597. return SVG.adopt(node)
  2598. })
  2599. }
  2600. // Add given element at a position
  2601. , add: function(element, i) {
  2602. if (i == null)
  2603. this.node.appendChild(element.node)
  2604. else if (element.node != this.node.childNodes[i])
  2605. this.node.insertBefore(element.node, this.node.childNodes[i])
  2606. return this
  2607. }
  2608. // Basically does the same as `add()` but returns the added element instead
  2609. , put: function(element, i) {
  2610. this.add(element, i)
  2611. return element
  2612. }
  2613. // Checks if the given element is a child
  2614. , has: function(element) {
  2615. return this.index(element) >= 0
  2616. }
  2617. // Gets index of given element
  2618. , index: function(element) {
  2619. return [].slice.call(this.node.childNodes).indexOf(element.node)
  2620. }
  2621. // Get a element at the given index
  2622. , get: function(i) {
  2623. return SVG.adopt(this.node.childNodes[i])
  2624. }
  2625. // Get first child
  2626. , first: function() {
  2627. return this.get(0)
  2628. }
  2629. // Get the last child
  2630. , last: function() {
  2631. return this.get(this.node.childNodes.length - 1)
  2632. }
  2633. // Iterates over all children and invokes a given block
  2634. , each: function(block, deep) {
  2635. var i, il
  2636. , children = this.children()
  2637. for (i = 0, il = children.length; i < il; i++) {
  2638. if (children[i] instanceof SVG.Element)
  2639. block.apply(children[i], [i, children])
  2640. if (deep && (children[i] instanceof SVG.Container))
  2641. children[i].each(block, deep)
  2642. }
  2643. return this
  2644. }
  2645. // Remove a given child
  2646. , removeElement: function(element) {
  2647. this.node.removeChild(element.node)
  2648. return this
  2649. }
  2650. // Remove all elements in this container
  2651. , clear: function() {
  2652. // remove children
  2653. while(this.node.hasChildNodes())
  2654. this.node.removeChild(this.node.lastChild)
  2655. // remove defs reference
  2656. delete this._defs
  2657. return this
  2658. }
  2659. , // Get defs
  2660. defs: function() {
  2661. return this.doc().defs()
  2662. }
  2663. }
  2664. })
  2665. SVG.extend(SVG.Parent, {
  2666. ungroup: function(parent, depth) {
  2667. if(depth === 0 || this instanceof SVG.Defs || this.node == SVG.parser.draw) return this
  2668. parent = parent || (this instanceof SVG.Doc ? this : this.parent(SVG.Parent))
  2669. depth = depth || Infinity
  2670. this.each(function(){
  2671. if(this instanceof SVG.Defs) return this
  2672. if(this instanceof SVG.Parent) return this.ungroup(parent, depth-1)
  2673. return this.toParent(parent)
  2674. })
  2675. this.node.firstChild || this.remove()
  2676. return this
  2677. },
  2678. flatten: function(parent, depth) {
  2679. return this.ungroup(parent, depth)
  2680. }
  2681. })
  2682. SVG.Container = SVG.invent({
  2683. // Initialize node
  2684. create: function(element) {
  2685. this.constructor.call(this, element)
  2686. }
  2687. // Inherit from
  2688. , inherit: SVG.Parent
  2689. })
  2690. SVG.ViewBox = SVG.invent({
  2691. create: function(source) {
  2692. var i, base = [0, 0, 0, 0]
  2693. var x, y, width, height, box, view, we, he
  2694. , wm = 1 // width multiplier
  2695. , hm = 1 // height multiplier
  2696. , reg = /[+-]?(?:\d+(?:\.\d*)?|\.\d+)(?:e[+-]?\d+)?/gi
  2697. if(source instanceof SVG.Element){
  2698. we = source
  2699. he = source
  2700. view = (source.attr('viewBox') || '').match(reg)
  2701. box = source.bbox
  2702. // get dimensions of current node
  2703. width = new SVG.Number(source.width())
  2704. height = new SVG.Number(source.height())
  2705. // find nearest non-percentual dimensions
  2706. while (width.unit == '%') {
  2707. wm *= width.value
  2708. width = new SVG.Number(we instanceof SVG.Doc ? we.parent().offsetWidth : we.parent().width())
  2709. we = we.parent()
  2710. }
  2711. while (height.unit == '%') {
  2712. hm *= height.value
  2713. height = new SVG.Number(he instanceof SVG.Doc ? he.parent().offsetHeight : he.parent().height())
  2714. he = he.parent()
  2715. }
  2716. // ensure defaults
  2717. this.x = 0
  2718. this.y = 0
  2719. this.width = width * wm
  2720. this.height = height * hm
  2721. this.zoom = 1
  2722. if (view) {
  2723. // get width and height from viewbox
  2724. x = parseFloat(view[0])
  2725. y = parseFloat(view[1])
  2726. width = parseFloat(view[2])
  2727. height = parseFloat(view[3])
  2728. // calculate zoom accoring to viewbox
  2729. this.zoom = ((this.width / this.height) > (width / height)) ?
  2730. this.height / height :
  2731. this.width / width
  2732. // calculate real pixel dimensions on parent SVG.Doc element
  2733. this.x = x
  2734. this.y = y
  2735. this.width = width
  2736. this.height = height
  2737. }
  2738. }else{
  2739. // ensure source as object
  2740. source = typeof source === 'string' ?
  2741. source.match(reg).map(function(el){ return parseFloat(el) }) :
  2742. Array.isArray(source) ?
  2743. source :
  2744. typeof source == 'object' ?
  2745. [source.x, source.y, source.width, source.height] :
  2746. arguments.length == 4 ?
  2747. [].slice.call(arguments) :
  2748. base
  2749. this.x = source[0]
  2750. this.y = source[1]
  2751. this.width = source[2]
  2752. this.height = source[3]
  2753. }
  2754. }
  2755. , extend: {
  2756. toString: function() {
  2757. return this.x + ' ' + this.y + ' ' + this.width + ' ' + this.height
  2758. }
  2759. , morph: function(x, y, width, height){
  2760. this.destination = new SVG.ViewBox(x, y, width, height)
  2761. return this
  2762. }
  2763. , at: function(pos) {
  2764. if(!this.destination) return this
  2765. return new SVG.ViewBox([
  2766. this.x + (this.destination.x - this.x) * pos
  2767. , this.y + (this.destination.y - this.y) * pos
  2768. , this.width + (this.destination.width - this.width) * pos
  2769. , this.height + (this.destination.height - this.height) * pos
  2770. ])
  2771. }
  2772. }
  2773. // Define parent
  2774. , parent: SVG.Container
  2775. // Add parent method
  2776. , construct: {
  2777. // get/set viewbox
  2778. viewbox: function(x, y, width, height) {
  2779. if (arguments.length == 0)
  2780. // act as a getter if there are no arguments
  2781. return new SVG.ViewBox(this)
  2782. // otherwise act as a setter
  2783. return this.attr('viewBox', new SVG.ViewBox(x, y, width, height))
  2784. }
  2785. }
  2786. })
  2787. // Add events to elements
  2788. ;[ 'click',
  2789. 'dblclick',
  2790. 'mousedown',
  2791. 'mouseup',
  2792. 'mouseover',
  2793. 'mouseout',
  2794. 'mousemove',
  2795. 'mouseenter',
  2796. 'mouseleave',
  2797. 'touchstart',
  2798. 'touchmove',
  2799. 'touchleave',
  2800. 'touchend',
  2801. 'touchcancel' ].forEach(function (event) {
  2802. // add event to SVG.Element
  2803. SVG.Element.prototype[event] = function (f) {
  2804. // bind event to element rather than element node
  2805. if (f == null) {
  2806. SVG.off(this, event)
  2807. } else {
  2808. SVG.on(this, event, f)
  2809. }
  2810. return this
  2811. }
  2812. })
  2813. SVG.listenerId = 0
  2814. // Add event binder in the SVG namespace
  2815. SVG.on = function (node, events, listener, binding, options) {
  2816. var l = listener.bind(binding || node)
  2817. var n = node instanceof SVG.Element ? node.node : node
  2818. // ensure instance object for nodes which are not adopted
  2819. n.instance = n.instance || {_events: {}}
  2820. var bag = n.instance._events
  2821. // add id to listener
  2822. if (!listener._svgjsListenerId) { listener._svgjsListenerId = ++SVG.listenerId }
  2823. events.split(SVG.regex.delimiter).forEach(function (event) {
  2824. var ev = event.split('.')[0]
  2825. var ns = event.split('.')[1] || '*'
  2826. // ensure valid object
  2827. bag[ev] = bag[ev] || {}
  2828. bag[ev][ns] = bag[ev][ns] || {}
  2829. // reference listener
  2830. bag[ev][ns][listener._svgjsListenerId] = l
  2831. // add listener
  2832. n.addEventListener(ev, l, options || false)
  2833. })
  2834. }
  2835. // Add event unbinder in the SVG namespace
  2836. SVG.off = function (node, events, listener, options) {
  2837. var n = node instanceof SVG.Element ? node.node : node
  2838. if (!n.instance) return
  2839. // listener can be a function or a number
  2840. if (typeof listener === 'function') {
  2841. listener = listener._svgjsListenerId
  2842. if (!listener) return
  2843. }
  2844. var bag = n.instance._events
  2845. ;(events || '').split(SVG.regex.delimiter).forEach(function (event) {
  2846. var ev = event && event.split('.')[0]
  2847. var ns = event && event.split('.')[1]
  2848. var namespace, l
  2849. if (listener) {
  2850. // remove listener reference
  2851. if (bag[ev] && bag[ev][ns || '*']) {
  2852. // removeListener
  2853. n.removeEventListener(ev, bag[ev][ns || '*'][listener], options || false)
  2854. delete bag[ev][ns || '*'][listener]
  2855. }
  2856. } else if (ev && ns) {
  2857. // remove all listeners for a namespaced event
  2858. if (bag[ev] && bag[ev][ns]) {
  2859. for (l in bag[ev][ns]) { SVG.off(n, [ev, ns].join('.'), l) }
  2860. delete bag[ev][ns]
  2861. }
  2862. } else if (ns) {
  2863. // remove all listeners for a specific namespace
  2864. for (event in bag) {
  2865. for (namespace in bag[event]) {
  2866. if (ns === namespace) { SVG.off(n, [event, ns].join('.')) }
  2867. }
  2868. }
  2869. } else if (ev) {
  2870. // remove all listeners for the event
  2871. if (bag[ev]) {
  2872. for (namespace in bag[ev]) { SVG.off(n, [ev, namespace].join('.')) }
  2873. delete bag[ev]
  2874. }
  2875. } else {
  2876. // remove all listeners on a given node
  2877. for (event in bag) { SVG.off(n, event) }
  2878. n.instance._events = {}
  2879. }
  2880. })
  2881. }
  2882. SVG.extend(SVG.Element, {
  2883. // Bind given event to listener
  2884. on: function (event, listener, binding, options) {
  2885. SVG.on(this, event, listener, binding, options)
  2886. return this
  2887. },
  2888. // Unbind event from listener
  2889. off: function (event, listener) {
  2890. SVG.off(this.node, event, listener)
  2891. return this
  2892. },
  2893. fire: function (event, data) {
  2894. // Dispatch event
  2895. if (event instanceof window.Event) {
  2896. this.node.dispatchEvent(event)
  2897. } else {
  2898. this.node.dispatchEvent(event = new SVG.CustomEvent(event, {detail: data, cancelable: true}))
  2899. }
  2900. this._event = event
  2901. return this
  2902. },
  2903. event: function() {
  2904. return this._event
  2905. }
  2906. })
  2907. SVG.Defs = SVG.invent({
  2908. // Initialize node
  2909. create: 'defs'
  2910. // Inherit from
  2911. , inherit: SVG.Container
  2912. })
  2913. SVG.G = SVG.invent({
  2914. // Initialize node
  2915. create: 'g'
  2916. // Inherit from
  2917. , inherit: SVG.Container
  2918. // Add class methods
  2919. , extend: {
  2920. // Move over x-axis
  2921. x: function(x) {
  2922. return x == null ? this.transform('x') : this.transform({ x: x - this.x() }, true)
  2923. }
  2924. // Move over y-axis
  2925. , y: function(y) {
  2926. return y == null ? this.transform('y') : this.transform({ y: y - this.y() }, true)
  2927. }
  2928. // Move by center over x-axis
  2929. , cx: function(x) {
  2930. return x == null ? this.gbox().cx : this.x(x - this.gbox().width / 2)
  2931. }
  2932. // Move by center over y-axis
  2933. , cy: function(y) {
  2934. return y == null ? this.gbox().cy : this.y(y - this.gbox().height / 2)
  2935. }
  2936. , gbox: function() {
  2937. var bbox = this.bbox()
  2938. , trans = this.transform()
  2939. bbox.x += trans.x
  2940. bbox.x2 += trans.x
  2941. bbox.cx += trans.x
  2942. bbox.y += trans.y
  2943. bbox.y2 += trans.y
  2944. bbox.cy += trans.y
  2945. return bbox
  2946. }
  2947. }
  2948. // Add parent method
  2949. , construct: {
  2950. // Create a group element
  2951. group: function() {
  2952. return this.put(new SVG.G)
  2953. }
  2954. }
  2955. })
  2956. SVG.Doc = SVG.invent({
  2957. // Initialize node
  2958. create: function(element) {
  2959. if (element) {
  2960. // ensure the presence of a dom element
  2961. element = typeof element == 'string' ?
  2962. document.getElementById(element) :
  2963. element
  2964. // If the target is an svg element, use that element as the main wrapper.
  2965. // This allows svg.js to work with svg documents as well.
  2966. if (element.nodeName == 'svg') {
  2967. this.constructor.call(this, element)
  2968. } else {
  2969. this.constructor.call(this, SVG.create('svg'))
  2970. element.appendChild(this.node)
  2971. this.size('100%', '100%')
  2972. }
  2973. // set svg element attributes and ensure defs node
  2974. this.namespace().defs()
  2975. }
  2976. }
  2977. // Inherit from
  2978. , inherit: SVG.Container
  2979. // Add class methods
  2980. , extend: {
  2981. // Add namespaces
  2982. namespace: function() {
  2983. return this
  2984. .attr({ xmlns: SVG.ns, version: '1.1' })
  2985. .attr('xmlns:xlink', SVG.xlink, SVG.xmlns)
  2986. .attr('xmlns:svgjs', SVG.svgjs, SVG.xmlns)
  2987. }
  2988. // Creates and returns defs element
  2989. , defs: function() {
  2990. if (!this._defs) {
  2991. var defs
  2992. // Find or create a defs element in this instance
  2993. if (defs = this.node.getElementsByTagName('defs')[0])
  2994. this._defs = SVG.adopt(defs)
  2995. else
  2996. this._defs = new SVG.Defs
  2997. // Make sure the defs node is at the end of the stack
  2998. this.node.appendChild(this._defs.node)
  2999. }
  3000. return this._defs
  3001. }
  3002. // custom parent method
  3003. , parent: function() {
  3004. if(!this.node.parentNode || this.node.parentNode.nodeName == '#document' || this.node.parentNode.nodeName == '#document-fragment') return null
  3005. return this.node.parentNode
  3006. }
  3007. // Fix for possible sub-pixel offset. See:
  3008. // https://bugzilla.mozilla.org/show_bug.cgi?id=608812
  3009. , spof: function() {
  3010. var pos = this.node.getScreenCTM()
  3011. if (pos)
  3012. this
  3013. .style('left', (-pos.e % 1) + 'px')
  3014. .style('top', (-pos.f % 1) + 'px')
  3015. return this
  3016. }
  3017. // Removes the doc from the DOM
  3018. , remove: function() {
  3019. if(this.parent()) {
  3020. this.parent().removeChild(this.node)
  3021. }
  3022. return this
  3023. }
  3024. , clear: function() {
  3025. // remove children
  3026. while(this.node.hasChildNodes())
  3027. this.node.removeChild(this.node.lastChild)
  3028. // remove defs reference
  3029. delete this._defs
  3030. // add back parser
  3031. if(!SVG.parser.draw.parentNode)
  3032. this.node.appendChild(SVG.parser.draw)
  3033. return this
  3034. }
  3035. , clone: function (parent) {
  3036. // write dom data to the dom so the clone can pickup the data
  3037. this.writeDataToDom()
  3038. // get reference to node
  3039. var node = this.node
  3040. // clone element and assign new id
  3041. var clone = assignNewId(node.cloneNode(true))
  3042. // insert the clone in the given parent or after myself
  3043. if(parent) {
  3044. (parent.node || parent).appendChild(clone.node)
  3045. } else {
  3046. node.parentNode.insertBefore(clone.node, node.nextSibling)
  3047. }
  3048. return clone
  3049. }
  3050. }
  3051. })
  3052. // ### This module adds backward / forward functionality to elements.
  3053. //
  3054. SVG.extend(SVG.Element, {
  3055. // Get all siblings, including myself
  3056. siblings: function() {
  3057. return this.parent().children()
  3058. }
  3059. // Get the curent position siblings
  3060. , position: function() {
  3061. return this.parent().index(this)
  3062. }
  3063. // Get the next element (will return null if there is none)
  3064. , next: function() {
  3065. return this.siblings()[this.position() + 1]
  3066. }
  3067. // Get the next element (will return null if there is none)
  3068. , previous: function() {
  3069. return this.siblings()[this.position() - 1]
  3070. }
  3071. // Send given element one step forward
  3072. , forward: function() {
  3073. var i = this.position() + 1
  3074. , p = this.parent()
  3075. // move node one step forward
  3076. p.removeElement(this).add(this, i)
  3077. // make sure defs node is always at the top
  3078. if (p instanceof SVG.Doc)
  3079. p.node.appendChild(p.defs().node)
  3080. return this
  3081. }
  3082. // Send given element one step backward
  3083. , backward: function() {
  3084. var i = this.position()
  3085. if (i > 0)
  3086. this.parent().removeElement(this).add(this, i - 1)
  3087. return this
  3088. }
  3089. // Send given element all the way to the front
  3090. , front: function() {
  3091. var p = this.parent()
  3092. // Move node forward
  3093. p.node.appendChild(this.node)
  3094. // Make sure defs node is always at the top
  3095. if (p instanceof SVG.Doc)
  3096. p.node.appendChild(p.defs().node)
  3097. return this
  3098. }
  3099. // Send given element all the way to the back
  3100. , back: function() {
  3101. if (this.position() > 0)
  3102. this.parent().removeElement(this).add(this, 0)
  3103. return this
  3104. }
  3105. // Inserts a given element before the targeted element
  3106. , before: function(element) {
  3107. element.remove()
  3108. var i = this.position()
  3109. this.parent().add(element, i)
  3110. return this
  3111. }
  3112. // Insters a given element after the targeted element
  3113. , after: function(element) {
  3114. element.remove()
  3115. var i = this.position()
  3116. this.parent().add(element, i + 1)
  3117. return this
  3118. }
  3119. })
  3120. SVG.Mask = SVG.invent({
  3121. // Initialize node
  3122. create: function() {
  3123. this.constructor.call(this, SVG.create('mask'))
  3124. // keep references to masked elements
  3125. this.targets = []
  3126. }
  3127. // Inherit from
  3128. , inherit: SVG.Container
  3129. // Add class methods
  3130. , extend: {
  3131. // Unmask all masked elements and remove itself
  3132. remove: function() {
  3133. // unmask all targets
  3134. for (var i = this.targets.length - 1; i >= 0; i--)
  3135. if (this.targets[i])
  3136. this.targets[i].unmask()
  3137. this.targets = []
  3138. // remove mask from parent
  3139. SVG.Element.prototype.remove.call(this)
  3140. return this
  3141. }
  3142. }
  3143. // Add parent method
  3144. , construct: {
  3145. // Create masking element
  3146. mask: function() {
  3147. return this.defs().put(new SVG.Mask)
  3148. }
  3149. }
  3150. })
  3151. SVG.extend(SVG.Element, {
  3152. // Distribute mask to svg element
  3153. maskWith: function(element) {
  3154. // use given mask or create a new one
  3155. this.masker = element instanceof SVG.Mask ? element : this.parent().mask().add(element)
  3156. // store reverence on self in mask
  3157. this.masker.targets.push(this)
  3158. // apply mask
  3159. return this.attr('mask', 'url("#' + this.masker.attr('id') + '")')
  3160. }
  3161. // Unmask element
  3162. , unmask: function() {
  3163. delete this.masker
  3164. return this.attr('mask', null)
  3165. }
  3166. })
  3167. SVG.ClipPath = SVG.invent({
  3168. // Initialize node
  3169. create: function() {
  3170. this.constructor.call(this, SVG.create('clipPath'))
  3171. // keep references to clipped elements
  3172. this.targets = []
  3173. }
  3174. // Inherit from
  3175. , inherit: SVG.Container
  3176. // Add class methods
  3177. , extend: {
  3178. // Unclip all clipped elements and remove itself
  3179. remove: function() {
  3180. // unclip all targets
  3181. for (var i = this.targets.length - 1; i >= 0; i--)
  3182. if (this.targets[i])
  3183. this.targets[i].unclip()
  3184. this.targets = []
  3185. // remove clipPath from parent
  3186. this.parent().removeElement(this)
  3187. return this
  3188. }
  3189. }
  3190. // Add parent method
  3191. , construct: {
  3192. // Create clipping element
  3193. clip: function() {
  3194. return this.defs().put(new SVG.ClipPath)
  3195. }
  3196. }
  3197. })
  3198. //
  3199. SVG.extend(SVG.Element, {
  3200. // Distribute clipPath to svg element
  3201. clipWith: function(element) {
  3202. // use given clip or create a new one
  3203. this.clipper = element instanceof SVG.ClipPath ? element : this.parent().clip().add(element)
  3204. // store reverence on self in mask
  3205. this.clipper.targets.push(this)
  3206. // apply mask
  3207. return this.attr('clip-path', 'url("#' + this.clipper.attr('id') + '")')
  3208. }
  3209. // Unclip element
  3210. , unclip: function() {
  3211. delete this.clipper
  3212. return this.attr('clip-path', null)
  3213. }
  3214. })
  3215. SVG.Gradient = SVG.invent({
  3216. // Initialize node
  3217. create: function(type) {
  3218. this.constructor.call(this, SVG.create(type + 'Gradient'))
  3219. // store type
  3220. this.type = type
  3221. }
  3222. // Inherit from
  3223. , inherit: SVG.Container
  3224. // Add class methods
  3225. , extend: {
  3226. // Add a color stop
  3227. at: function(offset, color, opacity) {
  3228. return this.put(new SVG.Stop).update(offset, color, opacity)
  3229. }
  3230. // Update gradient
  3231. , update: function(block) {
  3232. // remove all stops
  3233. this.clear()
  3234. // invoke passed block
  3235. if (typeof block == 'function')
  3236. block.call(this, this)
  3237. return this
  3238. }
  3239. // Return the fill id
  3240. , fill: function() {
  3241. return 'url(#' + this.id() + ')'
  3242. }
  3243. // Alias string convertion to fill
  3244. , toString: function() {
  3245. return this.fill()
  3246. }
  3247. // custom attr to handle transform
  3248. , attr: function(a, b, c) {
  3249. if(a == 'transform') a = 'gradientTransform'
  3250. return SVG.Container.prototype.attr.call(this, a, b, c)
  3251. }
  3252. }
  3253. // Add parent method
  3254. , construct: {
  3255. // Create gradient element in defs
  3256. gradient: function(type, block) {
  3257. return this.defs().gradient(type, block)
  3258. }
  3259. }
  3260. })
  3261. // Add animatable methods to both gradient and fx module
  3262. SVG.extend(SVG.Gradient, SVG.FX, {
  3263. // From position
  3264. from: function(x, y) {
  3265. return (this._target || this).type == 'radial' ?
  3266. this.attr({ fx: new SVG.Number(x), fy: new SVG.Number(y) }) :
  3267. this.attr({ x1: new SVG.Number(x), y1: new SVG.Number(y) })
  3268. }
  3269. // To position
  3270. , to: function(x, y) {
  3271. return (this._target || this).type == 'radial' ?
  3272. this.attr({ cx: new SVG.Number(x), cy: new SVG.Number(y) }) :
  3273. this.attr({ x2: new SVG.Number(x), y2: new SVG.Number(y) })
  3274. }
  3275. })
  3276. // Base gradient generation
  3277. SVG.extend(SVG.Defs, {
  3278. // define gradient
  3279. gradient: function(type, block) {
  3280. return this.put(new SVG.Gradient(type)).update(block)
  3281. }
  3282. })
  3283. SVG.Stop = SVG.invent({
  3284. // Initialize node
  3285. create: 'stop'
  3286. // Inherit from
  3287. , inherit: SVG.Element
  3288. // Add class methods
  3289. , extend: {
  3290. // add color stops
  3291. update: function(o) {
  3292. if (typeof o == 'number' || o instanceof SVG.Number) {
  3293. o = {
  3294. offset: arguments[0]
  3295. , color: arguments[1]
  3296. , opacity: arguments[2]
  3297. }
  3298. }
  3299. // set attributes
  3300. if (o.opacity != null) this.attr('stop-opacity', o.opacity)
  3301. if (o.color != null) this.attr('stop-color', o.color)
  3302. if (o.offset != null) this.attr('offset', new SVG.Number(o.offset))
  3303. return this
  3304. }
  3305. }
  3306. })
  3307. SVG.Pattern = SVG.invent({
  3308. // Initialize node
  3309. create: 'pattern'
  3310. // Inherit from
  3311. , inherit: SVG.Container
  3312. // Add class methods
  3313. , extend: {
  3314. // Return the fill id
  3315. fill: function() {
  3316. return 'url(#' + this.id() + ')'
  3317. }
  3318. // Update pattern by rebuilding
  3319. , update: function(block) {
  3320. // remove content
  3321. this.clear()
  3322. // invoke passed block
  3323. if (typeof block == 'function')
  3324. block.call(this, this)
  3325. return this
  3326. }
  3327. // Alias string convertion to fill
  3328. , toString: function() {
  3329. return this.fill()
  3330. }
  3331. // custom attr to handle transform
  3332. , attr: function(a, b, c) {
  3333. if(a == 'transform') a = 'patternTransform'
  3334. return SVG.Container.prototype.attr.call(this, a, b, c)
  3335. }
  3336. }
  3337. // Add parent method
  3338. , construct: {
  3339. // Create pattern element in defs
  3340. pattern: function(width, height, block) {
  3341. return this.defs().pattern(width, height, block)
  3342. }
  3343. }
  3344. })
  3345. SVG.extend(SVG.Defs, {
  3346. // Define gradient
  3347. pattern: function(width, height, block) {
  3348. return this.put(new SVG.Pattern).update(block).attr({
  3349. x: 0
  3350. , y: 0
  3351. , width: width
  3352. , height: height
  3353. , patternUnits: 'userSpaceOnUse'
  3354. })
  3355. }
  3356. })
  3357. SVG.Shape = SVG.invent({
  3358. // Initialize node
  3359. create: function(element) {
  3360. this.constructor.call(this, element)
  3361. }
  3362. // Inherit from
  3363. , inherit: SVG.Element
  3364. })
  3365. SVG.Bare = SVG.invent({
  3366. // Initialize
  3367. create: function(element, inherit) {
  3368. // construct element
  3369. this.constructor.call(this, SVG.create(element))
  3370. // inherit custom methods
  3371. if (inherit)
  3372. for (var method in inherit.prototype)
  3373. if (typeof inherit.prototype[method] === 'function')
  3374. this[method] = inherit.prototype[method]
  3375. }
  3376. // Inherit from
  3377. , inherit: SVG.Element
  3378. // Add methods
  3379. , extend: {
  3380. // Insert some plain text
  3381. words: function(text) {
  3382. // remove contents
  3383. while (this.node.hasChildNodes())
  3384. this.node.removeChild(this.node.lastChild)
  3385. // create text node
  3386. this.node.appendChild(document.createTextNode(text))
  3387. return this
  3388. }
  3389. }
  3390. })
  3391. SVG.extend(SVG.Parent, {
  3392. // Create an element that is not described by SVG.js
  3393. element: function(element, inherit) {
  3394. return this.put(new SVG.Bare(element, inherit))
  3395. }
  3396. })
  3397. SVG.Symbol = SVG.invent({
  3398. // Initialize node
  3399. create: 'symbol'
  3400. // Inherit from
  3401. , inherit: SVG.Container
  3402. , construct: {
  3403. // create symbol
  3404. symbol: function() {
  3405. return this.put(new SVG.Symbol)
  3406. }
  3407. }
  3408. })
  3409. SVG.Use = SVG.invent({
  3410. // Initialize node
  3411. create: 'use'
  3412. // Inherit from
  3413. , inherit: SVG.Shape
  3414. // Add class methods
  3415. , extend: {
  3416. // Use element as a reference
  3417. element: function(element, file) {
  3418. // Set lined element
  3419. return this.attr('href', (file || '') + '#' + element, SVG.xlink)
  3420. }
  3421. }
  3422. // Add parent method
  3423. , construct: {
  3424. // Create a use element
  3425. use: function(element, file) {
  3426. return this.put(new SVG.Use).element(element, file)
  3427. }
  3428. }
  3429. })
  3430. SVG.Rect = SVG.invent({
  3431. // Initialize node
  3432. create: 'rect'
  3433. // Inherit from
  3434. , inherit: SVG.Shape
  3435. // Add parent method
  3436. , construct: {
  3437. // Create a rect element
  3438. rect: function(width, height) {
  3439. return this.put(new SVG.Rect()).size(width, height)
  3440. }
  3441. }
  3442. })
  3443. SVG.Circle = SVG.invent({
  3444. // Initialize node
  3445. create: 'circle'
  3446. // Inherit from
  3447. , inherit: SVG.Shape
  3448. // Add parent method
  3449. , construct: {
  3450. // Create circle element, based on ellipse
  3451. circle: function(size) {
  3452. return this.put(new SVG.Circle).rx(new SVG.Number(size).divide(2)).move(0, 0)
  3453. }
  3454. }
  3455. })
  3456. SVG.extend(SVG.Circle, SVG.FX, {
  3457. // Radius x value
  3458. rx: function(rx) {
  3459. return this.attr('r', rx)
  3460. }
  3461. // Alias radius x value
  3462. , ry: function(ry) {
  3463. return this.rx(ry)
  3464. }
  3465. })
  3466. SVG.Ellipse = SVG.invent({
  3467. // Initialize node
  3468. create: 'ellipse'
  3469. // Inherit from
  3470. , inherit: SVG.Shape
  3471. // Add parent method
  3472. , construct: {
  3473. // Create an ellipse
  3474. ellipse: function(width, height) {
  3475. return this.put(new SVG.Ellipse).size(width, height).move(0, 0)
  3476. }
  3477. }
  3478. })
  3479. SVG.extend(SVG.Ellipse, SVG.Rect, SVG.FX, {
  3480. // Radius x value
  3481. rx: function(rx) {
  3482. return this.attr('rx', rx)
  3483. }
  3484. // Radius y value
  3485. , ry: function(ry) {
  3486. return this.attr('ry', ry)
  3487. }
  3488. })
  3489. // Add common method
  3490. SVG.extend(SVG.Circle, SVG.Ellipse, {
  3491. // Move over x-axis
  3492. x: function(x) {
  3493. return x == null ? this.cx() - this.rx() : this.cx(x + this.rx())
  3494. }
  3495. // Move over y-axis
  3496. , y: function(y) {
  3497. return y == null ? this.cy() - this.ry() : this.cy(y + this.ry())
  3498. }
  3499. // Move by center over x-axis
  3500. , cx: function(x) {
  3501. return x == null ? this.attr('cx') : this.attr('cx', x)
  3502. }
  3503. // Move by center over y-axis
  3504. , cy: function(y) {
  3505. return y == null ? this.attr('cy') : this.attr('cy', y)
  3506. }
  3507. // Set width of element
  3508. , width: function(width) {
  3509. return width == null ? this.rx() * 2 : this.rx(new SVG.Number(width).divide(2))
  3510. }
  3511. // Set height of element
  3512. , height: function(height) {
  3513. return height == null ? this.ry() * 2 : this.ry(new SVG.Number(height).divide(2))
  3514. }
  3515. // Custom size function
  3516. , size: function(width, height) {
  3517. var p = proportionalSize(this, width, height)
  3518. return this
  3519. .rx(new SVG.Number(p.width).divide(2))
  3520. .ry(new SVG.Number(p.height).divide(2))
  3521. }
  3522. })
  3523. SVG.Line = SVG.invent({
  3524. // Initialize node
  3525. create: 'line'
  3526. // Inherit from
  3527. , inherit: SVG.Shape
  3528. // Add class methods
  3529. , extend: {
  3530. // Get array
  3531. array: function() {
  3532. return new SVG.PointArray([
  3533. [ this.attr('x1'), this.attr('y1') ]
  3534. , [ this.attr('x2'), this.attr('y2') ]
  3535. ])
  3536. }
  3537. // Overwrite native plot() method
  3538. , plot: function(x1, y1, x2, y2) {
  3539. if (x1 == null)
  3540. return this.array()
  3541. else if (typeof y1 !== 'undefined')
  3542. x1 = { x1: x1, y1: y1, x2: x2, y2: y2 }
  3543. else
  3544. x1 = new SVG.PointArray(x1).toLine()
  3545. return this.attr(x1)
  3546. }
  3547. // Move by left top corner
  3548. , move: function(x, y) {
  3549. return this.attr(this.array().move(x, y).toLine())
  3550. }
  3551. // Set element size to given width and height
  3552. , size: function(width, height) {
  3553. var p = proportionalSize(this, width, height)
  3554. return this.attr(this.array().size(p.width, p.height).toLine())
  3555. }
  3556. }
  3557. // Add parent method
  3558. , construct: {
  3559. // Create a line element
  3560. line: function(x1, y1, x2, y2) {
  3561. // make sure plot is called as a setter
  3562. // x1 is not necessarily a number, it can also be an array, a string and a SVG.PointArray
  3563. return SVG.Line.prototype.plot.apply(
  3564. this.put(new SVG.Line)
  3565. , x1 != null ? [x1, y1, x2, y2] : [0, 0, 0, 0]
  3566. )
  3567. }
  3568. }
  3569. })
  3570. SVG.Polyline = SVG.invent({
  3571. // Initialize node
  3572. create: 'polyline'
  3573. // Inherit from
  3574. , inherit: SVG.Shape
  3575. // Add parent method
  3576. , construct: {
  3577. // Create a wrapped polyline element
  3578. polyline: function(p) {
  3579. // make sure plot is called as a setter
  3580. return this.put(new SVG.Polyline).plot(p || new SVG.PointArray)
  3581. }
  3582. }
  3583. })
  3584. SVG.Polygon = SVG.invent({
  3585. // Initialize node
  3586. create: 'polygon'
  3587. // Inherit from
  3588. , inherit: SVG.Shape
  3589. // Add parent method
  3590. , construct: {
  3591. // Create a wrapped polygon element
  3592. polygon: function(p) {
  3593. // make sure plot is called as a setter
  3594. return this.put(new SVG.Polygon).plot(p || new SVG.PointArray)
  3595. }
  3596. }
  3597. })
  3598. // Add polygon-specific functions
  3599. SVG.extend(SVG.Polyline, SVG.Polygon, {
  3600. // Get array
  3601. array: function() {
  3602. return this._array || (this._array = new SVG.PointArray(this.attr('points')))
  3603. }
  3604. // Plot new path
  3605. , plot: function(p) {
  3606. return (p == null) ?
  3607. this.array() :
  3608. this.clear().attr('points', typeof p == 'string' ? p : (this._array = new SVG.PointArray(p)))
  3609. }
  3610. // Clear array cache
  3611. , clear: function() {
  3612. delete this._array
  3613. return this
  3614. }
  3615. // Move by left top corner
  3616. , move: function(x, y) {
  3617. return this.attr('points', this.array().move(x, y))
  3618. }
  3619. // Set element size to given width and height
  3620. , size: function(width, height) {
  3621. var p = proportionalSize(this, width, height)
  3622. return this.attr('points', this.array().size(p.width, p.height))
  3623. }
  3624. })
  3625. // unify all point to point elements
  3626. SVG.extend(SVG.Line, SVG.Polyline, SVG.Polygon, {
  3627. // Define morphable array
  3628. morphArray: SVG.PointArray
  3629. // Move by left top corner over x-axis
  3630. , x: function(x) {
  3631. return x == null ? this.bbox().x : this.move(x, this.bbox().y)
  3632. }
  3633. // Move by left top corner over y-axis
  3634. , y: function(y) {
  3635. return y == null ? this.bbox().y : this.move(this.bbox().x, y)
  3636. }
  3637. // Set width of element
  3638. , width: function(width) {
  3639. var b = this.bbox()
  3640. return width == null ? b.width : this.size(width, b.height)
  3641. }
  3642. // Set height of element
  3643. , height: function(height) {
  3644. var b = this.bbox()
  3645. return height == null ? b.height : this.size(b.width, height)
  3646. }
  3647. })
  3648. SVG.Path = SVG.invent({
  3649. // Initialize node
  3650. create: 'path'
  3651. // Inherit from
  3652. , inherit: SVG.Shape
  3653. // Add class methods
  3654. , extend: {
  3655. // Define morphable array
  3656. morphArray: SVG.PathArray
  3657. // Get array
  3658. , array: function() {
  3659. return this._array || (this._array = new SVG.PathArray(this.attr('d')))
  3660. }
  3661. // Plot new path
  3662. , plot: function(d) {
  3663. return (d == null) ?
  3664. this.array() :
  3665. this.clear().attr('d', typeof d == 'string' ? d : (this._array = new SVG.PathArray(d)))
  3666. }
  3667. // Clear array cache
  3668. , clear: function() {
  3669. delete this._array
  3670. return this
  3671. }
  3672. // Move by left top corner
  3673. , move: function(x, y) {
  3674. return this.attr('d', this.array().move(x, y))
  3675. }
  3676. // Move by left top corner over x-axis
  3677. , x: function(x) {
  3678. return x == null ? this.bbox().x : this.move(x, this.bbox().y)
  3679. }
  3680. // Move by left top corner over y-axis
  3681. , y: function(y) {
  3682. return y == null ? this.bbox().y : this.move(this.bbox().x, y)
  3683. }
  3684. // Set element size to given width and height
  3685. , size: function(width, height) {
  3686. var p = proportionalSize(this, width, height)
  3687. return this.attr('d', this.array().size(p.width, p.height))
  3688. }
  3689. // Set width of element
  3690. , width: function(width) {
  3691. return width == null ? this.bbox().width : this.size(width, this.bbox().height)
  3692. }
  3693. // Set height of element
  3694. , height: function(height) {
  3695. return height == null ? this.bbox().height : this.size(this.bbox().width, height)
  3696. }
  3697. }
  3698. // Add parent method
  3699. , construct: {
  3700. // Create a wrapped path element
  3701. path: function(d) {
  3702. // make sure plot is called as a setter
  3703. return this.put(new SVG.Path).plot(d || new SVG.PathArray)
  3704. }
  3705. }
  3706. })
  3707. SVG.Image = SVG.invent({
  3708. // Initialize node
  3709. create: 'image'
  3710. // Inherit from
  3711. , inherit: SVG.Shape
  3712. // Add class methods
  3713. , extend: {
  3714. // (re)load image
  3715. load: function(url) {
  3716. if (!url) return this
  3717. var self = this
  3718. , img = new window.Image()
  3719. // preload image
  3720. SVG.on(img, 'load', function() {
  3721. SVG.off(img)
  3722. var p = self.parent(SVG.Pattern)
  3723. if(p === null) return
  3724. // ensure image size
  3725. if (self.width() == 0 && self.height() == 0)
  3726. self.size(img.width, img.height)
  3727. // ensure pattern size if not set
  3728. if (p && p.width() == 0 && p.height() == 0)
  3729. p.size(self.width(), self.height())
  3730. // callback
  3731. if (typeof self._loaded === 'function')
  3732. self._loaded.call(self, {
  3733. width: img.width
  3734. , height: img.height
  3735. , ratio: img.width / img.height
  3736. , url: url
  3737. })
  3738. })
  3739. SVG.on(img, 'error', function(e){
  3740. SVG.off(img)
  3741. if (typeof self._error === 'function'){
  3742. self._error.call(self, e)
  3743. }
  3744. })
  3745. return this.attr('href', (img.src = this.src = url), SVG.xlink)
  3746. }
  3747. // Add loaded callback
  3748. , loaded: function(loaded) {
  3749. this._loaded = loaded
  3750. return this
  3751. }
  3752. , error: function(error) {
  3753. this._error = error
  3754. return this
  3755. }
  3756. }
  3757. // Add parent method
  3758. , construct: {
  3759. // create image element, load image and set its size
  3760. image: function(source, width, height) {
  3761. return this.put(new SVG.Image).load(source).size(width || 0, height || width || 0)
  3762. }
  3763. }
  3764. })
  3765. SVG.Text = SVG.invent({
  3766. // Initialize node
  3767. create: function() {
  3768. this.constructor.call(this, SVG.create('text'))
  3769. this.dom.leading = new SVG.Number(1.3) // store leading value for rebuilding
  3770. this._rebuild = true // enable automatic updating of dy values
  3771. this._build = false // disable build mode for adding multiple lines
  3772. // set default font
  3773. this.attr('font-family', SVG.defaults.attrs['font-family'])
  3774. }
  3775. // Inherit from
  3776. , inherit: SVG.Shape
  3777. // Add class methods
  3778. , extend: {
  3779. // Move over x-axis
  3780. x: function(x) {
  3781. // act as getter
  3782. if (x == null)
  3783. return this.attr('x')
  3784. return this.attr('x', x)
  3785. }
  3786. // Move over y-axis
  3787. , y: function(y) {
  3788. var oy = this.attr('y')
  3789. , o = typeof oy === 'number' ? oy - this.bbox().y : 0
  3790. // act as getter
  3791. if (y == null)
  3792. return typeof oy === 'number' ? oy - o : oy
  3793. return this.attr('y', typeof y.valueOf() === 'number' ? y + o : y)
  3794. }
  3795. // Move center over x-axis
  3796. , cx: function(x) {
  3797. return x == null ? this.bbox().cx : this.x(x - this.bbox().width / 2)
  3798. }
  3799. // Move center over y-axis
  3800. , cy: function(y) {
  3801. return y == null ? this.bbox().cy : this.y(y - this.bbox().height / 2)
  3802. }
  3803. // Set the text content
  3804. , text: function(text) {
  3805. // act as getter
  3806. if (typeof text === 'undefined'){
  3807. var text = ''
  3808. var children = this.node.childNodes
  3809. for(var i = 0, len = children.length; i < len; ++i){
  3810. // add newline if its not the first child and newLined is set to true
  3811. if(i != 0 && children[i].nodeType != 3 && SVG.adopt(children[i]).dom.newLined == true){
  3812. text += '\n'
  3813. }
  3814. // add content of this node
  3815. text += children[i].textContent
  3816. }
  3817. return text
  3818. }
  3819. // remove existing content
  3820. this.clear().build(true)
  3821. if (typeof text === 'function') {
  3822. // call block
  3823. text.call(this, this)
  3824. } else {
  3825. // store text and make sure text is not blank
  3826. text = text.split('\n')
  3827. // build new lines
  3828. for (var i = 0, il = text.length; i < il; i++)
  3829. this.tspan(text[i]).newLine()
  3830. }
  3831. // disable build mode and rebuild lines
  3832. return this.build(false).rebuild()
  3833. }
  3834. // Set font size
  3835. , size: function(size) {
  3836. return this.attr('font-size', size).rebuild()
  3837. }
  3838. // Set / get leading
  3839. , leading: function(value) {
  3840. // act as getter
  3841. if (value == null)
  3842. return this.dom.leading
  3843. // act as setter
  3844. this.dom.leading = new SVG.Number(value)
  3845. return this.rebuild()
  3846. }
  3847. // Get all the first level lines
  3848. , lines: function() {
  3849. var node = (this.textPath && this.textPath() || this).node
  3850. // filter tspans and map them to SVG.js instances
  3851. var lines = SVG.utils.map(SVG.utils.filterSVGElements(node.childNodes), function(el){
  3852. return SVG.adopt(el)
  3853. })
  3854. // return an instance of SVG.set
  3855. return new SVG.Set(lines)
  3856. }
  3857. // Rebuild appearance type
  3858. , rebuild: function(rebuild) {
  3859. // store new rebuild flag if given
  3860. if (typeof rebuild == 'boolean')
  3861. this._rebuild = rebuild
  3862. // define position of all lines
  3863. if (this._rebuild) {
  3864. var self = this
  3865. , blankLineOffset = 0
  3866. , dy = this.dom.leading * new SVG.Number(this.attr('font-size'))
  3867. this.lines().each(function() {
  3868. if (this.dom.newLined) {
  3869. if (!self.textPath())
  3870. this.attr('x', self.attr('x'))
  3871. if(this.text() == '\n') {
  3872. blankLineOffset += dy
  3873. }else{
  3874. this.attr('dy', dy + blankLineOffset)
  3875. blankLineOffset = 0
  3876. }
  3877. }
  3878. })
  3879. this.fire('rebuild')
  3880. }
  3881. return this
  3882. }
  3883. // Enable / disable build mode
  3884. , build: function(build) {
  3885. this._build = !!build
  3886. return this
  3887. }
  3888. // overwrite method from parent to set data properly
  3889. , setData: function(o){
  3890. this.dom = o
  3891. this.dom.leading = new SVG.Number(o.leading || 1.3)
  3892. return this
  3893. }
  3894. }
  3895. // Add parent method
  3896. , construct: {
  3897. // Create text element
  3898. text: function(text) {
  3899. return this.put(new SVG.Text).text(text)
  3900. }
  3901. // Create plain text element
  3902. , plain: function(text) {
  3903. return this.put(new SVG.Text).plain(text)
  3904. }
  3905. }
  3906. })
  3907. SVG.Tspan = SVG.invent({
  3908. // Initialize node
  3909. create: 'tspan'
  3910. // Inherit from
  3911. , inherit: SVG.Shape
  3912. // Add class methods
  3913. , extend: {
  3914. // Set text content
  3915. text: function(text) {
  3916. if(text == null) return this.node.textContent + (this.dom.newLined ? '\n' : '')
  3917. typeof text === 'function' ? text.call(this, this) : this.plain(text)
  3918. return this
  3919. }
  3920. // Shortcut dx
  3921. , dx: function(dx) {
  3922. return this.attr('dx', dx)
  3923. }
  3924. // Shortcut dy
  3925. , dy: function(dy) {
  3926. return this.attr('dy', dy)
  3927. }
  3928. // Create new line
  3929. , newLine: function() {
  3930. // fetch text parent
  3931. var t = this.parent(SVG.Text)
  3932. // mark new line
  3933. this.dom.newLined = true
  3934. // apply new hy¡n
  3935. return this.dy(t.dom.leading * t.attr('font-size')).attr('x', t.x())
  3936. }
  3937. }
  3938. })
  3939. SVG.extend(SVG.Text, SVG.Tspan, {
  3940. // Create plain text node
  3941. plain: function(text) {
  3942. // clear if build mode is disabled
  3943. if (this._build === false)
  3944. this.clear()
  3945. // create text node
  3946. this.node.appendChild(document.createTextNode(text))
  3947. return this
  3948. }
  3949. // Create a tspan
  3950. , tspan: function(text) {
  3951. var node = (this.textPath && this.textPath() || this).node
  3952. , tspan = new SVG.Tspan
  3953. // clear if build mode is disabled
  3954. if (this._build === false)
  3955. this.clear()
  3956. // add new tspan
  3957. node.appendChild(tspan.node)
  3958. return tspan.text(text)
  3959. }
  3960. // Clear all lines
  3961. , clear: function() {
  3962. var node = (this.textPath && this.textPath() || this).node
  3963. // remove existing child nodes
  3964. while (node.hasChildNodes())
  3965. node.removeChild(node.lastChild)
  3966. return this
  3967. }
  3968. // Get length of text element
  3969. , length: function() {
  3970. return this.node.getComputedTextLength()
  3971. }
  3972. })
  3973. SVG.TextPath = SVG.invent({
  3974. // Initialize node
  3975. create: 'textPath'
  3976. // Inherit from
  3977. , inherit: SVG.Parent
  3978. // Define parent class
  3979. , parent: SVG.Text
  3980. // Add parent method
  3981. , construct: {
  3982. morphArray: SVG.PathArray
  3983. // Create path for text to run on
  3984. , path: function(d) {
  3985. // create textPath element
  3986. var path = new SVG.TextPath
  3987. , track = this.doc().defs().path(d)
  3988. // move lines to textpath
  3989. while (this.node.hasChildNodes())
  3990. path.node.appendChild(this.node.firstChild)
  3991. // add textPath element as child node
  3992. this.node.appendChild(path.node)
  3993. // link textPath to path and add content
  3994. path.attr('href', '#' + track, SVG.xlink)
  3995. return this
  3996. }
  3997. // return the array of the path track element
  3998. , array: function() {
  3999. var track = this.track()
  4000. return track ? track.array() : null
  4001. }
  4002. // Plot path if any
  4003. , plot: function(d) {
  4004. var track = this.track()
  4005. , pathArray = null
  4006. if (track) {
  4007. pathArray = track.plot(d)
  4008. }
  4009. return (d == null) ? pathArray : this
  4010. }
  4011. // Get the path track element
  4012. , track: function() {
  4013. var path = this.textPath()
  4014. if (path)
  4015. return path.reference('href')
  4016. }
  4017. // Get the textPath child
  4018. , textPath: function() {
  4019. if (this.node.firstChild && this.node.firstChild.nodeName == 'textPath')
  4020. return SVG.adopt(this.node.firstChild)
  4021. }
  4022. }
  4023. })
  4024. SVG.Nested = SVG.invent({
  4025. // Initialize node
  4026. create: function() {
  4027. this.constructor.call(this, SVG.create('svg'))
  4028. this.style('overflow', 'visible')
  4029. }
  4030. // Inherit from
  4031. , inherit: SVG.Container
  4032. // Add parent method
  4033. , construct: {
  4034. // Create nested svg document
  4035. nested: function() {
  4036. return this.put(new SVG.Nested)
  4037. }
  4038. }
  4039. })
  4040. SVG.A = SVG.invent({
  4041. // Initialize node
  4042. create: 'a'
  4043. // Inherit from
  4044. , inherit: SVG.Container
  4045. // Add class methods
  4046. , extend: {
  4047. // Link url
  4048. to: function(url) {
  4049. return this.attr('href', url, SVG.xlink)
  4050. }
  4051. // Link show attribute
  4052. , show: function(target) {
  4053. return this.attr('show', target, SVG.xlink)
  4054. }
  4055. // Link target attribute
  4056. , target: function(target) {
  4057. return this.attr('target', target)
  4058. }
  4059. }
  4060. // Add parent method
  4061. , construct: {
  4062. // Create a hyperlink element
  4063. link: function(url) {
  4064. return this.put(new SVG.A).to(url)
  4065. }
  4066. }
  4067. })
  4068. SVG.extend(SVG.Element, {
  4069. // Create a hyperlink element
  4070. linkTo: function(url) {
  4071. var link = new SVG.A
  4072. if (typeof url == 'function')
  4073. url.call(link, link)
  4074. else
  4075. link.to(url)
  4076. return this.parent().put(link).put(this)
  4077. }
  4078. })
  4079. SVG.Marker = SVG.invent({
  4080. // Initialize node
  4081. create: 'marker'
  4082. // Inherit from
  4083. , inherit: SVG.Container
  4084. // Add class methods
  4085. , extend: {
  4086. // Set width of element
  4087. width: function(width) {
  4088. return this.attr('markerWidth', width)
  4089. }
  4090. // Set height of element
  4091. , height: function(height) {
  4092. return this.attr('markerHeight', height)
  4093. }
  4094. // Set marker refX and refY
  4095. , ref: function(x, y) {
  4096. return this.attr('refX', x).attr('refY', y)
  4097. }
  4098. // Update marker
  4099. , update: function(block) {
  4100. // remove all content
  4101. this.clear()
  4102. // invoke passed block
  4103. if (typeof block == 'function')
  4104. block.call(this, this)
  4105. return this
  4106. }
  4107. // Return the fill id
  4108. , toString: function() {
  4109. return 'url(#' + this.id() + ')'
  4110. }
  4111. }
  4112. // Add parent method
  4113. , construct: {
  4114. marker: function(width, height, block) {
  4115. // Create marker element in defs
  4116. return this.defs().marker(width, height, block)
  4117. }
  4118. }
  4119. })
  4120. SVG.extend(SVG.Defs, {
  4121. // Create marker
  4122. marker: function(width, height, block) {
  4123. // Set default viewbox to match the width and height, set ref to cx and cy and set orient to auto
  4124. return this.put(new SVG.Marker)
  4125. .size(width, height)
  4126. .ref(width / 2, height / 2)
  4127. .viewbox(0, 0, width, height)
  4128. .attr('orient', 'auto')
  4129. .update(block)
  4130. }
  4131. })
  4132. SVG.extend(SVG.Line, SVG.Polyline, SVG.Polygon, SVG.Path, {
  4133. // Create and attach markers
  4134. marker: function(marker, width, height, block) {
  4135. var attr = ['marker']
  4136. // Build attribute name
  4137. if (marker != 'all') attr.push(marker)
  4138. attr = attr.join('-')
  4139. // Set marker attribute
  4140. marker = arguments[1] instanceof SVG.Marker ?
  4141. arguments[1] :
  4142. this.doc().marker(width, height, block)
  4143. return this.attr(attr, marker)
  4144. }
  4145. })
  4146. // Define list of available attributes for stroke and fill
  4147. var sugar = {
  4148. stroke: ['color', 'width', 'opacity', 'linecap', 'linejoin', 'miterlimit', 'dasharray', 'dashoffset']
  4149. , fill: ['color', 'opacity', 'rule']
  4150. , prefix: function(t, a) {
  4151. return a == 'color' ? t : t + '-' + a
  4152. }
  4153. }
  4154. // Add sugar for fill and stroke
  4155. ;['fill', 'stroke'].forEach(function(m) {
  4156. var i, extension = {}
  4157. extension[m] = function(o) {
  4158. if (typeof o == 'undefined')
  4159. return this
  4160. if (typeof o == 'string' || SVG.Color.isRgb(o) || (o && typeof o.fill === 'function'))
  4161. this.attr(m, o)
  4162. else
  4163. // set all attributes from sugar.fill and sugar.stroke list
  4164. for (i = sugar[m].length - 1; i >= 0; i--)
  4165. if (o[sugar[m][i]] != null)
  4166. this.attr(sugar.prefix(m, sugar[m][i]), o[sugar[m][i]])
  4167. return this
  4168. }
  4169. SVG.extend(SVG.Element, SVG.FX, extension)
  4170. })
  4171. SVG.extend(SVG.Element, SVG.FX, {
  4172. // Map rotation to transform
  4173. rotate: function(d, cx, cy) {
  4174. return this.transform({ rotation: d, cx: cx, cy: cy })
  4175. }
  4176. // Map skew to transform
  4177. , skew: function(x, y, cx, cy) {
  4178. return arguments.length == 1 || arguments.length == 3 ?
  4179. this.transform({ skew: x, cx: y, cy: cx }) :
  4180. this.transform({ skewX: x, skewY: y, cx: cx, cy: cy })
  4181. }
  4182. // Map scale to transform
  4183. , scale: function(x, y, cx, cy) {
  4184. return arguments.length == 1 || arguments.length == 3 ?
  4185. this.transform({ scale: x, cx: y, cy: cx }) :
  4186. this.transform({ scaleX: x, scaleY: y, cx: cx, cy: cy })
  4187. }
  4188. // Map translate to transform
  4189. , translate: function(x, y) {
  4190. return this.transform({ x: x, y: y })
  4191. }
  4192. // Map flip to transform
  4193. , flip: function(a, o) {
  4194. o = typeof a == 'number' ? a : o
  4195. return this.transform({ flip: a || 'both', offset: o })
  4196. }
  4197. // Map matrix to transform
  4198. , matrix: function(m) {
  4199. return this.attr('transform', new SVG.Matrix(arguments.length == 6 ? [].slice.call(arguments) : m))
  4200. }
  4201. // Opacity
  4202. , opacity: function(value) {
  4203. return this.attr('opacity', value)
  4204. }
  4205. // Relative move over x axis
  4206. , dx: function(x) {
  4207. return this.x(new SVG.Number(x).plus(this instanceof SVG.FX ? 0 : this.x()), true)
  4208. }
  4209. // Relative move over y axis
  4210. , dy: function(y) {
  4211. return this.y(new SVG.Number(y).plus(this instanceof SVG.FX ? 0 : this.y()), true)
  4212. }
  4213. // Relative move over x and y axes
  4214. , dmove: function(x, y) {
  4215. return this.dx(x).dy(y)
  4216. }
  4217. })
  4218. SVG.extend(SVG.Rect, SVG.Ellipse, SVG.Circle, SVG.Gradient, SVG.FX, {
  4219. // Add x and y radius
  4220. radius: function(x, y) {
  4221. var type = (this._target || this).type;
  4222. return type == 'radial' || type == 'circle' ?
  4223. this.attr('r', new SVG.Number(x)) :
  4224. this.rx(x).ry(y == null ? x : y)
  4225. }
  4226. })
  4227. SVG.extend(SVG.Path, {
  4228. // Get path length
  4229. length: function() {
  4230. return this.node.getTotalLength()
  4231. }
  4232. // Get point at length
  4233. , pointAt: function(length) {
  4234. return this.node.getPointAtLength(length)
  4235. }
  4236. })
  4237. SVG.extend(SVG.Parent, SVG.Text, SVG.Tspan, SVG.FX, {
  4238. // Set font
  4239. font: function(a, v) {
  4240. if (typeof a == 'object') {
  4241. for (v in a) this.font(v, a[v])
  4242. }
  4243. return a == 'leading' ?
  4244. this.leading(v) :
  4245. a == 'anchor' ?
  4246. this.attr('text-anchor', v) :
  4247. a == 'size' || a == 'family' || a == 'weight' || a == 'stretch' || a == 'variant' || a == 'style' ?
  4248. this.attr('font-'+ a, v) :
  4249. this.attr(a, v)
  4250. }
  4251. })
  4252. SVG.Set = SVG.invent({
  4253. // Initialize
  4254. create: function(members) {
  4255. if (members instanceof SVG.Set) {
  4256. this.members = members.members.slice()
  4257. } else {
  4258. Array.isArray(members) ? this.members = members : this.clear()
  4259. }
  4260. }
  4261. // Add class methods
  4262. , extend: {
  4263. // Add element to set
  4264. add: function() {
  4265. var i, il, elements = [].slice.call(arguments)
  4266. for (i = 0, il = elements.length; i < il; i++)
  4267. this.members.push(elements[i])
  4268. return this
  4269. }
  4270. // Remove element from set
  4271. , remove: function(element) {
  4272. var i = this.index(element)
  4273. // remove given child
  4274. if (i > -1)
  4275. this.members.splice(i, 1)
  4276. return this
  4277. }
  4278. // Iterate over all members
  4279. , each: function(block) {
  4280. for (var i = 0, il = this.members.length; i < il; i++)
  4281. block.apply(this.members[i], [i, this.members])
  4282. return this
  4283. }
  4284. // Restore to defaults
  4285. , clear: function() {
  4286. // initialize store
  4287. this.members = []
  4288. return this
  4289. }
  4290. // Get the length of a set
  4291. , length: function() {
  4292. return this.members.length
  4293. }
  4294. // Checks if a given element is present in set
  4295. , has: function(element) {
  4296. return this.index(element) >= 0
  4297. }
  4298. // retuns index of given element in set
  4299. , index: function(element) {
  4300. return this.members.indexOf(element)
  4301. }
  4302. // Get member at given index
  4303. , get: function(i) {
  4304. return this.members[i]
  4305. }
  4306. // Get first member
  4307. , first: function() {
  4308. return this.get(0)
  4309. }
  4310. // Get last member
  4311. , last: function() {
  4312. return this.get(this.members.length - 1)
  4313. }
  4314. // Default value
  4315. , valueOf: function() {
  4316. return this.members
  4317. }
  4318. // Get the bounding box of all members included or empty box if set has no items
  4319. , bbox: function(){
  4320. // return an empty box of there are no members
  4321. if (this.members.length == 0)
  4322. return new SVG.RBox()
  4323. // get the first rbox and update the target bbox
  4324. var rbox = this.members[0].rbox(this.members[0].doc())
  4325. this.each(function() {
  4326. // user rbox for correct position and visual representation
  4327. rbox = rbox.merge(this.rbox(this.doc()))
  4328. })
  4329. return rbox
  4330. }
  4331. }
  4332. // Add parent method
  4333. , construct: {
  4334. // Create a new set
  4335. set: function(members) {
  4336. return new SVG.Set(members)
  4337. }
  4338. }
  4339. })
  4340. SVG.FX.Set = SVG.invent({
  4341. // Initialize node
  4342. create: function(set) {
  4343. // store reference to set
  4344. this.set = set
  4345. }
  4346. })
  4347. // Alias methods
  4348. SVG.Set.inherit = function() {
  4349. var m
  4350. , methods = []
  4351. // gather shape methods
  4352. for(var m in SVG.Shape.prototype)
  4353. if (typeof SVG.Shape.prototype[m] == 'function' && typeof SVG.Set.prototype[m] != 'function')
  4354. methods.push(m)
  4355. // apply shape aliasses
  4356. methods.forEach(function(method) {
  4357. SVG.Set.prototype[method] = function() {
  4358. for (var i = 0, il = this.members.length; i < il; i++)
  4359. if (this.members[i] && typeof this.members[i][method] == 'function')
  4360. this.members[i][method].apply(this.members[i], arguments)
  4361. return method == 'animate' ? (this.fx || (this.fx = new SVG.FX.Set(this))) : this
  4362. }
  4363. })
  4364. // clear methods for the next round
  4365. methods = []
  4366. // gather fx methods
  4367. for(var m in SVG.FX.prototype)
  4368. if (typeof SVG.FX.prototype[m] == 'function' && typeof SVG.FX.Set.prototype[m] != 'function')
  4369. methods.push(m)
  4370. // apply fx aliasses
  4371. methods.forEach(function(method) {
  4372. SVG.FX.Set.prototype[method] = function() {
  4373. for (var i = 0, il = this.set.members.length; i < il; i++)
  4374. this.set.members[i].fx[method].apply(this.set.members[i].fx, arguments)
  4375. return this
  4376. }
  4377. })
  4378. }
  4379. SVG.extend(SVG.Element, {
  4380. // Store data values on svg nodes
  4381. data: function(a, v, r) {
  4382. if (typeof a == 'object') {
  4383. for (v in a)
  4384. this.data(v, a[v])
  4385. } else if (arguments.length < 2) {
  4386. try {
  4387. return JSON.parse(this.attr('data-' + a))
  4388. } catch(e) {
  4389. return this.attr('data-' + a)
  4390. }
  4391. } else {
  4392. this.attr(
  4393. 'data-' + a
  4394. , v === null ?
  4395. null :
  4396. r === true || typeof v === 'string' || typeof v === 'number' ?
  4397. v :
  4398. JSON.stringify(v)
  4399. )
  4400. }
  4401. return this
  4402. }
  4403. })
  4404. SVG.extend(SVG.Element, {
  4405. // Remember arbitrary data
  4406. remember: function(k, v) {
  4407. // remember every item in an object individually
  4408. if (typeof arguments[0] == 'object')
  4409. for (var v in k)
  4410. this.remember(v, k[v])
  4411. // retrieve memory
  4412. else if (arguments.length == 1)
  4413. return this.memory()[k]
  4414. // store memory
  4415. else
  4416. this.memory()[k] = v
  4417. return this
  4418. }
  4419. // Erase a given memory
  4420. , forget: function() {
  4421. if (arguments.length == 0)
  4422. this._memory = {}
  4423. else
  4424. for (var i = arguments.length - 1; i >= 0; i--)
  4425. delete this.memory()[arguments[i]]
  4426. return this
  4427. }
  4428. // Initialize or return local memory object
  4429. , memory: function() {
  4430. return this._memory || (this._memory = {})
  4431. }
  4432. })
  4433. // Method for getting an element by id
  4434. SVG.get = function(id) {
  4435. var node = document.getElementById(idFromReference(id) || id)
  4436. return SVG.adopt(node)
  4437. }
  4438. // Select elements by query string
  4439. SVG.select = function(query, parent) {
  4440. return new SVG.Set(
  4441. SVG.utils.map((parent || document).querySelectorAll(query), function(node) {
  4442. return SVG.adopt(node)
  4443. })
  4444. )
  4445. }
  4446. SVG.extend(SVG.Parent, {
  4447. // Scoped select method
  4448. select: function(query) {
  4449. return SVG.select(query, this.node)
  4450. }
  4451. })
  4452. function pathRegReplace(a, b, c, d) {
  4453. return c + d.replace(SVG.regex.dots, ' .')
  4454. }
  4455. // creates deep clone of array
  4456. function array_clone(arr){
  4457. var clone = arr.slice(0)
  4458. for(var i = clone.length; i--;){
  4459. if(Array.isArray(clone[i])){
  4460. clone[i] = array_clone(clone[i])
  4461. }
  4462. }
  4463. return clone
  4464. }
  4465. // tests if a given element is instance of an object
  4466. function is(el, obj){
  4467. return el instanceof obj
  4468. }
  4469. // tests if a given selector matches an element
  4470. function matches(el, selector) {
  4471. return (el.matches || el.matchesSelector || el.msMatchesSelector || el.mozMatchesSelector || el.webkitMatchesSelector || el.oMatchesSelector).call(el, selector);
  4472. }
  4473. // Convert dash-separated-string to camelCase
  4474. function camelCase(s) {
  4475. return s.toLowerCase().replace(/-(.)/g, function(m, g) {
  4476. return g.toUpperCase()
  4477. })
  4478. }
  4479. // Capitalize first letter of a string
  4480. function capitalize(s) {
  4481. return s.charAt(0).toUpperCase() + s.slice(1)
  4482. }
  4483. // Ensure to six-based hex
  4484. function fullHex(hex) {
  4485. return hex.length == 4 ?
  4486. [ '#',
  4487. hex.substring(1, 2), hex.substring(1, 2)
  4488. , hex.substring(2, 3), hex.substring(2, 3)
  4489. , hex.substring(3, 4), hex.substring(3, 4)
  4490. ].join('') : hex
  4491. }
  4492. // Component to hex value
  4493. function compToHex(comp) {
  4494. var hex = comp.toString(16)
  4495. return hex.length == 1 ? '0' + hex : hex
  4496. }
  4497. // Calculate proportional width and height values when necessary
  4498. function proportionalSize(element, width, height) {
  4499. if (width == null || height == null) {
  4500. var box = element.bbox()
  4501. if (width == null)
  4502. width = box.width / box.height * height
  4503. else if (height == null)
  4504. height = box.height / box.width * width
  4505. }
  4506. return {
  4507. width: width
  4508. , height: height
  4509. }
  4510. }
  4511. // Delta transform point
  4512. function deltaTransformPoint(matrix, x, y) {
  4513. return {
  4514. x: x * matrix.a + y * matrix.c + 0
  4515. , y: x * matrix.b + y * matrix.d + 0
  4516. }
  4517. }
  4518. // Map matrix array to object
  4519. function arrayToMatrix(a) {
  4520. return { a: a[0], b: a[1], c: a[2], d: a[3], e: a[4], f: a[5] }
  4521. }
  4522. // Parse matrix if required
  4523. function parseMatrix(matrix) {
  4524. if (!(matrix instanceof SVG.Matrix))
  4525. matrix = new SVG.Matrix(matrix)
  4526. return matrix
  4527. }
  4528. // Add centre point to transform object
  4529. function ensureCentre(o, target) {
  4530. o.cx = o.cx == null ? target.bbox().cx : o.cx
  4531. o.cy = o.cy == null ? target.bbox().cy : o.cy
  4532. }
  4533. // PathArray Helpers
  4534. function arrayToString(a) {
  4535. for (var i = 0, il = a.length, s = ''; i < il; i++) {
  4536. s += a[i][0]
  4537. if (a[i][1] != null) {
  4538. s += a[i][1]
  4539. if (a[i][2] != null) {
  4540. s += ' '
  4541. s += a[i][2]
  4542. if (a[i][3] != null) {
  4543. s += ' '
  4544. s += a[i][3]
  4545. s += ' '
  4546. s += a[i][4]
  4547. if (a[i][5] != null) {
  4548. s += ' '
  4549. s += a[i][5]
  4550. s += ' '
  4551. s += a[i][6]
  4552. if (a[i][7] != null) {
  4553. s += ' '
  4554. s += a[i][7]
  4555. }
  4556. }
  4557. }
  4558. }
  4559. }
  4560. }
  4561. return s + ' '
  4562. }
  4563. // Deep new id assignment
  4564. function assignNewId(node) {
  4565. // do the same for SVG child nodes as well
  4566. for (var i = node.childNodes.length - 1; i >= 0; i--)
  4567. if (node.childNodes[i] instanceof window.SVGElement)
  4568. assignNewId(node.childNodes[i])
  4569. return SVG.adopt(node).id(SVG.eid(node.nodeName))
  4570. }
  4571. // Add more bounding box properties
  4572. function fullBox(b) {
  4573. if (b.x == null) {
  4574. b.x = 0
  4575. b.y = 0
  4576. b.width = 0
  4577. b.height = 0
  4578. }
  4579. b.w = b.width
  4580. b.h = b.height
  4581. b.x2 = b.x + b.width
  4582. b.y2 = b.y + b.height
  4583. b.cx = b.x + b.width / 2
  4584. b.cy = b.y + b.height / 2
  4585. return b
  4586. }
  4587. // Get id from reference string
  4588. function idFromReference(url) {
  4589. var m = (url || '').toString().match(SVG.regex.reference)
  4590. if (m) return m[1]
  4591. }
  4592. // If values like 1e-88 are passed, this is not a valid 32 bit float,
  4593. // but in those cases, we are so close to 0 that 0 works well!
  4594. function float32String(v) {
  4595. return Math.abs(v) > 1e-37 ? v : 0
  4596. }
  4597. // Create matrix array for looping
  4598. var abcdef = 'abcdef'.split('')
  4599. // Add CustomEvent to IE9 and IE10
  4600. if (typeof window.CustomEvent !== 'function') {
  4601. // Code from: https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent
  4602. var CustomEventPoly = function(event, options) {
  4603. options = options || { bubbles: false, cancelable: false, detail: undefined }
  4604. var e = document.createEvent('CustomEvent')
  4605. e.initCustomEvent(event, options.bubbles, options.cancelable, options.detail)
  4606. return e
  4607. }
  4608. CustomEventPoly.prototype = window.Event.prototype
  4609. SVG.CustomEvent = CustomEventPoly
  4610. } else {
  4611. SVG.CustomEvent = window.CustomEvent
  4612. }
  4613. // requestAnimationFrame / cancelAnimationFrame Polyfill with fallback based on Paul Irish
  4614. (function(w) {
  4615. var lastTime = 0
  4616. var vendors = ['moz', 'webkit']
  4617. for(var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) {
  4618. w.requestAnimationFrame = w[vendors[x] + 'RequestAnimationFrame']
  4619. w.cancelAnimationFrame = w[vendors[x] + 'CancelAnimationFrame'] ||
  4620. w[vendors[x] + 'CancelRequestAnimationFrame']
  4621. }
  4622. w.requestAnimationFrame = w.requestAnimationFrame ||
  4623. function(callback) {
  4624. var currTime = new Date().getTime()
  4625. var timeToCall = Math.max(0, 16 - (currTime - lastTime))
  4626. var id = w.setTimeout(function() {
  4627. callback(currTime + timeToCall)
  4628. }, timeToCall)
  4629. lastTime = currTime + timeToCall
  4630. return id
  4631. }
  4632. w.cancelAnimationFrame = w.cancelAnimationFrame || w.clearTimeout;
  4633. }(window))
  4634. return SVG
  4635. }));