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