From 685d53295dd005c6f513b6123d8cd3fb3e671c8a Mon Sep 17 00:00:00 2001 From: Saivan Date: Tue, 15 May 2018 15:03:28 +1000 Subject: [PATCH] The timeline is now decoupled from the real time This commit allows the timeline to exist independently of the actual time. This will allow it to be used to seek through an animation with relative ease. We also made some architectural changes to the timeline to support this. Changes ======= - Refactored the runner into its own file without exposing it to the user (changed the gulpfile) - The timeline no longer depends on the current time - The user can supply absolute times to queue events - Some more methods have been integrated into the timeline --- dirty.html | 50 +- dist/svg.js | 9189 +++++++++++++++++++++++----------------------- dist/svg.min.js | 4 +- gulpfile.js | 7 + src/array.js | 6 + src/default.js | 2 + src/morph.js | 1 + src/runner.js | 118 + src/timeline.js | 202 +- src/transform.js | 7 +- useCases.md | 44 + 11 files changed, 4897 insertions(+), 4733 deletions(-) create mode 100644 src/runner.js create mode 100644 useCases.md diff --git a/dirty.html b/dirty.html index 0a8b988..e3cc439 100644 --- a/dirty.html +++ b/dirty.html @@ -6,20 +6,20 @@ + - + diff --git a/dist/svg.js b/dist/svg.js index fa5da9d..e97685a 100644 --- a/dist/svg.js +++ b/dist/svg.js @@ -6,4592 +6,4601 @@ * @copyright Wout Fierens * @license MIT * -* BUILT: Tue May 01 2018 19:45:50 GMT+0200 (Mitteleuropäische Sommerzeit) +* BUILT: Mon May 14 2018 23:48:13 GMT+1000 (AEST) */; - -(function(root, factory) { - /* istanbul ignore next */ - if (typeof define === 'function' && define.amd) { - define(function(){ - return factory(root, root.document) - }) - } else if (typeof exports === 'object') { - module.exports = root.document ? factory(root, root.document) : function(w){ return factory(w, w.document) } - } else { - root.SVG = factory(root, root.document) - } -}(typeof window !== "undefined" ? window : this, function(window, document) { - -// Check that our browser supports svg -var supported = !! document.createElementNS && - !! document.createElementNS('http://www.w3.org/2000/svg','svg').createSVGRect - -// If we don't support svg, just exit without doing anything -if (!supported) - return {supported: false} - -// Otherwise, the library will be here -/* global createElement, capitalize */ -/* eslint-disable new-cap */ - -// The main wrapping element -var SVG = this.SVG = function (element) { - if (SVG.supported) { - element = createElement(element) - return element - } -} - -// Svg must be supported if we reached this stage -SVG.supported = true - -// Default namespaces -SVG.ns = 'http://www.w3.org/2000/svg' -SVG.xmlns = 'http://www.w3.org/2000/xmlns/' -SVG.xlink = 'http://www.w3.org/1999/xlink' -SVG.svgjs = 'http://svgjs.com/svgjs' - -// Element id sequence -SVG.did = 1000 - -// Get next named element id -SVG.eid = function (name) { - return 'Svgjs' + capitalize(name) + (SVG.did++) -} - -// Method for element creation -SVG.create = function (name) { - // create element - return document.createElementNS(this.ns, name) -} - -// Method for extending objects -SVG.extend = function (modules, methods) { - var key, i - - modules = Array.isArray(modules) ? modules : [modules] - - for (i = modules.length - 1; i >= 0; i--) { - if (modules[i]) { - for (key in methods) { - modules[i].prototype[key] = methods[key] - } - } - } -} - -// Invent new element -SVG.invent = function (config) { - // Create element initializer - var initializer = typeof config.create === 'function' ? config.create - : function (node) { - SVG.Element.call(this, node || SVG.create(config.create)) - } - - // Inherit prototype - if (config.inherit) { - initializer.prototype = new config.inherit() - initializer.prototype.constructor = initializer - } - - // Extend with methods - if (config.extend) { - SVG.extend(initializer, config.extend) - } - - // Attach construct method to parent - if (config.construct) { SVG.extend(config.parent || SVG.Container, config.construct) } - - return initializer -} - -// Adopt existing svg elements -SVG.adopt = function (node) { - // check for presence of node - if (!node) return null - - // make sure a node isn't already adopted - if (node.instance instanceof SVG.Element) return node.instance - - if (!(node instanceof window.SVGElement)) { - return new SVG.HtmlNode(node) - } - - // initialize variables - var element - - // adopt with element-specific settings - if (node.nodeName === 'svg') { - element = new SVG.Doc(node) - } else if (node.nodeName === 'linearGradient' || node.nodeName === 'radialGradient') { - element = new SVG.Gradient(node) - } else if (SVG[capitalize(node.nodeName)]) { - element = new SVG[capitalize(node.nodeName)](node) - } else { - element = new SVG.Parent(node) - } - - return element -} - -// Storage for regular expressions -SVG.regex = { - // Parse unit value - numberAndUnit: /^([+-]?(\d+(\.\d*)?|\.\d+)(e[+-]?\d+)?)([a-z%]*)$/i, - - // Parse hex value - hex: /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i, - - // Parse rgb value - rgb: /rgb\((\d+),(\d+),(\d+)\)/, - - // Parse reference id - reference: /#([a-z0-9\-_]+)/i, - - // splits a transformation chain - transforms: /\)\s*,?\s*/, - - // Whitespace - whitespace: /\s/g, - - // Test hex value - isHex: /^#[a-f0-9]{3,6}$/i, - - // Test rgb value - isRgb: /^rgb\(/, - - // Test css declaration - isCss: /[^:]+:[^;]+;?/, - - // Test for blank string - isBlank: /^(\s+)?$/, - - // Test for numeric string - isNumber: /^[+-]?(\d+(\.\d*)?|\.\d+)(e[+-]?\d+)?$/i, - - // Test for percent value - isPercent: /^-?[\d.]+%$/, - - // Test for image url - isImage: /\.(jpg|jpeg|png|gif|svg)(\?[^=]+.*)?/i, - - // split at whitespace and comma - delimiter: /[\s,]+/, - - // The following regex are used to parse the d attribute of a path - - // Matches all hyphens which are not after an exponent - hyphen: /([^e])-/gi, - - // Replaces and tests for all path letters - pathLetters: /[MLHVCSQTAZ]/gi, - - // yes we need this one, too - isPathLetter: /[MLHVCSQTAZ]/i, - - // matches 0.154.23.45 - numbersWithDots: /((\d?\.\d+(?:e[+-]?\d+)?)((?:\.\d+(?:e[+-]?\d+)?)+))+/gi, - - // matches . - dots: /\./g -} - - -SVG.utils = { - // Map function - map: function (array, block) { - var i - var il = array.length - var result = [] - - for (i = 0; i < il; i++) { - result.push(block(array[i])) - } - - return result - }, - - // Filter function - filter: function (array, block) { - var i - var il = array.length - var result = [] - - for (i = 0; i < il; i++) { - if (block(array[i])) { result.push(array[i]) } - } - - return result - }, - - // Degrees to radians - radians: function (d) { - return d % 360 * Math.PI / 180 - }, - - // Radians to degrees - degrees: function (r) { - return r * 180 / Math.PI % 360 - }, - - filterSVGElements: function (nodes) { - return this.filter(nodes, function (el) { return el instanceof window.SVGElement }) - } - -} - - -SVG.defaults = { - - // Default animation values - timeline: { - duration: 600, - ease: '>', - delay: 0, - }, - - // Default attribute values - attrs: { - - // fill and stroke - 'fill-opacity': 1, - 'stroke-opacity': 1, - 'stroke-width': 0, - 'stroke-linejoin': 'miter', - 'stroke-linecap': 'butt', - fill: '#000000', - stroke: '#000000', - opacity: 1, - - // position - x: 0, - y: 0, - cx: 0, - cy: 0, - - // size - width: 0, - height: 0, - - // radius - r: 0, - rx: 0, - ry: 0, - - // gradient - offset: 0, - 'stop-opacity': 1, - 'stop-color': '#000000', - - // text - 'font-size': 16, - 'font-family': 'Helvetica, Arial, sans-serif', - 'text-anchor': 'start' - } -} - -SVG.Queue = SVG.invent({ - create: function () { - this._first = null - this._last = null - this.length = 0 - this.id = 0 - }, - - extend: { - push: function (value) { - - // An item stores an id and the provided value - var item = { id: this.id++, value: value } - - // Deal with the queue being empty or populated - if (this._last) { - this._last = this._last.next = item - } else { - this._last = this._first = item - } - - this.length++ - }, - - shift: function () { - if (this.length == 0) { - return - } - - var remove = this._first - this._first = remove.next - this._last = --this.length ? this._last : null - return remove.value - }, - - // Shows us the first item in the list - first: function () { - return this._first && this._first.value - }, - - // Shows us the last item in the list - last: function () { - return this._last && this._last.value - }, - - // Removes the first item from the front where matcher returns true - remove: function (matcher) { - // Find the first match - var previous = null - var current = this._first - while (current) { - - // If we have a match, we are done - if (matcher(current)) break - - // Otherwise, advance both of the pointers - previous = current - current = current.next - } - - // If we got the first item, adjust the first pointer - if (current && current === this._first) - this._first = this._first.next - - // If we got the last item, adjust the last pointer - if (current && current === this._last) - this._last = previous - - // If we got an item, fix the list and return the item - if (current) { - --this.length - - if (previous) { - previous.next = current.next - } - - return current.item - } - } - } -}) - -/* globals fullHex, compToHex */ - -/* - -Color { - constructor (a, b, c, space) { - space: 'hsl' - a: 30 - b: 20 - c: 10 - }, - - toRgb () { return new Color in rgb space } - toHsl () { return new Color in hsl space } - toLab () { return new Color in lab space } - - toArray () { [space, a, b, c] } - fromArray () { convert it back } -} - -// Conversions aren't always exact because of monitor profiles etc... -new Color(h, s, l, 'hsl') !== new Color(r, g, b).hsl() -new Color(100, 100, 100, [space]) -new Color('hsl(30, 20, 10)') - -// Sugar -SVG.rgb(30, 20, 50).lab() -SVG.hsl() -SVG.lab('rgb(100, 100, 100)') -*/ - -// Module for color convertions -SVG.Color = function (color) { - var match - - // initialize defaults - this.r = 0 - this.g = 0 - this.b = 0 - - if (!color) return - - // parse color - if (typeof color === 'string') { - if (SVG.regex.isRgb.test(color)) { - // get rgb values - match = SVG.regex.rgb.exec(color.replace(SVG.regex.whitespace, '')) - - // parse numeric values - this.r = parseInt(match[1]) - this.g = parseInt(match[2]) - this.b = parseInt(match[3]) - } else if (SVG.regex.isHex.test(color)) { - // get hex values - match = SVG.regex.hex.exec(fullHex(color)) - - // parse numeric values - this.r = parseInt(match[1], 16) - this.g = parseInt(match[2], 16) - this.b = parseInt(match[3], 16) - } - } else if (typeof color === 'object') { - this.r = color.r - this.g = color.g - this.b = color.b - } -} - -SVG.extend(SVG.Color, { - // Default to hex conversion - toString: function () { - return this.toHex() - }, - toArray: function () { - return [this.r, this.g, this.b] - }, - fromArray: function (a) { - return new SVG.Color(a[0], a[1], a[2]) - }, - // Build hex value - toHex: function () { - return '#' + - compToHex(this.r) + - compToHex(this.g) + - compToHex(this.b) - }, - // Build rgb value - toRgb: function () { - return 'rgb(' + [this.r, this.g, this.b].join() + ')' - }, - // Calculate true brightness - brightness: function () { - return (this.r / 255 * 0.30) + - (this.g / 255 * 0.59) + - (this.b / 255 * 0.11) - }, - // Make color morphable - morph: function (color) { - this.destination = new SVG.Color(color) - - return this - }, - // Get morphed color at given position - at: function (pos) { - // make sure a destination is defined - if (!this.destination) return this - - // normalise pos - pos = pos < 0 ? 0 : pos > 1 ? 1 : pos - - // generate morphed color - return new SVG.Color({ - r: ~~(this.r + (this.destination.r - this.r) * pos), - g: ~~(this.g + (this.destination.g - this.g) * pos), - b: ~~(this.b + (this.destination.b - this.b) * pos) - }) - } - -}) - -// Testers - -// Test if given value is a color string -SVG.Color.test = function (color) { - color += '' - return SVG.regex.isHex.test(color) || - SVG.regex.isRgb.test(color) -} - -// Test if given value is a rgb object -SVG.Color.isRgb = function (color) { - return color && typeof color.r === 'number' && - typeof color.g === 'number' && - typeof color.b === 'number' -} - -// Test if given value is a color -SVG.Color.isColor = function (color) { - return SVG.Color.isRgb(color) || SVG.Color.test(color) -} - -/* global arrayClone */ - -// Module for array conversion -SVG.Array = function (array, fallback) { - array = (array || []).valueOf() - - // if array is empty and fallback is provided, use fallback - if (array.length === 0 && fallback) { - array = fallback.valueOf() - } - - // parse array - this.value = this.parse(array) -} - -SVG.extend(SVG.Array, { - // Make array morphable - morph: function (array) { - this.destination = this.parse(array) - - // normalize length of arrays - if (this.value.length !== this.destination.length) { - var lastValue = this.value[this.value.length - 1] - var lastDestination = this.destination[this.destination.length - 1] - - while (this.value.length > this.destination.length) { - this.destination.push(lastDestination) - } - while (this.value.length < this.destination.length) { - this.value.push(lastValue) - } - } - - return this - }, - // Clean up any duplicate points - settle: function () { - // find all unique values - for (var i = 0, il = this.value.length, seen = []; i < il; i++) { - if (seen.indexOf(this.value[i]) === -1) { - seen.push(this.value[i]) - } - } - - // set new value - this.value = seen - return seen - }, - // Get morphed array at given position - at: function (pos) { - // make sure a destination is defined - if (!this.destination) return this - - // generate morphed array - for (var i = 0, il = this.value.length, array = []; i < il; i++) { - array.push(this.value[i] + (this.destination[i] - this.value[i]) * pos) - } - - return new SVG.Array(array) - }, - // Convert array to string - toString: function () { - return this.value.join(' ') - }, - // Real value - valueOf: function () { - return this.value - }, - // Parse whitespace separated string - parse: function (array) { - array = array.valueOf() - - // if already is an array, no need to parse it - if (Array.isArray(array)) return array - - return array.trim().split(SVG.regex.delimiter).map(parseFloat) - }, - // Reverse array - reverse: function () { - this.value.reverse() - - return this - }, - clone: function () { - var clone = new this.constructor() - clone.value = arrayClone(this.value) - return clone - } -}) - - -// Poly points array -SVG.PointArray = function (array, fallback) { - SVG.Array.call(this, array, fallback || [[0, 0]]) -} - -// Inherit from SVG.Array -SVG.PointArray.prototype = new SVG.Array() -SVG.PointArray.prototype.constructor = SVG.PointArray - -SVG.extend(SVG.PointArray, { - // Convert array to string - toString: function () { - // convert to a poly point string - for (var i = 0, il = this.value.length, array = []; i < il; i++) { - array.push(this.value[i].join(',')) - } - - return array.join(' ') - }, - - toArray: function () { - return this.value.reduce(function (prev, curr) { - return [].concat.call(prev, curr) - }, []) - }, - - fromArray: function (a) { - return new SVG.PointArray(a) - }, - - // Convert array to line object - toLine: function () { - return { - x1: this.value[0][0], - y1: this.value[0][1], - x2: this.value[1][0], - y2: this.value[1][1] - } - }, - - // Get morphed array at given position - at: function (pos) { - // make sure a destination is defined - if (!this.destination) return this - - // generate morphed point string - for (var i = 0, il = this.value.length, array = []; i < il; i++) { - array.push([ - this.value[i][0] + (this.destination[i][0] - this.value[i][0]) * pos, - this.value[i][1] + (this.destination[i][1] - this.value[i][1]) * pos - ]) - } - - return new SVG.PointArray(array) - }, - - // Parse point string and flat array - parse: function (array) { - var points = [] - - array = array.valueOf() - - // if it is an array - if (Array.isArray(array)) { - // and it is not flat, there is no need to parse it - if (Array.isArray(array[0])) { - return array - } - } else { // Else, it is considered as a string - // parse points - array = array.trim().split(SVG.regex.delimiter).map(parseFloat) - } - - // validate points - https://svgwg.org/svg2-draft/shapes.html#DataTypePoints - // Odd number of coordinates is an error. In such cases, drop the last odd coordinate. - if (array.length % 2 !== 0) array.pop() - - // wrap points in two-tuples and parse points as floats - for (var i = 0, len = array.length; i < len; i = i + 2) { - points.push([ array[i], array[i + 1] ]) - } - - return points - }, - - // Move point string - move: function (x, y) { - var box = this.bbox() - - // get relative offset - x -= box.x - y -= box.y - - // move every point - if (!isNaN(x) && !isNaN(y)) { - for (var i = this.value.length - 1; i >= 0; i--) { - this.value[i] = [this.value[i][0] + x, this.value[i][1] + y] - } - } - - return this - }, - // Resize poly string - size: function (width, height) { - var i - var box = this.bbox() - - // recalculate position of all points according to new size - for (i = this.value.length - 1; i >= 0; i--) { - if (box.width) this.value[i][0] = ((this.value[i][0] - box.x) * width) / box.width + box.x - if (box.height) this.value[i][1] = ((this.value[i][1] - box.y) * height) / box.height + box.y - } - - return this - }, - - // Get bounding box of points - bbox: function () { - var maxX = -Infinity - var maxY = -Infinity - var minX = Infinity - var minY = Infinity - this.value.forEach(function (el) { - maxX = Math.max(el[0], maxX) - maxY = Math.max(el[1], maxY) - minX = Math.min(el[0], minX) - minY = Math.min(el[1], minY) - }) - return {x: minX, y: minY, width: maxX - minX, height: maxY - minY} - } -}) - -/* globals arrayToString, pathRegReplace */ - -var pathHandlers = { - M: function (c, p, p0) { - p.x = p0.x = c[0] - p.y = p0.y = c[1] - - return ['M', p.x, p.y] - }, - L: function (c, p) { - p.x = c[0] - p.y = c[1] - return ['L', c[0], c[1]] - }, - H: function (c, p) { - p.x = c[0] - return ['H', c[0]] - }, - V: function (c, p) { - p.y = c[0] - return ['V', c[0]] - }, - C: function (c, p) { - p.x = c[4] - p.y = c[5] - return ['C', c[0], c[1], c[2], c[3], c[4], c[5]] - }, - S: function (c, p) { - p.x = c[2] - p.y = c[3] - return ['S', c[0], c[1], c[2], c[3]] - }, - Q: function (c, p) { - p.x = c[2] - p.y = c[3] - return ['Q', c[0], c[1], c[2], c[3]] - }, - T: function (c, p) { - p.x = c[0] - p.y = c[1] - return ['T', c[0], c[1]] - }, - Z: function (c, p, p0) { - p.x = p0.x - p.y = p0.y - return ['Z'] - }, - A: function (c, p) { - p.x = c[5] - p.y = c[6] - return ['A', c[0], c[1], c[2], c[3], c[4], c[5], c[6]] - } -} - -var mlhvqtcsaz = 'mlhvqtcsaz'.split('') - -for (var i = 0, il = mlhvqtcsaz.length; i < il; ++i) { - pathHandlers[mlhvqtcsaz[i]] = (function (i) { - return function (c, p, p0) { - if (i === 'H') c[0] = c[0] + p.x - else if (i === 'V') c[0] = c[0] + p.y - else if (i === 'A') { - c[5] = c[5] + p.x - c[6] = c[6] + p.y - } else { - for (var j = 0, jl = c.length; j < jl; ++j) { - c[j] = c[j] + (j % 2 ? p.y : p.x) - } - } - - return pathHandlers[i](c, p, p0) - } - })(mlhvqtcsaz[i].toUpperCase()) -} - -// Path points array -SVG.PathArray = function (array, fallback) { - SVG.Array.call(this, array, fallback || [['M', 0, 0]]) -} - -// Inherit from SVG.Array -SVG.PathArray.prototype = new SVG.Array() -SVG.PathArray.prototype.constructor = SVG.PathArray - -SVG.extend(SVG.PathArray, { - // Convert array to string - toString: function () { - return arrayToString(this.value) - }, - toArray: function () { - return this.value.reduce(function (prev, curr) { - return [].concat.call(prev, curr) - }, []) - }, - fromArray: function (a) { - return new SVG.PathArray(a) - }, - // Move path string - move: function (x, y) { - // get bounding box of current situation - var box = this.bbox() - - // get relative offset - x -= box.x - y -= box.y - - if (!isNaN(x) && !isNaN(y)) { - // move every point - for (var l, i = this.value.length - 1; i >= 0; i--) { - l = this.value[i][0] - - if (l === 'M' || l === 'L' || l === 'T') { - this.value[i][1] += x - this.value[i][2] += y - } else if (l === 'H') { - this.value[i][1] += x - } else if (l === 'V') { - this.value[i][1] += y - } else if (l === 'C' || l === 'S' || l === 'Q') { - this.value[i][1] += x - this.value[i][2] += y - this.value[i][3] += x - this.value[i][4] += y - - if (l === 'C') { - this.value[i][5] += x - this.value[i][6] += y - } - } else if (l === 'A') { - this.value[i][6] += x - this.value[i][7] += y - } - } - } - - return this - }, - // Resize path string - size: function (width, height) { - // get bounding box of current situation - var box = this.bbox() - var i, l - - // recalculate position of all points according to new size - for (i = this.value.length - 1; i >= 0; i--) { - l = this.value[i][0] - - if (l === 'M' || l === 'L' || l === 'T') { - this.value[i][1] = ((this.value[i][1] - box.x) * width) / box.width + box.x - this.value[i][2] = ((this.value[i][2] - box.y) * height) / box.height + box.y - } else if (l === 'H') { - this.value[i][1] = ((this.value[i][1] - box.x) * width) / box.width + box.x - } else if (l === 'V') { - this.value[i][1] = ((this.value[i][1] - box.y) * height) / box.height + box.y - } else if (l === 'C' || l === 'S' || l === 'Q') { - this.value[i][1] = ((this.value[i][1] - box.x) * width) / box.width + box.x - this.value[i][2] = ((this.value[i][2] - box.y) * height) / box.height + box.y - this.value[i][3] = ((this.value[i][3] - box.x) * width) / box.width + box.x - this.value[i][4] = ((this.value[i][4] - box.y) * height) / box.height + box.y - - if (l === 'C') { - this.value[i][5] = ((this.value[i][5] - box.x) * width) / box.width + box.x - this.value[i][6] = ((this.value[i][6] - box.y) * height) / box.height + box.y - } - } else if (l === 'A') { - // resize radii - this.value[i][1] = (this.value[i][1] * width) / box.width - this.value[i][2] = (this.value[i][2] * height) / box.height - - // move position values - this.value[i][6] = ((this.value[i][6] - box.x) * width) / box.width + box.x - this.value[i][7] = ((this.value[i][7] - box.y) * height) / box.height + box.y - } - } - - return this - }, - // Test if the passed path array use the same path data commands as this path array - equalCommands: function (pathArray) { - var i, il, equalCommands - - pathArray = new SVG.PathArray(pathArray) - - equalCommands = this.value.length === pathArray.value.length - for (i = 0, il = this.value.length; equalCommands && i < il; i++) { - equalCommands = this.value[i][0] === pathArray.value[i][0] - } - - return equalCommands - }, - // Make path array morphable - morph: function (pathArray) { - pathArray = new SVG.PathArray(pathArray) - - if (this.equalCommands(pathArray)) { - this.destination = pathArray - } else { - this.destination = null - } - - return this - }, - // Get morphed path array at given position - at: function (pos) { - // make sure a destination is defined - if (!this.destination) return this - - var sourceArray = this.value - var destinationArray = this.destination.value - var array = [] - var pathArray = new SVG.PathArray() - var i, il, j, jl - - // Animate has specified in the SVG spec - // See: https://www.w3.org/TR/SVG11/paths.html#PathElement - for (i = 0, il = sourceArray.length; i < il; i++) { - array[i] = [sourceArray[i][0]] - for (j = 1, jl = sourceArray[i].length; j < jl; j++) { - array[i][j] = sourceArray[i][j] + (destinationArray[i][j] - sourceArray[i][j]) * pos - } - // For the two flags of the elliptical arc command, the SVG spec say: - // Flags and booleans are interpolated as fractions between zero and one, with any non-zero value considered to be a value of one/true - // Elliptical arc command as an array followed by corresponding indexes: - // ['A', rx, ry, x-axis-rotation, large-arc-flag, sweep-flag, x, y] - // 0 1 2 3 4 5 6 7 - if (array[i][0] === 'A') { - array[i][4] = +(array[i][4] !== 0) - array[i][5] = +(array[i][5] !== 0) - } - } - - // Directly modify the value of a path array, this is done this way for performance - pathArray.value = array - return pathArray - }, - // Absolutize and parse path to array - parse: function (array) { - // if it's already a patharray, no need to parse it - if (array instanceof SVG.PathArray) return array.valueOf() - - // prepare for parsing - var s - var paramCnt = { 'M': 2, 'L': 2, 'H': 1, 'V': 1, 'C': 6, 'S': 4, 'Q': 4, 'T': 2, 'A': 7, 'Z': 0 } - - if (typeof array === 'string') { - array = array - .replace(SVG.regex.numbersWithDots, pathRegReplace) // convert 45.123.123 to 45.123 .123 - .replace(SVG.regex.pathLetters, ' $& ') // put some room between letters and numbers - .replace(SVG.regex.hyphen, '$1 -') // add space before hyphen - .trim() // trim - .split(SVG.regex.delimiter) // split into array - } else { - array = array.reduce(function (prev, curr) { - return [].concat.call(prev, curr) - }, []) - } - - // array now is an array containing all parts of a path e.g. ['M', '0', '0', 'L', '30', '30' ...] - var result = [] - var p = new SVG.Point() - var p0 = new SVG.Point() - var index = 0 - var len = array.length - - do { - // Test if we have a path letter - if (SVG.regex.isPathLetter.test(array[index])) { - s = array[index] - ++index - // If last letter was a move command and we got no new, it defaults to [L]ine - } else if (s === 'M') { - s = 'L' - } else if (s === 'm') { - s = 'l' - } - - result.push(pathHandlers[s].call(null, - array.slice(index, (index = index + paramCnt[s.toUpperCase()])).map(parseFloat), - p, p0 - ) - ) - } while (len > index) - - return result - }, - // Get bounding box of path - bbox: function () { - SVG.parser().path.setAttribute('d', this.toString()) - return SVG.parser.nodes.path.getBBox() - } - -}) - - -// Module for unit convertions -SVG.Number = SVG.invent({ - // Initialize - create: function (value, unit) { - // initialize defaults - this.value = 0 - this.unit = unit || '' - - // parse value - if (typeof value === 'number') { - // ensure a valid numeric value - this.value = isNaN(value) ? 0 : !isFinite(value) ? (value < 0 ? -3.4e+38 : +3.4e+38) : value - } else if (typeof value === 'string') { - unit = value.match(SVG.regex.numberAndUnit) - - if (unit) { - // make value numeric - this.value = parseFloat(unit[1]) - - // normalize - if (unit[5] === '%') { this.value /= 100 } else if (unit[5] === 's') { - this.value *= 1000 - } - - // store unit - this.unit = unit[5] - } - } else { - if (value instanceof SVG.Number) { - this.value = value.valueOf() - this.unit = value.unit - } - } - }, - // Add methods - extend: { - // Stringalize - toString: function () { - return (this.unit === '%' ? ~~(this.value * 1e8) / 1e6 - : this.unit === 's' ? this.value / 1e3 - : this.value - ) + this.unit - }, - toJSON: function () { - return this.toString() - }, // Convert to primitive - toArray: function () { - return [this.value] - }, - fromArray: function (val) { - return new SVG.Number(val[0]) - }, - valueOf: function () { - return this.value - }, - // Add number - plus: function (number) { - number = new SVG.Number(number) - return new SVG.Number(this + number, this.unit || number.unit) - }, - // Subtract number - minus: function (number) { - number = new SVG.Number(number) - return new SVG.Number(this - number, this.unit || number.unit) - }, - // Multiply number - times: function (number) { - number = new SVG.Number(number) - return new SVG.Number(this * number, this.unit || number.unit) - }, - // Divide number - divide: function (number) { - number = new SVG.Number(number) - return new SVG.Number(this / number, this.unit || number.unit) - }, - // Convert to different unit - to: function (unit) { - var number = new SVG.Number(this) - - if (typeof unit === 'string') { - number.unit = unit - } - - return number - }, - // Make number morphable - morph: function (number) { - this.destination = new SVG.Number(number) - - if (number.relative) { - this.destination.value += this.value - } - - return this - }, - // Get morphed number at given position - at: function (pos) { - // Make sure a destination is defined - if (!this.destination) return this - - // Generate new morphed number - return new SVG.Number(this.destination) - .minus(this) - .times(pos) - .plus(this) - } - } -}) - -/* global createElement */ - -SVG.HtmlNode = SVG.invent({ - create: function (element) { - this.node = element - }, - - extend: { - add: function (element, i) { - element = createElement(element) - - if (element.node !== this.node.children[i]) { - this.node.insertBefore(element.node, this.node.children[i] || null) - } - - return this - }, - - put: function (element, i) { - this.add(element, i) - return element - } - } -}) - -/* global proportionalSize, assignNewId, createElement, matches, is */ - -SVG.Element = SVG.invent({ - // Initialize node - create: function (node) { - // event listener - this.events = {} - - // initialize data object - this.dom = {} - - // create circular reference - this.node = node - if (this.node) { - this.type = node.nodeName - this.node.instance = this - this.events = node.events || {} - - if (node.hasAttribute('svgjs:data')) { - // pull svgjs data from the dom (getAttributeNS doesn't work in html5) - this.setData(JSON.parse(node.getAttribute('svgjs:data')) || {}) - } - } - }, - - // Add class methods - extend: { - // Move over x-axis - x: function (x) { - return this.attr('x', x) - }, - - // Move over y-axis - y: function (y) { - return this.attr('y', y) - }, - - // Move by center over x-axis - cx: function (x) { - return x == null ? this.x() + this.width() / 2 : this.x(x - this.width() / 2) - }, - - // Move by center over y-axis - cy: function (y) { - return y == null ? this.y() + this.height() / 2 : this.y(y - this.height() / 2) - }, - - // Move element to given x and y values - move: function (x, y) { - return this.x(x).y(y) - }, - - // Move element by its center - center: function (x, y) { - return this.cx(x).cy(y) - }, - - // Set width of element - width: function (width) { - return this.attr('width', width) - }, - - // Set height of element - height: function (height) { - return this.attr('height', height) - }, - - // Set element size to given width and height - size: function (width, height) { - var p = proportionalSize(this, width, height) - - return this - .width(new SVG.Number(p.width)) - .height(new SVG.Number(p.height)) - }, - - // Clone element - clone: function (parent) { - // write dom data to the dom so the clone can pickup the data - this.writeDataToDom() - - // clone element and assign new id - var clone = assignNewId(this.node.cloneNode(true)) - - // insert the clone in the given parent or after myself - if (parent) parent.add(clone) - else this.after(clone) - - return clone - }, - - // Remove element - remove: function () { - if (this.parent()) { this.parent().removeElement(this) } - - return this - }, - - // Replace element - replace: function (element) { - this.after(element).remove() - - return element - }, - - // Add element to given container and return self - addTo: function (parent) { - return createElement(parent).put(this) - }, - - // Add element to given container and return container - putIn: function (parent) { - return createElement(parent).add(this) - }, - - // Get / set id - id: function (id) { - // generate new id if no id set - if (typeof id === 'undefined' && !this.node.id) { - this.node.id = SVG.eid(this.type) - } - - // dont't set directly width this.node.id to make `null` work correctly - return this.attr('id', id) - }, - - // Checks whether the given point inside the bounding box of the element - inside: function (x, y) { - var box = this.bbox() - - return x > box.x && - y > box.y && - x < box.x + box.width && - y < box.y + box.height - }, - - // Show element - show: function () { - return this.css('display', '') - }, - - // Hide element - hide: function () { - return this.css('display', 'none') - }, - - // Is element visible? - visible: function () { - return this.css('display') !== 'none' - }, - - // Return id on string conversion - toString: function () { - return this.id() - }, - - // Return array of classes on the node - classes: function () { - var attr = this.attr('class') - return attr == null ? [] : attr.trim().split(SVG.regex.delimiter) - }, - - // Return true if class exists on the node, false otherwise - hasClass: function (name) { - return this.classes().indexOf(name) !== -1 - }, - - // Add class to the node - addClass: function (name) { - if (!this.hasClass(name)) { - var array = this.classes() - array.push(name) - this.attr('class', array.join(' ')) - } - - return this - }, - - // Remove class from the node - removeClass: function (name) { - if (this.hasClass(name)) { - this.attr('class', this.classes().filter(function (c) { - return c !== name - }).join(' ')) - } - - return this - }, - - // Toggle the presence of a class on the node - toggleClass: function (name) { - return this.hasClass(name) ? this.removeClass(name) : this.addClass(name) - }, - - // Get referenced element form attribute value - reference: function (attr) { - return SVG.get(this.attr(attr)) - }, - - // Returns the parent element instance - parent: function (type) { - var parent = this - - // check for parent - if (!parent.node.parentNode) return null - - // get parent element - parent = SVG.adopt(parent.node.parentNode) - - if (!type) return parent - - // loop trough ancestors if type is given - while (parent && parent.node instanceof window.SVGElement) { - if (typeof type === 'string' ? parent.matches(type) : parent instanceof type) return parent - parent = SVG.adopt(parent.node.parentNode) - } - }, - - // Get parent document - doc: function () { - var p = this.parent(SVG.Doc) - return p && p.doc() - }, - - // Get defs - defs: function () { - return this.doc().defs() - }, - - // return array of all ancestors of given type up to the root svg - parents: function (type) { - var parents = [] - var parent = this - - do { - parent = parent.parent(type) - if (!parent || !parent.node) break - - parents.push(parent) - } while (parent.parent) - - return parents - }, - - // matches the element vs a css selector - matches: function (selector) { - return matches(this.node, selector) - }, - - // Returns the svg node to call native svg methods on it - native: function () { - return this.node - }, - - // Import raw svg - svg: function (svg) { - var well, len - - // act as a setter if svg is given - if (svg && this instanceof SVG.Parent) { - // create temporary holder - well = document.createElementNS(SVG.ns, 'svg') - // dump raw svg - well.innerHTML = svg - - // transplant nodes - for (len = well.children.length; len--;) { - this.node.appendChild(well.firstElementChild) - } - - // otherwise act as a getter - } else { - // write svgjs data to the dom - this.writeDataToDom() - - return this.node.outerHTML - } - - return this - }, - - // write svgjs data to the dom - writeDataToDom: function () { - // dump variables recursively - if (this.is(SVG.Parent)) { - this.each(function () { - this.writeDataToDom() - }) - } - - // remove previously set data - this.node.removeAttribute('svgjs:data') - - if (Object.keys(this.dom).length) { - this.node.setAttribute('svgjs:data', JSON.stringify(this.dom)) // see #428 - } - return this - }, - - // set given data to the elements data property - setData: function (o) { - this.dom = o - return this - }, - is: function (obj) { - return is(this, obj) - } - } -}) - -/* global abcdef, arrayToMatrix, closeEnough, formatTransforms */ - -SVG.Matrix = SVG.invent({ - // Initialize - create: function (source) { - var base = arrayToMatrix([1, 0, 0, 1, 0, 0]) - var i - - // ensure source as object - source = source instanceof SVG.Element ? source.matrixify() - : typeof source === 'string' ? arrayToMatrix(source.split(SVG.regex.delimiter).map(parseFloat)) - : Array.isArray(source) ? arrayToMatrix(source) - : (typeof source === 'object' && ( - source.a != null || source.b != null || source.c != null - || source.d != null || source.e != null || source.f != null - )) ? source - : (typeof source === 'object') ? new SVG.Matrix().transform(source) - : arguments.length === 6 ? arrayToMatrix([].slice.call(arguments)) - : base - - // merge source - for (i = abcdef.length - 1; i >= 0; --i) { - this[abcdef[i]] = source[abcdef[i]] != null - ? source[abcdef[i]] - : base[abcdef[i]] - } - }, - - // Add methods - extend: { - - // Clones this matrix - clone: function () { - return new SVG.Matrix(this) - }, - - // Transform a matrix into another matrix by manipulating the space - transform: function (o) { - // Check if o is a matrix and then left multiply it directly - if (o.a != null) { - var matrix = new SVG.Matrix(o) - var newMatrix = this.lmultiply(matrix) - return newMatrix - } - - // Get the proposed transformations and the current transformations - var t = formatTransforms(o) - var currentTransform = new SVG.Matrix(this) - - // Construct the resulting matrix - var transformer = new SVG.Matrix() - .translate(-t.ox, -t.oy) - .scale(t.scaleX, t.scaleY) - .skew(t.skewX, t.skewY) - .shear(t.shear) - .rotate(t.theta) - .translate(t.ox, t.oy) - .translate(t.rx, t.ry) - .lmultiply(currentTransform) - - // If we want the origin at a particular place, we force it there - if (isFinite(t.px) || isFinite(t.py)) { - - // Figure out where the origin went and the delta to get there - var current = new SVG.Point(t.ox - t.rx, t.oy - t.ry).transform(transformer) - var dx = t.px ? t.px - current.x : 0 - var dy = t.py ? t.py - current.y : 0 - - // Apply another translation - transformer = transformer.translate(dx, dy) - } - - // We can apply translations after everything else - transformer = transformer.translate(t.tx, t.ty) - return transformer - }, - - // Applies a matrix defined by its affine parameters - compose: function (o) { - // Get the parameters - var sx = o.scaleX || 1 - var sy = o.scaleY || 1 - var lam = o.shear || 0 - var theta = o.rotate || 0 - var tx = o.translateX || 0 - var ty = o.translateY || 0 - - // Apply the standard matrix - var result = new SVG.Matrix() - .scale(sx, sy) - .shear(lam) - .rotate(theta) - .translate(tx, ty) - .lmultiply(this) - return result - }, - - // Decomposes this matrix into its affine parameters - decompose: function () { - // Get the parameters from the matrix - var a = this.a - var b = this.b - var c = this.c - var d = this.d - var e = this.e - var f = this.f - - // Figure out if the winding direction is clockwise or counterclockwise - var determinant = a * d - b * c - var ccw = determinant > 0 ? 1 : -1 - - // Since we only shear in x, we can use the x basis to get the x scale - // and the rotation of the resulting matrix - var sx = ccw * Math.sqrt(a * a + b * b) - var theta = 180 / Math.PI * Math.atan2(ccw * b, ccw * a) - - // We can then solve the y basis vector simultaneously to get the other - // two affine parameters directly from these parameters - var lam = (a * c + b * d) / determinant - var sy = ((c * sx) / (lam * a - b)) || ((d * sx) / (lam * b + a)) - - // Construct the decomposition and return it - return { - // Return the affine parameters - scaleX: sx, - scaleY: sy, - shear: lam, - rotate: theta, - translateX: e, - translateY: f, - - // Return the matrix parameters - a: this.a, - b: this.b, - c: this.c, - d: this.d, - e: this.e, - f: this.f - } - }, - - // Morph one matrix into another - morph: function (matrix) { - // Store new destination - this.destination = new SVG.Matrix(matrix) - return this - }, - - // Get morphed matrix at a given position - at: function (pos) { - // Make sure a destination is defined - if (!this.destination) return this - - // Calculate morphed matrix at a given position - var matrix = new SVG.Matrix({ - a: this.a + (this.destination.a - this.a) * pos, - b: this.b + (this.destination.b - this.b) * pos, - c: this.c + (this.destination.c - this.c) * pos, - d: this.d + (this.destination.d - this.d) * pos, - e: this.e + (this.destination.e - this.e) * pos, - f: this.f + (this.destination.f - this.f) * pos - }) - - return matrix - }, - - // Left multiplies by the given matrix - multiply: function (matrix) { - // Get the matrices - var l = this - var r = new SVG.Matrix(matrix) - - // Work out the product directly - var a = l.a * r.a + l.c * r.b - var b = l.b * r.a + l.d * r.b - var c = l.a * r.c + l.c * r.d - var d = l.b * r.c + l.d * r.d - var e = l.e + l.a * r.e + l.c * r.f - var f = l.f + l.b * r.e + l.d * r.f - - // Form the matrix and return it - var product = new SVG.Matrix(a, b, c, d, e, f) - return product - }, - - lmultiply: function (matrix) { - var result = new SVG.Matrix(matrix).multiply(this) - return result - }, - - // Inverses matrix - inverse: function () { - - // Get the current parameters out of the matrix - var a = this.a - var b = this.b - var c = this.c - var d = this.d - var e = this.e - var f = this.f - - // Invert the 2x2 matrix in the top left - var det = a * d - b * c - if (!det) throw new Error("Cannot invert " + this) - - // Calculate the top 2x2 matrix - var na = d / det - var nb = -b / det - var nc = -c / det - var nd = a / det - - // Apply the inverted matrix to the top right - var ne = - ( na * e + nc * f ) - var nf = - ( nb * e + nd * f ) - - // Construct the inverted matrix - return new SVG.Matrix(na, nb, nc, nd, ne, nf) - }, - - // Translate matrix - translate: function (x, y) { - var translation = new SVG.Matrix(this) - translation.e += x || 0 - translation.f += y || 0 - return translation - }, - - // Scale matrix - scale: function (x, y, cx, cy) { - // Support uniform scaling - if (arguments.length === 1) { - y = x - } else if (arguments.length === 3) { - cy = cx - cx = y - y = x - } - - // Scale the current matrix - var scale = new SVG.Matrix(x, 0, 0, y, 0, 0) - var matrix = this.around(cx, cy, scale) - return matrix - }, - - // Rotate matrix - rotate: function (r, cx, cy) { - // Convert degrees to radians - r = SVG.utils.radians(r) - - // Construct the rotation matrix - var rotation = new SVG.Matrix(Math.cos(r), Math.sin(r), -Math.sin(r), Math.cos(r), 0, 0) - var matrix = this.around(cx, cy, rotation) - return matrix - }, - - // Flip matrix on x or y, at a given offset - flip: function (axis, around) { - return axis === 'x' ? this.scale(-1, 1, around, 0) - : axis === 'y' ? this.scale(1, -1, 0, around) - : this.scale(-1, -1, axis, around || axis) // Define an x, y flip point - }, - - // Shear matrix - shear: function (a, cx, cy) { - var shear = new SVG.Matrix(1, 0, a, 1, 0, 0) - var matrix = this.around(cx, cy, shear) - return matrix - }, - - // Skew Matrix - skew: function (x, y, cx, cy) { - // support uniformal skew - if (arguments.length === 1) { - y = x - } else if (arguments.length === 3) { - cy = cx - cx = y - y = x - } - - // Convert degrees to radians - x = SVG.utils.radians(x) - y = SVG.utils.radians(y) - - // Construct the matrix - var skew = new SVG.Matrix(1, Math.tan(y), Math.tan(x), 1, 0, 0) - var matrix = this.around(cx, cy, skew) - return matrix - }, - - // SkewX - skewX: function (x, cx, cy) { - return this.skew(x, 0, cx, cy) - }, - - // SkewY - skewY: function (y, cx, cy) { - return this.skew(0, y, cx, cy) - }, - - // Transform around a center point - around: function (cx, cy, matrix) { - var dx = cx || 0 - var dy = cy || 0 - return this.translate(-dx, -dy).lmultiply(matrix).translate(dx, dy) - }, - - // Convert to native SVGMatrix - native: function () { - // create new matrix - var matrix = SVG.parser.nodes.svg.node.createSVGMatrix() - - // update with current values - for (var i = abcdef.length - 1; i >= 0; i--) { - matrix[abcdef[i]] = this[abcdef[i]] - } - - return matrix - }, - - // Check if two matrices are equal - equals: function (other) { - var comp = new SVG.Matrix(other) - return closeEnough(this.a, comp.a) && closeEnough(this.b, comp.b) && - closeEnough(this.c, comp.c) && closeEnough(this.d, comp.d) && - closeEnough(this.e, comp.e) && closeEnough(this.f, comp.f) - }, - - // Convert matrix to string - toString: function () { - return 'matrix(' + this.a + ',' + this.b + ',' + this.c + ',' + this.d + ',' + this.e + ',' + this.f + ')' - }, - - toArray: function () { - return [this.a, this.b, this.c, this.d, this.e, this.f] - }, - - fromArray: function (a) { - return new SVG.Matrix(a) - } - }, - - // Define parent - parent: SVG.Element, - - // Add parent method - construct: { - // Get current matrix - ctm: function () { - return new SVG.Matrix(this.node.getCTM()) - }, - // Get current screen matrix - screenCTM: function () { - /* https://bugzilla.mozilla.org/show_bug.cgi?id=1344537 - This is needed because FF does not return the transformation matrix - for the inner coordinate system when getScreenCTM() is called on nested svgs. - However all other Browsers do that */ - if (this instanceof SVG.Doc && !this.isRoot()) { - var rect = this.rect(1, 1) - var m = rect.node.getScreenCTM() - rect.remove() - return new SVG.Matrix(m) - } - return new SVG.Matrix(this.node.getScreenCTM()) - } - } -}) - - -SVG.Point = SVG.invent({ - // Initialize - create: function (x, y, base) { - var source - base = base || {x: 0, y: 0} - - // ensure source as object - source = Array.isArray(x) ? {x: x[0], y: x[1]} - : typeof x === 'object' ? {x: x.x, y: x.y} - : {x: x, y: y} - - // merge source - this.x = source.x == null ? base.x : source.x - this.y = source.y == null ? base.y : source.y - }, - - // Add methods - extend: { - // Clone point - clone: function () { - return new SVG.Point(this) - }, - - // Morph one point into another - morph: function (x, y) { - // store new destination - this.destination = new SVG.Point(x, y) - return this - }, - - // Get morphed point at a given position - at: function (pos) { - // make sure a destination is defined - if (!this.destination) return this - - // calculate morphed matrix at a given position - var point = new SVG.Point({ - x: this.x + (this.destination.x - this.x) * pos, - y: this.y + (this.destination.y - this.y) * pos - }) - return point - }, - - // Convert to native SVGPoint - native: function () { - // create new point - var point = SVG.parser.nodes.svg.node.createSVGPoint() - - // update with current values - point.x = this.x - point.y = this.y - return point - }, - - // transform point with matrix - transform: function (matrix) { - return new SVG.Point(this.native().matrixTransform(matrix.native())) - } - } -}) - -SVG.extend(SVG.Element, { - - // Get point - point: function (x, y) { - return new SVG.Point(x, y).transform(this.screenCTM().inverse()) - } -}) - -SVG.extend(SVG.Element, { - // Set svg element attribute - attr: function (a, v, n) { - // act as full getter - if (a == null) { - // get an object of attributes - a = {} - v = this.node.attributes - for (n = v.length - 1; n >= 0; n--) { - a[v[n].nodeName] = SVG.regex.isNumber.test(v[n].nodeValue) - ? parseFloat(v[n].nodeValue) - : v[n].nodeValue - } - return a - } else if (typeof a === 'object') { - // apply every attribute individually if an object is passed - for (v in a) this.attr(v, a[v]) - } else if (v === null) { - // remove value - this.node.removeAttribute(a) - } else if (v == null) { - // act as a getter if the first and only argument is not an object - v = this.node.getAttribute(a) - return v == null ? SVG.defaults.attrs[a] - : SVG.regex.isNumber.test(v) ? parseFloat(v) - : v - } else { - // convert image fill and stroke to patterns - if (a === 'fill' || a === 'stroke') { - if (SVG.regex.isImage.test(v)) { - v = this.doc().defs().image(v) - } - - if (v instanceof SVG.Image) { - v = this.doc().defs().pattern(0, 0, function () { - this.add(v) - }) - } - } - - // ensure correct numeric values (also accepts NaN and Infinity) - if (typeof v === 'number') { - v = new SVG.Number(v) - } else if (SVG.Color.isColor(v)) { - // ensure full hex color - v = new SVG.Color(v) - } else if (Array.isArray(v)) { - // parse array values - v = new SVG.Array(v) - } - - // if the passed attribute is leading... - if (a === 'leading') { - // ... call the leading method instead - if (this.leading) { - this.leading(v) - } - } else { - // set given attribute on node - typeof n === 'string' ? this.node.setAttributeNS(n, a, v.toString()) - : this.node.setAttribute(a, v.toString()) - } - - // rebuild if required - if (this.rebuild && (a === 'font-size' || a === 'x')) { - this.rebuild(a, v) - } - } - - return this - } -}) - -/* global arrayToMatrix */ - -SVG.extend(SVG.Element, { - // Reset all transformations - untransform: function () { - return this.attr('transform', null) - }, - - // merge the whole transformation chain into one matrix and returns it - matrixify: function () { - var matrix = (this.attr('transform') || '') - // split transformations - .split(SVG.regex.transforms).slice(0, -1).map(function (str) { - // generate key => value pairs - var kv = str.trim().split('(') - return [kv[0], - kv[1].split(SVG.regex.delimiter) - .map(function (str) { return parseFloat(str) }) - ] - }) - .reverse() - // merge every transformation into one matrix - .reduce(function (matrix, transform) { - if (transform[0] === 'matrix') { - return matrix.lmultiply(arrayToMatrix(transform[1])) - } - return matrix[transform[0]].apply(matrix, transform[1]) - }, new SVG.Matrix()) - - return matrix - }, - - // add an element to another parent without changing the visual representation on the screen - toParent: function (parent) { - if (this === parent) return this - var ctm = this.screenCTM() - var pCtm = parent.screenCTM().inverse() - - this.addTo(parent).untransform().transform(pCtm.multiply(ctm)) - - return this - }, - - // same as above with parent equals root-svg - toDoc: function () { - return this.toParent(this.doc()) - } -}) - -SVG.extend(SVG.Element, { - - // Add transformations - transform: function (o, relative) { - - // Get the bounding box of the element with no transformations applied - var bbox = this.bbox() - - // Act as a getter if no object was passed - if (o == null || typeof o === 'string') { - var decomposed = new SVG.Matrix(this).decompose() - return decomposed[o] || decomposed - - // Allow the user to define the origin with a string - } else if (typeof o.origin === 'string' || - (o.origin == null && o.ox == null && o.oy == null) - ) { - // Get the bounding box and string to use in our calculations - var string = typeof o.origin === 'string' - ? o.origin.toLowerCase().trim() - : 'center' // We want the center by default - var height = bbox.height - var width = bbox.width - var x = bbox.x - var y = bbox.y - - // Set the bounds eg : "bottom-left", "Top right", "middle" etc... - o.ox = string.includes('left') ? x - : string.includes('right') ? x + width - : x + width / 2 - o.oy = string.includes('top') ? y - : string.includes('bottom') ? y + height - : y + height / 2 - - // Make sure we only pass ox and oy - o.origin = null - } - - // The user can pass a boolean, an SVG.Element or an SVG.Matrix or nothing - var cleanRelative = relative === true ? this : (relative || false) - var result = new SVG.Matrix(cleanRelative).transform(o) - return this.attr('transform', result) - } -}) - -SVG.extend(SVG.Timeline, { - transform: function (o, relative, affine) { - - // // get target in case of the fx module, otherwise reference this - // var target = this.target() - // , matrix, bbox - // - // // act as a getter - // if (typeof o !== 'object') { - // // get current matrix - // matrix = new SVG.Matrix(target).extract() - // - // return typeof o === 'string' ? matrix[o] : matrix - // } - // - // // ensure relative flag - // relative = !!relative || !!o.relative - // - // // act on matrix - // if (o.a != null) { - // matrix = new SVG.Matrix(o) - // - // // act on rotation - // } else if (o.rotation != null) { - // // ensure centre point - // ensureCentre(o, target) - // - // // apply transformation - // matrix = new SVG.Rotate(o.rotation, o.cx, o.cy) - // - // // act on scale - // } else if (o.scale != null || o.scaleX != null || o.scaleY != null) { - // // ensure centre point - // ensureCentre(o, target) - // - // // ensure scale values on both axes - // o.scaleX = o.scale != null ? o.scale : o.scaleX != null ? o.scaleX : 1 - // o.scaleY = o.scale != null ? o.scale : o.scaleY != null ? o.scaleY : 1 - // - // matrix = new SVG.Scale(o.scaleX, o.scaleY, o.cx, o.cy) - // - // // act on skew - // } else if (o.skewX != null || o.skewY != null) { - // // ensure centre point - // ensureCentre(o, target) - // - // // ensure skew values on both axes - // o.skewX = o.skewX != null ? o.skewX : 0 - // o.skewY = o.skewY != null ? o.skewY : 0 - // - // matrix = new SVG.Skew(o.skewX, o.skewY, o.cx, o.cy) - // - // // act on flip - // } else if (o.flip) { - // if(o.flip == 'x' || o.flip == 'y') { - // o.offset = o.offset == null ? target.bbox()['c' + o.flip] : o.offset - // } else { - // if(o.offset == null) { - // bbox = target.bbox() - // o.flip = bbox.cx - // o.offset = bbox.cy - // } else { - // o.flip = o.offset - // } - // } - // - // matrix = new SVG.Matrix().flip(o.flip, o.offset) - // - // // act on translate - // } else if (o.x != null || o.y != null) { - // matrix = new SVG.Translate(o.x, o.y) - // } - // - // if(!matrix) return this - // - // matrix.relative = relative - // - // this.last().transforms.push(matrix) - // - // return this._callStart() - // } - // // ensure scale values on both axes - // o.scaleX = o.scale != null ? o.scale : o.scaleX != null ? o.scaleX : 1 - // o.scaleY = o.scale != null ? o.scale : o.scaleY != null ? o.scaleY : 1 - // - // matrix = new SVG.Scale(o.scaleX, o.scaleY, o.cx, o.cy) - // - // // act on skew - // } else if (o.skewX != null || o.skewY != null) { - // // ensure centre point - // ensureCentre(o, target) - // - // // ensure skew values on both axes - // o.skewX = o.skewX != null ? o.skewX : 0 - // o.skewY = o.skewY != null ? o.skewY : 0 - // - // matrix = new SVG.Skew(o.skewX, o.skewY, o.cx, o.cy) - // - // // act on flip - // } else if (o.flip) { - // if (o.flip === 'x' || o.flip === 'y') { - // o.offset = o.offset == null ? target.bbox()['c' + o.flip] : o.offset - // } else { - // if (o.offset == null) { - // bbox = target.bbox() - // o.flip = bbox.cx - // o.offset = bbox.cy - // } else { - // o.flip = o.offset - // } - // } - // - // matrix = new SVG.Matrix().flip(o.flip, o.offset) - // - // // act on translate - // } else if (o.x != null || o.y != null) { - // matrix = new SVG.Translate(o.x, o.y) - // } - // - // if (!matrix) return this - // - // matrix.relative = relative - // - // this.last().transforms.push(matrix) - // - // return this._callStart() - } -}) - -/* global camelCase */ - -SVG.extend(SVG.Element, { - // Dynamic style generator - css: function (s, v) { - var ret = {} - var t, i - if (arguments.length === 0) { - // get full style as object - this.node.style.cssText.split(/\s*;\s*/).filter(function (el) { return !!el.length }).forEach(function (el) { - t = el.split(/\s*:\s*/) - ret[t[0]] = t[1] - }) - return ret - } - - if (arguments.length < 2) { - // get style properties in the array - if (Array.isArray(s)) { - for (i = s.length; i--;) { - ret[camelCase(s[i])] = this.node.style[camelCase(s[i])] - } - return ret - } - - // get style for property - if (typeof s === 'string') { - return this.node.style[camelCase(s)] - } - - // set styles in object - if (typeof s === 'object') { - for (i in s) { - // set empty string if null/undefined/'' was given - this.node.style[camelCase(i)] = (s[i] == null || SVG.regex.isBlank.test(s[i])) ? '' : s[i] - } - } - } - - // set style for property - if (arguments.length === 2) { - this.node.style[camelCase(s)] = (v == null || SVG.regex.isBlank.test(v)) ? '' : v - } - - return this - } -}) - -/* global createElement */ - -SVG.Parent = SVG.invent({ - // Initialize node - create: function (node) { - SVG.Element.call(this, node) - }, - - // Inherit from - inherit: SVG.Element, - - // Add class methods - extend: { - // Returns all child elements - children: function () { - return SVG.utils.map(this.node.children, function (node) { - return SVG.adopt(node) - }) - }, - // Add given element at a position - add: function (element, i) { - element = createElement(element) - - if (element.node !== this.node.children[i]) { - this.node.insertBefore(element.node, this.node.children[i] || null) - } - - return this - }, - // Basically does the same as `add()` but returns the added element instead - put: function (element, i) { - this.add(element, i) - return element.instance || element - }, - // Checks if the given element is a child - has: function (element) { - return this.index(element) >= 0 - }, - // Gets index of given element - index: function (element) { - return [].slice.call(this.node.children).indexOf(element.node) - }, - // Get a element at the given index - get: function (i) { - return SVG.adopt(this.node.children[i]) - }, - // Get first child - first: function () { - return this.get(0) - }, - // Get the last child - last: function () { - return this.get(this.node.children.length - 1) - }, - // Iterates over all children and invokes a given block - each: function (block, deep) { - var children = this.children() - var i, il - - for (i = 0, il = children.length; i < il; i++) { - if (children[i] instanceof SVG.Element) { - block.apply(children[i], [i, children]) - } - - if (deep && (children[i] instanceof SVG.Parent)) { - children[i].each(block, deep) - } - } - - return this - }, - // Remove a given child - removeElement: function (element) { - this.node.removeChild(element.node) - - return this - }, - // Remove all elements in this container - clear: function () { - // remove children - while (this.node.hasChildNodes()) { - this.node.removeChild(this.node.lastChild) - } - - // remove defs reference - delete this._defs - - return this - } - } - -}) - -SVG.extend(SVG.Parent, { - flatten: function (parent) { - // flattens is only possible for nested svgs and groups - if (!(this instanceof SVG.G || this instanceof SVG.Doc)) { - return this - } - - parent = parent || (this instanceof SVG.Doc && this.isRoot() ? this : this.parent(SVG.Parent)) - - this.each(function () { - if (this instanceof SVG.Defs) return this - if (this instanceof SVG.Parent) return this.flatten(parent) - return this.toParent(parent) - }) - - // we need this so that SVG.Doc does not get removed - this.node.firstElementChild || this.remove() - - return this - }, - ungroup: function (parent) { - // ungroup is only possible for nested svgs and groups - if (!(this instanceof SVG.G || (this instanceof SVG.Doc && !this.isRoot()))) { - return this - } - - parent = parent || this.parent(SVG.Parent) - - this.each(function () { - return this.toParent(parent) - }) - - // we need this so that SVG.Doc does not get removed - this.remove() - - return this - } -}) - -SVG.Container = SVG.invent({ - // Initialize node - create: function (node) { - SVG.Element.call(this, node) - }, - - // Inherit from - inherit: SVG.Parent -}) - -// Add events to elements - -;[ 'click', - 'dblclick', - 'mousedown', - 'mouseup', - 'mouseover', - 'mouseout', - 'mousemove', - 'mouseenter', - 'mouseleave', - 'touchstart', - 'touchmove', - 'touchleave', - 'touchend', - 'touchcancel' ].forEach(function (event) { - // add event to SVG.Element - SVG.Element.prototype[event] = function (f) { - // bind event to element rather than element node - SVG.on(this, event, f) - return this - } - }) - -SVG.listenerId = 0 - -// Add event binder in the SVG namespace -SVG.on = function (node, events, listener, binding, options) { - var l = listener.bind(binding || node) - var n = node instanceof SVG.Element ? node.node : node - - // ensure instance object for nodes which are not adopted - n.instance = n.instance || {events: {}} - - var bag = n.instance.events - - // add id to listener - if (!listener._svgjsListenerId) { listener._svgjsListenerId = ++SVG.listenerId } - - events.split(SVG.regex.delimiter).forEach(function (event) { - var ev = event.split('.')[0] - var ns = event.split('.')[1] || '*' - - // ensure valid object - bag[ev] = bag[ev] || {} - bag[ev][ns] = bag[ev][ns] || {} - - // reference listener - bag[ev][ns][listener._svgjsListenerId] = l - - // add listener - n.addEventListener(ev, l, options || false) - }) -} - -// Add event unbinder in the SVG namespace -SVG.off = function (node, events, listener, options) { - var n = node instanceof SVG.Element ? node.node : node - if (!n.instance) return - - // listener can be a function or a number - if (typeof listener === 'function') { - listener = listener._svgjsListenerId - if (!listener) return - } - - var bag = n.instance.events - - ;(events || '').split(SVG.regex.delimiter).forEach(function (event) { - var ev = event && event.split('.')[0] - var ns = event && event.split('.')[1] - var namespace, l - - if (listener) { - // remove listener reference - if (bag[ev] && bag[ev][ns || '*']) { - // removeListener - n.removeEventListener(ev, bag[ev][ns || '*'][listener], options || false) - - delete bag[ev][ns || '*'][listener] - } - } else if (ev && ns) { - // remove all listeners for a namespaced event - if (bag[ev] && bag[ev][ns]) { - for (l in bag[ev][ns]) { SVG.off(n, [ev, ns].join('.'), l) } - - delete bag[ev][ns] - } - } else if (ns) { - // remove all listeners for a specific namespace - for (event in bag) { - for (namespace in bag[event]) { - if (ns === namespace) { SVG.off(n, [event, ns].join('.')) } - } - } - } else if (ev) { - // remove all listeners for the event - if (bag[ev]) { - for (namespace in bag[ev]) { SVG.off(n, [ev, namespace].join('.')) } - - delete bag[ev] - } - } else { - // remove all listeners on a given node - for (event in bag) { SVG.off(n, event) } - - n.instance.events = {} - } - }) -} - -SVG.extend(SVG.Element, { - // Bind given event to listener - on: function (event, listener, binding, options) { - SVG.on(this, event, listener, binding, options) - return this - }, - // Unbind event from listener - off: function (event, listener) { - SVG.off(this.node, event, listener) - return this - }, - dispatch: function (event, data) { - // Dispatch event - if (event instanceof window.Event) { - this.node.dispatchEvent(event) - } else { - this.node.dispatchEvent(event = new window.CustomEvent(event, {detail: data, cancelable: true})) - } - return event - }, - // Fire given event - fire: function (event, data) { - this.dispatch(event, data) - return this - } -}) - -SVG.Defs = SVG.invent({ - // Initialize node - create: 'defs', - - // Inherit from - inherit: SVG.Container -}) - -SVG.G = SVG.invent({ - // Initialize node - create: 'g', - - // Inherit from - inherit: SVG.Container, - - // Add class methods - extend: { - }, - - // Add parent method - construct: { - // Create a group element - group: function () { - return this.put(new SVG.G()) - } - } -}) - -// ### This module adds backward / forward functionality to elements. - -// -SVG.extend(SVG.Element, { - // Get all siblings, including myself - siblings: function () { - return this.parent().children() - }, - - // Get the curent position siblings - position: function () { - return this.parent().index(this) - }, - - // Get the next element (will return null if there is none) - next: function () { - return this.siblings()[this.position() + 1] - }, - - // Get the next element (will return null if there is none) - prev: function () { - return this.siblings()[this.position() - 1] - }, - - // Send given element one step forward - forward: function () { - var i = this.position() + 1 - var p = this.parent() - - // move node one step forward - p.removeElement(this).add(this, i) - - // make sure defs node is always at the top - if (p instanceof SVG.Doc) { - p.node.appendChild(p.defs().node) - } - - return this - }, - - // Send given element one step backward - backward: function () { - var i = this.position() - - if (i > 0) { - this.parent().removeElement(this).add(this, i - 1) - } - - return this - }, - - // Send given element all the way to the front - front: function () { - var p = this.parent() - - // Move node forward - p.node.appendChild(this.node) - - // Make sure defs node is always at the top - if (p instanceof SVG.Doc) { - p.node.appendChild(p.defs().node) - } - - return this - }, - - // Send given element all the way to the back - back: function () { - if (this.position() > 0) { - this.parent().removeElement(this).add(this, 0) - } - - return this - }, - - // Inserts a given element before the targeted element - before: function (element) { - element.remove() - - var i = this.position() - - this.parent().add(element, i) - - return this - }, - - // Insters a given element after the targeted element - after: function (element) { - element.remove() - - var i = this.position() - - this.parent().add(element, i + 1) - - return this - } -}) - -SVG.Mask = SVG.invent({ - // Initialize node - create: 'mask', - - // Inherit from - inherit: SVG.Container, - - // Add class methods - extend: { - // Unmask all masked elements and remove itself - remove: function () { - // unmask all targets - this.targets().forEach(function (el) { - el.unmask() - }) - - // remove mask from parent - return SVG.Element.prototype.remove.call(this) - }, - - targets: function () { - return SVG.select('svg [mask*="' + this.id() + '"]') - } - }, - - // Add parent method - construct: { - // Create masking element - mask: function () { - return this.defs().put(new SVG.Mask()) - } - } -}) - -SVG.extend(SVG.Element, { - // Distribute mask to svg element - maskWith: function (element) { - // use given mask or create a new one - var masker = element instanceof SVG.Mask ? element : this.parent().mask().add(element) - - // apply mask - return this.attr('mask', 'url("#' + masker.id() + '")') - }, - // Unmask element - unmask: function () { - return this.attr('mask', null) - }, - masker: function () { - return this.reference('mask') - } -}) - -SVG.ClipPath = SVG.invent({ - // Initialize node - create: 'clipPath', - - // Inherit from - inherit: SVG.Container, - - // Add class methods - extend: { - // Unclip all clipped elements and remove itself - remove: function () { - // unclip all targets - this.targets().forEach(function (el) { - el.unclip() - }) - - // remove clipPath from parent - return SVG.Element.prototype.remove.call(this) - }, - - targets: function () { - return SVG.select('svg [clip-path*="' + this.id() + '"]') - } - }, - - // Add parent method - construct: { - // Create clipping element - clip: function () { - return this.defs().put(new SVG.ClipPath()) - } - } -}) - -// -SVG.extend(SVG.Element, { - // Distribute clipPath to svg element - clipWith: function (element) { - // use given clip or create a new one - var clipper = element instanceof SVG.ClipPath ? element : this.parent().clip().add(element) - - // apply mask - return this.attr('clip-path', 'url("#' + clipper.id() + '")') - }, - // Unclip element - unclip: function () { - return this.attr('clip-path', null) - }, - clipper: function () { - return this.reference('clip-path') - } - -}) - -SVG.Gradient = SVG.invent({ - // Initialize node - create: function (type) { - SVG.Element.call(this, typeof type === 'object' ? type : SVG.create(type + 'Gradient')) - }, - - // Inherit from - inherit: SVG.Container, - - // Add class methods - extend: { - // Add a color stop - stop: function (offset, color, opacity) { - return this.put(new SVG.Stop()).update(offset, color, opacity) - }, - // Update gradient - update: function (block) { - // remove all stops - this.clear() - - // invoke passed block - if (typeof block === 'function') { - block.call(this, this) - } - - return this - }, - // Return the fill id - url: function () { - return 'url(#' + this.id() + ')' - }, - // Alias string convertion to fill - toString: function () { - return this.url() - }, - // custom attr to handle transform - attr: function (a, b, c) { - if (a === 'transform') a = 'gradientTransform' - return SVG.Container.prototype.attr.call(this, a, b, c) - } - }, - - // Add parent method - construct: { - // Create gradient element in defs - gradient: function (type, block) { - return this.defs().gradient(type, block) - } - } -}) - -// Add animatable methods to both gradient and fx module -SVG.extend([SVG.Gradient, SVG.Timeline], { - // From position - from: function (x, y) { - return (this._target || this).type === 'radialGradient' - ? this.attr({ fx: new SVG.Number(x), fy: new SVG.Number(y) }) - : this.attr({ x1: new SVG.Number(x), y1: new SVG.Number(y) }) - }, - // To position - to: function (x, y) { - return (this._target || this).type === 'radialGradient' - ? this.attr({ cx: new SVG.Number(x), cy: new SVG.Number(y) }) - : this.attr({ x2: new SVG.Number(x), y2: new SVG.Number(y) }) - } -}) - -// Base gradient generation -SVG.extend(SVG.Defs, { - // define gradient - gradient: function (type, block) { - return this.put(new SVG.Gradient(type)).update(block) - } - -}) - -SVG.Stop = SVG.invent({ - // Initialize node - create: 'stop', - - // Inherit from - inherit: SVG.Element, - - // Add class methods - extend: { - // add color stops - update: function (o) { - if (typeof o === 'number' || o instanceof SVG.Number) { - o = { - offset: arguments[0], - color: arguments[1], - opacity: arguments[2] - } - } - - // set attributes - if (o.opacity != null) this.attr('stop-opacity', o.opacity) - if (o.color != null) this.attr('stop-color', o.color) - if (o.offset != null) this.attr('offset', new SVG.Number(o.offset)) - - return this - } - } -}) - -SVG.Pattern = SVG.invent({ - // Initialize node - create: 'pattern', - - // Inherit from - inherit: SVG.Container, - - // Add class methods - extend: { - // Return the fill id - url: function () { - return 'url(#' + this.id() + ')' - }, - // Update pattern by rebuilding - update: function (block) { - // remove content - this.clear() - - // invoke passed block - if (typeof block === 'function') { - block.call(this, this) - } - - return this - }, - // Alias string convertion to fill - toString: function () { - return this.url() - }, - // custom attr to handle transform - attr: function (a, b, c) { - if (a === 'transform') a = 'patternTransform' - return SVG.Container.prototype.attr.call(this, a, b, c) - } - - }, - - // Add parent method - construct: { - // Create pattern element in defs - pattern: function (width, height, block) { - return this.defs().pattern(width, height, block) - } - } -}) - -SVG.extend(SVG.Defs, { - // Define gradient - pattern: function (width, height, block) { - return this.put(new SVG.Pattern()).update(block).attr({ - x: 0, - y: 0, - width: width, - height: height, - patternUnits: 'userSpaceOnUse' - }) - } - -}) - -SVG.Doc = SVG.invent({ - // Initialize node - create: function (node) { - SVG.Element.call(this, node || SVG.create('svg')) - - // set svg element attributes and ensure defs node - this.namespace() - }, - - // Inherit from - inherit: SVG.Container, - - // Add class methods - extend: { - isRoot: function () { - return !this.node.parentNode || !(this.node.parentNode instanceof window.SVGElement) || this.node.parentNode.nodeName === '#document' - }, - // Check if this is a root svg. If not, call docs from this element - doc: function () { - if (this.isRoot()) return this - return SVG.Element.prototype.doc.call(this) - }, - // Add namespaces - namespace: function () { - if (!this.isRoot()) return this.doc().namespace() - return this - .attr({ xmlns: SVG.ns, version: '1.1' }) - .attr('xmlns:xlink', SVG.xlink, SVG.xmlns) - .attr('xmlns:svgjs', SVG.svgjs, SVG.xmlns) - }, - // Creates and returns defs element - defs: function () { - if (!this.isRoot()) return this.doc().defs() - return SVG.adopt(this.node.getElementsByTagName('defs')[0]) || this.put(new SVG.Defs()) - }, - // custom parent method - parent: function (type) { - if (this.isRoot()) { - return this.node.parentNode.nodeName === '#document' ? null : this.node.parentNode - } - - return SVG.Element.prototype.parent.call(this, type) - }, - // Removes the doc from the DOM - remove: function () { - if (!this.isRoot()) { - return SVG.Element.prototype.remove.call(this) - } - - if (this.parent()) { - this.parent().removeChild(this.node) - } - - return this - }, - clear: function () { - // remove children - while (this.node.hasChildNodes()) { - this.node.removeChild(this.node.lastChild) - } - return this - } - }, - construct: { - // Create nested svg document - nested: function () { - return this.put(new SVG.Doc()) - } - } -}) - - -SVG.Shape = SVG.invent({ - // Initialize node - create: function (node) { - SVG.Element.call(this, node) - }, - - // Inherit from - inherit: SVG.Element -}) - - -SVG.Bare = SVG.invent({ - // Initialize - create: function (element, inherit) { - // construct element - SVG.Element.call(this, SVG.create(element)) - - // inherit custom methods - if (inherit) { - for (var method in inherit.prototype) { - if (typeof inherit.prototype[method] === 'function') { - this[method] = inherit.prototype[method] - } - } - } - }, - - // Inherit from - inherit: SVG.Element, - - // Add methods - extend: { - // Insert some plain text - words: function (text) { - // remove contents - while (this.node.hasChildNodes()) { - this.node.removeChild(this.node.lastChild) - } - - // create text node - this.node.appendChild(document.createTextNode(text)) - - return this - } - } -}) - -SVG.extend(SVG.Parent, { - // Create an element that is not described by SVG.js - element: function (element, inherit) { - return this.put(new SVG.Bare(element, inherit)) - } -}) - - -SVG.Symbol = SVG.invent({ - // Initialize node - create: 'symbol', - - // Inherit from - inherit: SVG.Container, - - construct: { - // create symbol - symbol: function () { - return this.put(new SVG.Symbol()) - } - } -}) - - -SVG.Use = SVG.invent({ - // Initialize node - create: 'use', - - // Inherit from - inherit: SVG.Shape, - - // Add class methods - extend: { - // Use element as a reference - element: function (element, file) { - // Set lined element - return this.attr('href', (file || '') + '#' + element, SVG.xlink) - } - }, - - // Add parent method - construct: { - // Create a use element - use: function (element, file) { - return this.put(new SVG.Use()).element(element, file) - } - } -}) - - -SVG.Rect = SVG.invent({ - // Initialize node - create: 'rect', - - // Inherit from - inherit: SVG.Shape, - - // Add parent method - construct: { - // Create a rect element - rect: function (width, height) { - return this.put(new SVG.Rect()).size(width, height) - } - } -}) - -/* global proportionalSize */ - -SVG.Circle = SVG.invent({ - // Initialize node - create: 'circle', - - // Inherit from - inherit: SVG.Shape, - - // Add parent method - construct: { - // Create circle element, based on ellipse - circle: function (size) { - return this.put(new SVG.Circle()).rx(new SVG.Number(size).divide(2)).move(0, 0) - } - } -}) - -SVG.extend([SVG.Circle, SVG.Timeline], { - // Radius x value - rx: function (rx) { - return this.attr('r', rx) - }, - // Alias radius x value - ry: function (ry) { - return this.rx(ry) - } -}) - -SVG.Ellipse = SVG.invent({ - // Initialize node - create: 'ellipse', - - // Inherit from - inherit: SVG.Shape, - - // Add parent method - construct: { - // Create an ellipse - ellipse: function (width, height) { - return this.put(new SVG.Ellipse()).size(width, height).move(0, 0) - } - } -}) - -SVG.extend([SVG.Ellipse, SVG.Rect, SVG.Timeline], { - // Radius x value - rx: function (rx) { - return this.attr('rx', rx) - }, - // Radius y value - ry: function (ry) { - return this.attr('ry', ry) - } -}) - -// Add common method -SVG.extend([SVG.Circle, SVG.Ellipse], { - // Move over x-axis - x: function (x) { - return x == null ? this.cx() - this.rx() : this.cx(x + this.rx()) - }, - // Move over y-axis - y: function (y) { - return y == null ? this.cy() - this.ry() : this.cy(y + this.ry()) - }, - // Move by center over x-axis - cx: function (x) { - return x == null ? this.attr('cx') : this.attr('cx', x) - }, - // Move by center over y-axis - cy: function (y) { - return y == null ? this.attr('cy') : this.attr('cy', y) - }, - // Set width of element - width: function (width) { - return width == null ? this.rx() * 2 : this.rx(new SVG.Number(width).divide(2)) - }, - // Set height of element - height: function (height) { - return height == null ? this.ry() * 2 : this.ry(new SVG.Number(height).divide(2)) - }, - // Custom size function - size: function (width, height) { - var p = proportionalSize(this, width, height) - - return this - .rx(new SVG.Number(p.width).divide(2)) - .ry(new SVG.Number(p.height).divide(2)) - } -}) - -/* global proportionalSize */ - -SVG.Line = SVG.invent({ - // Initialize node - create: 'line', - - // Inherit from - inherit: SVG.Shape, - - // Add class methods - extend: { - // Get array - array: function () { - return new SVG.PointArray([ - [ this.attr('x1'), this.attr('y1') ], - [ this.attr('x2'), this.attr('y2') ] - ]) - }, - - // Overwrite native plot() method - plot: function (x1, y1, x2, y2) { - if (x1 == null) { - return this.array() - } else if (typeof y1 !== 'undefined') { - x1 = { x1: x1, y1: y1, x2: x2, y2: y2 } - } else { - x1 = new SVG.PointArray(x1).toLine() - } - - return this.attr(x1) - }, - - // Move by left top corner - move: function (x, y) { - return this.attr(this.array().move(x, y).toLine()) - }, - - // Set element size to given width and height - size: function (width, height) { - var p = proportionalSize(this, width, height) - return this.attr(this.array().size(p.width, p.height).toLine()) - } - }, - - // Add parent method - construct: { - // Create a line element - line: function (x1, y1, x2, y2) { - // make sure plot is called as a setter - // x1 is not necessarily a number, it can also be an array, a string and a SVG.PointArray - return SVG.Line.prototype.plot.apply( - this.put(new SVG.Line()) - , x1 != null ? [x1, y1, x2, y2] : [0, 0, 0, 0] - ) - } - } -}) - -/* global proportionalSize */ - -SVG.Polyline = SVG.invent({ - // Initialize node - create: 'polyline', - - // Inherit from - inherit: SVG.Shape, - - // Add parent method - construct: { - // Create a wrapped polyline element - polyline: function (p) { - // make sure plot is called as a setter - return this.put(new SVG.Polyline()).plot(p || new SVG.PointArray()) - } - } -}) - -SVG.Polygon = SVG.invent({ - // Initialize node - create: 'polygon', - - // Inherit from - inherit: SVG.Shape, - - // Add parent method - construct: { - // Create a wrapped polygon element - polygon: function (p) { - // make sure plot is called as a setter - return this.put(new SVG.Polygon()).plot(p || new SVG.PointArray()) - } - } -}) - -// Add polygon-specific functions -SVG.extend([SVG.Polyline, SVG.Polygon], { - // Get array - array: function () { - return this._array || (this._array = new SVG.PointArray(this.attr('points'))) - }, - - // Plot new path - plot: function (p) { - return (p == null) ? this.array() - : this.clear().attr('points', typeof p === 'string' ? p - : (this._array = new SVG.PointArray(p))) - }, - - // Clear array cache - clear: function () { - delete this._array - return this - }, - - // Move by left top corner - move: function (x, y) { - return this.attr('points', this.array().move(x, y)) - }, - - // Set element size to given width and height - size: function (width, height) { - var p = proportionalSize(this, width, height) - return this.attr('points', this.array().size(p.width, p.height)) - } -}) - -// unify all point to point elements -SVG.extend([SVG.Line, SVG.Polyline, SVG.Polygon], { - // Define morphable array - MorphArray: SVG.PointArray, - // Move by left top corner over x-axis - x: function (x) { - return x == null ? this.bbox().x : this.move(x, this.bbox().y) - }, - // Move by left top corner over y-axis - y: function (y) { - return y == null ? this.bbox().y : this.move(this.bbox().x, y) - }, - // Set width of element - width: function (width) { - var b = this.bbox() - - return width == null ? b.width : this.size(width, b.height) - }, - // Set height of element - height: function (height) { - var b = this.bbox() - - return height == null ? b.height : this.size(b.width, height) - } -}) - -/* global proportionalSize */ - -SVG.Path = SVG.invent({ - // Initialize node - create: 'path', - - // Inherit from - inherit: SVG.Shape, - - // Add class methods - extend: { - // Define morphable array - MorphArray: SVG.PathArray, - // Get array - array: function () { - return this._array || (this._array = new SVG.PathArray(this.attr('d'))) - }, - // Plot new path - plot: function (d) { - return (d == null) ? this.array() - : this.clear().attr('d', typeof d === 'string' ? d : (this._array = new SVG.PathArray(d))) - }, - // Clear array cache - clear: function () { - delete this._array - return this - }, - // Move by left top corner - move: function (x, y) { - return this.attr('d', this.array().move(x, y)) - }, - // Move by left top corner over x-axis - x: function (x) { - return x == null ? this.bbox().x : this.move(x, this.bbox().y) - }, - // Move by left top corner over y-axis - y: function (y) { - return y == null ? this.bbox().y : this.move(this.bbox().x, y) - }, - // Set element size to given width and height - size: function (width, height) { - var p = proportionalSize(this, width, height) - return this.attr('d', this.array().size(p.width, p.height)) - }, - // Set width of element - width: function (width) { - return width == null ? this.bbox().width : this.size(width, this.bbox().height) - }, - // Set height of element - height: function (height) { - return height == null ? this.bbox().height : this.size(this.bbox().width, height) - } - }, - - // Add parent method - construct: { - // Create a wrapped path element - path: function (d) { - // make sure plot is called as a setter - return this.put(new SVG.Path()).plot(d || new SVG.PathArray()) - } - } -}) - -SVG.Image = SVG.invent({ - // Initialize node - create: 'image', - - // Inherit from - inherit: SVG.Shape, - - // Add class methods - extend: { - // (re)load image - load: function (url, callback) { - if (!url) return this - - var img = new window.Image() - - SVG.on(img, 'load', function (e) { - var p = this.parent(SVG.Pattern) - - // ensure image size - if (this.width() === 0 && this.height() === 0) { - this.size(img.width, img.height) - } - - if (p instanceof SVG.Pattern) { - // ensure pattern size if not set - if (p.width() === 0 && p.height() === 0) { - p.size(this.width(), this.height()) - } - } - - if (typeof callback === 'function') { - callback.call(this, { - width: img.width, - height: img.height, - ratio: img.width / img.height, - url: url - }) - } - }, this) - - SVG.on(img, 'load error', function () { - // dont forget to unbind memory leaking events - SVG.off(img) - }) - - return this.attr('href', (img.src = url), SVG.xlink) - } - }, - - // Add parent method - construct: { - // create image element, load image and set its size - image: function (source, callback) { - return this.put(new SVG.Image()).size(0, 0).load(source, callback) - } - } -}) - -SVG.Text = SVG.invent({ - // Initialize node - create: function (node) { - SVG.Element.call(this, node || SVG.create('text')) - this.dom.leading = new SVG.Number(1.3) // store leading value for rebuilding - this._rebuild = true // enable automatic updating of dy values - this._build = false // disable build mode for adding multiple lines - - // set default font - this.attr('font-family', SVG.defaults.attrs['font-family']) - }, - - // Inherit from - inherit: SVG.Parent, - - // Add class methods - extend: { - // Move over x-axis - x: function (x) { - // act as getter - if (x == null) { - return this.attr('x') - } - - return this.attr('x', x) - }, - // Move over y-axis - y: function (y) { - var oy = this.attr('y') - var o = typeof oy === 'number' ? oy - this.bbox().y : 0 - - // act as getter - if (y == null) { - return typeof oy === 'number' ? oy - o : oy - } - - return this.attr('y', typeof y === 'number' ? y + o : y) - }, - // Move center over x-axis - cx: function (x) { - return x == null ? this.bbox().cx : this.x(x - this.bbox().width / 2) - }, - // Move center over y-axis - cy: function (y) { - return y == null ? this.bbox().cy : this.y(y - this.bbox().height / 2) - }, - // Set the text content - text: function (text) { - // act as getter - if (text === undefined) { - var children = this.node.childNodes - var firstLine = 0 - text = '' - - for (var i = 0, len = children.length; i < len; ++i) { - // skip textPaths - they are no lines - if (children[i].nodeName === 'textPath') { - if (i === 0) firstLine = 1 - continue - } - - // add newline if its not the first child and newLined is set to true - if (i !== firstLine && children[i].nodeType !== 3 && SVG.adopt(children[i]).dom.newLined === true) { - text += '\n' - } - - // add content of this node - text += children[i].textContent - } - - return text - } - - // remove existing content - this.clear().build(true) - - if (typeof text === 'function') { - // call block - text.call(this, this) - } else { - // store text and make sure text is not blank - text = text.split('\n') - - // build new lines - for (var j = 0, jl = text.length; j < jl; j++) { - this.tspan(text[j]).newLine() - } - } - - // disable build mode and rebuild lines - return this.build(false).rebuild() - }, - // Set / get leading - leading: function (value) { - // act as getter - if (value == null) { - return this.dom.leading - } - - // act as setter - this.dom.leading = new SVG.Number(value) - - return this.rebuild() - }, - // Rebuild appearance type - rebuild: function (rebuild) { - // store new rebuild flag if given - if (typeof rebuild === 'boolean') { - this._rebuild = rebuild - } - - // define position of all lines - if (this._rebuild) { - var self = this - var blankLineOffset = 0 - var dy = this.dom.leading * new SVG.Number(this.attr('font-size')) - - this.each(function () { - if (this.dom.newLined) { - this.attr('x', self.attr('x')) - - if (this.text() === '\n') { - blankLineOffset += dy - } else { - this.attr('dy', dy + blankLineOffset) - blankLineOffset = 0 - } - } - }) - - this.fire('rebuild') - } - - return this - }, - // Enable / disable build mode - build: function (build) { - this._build = !!build - return this - }, - // overwrite method from parent to set data properly - setData: function (o) { - this.dom = o - this.dom.leading = new SVG.Number(o.leading || 1.3) - return this - } - }, - - // Add parent method - construct: { - // Create text element - text: function (text) { - return this.put(new SVG.Text()).text(text) - }, - // Create plain text element - plain: function (text) { - return this.put(new SVG.Text()).plain(text) - } - } - -}) - -SVG.Tspan = SVG.invent({ - // Initialize node - create: 'tspan', - - // Inherit from - inherit: SVG.Parent, - - // Add class methods - extend: { - // Set text content - text: function (text) { - if (text == null) return this.node.textContent + (this.dom.newLined ? '\n' : '') - - typeof text === 'function' ? text.call(this, this) : this.plain(text) - - return this - }, - // Shortcut dx - dx: function (dx) { - return this.attr('dx', dx) - }, - // Shortcut dy - dy: function (dy) { - return this.attr('dy', dy) - }, - // Create new line - newLine: function () { - // fetch text parent - var t = this.parent(SVG.Text) - - // mark new line - this.dom.newLined = true - - // apply new position - return this.dy(t.dom.leading * t.attr('font-size')).attr('x', t.x()) - } - } -}) - -SVG.extend([SVG.Text, SVG.Tspan], { - // Create plain text node - plain: function (text) { - // clear if build mode is disabled - if (this._build === false) { - this.clear() - } - - // create text node - this.node.appendChild(document.createTextNode(text)) - - return this - }, - // Create a tspan - tspan: function (text) { - var tspan = new SVG.Tspan() - - // clear if build mode is disabled - if (!this._build) { - this.clear() - } - - // add new tspan - this.node.appendChild(tspan.node) - - return tspan.text(text) - }, - // FIXME: Does this also work for textpath? - // Get length of text element - length: function () { - return this.node.getComputedTextLength() - } -}) - -SVG.TextPath = SVG.invent({ - // Initialize node - create: 'textPath', - - // Inherit from - inherit: SVG.Text, - - // Define parent class - parent: SVG.Parent, - - // Add parent method - extend: { - MorphArray: SVG.PathArray, - // return the array of the path track element - array: function () { - var track = this.track() - - return track ? track.array() : null - }, - // Plot path if any - plot: function (d) { - var track = this.track() - var pathArray = null - - if (track) { - pathArray = track.plot(d) - } - - return (d == null) ? pathArray : this - }, - // Get the path element - track: function () { - return this.reference('href') - } - }, - construct: { - textPath: function (text, path) { - return this.defs().path(path).text(text).addTo(this) - } - } -}) - -SVG.extend([SVG.Text], { - // Create path for text to run on - path: function (track) { - var path = new SVG.TextPath() - - // if d is a path, reuse it - if (!(track instanceof SVG.Path)) { - // create path element - track = this.doc().defs().path(track) - } - - // link textPath to path and add content - path.attr('href', '#' + track, SVG.xlink) - - // add textPath element as child node and return textPath - return this.put(path) - }, - // Todo: make this plural? - // Get the textPath children - textPath: function () { - return this.select('textPath') - } -}) - -SVG.extend([SVG.Path], { - // creates a textPath from this path - text: function (text) { - if (text instanceof SVG.Text) { - var txt = text.text() - return text.clear().path(this).text(txt) - } - return this.parent().put(new SVG.Text()).path(this).text(text) - } - // TODO: Maybe add `targets` to get all textPaths associated with this path -}) - -SVG.A = SVG.invent({ - // Initialize node - create: 'a', - - // Inherit from - inherit: SVG.Container, - - // Add class methods - extend: { - // Link url - to: function (url) { - return this.attr('href', url, SVG.xlink) - }, - // Link target attribute - target: function (target) { - return this.attr('target', target) - } - }, - - // Add parent method - construct: { - // Create a hyperlink element - link: function (url) { - return this.put(new SVG.A()).to(url) - } - } -}) - -SVG.extend(SVG.Element, { - // Create a hyperlink element - linkTo: function (url) { - var link = new SVG.A() - - if (typeof url === 'function') { url.call(link, link) } else { - link.to(url) - } - - return this.parent().put(link).put(this) - } - -}) - -SVG.Marker = SVG.invent({ - // Initialize node - create: 'marker', - - // Inherit from - inherit: SVG.Container, - - // Add class methods - extend: { - // Set width of element - width: function (width) { - return this.attr('markerWidth', width) - }, - // Set height of element - height: function (height) { - return this.attr('markerHeight', height) - }, - // Set marker refX and refY - ref: function (x, y) { - return this.attr('refX', x).attr('refY', y) - }, - // Update marker - update: function (block) { - // remove all content - this.clear() - - // invoke passed block - if (typeof block === 'function') { block.call(this, this) } - - return this - }, - // Return the fill id - toString: function () { - return 'url(#' + this.id() + ')' - } - }, - - // Add parent method - construct: { - marker: function (width, height, block) { - // Create marker element in defs - return this.defs().marker(width, height, block) - } - } - -}) - -SVG.extend(SVG.Defs, { - // Create marker - marker: function (width, height, block) { - // Set default viewbox to match the width and height, set ref to cx and cy and set orient to auto - return this.put(new SVG.Marker()) - .size(width, height) - .ref(width / 2, height / 2) - .viewbox(0, 0, width, height) - .attr('orient', 'auto') - .update(block) - } - -}) - -SVG.extend([SVG.Line, SVG.Polyline, SVG.Polygon, SVG.Path], { - // Create and attach markers - marker: function (marker, width, height, block) { - var attr = ['marker'] - - // Build attribute name - if (marker !== 'all') attr.push(marker) - attr = attr.join('-') - - // Set marker attribute - marker = arguments[1] instanceof SVG.Marker - ? arguments[1] - : this.doc().marker(width, height, block) - - return this.attr(attr, marker) - } -}) - -// // Define list of available attributes for stroke and fill -// var sugar = { -// stroke: ['color', 'width', 'opacity', 'linecap', 'linejoin', 'miterlimit', 'dasharray', 'dashoffset'], -// fill: ['color', 'opacity', 'rule'], -// prefix: function (t, a) { -// return a === 'color' ? t : t + '-' + a -// } -// } -// -// // Add sugar for fill and stroke -// ;['fill', 'stroke'].forEach(function (m) { -// var extension = {} -// var i -// -// extension[m] = function (o) { -// if (typeof o === 'undefined') { -// return this -// } -// if (typeof o === 'string' || SVG.Color.isRgb(o) || (o && typeof o.fill === 'function')) { -// this.attr(m, o) -// } else { -// // set all attributes from sugar.fill and sugar.stroke list -// for (i = sugar[m].length - 1; i >= 0; i--) { -// if (o[sugar[m][i]] != null) { -// this.attr(sugar.prefix(m, sugar[m][i]), o[sugar[m][i]]) -// } -// } -// } -// -// return this -// } -// -// SVG.extend([SVG.Element, SVG.Timeline], extension) -// }) -// -// SVG.extend([SVG.Element, SVG.Timeline], { -// // Let the user set the matrix directly -// matrix: function (mat, b, c, d, e, f) { -// // Act as a getter -// if (mat == null) { -// return new SVG.Matrix(this) -// } -// -// // Act as a setter, the user can pass a matrix or a set of numbers -// return this.attr('transform', new SVG.Matrix(mat, b, c, d, e, f)) -// }, -// -// // Map rotation to transform -// rotate: function (angle, cx, cy) { -// return this.transform({rotate: angle, ox: cx, oy: cy}, true) -// }, -// -// // Map skew to transform -// skew: function (x, y, cx, cy) { -// return arguments.length === 1 || arguments.length === 3 -// ? this.transform({skew: x, ox: y, oy: cx}, true) -// : this.transform({skew: [x, y], ox: cx, oy: cy}, true) -// }, -// -// shear: function (lam, cx, cy) { -// return this.transform({shear: lam, ox: cx, oy: cy}, true) -// }, -// -// // Map scale to transform -// scale: function (x, y, cx, cy) { -// return arguments.length === 1 || arguments.length === 3 -// ? this.transform({ scale: x, ox: y, oy: cx }, true) -// : this.transform({ scale: [x, y], ox: cx, oy: cy }, true) -// }, -// -// // Map translate to transform -// translate: function (x, y) { -// return this.transform({ translate: [x, y] }, true) -// }, -// -// // Map relative translations to transform -// relative: function (x, y) { -// return this.transform({ relative: [x, y] }, true) -// }, -// -// // Map flip to transform -// flip: function (direction, around) { -// var directionString = typeof direction === 'string' ? direction -// : isFinite(direction) ? 'both' -// : 'both' -// var origin = (direction === 'both' && isFinite(around)) ? [around, around] -// : (direction === 'x') ? [around, 0] -// : (direction === 'y') ? [0, around] -// : isFinite(direction) ? [direction, direction] -// : [0, 0] -// this.transform({flip: directionString, origin: origin}, true) -// }, -// -// // Opacity -// opacity: function (value) { -// return this.attr('opacity', value) -// }, -// -// // Relative move over x axis -// dx: function (x) { -// return this.x(new SVG.Number(x).plus(this instanceof SVG.Timeline ? 0 : this.x()), true) -// }, -// -// // Relative move over y axis -// dy: function (y) { -// return this.y(new SVG.Number(y).plus(this instanceof SVG.Timeline ? 0 : this.y()), true) -// }, -// -// // Relative move over x and y axes -// dmove: function (x, y) { -// return this.dx(x).dy(y) -// } -// }) -// -// SVG.extend([SVG.Rect, SVG.Ellipse, SVG.Circle, SVG.Gradient, SVG.Timeline], { -// // Add x and y radius -// radius: function (x, y) { -// var type = (this._target || this).type -// return type === 'radialGradient' || type === 'radialGradient' -// ? this.attr('r', new SVG.Number(x)) -// : this.rx(x).ry(y == null ? x : y) -// } -// }) -// -// SVG.extend(SVG.Path, { -// // Get path length -// length: function () { -// return this.node.getTotalLength() -// }, -// // Get point at length -// pointAt: function (length) { -// return new SVG.Point(this.node.getPointAtLength(length)) -// } -// }) -// -// SVG.extend([SVG.Parent, SVG.Text, SVG.Tspan, SVG.Timeline], { -// // Set font -// font: function (a, v) { -// if (typeof a === 'object') { -// for (v in a) this.font(v, a[v]) -// } -// -// return a === 'leading' -// ? this.leading(v) -// : a === 'anchor' -// ? this.attr('text-anchor', v) -// : a === 'size' || a === 'family' || a === 'weight' || a === 'stretch' || a === 'variant' || a === 'style' -// ? this.attr('font-' + a, v) -// : this.attr(a, v) -// } -// }) - - -SVG.extend(SVG.Element, { - // Store data values on svg nodes - data: function (a, v, r) { - if (typeof a === 'object') { - for (v in a) { - this.data(v, a[v]) - } - } else if (arguments.length < 2) { - try { - return JSON.parse(this.attr('data-' + a)) - } catch (e) { - return this.attr('data-' + a) - } - } else { - this.attr('data-' + a, - v === null ? null - : r === true || typeof v === 'string' || typeof v === 'number' ? v - : JSON.stringify(v) - ) - } - - return this - } -}) - - -SVG.extend(SVG.Element, { - // Remember arbitrary data - remember: function (k, v) { - // remember every item in an object individually - if (typeof arguments[0] === 'object') { - for (var key in k) { - this.remember(key, k[key]) - } - } else if (arguments.length === 1) { - // retrieve memory - return this.memory()[k] - } else { - // store memory - this.memory()[k] = v - } - - return this - }, - - // Erase a given memory - forget: function () { - if (arguments.length === 0) { - this._memory = {} - } else { - for (var i = arguments.length - 1; i >= 0; i--) { - delete this.memory()[arguments[i]] - } - } - return this - }, - - // Initialize or return local memory object - memory: function () { - return this._memory || (this._memory = {}) - } -}) - -/* global idFromReference */ - -// Method for getting an element by id -SVG.get = function (id) { - var node = document.getElementById(idFromReference(id) || id) - return SVG.adopt(node) -} - -// Select elements by query string -SVG.select = function (query, parent) { - return SVG.utils.map((parent || document).querySelectorAll(query), function (node) { - return SVG.adopt(node) - }) -} - -SVG.$$ = function (query, parent) { - return SVG.utils.map((parent || document).querySelectorAll(query), function (node) { - return SVG.adopt(node) - }) -} - -SVG.$ = function (query, parent) { - return SVG.adopt((parent || document).querySelector(query)) -} - -SVG.extend(SVG.Parent, { - // Scoped select method - select: function (query) { - return SVG.select(query, this.node) - } -}) - -/* eslint no-unused-vars: 0 */ - -function createElement (element, makeNested) { - if (element instanceof SVG.Element) return element - - if (typeof element === 'object') { - return SVG.adopt(element) - } - - if (element == null) { - return new SVG.Doc() - } - - if (typeof element === 'string' && element.charAt(0) !== '<') { - return SVG.adopt(document.querySelector(element)) - } - - var node = SVG.create('svg') - node.innerHTML = element - - element = SVG.adopt(node.firstElementChild) - - return element -} - -function isNulledBox (box) { - return !box.w && !box.h && !box.x && !box.y -} - -function domContains (node) { - return (document.documentElement.contains || function (node) { - // This is IE - it does not support contains() for top-level SVGs - while (node.parentNode) { - node = node.parentNode - } - return node === document - }).call(document.documentElement, node) -} - -function pathRegReplace (a, b, c, d) { - return c + d.replace(SVG.regex.dots, ' .') -} - -// creates deep clone of array -function arrayClone (arr) { - var clone = arr.slice(0) - for (var i = clone.length; i--;) { - if (Array.isArray(clone[i])) { - clone[i] = arrayClone(clone[i]) - } - } - return clone -} - -// tests if a given element is instance of an object -function is (el, obj) { - return el instanceof obj -} - -// tests if a given selector matches an element -function matches (el, selector) { - return (el.matches || el.matchesSelector || el.msMatchesSelector || el.mozMatchesSelector || el.webkitMatchesSelector || el.oMatchesSelector).call(el, selector) -} - -// Convert dash-separated-string to camelCase -function camelCase (s) { - return s.toLowerCase().replace(/-(.)/g, function (m, g) { - return g.toUpperCase() - }) -} - -// Capitalize first letter of a string -function capitalize (s) { - return s.charAt(0).toUpperCase() + s.slice(1) -} - -// Ensure to six-based hex -function fullHex (hex) { - return hex.length === 4 - ? [ '#', - hex.substring(1, 2), hex.substring(1, 2), - hex.substring(2, 3), hex.substring(2, 3), - hex.substring(3, 4), hex.substring(3, 4) - ].join('') - : hex -} - -// Component to hex value -function compToHex (comp) { - var hex = comp.toString(16) - return hex.length === 1 ? '0' + hex : hex -} - -// Calculate proportional width and height values when necessary -function proportionalSize (element, width, height) { - if (width == null || height == null) { - var box = element.bbox() - - if (width == null) { - width = box.width / box.height * height - } else if (height == null) { - height = box.height / box.width * width - } - } - - return { - width: width, - height: height - } -} - -// Map matrix array to object -function arrayToMatrix (a) { - return { a: a[0], b: a[1], c: a[2], d: a[3], e: a[4], f: a[5] } -} - -// Add centre point to transform object -function ensureCentre (o, target) { - o.cx = o.cx == null ? target.bbox().cx : o.cx - o.cy = o.cy == null ? target.bbox().cy : o.cy -} - -// PathArray Helpers -function arrayToString (a) { - for (var i = 0, il = a.length, s = ''; i < il; i++) { - s += a[i][0] - - if (a[i][1] != null) { - s += a[i][1] - - if (a[i][2] != null) { - s += ' ' - s += a[i][2] - - if (a[i][3] != null) { - s += ' ' - s += a[i][3] - s += ' ' - s += a[i][4] - - if (a[i][5] != null) { - s += ' ' - s += a[i][5] - s += ' ' - s += a[i][6] - - if (a[i][7] != null) { - s += ' ' - s += a[i][7] - } - } - } - } - } - } - - return s + ' ' -} - -// Deep new id assignment -function assignNewId (node) { - // do the same for SVG child nodes as well - for (var i = node.children.length - 1; i >= 0; i--) { - assignNewId(node.children[i]) - } - - if (node.id) { - return SVG.adopt(node).id(SVG.eid(node.nodeName)) - } - - return SVG.adopt(node) -} - -// Add more bounding box properties -function fullBox (b) { - if (b.x == null) { - b.x = 0 - b.y = 0 - b.width = 0 - b.height = 0 - } - - b.w = b.width - b.h = b.height - b.x2 = b.x + b.width - b.y2 = b.y + b.height - b.cx = b.x + b.width / 2 - b.cy = b.y + b.height / 2 - - return b -} - -// Get id from reference string -function idFromReference (url) { - var m = (url || '').toString().match(SVG.regex.reference) - - if (m) return m[1] -} - -// Create matrix array for looping -var abcdef = 'abcdef'.split('') - -function closeEnough (a, b, threshold) { - return Math.abs(b - a) < (threshold || 1e-6) -} - -// TODO: Refactor this to a static function of matrix.js -function formatTransforms (o) { - - // Get all of the parameters required to form the matrix - var flipBoth = o.flip === 'both' || o.flip === true - var flipX = o.flip && (flipBoth || o.flip === 'x') ? -1 : 1 - var flipY = o.flip && (flipBoth || o.flip === 'y') ? -1 : 1 - var skewX = o.skew && o.skew.length ? o.skew[0] - : isFinite(o.skew) ? o.skew - : isFinite(o.skewX) ? o.skewX - : 0 - var skewY = o.skew && o.skew.length ? o.skew[1] - : isFinite(o.skew) ? o.skew - : isFinite(o.skewY) ? o.skewY - : 0 - var scaleX = o.scale && o.scale.length ? o.scale[0] * flipX - : isFinite(o.scale) ? o.scale * flipX - : isFinite(o.scaleX) ? o.scaleX * flipX - : flipX - var scaleY = o.scale && o.scale.length ? o.scale[1] * flipY - : isFinite(o.scale) ? o.scale * flipY - : isFinite(o.scaleY) ? o.scaleY * flipY - : flipY - var shear = o.shear || 0 - var theta = o.rotate || 0 - var origin = new SVG.Point(o.ox == null ? o.origin : o.ox, o.oy) - var ox = origin.x - var oy = origin.y - var position = new SVG.Point(o.px == null - ? o.position : o.px, o.py, {x: null, y: null}) - var px = position.x - var py = position.y - var translate = new SVG.Point(o.tx == null ? o.translate : o.tx, o.ty) - var tx = translate.x - var ty = translate.y - var relative = new SVG.Point(o.rx == null ? o.relative : o.rx, o.ry) - var rx = relative.x - var ry = relative.y - - // Populate all of the values - return { - scaleX: scaleX, - scaleY: scaleY, - skewX: skewX, - skewY: skewY, - shear: shear, - theta: theta, - rx: rx, - ry: ry, - tx: tx, - ty: ty, - ox: ox, - oy: oy, - px: px, - py: py, - } -} - -/* globals fullBox, domContains, isNulledBox, Exception */ - -SVG.Box = SVG.invent({ - create: function (source) { - var base = [0, 0, 0, 0] - source = typeof source === 'string' ? source.split(SVG.regex.delimiter).map(parseFloat) - : Array.isArray(source) ? source - : typeof source === 'object' ? [source.left != null ? source.left - : source.x, source.top != null ? source.top : source.y, source.width, source.height] - : arguments.length === 4 ? [].slice.call(arguments) - : base - - this.x = source[0] - this.y = source[1] - this.width = source[2] - this.height = source[3] - - // add center, right, bottom... - fullBox(this) - }, - extend: { - // Merge rect box with another, return a new instance - merge: function (box) { - var x = Math.min(this.x, box.x) - var y = Math.min(this.y, box.y) - - return new SVG.Box( - x, y, - Math.max(this.x + this.width, box.x + box.width) - x, - Math.max(this.y + this.height, box.y + box.height) - y - ) - }, - - transform: function (m) { - var xMin = Infinity - var xMax = -Infinity - var yMin = Infinity - var yMax = -Infinity - - var pts = [ - new SVG.Point(this.x, this.y), - new SVG.Point(this.x2, this.y), - new SVG.Point(this.x, this.y2), - new SVG.Point(this.x2, this.y2) - ] - - pts.forEach(function (p) { - p = p.transform(m) - xMin = Math.min(xMin, p.x) - xMax = Math.max(xMax, p.x) - yMin = Math.min(yMin, p.y) - yMax = Math.max(yMax, p.y) - }) - - return new SVG.Box( - xMin, yMin, - xMax - xMin, - yMax - yMin - ) - }, - - addOffset: function () { - // offset by window scroll position, because getBoundingClientRect changes when window is scrolled - this.x += window.pageXOffset - this.y += window.pageYOffset - return this - }, - toString: function () { - return this.x + ' ' + this.y + ' ' + this.width + ' ' + this.height - }, - morph: function (x, y, width, height) { - this.destination = new SVG.Box(x, y, width, height) - return this - }, - - at: function (pos) { - if (!this.destination) return this - - return new SVG.Box( - this.x + (this.destination.x - this.x) * pos - , this.y + (this.destination.y - this.y) * pos - , this.width + (this.destination.width - this.width) * pos - , this.height + (this.destination.height - this.height) * pos - ) - } - }, - - // Define Parent - parent: SVG.Element, - - // Constructor - construct: { - // Get bounding box - bbox: function () { - var box - - try { - // find native bbox - box = this.node.getBBox() - - if (isNulledBox(box) && !domContains(this.node)) { - throw new Exception('Element not in the dom') - } - } catch (e) { - try { - var clone = this.clone(SVG.parser().svg).show() - box = clone.node.getBBox() - clone.remove() - } catch (e) { - console.warn('Getting a bounding box of this element is not possible') - } - } - - return new SVG.Box(box) - }, - - rbox: function (el) { - // IE11 throws an error when element not in dom - try { - var box = new SVG.Box(this.node.getBoundingClientRect()) - if (el) return box.transform(el.screenCTM().inverse()) - return box.addOffset() - } catch (e) { - return new SVG.Box() - } - } - } -}) - -SVG.extend([SVG.Doc, SVG.Symbol, SVG.Image, SVG.Pattern, SVG.Marker, SVG.ForeignObject, SVG.View], { - viewbox: function (x, y, width, height) { - // act as getter - if (x == null) return new SVG.Box(this.attr('viewBox')) - - // act as setter - return this.attr('viewBox', new SVG.Box(x, y, width, height)) - } -}) - - -SVG.parser = function () { - var b - - if (!SVG.parser.nodes.svg.node.parentNode) { - b = document.body || document.documentElement - SVG.parser.nodes.svg.addTo(b) - } - - return SVG.parser.nodes -} - -SVG.parser.nodes = { - svg: SVG().size(2, 0).css({ - opacity: 0, - position: 'absolute', - left: '-100%', - top: '-100%', - overflow: 'hidden' - }) -} - -SVG.parser.nodes.path = SVG.parser.nodes.svg.path().node + +(function(root, factory) { + /* istanbul ignore next */ + if (typeof define === 'function' && define.amd) { + define(function(){ + return factory(root, root.document) + }) + } else if (typeof exports === 'object') { + module.exports = root.document ? factory(root, root.document) : function(w){ return factory(w, w.document) } + } else { + root.SVG = factory(root, root.document) + } +}(typeof window !== "undefined" ? window : this, function(window, document) { + +// Check that our browser supports svg +var supported = !! document.createElementNS && + !! document.createElementNS('http://www.w3.org/2000/svg','svg').createSVGRect + +// If we don't support svg, just exit without doing anything +if (!supported) + return {supported: false} + +// Otherwise, the library will be here +/* global createElement, capitalize */ +/* eslint-disable new-cap */ + +// The main wrapping element +var SVG = this.SVG = function (element) { + if (SVG.supported) { + element = createElement(element) + return element + } +} + +// Svg must be supported if we reached this stage +SVG.supported = true + +// Default namespaces +SVG.ns = 'http://www.w3.org/2000/svg' +SVG.xmlns = 'http://www.w3.org/2000/xmlns/' +SVG.xlink = 'http://www.w3.org/1999/xlink' +SVG.svgjs = 'http://svgjs.com/svgjs' + +// Element id sequence +SVG.did = 1000 + +// Get next named element id +SVG.eid = function (name) { + return 'Svgjs' + capitalize(name) + (SVG.did++) +} + +// Method for element creation +SVG.create = function (name) { + // create element + return document.createElementNS(this.ns, name) +} + +// Method for extending objects +SVG.extend = function (modules, methods) { + var key, i + + modules = Array.isArray(modules) ? modules : [modules] + + for (i = modules.length - 1; i >= 0; i--) { + if (modules[i]) { + for (key in methods) { + modules[i].prototype[key] = methods[key] + } + } + } +} + +// Invent new element +SVG.invent = function (config) { + // Create element initializer + var initializer = typeof config.create === 'function' ? config.create + : function (node) { + SVG.Element.call(this, node || SVG.create(config.create)) + } + + // Inherit prototype + if (config.inherit) { + initializer.prototype = new config.inherit() + initializer.prototype.constructor = initializer + } + + // Extend with methods + if (config.extend) { + SVG.extend(initializer, config.extend) + } + + // Attach construct method to parent + if (config.construct) { SVG.extend(config.parent || SVG.Container, config.construct) } + + return initializer +} + +// Adopt existing svg elements +SVG.adopt = function (node) { + // check for presence of node + if (!node) return null + + // make sure a node isn't already adopted + if (node.instance instanceof SVG.Element) return node.instance + + if (!(node instanceof window.SVGElement)) { + return new SVG.HtmlNode(node) + } + + // initialize variables + var element + + // adopt with element-specific settings + if (node.nodeName === 'svg') { + element = new SVG.Doc(node) + } else if (node.nodeName === 'linearGradient' || node.nodeName === 'radialGradient') { + element = new SVG.Gradient(node) + } else if (SVG[capitalize(node.nodeName)]) { + element = new SVG[capitalize(node.nodeName)](node) + } else { + element = new SVG.Parent(node) + } + + return element +} + +// Storage for regular expressions +SVG.regex = { + // Parse unit value + numberAndUnit: /^([+-]?(\d+(\.\d*)?|\.\d+)(e[+-]?\d+)?)([a-z%]*)$/i, + + // Parse hex value + hex: /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i, + + // Parse rgb value + rgb: /rgb\((\d+),(\d+),(\d+)\)/, + + // Parse reference id + reference: /#([a-z0-9\-_]+)/i, + + // splits a transformation chain + transforms: /\)\s*,?\s*/, + + // Whitespace + whitespace: /\s/g, + + // Test hex value + isHex: /^#[a-f0-9]{3,6}$/i, + + // Test rgb value + isRgb: /^rgb\(/, + + // Test css declaration + isCss: /[^:]+:[^;]+;?/, + + // Test for blank string + isBlank: /^(\s+)?$/, + + // Test for numeric string + isNumber: /^[+-]?(\d+(\.\d*)?|\.\d+)(e[+-]?\d+)?$/i, + + // Test for percent value + isPercent: /^-?[\d.]+%$/, + + // Test for image url + isImage: /\.(jpg|jpeg|png|gif|svg)(\?[^=]+.*)?/i, + + // split at whitespace and comma + delimiter: /[\s,]+/, + + // The following regex are used to parse the d attribute of a path + + // Matches all hyphens which are not after an exponent + hyphen: /([^e])-/gi, + + // Replaces and tests for all path letters + pathLetters: /[MLHVCSQTAZ]/gi, + + // yes we need this one, too + isPathLetter: /[MLHVCSQTAZ]/i, + + // matches 0.154.23.45 + numbersWithDots: /((\d?\.\d+(?:e[+-]?\d+)?)((?:\.\d+(?:e[+-]?\d+)?)+))+/gi, + + // matches . + dots: /\./g +} + + +SVG.utils = { + // Map function + map: function (array, block) { + var i + var il = array.length + var result = [] + + for (i = 0; i < il; i++) { + result.push(block(array[i])) + } + + return result + }, + + // Filter function + filter: function (array, block) { + var i + var il = array.length + var result = [] + + for (i = 0; i < il; i++) { + if (block(array[i])) { result.push(array[i]) } + } + + return result + }, + + // Degrees to radians + radians: function (d) { + return d % 360 * Math.PI / 180 + }, + + // Radians to degrees + degrees: function (r) { + return r * 180 / Math.PI % 360 + }, + + filterSVGElements: function (nodes) { + return this.filter(nodes, function (el) { return el instanceof window.SVGElement }) + } + +} + + +SVG.void = function () {} + +SVG.defaults = { + + // Default animation values + timeline: { + duration: 600, + ease: '>', + delay: 0, + }, + + // Default attribute values + attrs: { + + // fill and stroke + 'fill-opacity': 1, + 'stroke-opacity': 1, + 'stroke-width': 0, + 'stroke-linejoin': 'miter', + 'stroke-linecap': 'butt', + fill: '#000000', + stroke: '#000000', + opacity: 1, + + // position + x: 0, + y: 0, + cx: 0, + cy: 0, + + // size + width: 0, + height: 0, + + // radius + r: 0, + rx: 0, + ry: 0, + + // gradient + offset: 0, + 'stop-opacity': 1, + 'stop-color': '#000000', + + // text + 'font-size': 16, + 'font-family': 'Helvetica, Arial, sans-serif', + 'text-anchor': 'start' + } +} + +SVG.Queue = SVG.invent({ + create: function () { + this._first = null + this._last = null + this.length = 0 + this.id = 0 + }, + + extend: { + push: function (value) { + + // An item stores an id and the provided value + var item = { id: this.id++, value: value } + + // Deal with the queue being empty or populated + if (this._last) { + this._last = this._last.next = item + } else { + this._last = this._first = item + } + + this.length++ + }, + + shift: function () { + if (this.length == 0) { + return + } + + var remove = this._first + this._first = remove.next + this._last = --this.length ? this._last : null + return remove.value + }, + + // Shows us the first item in the list + first: function () { + return this._first && this._first.value + }, + + // Shows us the last item in the list + last: function () { + return this._last && this._last.value + }, + + // Removes the first item from the front where matcher returns true + remove: function (matcher) { + // Find the first match + var previous = null + var current = this._first + while (current) { + + // If we have a match, we are done + if (matcher(current)) break + + // Otherwise, advance both of the pointers + previous = current + current = current.next + } + + // If we got the first item, adjust the first pointer + if (current && current === this._first) + this._first = this._first.next + + // If we got the last item, adjust the last pointer + if (current && current === this._last) + this._last = previous + + // If we got an item, fix the list and return the item + if (current) { + --this.length + + if (previous) { + previous.next = current.next + } + + return current.item + } + } + } +}) + +/* globals fullHex, compToHex */ + +/* + +Color { + constructor (a, b, c, space) { + space: 'hsl' + a: 30 + b: 20 + c: 10 + }, + + toRgb () { return new Color in rgb space } + toHsl () { return new Color in hsl space } + toLab () { return new Color in lab space } + + toArray () { [space, a, b, c] } + fromArray () { convert it back } +} + +// Conversions aren't always exact because of monitor profiles etc... +new Color(h, s, l, 'hsl') !== new Color(r, g, b).hsl() +new Color(100, 100, 100, [space]) +new Color('hsl(30, 20, 10)') + +// Sugar +SVG.rgb(30, 20, 50).lab() +SVG.hsl() +SVG.lab('rgb(100, 100, 100)') +*/ + +// Module for color convertions +SVG.Color = function (color) { + var match + + // initialize defaults + this.r = 0 + this.g = 0 + this.b = 0 + + if (!color) return + + // parse color + if (typeof color === 'string') { + if (SVG.regex.isRgb.test(color)) { + // get rgb values + match = SVG.regex.rgb.exec(color.replace(SVG.regex.whitespace, '')) + + // parse numeric values + this.r = parseInt(match[1]) + this.g = parseInt(match[2]) + this.b = parseInt(match[3]) + } else if (SVG.regex.isHex.test(color)) { + // get hex values + match = SVG.regex.hex.exec(fullHex(color)) + + // parse numeric values + this.r = parseInt(match[1], 16) + this.g = parseInt(match[2], 16) + this.b = parseInt(match[3], 16) + } + } else if (typeof color === 'object') { + this.r = color.r + this.g = color.g + this.b = color.b + } +} + +SVG.extend(SVG.Color, { + // Default to hex conversion + toString: function () { + return this.toHex() + }, + toArray: function () { + return [this.r, this.g, this.b] + }, + fromArray: function (a) { + return new SVG.Color(a[0], a[1], a[2]) + }, + // Build hex value + toHex: function () { + return '#' + + compToHex(this.r) + + compToHex(this.g) + + compToHex(this.b) + }, + // Build rgb value + toRgb: function () { + return 'rgb(' + [this.r, this.g, this.b].join() + ')' + }, + // Calculate true brightness + brightness: function () { + return (this.r / 255 * 0.30) + + (this.g / 255 * 0.59) + + (this.b / 255 * 0.11) + }, + // Make color morphable + morph: function (color) { + this.destination = new SVG.Color(color) + + return this + }, + // Get morphed color at given position + at: function (pos) { + // make sure a destination is defined + if (!this.destination) return this + + // normalise pos + pos = pos < 0 ? 0 : pos > 1 ? 1 : pos + + // generate morphed color + return new SVG.Color({ + r: ~~(this.r + (this.destination.r - this.r) * pos), + g: ~~(this.g + (this.destination.g - this.g) * pos), + b: ~~(this.b + (this.destination.b - this.b) * pos) + }) + } + +}) + +// Testers + +// Test if given value is a color string +SVG.Color.test = function (color) { + color += '' + return SVG.regex.isHex.test(color) || + SVG.regex.isRgb.test(color) +} + +// Test if given value is a rgb object +SVG.Color.isRgb = function (color) { + return color && typeof color.r === 'number' && + typeof color.g === 'number' && + typeof color.b === 'number' +} + +// Test if given value is a color +SVG.Color.isColor = function (color) { + return SVG.Color.isRgb(color) || SVG.Color.test(color) +} + +/* global arrayClone */ + +// Module for array conversion +SVG.Array = function (array, fallback) { + array = (array || []).valueOf() + + // if array is empty and fallback is provided, use fallback + if (array.length === 0 && fallback) { + array = fallback.valueOf() + } + + // parse array + this.value = this.parse(array) +} + +SVG.extend(SVG.Array, { + // Make array morphable + morph: function (array) { + this.destination = this.parse(array) + + // normalize length of arrays + if (this.value.length !== this.destination.length) { + var lastValue = this.value[this.value.length - 1] + var lastDestination = this.destination[this.destination.length - 1] + + while (this.value.length > this.destination.length) { + this.destination.push(lastDestination) + } + while (this.value.length < this.destination.length) { + this.value.push(lastValue) + } + } + + return this + }, + // Clean up any duplicate points + settle: function () { + // find all unique values + for (var i = 0, il = this.value.length, seen = []; i < il; i++) { + if (seen.indexOf(this.value[i]) === -1) { + seen.push(this.value[i]) + } + } + + // set new value + this.value = seen + return seen + }, + // Get morphed array at given position + at: function (pos) { + // make sure a destination is defined + if (!this.destination) return this + + // generate morphed array + for (var i = 0, il = this.value.length, array = []; i < il; i++) { + array.push(this.value[i] + (this.destination[i] - this.value[i]) * pos) + } + + return new SVG.Array(array) + }, + toArray: function () { + return this.value + }, + fromArray: function (a) { + return new SVG.Array(a) + }, + // Convert array to string + toString: function () { + return this.value.join(' ') + }, + // Real value + valueOf: function () { + return this.value + }, + // Parse whitespace separated string + parse: function (array) { + array = array.valueOf() + + // if already is an array, no need to parse it + if (Array.isArray(array)) return array + + return array.trim().split(SVG.regex.delimiter).map(parseFloat) + }, + // Reverse array + reverse: function () { + this.value.reverse() + + return this + }, + clone: function () { + var clone = new this.constructor() + clone.value = arrayClone(this.value) + return clone + } +}) + + +// Poly points array +SVG.PointArray = function (array, fallback) { + SVG.Array.call(this, array, fallback || [[0, 0]]) +} + +// Inherit from SVG.Array +SVG.PointArray.prototype = new SVG.Array() +SVG.PointArray.prototype.constructor = SVG.PointArray + +SVG.extend(SVG.PointArray, { + // Convert array to string + toString: function () { + // convert to a poly point string + for (var i = 0, il = this.value.length, array = []; i < il; i++) { + array.push(this.value[i].join(',')) + } + + return array.join(' ') + }, + + toArray: function () { + return this.value.reduce(function (prev, curr) { + return [].concat.call(prev, curr) + }, []) + }, + + fromArray: function (a) { + return new SVG.PointArray(a) + }, + + // Convert array to line object + toLine: function () { + return { + x1: this.value[0][0], + y1: this.value[0][1], + x2: this.value[1][0], + y2: this.value[1][1] + } + }, + + // Get morphed array at given position + at: function (pos) { + // make sure a destination is defined + if (!this.destination) return this + + // generate morphed point string + for (var i = 0, il = this.value.length, array = []; i < il; i++) { + array.push([ + this.value[i][0] + (this.destination[i][0] - this.value[i][0]) * pos, + this.value[i][1] + (this.destination[i][1] - this.value[i][1]) * pos + ]) + } + + return new SVG.PointArray(array) + }, + + // Parse point string and flat array + parse: function (array) { + var points = [] + + array = array.valueOf() + + // if it is an array + if (Array.isArray(array)) { + // and it is not flat, there is no need to parse it + if (Array.isArray(array[0])) { + return array + } + } else { // Else, it is considered as a string + // parse points + array = array.trim().split(SVG.regex.delimiter).map(parseFloat) + } + + // validate points - https://svgwg.org/svg2-draft/shapes.html#DataTypePoints + // Odd number of coordinates is an error. In such cases, drop the last odd coordinate. + if (array.length % 2 !== 0) array.pop() + + // wrap points in two-tuples and parse points as floats + for (var i = 0, len = array.length; i < len; i = i + 2) { + points.push([ array[i], array[i + 1] ]) + } + + return points + }, + + // Move point string + move: function (x, y) { + var box = this.bbox() + + // get relative offset + x -= box.x + y -= box.y + + // move every point + if (!isNaN(x) && !isNaN(y)) { + for (var i = this.value.length - 1; i >= 0; i--) { + this.value[i] = [this.value[i][0] + x, this.value[i][1] + y] + } + } + + return this + }, + // Resize poly string + size: function (width, height) { + var i + var box = this.bbox() + + // recalculate position of all points according to new size + for (i = this.value.length - 1; i >= 0; i--) { + if (box.width) this.value[i][0] = ((this.value[i][0] - box.x) * width) / box.width + box.x + if (box.height) this.value[i][1] = ((this.value[i][1] - box.y) * height) / box.height + box.y + } + + return this + }, + + // Get bounding box of points + bbox: function () { + var maxX = -Infinity + var maxY = -Infinity + var minX = Infinity + var minY = Infinity + this.value.forEach(function (el) { + maxX = Math.max(el[0], maxX) + maxY = Math.max(el[1], maxY) + minX = Math.min(el[0], minX) + minY = Math.min(el[1], minY) + }) + return {x: minX, y: minY, width: maxX - minX, height: maxY - minY} + } +}) + +/* globals arrayToString, pathRegReplace */ + +var pathHandlers = { + M: function (c, p, p0) { + p.x = p0.x = c[0] + p.y = p0.y = c[1] + + return ['M', p.x, p.y] + }, + L: function (c, p) { + p.x = c[0] + p.y = c[1] + return ['L', c[0], c[1]] + }, + H: function (c, p) { + p.x = c[0] + return ['H', c[0]] + }, + V: function (c, p) { + p.y = c[0] + return ['V', c[0]] + }, + C: function (c, p) { + p.x = c[4] + p.y = c[5] + return ['C', c[0], c[1], c[2], c[3], c[4], c[5]] + }, + S: function (c, p) { + p.x = c[2] + p.y = c[3] + return ['S', c[0], c[1], c[2], c[3]] + }, + Q: function (c, p) { + p.x = c[2] + p.y = c[3] + return ['Q', c[0], c[1], c[2], c[3]] + }, + T: function (c, p) { + p.x = c[0] + p.y = c[1] + return ['T', c[0], c[1]] + }, + Z: function (c, p, p0) { + p.x = p0.x + p.y = p0.y + return ['Z'] + }, + A: function (c, p) { + p.x = c[5] + p.y = c[6] + return ['A', c[0], c[1], c[2], c[3], c[4], c[5], c[6]] + } +} + +var mlhvqtcsaz = 'mlhvqtcsaz'.split('') + +for (var i = 0, il = mlhvqtcsaz.length; i < il; ++i) { + pathHandlers[mlhvqtcsaz[i]] = (function (i) { + return function (c, p, p0) { + if (i === 'H') c[0] = c[0] + p.x + else if (i === 'V') c[0] = c[0] + p.y + else if (i === 'A') { + c[5] = c[5] + p.x + c[6] = c[6] + p.y + } else { + for (var j = 0, jl = c.length; j < jl; ++j) { + c[j] = c[j] + (j % 2 ? p.y : p.x) + } + } + + return pathHandlers[i](c, p, p0) + } + })(mlhvqtcsaz[i].toUpperCase()) +} + +// Path points array +SVG.PathArray = function (array, fallback) { + SVG.Array.call(this, array, fallback || [['M', 0, 0]]) +} + +// Inherit from SVG.Array +SVG.PathArray.prototype = new SVG.Array() +SVG.PathArray.prototype.constructor = SVG.PathArray + +SVG.extend(SVG.PathArray, { + // Convert array to string + toString: function () { + return arrayToString(this.value) + }, + toArray: function () { + return this.value.reduce(function (prev, curr) { + return [].concat.call(prev, curr) + }, []) + }, + fromArray: function (a) { + return new SVG.PathArray(a) + }, + // Move path string + move: function (x, y) { + // get bounding box of current situation + var box = this.bbox() + + // get relative offset + x -= box.x + y -= box.y + + if (!isNaN(x) && !isNaN(y)) { + // move every point + for (var l, i = this.value.length - 1; i >= 0; i--) { + l = this.value[i][0] + + if (l === 'M' || l === 'L' || l === 'T') { + this.value[i][1] += x + this.value[i][2] += y + } else if (l === 'H') { + this.value[i][1] += x + } else if (l === 'V') { + this.value[i][1] += y + } else if (l === 'C' || l === 'S' || l === 'Q') { + this.value[i][1] += x + this.value[i][2] += y + this.value[i][3] += x + this.value[i][4] += y + + if (l === 'C') { + this.value[i][5] += x + this.value[i][6] += y + } + } else if (l === 'A') { + this.value[i][6] += x + this.value[i][7] += y + } + } + } + + return this + }, + // Resize path string + size: function (width, height) { + // get bounding box of current situation + var box = this.bbox() + var i, l + + // recalculate position of all points according to new size + for (i = this.value.length - 1; i >= 0; i--) { + l = this.value[i][0] + + if (l === 'M' || l === 'L' || l === 'T') { + this.value[i][1] = ((this.value[i][1] - box.x) * width) / box.width + box.x + this.value[i][2] = ((this.value[i][2] - box.y) * height) / box.height + box.y + } else if (l === 'H') { + this.value[i][1] = ((this.value[i][1] - box.x) * width) / box.width + box.x + } else if (l === 'V') { + this.value[i][1] = ((this.value[i][1] - box.y) * height) / box.height + box.y + } else if (l === 'C' || l === 'S' || l === 'Q') { + this.value[i][1] = ((this.value[i][1] - box.x) * width) / box.width + box.x + this.value[i][2] = ((this.value[i][2] - box.y) * height) / box.height + box.y + this.value[i][3] = ((this.value[i][3] - box.x) * width) / box.width + box.x + this.value[i][4] = ((this.value[i][4] - box.y) * height) / box.height + box.y + + if (l === 'C') { + this.value[i][5] = ((this.value[i][5] - box.x) * width) / box.width + box.x + this.value[i][6] = ((this.value[i][6] - box.y) * height) / box.height + box.y + } + } else if (l === 'A') { + // resize radii + this.value[i][1] = (this.value[i][1] * width) / box.width + this.value[i][2] = (this.value[i][2] * height) / box.height + + // move position values + this.value[i][6] = ((this.value[i][6] - box.x) * width) / box.width + box.x + this.value[i][7] = ((this.value[i][7] - box.y) * height) / box.height + box.y + } + } + + return this + }, + // Test if the passed path array use the same path data commands as this path array + equalCommands: function (pathArray) { + var i, il, equalCommands + + pathArray = new SVG.PathArray(pathArray) + + equalCommands = this.value.length === pathArray.value.length + for (i = 0, il = this.value.length; equalCommands && i < il; i++) { + equalCommands = this.value[i][0] === pathArray.value[i][0] + } + + return equalCommands + }, + // Make path array morphable + morph: function (pathArray) { + pathArray = new SVG.PathArray(pathArray) + + if (this.equalCommands(pathArray)) { + this.destination = pathArray + } else { + this.destination = null + } + + return this + }, + // Get morphed path array at given position + at: function (pos) { + // make sure a destination is defined + if (!this.destination) return this + + var sourceArray = this.value + var destinationArray = this.destination.value + var array = [] + var pathArray = new SVG.PathArray() + var i, il, j, jl + + // Animate has specified in the SVG spec + // See: https://www.w3.org/TR/SVG11/paths.html#PathElement + for (i = 0, il = sourceArray.length; i < il; i++) { + array[i] = [sourceArray[i][0]] + for (j = 1, jl = sourceArray[i].length; j < jl; j++) { + array[i][j] = sourceArray[i][j] + (destinationArray[i][j] - sourceArray[i][j]) * pos + } + // For the two flags of the elliptical arc command, the SVG spec say: + // Flags and booleans are interpolated as fractions between zero and one, with any non-zero value considered to be a value of one/true + // Elliptical arc command as an array followed by corresponding indexes: + // ['A', rx, ry, x-axis-rotation, large-arc-flag, sweep-flag, x, y] + // 0 1 2 3 4 5 6 7 + if (array[i][0] === 'A') { + array[i][4] = +(array[i][4] !== 0) + array[i][5] = +(array[i][5] !== 0) + } + } + + // Directly modify the value of a path array, this is done this way for performance + pathArray.value = array + return pathArray + }, + // Absolutize and parse path to array + parse: function (array) { + // if it's already a patharray, no need to parse it + if (array instanceof SVG.PathArray) return array.valueOf() + + // prepare for parsing + var s + var paramCnt = { 'M': 2, 'L': 2, 'H': 1, 'V': 1, 'C': 6, 'S': 4, 'Q': 4, 'T': 2, 'A': 7, 'Z': 0 } + + if (typeof array === 'string') { + array = array + .replace(SVG.regex.numbersWithDots, pathRegReplace) // convert 45.123.123 to 45.123 .123 + .replace(SVG.regex.pathLetters, ' $& ') // put some room between letters and numbers + .replace(SVG.regex.hyphen, '$1 -') // add space before hyphen + .trim() // trim + .split(SVG.regex.delimiter) // split into array + } else { + array = array.reduce(function (prev, curr) { + return [].concat.call(prev, curr) + }, []) + } + + // array now is an array containing all parts of a path e.g. ['M', '0', '0', 'L', '30', '30' ...] + var result = [] + var p = new SVG.Point() + var p0 = new SVG.Point() + var index = 0 + var len = array.length + + do { + // Test if we have a path letter + if (SVG.regex.isPathLetter.test(array[index])) { + s = array[index] + ++index + // If last letter was a move command and we got no new, it defaults to [L]ine + } else if (s === 'M') { + s = 'L' + } else if (s === 'm') { + s = 'l' + } + + result.push(pathHandlers[s].call(null, + array.slice(index, (index = index + paramCnt[s.toUpperCase()])).map(parseFloat), + p, p0 + ) + ) + } while (len > index) + + return result + }, + // Get bounding box of path + bbox: function () { + SVG.parser().path.setAttribute('d', this.toString()) + return SVG.parser.nodes.path.getBBox() + } + +}) + + +// Module for unit convertions +SVG.Number = SVG.invent({ + // Initialize + create: function (value, unit) { + // initialize defaults + this.value = 0 + this.unit = unit || '' + + // parse value + if (typeof value === 'number') { + // ensure a valid numeric value + this.value = isNaN(value) ? 0 : !isFinite(value) ? (value < 0 ? -3.4e+38 : +3.4e+38) : value + } else if (typeof value === 'string') { + unit = value.match(SVG.regex.numberAndUnit) + + if (unit) { + // make value numeric + this.value = parseFloat(unit[1]) + + // normalize + if (unit[5] === '%') { this.value /= 100 } else if (unit[5] === 's') { + this.value *= 1000 + } + + // store unit + this.unit = unit[5] + } + } else { + if (value instanceof SVG.Number) { + this.value = value.valueOf() + this.unit = value.unit + } + } + }, + // Add methods + extend: { + // Stringalize + toString: function () { + return (this.unit === '%' ? ~~(this.value * 1e8) / 1e6 + : this.unit === 's' ? this.value / 1e3 + : this.value + ) + this.unit + }, + toJSON: function () { + return this.toString() + }, // Convert to primitive + toArray: function () { + return [this.value] + }, + fromArray: function (val) { + return new SVG.Number(val[0]) + }, + valueOf: function () { + return this.value + }, + // Add number + plus: function (number) { + number = new SVG.Number(number) + return new SVG.Number(this + number, this.unit || number.unit) + }, + // Subtract number + minus: function (number) { + number = new SVG.Number(number) + return new SVG.Number(this - number, this.unit || number.unit) + }, + // Multiply number + times: function (number) { + number = new SVG.Number(number) + return new SVG.Number(this * number, this.unit || number.unit) + }, + // Divide number + divide: function (number) { + number = new SVG.Number(number) + return new SVG.Number(this / number, this.unit || number.unit) + }, + // Convert to different unit + to: function (unit) { + var number = new SVG.Number(this) + + if (typeof unit === 'string') { + number.unit = unit + } + + return number + }, + // Make number morphable + morph: function (number) { + this.destination = new SVG.Number(number) + + if (number.relative) { + this.destination.value += this.value + } + + return this + }, + // Get morphed number at given position + at: function (pos) { + // Make sure a destination is defined + if (!this.destination) return this + + // Generate new morphed number + return new SVG.Number(this.destination) + .minus(this) + .times(pos) + .plus(this) + } + } +}) + +/* global createElement */ + +SVG.HtmlNode = SVG.invent({ + create: function (element) { + this.node = element + }, + + extend: { + add: function (element, i) { + element = createElement(element) + + if (element.node !== this.node.children[i]) { + this.node.insertBefore(element.node, this.node.children[i] || null) + } + + return this + }, + + put: function (element, i) { + this.add(element, i) + return element + } + } +}) + +/* global proportionalSize, assignNewId, createElement, matches, is */ + +SVG.Element = SVG.invent({ + // Initialize node + create: function (node) { + // event listener + this.events = {} + + // initialize data object + this.dom = {} + + // create circular reference + this.node = node + if (this.node) { + this.type = node.nodeName + this.node.instance = this + this.events = node.events || {} + + if (node.hasAttribute('svgjs:data')) { + // pull svgjs data from the dom (getAttributeNS doesn't work in html5) + this.setData(JSON.parse(node.getAttribute('svgjs:data')) || {}) + } + } + }, + + // Add class methods + extend: { + // Move over x-axis + x: function (x) { + return this.attr('x', x) + }, + + // Move over y-axis + y: function (y) { + return this.attr('y', y) + }, + + // Move by center over x-axis + cx: function (x) { + return x == null ? this.x() + this.width() / 2 : this.x(x - this.width() / 2) + }, + + // Move by center over y-axis + cy: function (y) { + return y == null ? this.y() + this.height() / 2 : this.y(y - this.height() / 2) + }, + + // Move element to given x and y values + move: function (x, y) { + return this.x(x).y(y) + }, + + // Move element by its center + center: function (x, y) { + return this.cx(x).cy(y) + }, + + // Set width of element + width: function (width) { + return this.attr('width', width) + }, + + // Set height of element + height: function (height) { + return this.attr('height', height) + }, + + // Set element size to given width and height + size: function (width, height) { + var p = proportionalSize(this, width, height) + + return this + .width(new SVG.Number(p.width)) + .height(new SVG.Number(p.height)) + }, + + // Clone element + clone: function (parent) { + // write dom data to the dom so the clone can pickup the data + this.writeDataToDom() + + // clone element and assign new id + var clone = assignNewId(this.node.cloneNode(true)) + + // insert the clone in the given parent or after myself + if (parent) parent.add(clone) + else this.after(clone) + + return clone + }, + + // Remove element + remove: function () { + if (this.parent()) { this.parent().removeElement(this) } + + return this + }, + + // Replace element + replace: function (element) { + this.after(element).remove() + + return element + }, + + // Add element to given container and return self + addTo: function (parent) { + return createElement(parent).put(this) + }, + + // Add element to given container and return container + putIn: function (parent) { + return createElement(parent).add(this) + }, + + // Get / set id + id: function (id) { + // generate new id if no id set + if (typeof id === 'undefined' && !this.node.id) { + this.node.id = SVG.eid(this.type) + } + + // dont't set directly width this.node.id to make `null` work correctly + return this.attr('id', id) + }, + + // Checks whether the given point inside the bounding box of the element + inside: function (x, y) { + var box = this.bbox() + + return x > box.x && + y > box.y && + x < box.x + box.width && + y < box.y + box.height + }, + + // Show element + show: function () { + return this.css('display', '') + }, + + // Hide element + hide: function () { + return this.css('display', 'none') + }, + + // Is element visible? + visible: function () { + return this.css('display') !== 'none' + }, + + // Return id on string conversion + toString: function () { + return this.id() + }, + + // Return array of classes on the node + classes: function () { + var attr = this.attr('class') + return attr == null ? [] : attr.trim().split(SVG.regex.delimiter) + }, + + // Return true if class exists on the node, false otherwise + hasClass: function (name) { + return this.classes().indexOf(name) !== -1 + }, + + // Add class to the node + addClass: function (name) { + if (!this.hasClass(name)) { + var array = this.classes() + array.push(name) + this.attr('class', array.join(' ')) + } + + return this + }, + + // Remove class from the node + removeClass: function (name) { + if (this.hasClass(name)) { + this.attr('class', this.classes().filter(function (c) { + return c !== name + }).join(' ')) + } + + return this + }, + + // Toggle the presence of a class on the node + toggleClass: function (name) { + return this.hasClass(name) ? this.removeClass(name) : this.addClass(name) + }, + + // Get referenced element form attribute value + reference: function (attr) { + return SVG.get(this.attr(attr)) + }, + + // Returns the parent element instance + parent: function (type) { + var parent = this + + // check for parent + if (!parent.node.parentNode) return null + + // get parent element + parent = SVG.adopt(parent.node.parentNode) + + if (!type) return parent + + // loop trough ancestors if type is given + while (parent && parent.node instanceof window.SVGElement) { + if (typeof type === 'string' ? parent.matches(type) : parent instanceof type) return parent + parent = SVG.adopt(parent.node.parentNode) + } + }, + + // Get parent document + doc: function () { + var p = this.parent(SVG.Doc) + return p && p.doc() + }, + + // Get defs + defs: function () { + return this.doc().defs() + }, + + // return array of all ancestors of given type up to the root svg + parents: function (type) { + var parents = [] + var parent = this + + do { + parent = parent.parent(type) + if (!parent || !parent.node) break + + parents.push(parent) + } while (parent.parent) + + return parents + }, + + // matches the element vs a css selector + matches: function (selector) { + return matches(this.node, selector) + }, + + // Returns the svg node to call native svg methods on it + native: function () { + return this.node + }, + + // Import raw svg + svg: function (svg) { + var well, len + + // act as a setter if svg is given + if (svg && this instanceof SVG.Parent) { + // create temporary holder + well = document.createElementNS(SVG.ns, 'svg') + // dump raw svg + well.innerHTML = svg + + // transplant nodes + for (len = well.children.length; len--;) { + this.node.appendChild(well.firstElementChild) + } + + // otherwise act as a getter + } else { + // write svgjs data to the dom + this.writeDataToDom() + + return this.node.outerHTML + } + + return this + }, + + // write svgjs data to the dom + writeDataToDom: function () { + // dump variables recursively + if (this.is(SVG.Parent)) { + this.each(function () { + this.writeDataToDom() + }) + } + + // remove previously set data + this.node.removeAttribute('svgjs:data') + + if (Object.keys(this.dom).length) { + this.node.setAttribute('svgjs:data', JSON.stringify(this.dom)) // see #428 + } + return this + }, + + // set given data to the elements data property + setData: function (o) { + this.dom = o + return this + }, + is: function (obj) { + return is(this, obj) + } + } +}) + +/* global abcdef, arrayToMatrix, closeEnough, formatTransforms */ + +SVG.Matrix = SVG.invent({ + // Initialize + create: function (source) { + var base = arrayToMatrix([1, 0, 0, 1, 0, 0]) + var i + + // ensure source as object + source = source instanceof SVG.Element ? source.matrixify() + : typeof source === 'string' ? arrayToMatrix(source.split(SVG.regex.delimiter).map(parseFloat)) + : Array.isArray(source) ? arrayToMatrix(source) + : (typeof source === 'object' && ( + source.a != null || source.b != null || source.c != null + || source.d != null || source.e != null || source.f != null + )) ? source + : (typeof source === 'object') ? new SVG.Matrix().transform(source) + : arguments.length === 6 ? arrayToMatrix([].slice.call(arguments)) + : base + + // merge source + for (i = abcdef.length - 1; i >= 0; --i) { + this[abcdef[i]] = source[abcdef[i]] != null + ? source[abcdef[i]] + : base[abcdef[i]] + } + }, + + // Add methods + extend: { + + // Clones this matrix + clone: function () { + return new SVG.Matrix(this) + }, + + // Transform a matrix into another matrix by manipulating the space + transform: function (o) { + // Check if o is a matrix and then left multiply it directly + if (o.a != null) { + var matrix = new SVG.Matrix(o) + var newMatrix = this.lmultiply(matrix) + return newMatrix + } + + // Get the proposed transformations and the current transformations + var t = formatTransforms(o) + var currentTransform = new SVG.Matrix(this) + + // Construct the resulting matrix + var transformer = new SVG.Matrix() + .translate(-t.ox, -t.oy) + .scale(t.scaleX, t.scaleY) + .skew(t.skewX, t.skewY) + .shear(t.shear) + .rotate(t.theta) + .translate(t.ox, t.oy) + .translate(t.rx, t.ry) + .lmultiply(currentTransform) + + // If we want the origin at a particular place, we force it there + if (isFinite(t.px) || isFinite(t.py)) { + + // Figure out where the origin went and the delta to get there + var current = new SVG.Point(t.ox - t.rx, t.oy - t.ry).transform(transformer) + var dx = t.px ? t.px - current.x : 0 + var dy = t.py ? t.py - current.y : 0 + + // Apply another translation + transformer = transformer.translate(dx, dy) + } + + // We can apply translations after everything else + transformer = transformer.translate(t.tx, t.ty) + return transformer + }, + + // Applies a matrix defined by its affine parameters + compose: function (o) { + // Get the parameters + var sx = o.scaleX || 1 + var sy = o.scaleY || 1 + var lam = o.shear || 0 + var theta = o.rotate || 0 + var tx = o.translateX || 0 + var ty = o.translateY || 0 + + // Apply the standard matrix + var result = new SVG.Matrix() + .scale(sx, sy) + .shear(lam) + .rotate(theta) + .translate(tx, ty) + .lmultiply(this) + return result + }, + + // Decomposes this matrix into its affine parameters + decompose: function () { + // Get the parameters from the matrix + var a = this.a + var b = this.b + var c = this.c + var d = this.d + var e = this.e + var f = this.f + + // Figure out if the winding direction is clockwise or counterclockwise + var determinant = a * d - b * c + var ccw = determinant > 0 ? 1 : -1 + + // Since we only shear in x, we can use the x basis to get the x scale + // and the rotation of the resulting matrix + var sx = ccw * Math.sqrt(a * a + b * b) + var theta = 180 / Math.PI * Math.atan2(ccw * b, ccw * a) + + // We can then solve the y basis vector simultaneously to get the other + // two affine parameters directly from these parameters + var lam = (a * c + b * d) / determinant + var sy = ((c * sx) / (lam * a - b)) || ((d * sx) / (lam * b + a)) + + // Construct the decomposition and return it + return { + // Return the affine parameters + scaleX: sx, + scaleY: sy, + shear: lam, + rotate: theta, + translateX: e, + translateY: f, + + // Return the matrix parameters + a: this.a, + b: this.b, + c: this.c, + d: this.d, + e: this.e, + f: this.f + } + }, + + // Morph one matrix into another + morph: function (matrix) { + // Store new destination + this.destination = new SVG.Matrix(matrix) + return this + }, + + // Get morphed matrix at a given position + at: function (pos) { + // Make sure a destination is defined + if (!this.destination) return this + + // Calculate morphed matrix at a given position + var matrix = new SVG.Matrix({ + a: this.a + (this.destination.a - this.a) * pos, + b: this.b + (this.destination.b - this.b) * pos, + c: this.c + (this.destination.c - this.c) * pos, + d: this.d + (this.destination.d - this.d) * pos, + e: this.e + (this.destination.e - this.e) * pos, + f: this.f + (this.destination.f - this.f) * pos + }) + + return matrix + }, + + // Left multiplies by the given matrix + multiply: function (matrix) { + // Get the matrices + var l = this + var r = new SVG.Matrix(matrix) + + // Work out the product directly + var a = l.a * r.a + l.c * r.b + var b = l.b * r.a + l.d * r.b + var c = l.a * r.c + l.c * r.d + var d = l.b * r.c + l.d * r.d + var e = l.e + l.a * r.e + l.c * r.f + var f = l.f + l.b * r.e + l.d * r.f + + // Form the matrix and return it + var product = new SVG.Matrix(a, b, c, d, e, f) + return product + }, + + lmultiply: function (matrix) { + var result = new SVG.Matrix(matrix).multiply(this) + return result + }, + + // Inverses matrix + inverse: function () { + + // Get the current parameters out of the matrix + var a = this.a + var b = this.b + var c = this.c + var d = this.d + var e = this.e + var f = this.f + + // Invert the 2x2 matrix in the top left + var det = a * d - b * c + if (!det) throw new Error("Cannot invert " + this) + + // Calculate the top 2x2 matrix + var na = d / det + var nb = -b / det + var nc = -c / det + var nd = a / det + + // Apply the inverted matrix to the top right + var ne = - ( na * e + nc * f ) + var nf = - ( nb * e + nd * f ) + + // Construct the inverted matrix + return new SVG.Matrix(na, nb, nc, nd, ne, nf) + }, + + // Translate matrix + translate: function (x, y) { + var translation = new SVG.Matrix(this) + translation.e += x || 0 + translation.f += y || 0 + return translation + }, + + // Scale matrix + scale: function (x, y, cx, cy) { + // Support uniform scaling + if (arguments.length === 1) { + y = x + } else if (arguments.length === 3) { + cy = cx + cx = y + y = x + } + + // Scale the current matrix + var scale = new SVG.Matrix(x, 0, 0, y, 0, 0) + var matrix = this.around(cx, cy, scale) + return matrix + }, + + // Rotate matrix + rotate: function (r, cx, cy) { + // Convert degrees to radians + r = SVG.utils.radians(r) + + // Construct the rotation matrix + var rotation = new SVG.Matrix(Math.cos(r), Math.sin(r), -Math.sin(r), Math.cos(r), 0, 0) + var matrix = this.around(cx, cy, rotation) + return matrix + }, + + // Flip matrix on x or y, at a given offset + flip: function (axis, around) { + return axis === 'x' ? this.scale(-1, 1, around, 0) + : axis === 'y' ? this.scale(1, -1, 0, around) + : this.scale(-1, -1, axis, around || axis) // Define an x, y flip point + }, + + // Shear matrix + shear: function (a, cx, cy) { + var shear = new SVG.Matrix(1, 0, a, 1, 0, 0) + var matrix = this.around(cx, cy, shear) + return matrix + }, + + // Skew Matrix + skew: function (x, y, cx, cy) { + // support uniformal skew + if (arguments.length === 1) { + y = x + } else if (arguments.length === 3) { + cy = cx + cx = y + y = x + } + + // Convert degrees to radians + x = SVG.utils.radians(x) + y = SVG.utils.radians(y) + + // Construct the matrix + var skew = new SVG.Matrix(1, Math.tan(y), Math.tan(x), 1, 0, 0) + var matrix = this.around(cx, cy, skew) + return matrix + }, + + // SkewX + skewX: function (x, cx, cy) { + return this.skew(x, 0, cx, cy) + }, + + // SkewY + skewY: function (y, cx, cy) { + return this.skew(0, y, cx, cy) + }, + + // Transform around a center point + around: function (cx, cy, matrix) { + var dx = cx || 0 + var dy = cy || 0 + return this.translate(-dx, -dy).lmultiply(matrix).translate(dx, dy) + }, + + // Convert to native SVGMatrix + native: function () { + // create new matrix + var matrix = SVG.parser.nodes.svg.node.createSVGMatrix() + + // update with current values + for (var i = abcdef.length - 1; i >= 0; i--) { + matrix[abcdef[i]] = this[abcdef[i]] + } + + return matrix + }, + + // Check if two matrices are equal + equals: function (other) { + var comp = new SVG.Matrix(other) + return closeEnough(this.a, comp.a) && closeEnough(this.b, comp.b) && + closeEnough(this.c, comp.c) && closeEnough(this.d, comp.d) && + closeEnough(this.e, comp.e) && closeEnough(this.f, comp.f) + }, + + // Convert matrix to string + toString: function () { + return 'matrix(' + this.a + ',' + this.b + ',' + this.c + ',' + this.d + ',' + this.e + ',' + this.f + ')' + }, + + toArray: function () { + return [this.a, this.b, this.c, this.d, this.e, this.f] + }, + + fromArray: function (a) { + return new SVG.Matrix(a) + } + }, + + // Define parent + parent: SVG.Element, + + // Add parent method + construct: { + // Get current matrix + ctm: function () { + return new SVG.Matrix(this.node.getCTM()) + }, + // Get current screen matrix + screenCTM: function () { + /* https://bugzilla.mozilla.org/show_bug.cgi?id=1344537 + This is needed because FF does not return the transformation matrix + for the inner coordinate system when getScreenCTM() is called on nested svgs. + However all other Browsers do that */ + if (this instanceof SVG.Doc && !this.isRoot()) { + var rect = this.rect(1, 1) + var m = rect.node.getScreenCTM() + rect.remove() + return new SVG.Matrix(m) + } + return new SVG.Matrix(this.node.getScreenCTM()) + } + } +}) + + +SVG.Point = SVG.invent({ + // Initialize + create: function (x, y, base) { + var source + base = base || {x: 0, y: 0} + + // ensure source as object + source = Array.isArray(x) ? {x: x[0], y: x[1]} + : typeof x === 'object' ? {x: x.x, y: x.y} + : {x: x, y: y} + + // merge source + this.x = source.x == null ? base.x : source.x + this.y = source.y == null ? base.y : source.y + }, + + // Add methods + extend: { + // Clone point + clone: function () { + return new SVG.Point(this) + }, + + // Morph one point into another + morph: function (x, y) { + // store new destination + this.destination = new SVG.Point(x, y) + return this + }, + + // Get morphed point at a given position + at: function (pos) { + // make sure a destination is defined + if (!this.destination) return this + + // calculate morphed matrix at a given position + var point = new SVG.Point({ + x: this.x + (this.destination.x - this.x) * pos, + y: this.y + (this.destination.y - this.y) * pos + }) + return point + }, + + // Convert to native SVGPoint + native: function () { + // create new point + var point = SVG.parser.nodes.svg.node.createSVGPoint() + + // update with current values + point.x = this.x + point.y = this.y + return point + }, + + // transform point with matrix + transform: function (matrix) { + return new SVG.Point(this.native().matrixTransform(matrix.native())) + } + } +}) + +SVG.extend(SVG.Element, { + + // Get point + point: function (x, y) { + return new SVG.Point(x, y).transform(this.screenCTM().inverse()) + } +}) + +SVG.extend(SVG.Element, { + // Set svg element attribute + attr: function (a, v, n) { + // act as full getter + if (a == null) { + // get an object of attributes + a = {} + v = this.node.attributes + for (n = v.length - 1; n >= 0; n--) { + a[v[n].nodeName] = SVG.regex.isNumber.test(v[n].nodeValue) + ? parseFloat(v[n].nodeValue) + : v[n].nodeValue + } + return a + } else if (typeof a === 'object') { + // apply every attribute individually if an object is passed + for (v in a) this.attr(v, a[v]) + } else if (v === null) { + // remove value + this.node.removeAttribute(a) + } else if (v == null) { + // act as a getter if the first and only argument is not an object + v = this.node.getAttribute(a) + return v == null ? SVG.defaults.attrs[a] + : SVG.regex.isNumber.test(v) ? parseFloat(v) + : v + } else { + // convert image fill and stroke to patterns + if (a === 'fill' || a === 'stroke') { + if (SVG.regex.isImage.test(v)) { + v = this.doc().defs().image(v) + } + + if (v instanceof SVG.Image) { + v = this.doc().defs().pattern(0, 0, function () { + this.add(v) + }) + } + } + + // ensure correct numeric values (also accepts NaN and Infinity) + if (typeof v === 'number') { + v = new SVG.Number(v) + } else if (SVG.Color.isColor(v)) { + // ensure full hex color + v = new SVG.Color(v) + } else if (Array.isArray(v)) { + // parse array values + v = new SVG.Array(v) + } + + // if the passed attribute is leading... + if (a === 'leading') { + // ... call the leading method instead + if (this.leading) { + this.leading(v) + } + } else { + // set given attribute on node + typeof n === 'string' ? this.node.setAttributeNS(n, a, v.toString()) + : this.node.setAttribute(a, v.toString()) + } + + // rebuild if required + if (this.rebuild && (a === 'font-size' || a === 'x')) { + this.rebuild(a, v) + } + } + + return this + } +}) + +/* global arrayToMatrix */ + +SVG.extend(SVG.Element, { + // Reset all transformations + untransform: function () { + return this.attr('transform', null) + }, + + // merge the whole transformation chain into one matrix and returns it + matrixify: function () { + var matrix = (this.attr('transform') || '') + // split transformations + .split(SVG.regex.transforms).slice(0, -1).map(function (str) { + // generate key => value pairs + var kv = str.trim().split('(') + return [kv[0], + kv[1].split(SVG.regex.delimiter) + .map(function (str) { return parseFloat(str) }) + ] + }) + .reverse() + // merge every transformation into one matrix + .reduce(function (matrix, transform) { + if (transform[0] === 'matrix') { + return matrix.lmultiply(arrayToMatrix(transform[1])) + } + return matrix[transform[0]].apply(matrix, transform[1]) + }, new SVG.Matrix()) + + return matrix + }, + + // add an element to another parent without changing the visual representation on the screen + toParent: function (parent) { + if (this === parent) return this + var ctm = this.screenCTM() + var pCtm = parent.screenCTM().inverse() + + this.addTo(parent).untransform().transform(pCtm.multiply(ctm)) + + return this + }, + + // same as above with parent equals root-svg + toDoc: function () { + return this.toParent(this.doc()) + } +}) + +SVG.extend(SVG.Element, { + + // Add transformations + transform: function (o, relative) { + + // Act as a getter if no object was passed + if (o == null || typeof o === 'string') { + var decomposed = new SVG.Matrix(this).decompose() + return decomposed[o] || decomposed + + // Allow the user to define the origin with a string + } else if (typeof o.origin === 'string' || + (o.origin == null && o.ox == null && o.oy == null) + ) { + + // Get the bounding box of the element with no transformations applied + var bbox = this.bbox() + + // Get the bounding box and string to use in our calculations + var string = typeof o.origin === 'string' + ? o.origin.toLowerCase().trim() + : 'center' // We want the center by default + var height = bbox.height + var width = bbox.width + var x = bbox.x + var y = bbox.y + + // Set the bounds eg : "bottom-left", "Top right", "middle" etc... + o.ox = string.includes('left') ? x + : string.includes('right') ? x + width + : x + width / 2 + o.oy = string.includes('top') ? y + : string.includes('bottom') ? y + height + : y + height / 2 + + // Make sure we only pass ox and oy + o.origin = null + } + + // The user can pass a boolean, an SVG.Element or an SVG.Matrix or nothing + var cleanRelative = relative === true ? this : (relative || false) + var result = new SVG.Matrix(cleanRelative).transform(o) + return this.attr('transform', result) + } +}) + +SVG.extend(SVG.Timeline, { + transform: function (o, relative, affine) { + + // // get target in case of the fx module, otherwise reference this + // var target = this.target() + // , matrix, bbox + // + // // act as a getter + // if (typeof o !== 'object') { + // // get current matrix + // matrix = new SVG.Matrix(target).extract() + // + // return typeof o === 'string' ? matrix[o] : matrix + // } + // + // // ensure relative flag + // relative = !!relative || !!o.relative + // + // // act on matrix + // if (o.a != null) { + // matrix = new SVG.Matrix(o) + // + // // act on rotation + // } else if (o.rotation != null) { + // // ensure centre point + // ensureCentre(o, target) + // + // // apply transformation + // matrix = new SVG.Rotate(o.rotation, o.cx, o.cy) + // + // // act on scale + // } else if (o.scale != null || o.scaleX != null || o.scaleY != null) { + // // ensure centre point + // ensureCentre(o, target) + // + // // ensure scale values on both axes + // o.scaleX = o.scale != null ? o.scale : o.scaleX != null ? o.scaleX : 1 + // o.scaleY = o.scale != null ? o.scale : o.scaleY != null ? o.scaleY : 1 + // + // matrix = new SVG.Scale(o.scaleX, o.scaleY, o.cx, o.cy) + // + // // act on skew + // } else if (o.skewX != null || o.skewY != null) { + // // ensure centre point + // ensureCentre(o, target) + // + // // ensure skew values on both axes + // o.skewX = o.skewX != null ? o.skewX : 0 + // o.skewY = o.skewY != null ? o.skewY : 0 + // + // matrix = new SVG.Skew(o.skewX, o.skewY, o.cx, o.cy) + // + // // act on flip + // } else if (o.flip) { + // if(o.flip == 'x' || o.flip == 'y') { + // o.offset = o.offset == null ? target.bbox()['c' + o.flip] : o.offset + // } else { + // if(o.offset == null) { + // bbox = target.bbox() + // o.flip = bbox.cx + // o.offset = bbox.cy + // } else { + // o.flip = o.offset + // } + // } + // + // matrix = new SVG.Matrix().flip(o.flip, o.offset) + // + // // act on translate + // } else if (o.x != null || o.y != null) { + // matrix = new SVG.Translate(o.x, o.y) + // } + // + // if(!matrix) return this + // + // matrix.relative = relative + // + // this.last().transforms.push(matrix) + // + // return this._callStart() + // } + // // ensure scale values on both axes + // o.scaleX = o.scale != null ? o.scale : o.scaleX != null ? o.scaleX : 1 + // o.scaleY = o.scale != null ? o.scale : o.scaleY != null ? o.scaleY : 1 + // + // matrix = new SVG.Scale(o.scaleX, o.scaleY, o.cx, o.cy) + // + // // act on skew + // } else if (o.skewX != null || o.skewY != null) { + // // ensure centre point + // ensureCentre(o, target) + // + // // ensure skew values on both axes + // o.skewX = o.skewX != null ? o.skewX : 0 + // o.skewY = o.skewY != null ? o.skewY : 0 + // + // matrix = new SVG.Skew(o.skewX, o.skewY, o.cx, o.cy) + // + // // act on flip + // } else if (o.flip) { + // if (o.flip === 'x' || o.flip === 'y') { + // o.offset = o.offset == null ? target.bbox()['c' + o.flip] : o.offset + // } else { + // if (o.offset == null) { + // bbox = target.bbox() + // o.flip = bbox.cx + // o.offset = bbox.cy + // } else { + // o.flip = o.offset + // } + // } + // + // matrix = new SVG.Matrix().flip(o.flip, o.offset) + // + // // act on translate + // } else if (o.x != null || o.y != null) { + // matrix = new SVG.Translate(o.x, o.y) + // } + // + // if (!matrix) return this + // + // matrix.relative = relative + // + // this.last().transforms.push(matrix) + // + // return this._callStart() + } +}) + +/* global camelCase */ + +SVG.extend(SVG.Element, { + // Dynamic style generator + css: function (s, v) { + var ret = {} + var t, i + if (arguments.length === 0) { + // get full style as object + this.node.style.cssText.split(/\s*;\s*/).filter(function (el) { return !!el.length }).forEach(function (el) { + t = el.split(/\s*:\s*/) + ret[t[0]] = t[1] + }) + return ret + } + + if (arguments.length < 2) { + // get style properties in the array + if (Array.isArray(s)) { + for (i = s.length; i--;) { + ret[camelCase(s[i])] = this.node.style[camelCase(s[i])] + } + return ret + } + + // get style for property + if (typeof s === 'string') { + return this.node.style[camelCase(s)] + } + + // set styles in object + if (typeof s === 'object') { + for (i in s) { + // set empty string if null/undefined/'' was given + this.node.style[camelCase(i)] = (s[i] == null || SVG.regex.isBlank.test(s[i])) ? '' : s[i] + } + } + } + + // set style for property + if (arguments.length === 2) { + this.node.style[camelCase(s)] = (v == null || SVG.regex.isBlank.test(v)) ? '' : v + } + + return this + } +}) + +/* global createElement */ + +SVG.Parent = SVG.invent({ + // Initialize node + create: function (node) { + SVG.Element.call(this, node) + }, + + // Inherit from + inherit: SVG.Element, + + // Add class methods + extend: { + // Returns all child elements + children: function () { + return SVG.utils.map(this.node.children, function (node) { + return SVG.adopt(node) + }) + }, + // Add given element at a position + add: function (element, i) { + element = createElement(element) + + if (element.node !== this.node.children[i]) { + this.node.insertBefore(element.node, this.node.children[i] || null) + } + + return this + }, + // Basically does the same as `add()` but returns the added element instead + put: function (element, i) { + this.add(element, i) + return element.instance || element + }, + // Checks if the given element is a child + has: function (element) { + return this.index(element) >= 0 + }, + // Gets index of given element + index: function (element) { + return [].slice.call(this.node.children).indexOf(element.node) + }, + // Get a element at the given index + get: function (i) { + return SVG.adopt(this.node.children[i]) + }, + // Get first child + first: function () { + return this.get(0) + }, + // Get the last child + last: function () { + return this.get(this.node.children.length - 1) + }, + // Iterates over all children and invokes a given block + each: function (block, deep) { + var children = this.children() + var i, il + + for (i = 0, il = children.length; i < il; i++) { + if (children[i] instanceof SVG.Element) { + block.apply(children[i], [i, children]) + } + + if (deep && (children[i] instanceof SVG.Parent)) { + children[i].each(block, deep) + } + } + + return this + }, + // Remove a given child + removeElement: function (element) { + this.node.removeChild(element.node) + + return this + }, + // Remove all elements in this container + clear: function () { + // remove children + while (this.node.hasChildNodes()) { + this.node.removeChild(this.node.lastChild) + } + + // remove defs reference + delete this._defs + + return this + } + } + +}) + +SVG.extend(SVG.Parent, { + flatten: function (parent) { + // flattens is only possible for nested svgs and groups + if (!(this instanceof SVG.G || this instanceof SVG.Doc)) { + return this + } + + parent = parent || (this instanceof SVG.Doc && this.isRoot() ? this : this.parent(SVG.Parent)) + + this.each(function () { + if (this instanceof SVG.Defs) return this + if (this instanceof SVG.Parent) return this.flatten(parent) + return this.toParent(parent) + }) + + // we need this so that SVG.Doc does not get removed + this.node.firstElementChild || this.remove() + + return this + }, + ungroup: function (parent) { + // ungroup is only possible for nested svgs and groups + if (!(this instanceof SVG.G || (this instanceof SVG.Doc && !this.isRoot()))) { + return this + } + + parent = parent || this.parent(SVG.Parent) + + this.each(function () { + return this.toParent(parent) + }) + + // we need this so that SVG.Doc does not get removed + this.remove() + + return this + } +}) + +SVG.Container = SVG.invent({ + // Initialize node + create: function (node) { + SVG.Element.call(this, node) + }, + + // Inherit from + inherit: SVG.Parent +}) + +// Add events to elements + +;[ 'click', + 'dblclick', + 'mousedown', + 'mouseup', + 'mouseover', + 'mouseout', + 'mousemove', + 'mouseenter', + 'mouseleave', + 'touchstart', + 'touchmove', + 'touchleave', + 'touchend', + 'touchcancel' ].forEach(function (event) { + // add event to SVG.Element + SVG.Element.prototype[event] = function (f) { + // bind event to element rather than element node + SVG.on(this, event, f) + return this + } + }) + +SVG.listenerId = 0 + +// Add event binder in the SVG namespace +SVG.on = function (node, events, listener, binding, options) { + var l = listener.bind(binding || node) + var n = node instanceof SVG.Element ? node.node : node + + // ensure instance object for nodes which are not adopted + n.instance = n.instance || {events: {}} + + var bag = n.instance.events + + // add id to listener + if (!listener._svgjsListenerId) { listener._svgjsListenerId = ++SVG.listenerId } + + events.split(SVG.regex.delimiter).forEach(function (event) { + var ev = event.split('.')[0] + var ns = event.split('.')[1] || '*' + + // ensure valid object + bag[ev] = bag[ev] || {} + bag[ev][ns] = bag[ev][ns] || {} + + // reference listener + bag[ev][ns][listener._svgjsListenerId] = l + + // add listener + n.addEventListener(ev, l, options || false) + }) +} + +// Add event unbinder in the SVG namespace +SVG.off = function (node, events, listener, options) { + var n = node instanceof SVG.Element ? node.node : node + if (!n.instance) return + + // listener can be a function or a number + if (typeof listener === 'function') { + listener = listener._svgjsListenerId + if (!listener) return + } + + var bag = n.instance.events + + ;(events || '').split(SVG.regex.delimiter).forEach(function (event) { + var ev = event && event.split('.')[0] + var ns = event && event.split('.')[1] + var namespace, l + + if (listener) { + // remove listener reference + if (bag[ev] && bag[ev][ns || '*']) { + // removeListener + n.removeEventListener(ev, bag[ev][ns || '*'][listener], options || false) + + delete bag[ev][ns || '*'][listener] + } + } else if (ev && ns) { + // remove all listeners for a namespaced event + if (bag[ev] && bag[ev][ns]) { + for (l in bag[ev][ns]) { SVG.off(n, [ev, ns].join('.'), l) } + + delete bag[ev][ns] + } + } else if (ns) { + // remove all listeners for a specific namespace + for (event in bag) { + for (namespace in bag[event]) { + if (ns === namespace) { SVG.off(n, [event, ns].join('.')) } + } + } + } else if (ev) { + // remove all listeners for the event + if (bag[ev]) { + for (namespace in bag[ev]) { SVG.off(n, [ev, namespace].join('.')) } + + delete bag[ev] + } + } else { + // remove all listeners on a given node + for (event in bag) { SVG.off(n, event) } + + n.instance.events = {} + } + }) +} + +SVG.extend(SVG.Element, { + // Bind given event to listener + on: function (event, listener, binding, options) { + SVG.on(this, event, listener, binding, options) + return this + }, + // Unbind event from listener + off: function (event, listener) { + SVG.off(this.node, event, listener) + return this + }, + dispatch: function (event, data) { + // Dispatch event + if (event instanceof window.Event) { + this.node.dispatchEvent(event) + } else { + this.node.dispatchEvent(event = new window.CustomEvent(event, {detail: data, cancelable: true})) + } + return event + }, + // Fire given event + fire: function (event, data) { + this.dispatch(event, data) + return this + } +}) + +SVG.Defs = SVG.invent({ + // Initialize node + create: 'defs', + + // Inherit from + inherit: SVG.Container +}) + +SVG.G = SVG.invent({ + // Initialize node + create: 'g', + + // Inherit from + inherit: SVG.Container, + + // Add class methods + extend: { + }, + + // Add parent method + construct: { + // Create a group element + group: function () { + return this.put(new SVG.G()) + } + } +}) + +// ### This module adds backward / forward functionality to elements. + +// +SVG.extend(SVG.Element, { + // Get all siblings, including myself + siblings: function () { + return this.parent().children() + }, + + // Get the curent position siblings + position: function () { + return this.parent().index(this) + }, + + // Get the next element (will return null if there is none) + next: function () { + return this.siblings()[this.position() + 1] + }, + + // Get the next element (will return null if there is none) + prev: function () { + return this.siblings()[this.position() - 1] + }, + + // Send given element one step forward + forward: function () { + var i = this.position() + 1 + var p = this.parent() + + // move node one step forward + p.removeElement(this).add(this, i) + + // make sure defs node is always at the top + if (p instanceof SVG.Doc) { + p.node.appendChild(p.defs().node) + } + + return this + }, + + // Send given element one step backward + backward: function () { + var i = this.position() + + if (i > 0) { + this.parent().removeElement(this).add(this, i - 1) + } + + return this + }, + + // Send given element all the way to the front + front: function () { + var p = this.parent() + + // Move node forward + p.node.appendChild(this.node) + + // Make sure defs node is always at the top + if (p instanceof SVG.Doc) { + p.node.appendChild(p.defs().node) + } + + return this + }, + + // Send given element all the way to the back + back: function () { + if (this.position() > 0) { + this.parent().removeElement(this).add(this, 0) + } + + return this + }, + + // Inserts a given element before the targeted element + before: function (element) { + element.remove() + + var i = this.position() + + this.parent().add(element, i) + + return this + }, + + // Insters a given element after the targeted element + after: function (element) { + element.remove() + + var i = this.position() + + this.parent().add(element, i + 1) + + return this + } +}) + +SVG.Mask = SVG.invent({ + // Initialize node + create: 'mask', + + // Inherit from + inherit: SVG.Container, + + // Add class methods + extend: { + // Unmask all masked elements and remove itself + remove: function () { + // unmask all targets + this.targets().forEach(function (el) { + el.unmask() + }) + + // remove mask from parent + return SVG.Element.prototype.remove.call(this) + }, + + targets: function () { + return SVG.select('svg [mask*="' + this.id() + '"]') + } + }, + + // Add parent method + construct: { + // Create masking element + mask: function () { + return this.defs().put(new SVG.Mask()) + } + } +}) + +SVG.extend(SVG.Element, { + // Distribute mask to svg element + maskWith: function (element) { + // use given mask or create a new one + var masker = element instanceof SVG.Mask ? element : this.parent().mask().add(element) + + // apply mask + return this.attr('mask', 'url("#' + masker.id() + '")') + }, + // Unmask element + unmask: function () { + return this.attr('mask', null) + }, + masker: function () { + return this.reference('mask') + } +}) + +SVG.ClipPath = SVG.invent({ + // Initialize node + create: 'clipPath', + + // Inherit from + inherit: SVG.Container, + + // Add class methods + extend: { + // Unclip all clipped elements and remove itself + remove: function () { + // unclip all targets + this.targets().forEach(function (el) { + el.unclip() + }) + + // remove clipPath from parent + return SVG.Element.prototype.remove.call(this) + }, + + targets: function () { + return SVG.select('svg [clip-path*="' + this.id() + '"]') + } + }, + + // Add parent method + construct: { + // Create clipping element + clip: function () { + return this.defs().put(new SVG.ClipPath()) + } + } +}) + +// +SVG.extend(SVG.Element, { + // Distribute clipPath to svg element + clipWith: function (element) { + // use given clip or create a new one + var clipper = element instanceof SVG.ClipPath ? element : this.parent().clip().add(element) + + // apply mask + return this.attr('clip-path', 'url("#' + clipper.id() + '")') + }, + // Unclip element + unclip: function () { + return this.attr('clip-path', null) + }, + clipper: function () { + return this.reference('clip-path') + } + +}) + +SVG.Gradient = SVG.invent({ + // Initialize node + create: function (type) { + SVG.Element.call(this, typeof type === 'object' ? type : SVG.create(type + 'Gradient')) + }, + + // Inherit from + inherit: SVG.Container, + + // Add class methods + extend: { + // Add a color stop + stop: function (offset, color, opacity) { + return this.put(new SVG.Stop()).update(offset, color, opacity) + }, + // Update gradient + update: function (block) { + // remove all stops + this.clear() + + // invoke passed block + if (typeof block === 'function') { + block.call(this, this) + } + + return this + }, + // Return the fill id + url: function () { + return 'url(#' + this.id() + ')' + }, + // Alias string convertion to fill + toString: function () { + return this.url() + }, + // custom attr to handle transform + attr: function (a, b, c) { + if (a === 'transform') a = 'gradientTransform' + return SVG.Container.prototype.attr.call(this, a, b, c) + } + }, + + // Add parent method + construct: { + // Create gradient element in defs + gradient: function (type, block) { + return this.defs().gradient(type, block) + } + } +}) + +// Add animatable methods to both gradient and fx module +SVG.extend([SVG.Gradient, SVG.Timeline], { + // From position + from: function (x, y) { + return (this._target || this).type === 'radialGradient' + ? this.attr({ fx: new SVG.Number(x), fy: new SVG.Number(y) }) + : this.attr({ x1: new SVG.Number(x), y1: new SVG.Number(y) }) + }, + // To position + to: function (x, y) { + return (this._target || this).type === 'radialGradient' + ? this.attr({ cx: new SVG.Number(x), cy: new SVG.Number(y) }) + : this.attr({ x2: new SVG.Number(x), y2: new SVG.Number(y) }) + } +}) + +// Base gradient generation +SVG.extend(SVG.Defs, { + // define gradient + gradient: function (type, block) { + return this.put(new SVG.Gradient(type)).update(block) + } + +}) + +SVG.Stop = SVG.invent({ + // Initialize node + create: 'stop', + + // Inherit from + inherit: SVG.Element, + + // Add class methods + extend: { + // add color stops + update: function (o) { + if (typeof o === 'number' || o instanceof SVG.Number) { + o = { + offset: arguments[0], + color: arguments[1], + opacity: arguments[2] + } + } + + // set attributes + if (o.opacity != null) this.attr('stop-opacity', o.opacity) + if (o.color != null) this.attr('stop-color', o.color) + if (o.offset != null) this.attr('offset', new SVG.Number(o.offset)) + + return this + } + } +}) + +SVG.Pattern = SVG.invent({ + // Initialize node + create: 'pattern', + + // Inherit from + inherit: SVG.Container, + + // Add class methods + extend: { + // Return the fill id + url: function () { + return 'url(#' + this.id() + ')' + }, + // Update pattern by rebuilding + update: function (block) { + // remove content + this.clear() + + // invoke passed block + if (typeof block === 'function') { + block.call(this, this) + } + + return this + }, + // Alias string convertion to fill + toString: function () { + return this.url() + }, + // custom attr to handle transform + attr: function (a, b, c) { + if (a === 'transform') a = 'patternTransform' + return SVG.Container.prototype.attr.call(this, a, b, c) + } + + }, + + // Add parent method + construct: { + // Create pattern element in defs + pattern: function (width, height, block) { + return this.defs().pattern(width, height, block) + } + } +}) + +SVG.extend(SVG.Defs, { + // Define gradient + pattern: function (width, height, block) { + return this.put(new SVG.Pattern()).update(block).attr({ + x: 0, + y: 0, + width: width, + height: height, + patternUnits: 'userSpaceOnUse' + }) + } + +}) + +SVG.Doc = SVG.invent({ + // Initialize node + create: function (node) { + SVG.Element.call(this, node || SVG.create('svg')) + + // set svg element attributes and ensure defs node + this.namespace() + }, + + // Inherit from + inherit: SVG.Container, + + // Add class methods + extend: { + isRoot: function () { + return !this.node.parentNode || !(this.node.parentNode instanceof window.SVGElement) || this.node.parentNode.nodeName === '#document' + }, + // Check if this is a root svg. If not, call docs from this element + doc: function () { + if (this.isRoot()) return this + return SVG.Element.prototype.doc.call(this) + }, + // Add namespaces + namespace: function () { + if (!this.isRoot()) return this.doc().namespace() + return this + .attr({ xmlns: SVG.ns, version: '1.1' }) + .attr('xmlns:xlink', SVG.xlink, SVG.xmlns) + .attr('xmlns:svgjs', SVG.svgjs, SVG.xmlns) + }, + // Creates and returns defs element + defs: function () { + if (!this.isRoot()) return this.doc().defs() + return SVG.adopt(this.node.getElementsByTagName('defs')[0]) || this.put(new SVG.Defs()) + }, + // custom parent method + parent: function (type) { + if (this.isRoot()) { + return this.node.parentNode.nodeName === '#document' ? null : this.node.parentNode + } + + return SVG.Element.prototype.parent.call(this, type) + }, + // Removes the doc from the DOM + remove: function () { + if (!this.isRoot()) { + return SVG.Element.prototype.remove.call(this) + } + + if (this.parent()) { + this.parent().removeChild(this.node) + } + + return this + }, + clear: function () { + // remove children + while (this.node.hasChildNodes()) { + this.node.removeChild(this.node.lastChild) + } + return this + } + }, + construct: { + // Create nested svg document + nested: function () { + return this.put(new SVG.Doc()) + } + } +}) + + +SVG.Shape = SVG.invent({ + // Initialize node + create: function (node) { + SVG.Element.call(this, node) + }, + + // Inherit from + inherit: SVG.Element +}) + + +SVG.Bare = SVG.invent({ + // Initialize + create: function (element, inherit) { + // construct element + SVG.Element.call(this, SVG.create(element)) + + // inherit custom methods + if (inherit) { + for (var method in inherit.prototype) { + if (typeof inherit.prototype[method] === 'function') { + this[method] = inherit.prototype[method] + } + } + } + }, + + // Inherit from + inherit: SVG.Element, + + // Add methods + extend: { + // Insert some plain text + words: function (text) { + // remove contents + while (this.node.hasChildNodes()) { + this.node.removeChild(this.node.lastChild) + } + + // create text node + this.node.appendChild(document.createTextNode(text)) + + return this + } + } +}) + +SVG.extend(SVG.Parent, { + // Create an element that is not described by SVG.js + element: function (element, inherit) { + return this.put(new SVG.Bare(element, inherit)) + } +}) + + +SVG.Symbol = SVG.invent({ + // Initialize node + create: 'symbol', + + // Inherit from + inherit: SVG.Container, + + construct: { + // create symbol + symbol: function () { + return this.put(new SVG.Symbol()) + } + } +}) + + +SVG.Use = SVG.invent({ + // Initialize node + create: 'use', + + // Inherit from + inherit: SVG.Shape, + + // Add class methods + extend: { + // Use element as a reference + element: function (element, file) { + // Set lined element + return this.attr('href', (file || '') + '#' + element, SVG.xlink) + } + }, + + // Add parent method + construct: { + // Create a use element + use: function (element, file) { + return this.put(new SVG.Use()).element(element, file) + } + } +}) + + +SVG.Rect = SVG.invent({ + // Initialize node + create: 'rect', + + // Inherit from + inherit: SVG.Shape, + + // Add parent method + construct: { + // Create a rect element + rect: function (width, height) { + return this.put(new SVG.Rect()).size(width, height) + } + } +}) + +/* global proportionalSize */ + +SVG.Circle = SVG.invent({ + // Initialize node + create: 'circle', + + // Inherit from + inherit: SVG.Shape, + + // Add parent method + construct: { + // Create circle element, based on ellipse + circle: function (size) { + return this.put(new SVG.Circle()).rx(new SVG.Number(size).divide(2)).move(0, 0) + } + } +}) + +SVG.extend([SVG.Circle, SVG.Timeline], { + // Radius x value + rx: function (rx) { + return this.attr('r', rx) + }, + // Alias radius x value + ry: function (ry) { + return this.rx(ry) + } +}) + +SVG.Ellipse = SVG.invent({ + // Initialize node + create: 'ellipse', + + // Inherit from + inherit: SVG.Shape, + + // Add parent method + construct: { + // Create an ellipse + ellipse: function (width, height) { + return this.put(new SVG.Ellipse()).size(width, height).move(0, 0) + } + } +}) + +SVG.extend([SVG.Ellipse, SVG.Rect, SVG.Timeline], { + // Radius x value + rx: function (rx) { + return this.attr('rx', rx) + }, + // Radius y value + ry: function (ry) { + return this.attr('ry', ry) + } +}) + +// Add common method +SVG.extend([SVG.Circle, SVG.Ellipse], { + // Move over x-axis + x: function (x) { + return x == null ? this.cx() - this.rx() : this.cx(x + this.rx()) + }, + // Move over y-axis + y: function (y) { + return y == null ? this.cy() - this.ry() : this.cy(y + this.ry()) + }, + // Move by center over x-axis + cx: function (x) { + return x == null ? this.attr('cx') : this.attr('cx', x) + }, + // Move by center over y-axis + cy: function (y) { + return y == null ? this.attr('cy') : this.attr('cy', y) + }, + // Set width of element + width: function (width) { + return width == null ? this.rx() * 2 : this.rx(new SVG.Number(width).divide(2)) + }, + // Set height of element + height: function (height) { + return height == null ? this.ry() * 2 : this.ry(new SVG.Number(height).divide(2)) + }, + // Custom size function + size: function (width, height) { + var p = proportionalSize(this, width, height) + + return this + .rx(new SVG.Number(p.width).divide(2)) + .ry(new SVG.Number(p.height).divide(2)) + } +}) + +/* global proportionalSize */ + +SVG.Line = SVG.invent({ + // Initialize node + create: 'line', + + // Inherit from + inherit: SVG.Shape, + + // Add class methods + extend: { + // Get array + array: function () { + return new SVG.PointArray([ + [ this.attr('x1'), this.attr('y1') ], + [ this.attr('x2'), this.attr('y2') ] + ]) + }, + + // Overwrite native plot() method + plot: function (x1, y1, x2, y2) { + if (x1 == null) { + return this.array() + } else if (typeof y1 !== 'undefined') { + x1 = { x1: x1, y1: y1, x2: x2, y2: y2 } + } else { + x1 = new SVG.PointArray(x1).toLine() + } + + return this.attr(x1) + }, + + // Move by left top corner + move: function (x, y) { + return this.attr(this.array().move(x, y).toLine()) + }, + + // Set element size to given width and height + size: function (width, height) { + var p = proportionalSize(this, width, height) + return this.attr(this.array().size(p.width, p.height).toLine()) + } + }, + + // Add parent method + construct: { + // Create a line element + line: function (x1, y1, x2, y2) { + // make sure plot is called as a setter + // x1 is not necessarily a number, it can also be an array, a string and a SVG.PointArray + return SVG.Line.prototype.plot.apply( + this.put(new SVG.Line()) + , x1 != null ? [x1, y1, x2, y2] : [0, 0, 0, 0] + ) + } + } +}) + +/* global proportionalSize */ + +SVG.Polyline = SVG.invent({ + // Initialize node + create: 'polyline', + + // Inherit from + inherit: SVG.Shape, + + // Add parent method + construct: { + // Create a wrapped polyline element + polyline: function (p) { + // make sure plot is called as a setter + return this.put(new SVG.Polyline()).plot(p || new SVG.PointArray()) + } + } +}) + +SVG.Polygon = SVG.invent({ + // Initialize node + create: 'polygon', + + // Inherit from + inherit: SVG.Shape, + + // Add parent method + construct: { + // Create a wrapped polygon element + polygon: function (p) { + // make sure plot is called as a setter + return this.put(new SVG.Polygon()).plot(p || new SVG.PointArray()) + } + } +}) + +// Add polygon-specific functions +SVG.extend([SVG.Polyline, SVG.Polygon], { + // Get array + array: function () { + return this._array || (this._array = new SVG.PointArray(this.attr('points'))) + }, + + // Plot new path + plot: function (p) { + return (p == null) ? this.array() + : this.clear().attr('points', typeof p === 'string' ? p + : (this._array = new SVG.PointArray(p))) + }, + + // Clear array cache + clear: function () { + delete this._array + return this + }, + + // Move by left top corner + move: function (x, y) { + return this.attr('points', this.array().move(x, y)) + }, + + // Set element size to given width and height + size: function (width, height) { + var p = proportionalSize(this, width, height) + return this.attr('points', this.array().size(p.width, p.height)) + } +}) + +// unify all point to point elements +SVG.extend([SVG.Line, SVG.Polyline, SVG.Polygon], { + // Define morphable array + MorphArray: SVG.PointArray, + // Move by left top corner over x-axis + x: function (x) { + return x == null ? this.bbox().x : this.move(x, this.bbox().y) + }, + // Move by left top corner over y-axis + y: function (y) { + return y == null ? this.bbox().y : this.move(this.bbox().x, y) + }, + // Set width of element + width: function (width) { + var b = this.bbox() + + return width == null ? b.width : this.size(width, b.height) + }, + // Set height of element + height: function (height) { + var b = this.bbox() + + return height == null ? b.height : this.size(b.width, height) + } +}) + +/* global proportionalSize */ + +SVG.Path = SVG.invent({ + // Initialize node + create: 'path', + + // Inherit from + inherit: SVG.Shape, + + // Add class methods + extend: { + // Define morphable array + MorphArray: SVG.PathArray, + // Get array + array: function () { + return this._array || (this._array = new SVG.PathArray(this.attr('d'))) + }, + // Plot new path + plot: function (d) { + return (d == null) ? this.array() + : this.clear().attr('d', typeof d === 'string' ? d : (this._array = new SVG.PathArray(d))) + }, + // Clear array cache + clear: function () { + delete this._array + return this + }, + // Move by left top corner + move: function (x, y) { + return this.attr('d', this.array().move(x, y)) + }, + // Move by left top corner over x-axis + x: function (x) { + return x == null ? this.bbox().x : this.move(x, this.bbox().y) + }, + // Move by left top corner over y-axis + y: function (y) { + return y == null ? this.bbox().y : this.move(this.bbox().x, y) + }, + // Set element size to given width and height + size: function (width, height) { + var p = proportionalSize(this, width, height) + return this.attr('d', this.array().size(p.width, p.height)) + }, + // Set width of element + width: function (width) { + return width == null ? this.bbox().width : this.size(width, this.bbox().height) + }, + // Set height of element + height: function (height) { + return height == null ? this.bbox().height : this.size(this.bbox().width, height) + } + }, + + // Add parent method + construct: { + // Create a wrapped path element + path: function (d) { + // make sure plot is called as a setter + return this.put(new SVG.Path()).plot(d || new SVG.PathArray()) + } + } +}) + +SVG.Image = SVG.invent({ + // Initialize node + create: 'image', + + // Inherit from + inherit: SVG.Shape, + + // Add class methods + extend: { + // (re)load image + load: function (url, callback) { + if (!url) return this + + var img = new window.Image() + + SVG.on(img, 'load', function (e) { + var p = this.parent(SVG.Pattern) + + // ensure image size + if (this.width() === 0 && this.height() === 0) { + this.size(img.width, img.height) + } + + if (p instanceof SVG.Pattern) { + // ensure pattern size if not set + if (p.width() === 0 && p.height() === 0) { + p.size(this.width(), this.height()) + } + } + + if (typeof callback === 'function') { + callback.call(this, { + width: img.width, + height: img.height, + ratio: img.width / img.height, + url: url + }) + } + }, this) + + SVG.on(img, 'load error', function () { + // dont forget to unbind memory leaking events + SVG.off(img) + }) + + return this.attr('href', (img.src = url), SVG.xlink) + } + }, + + // Add parent method + construct: { + // create image element, load image and set its size + image: function (source, callback) { + return this.put(new SVG.Image()).size(0, 0).load(source, callback) + } + } +}) + +SVG.Text = SVG.invent({ + // Initialize node + create: function (node) { + SVG.Element.call(this, node || SVG.create('text')) + this.dom.leading = new SVG.Number(1.3) // store leading value for rebuilding + this._rebuild = true // enable automatic updating of dy values + this._build = false // disable build mode for adding multiple lines + + // set default font + this.attr('font-family', SVG.defaults.attrs['font-family']) + }, + + // Inherit from + inherit: SVG.Parent, + + // Add class methods + extend: { + // Move over x-axis + x: function (x) { + // act as getter + if (x == null) { + return this.attr('x') + } + + return this.attr('x', x) + }, + // Move over y-axis + y: function (y) { + var oy = this.attr('y') + var o = typeof oy === 'number' ? oy - this.bbox().y : 0 + + // act as getter + if (y == null) { + return typeof oy === 'number' ? oy - o : oy + } + + return this.attr('y', typeof y === 'number' ? y + o : y) + }, + // Move center over x-axis + cx: function (x) { + return x == null ? this.bbox().cx : this.x(x - this.bbox().width / 2) + }, + // Move center over y-axis + cy: function (y) { + return y == null ? this.bbox().cy : this.y(y - this.bbox().height / 2) + }, + // Set the text content + text: function (text) { + // act as getter + if (text === undefined) { + var children = this.node.childNodes + var firstLine = 0 + text = '' + + for (var i = 0, len = children.length; i < len; ++i) { + // skip textPaths - they are no lines + if (children[i].nodeName === 'textPath') { + if (i === 0) firstLine = 1 + continue + } + + // add newline if its not the first child and newLined is set to true + if (i !== firstLine && children[i].nodeType !== 3 && SVG.adopt(children[i]).dom.newLined === true) { + text += '\n' + } + + // add content of this node + text += children[i].textContent + } + + return text + } + + // remove existing content + this.clear().build(true) + + if (typeof text === 'function') { + // call block + text.call(this, this) + } else { + // store text and make sure text is not blank + text = text.split('\n') + + // build new lines + for (var j = 0, jl = text.length; j < jl; j++) { + this.tspan(text[j]).newLine() + } + } + + // disable build mode and rebuild lines + return this.build(false).rebuild() + }, + // Set / get leading + leading: function (value) { + // act as getter + if (value == null) { + return this.dom.leading + } + + // act as setter + this.dom.leading = new SVG.Number(value) + + return this.rebuild() + }, + // Rebuild appearance type + rebuild: function (rebuild) { + // store new rebuild flag if given + if (typeof rebuild === 'boolean') { + this._rebuild = rebuild + } + + // define position of all lines + if (this._rebuild) { + var self = this + var blankLineOffset = 0 + var dy = this.dom.leading * new SVG.Number(this.attr('font-size')) + + this.each(function () { + if (this.dom.newLined) { + this.attr('x', self.attr('x')) + + if (this.text() === '\n') { + blankLineOffset += dy + } else { + this.attr('dy', dy + blankLineOffset) + blankLineOffset = 0 + } + } + }) + + this.fire('rebuild') + } + + return this + }, + // Enable / disable build mode + build: function (build) { + this._build = !!build + return this + }, + // overwrite method from parent to set data properly + setData: function (o) { + this.dom = o + this.dom.leading = new SVG.Number(o.leading || 1.3) + return this + } + }, + + // Add parent method + construct: { + // Create text element + text: function (text) { + return this.put(new SVG.Text()).text(text) + }, + // Create plain text element + plain: function (text) { + return this.put(new SVG.Text()).plain(text) + } + } + +}) + +SVG.Tspan = SVG.invent({ + // Initialize node + create: 'tspan', + + // Inherit from + inherit: SVG.Parent, + + // Add class methods + extend: { + // Set text content + text: function (text) { + if (text == null) return this.node.textContent + (this.dom.newLined ? '\n' : '') + + typeof text === 'function' ? text.call(this, this) : this.plain(text) + + return this + }, + // Shortcut dx + dx: function (dx) { + return this.attr('dx', dx) + }, + // Shortcut dy + dy: function (dy) { + return this.attr('dy', dy) + }, + // Create new line + newLine: function () { + // fetch text parent + var t = this.parent(SVG.Text) + + // mark new line + this.dom.newLined = true + + // apply new position + return this.dy(t.dom.leading * t.attr('font-size')).attr('x', t.x()) + } + } +}) + +SVG.extend([SVG.Text, SVG.Tspan], { + // Create plain text node + plain: function (text) { + // clear if build mode is disabled + if (this._build === false) { + this.clear() + } + + // create text node + this.node.appendChild(document.createTextNode(text)) + + return this + }, + // Create a tspan + tspan: function (text) { + var tspan = new SVG.Tspan() + + // clear if build mode is disabled + if (!this._build) { + this.clear() + } + + // add new tspan + this.node.appendChild(tspan.node) + + return tspan.text(text) + }, + // FIXME: Does this also work for textpath? + // Get length of text element + length: function () { + return this.node.getComputedTextLength() + } +}) + +SVG.TextPath = SVG.invent({ + // Initialize node + create: 'textPath', + + // Inherit from + inherit: SVG.Text, + + // Define parent class + parent: SVG.Parent, + + // Add parent method + extend: { + MorphArray: SVG.PathArray, + // return the array of the path track element + array: function () { + var track = this.track() + + return track ? track.array() : null + }, + // Plot path if any + plot: function (d) { + var track = this.track() + var pathArray = null + + if (track) { + pathArray = track.plot(d) + } + + return (d == null) ? pathArray : this + }, + // Get the path element + track: function () { + return this.reference('href') + } + }, + construct: { + textPath: function (text, path) { + return this.defs().path(path).text(text).addTo(this) + } + } +}) + +SVG.extend([SVG.Text], { + // Create path for text to run on + path: function (track) { + var path = new SVG.TextPath() + + // if d is a path, reuse it + if (!(track instanceof SVG.Path)) { + // create path element + track = this.doc().defs().path(track) + } + + // link textPath to path and add content + path.attr('href', '#' + track, SVG.xlink) + + // add textPath element as child node and return textPath + return this.put(path) + }, + // Todo: make this plural? + // Get the textPath children + textPath: function () { + return this.select('textPath') + } +}) + +SVG.extend([SVG.Path], { + // creates a textPath from this path + text: function (text) { + if (text instanceof SVG.Text) { + var txt = text.text() + return text.clear().path(this).text(txt) + } + return this.parent().put(new SVG.Text()).path(this).text(text) + } + // TODO: Maybe add `targets` to get all textPaths associated with this path +}) + +SVG.A = SVG.invent({ + // Initialize node + create: 'a', + + // Inherit from + inherit: SVG.Container, + + // Add class methods + extend: { + // Link url + to: function (url) { + return this.attr('href', url, SVG.xlink) + }, + // Link target attribute + target: function (target) { + return this.attr('target', target) + } + }, + + // Add parent method + construct: { + // Create a hyperlink element + link: function (url) { + return this.put(new SVG.A()).to(url) + } + } +}) + +SVG.extend(SVG.Element, { + // Create a hyperlink element + linkTo: function (url) { + var link = new SVG.A() + + if (typeof url === 'function') { url.call(link, link) } else { + link.to(url) + } + + return this.parent().put(link).put(this) + } + +}) + +SVG.Marker = SVG.invent({ + // Initialize node + create: 'marker', + + // Inherit from + inherit: SVG.Container, + + // Add class methods + extend: { + // Set width of element + width: function (width) { + return this.attr('markerWidth', width) + }, + // Set height of element + height: function (height) { + return this.attr('markerHeight', height) + }, + // Set marker refX and refY + ref: function (x, y) { + return this.attr('refX', x).attr('refY', y) + }, + // Update marker + update: function (block) { + // remove all content + this.clear() + + // invoke passed block + if (typeof block === 'function') { block.call(this, this) } + + return this + }, + // Return the fill id + toString: function () { + return 'url(#' + this.id() + ')' + } + }, + + // Add parent method + construct: { + marker: function (width, height, block) { + // Create marker element in defs + return this.defs().marker(width, height, block) + } + } + +}) + +SVG.extend(SVG.Defs, { + // Create marker + marker: function (width, height, block) { + // Set default viewbox to match the width and height, set ref to cx and cy and set orient to auto + return this.put(new SVG.Marker()) + .size(width, height) + .ref(width / 2, height / 2) + .viewbox(0, 0, width, height) + .attr('orient', 'auto') + .update(block) + } + +}) + +SVG.extend([SVG.Line, SVG.Polyline, SVG.Polygon, SVG.Path], { + // Create and attach markers + marker: function (marker, width, height, block) { + var attr = ['marker'] + + // Build attribute name + if (marker !== 'all') attr.push(marker) + attr = attr.join('-') + + // Set marker attribute + marker = arguments[1] instanceof SVG.Marker + ? arguments[1] + : this.doc().marker(width, height, block) + + return this.attr(attr, marker) + } +}) + +// // Define list of available attributes for stroke and fill +// var sugar = { +// stroke: ['color', 'width', 'opacity', 'linecap', 'linejoin', 'miterlimit', 'dasharray', 'dashoffset'], +// fill: ['color', 'opacity', 'rule'], +// prefix: function (t, a) { +// return a === 'color' ? t : t + '-' + a +// } +// } +// +// // Add sugar for fill and stroke +// ;['fill', 'stroke'].forEach(function (m) { +// var extension = {} +// var i +// +// extension[m] = function (o) { +// if (typeof o === 'undefined') { +// return this +// } +// if (typeof o === 'string' || SVG.Color.isRgb(o) || (o && typeof o.fill === 'function')) { +// this.attr(m, o) +// } else { +// // set all attributes from sugar.fill and sugar.stroke list +// for (i = sugar[m].length - 1; i >= 0; i--) { +// if (o[sugar[m][i]] != null) { +// this.attr(sugar.prefix(m, sugar[m][i]), o[sugar[m][i]]) +// } +// } +// } +// +// return this +// } +// +// SVG.extend([SVG.Element, SVG.Timeline], extension) +// }) +// +// SVG.extend([SVG.Element, SVG.Timeline], { +// // Let the user set the matrix directly +// matrix: function (mat, b, c, d, e, f) { +// // Act as a getter +// if (mat == null) { +// return new SVG.Matrix(this) +// } +// +// // Act as a setter, the user can pass a matrix or a set of numbers +// return this.attr('transform', new SVG.Matrix(mat, b, c, d, e, f)) +// }, +// +// // Map rotation to transform +// rotate: function (angle, cx, cy) { +// return this.transform({rotate: angle, ox: cx, oy: cy}, true) +// }, +// +// // Map skew to transform +// skew: function (x, y, cx, cy) { +// return arguments.length === 1 || arguments.length === 3 +// ? this.transform({skew: x, ox: y, oy: cx}, true) +// : this.transform({skew: [x, y], ox: cx, oy: cy}, true) +// }, +// +// shear: function (lam, cx, cy) { +// return this.transform({shear: lam, ox: cx, oy: cy}, true) +// }, +// +// // Map scale to transform +// scale: function (x, y, cx, cy) { +// return arguments.length === 1 || arguments.length === 3 +// ? this.transform({ scale: x, ox: y, oy: cx }, true) +// : this.transform({ scale: [x, y], ox: cx, oy: cy }, true) +// }, +// +// // Map translate to transform +// translate: function (x, y) { +// return this.transform({ translate: [x, y] }, true) +// }, +// +// // Map relative translations to transform +// relative: function (x, y) { +// return this.transform({ relative: [x, y] }, true) +// }, +// +// // Map flip to transform +// flip: function (direction, around) { +// var directionString = typeof direction === 'string' ? direction +// : isFinite(direction) ? 'both' +// : 'both' +// var origin = (direction === 'both' && isFinite(around)) ? [around, around] +// : (direction === 'x') ? [around, 0] +// : (direction === 'y') ? [0, around] +// : isFinite(direction) ? [direction, direction] +// : [0, 0] +// this.transform({flip: directionString, origin: origin}, true) +// }, +// +// // Opacity +// opacity: function (value) { +// return this.attr('opacity', value) +// }, +// +// // Relative move over x axis +// dx: function (x) { +// return this.x(new SVG.Number(x).plus(this instanceof SVG.Timeline ? 0 : this.x()), true) +// }, +// +// // Relative move over y axis +// dy: function (y) { +// return this.y(new SVG.Number(y).plus(this instanceof SVG.Timeline ? 0 : this.y()), true) +// }, +// +// // Relative move over x and y axes +// dmove: function (x, y) { +// return this.dx(x).dy(y) +// } +// }) +// +// SVG.extend([SVG.Rect, SVG.Ellipse, SVG.Circle, SVG.Gradient, SVG.Timeline], { +// // Add x and y radius +// radius: function (x, y) { +// var type = (this._target || this).type +// return type === 'radialGradient' || type === 'radialGradient' +// ? this.attr('r', new SVG.Number(x)) +// : this.rx(x).ry(y == null ? x : y) +// } +// }) +// +// SVG.extend(SVG.Path, { +// // Get path length +// length: function () { +// return this.node.getTotalLength() +// }, +// // Get point at length +// pointAt: function (length) { +// return new SVG.Point(this.node.getPointAtLength(length)) +// } +// }) +// +// SVG.extend([SVG.Parent, SVG.Text, SVG.Tspan, SVG.Timeline], { +// // Set font +// font: function (a, v) { +// if (typeof a === 'object') { +// for (v in a) this.font(v, a[v]) +// } +// +// return a === 'leading' +// ? this.leading(v) +// : a === 'anchor' +// ? this.attr('text-anchor', v) +// : a === 'size' || a === 'family' || a === 'weight' || a === 'stretch' || a === 'variant' || a === 'style' +// ? this.attr('font-' + a, v) +// : this.attr(a, v) +// } +// }) + + +SVG.extend(SVG.Element, { + // Store data values on svg nodes + data: function (a, v, r) { + if (typeof a === 'object') { + for (v in a) { + this.data(v, a[v]) + } + } else if (arguments.length < 2) { + try { + return JSON.parse(this.attr('data-' + a)) + } catch (e) { + return this.attr('data-' + a) + } + } else { + this.attr('data-' + a, + v === null ? null + : r === true || typeof v === 'string' || typeof v === 'number' ? v + : JSON.stringify(v) + ) + } + + return this + } +}) + + +SVG.extend(SVG.Element, { + // Remember arbitrary data + remember: function (k, v) { + // remember every item in an object individually + if (typeof arguments[0] === 'object') { + for (var key in k) { + this.remember(key, k[key]) + } + } else if (arguments.length === 1) { + // retrieve memory + return this.memory()[k] + } else { + // store memory + this.memory()[k] = v + } + + return this + }, + + // Erase a given memory + forget: function () { + if (arguments.length === 0) { + this._memory = {} + } else { + for (var i = arguments.length - 1; i >= 0; i--) { + delete this.memory()[arguments[i]] + } + } + return this + }, + + // Initialize or return local memory object + memory: function () { + return this._memory || (this._memory = {}) + } +}) + +/* global idFromReference */ + +// Method for getting an element by id +SVG.get = function (id) { + var node = document.getElementById(idFromReference(id) || id) + return SVG.adopt(node) +} + +// Select elements by query string +SVG.select = function (query, parent) { + return SVG.utils.map((parent || document).querySelectorAll(query), function (node) { + return SVG.adopt(node) + }) +} + +SVG.$$ = function (query, parent) { + return SVG.utils.map((parent || document).querySelectorAll(query), function (node) { + return SVG.adopt(node) + }) +} + +SVG.$ = function (query, parent) { + return SVG.adopt((parent || document).querySelector(query)) +} + +SVG.extend(SVG.Parent, { + // Scoped select method + select: function (query) { + return SVG.select(query, this.node) + } +}) + +/* eslint no-unused-vars: 0 */ + +function createElement (element, makeNested) { + if (element instanceof SVG.Element) return element + + if (typeof element === 'object') { + return SVG.adopt(element) + } + + if (element == null) { + return new SVG.Doc() + } + + if (typeof element === 'string' && element.charAt(0) !== '<') { + return SVG.adopt(document.querySelector(element)) + } + + var node = SVG.create('svg') + node.innerHTML = element + + element = SVG.adopt(node.firstElementChild) + + return element +} + +function isNulledBox (box) { + return !box.w && !box.h && !box.x && !box.y +} + +function domContains (node) { + return (document.documentElement.contains || function (node) { + // This is IE - it does not support contains() for top-level SVGs + while (node.parentNode) { + node = node.parentNode + } + return node === document + }).call(document.documentElement, node) +} + +function pathRegReplace (a, b, c, d) { + return c + d.replace(SVG.regex.dots, ' .') +} + +// creates deep clone of array +function arrayClone (arr) { + var clone = arr.slice(0) + for (var i = clone.length; i--;) { + if (Array.isArray(clone[i])) { + clone[i] = arrayClone(clone[i]) + } + } + return clone +} + +// tests if a given element is instance of an object +function is (el, obj) { + return el instanceof obj +} + +// tests if a given selector matches an element +function matches (el, selector) { + return (el.matches || el.matchesSelector || el.msMatchesSelector || el.mozMatchesSelector || el.webkitMatchesSelector || el.oMatchesSelector).call(el, selector) +} + +// Convert dash-separated-string to camelCase +function camelCase (s) { + return s.toLowerCase().replace(/-(.)/g, function (m, g) { + return g.toUpperCase() + }) +} + +// Capitalize first letter of a string +function capitalize (s) { + return s.charAt(0).toUpperCase() + s.slice(1) +} + +// Ensure to six-based hex +function fullHex (hex) { + return hex.length === 4 + ? [ '#', + hex.substring(1, 2), hex.substring(1, 2), + hex.substring(2, 3), hex.substring(2, 3), + hex.substring(3, 4), hex.substring(3, 4) + ].join('') + : hex +} + +// Component to hex value +function compToHex (comp) { + var hex = comp.toString(16) + return hex.length === 1 ? '0' + hex : hex +} + +// Calculate proportional width and height values when necessary +function proportionalSize (element, width, height) { + if (width == null || height == null) { + var box = element.bbox() + + if (width == null) { + width = box.width / box.height * height + } else if (height == null) { + height = box.height / box.width * width + } + } + + return { + width: width, + height: height + } +} + +// Map matrix array to object +function arrayToMatrix (a) { + return { a: a[0], b: a[1], c: a[2], d: a[3], e: a[4], f: a[5] } +} + +// Add centre point to transform object +function ensureCentre (o, target) { + o.cx = o.cx == null ? target.bbox().cx : o.cx + o.cy = o.cy == null ? target.bbox().cy : o.cy +} + +// PathArray Helpers +function arrayToString (a) { + for (var i = 0, il = a.length, s = ''; i < il; i++) { + s += a[i][0] + + if (a[i][1] != null) { + s += a[i][1] + + if (a[i][2] != null) { + s += ' ' + s += a[i][2] + + if (a[i][3] != null) { + s += ' ' + s += a[i][3] + s += ' ' + s += a[i][4] + + if (a[i][5] != null) { + s += ' ' + s += a[i][5] + s += ' ' + s += a[i][6] + + if (a[i][7] != null) { + s += ' ' + s += a[i][7] + } + } + } + } + } + } + + return s + ' ' +} + +// Deep new id assignment +function assignNewId (node) { + // do the same for SVG child nodes as well + for (var i = node.children.length - 1; i >= 0; i--) { + assignNewId(node.children[i]) + } + + if (node.id) { + return SVG.adopt(node).id(SVG.eid(node.nodeName)) + } + + return SVG.adopt(node) +} + +// Add more bounding box properties +function fullBox (b) { + if (b.x == null) { + b.x = 0 + b.y = 0 + b.width = 0 + b.height = 0 + } + + b.w = b.width + b.h = b.height + b.x2 = b.x + b.width + b.y2 = b.y + b.height + b.cx = b.x + b.width / 2 + b.cy = b.y + b.height / 2 + + return b +} + +// Get id from reference string +function idFromReference (url) { + var m = (url || '').toString().match(SVG.regex.reference) + + if (m) return m[1] +} + +// Create matrix array for looping +var abcdef = 'abcdef'.split('') + +function closeEnough (a, b, threshold) { + return Math.abs(b - a) < (threshold || 1e-6) +} + +// TODO: Refactor this to a static function of matrix.js +function formatTransforms (o) { + + // Get all of the parameters required to form the matrix + var flipBoth = o.flip === 'both' || o.flip === true + var flipX = o.flip && (flipBoth || o.flip === 'x') ? -1 : 1 + var flipY = o.flip && (flipBoth || o.flip === 'y') ? -1 : 1 + var skewX = o.skew && o.skew.length ? o.skew[0] + : isFinite(o.skew) ? o.skew + : isFinite(o.skewX) ? o.skewX + : 0 + var skewY = o.skew && o.skew.length ? o.skew[1] + : isFinite(o.skew) ? o.skew + : isFinite(o.skewY) ? o.skewY + : 0 + var scaleX = o.scale && o.scale.length ? o.scale[0] * flipX + : isFinite(o.scale) ? o.scale * flipX + : isFinite(o.scaleX) ? o.scaleX * flipX + : flipX + var scaleY = o.scale && o.scale.length ? o.scale[1] * flipY + : isFinite(o.scale) ? o.scale * flipY + : isFinite(o.scaleY) ? o.scaleY * flipY + : flipY + var shear = o.shear || 0 + var theta = o.rotate || 0 + var origin = new SVG.Point(o.ox == null ? o.origin : o.ox, o.oy) + var ox = origin.x + var oy = origin.y + var position = new SVG.Point(o.px == null + ? o.position : o.px, o.py, {x: null, y: null}) + var px = position.x + var py = position.y + var translate = new SVG.Point(o.tx == null ? o.translate : o.tx, o.ty) + var tx = translate.x + var ty = translate.y + var relative = new SVG.Point(o.rx == null ? o.relative : o.rx, o.ry) + var rx = relative.x + var ry = relative.y + + // Populate all of the values + return { + scaleX: scaleX, + scaleY: scaleY, + skewX: skewX, + skewY: skewY, + shear: shear, + theta: theta, + rx: rx, + ry: ry, + tx: tx, + ty: ty, + ox: ox, + oy: oy, + px: px, + py: py, + } +} + +/* globals fullBox, domContains, isNulledBox, Exception */ + +SVG.Box = SVG.invent({ + create: function (source) { + var base = [0, 0, 0, 0] + source = typeof source === 'string' ? source.split(SVG.regex.delimiter).map(parseFloat) + : Array.isArray(source) ? source + : typeof source === 'object' ? [source.left != null ? source.left + : source.x, source.top != null ? source.top : source.y, source.width, source.height] + : arguments.length === 4 ? [].slice.call(arguments) + : base + + this.x = source[0] + this.y = source[1] + this.width = source[2] + this.height = source[3] + + // add center, right, bottom... + fullBox(this) + }, + extend: { + // Merge rect box with another, return a new instance + merge: function (box) { + var x = Math.min(this.x, box.x) + var y = Math.min(this.y, box.y) + + return new SVG.Box( + x, y, + Math.max(this.x + this.width, box.x + box.width) - x, + Math.max(this.y + this.height, box.y + box.height) - y + ) + }, + + transform: function (m) { + var xMin = Infinity + var xMax = -Infinity + var yMin = Infinity + var yMax = -Infinity + + var pts = [ + new SVG.Point(this.x, this.y), + new SVG.Point(this.x2, this.y), + new SVG.Point(this.x, this.y2), + new SVG.Point(this.x2, this.y2) + ] + + pts.forEach(function (p) { + p = p.transform(m) + xMin = Math.min(xMin, p.x) + xMax = Math.max(xMax, p.x) + yMin = Math.min(yMin, p.y) + yMax = Math.max(yMax, p.y) + }) + + return new SVG.Box( + xMin, yMin, + xMax - xMin, + yMax - yMin + ) + }, + + addOffset: function () { + // offset by window scroll position, because getBoundingClientRect changes when window is scrolled + this.x += window.pageXOffset + this.y += window.pageYOffset + return this + }, + toString: function () { + return this.x + ' ' + this.y + ' ' + this.width + ' ' + this.height + }, + morph: function (x, y, width, height) { + this.destination = new SVG.Box(x, y, width, height) + return this + }, + + at: function (pos) { + if (!this.destination) return this + + return new SVG.Box( + this.x + (this.destination.x - this.x) * pos + , this.y + (this.destination.y - this.y) * pos + , this.width + (this.destination.width - this.width) * pos + , this.height + (this.destination.height - this.height) * pos + ) + } + }, + + // Define Parent + parent: SVG.Element, + + // Constructor + construct: { + // Get bounding box + bbox: function () { + var box + + try { + // find native bbox + box = this.node.getBBox() + + if (isNulledBox(box) && !domContains(this.node)) { + throw new Exception('Element not in the dom') + } + } catch (e) { + try { + var clone = this.clone(SVG.parser().svg).show() + box = clone.node.getBBox() + clone.remove() + } catch (e) { + console.warn('Getting a bounding box of this element is not possible') + } + } + + return new SVG.Box(box) + }, + + rbox: function (el) { + // IE11 throws an error when element not in dom + try { + var box = new SVG.Box(this.node.getBoundingClientRect()) + if (el) return box.transform(el.screenCTM().inverse()) + return box.addOffset() + } catch (e) { + return new SVG.Box() + } + } + } +}) + +SVG.extend([SVG.Doc, SVG.Symbol, SVG.Image, SVG.Pattern, SVG.Marker, SVG.ForeignObject, SVG.View], { + viewbox: function (x, y, width, height) { + // act as getter + if (x == null) return new SVG.Box(this.attr('viewBox')) + + // act as setter + return this.attr('viewBox', new SVG.Box(x, y, width, height)) + } +}) + + +SVG.parser = function () { + var b + + if (!SVG.parser.nodes.svg.node.parentNode) { + b = document.body || document.documentElement + SVG.parser.nodes.svg.addTo(b) + } + + return SVG.parser.nodes +} + +SVG.parser.nodes = { + svg: SVG().size(2, 0).css({ + opacity: 0, + position: 'absolute', + left: '-100%', + top: '-100%', + overflow: 'hidden' + }) +} + +SVG.parser.nodes.path = SVG.parser.nodes.svg.path().node /* global requestAnimationFrame */ @@ -4682,8 +4691,8 @@ SVG.Animator = { : null } } - - -return SVG - -})); \ No newline at end of file + + +return SVG + +})); \ No newline at end of file diff --git a/dist/svg.min.js b/dist/svg.min.js index a569519..7762af0 100644 --- a/dist/svg.min.js +++ b/dist/svg.min.js @@ -1,2 +1,2 @@ -/*! svg.js v3.0.0 MIT*/;!function(t,e){"function"==typeof define&&define.amd?define(function(){return e(t,t.document)}):"object"==typeof exports?module.exports=t.document?e(t,t.document):function(t){return e(t,t.document)}:t.SVG=e(t,t.document)}("undefined"!=typeof window?window:this,function(t,e){function n(t,n){if(t instanceof b.Element)return t;if("object"==typeof t)return b.adopt(t);if(null==t)return new b.Doc;if("string"==typeof t&&"<"!==t.charAt(0))return b.adopt(e.querySelector(t));var i=b.create("svg");return i.innerHTML=t,t=b.adopt(i.firstElementChild)}function i(t){return!(t.w||t.h||t.x||t.y)}function r(t){return(e.documentElement.contains||function(t){for(;t.parentNode;)t=t.parentNode;return t===e}).call(e.documentElement,t)}function s(t,e,n,i){return n+i.replace(b.regex.dots," .")}function o(t){for(var e=t.slice(0),n=e.length;n--;)Array.isArray(e[n])&&(e[n]=o(e[n]));return e}function a(t,e){return t instanceof e}function h(t,e){return(t.matches||t.matchesSelector||t.msMatchesSelector||t.mozMatchesSelector||t.webkitMatchesSelector||t.oMatchesSelector).call(t,e)}function u(t){return t.toLowerCase().replace(/-(.)/g,function(t,e){return e.toUpperCase()})}function l(t){return t.charAt(0).toUpperCase()+t.slice(1)}function c(t){return 4===t.length?["#",t.substring(1,2),t.substring(1,2),t.substring(2,3),t.substring(2,3),t.substring(3,4),t.substring(3,4)].join(""):t}function f(t){var e=t.toString(16);return 1===e.length?"0"+e:e}function d(t,e,n){if(null==e||null==n){var i=t.bbox();null==e?e=i.width/i.height*n:null==n&&(n=i.height/i.width*e)}return{width:e,height:n}}function p(t){return{a:t[0],b:t[1],c:t[2],d:t[3],e:t[4],f:t[5]}}function m(t){for(var e=0,n=t.length,i="";e=0;e--)x(t.children[e]);return t.id?b.adopt(t).id(b.eid(t.nodeName)):b.adopt(t)}function v(t){return null==t.x&&(t.x=0,t.y=0,t.width=0,t.height=0),t.w=t.width,t.h=t.height,t.x2=t.x+t.width,t.y2=t.y+t.height,t.cx=t.x+t.width/2,t.cy=t.y+t.height/2,t}function y(t){var e=(t||"").toString().match(b.regex.reference);if(e)return e[1]}function g(t,e,n){return Math.abs(e-t)<(n||1e-6)}function w(t){var e="both"===t.flip||!0===t.flip,n=t.flip&&(e||"x"===t.flip)?-1:1,i=t.flip&&(e||"y"===t.flip)?-1:1,r=t.skew&&t.skew.length?t.skew[0]:isFinite(t.skew)?t.skew:isFinite(t.skewX)?t.skewX:0,s=t.skew&&t.skew.length?t.skew[1]:isFinite(t.skew)?t.skew:isFinite(t.skewY)?t.skewY:0,o=t.scale&&t.scale.length?t.scale[0]*n:isFinite(t.scale)?t.scale*n:isFinite(t.scaleX)?t.scaleX*n:n,a=t.scale&&t.scale.length?t.scale[1]*i:isFinite(t.scale)?t.scale*i:isFinite(t.scaleY)?t.scaleY*i:i,h=t.shear||0,u=t.rotate||0,l=new b.Point(null==t.ox?t.origin:t.ox,t.oy),c=l.x,f=l.y,d=new b.Point(null==t.px?t.position:t.px,t.py,{x:null,y:null}),p=d.x,m=d.y,x=new b.Point(null==t.tx?t.translate:t.tx,t.ty),v=x.x,y=x.y,g=new b.Point(null==t.rx?t.relative:t.rx,t.ry);return{scaleX:o,scaleY:a,skewX:r,skewY:s,shear:h,theta:u,rx:g.x,ry:g.y,tx:v,ty:y,ox:c,oy:f,px:p,py:m}}if(!e.createElementNS||!e.createElementNS("http://www.w3.org/2000/svg","svg").createSVGRect)return{supported:!1};var b=this.SVG=function(t){if(b.supported)return t=n(t)};b.supported=!0,b.ns="http://www.w3.org/2000/svg",b.xmlns="http://www.w3.org/2000/xmlns/",b.xlink="http://www.w3.org/1999/xlink",b.svgjs="http://svgjs.com/svgjs",b.did=1e3,b.eid=function(t){return"Svgjs"+l(t)+b.did++},b.create=function(t){return e.createElementNS(this.ns,t)},b.extend=function(t,e){var n,i;for(t=Array.isArray(t)?t:[t],i=t.length-1;i>=0;i--)if(t[i])for(n in e)t[i].prototype[n]=e[n]},b.invent=function(t){var e="function"==typeof t.create?t.create:function(e){b.Element.call(this,e||b.create(t.create))};return t.inherit&&(e.prototype=new t.inherit,e.prototype.constructor=e),t.extend&&b.extend(e,t.extend),t.construct&&b.extend(t.parent||b.Container,t.construct),e},b.adopt=function(e){if(!e)return null;if(e.instance instanceof b.Element)return e.instance;if(!(e instanceof t.SVGElement))return new b.HtmlNode(e);return"svg"===e.nodeName?new b.Doc(e):"linearGradient"===e.nodeName||"radialGradient"===e.nodeName?new b.Gradient(e):b[l(e.nodeName)]?new(b[l(e.nodeName)])(e):new b.Parent(e)},b.regex={numberAndUnit:/^([+-]?(\d+(\.\d*)?|\.\d+)(e[+-]?\d+)?)([a-z%]*)$/i,hex:/^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i,rgb:/rgb\((\d+),(\d+),(\d+)\)/,reference:/#([a-z0-9\-_]+)/i,transforms:/\)\s*,?\s*/,whitespace:/\s/g,isHex:/^#[a-f0-9]{3,6}$/i,isRgb:/^rgb\(/,isCss:/[^:]+:[^;]+;?/,isBlank:/^(\s+)?$/,isNumber:/^[+-]?(\d+(\.\d*)?|\.\d+)(e[+-]?\d+)?$/i,isPercent:/^-?[\d.]+%$/,isImage:/\.(jpg|jpeg|png|gif|svg)(\?[^=]+.*)?/i,delimiter:/[\s,]+/,hyphen:/([^e])-/gi,pathLetters:/[MLHVCSQTAZ]/gi,isPathLetter:/[MLHVCSQTAZ]/i,numbersWithDots:/((\d?\.\d+(?:e[+-]?\d+)?)((?:\.\d+(?:e[+-]?\d+)?)+))+/gi,dots:/\./g},b.utils={map:function(t,e){var n,i=t.length,r=[];for(n=0;n",delay:0},attrs:{"fill-opacity":1,"stroke-opacity":1,"stroke-width":0,"stroke-linejoin":"miter","stroke-linecap":"butt",fill:"#000000",stroke:"#000000",opacity:1,x:0,y:0,cx:0,cy:0,width:0,height:0,r:0,rx:0,ry:0,offset:0,"stop-opacity":1,"stop-color":"#000000","font-size":16,"font-family":"Helvetica, Arial, sans-serif","text-anchor":"start"}},b.Queue=b.invent({create:function(){this._first=null,this._last=null,this.length=0,this.id=0},extend:{push:function(t){var e={id:this.id++,value:t};this._last?this._last=this._last.next=e:this._last=this._first=e,this.length++},shift:function(){if(0!=this.length){var t=this._first;return this._first=t.next,this._last=--this.length?this._last:null,t.value}},first:function(){return this._first&&this._first.value},last:function(){return this._last&&this._last.value},remove:function(t){for(var e=null,n=this._first;n&&!t(n);)e=n,n=n.next;if(n&&n===this._first&&(this._first=this._first.next),n&&n===this._last&&(this._last=e),n)return--this.length,e&&(e.next=n.next),n.item}}}),b.Color=function(t){var e;this.r=0,this.g=0,this.b=0,t&&("string"==typeof t?b.regex.isRgb.test(t)?(e=b.regex.rgb.exec(t.replace(b.regex.whitespace,"")),this.r=parseInt(e[1]),this.g=parseInt(e[2]),this.b=parseInt(e[3])):b.regex.isHex.test(t)&&(e=b.regex.hex.exec(c(t)),this.r=parseInt(e[1],16),this.g=parseInt(e[2],16),this.b=parseInt(e[3],16)):"object"==typeof t&&(this.r=t.r,this.g=t.g,this.b=t.b))},b.extend(b.Color,{toString:function(){return this.toHex()},toArray:function(){return[this.r,this.g,this.b]},fromArray:function(t){return new b.Color(t[0],t[1],t[2])},toHex:function(){return"#"+f(this.r)+f(this.g)+f(this.b)},toRgb:function(){return"rgb("+[this.r,this.g,this.b].join()+")"},brightness:function(){return this.r/255*.3+this.g/255*.59+this.b/255*.11},morph:function(t){return this.destination=new b.Color(t),this},at:function(t){return this.destination?(t=t<0?0:t>1?1:t,new b.Color({r:~~(this.r+(this.destination.r-this.r)*t),g:~~(this.g+(this.destination.g-this.g)*t),b:~~(this.b+(this.destination.b-this.b)*t)})):this}}),b.Color.test=function(t){return t+="",b.regex.isHex.test(t)||b.regex.isRgb.test(t)},b.Color.isRgb=function(t){return t&&"number"==typeof t.r&&"number"==typeof t.g&&"number"==typeof t.b},b.Color.isColor=function(t){return b.Color.isRgb(t)||b.Color.test(t)},b.Array=function(t,e){t=(t||[]).valueOf(),0===t.length&&e&&(t=e.valueOf()),this.value=this.parse(t)},b.extend(b.Array,{morph:function(t){if(this.destination=this.parse(t),this.value.length!==this.destination.length){for(var e=this.value[this.value.length-1],n=this.destination[this.destination.length-1];this.value.length>this.destination.length;)this.destination.push(n);for(;this.value.length=0;i--)this.value[i]=[this.value[i][0]+t,this.value[i][1]+e];return this},size:function(t,e){var n,i=this.bbox();for(n=this.value.length-1;n>=0;n--)i.width&&(this.value[n][0]=(this.value[n][0]-i.x)*t/i.width+i.x),i.height&&(this.value[n][1]=(this.value[n][1]-i.y)*e/i.height+i.y);return this},bbox:function(){var t=-1/0,e=-1/0,n=1/0,i=1/0;return this.value.forEach(function(r){t=Math.max(r[0],t),e=Math.max(r[1],e),n=Math.min(r[0],n),i=Math.min(r[1],i)}),{x:n,y:i,width:t-n,height:e-i}}});for(var A={M:function(t,e,n){return e.x=n.x=t[0],e.y=n.y=t[1],["M",e.x,e.y]},L:function(t,e){return e.x=t[0],e.y=t[1],["L",t[0],t[1]]},H:function(t,e){return e.x=t[0],["H",t[0]]},V:function(t,e){return e.y=t[0],["V",t[0]]},C:function(t,e){return e.x=t[4],e.y=t[5],["C",t[0],t[1],t[2],t[3],t[4],t[5]]},S:function(t,e){return e.x=t[2],e.y=t[3],["S",t[0],t[1],t[2],t[3]]},Q:function(t,e){return e.x=t[2],e.y=t[3],["Q",t[0],t[1],t[2],t[3]]},T:function(t,e){return e.x=t[0],e.y=t[1],["T",t[0],t[1]]},Z:function(t,e,n){return e.x=n.x,e.y=n.y,["Z"]},A:function(t,e){return e.x=t[5],e.y=t[6],["A",t[0],t[1],t[2],t[3],t[4],t[5],t[6]]}},C="mlhvqtcsaz".split(""),P=0,N=C.length;P=0;r--)i=this.value[r][0],"M"===i||"L"===i||"T"===i?(this.value[r][1]+=t,this.value[r][2]+=e):"H"===i?this.value[r][1]+=t:"V"===i?this.value[r][1]+=e:"C"===i||"S"===i||"Q"===i?(this.value[r][1]+=t,this.value[r][2]+=e,this.value[r][3]+=t,this.value[r][4]+=e,"C"===i&&(this.value[r][5]+=t,this.value[r][6]+=e)):"A"===i&&(this.value[r][6]+=t,this.value[r][7]+=e);return this},size:function(t,e){var n,i,r=this.bbox();for(n=this.value.length-1;n>=0;n--)i=this.value[n][0],"M"===i||"L"===i||"T"===i?(this.value[n][1]=(this.value[n][1]-r.x)*t/r.width+r.x,this.value[n][2]=(this.value[n][2]-r.y)*e/r.height+r.y):"H"===i?this.value[n][1]=(this.value[n][1]-r.x)*t/r.width+r.x:"V"===i?this.value[n][1]=(this.value[n][1]-r.y)*e/r.height+r.y:"C"===i||"S"===i||"Q"===i?(this.value[n][1]=(this.value[n][1]-r.x)*t/r.width+r.x,this.value[n][2]=(this.value[n][2]-r.y)*e/r.height+r.y,this.value[n][3]=(this.value[n][3]-r.x)*t/r.width+r.x,this.value[n][4]=(this.value[n][4]-r.y)*e/r.height+r.y,"C"===i&&(this.value[n][5]=(this.value[n][5]-r.x)*t/r.width+r.x,this.value[n][6]=(this.value[n][6]-r.y)*e/r.height+r.y)):"A"===i&&(this.value[n][1]=this.value[n][1]*t/r.width,this.value[n][2]=this.value[n][2]*e/r.height,this.value[n][6]=(this.value[n][6]-r.x)*t/r.width+r.x,this.value[n][7]=(this.value[n][7]-r.y)*e/r.height+r.y);return this},equalCommands:function(t){var e,n,i;for(t=new b.PathArray(t),i=this.value.length===t.value.length,e=0,n=this.value.length;i&&ea);return i},bbox:function(){return b.parser().path.setAttribute("d",this.toString()),b.parser.nodes.path.getBBox()}}),b.Number=b.invent({create:function(t,e){this.value=0,this.unit=e||"","number"==typeof t?this.value=isNaN(t)?0:isFinite(t)?t:t<0?-3.4e38:3.4e38:"string"==typeof t?(e=t.match(b.regex.numberAndUnit))&&(this.value=parseFloat(e[1]),"%"===e[5]?this.value/=100:"s"===e[5]&&(this.value*=1e3),this.unit=e[5]):t instanceof b.Number&&(this.value=t.valueOf(),this.unit=t.unit)},extend:{toString:function(){return("%"===this.unit?~~(1e8*this.value)/1e6:"s"===this.unit?this.value/1e3:this.value)+this.unit},toJSON:function(){return this.toString()},toArray:function(){return[this.value]},fromArray:function(t){return new b.Number(t[0])},valueOf:function(){return this.value},plus:function(t){return t=new b.Number(t),new b.Number(this+t,this.unit||t.unit)},minus:function(t){return t=new b.Number(t),new b.Number(this-t,this.unit||t.unit)},times:function(t){return t=new b.Number(t),new b.Number(this*t,this.unit||t.unit)},divide:function(t){return t=new b.Number(t),new b.Number(this/t,this.unit||t.unit)},to:function(t){var e=new b.Number(this);return"string"==typeof t&&(e.unit=t),e},morph:function(t){return this.destination=new b.Number(t),t.relative&&(this.destination.value+=this.value),this},at:function(t){return this.destination?new b.Number(this.destination).minus(this).times(t).plus(this):this}}}),b.HtmlNode=b.invent({create:function(t){this.node=t},extend:{add:function(t,e){return t=n(t),t.node!==this.node.children[e]&&this.node.insertBefore(t.node,this.node.children[e]||null),this},put:function(t,e){return this.add(t,e),t}}}),b.Element=b.invent({create:function(t){this.events={},this.dom={},this.node=t,this.node&&(this.type=t.nodeName,this.node.instance=this,this.events=t.events||{},t.hasAttribute("svgjs:data")&&this.setData(JSON.parse(t.getAttribute("svgjs:data"))||{}))},extend:{x:function(t){return this.attr("x",t)},y:function(t){return this.attr("y",t)},cx:function(t){return null==t?this.x()+this.width()/2:this.x(t-this.width()/2)},cy:function(t){return null==t?this.y()+this.height()/2:this.y(t-this.height()/2)},move:function(t,e){return this.x(t).y(e)},center:function(t,e){return this.cx(t).cy(e)},width:function(t){return this.attr("width",t)},height:function(t){return this.attr("height",t)},size:function(t,e){var n=d(this,t,e);return this.width(new b.Number(n.width)).height(new b.Number(n.height))},clone:function(t){this.writeDataToDom();var e=x(this.node.cloneNode(!0));return t?t.add(e):this.after(e),e},remove:function(){return this.parent()&&this.parent().removeElement(this),this},replace:function(t){return this.after(t).remove(),t},addTo:function(t){return n(t).put(this)},putIn:function(t){return n(t).add(this)},id:function(t){return void 0!==t||this.node.id||(this.node.id=b.eid(this.type)),this.attr("id",t)},inside:function(t,e){var n=this.bbox();return t>n.x&&e>n.y&&t=0;--e)this[M[e]]=null!=t[M[e]]?t[M[e]]:n[M[e]]},extend:{clone:function(){return new b.Matrix(this)},transform:function(t){if(null!=t.a){var e=new b.Matrix(t);return this.lmultiply(e)}var n=w(t),i=new b.Matrix(this),r=(new b.Matrix).translate(-n.ox,-n.oy).scale(n.scaleX,n.scaleY).skew(n.skewX,n.skewY).shear(n.shear).rotate(n.theta).translate(n.ox,n.oy).translate(n.rx,n.ry).lmultiply(i);if(isFinite(n.px)||isFinite(n.py)){var s=new b.Point(n.ox-n.rx,n.oy-n.ry).transform(r),o=n.px?n.px-s.x:0,a=n.py?n.py-s.y:0;r=r.translate(o,a)}return r=r.translate(n.tx,n.ty)},compose:function(t){var e=t.scaleX||1,n=t.scaleY||1,i=t.shear||0,r=t.rotate||0,s=t.translateX||0,o=t.translateY||0;return(new b.Matrix).scale(e,n).shear(i).rotate(r).translate(s,o).lmultiply(this)},decompose:function(){var t=this.a,e=this.b,n=this.c,i=this.d,r=this.e,s=this.f,o=t*i-e*n,a=o>0?1:-1,h=a*Math.sqrt(t*t+e*e),u=180/Math.PI*Math.atan2(a*e,a*t),l=(t*n+e*i)/o;return{scaleX:h,scaleY:n*h/(l*t-e)||i*h/(l*e+t),shear:l,rotate:u,translateX:r,translateY:s,a:this.a,b:this.b,c:this.c,d:this.d,e:this.e,f:this.f}},morph:function(t){return this.destination=new b.Matrix(t),this},at:function(t){return this.destination?new b.Matrix({a:this.a+(this.destination.a-this.a)*t,b:this.b+(this.destination.b-this.b)*t,c:this.c+(this.destination.c-this.c)*t,d:this.d+(this.destination.d-this.d)*t,e:this.e+(this.destination.e-this.e)*t,f:this.f+(this.destination.f-this.f)*t}):this},multiply:function(t){var e=this,n=new b.Matrix(t),i=e.a*n.a+e.c*n.b,r=e.b*n.a+e.d*n.b,s=e.a*n.c+e.c*n.d,o=e.b*n.c+e.d*n.d,a=e.e+e.a*n.e+e.c*n.f,h=e.f+e.b*n.e+e.d*n.f;return new b.Matrix(i,r,s,o,a,h)},lmultiply:function(t){return new b.Matrix(t).multiply(this)},inverse:function(){var t=this.a,e=this.b,n=this.c,i=this.d,r=this.e,s=this.f,o=t*i-e*n;if(!o)throw new Error("Cannot invert "+this);var a=i/o,h=-e/o,u=-n/o,l=t/o,c=-(a*r+u*s),f=-(h*r+l*s);return new b.Matrix(a,h,u,l,c,f)},translate:function(t,e){var n=new b.Matrix(this);return n.e+=t||0,n.f+=e||0,n},scale:function(t,e,n,i){1===arguments.length?e=t:3===arguments.length&&(i=n,n=e,e=t);var r=new b.Matrix(t,0,0,e,0,0);return this.around(n,i,r)},rotate:function(t,e,n){t=b.utils.radians(t);var i=new b.Matrix(Math.cos(t),Math.sin(t),-Math.sin(t),Math.cos(t),0,0);return this.around(e,n,i)},flip:function(t,e){return"x"===t?this.scale(-1,1,e,0):"y"===t?this.scale(1,-1,0,e):this.scale(-1,-1,t,e||t)},shear:function(t,e,n){var i=new b.Matrix(1,0,t,1,0,0);return this.around(e,n,i)},skew:function(t,e,n,i){1===arguments.length?e=t:3===arguments.length&&(i=n,n=e,e=t),t=b.utils.radians(t),e=b.utils.radians(e);var r=new b.Matrix(1,Math.tan(e),Math.tan(t),1,0,0);return this.around(n,i,r)},skewX:function(t,e,n){return this.skew(t,0,e,n)},skewY:function(t,e,n){return this.skew(0,t,e,n)},around:function(t,e,n){var i=t||0,r=e||0;return this.translate(-i,-r).lmultiply(n).translate(i,r)},native:function(){for(var t=b.parser.nodes.svg.node.createSVGMatrix(),e=M.length-1;e>=0;e--)t[M[e]]=this[M[e]];return t},equals:function(t){var e=new b.Matrix(t);return g(this.a,e.a)&&g(this.b,e.b)&&g(this.c,e.c)&&g(this.d,e.d)&&g(this.e,e.e)&&g(this.f,e.f)},toString:function(){return"matrix("+this.a+","+this.b+","+this.c+","+this.d+","+this.e+","+this.f+")"},toArray:function(){return[this.a,this.b,this.c,this.d,this.e,this.f]},fromArray:function(t){return new b.Matrix(t)}},parent:b.Element,construct:{ctm:function(){return new b.Matrix(this.node.getCTM())},screenCTM:function(){if(this instanceof b.Doc&&!this.isRoot()){var t=this.rect(1,1),e=t.node.getScreenCTM();return t.remove(),new b.Matrix(e)}return new b.Matrix(this.node.getScreenCTM())}}}),b.Point=b.invent({create:function(t,e,n){var i;n=n||{x:0,y:0},i=Array.isArray(t)?{x:t[0],y:t[1]}:"object"==typeof t?{x:t.x,y:t.y}:{x:t,y:e},this.x=null==i.x?n.x:i.x,this.y=null==i.y?n.y:i.y},extend:{clone:function(){return new b.Point(this)},morph:function(t,e){return this.destination=new b.Point(t,e),this},at:function(t){return this.destination?new b.Point({x:this.x+(this.destination.x-this.x)*t,y:this.y+(this.destination.y-this.y)*t}):this},native:function(){var t=b.parser.nodes.svg.node.createSVGPoint();return t.x=this.x,t.y=this.y,t},transform:function(t){return new b.Point(this.native().matrixTransform(t.native()))}}}),b.extend(b.Element,{point:function(t,e){return new b.Point(t,e).transform(this.screenCTM().inverse())}}),b.extend(b.Element,{attr:function(t,e,n){if(null==t){for(t={},e=this.node.attributes,n=e.length-1;n>=0;n--)t[e[n].nodeName]=b.regex.isNumber.test(e[n].nodeValue)?parseFloat(e[n].nodeValue):e[n].nodeValue;return t}if("object"==typeof t)for(e in t)this.attr(e,t[e]);else if(null===e)this.node.removeAttribute(t);else{if(null==e)return e=this.node.getAttribute(t),null==e?b.defaults.attrs[t]:b.regex.isNumber.test(e)?parseFloat(e):e;"fill"!==t&&"stroke"!==t||(b.regex.isImage.test(e)&&(e=this.doc().defs().image(e)),e instanceof b.Image&&(e=this.doc().defs().pattern(0,0,function(){this.add(e)}))),"number"==typeof e?e=new b.Number(e):b.Color.isColor(e)?e=new b.Color(e):Array.isArray(e)&&(e=new b.Array(e)),"leading"===t?this.leading&&this.leading(e):"string"==typeof n?this.node.setAttributeNS(n,t,e.toString()):this.node.setAttribute(t,e.toString()),!this.rebuild||"font-size"!==t&&"x"!==t||this.rebuild(t,e)}return this}}),b.extend(b.Element,{untransform:function(){return this.attr("transform",null)},matrixify:function(){return(this.attr("transform")||"").split(b.regex.transforms).slice(0,-1).map(function(t){var e=t.trim().split("(");return[e[0],e[1].split(b.regex.delimiter).map(function(t){return parseFloat(t)})]}).reverse().reduce(function(t,e){return"matrix"===e[0]?t.lmultiply(p(e[1])):t[e[0]].apply(t,e[1])},new b.Matrix)},toParent:function(t){if(this===t)return this;var e=this.screenCTM(),n=t.screenCTM().inverse();return this.addTo(t).untransform().transform(n.multiply(e)),this},toDoc:function(){return this.toParent(this.doc())}}),b.extend(b.Element,{transform:function(t,e){var n=this.bbox();if(null==t||"string"==typeof t){var i=new b.Matrix(this).decompose();return i[t]||i}if("string"==typeof t.origin||null==t.origin&&null==t.ox&&null==t.oy){var r="string"==typeof t.origin?t.origin.toLowerCase().trim():"center",s=n.height,o=n.width,a=n.x,h=n.y;t.ox=r.includes("left")?a:r.includes("right")?a+o:a+o/2,t.oy=r.includes("top")?h:r.includes("bottom")?h+s:h+s/2,t.origin=null}var u=!0===e?this:e||!1,l=new b.Matrix(u).transform(t);return this.attr("transform",l)}}),b.extend(b.Timeline,{transform:function(t,e,n){}}),b.extend(b.Element,{css:function(t,e){var n,i,r={};if(0===arguments.length)return this.node.style.cssText.split(/\s*;\s*/).filter(function(t){return!!t.length}).forEach(function(t){n=t.split(/\s*:\s*/),r[n[0]]=n[1]}),r;if(arguments.length<2){if(Array.isArray(t)){for(i=t.length;i--;)r[u(t[i])]=this.node.style[u(t[i])];return r}if("string"==typeof t)return this.node.style[u(t)];if("object"==typeof t)for(i in t)this.node.style[u(i)]=null==t[i]||b.regex.isBlank.test(t[i])?"":t[i]}return 2===arguments.length&&(this.node.style[u(t)]=null==e||b.regex.isBlank.test(e)?"":e),this}}),b.Parent=b.invent({create:function(t){b.Element.call(this,t)},inherit:b.Element,extend:{children:function(){return b.utils.map(this.node.children,function(t){return b.adopt(t)})},add:function(t,e){return t=n(t),t.node!==this.node.children[e]&&this.node.insertBefore(t.node,this.node.children[e]||null),this},put:function(t,e){return this.add(t,e),t.instance||t},has:function(t){return this.index(t)>=0},index:function(t){return[].slice.call(this.node.children).indexOf(t.node)},get:function(t){return b.adopt(this.node.children[t])},first:function(){return this.get(0)},last:function(){return this.get(this.node.children.length-1)},each:function(t,e){var n,i,r=this.children();for(n=0,i=r.length;n0&&this.parent().removeElement(this).add(this,t-1),this},front:function(){var t=this.parent();return t.node.appendChild(this.node),t instanceof b.Doc&&t.node.appendChild(t.defs().node),this},back:function(){return this.position()>0&&this.parent().removeElement(this).add(this,0),this},before:function(t){t.remove();var e=this.position();return this.parent().add(t,e),this},after:function(t){t.remove();var e=this.position();return this.parent().add(t,e+1),this}}),b.Mask=b.invent({create:"mask",inherit:b.Container,extend:{remove:function(){return this.targets().forEach(function(t){t.unmask()}),b.Element.prototype.remove.call(this)},targets:function(){return b.select('svg [mask*="'+this.id()+'"]')}},construct:{mask:function(){return this.defs().put(new b.Mask)}}}),b.extend(b.Element,{maskWith:function(t){var e=t instanceof b.Mask?t:this.parent().mask().add(t);return this.attr("mask",'url("#'+e.id()+'")')},unmask:function(){return this.attr("mask",null)},masker:function(){return this.reference("mask")}}),b.ClipPath=b.invent({create:"clipPath",inherit:b.Container,extend:{remove:function(){return this.targets().forEach(function(t){t.unclip()}),b.Element.prototype.remove.call(this)},targets:function(){return b.select('svg [clip-path*="'+this.id()+'"]')}},construct:{clip:function(){return this.defs().put(new b.ClipPath)}}}),b.extend(b.Element,{clipWith:function(t){var e=t instanceof b.ClipPath?t:this.parent().clip().add(t);return this.attr("clip-path",'url("#'+e.id()+'")')},unclip:function(){return this.attr("clip-path",null)},clipper:function(){return this.reference("clip-path")}}),b.Gradient=b.invent({create:function(t){ -b.Element.call(this,"object"==typeof t?t:b.create(t+"Gradient"))},inherit:b.Container,extend:{stop:function(t,e,n){return this.put(new b.Stop).update(t,e,n)},update:function(t){return this.clear(),"function"==typeof t&&t.call(this,this),this},url:function(){return"url(#"+this.id()+")"},toString:function(){return this.url()},attr:function(t,e,n){return"transform"===t&&(t="gradientTransform"),b.Container.prototype.attr.call(this,t,e,n)}},construct:{gradient:function(t,e){return this.defs().gradient(t,e)}}}),b.extend([b.Gradient,b.Timeline],{from:function(t,e){return"radialGradient"===(this._target||this).type?this.attr({fx:new b.Number(t),fy:new b.Number(e)}):this.attr({x1:new b.Number(t),y1:new b.Number(e)})},to:function(t,e){return"radialGradient"===(this._target||this).type?this.attr({cx:new b.Number(t),cy:new b.Number(e)}):this.attr({x2:new b.Number(t),y2:new b.Number(e)})}}),b.extend(b.Defs,{gradient:function(t,e){return this.put(new b.Gradient(t)).update(e)}}),b.Stop=b.invent({create:"stop",inherit:b.Element,extend:{update:function(t){return("number"==typeof t||t instanceof b.Number)&&(t={offset:arguments[0],color:arguments[1],opacity:arguments[2]}),null!=t.opacity&&this.attr("stop-opacity",t.opacity),null!=t.color&&this.attr("stop-color",t.color),null!=t.offset&&this.attr("offset",new b.Number(t.offset)),this}}}),b.Pattern=b.invent({create:"pattern",inherit:b.Container,extend:{url:function(){return"url(#"+this.id()+")"},update:function(t){return this.clear(),"function"==typeof t&&t.call(this,this),this},toString:function(){return this.url()},attr:function(t,e,n){return"transform"===t&&(t="patternTransform"),b.Container.prototype.attr.call(this,t,e,n)}},construct:{pattern:function(t,e,n){return this.defs().pattern(t,e,n)}}}),b.extend(b.Defs,{pattern:function(t,e,n){return this.put(new b.Pattern).update(n).attr({x:0,y:0,width:t,height:e,patternUnits:"userSpaceOnUse"})}}),b.Doc=b.invent({create:function(t){b.Element.call(this,t||b.create("svg")),this.namespace()},inherit:b.Container,extend:{isRoot:function(){return!(this.node.parentNode&&this.node.parentNode instanceof t.SVGElement&&"#document"!==this.node.parentNode.nodeName)},doc:function(){return this.isRoot()?this:b.Element.prototype.doc.call(this)},namespace:function(){return this.isRoot()?this.attr({xmlns:b.ns,version:"1.1"}).attr("xmlns:xlink",b.xlink,b.xmlns).attr("xmlns:svgjs",b.svgjs,b.xmlns):this.doc().namespace()},defs:function(){return this.isRoot()?b.adopt(this.node.getElementsByTagName("defs")[0])||this.put(new b.Defs):this.doc().defs()},parent:function(t){return this.isRoot()?"#document"===this.node.parentNode.nodeName?null:this.node.parentNode:b.Element.prototype.parent.call(this,t)},remove:function(){return this.isRoot()?(this.parent()&&this.parent().removeChild(this.node),this):b.Element.prototype.remove.call(this)},clear:function(){for(;this.node.hasChildNodes();)this.node.removeChild(this.node.lastChild);return this}},construct:{nested:function(){return this.put(new b.Doc)}}}),b.Shape=b.invent({create:function(t){b.Element.call(this,t)},inherit:b.Element}),b.Bare=b.invent({create:function(t,e){if(b.Element.call(this,b.create(t)),e)for(var n in e.prototype)"function"==typeof e.prototype[n]&&(this[n]=e.prototype[n])},inherit:b.Element,extend:{words:function(t){for(;this.node.hasChildNodes();)this.node.removeChild(this.node.lastChild);return this.node.appendChild(e.createTextNode(t)),this}}}),b.extend(b.Parent,{element:function(t,e){return this.put(new b.Bare(t,e))}}),b.Symbol=b.invent({create:"symbol",inherit:b.Container,construct:{symbol:function(){return this.put(new b.Symbol)}}}),b.Use=b.invent({create:"use",inherit:b.Shape,extend:{element:function(t,e){return this.attr("href",(e||"")+"#"+t,b.xlink)}},construct:{use:function(t,e){return this.put(new b.Use).element(t,e)}}}),b.Rect=b.invent({create:"rect",inherit:b.Shape,construct:{rect:function(t,e){return this.put(new b.Rect).size(t,e)}}}),b.Circle=b.invent({create:"circle",inherit:b.Shape,construct:{circle:function(t){return this.put(new b.Circle).rx(new b.Number(t).divide(2)).move(0,0)}}}),b.extend([b.Circle,b.Timeline],{rx:function(t){return this.attr("r",t)},ry:function(t){return this.rx(t)}}),b.Ellipse=b.invent({create:"ellipse",inherit:b.Shape,construct:{ellipse:function(t,e){return this.put(new b.Ellipse).size(t,e).move(0,0)}}}),b.extend([b.Ellipse,b.Rect,b.Timeline],{rx:function(t){return this.attr("rx",t)},ry:function(t){return this.attr("ry",t)}}),b.extend([b.Circle,b.Ellipse],{x:function(t){return null==t?this.cx()-this.rx():this.cx(t+this.rx())},y:function(t){return null==t?this.cy()-this.ry():this.cy(t+this.ry())},cx:function(t){return null==t?this.attr("cx"):this.attr("cx",t)},cy:function(t){return null==t?this.attr("cy"):this.attr("cy",t)},width:function(t){return null==t?2*this.rx():this.rx(new b.Number(t).divide(2))},height:function(t){return null==t?2*this.ry():this.ry(new b.Number(t).divide(2))},size:function(t,e){var n=d(this,t,e);return this.rx(new b.Number(n.width).divide(2)).ry(new b.Number(n.height).divide(2))}}),b.Line=b.invent({create:"line",inherit:b.Shape,extend:{array:function(){return new b.PointArray([[this.attr("x1"),this.attr("y1")],[this.attr("x2"),this.attr("y2")]])},plot:function(t,e,n,i){return null==t?this.array():(t=void 0!==e?{x1:t,y1:e,x2:n,y2:i}:new b.PointArray(t).toLine(),this.attr(t))},move:function(t,e){return this.attr(this.array().move(t,e).toLine())},size:function(t,e){var n=d(this,t,e);return this.attr(this.array().size(n.width,n.height).toLine())}},construct:{line:function(t,e,n,i){return b.Line.prototype.plot.apply(this.put(new b.Line),null!=t?[t,e,n,i]:[0,0,0,0])}}}),b.Polyline=b.invent({create:"polyline",inherit:b.Shape,construct:{polyline:function(t){return this.put(new b.Polyline).plot(t||new b.PointArray)}}}),b.Polygon=b.invent({create:"polygon",inherit:b.Shape,construct:{polygon:function(t){return this.put(new b.Polygon).plot(t||new b.PointArray)}}}),b.extend([b.Polyline,b.Polygon],{array:function(){return this._array||(this._array=new b.PointArray(this.attr("points")))},plot:function(t){return null==t?this.array():this.clear().attr("points","string"==typeof t?t:this._array=new b.PointArray(t))},clear:function(){return delete this._array,this},move:function(t,e){return this.attr("points",this.array().move(t,e))},size:function(t,e){var n=d(this,t,e);return this.attr("points",this.array().size(n.width,n.height))}}),b.extend([b.Line,b.Polyline,b.Polygon],{MorphArray:b.PointArray,x:function(t){return null==t?this.bbox().x:this.move(t,this.bbox().y)},y:function(t){return null==t?this.bbox().y:this.move(this.bbox().x,t)},width:function(t){var e=this.bbox();return null==t?e.width:this.size(t,e.height)},height:function(t){var e=this.bbox();return null==t?e.height:this.size(e.width,t)}}),b.Path=b.invent({create:"path",inherit:b.Shape,extend:{MorphArray:b.PathArray,array:function(){return this._array||(this._array=new b.PathArray(this.attr("d")))},plot:function(t){return null==t?this.array():this.clear().attr("d","string"==typeof t?t:this._array=new b.PathArray(t))},clear:function(){return delete this._array,this},move:function(t,e){return this.attr("d",this.array().move(t,e))},x:function(t){return null==t?this.bbox().x:this.move(t,this.bbox().y)},y:function(t){return null==t?this.bbox().y:this.move(this.bbox().x,t)},size:function(t,e){var n=d(this,t,e);return this.attr("d",this.array().size(n.width,n.height))},width:function(t){return null==t?this.bbox().width:this.size(t,this.bbox().height)},height:function(t){return null==t?this.bbox().height:this.size(this.bbox().width,t)}},construct:{path:function(t){return this.put(new b.Path).plot(t||new b.PathArray)}}}),b.Image=b.invent({create:"image",inherit:b.Shape,extend:{load:function(e,n){if(!e)return this;var i=new t.Image;return b.on(i,"load",function(t){var r=this.parent(b.Pattern);0===this.width()&&0===this.height()&&this.size(i.width,i.height),r instanceof b.Pattern&&0===r.width()&&0===r.height()&&r.size(this.width(),this.height()),"function"==typeof n&&n.call(this,{width:i.width,height:i.height,ratio:i.width/i.height,url:e})},this),b.on(i,"load error",function(){b.off(i)}),this.attr("href",i.src=e,b.xlink)}},construct:{image:function(t,e){return this.put(new b.Image).size(0,0).load(t,e)}}}),b.Text=b.invent({create:function(t){b.Element.call(this,t||b.create("text")),this.dom.leading=new b.Number(1.3),this._rebuild=!0,this._build=!1,this.attr("font-family",b.defaults.attrs["font-family"])},inherit:b.Parent,extend:{x:function(t){return null==t?this.attr("x"):this.attr("x",t)},y:function(t){var e=this.attr("y"),n="number"==typeof e?e-this.bbox().y:0;return null==t?"number"==typeof e?e-n:e:this.attr("y","number"==typeof t?t+n:t)},cx:function(t){return null==t?this.bbox().cx:this.x(t-this.bbox().width/2)},cy:function(t){return null==t?this.bbox().cy:this.y(t-this.bbox().height/2)},text:function(t){if(void 0===t){var e=this.node.childNodes,n=0;t="";for(var i=0,r=e.length;i=0;t--)delete this.memory()[arguments[t]];return this},memory:function(){return this._memory||(this._memory={})}}),b.get=function(t){var n=e.getElementById(y(t)||t);return b.adopt(n)},b.select=function(t,n){return b.utils.map((n||e).querySelectorAll(t),function(t){return b.adopt(t)})},b.$$=function(t,n){return b.utils.map((n||e).querySelectorAll(t),function(t){return b.adopt(t)})},b.$=function(t,n){return b.adopt((n||e).querySelector(t))},b.extend(b.Parent,{select:function(t){return b.select(t,this.node)}});var M="abcdef".split("");return b.Box=b.invent({create:function(t){var e=[0,0,0,0];t="string"==typeof t?t.split(b.regex.delimiter).map(parseFloat):Array.isArray(t)?t:"object"==typeof t?[null!=t.left?t.left:t.x,null!=t.top?t.top:t.y,t.width,t.height]:4===arguments.length?[].slice.call(arguments):e,this.x=t[0],this.y=t[1],this.width=t[2],this.height=t[3],v(this)},extend:{merge:function(t){var e=Math.min(this.x,t.x),n=Math.min(this.y,t.y);return new b.Box(e,n,Math.max(this.x+this.width,t.x+t.width)-e,Math.max(this.y+this.height,t.y+t.height)-n)},transform:function(t){var e=1/0,n=-1/0,i=1/0,r=-1/0;return[new b.Point(this.x,this.y),new b.Point(this.x2,this.y),new b.Point(this.x,this.y2),new b.Point(this.x2,this.y2)].forEach(function(s){s=s.transform(t),e=Math.min(e,s.x),n=Math.max(n,s.x),i=Math.min(i,s.y),r=Math.max(r,s.y)}),new b.Box(e,i,n-e,r-i)},addOffset:function(){return this.x+=t.pageXOffset,this.y+=t.pageYOffset,this},toString:function(){return this.x+" "+this.y+" "+this.width+" "+this.height},morph:function(t,e,n,i){return this.destination=new b.Box(t,e,n,i),this},at:function(t){return this.destination?new b.Box(this.x+(this.destination.x-this.x)*t,this.y+(this.destination.y-this.y)*t,this.width+(this.destination.width-this.width)*t,this.height+(this.destination.height-this.height)*t):this}},parent:b.Element,construct:{bbox:function(){var t;try{if(t=this.node.getBBox(),i(t)&&!r(this.node))throw new Exception("Element not in the dom")}catch(n){try{var e=this.clone(b.parser().svg).show();t=e.node.getBBox(),e.remove()}catch(t){console.warn("Getting a bounding box of this element is not possible")}}return new b.Box(t)},rbox:function(t){try{var e=new b.Box(this.node.getBoundingClientRect());return t?e.transform(t.screenCTM().inverse()):e.addOffset()}catch(t){return new b.Box}}}}),b.extend([b.Doc,b.Symbol,b.Image,b.Pattern,b.Marker,b.ForeignObject,b.View],{viewbox:function(t,e,n,i){return null==t?new b.Box(this.attr("viewBox")):this.attr("viewBox",new b.Box(t,e,n,i))}}),b.parser=function(){var t;return b.parser.nodes.svg.node.parentNode||(t=e.body||e.documentElement,b.parser.nodes.svg.addTo(t)),b.parser.nodes},b.parser.nodes={svg:b().size(2,0).css({opacity:0,position:"absolute",left:"-100%",top:"-100%",overflow:"hidden"})},b.parser.nodes.path=b.parser.nodes.svg.path().node,b.Animator={nextDraw:null,frames:new b.Queue,timeouts:new b.Queue,frameCount:0,timeoutCount:0,timer:t.performance||t.Date,frame:function(t){return b.Animator.frames.push({id:b.Animator.frameCount,run:t}),null===b.Animator.nextDraw&&(b.Animator.nextDraw=requestAnimationFrame(b.Animator._draw)),++b.Animator.frameCount},timeout:function(t,e){e=e||0;var n=b.Animator.timer.now()+e,i=b.Animator.timeoutCount++;return b.Animator.timeouts.push({id:i,run:t,time:n}),null===b.Animator.nextDraw&&(b.Animator.nextDraw=requestAnimationFrame(b.Animator._draw)),i},cancelTimeout:function(t){return b.Animator.timeouts.remove(function(e){return e.id===t})},_draw:function(t){for(var e=null,n=b.Animator.timeouts.last();(e=b.Animator.timeouts.shift())&&(t>e.time?e.run():b.Animator.timeouts.push(e),e!==n););for(var i=(b.Animator.frames.last(),b.Animator.frameCount);b.Animator.frames.first()&&b.Animator.frames.first().id0||b.Animator.frames.length>0?requestAnimationFrame(b.Animator._draw):null}},b}); \ No newline at end of file +/*! svg.js v3.0.0 MIT*/;!function(t,e){"function"==typeof define&&define.amd?define(function(){return e(t,t.document)}):"object"==typeof exports?module.exports=t.document?e(t,t.document):function(t){return e(t,t.document)}:t.SVG=e(t,t.document)}("undefined"!=typeof window?window:this,function(t,e){function n(t,n){if(t instanceof b.Element)return t;if("object"==typeof t)return b.adopt(t);if(null==t)return new b.Doc;if("string"==typeof t&&"<"!==t.charAt(0))return b.adopt(e.querySelector(t));var i=b.create("svg");return i.innerHTML=t,t=b.adopt(i.firstElementChild)}function i(t){return!(t.w||t.h||t.x||t.y)}function r(t){return(e.documentElement.contains||function(t){for(;t.parentNode;)t=t.parentNode;return t===e}).call(e.documentElement,t)}function s(t,e,n,i){return n+i.replace(b.regex.dots," .")}function o(t){for(var e=t.slice(0),n=e.length;n--;)Array.isArray(e[n])&&(e[n]=o(e[n]));return e}function a(t,e){return t instanceof e}function h(t,e){return(t.matches||t.matchesSelector||t.msMatchesSelector||t.mozMatchesSelector||t.webkitMatchesSelector||t.oMatchesSelector).call(t,e)}function u(t){return t.toLowerCase().replace(/-(.)/g,function(t,e){return e.toUpperCase()})}function l(t){return t.charAt(0).toUpperCase()+t.slice(1)}function c(t){return 4===t.length?["#",t.substring(1,2),t.substring(1,2),t.substring(2,3),t.substring(2,3),t.substring(3,4),t.substring(3,4)].join(""):t}function f(t){var e=t.toString(16);return 1===e.length?"0"+e:e}function d(t,e,n){if(null==e||null==n){var i=t.bbox();null==e?e=i.width/i.height*n:null==n&&(n=i.height/i.width*e)}return{width:e,height:n}}function p(t){return{a:t[0],b:t[1],c:t[2],d:t[3],e:t[4],f:t[5]}}function m(t){for(var e=0,n=t.length,i="";e=0;e--)x(t.children[e]);return t.id?b.adopt(t).id(b.eid(t.nodeName)):b.adopt(t)}function v(t){return null==t.x&&(t.x=0,t.y=0,t.width=0,t.height=0),t.w=t.width,t.h=t.height,t.x2=t.x+t.width,t.y2=t.y+t.height,t.cx=t.x+t.width/2,t.cy=t.y+t.height/2,t}function y(t){var e=(t||"").toString().match(b.regex.reference);if(e)return e[1]}function g(t,e,n){return Math.abs(e-t)<(n||1e-6)}function w(t){var e="both"===t.flip||!0===t.flip,n=t.flip&&(e||"x"===t.flip)?-1:1,i=t.flip&&(e||"y"===t.flip)?-1:1,r=t.skew&&t.skew.length?t.skew[0]:isFinite(t.skew)?t.skew:isFinite(t.skewX)?t.skewX:0,s=t.skew&&t.skew.length?t.skew[1]:isFinite(t.skew)?t.skew:isFinite(t.skewY)?t.skewY:0,o=t.scale&&t.scale.length?t.scale[0]*n:isFinite(t.scale)?t.scale*n:isFinite(t.scaleX)?t.scaleX*n:n,a=t.scale&&t.scale.length?t.scale[1]*i:isFinite(t.scale)?t.scale*i:isFinite(t.scaleY)?t.scaleY*i:i,h=t.shear||0,u=t.rotate||0,l=new b.Point(null==t.ox?t.origin:t.ox,t.oy),c=l.x,f=l.y,d=new b.Point(null==t.px?t.position:t.px,t.py,{x:null,y:null}),p=d.x,m=d.y,x=new b.Point(null==t.tx?t.translate:t.tx,t.ty),v=x.x,y=x.y,g=new b.Point(null==t.rx?t.relative:t.rx,t.ry);return{scaleX:o,scaleY:a,skewX:r,skewY:s,shear:h,theta:u,rx:g.x,ry:g.y,tx:v,ty:y,ox:c,oy:f,px:p,py:m}}if(!e.createElementNS||!e.createElementNS("http://www.w3.org/2000/svg","svg").createSVGRect)return{supported:!1};var b=this.SVG=function(t){if(b.supported)return t=n(t)};b.supported=!0,b.ns="http://www.w3.org/2000/svg",b.xmlns="http://www.w3.org/2000/xmlns/",b.xlink="http://www.w3.org/1999/xlink",b.svgjs="http://svgjs.com/svgjs",b.did=1e3,b.eid=function(t){return"Svgjs"+l(t)+b.did++},b.create=function(t){return e.createElementNS(this.ns,t)},b.extend=function(t,e){var n,i;for(t=Array.isArray(t)?t:[t],i=t.length-1;i>=0;i--)if(t[i])for(n in e)t[i].prototype[n]=e[n]},b.invent=function(t){var e="function"==typeof t.create?t.create:function(e){b.Element.call(this,e||b.create(t.create))};return t.inherit&&(e.prototype=new t.inherit,e.prototype.constructor=e),t.extend&&b.extend(e,t.extend),t.construct&&b.extend(t.parent||b.Container,t.construct),e},b.adopt=function(e){if(!e)return null;if(e.instance instanceof b.Element)return e.instance;if(!(e instanceof t.SVGElement))return new b.HtmlNode(e);return"svg"===e.nodeName?new b.Doc(e):"linearGradient"===e.nodeName||"radialGradient"===e.nodeName?new b.Gradient(e):b[l(e.nodeName)]?new(b[l(e.nodeName)])(e):new b.Parent(e)},b.regex={numberAndUnit:/^([+-]?(\d+(\.\d*)?|\.\d+)(e[+-]?\d+)?)([a-z%]*)$/i,hex:/^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i,rgb:/rgb\((\d+),(\d+),(\d+)\)/,reference:/#([a-z0-9\-_]+)/i,transforms:/\)\s*,?\s*/,whitespace:/\s/g,isHex:/^#[a-f0-9]{3,6}$/i,isRgb:/^rgb\(/,isCss:/[^:]+:[^;]+;?/,isBlank:/^(\s+)?$/,isNumber:/^[+-]?(\d+(\.\d*)?|\.\d+)(e[+-]?\d+)?$/i,isPercent:/^-?[\d.]+%$/,isImage:/\.(jpg|jpeg|png|gif|svg)(\?[^=]+.*)?/i,delimiter:/[\s,]+/,hyphen:/([^e])-/gi,pathLetters:/[MLHVCSQTAZ]/gi,isPathLetter:/[MLHVCSQTAZ]/i,numbersWithDots:/((\d?\.\d+(?:e[+-]?\d+)?)((?:\.\d+(?:e[+-]?\d+)?)+))+/gi,dots:/\./g},b.utils={map:function(t,e){var n,i=t.length,r=[];for(n=0;n",delay:0},attrs:{"fill-opacity":1,"stroke-opacity":1,"stroke-width":0,"stroke-linejoin":"miter","stroke-linecap":"butt",fill:"#000000",stroke:"#000000",opacity:1,x:0,y:0,cx:0,cy:0,width:0,height:0,r:0,rx:0,ry:0,offset:0,"stop-opacity":1,"stop-color":"#000000","font-size":16,"font-family":"Helvetica, Arial, sans-serif","text-anchor":"start"}},b.Queue=b.invent({create:function(){this._first=null,this._last=null,this.length=0,this.id=0},extend:{push:function(t){var e={id:this.id++,value:t};this._last?this._last=this._last.next=e:this._last=this._first=e,this.length++},shift:function(){if(0!=this.length){var t=this._first;return this._first=t.next,this._last=--this.length?this._last:null,t.value}},first:function(){return this._first&&this._first.value},last:function(){return this._last&&this._last.value},remove:function(t){for(var e=null,n=this._first;n&&!t(n);)e=n,n=n.next;if(n&&n===this._first&&(this._first=this._first.next),n&&n===this._last&&(this._last=e),n)return--this.length,e&&(e.next=n.next),n.item}}}),b.Color=function(t){var e;this.r=0,this.g=0,this.b=0,t&&("string"==typeof t?b.regex.isRgb.test(t)?(e=b.regex.rgb.exec(t.replace(b.regex.whitespace,"")),this.r=parseInt(e[1]),this.g=parseInt(e[2]),this.b=parseInt(e[3])):b.regex.isHex.test(t)&&(e=b.regex.hex.exec(c(t)),this.r=parseInt(e[1],16),this.g=parseInt(e[2],16),this.b=parseInt(e[3],16)):"object"==typeof t&&(this.r=t.r,this.g=t.g,this.b=t.b))},b.extend(b.Color,{toString:function(){return this.toHex()},toArray:function(){return[this.r,this.g,this.b]},fromArray:function(t){return new b.Color(t[0],t[1],t[2])},toHex:function(){return"#"+f(this.r)+f(this.g)+f(this.b)},toRgb:function(){return"rgb("+[this.r,this.g,this.b].join()+")"},brightness:function(){return this.r/255*.3+this.g/255*.59+this.b/255*.11},morph:function(t){return this.destination=new b.Color(t),this},at:function(t){return this.destination?(t=t<0?0:t>1?1:t,new b.Color({r:~~(this.r+(this.destination.r-this.r)*t),g:~~(this.g+(this.destination.g-this.g)*t),b:~~(this.b+(this.destination.b-this.b)*t)})):this}}),b.Color.test=function(t){return t+="",b.regex.isHex.test(t)||b.regex.isRgb.test(t)},b.Color.isRgb=function(t){return t&&"number"==typeof t.r&&"number"==typeof t.g&&"number"==typeof t.b},b.Color.isColor=function(t){return b.Color.isRgb(t)||b.Color.test(t)},b.Array=function(t,e){t=(t||[]).valueOf(),0===t.length&&e&&(t=e.valueOf()),this.value=this.parse(t)},b.extend(b.Array,{morph:function(t){if(this.destination=this.parse(t),this.value.length!==this.destination.length){for(var e=this.value[this.value.length-1],n=this.destination[this.destination.length-1];this.value.length>this.destination.length;)this.destination.push(n);for(;this.value.length=0;i--)this.value[i]=[this.value[i][0]+t,this.value[i][1]+e];return this},size:function(t,e){var n,i=this.bbox();for(n=this.value.length-1;n>=0;n--)i.width&&(this.value[n][0]=(this.value[n][0]-i.x)*t/i.width+i.x),i.height&&(this.value[n][1]=(this.value[n][1]-i.y)*e/i.height+i.y);return this},bbox:function(){var t=-1/0,e=-1/0,n=1/0,i=1/0;return this.value.forEach(function(r){t=Math.max(r[0],t),e=Math.max(r[1],e),n=Math.min(r[0],n),i=Math.min(r[1],i)}),{x:n,y:i,width:t-n,height:e-i}}});for(var A={M:function(t,e,n){return e.x=n.x=t[0],e.y=n.y=t[1],["M",e.x,e.y]},L:function(t,e){return e.x=t[0],e.y=t[1],["L",t[0],t[1]]},H:function(t,e){return e.x=t[0],["H",t[0]]},V:function(t,e){return e.y=t[0],["V",t[0]]},C:function(t,e){return e.x=t[4],e.y=t[5],["C",t[0],t[1],t[2],t[3],t[4],t[5]]},S:function(t,e){return e.x=t[2],e.y=t[3],["S",t[0],t[1],t[2],t[3]]},Q:function(t,e){return e.x=t[2],e.y=t[3],["Q",t[0],t[1],t[2],t[3]]},T:function(t,e){return e.x=t[0],e.y=t[1],["T",t[0],t[1]]},Z:function(t,e,n){return e.x=n.x,e.y=n.y,["Z"]},A:function(t,e){return e.x=t[5],e.y=t[6],["A",t[0],t[1],t[2],t[3],t[4],t[5],t[6]]}},C="mlhvqtcsaz".split(""),P=0,N=C.length;P=0;r--)i=this.value[r][0],"M"===i||"L"===i||"T"===i?(this.value[r][1]+=t,this.value[r][2]+=e):"H"===i?this.value[r][1]+=t:"V"===i?this.value[r][1]+=e:"C"===i||"S"===i||"Q"===i?(this.value[r][1]+=t,this.value[r][2]+=e,this.value[r][3]+=t,this.value[r][4]+=e,"C"===i&&(this.value[r][5]+=t,this.value[r][6]+=e)):"A"===i&&(this.value[r][6]+=t,this.value[r][7]+=e);return this},size:function(t,e){var n,i,r=this.bbox();for(n=this.value.length-1;n>=0;n--)i=this.value[n][0],"M"===i||"L"===i||"T"===i?(this.value[n][1]=(this.value[n][1]-r.x)*t/r.width+r.x,this.value[n][2]=(this.value[n][2]-r.y)*e/r.height+r.y):"H"===i?this.value[n][1]=(this.value[n][1]-r.x)*t/r.width+r.x:"V"===i?this.value[n][1]=(this.value[n][1]-r.y)*e/r.height+r.y:"C"===i||"S"===i||"Q"===i?(this.value[n][1]=(this.value[n][1]-r.x)*t/r.width+r.x,this.value[n][2]=(this.value[n][2]-r.y)*e/r.height+r.y,this.value[n][3]=(this.value[n][3]-r.x)*t/r.width+r.x,this.value[n][4]=(this.value[n][4]-r.y)*e/r.height+r.y,"C"===i&&(this.value[n][5]=(this.value[n][5]-r.x)*t/r.width+r.x,this.value[n][6]=(this.value[n][6]-r.y)*e/r.height+r.y)):"A"===i&&(this.value[n][1]=this.value[n][1]*t/r.width,this.value[n][2]=this.value[n][2]*e/r.height,this.value[n][6]=(this.value[n][6]-r.x)*t/r.width+r.x,this.value[n][7]=(this.value[n][7]-r.y)*e/r.height+r.y);return this},equalCommands:function(t){var e,n,i;for(t=new b.PathArray(t),i=this.value.length===t.value.length,e=0,n=this.value.length;i&&ea);return i},bbox:function(){return b.parser().path.setAttribute("d",this.toString()),b.parser.nodes.path.getBBox()}}),b.Number=b.invent({create:function(t,e){this.value=0,this.unit=e||"","number"==typeof t?this.value=isNaN(t)?0:isFinite(t)?t:t<0?-3.4e38:3.4e38:"string"==typeof t?(e=t.match(b.regex.numberAndUnit))&&(this.value=parseFloat(e[1]),"%"===e[5]?this.value/=100:"s"===e[5]&&(this.value*=1e3),this.unit=e[5]):t instanceof b.Number&&(this.value=t.valueOf(),this.unit=t.unit)},extend:{toString:function(){return("%"===this.unit?~~(1e8*this.value)/1e6:"s"===this.unit?this.value/1e3:this.value)+this.unit},toJSON:function(){return this.toString()},toArray:function(){return[this.value]},fromArray:function(t){return new b.Number(t[0])},valueOf:function(){return this.value},plus:function(t){return t=new b.Number(t),new b.Number(this+t,this.unit||t.unit)},minus:function(t){return t=new b.Number(t),new b.Number(this-t,this.unit||t.unit)},times:function(t){return t=new b.Number(t),new b.Number(this*t,this.unit||t.unit)},divide:function(t){return t=new b.Number(t),new b.Number(this/t,this.unit||t.unit)},to:function(t){var e=new b.Number(this);return"string"==typeof t&&(e.unit=t),e},morph:function(t){return this.destination=new b.Number(t),t.relative&&(this.destination.value+=this.value),this},at:function(t){return this.destination?new b.Number(this.destination).minus(this).times(t).plus(this):this}}}),b.HtmlNode=b.invent({create:function(t){this.node=t},extend:{add:function(t,e){return t=n(t),t.node!==this.node.children[e]&&this.node.insertBefore(t.node,this.node.children[e]||null),this},put:function(t,e){return this.add(t,e),t}}}),b.Element=b.invent({create:function(t){this.events={},this.dom={},this.node=t,this.node&&(this.type=t.nodeName,this.node.instance=this,this.events=t.events||{},t.hasAttribute("svgjs:data")&&this.setData(JSON.parse(t.getAttribute("svgjs:data"))||{}))},extend:{x:function(t){return this.attr("x",t)},y:function(t){return this.attr("y",t)},cx:function(t){return null==t?this.x()+this.width()/2:this.x(t-this.width()/2)},cy:function(t){return null==t?this.y()+this.height()/2:this.y(t-this.height()/2)},move:function(t,e){return this.x(t).y(e)},center:function(t,e){return this.cx(t).cy(e)},width:function(t){return this.attr("width",t)},height:function(t){return this.attr("height",t)},size:function(t,e){var n=d(this,t,e);return this.width(new b.Number(n.width)).height(new b.Number(n.height))},clone:function(t){this.writeDataToDom();var e=x(this.node.cloneNode(!0));return t?t.add(e):this.after(e),e},remove:function(){return this.parent()&&this.parent().removeElement(this),this},replace:function(t){return this.after(t).remove(),t},addTo:function(t){return n(t).put(this)},putIn:function(t){return n(t).add(this)},id:function(t){return void 0!==t||this.node.id||(this.node.id=b.eid(this.type)),this.attr("id",t)},inside:function(t,e){var n=this.bbox();return t>n.x&&e>n.y&&t=0;--e)this[M[e]]=null!=t[M[e]]?t[M[e]]:n[M[e]]},extend:{clone:function(){return new b.Matrix(this)},transform:function(t){if(null!=t.a){var e=new b.Matrix(t);return this.lmultiply(e)}var n=w(t),i=new b.Matrix(this),r=(new b.Matrix).translate(-n.ox,-n.oy).scale(n.scaleX,n.scaleY).skew(n.skewX,n.skewY).shear(n.shear).rotate(n.theta).translate(n.ox,n.oy).translate(n.rx,n.ry).lmultiply(i);if(isFinite(n.px)||isFinite(n.py)){var s=new b.Point(n.ox-n.rx,n.oy-n.ry).transform(r),o=n.px?n.px-s.x:0,a=n.py?n.py-s.y:0;r=r.translate(o,a)}return r=r.translate(n.tx,n.ty)},compose:function(t){var e=t.scaleX||1,n=t.scaleY||1,i=t.shear||0,r=t.rotate||0,s=t.translateX||0,o=t.translateY||0;return(new b.Matrix).scale(e,n).shear(i).rotate(r).translate(s,o).lmultiply(this)},decompose:function(){var t=this.a,e=this.b,n=this.c,i=this.d,r=this.e,s=this.f,o=t*i-e*n,a=o>0?1:-1,h=a*Math.sqrt(t*t+e*e),u=180/Math.PI*Math.atan2(a*e,a*t),l=(t*n+e*i)/o;return{scaleX:h,scaleY:n*h/(l*t-e)||i*h/(l*e+t),shear:l,rotate:u,translateX:r,translateY:s,a:this.a,b:this.b,c:this.c,d:this.d,e:this.e,f:this.f}},morph:function(t){return this.destination=new b.Matrix(t),this},at:function(t){return this.destination?new b.Matrix({a:this.a+(this.destination.a-this.a)*t,b:this.b+(this.destination.b-this.b)*t,c:this.c+(this.destination.c-this.c)*t,d:this.d+(this.destination.d-this.d)*t,e:this.e+(this.destination.e-this.e)*t,f:this.f+(this.destination.f-this.f)*t}):this},multiply:function(t){var e=this,n=new b.Matrix(t),i=e.a*n.a+e.c*n.b,r=e.b*n.a+e.d*n.b,s=e.a*n.c+e.c*n.d,o=e.b*n.c+e.d*n.d,a=e.e+e.a*n.e+e.c*n.f,h=e.f+e.b*n.e+e.d*n.f;return new b.Matrix(i,r,s,o,a,h)},lmultiply:function(t){return new b.Matrix(t).multiply(this)},inverse:function(){var t=this.a,e=this.b,n=this.c,i=this.d,r=this.e,s=this.f,o=t*i-e*n;if(!o)throw new Error("Cannot invert "+this);var a=i/o,h=-e/o,u=-n/o,l=t/o,c=-(a*r+u*s),f=-(h*r+l*s);return new b.Matrix(a,h,u,l,c,f)},translate:function(t,e){var n=new b.Matrix(this);return n.e+=t||0,n.f+=e||0,n},scale:function(t,e,n,i){1===arguments.length?e=t:3===arguments.length&&(i=n,n=e,e=t);var r=new b.Matrix(t,0,0,e,0,0);return this.around(n,i,r)},rotate:function(t,e,n){t=b.utils.radians(t);var i=new b.Matrix(Math.cos(t),Math.sin(t),-Math.sin(t),Math.cos(t),0,0);return this.around(e,n,i)},flip:function(t,e){return"x"===t?this.scale(-1,1,e,0):"y"===t?this.scale(1,-1,0,e):this.scale(-1,-1,t,e||t)},shear:function(t,e,n){var i=new b.Matrix(1,0,t,1,0,0);return this.around(e,n,i)},skew:function(t,e,n,i){1===arguments.length?e=t:3===arguments.length&&(i=n,n=e,e=t),t=b.utils.radians(t),e=b.utils.radians(e);var r=new b.Matrix(1,Math.tan(e),Math.tan(t),1,0,0);return this.around(n,i,r)},skewX:function(t,e,n){return this.skew(t,0,e,n)},skewY:function(t,e,n){return this.skew(0,t,e,n)},around:function(t,e,n){var i=t||0,r=e||0;return this.translate(-i,-r).lmultiply(n).translate(i,r)},native:function(){for(var t=b.parser.nodes.svg.node.createSVGMatrix(),e=M.length-1;e>=0;e--)t[M[e]]=this[M[e]];return t},equals:function(t){var e=new b.Matrix(t);return g(this.a,e.a)&&g(this.b,e.b)&&g(this.c,e.c)&&g(this.d,e.d)&&g(this.e,e.e)&&g(this.f,e.f)},toString:function(){return"matrix("+this.a+","+this.b+","+this.c+","+this.d+","+this.e+","+this.f+")"},toArray:function(){return[this.a,this.b,this.c,this.d,this.e,this.f]},fromArray:function(t){return new b.Matrix(t)}},parent:b.Element,construct:{ctm:function(){return new b.Matrix(this.node.getCTM())},screenCTM:function(){if(this instanceof b.Doc&&!this.isRoot()){var t=this.rect(1,1),e=t.node.getScreenCTM();return t.remove(),new b.Matrix(e)}return new b.Matrix(this.node.getScreenCTM())}}}),b.Point=b.invent({create:function(t,e,n){var i;n=n||{x:0,y:0},i=Array.isArray(t)?{x:t[0],y:t[1]}:"object"==typeof t?{x:t.x,y:t.y}:{x:t,y:e},this.x=null==i.x?n.x:i.x,this.y=null==i.y?n.y:i.y},extend:{clone:function(){return new b.Point(this)},morph:function(t,e){return this.destination=new b.Point(t,e),this},at:function(t){return this.destination?new b.Point({x:this.x+(this.destination.x-this.x)*t,y:this.y+(this.destination.y-this.y)*t}):this},native:function(){var t=b.parser.nodes.svg.node.createSVGPoint();return t.x=this.x,t.y=this.y,t},transform:function(t){return new b.Point(this.native().matrixTransform(t.native()))}}}),b.extend(b.Element,{point:function(t,e){return new b.Point(t,e).transform(this.screenCTM().inverse())}}),b.extend(b.Element,{attr:function(t,e,n){if(null==t){for(t={},e=this.node.attributes,n=e.length-1;n>=0;n--)t[e[n].nodeName]=b.regex.isNumber.test(e[n].nodeValue)?parseFloat(e[n].nodeValue):e[n].nodeValue;return t}if("object"==typeof t)for(e in t)this.attr(e,t[e]);else if(null===e)this.node.removeAttribute(t);else{if(null==e)return e=this.node.getAttribute(t),null==e?b.defaults.attrs[t]:b.regex.isNumber.test(e)?parseFloat(e):e;"fill"!==t&&"stroke"!==t||(b.regex.isImage.test(e)&&(e=this.doc().defs().image(e)),e instanceof b.Image&&(e=this.doc().defs().pattern(0,0,function(){this.add(e)}))),"number"==typeof e?e=new b.Number(e):b.Color.isColor(e)?e=new b.Color(e):Array.isArray(e)&&(e=new b.Array(e)),"leading"===t?this.leading&&this.leading(e):"string"==typeof n?this.node.setAttributeNS(n,t,e.toString()):this.node.setAttribute(t,e.toString()),!this.rebuild||"font-size"!==t&&"x"!==t||this.rebuild(t,e)}return this}}),b.extend(b.Element,{untransform:function(){return this.attr("transform",null)},matrixify:function(){return(this.attr("transform")||"").split(b.regex.transforms).slice(0,-1).map(function(t){var e=t.trim().split("(");return[e[0],e[1].split(b.regex.delimiter).map(function(t){return parseFloat(t)})]}).reverse().reduce(function(t,e){return"matrix"===e[0]?t.lmultiply(p(e[1])):t[e[0]].apply(t,e[1])},new b.Matrix)},toParent:function(t){if(this===t)return this;var e=this.screenCTM(),n=t.screenCTM().inverse();return this.addTo(t).untransform().transform(n.multiply(e)),this},toDoc:function(){return this.toParent(this.doc())}}),b.extend(b.Element,{transform:function(t,e){if(null==t||"string"==typeof t){var n=new b.Matrix(this).decompose();return n[t]||n}if("string"==typeof t.origin||null==t.origin&&null==t.ox&&null==t.oy){var i=this.bbox(),r="string"==typeof t.origin?t.origin.toLowerCase().trim():"center",s=i.height,o=i.width,a=i.x,h=i.y;t.ox=r.includes("left")?a:r.includes("right")?a+o:a+o/2,t.oy=r.includes("top")?h:r.includes("bottom")?h+s:h+s/2,t.origin=null}var u=!0===e?this:e||!1,l=new b.Matrix(u).transform(t);return this.attr("transform",l)}}),b.extend(b.Timeline,{transform:function(t,e,n){}}),b.extend(b.Element,{css:function(t,e){var n,i,r={};if(0===arguments.length)return this.node.style.cssText.split(/\s*;\s*/).filter(function(t){return!!t.length}).forEach(function(t){n=t.split(/\s*:\s*/),r[n[0]]=n[1]}),r;if(arguments.length<2){if(Array.isArray(t)){for(i=t.length;i--;)r[u(t[i])]=this.node.style[u(t[i])];return r}if("string"==typeof t)return this.node.style[u(t)];if("object"==typeof t)for(i in t)this.node.style[u(i)]=null==t[i]||b.regex.isBlank.test(t[i])?"":t[i]}return 2===arguments.length&&(this.node.style[u(t)]=null==e||b.regex.isBlank.test(e)?"":e),this}}),b.Parent=b.invent({create:function(t){b.Element.call(this,t)},inherit:b.Element,extend:{children:function(){return b.utils.map(this.node.children,function(t){return b.adopt(t)})},add:function(t,e){return t=n(t),t.node!==this.node.children[e]&&this.node.insertBefore(t.node,this.node.children[e]||null),this},put:function(t,e){return this.add(t,e),t.instance||t},has:function(t){return this.index(t)>=0},index:function(t){return[].slice.call(this.node.children).indexOf(t.node)},get:function(t){return b.adopt(this.node.children[t])},first:function(){return this.get(0)},last:function(){return this.get(this.node.children.length-1)},each:function(t,e){var n,i,r=this.children();for(n=0,i=r.length;n0&&this.parent().removeElement(this).add(this,t-1),this},front:function(){var t=this.parent();return t.node.appendChild(this.node),t instanceof b.Doc&&t.node.appendChild(t.defs().node),this},back:function(){return this.position()>0&&this.parent().removeElement(this).add(this,0),this},before:function(t){t.remove();var e=this.position();return this.parent().add(t,e),this},after:function(t){t.remove();var e=this.position();return this.parent().add(t,e+1),this}}),b.Mask=b.invent({create:"mask",inherit:b.Container,extend:{remove:function(){return this.targets().forEach(function(t){t.unmask()}),b.Element.prototype.remove.call(this)},targets:function(){return b.select('svg [mask*="'+this.id()+'"]')}},construct:{mask:function(){return this.defs().put(new b.Mask)}}}),b.extend(b.Element,{maskWith:function(t){var e=t instanceof b.Mask?t:this.parent().mask().add(t);return this.attr("mask",'url("#'+e.id()+'")')},unmask:function(){return this.attr("mask",null)},masker:function(){return this.reference("mask")}}),b.ClipPath=b.invent({create:"clipPath",inherit:b.Container,extend:{remove:function(){return this.targets().forEach(function(t){t.unclip()}),b.Element.prototype.remove.call(this)},targets:function(){return b.select('svg [clip-path*="'+this.id()+'"]')}},construct:{clip:function(){return this.defs().put(new b.ClipPath)}}}),b.extend(b.Element,{clipWith:function(t){var e=t instanceof b.ClipPath?t:this.parent().clip().add(t);return this.attr("clip-path",'url("#'+e.id()+'")')},unclip:function(){return this.attr("clip-path",null)}, +clipper:function(){return this.reference("clip-path")}}),b.Gradient=b.invent({create:function(t){b.Element.call(this,"object"==typeof t?t:b.create(t+"Gradient"))},inherit:b.Container,extend:{stop:function(t,e,n){return this.put(new b.Stop).update(t,e,n)},update:function(t){return this.clear(),"function"==typeof t&&t.call(this,this),this},url:function(){return"url(#"+this.id()+")"},toString:function(){return this.url()},attr:function(t,e,n){return"transform"===t&&(t="gradientTransform"),b.Container.prototype.attr.call(this,t,e,n)}},construct:{gradient:function(t,e){return this.defs().gradient(t,e)}}}),b.extend([b.Gradient,b.Timeline],{from:function(t,e){return"radialGradient"===(this._target||this).type?this.attr({fx:new b.Number(t),fy:new b.Number(e)}):this.attr({x1:new b.Number(t),y1:new b.Number(e)})},to:function(t,e){return"radialGradient"===(this._target||this).type?this.attr({cx:new b.Number(t),cy:new b.Number(e)}):this.attr({x2:new b.Number(t),y2:new b.Number(e)})}}),b.extend(b.Defs,{gradient:function(t,e){return this.put(new b.Gradient(t)).update(e)}}),b.Stop=b.invent({create:"stop",inherit:b.Element,extend:{update:function(t){return("number"==typeof t||t instanceof b.Number)&&(t={offset:arguments[0],color:arguments[1],opacity:arguments[2]}),null!=t.opacity&&this.attr("stop-opacity",t.opacity),null!=t.color&&this.attr("stop-color",t.color),null!=t.offset&&this.attr("offset",new b.Number(t.offset)),this}}}),b.Pattern=b.invent({create:"pattern",inherit:b.Container,extend:{url:function(){return"url(#"+this.id()+")"},update:function(t){return this.clear(),"function"==typeof t&&t.call(this,this),this},toString:function(){return this.url()},attr:function(t,e,n){return"transform"===t&&(t="patternTransform"),b.Container.prototype.attr.call(this,t,e,n)}},construct:{pattern:function(t,e,n){return this.defs().pattern(t,e,n)}}}),b.extend(b.Defs,{pattern:function(t,e,n){return this.put(new b.Pattern).update(n).attr({x:0,y:0,width:t,height:e,patternUnits:"userSpaceOnUse"})}}),b.Doc=b.invent({create:function(t){b.Element.call(this,t||b.create("svg")),this.namespace()},inherit:b.Container,extend:{isRoot:function(){return!(this.node.parentNode&&this.node.parentNode instanceof t.SVGElement&&"#document"!==this.node.parentNode.nodeName)},doc:function(){return this.isRoot()?this:b.Element.prototype.doc.call(this)},namespace:function(){return this.isRoot()?this.attr({xmlns:b.ns,version:"1.1"}).attr("xmlns:xlink",b.xlink,b.xmlns).attr("xmlns:svgjs",b.svgjs,b.xmlns):this.doc().namespace()},defs:function(){return this.isRoot()?b.adopt(this.node.getElementsByTagName("defs")[0])||this.put(new b.Defs):this.doc().defs()},parent:function(t){return this.isRoot()?"#document"===this.node.parentNode.nodeName?null:this.node.parentNode:b.Element.prototype.parent.call(this,t)},remove:function(){return this.isRoot()?(this.parent()&&this.parent().removeChild(this.node),this):b.Element.prototype.remove.call(this)},clear:function(){for(;this.node.hasChildNodes();)this.node.removeChild(this.node.lastChild);return this}},construct:{nested:function(){return this.put(new b.Doc)}}}),b.Shape=b.invent({create:function(t){b.Element.call(this,t)},inherit:b.Element}),b.Bare=b.invent({create:function(t,e){if(b.Element.call(this,b.create(t)),e)for(var n in e.prototype)"function"==typeof e.prototype[n]&&(this[n]=e.prototype[n])},inherit:b.Element,extend:{words:function(t){for(;this.node.hasChildNodes();)this.node.removeChild(this.node.lastChild);return this.node.appendChild(e.createTextNode(t)),this}}}),b.extend(b.Parent,{element:function(t,e){return this.put(new b.Bare(t,e))}}),b.Symbol=b.invent({create:"symbol",inherit:b.Container,construct:{symbol:function(){return this.put(new b.Symbol)}}}),b.Use=b.invent({create:"use",inherit:b.Shape,extend:{element:function(t,e){return this.attr("href",(e||"")+"#"+t,b.xlink)}},construct:{use:function(t,e){return this.put(new b.Use).element(t,e)}}}),b.Rect=b.invent({create:"rect",inherit:b.Shape,construct:{rect:function(t,e){return this.put(new b.Rect).size(t,e)}}}),b.Circle=b.invent({create:"circle",inherit:b.Shape,construct:{circle:function(t){return this.put(new b.Circle).rx(new b.Number(t).divide(2)).move(0,0)}}}),b.extend([b.Circle,b.Timeline],{rx:function(t){return this.attr("r",t)},ry:function(t){return this.rx(t)}}),b.Ellipse=b.invent({create:"ellipse",inherit:b.Shape,construct:{ellipse:function(t,e){return this.put(new b.Ellipse).size(t,e).move(0,0)}}}),b.extend([b.Ellipse,b.Rect,b.Timeline],{rx:function(t){return this.attr("rx",t)},ry:function(t){return this.attr("ry",t)}}),b.extend([b.Circle,b.Ellipse],{x:function(t){return null==t?this.cx()-this.rx():this.cx(t+this.rx())},y:function(t){return null==t?this.cy()-this.ry():this.cy(t+this.ry())},cx:function(t){return null==t?this.attr("cx"):this.attr("cx",t)},cy:function(t){return null==t?this.attr("cy"):this.attr("cy",t)},width:function(t){return null==t?2*this.rx():this.rx(new b.Number(t).divide(2))},height:function(t){return null==t?2*this.ry():this.ry(new b.Number(t).divide(2))},size:function(t,e){var n=d(this,t,e);return this.rx(new b.Number(n.width).divide(2)).ry(new b.Number(n.height).divide(2))}}),b.Line=b.invent({create:"line",inherit:b.Shape,extend:{array:function(){return new b.PointArray([[this.attr("x1"),this.attr("y1")],[this.attr("x2"),this.attr("y2")]])},plot:function(t,e,n,i){return null==t?this.array():(t=void 0!==e?{x1:t,y1:e,x2:n,y2:i}:new b.PointArray(t).toLine(),this.attr(t))},move:function(t,e){return this.attr(this.array().move(t,e).toLine())},size:function(t,e){var n=d(this,t,e);return this.attr(this.array().size(n.width,n.height).toLine())}},construct:{line:function(t,e,n,i){return b.Line.prototype.plot.apply(this.put(new b.Line),null!=t?[t,e,n,i]:[0,0,0,0])}}}),b.Polyline=b.invent({create:"polyline",inherit:b.Shape,construct:{polyline:function(t){return this.put(new b.Polyline).plot(t||new b.PointArray)}}}),b.Polygon=b.invent({create:"polygon",inherit:b.Shape,construct:{polygon:function(t){return this.put(new b.Polygon).plot(t||new b.PointArray)}}}),b.extend([b.Polyline,b.Polygon],{array:function(){return this._array||(this._array=new b.PointArray(this.attr("points")))},plot:function(t){return null==t?this.array():this.clear().attr("points","string"==typeof t?t:this._array=new b.PointArray(t))},clear:function(){return delete this._array,this},move:function(t,e){return this.attr("points",this.array().move(t,e))},size:function(t,e){var n=d(this,t,e);return this.attr("points",this.array().size(n.width,n.height))}}),b.extend([b.Line,b.Polyline,b.Polygon],{MorphArray:b.PointArray,x:function(t){return null==t?this.bbox().x:this.move(t,this.bbox().y)},y:function(t){return null==t?this.bbox().y:this.move(this.bbox().x,t)},width:function(t){var e=this.bbox();return null==t?e.width:this.size(t,e.height)},height:function(t){var e=this.bbox();return null==t?e.height:this.size(e.width,t)}}),b.Path=b.invent({create:"path",inherit:b.Shape,extend:{MorphArray:b.PathArray,array:function(){return this._array||(this._array=new b.PathArray(this.attr("d")))},plot:function(t){return null==t?this.array():this.clear().attr("d","string"==typeof t?t:this._array=new b.PathArray(t))},clear:function(){return delete this._array,this},move:function(t,e){return this.attr("d",this.array().move(t,e))},x:function(t){return null==t?this.bbox().x:this.move(t,this.bbox().y)},y:function(t){return null==t?this.bbox().y:this.move(this.bbox().x,t)},size:function(t,e){var n=d(this,t,e);return this.attr("d",this.array().size(n.width,n.height))},width:function(t){return null==t?this.bbox().width:this.size(t,this.bbox().height)},height:function(t){return null==t?this.bbox().height:this.size(this.bbox().width,t)}},construct:{path:function(t){return this.put(new b.Path).plot(t||new b.PathArray)}}}),b.Image=b.invent({create:"image",inherit:b.Shape,extend:{load:function(e,n){if(!e)return this;var i=new t.Image;return b.on(i,"load",function(t){var r=this.parent(b.Pattern);0===this.width()&&0===this.height()&&this.size(i.width,i.height),r instanceof b.Pattern&&0===r.width()&&0===r.height()&&r.size(this.width(),this.height()),"function"==typeof n&&n.call(this,{width:i.width,height:i.height,ratio:i.width/i.height,url:e})},this),b.on(i,"load error",function(){b.off(i)}),this.attr("href",i.src=e,b.xlink)}},construct:{image:function(t,e){return this.put(new b.Image).size(0,0).load(t,e)}}}),b.Text=b.invent({create:function(t){b.Element.call(this,t||b.create("text")),this.dom.leading=new b.Number(1.3),this._rebuild=!0,this._build=!1,this.attr("font-family",b.defaults.attrs["font-family"])},inherit:b.Parent,extend:{x:function(t){return null==t?this.attr("x"):this.attr("x",t)},y:function(t){var e=this.attr("y"),n="number"==typeof e?e-this.bbox().y:0;return null==t?"number"==typeof e?e-n:e:this.attr("y","number"==typeof t?t+n:t)},cx:function(t){return null==t?this.bbox().cx:this.x(t-this.bbox().width/2)},cy:function(t){return null==t?this.bbox().cy:this.y(t-this.bbox().height/2)},text:function(t){if(void 0===t){var e=this.node.childNodes,n=0;t="";for(var i=0,r=e.length;i=0;t--)delete this.memory()[arguments[t]];return this},memory:function(){return this._memory||(this._memory={})}}),b.get=function(t){var n=e.getElementById(y(t)||t);return b.adopt(n)},b.select=function(t,n){return b.utils.map((n||e).querySelectorAll(t),function(t){return b.adopt(t)})},b.$$=function(t,n){return b.utils.map((n||e).querySelectorAll(t),function(t){return b.adopt(t)})},b.$=function(t,n){return b.adopt((n||e).querySelector(t))},b.extend(b.Parent,{select:function(t){return b.select(t,this.node)}});var M="abcdef".split("");return b.Box=b.invent({create:function(t){var e=[0,0,0,0];t="string"==typeof t?t.split(b.regex.delimiter).map(parseFloat):Array.isArray(t)?t:"object"==typeof t?[null!=t.left?t.left:t.x,null!=t.top?t.top:t.y,t.width,t.height]:4===arguments.length?[].slice.call(arguments):e,this.x=t[0],this.y=t[1],this.width=t[2],this.height=t[3],v(this)},extend:{merge:function(t){var e=Math.min(this.x,t.x),n=Math.min(this.y,t.y);return new b.Box(e,n,Math.max(this.x+this.width,t.x+t.width)-e,Math.max(this.y+this.height,t.y+t.height)-n)},transform:function(t){var e=1/0,n=-1/0,i=1/0,r=-1/0;return[new b.Point(this.x,this.y),new b.Point(this.x2,this.y),new b.Point(this.x,this.y2),new b.Point(this.x2,this.y2)].forEach(function(s){s=s.transform(t),e=Math.min(e,s.x),n=Math.max(n,s.x),i=Math.min(i,s.y),r=Math.max(r,s.y)}),new b.Box(e,i,n-e,r-i)},addOffset:function(){return this.x+=t.pageXOffset,this.y+=t.pageYOffset,this},toString:function(){return this.x+" "+this.y+" "+this.width+" "+this.height},morph:function(t,e,n,i){return this.destination=new b.Box(t,e,n,i),this},at:function(t){return this.destination?new b.Box(this.x+(this.destination.x-this.x)*t,this.y+(this.destination.y-this.y)*t,this.width+(this.destination.width-this.width)*t,this.height+(this.destination.height-this.height)*t):this}},parent:b.Element,construct:{bbox:function(){var t;try{if(t=this.node.getBBox(),i(t)&&!r(this.node))throw new Exception("Element not in the dom")}catch(n){try{var e=this.clone(b.parser().svg).show();t=e.node.getBBox(),e.remove()}catch(t){console.warn("Getting a bounding box of this element is not possible")}}return new b.Box(t)},rbox:function(t){try{var e=new b.Box(this.node.getBoundingClientRect());return t?e.transform(t.screenCTM().inverse()):e.addOffset()}catch(t){return new b.Box}}}}),b.extend([b.Doc,b.Symbol,b.Image,b.Pattern,b.Marker,b.ForeignObject,b.View],{viewbox:function(t,e,n,i){return null==t?new b.Box(this.attr("viewBox")):this.attr("viewBox",new b.Box(t,e,n,i))}}),b.parser=function(){var t;return b.parser.nodes.svg.node.parentNode||(t=e.body||e.documentElement,b.parser.nodes.svg.addTo(t)),b.parser.nodes},b.parser.nodes={svg:b().size(2,0).css({opacity:0,position:"absolute",left:"-100%",top:"-100%",overflow:"hidden"})},b.parser.nodes.path=b.parser.nodes.svg.path().node,b.Animator={nextDraw:null,frames:new b.Queue,timeouts:new b.Queue,frameCount:0,timeoutCount:0,timer:t.performance||t.Date,frame:function(t){return b.Animator.frames.push({id:b.Animator.frameCount,run:t}),null===b.Animator.nextDraw&&(b.Animator.nextDraw=requestAnimationFrame(b.Animator._draw)),++b.Animator.frameCount},timeout:function(t,e){e=e||0;var n=b.Animator.timer.now()+e,i=b.Animator.timeoutCount++;return b.Animator.timeouts.push({id:i,run:t,time:n}),null===b.Animator.nextDraw&&(b.Animator.nextDraw=requestAnimationFrame(b.Animator._draw)),i},cancelTimeout:function(t){return b.Animator.timeouts.remove(function(e){return e.id===t})},_draw:function(t){for(var e=null,n=b.Animator.timeouts.last();(e=b.Animator.timeouts.shift())&&(t>e.time?e.run():b.Animator.timeouts.push(e),e!==n););for(var i=(b.Animator.frames.last(),b.Animator.frameCount);b.Animator.frames.first()&&b.Animator.frames.first().id0||b.Animator.frames.length>0?requestAnimationFrame(b.Animator._draw):null}},b}); \ No newline at end of file diff --git a/gulpfile.js b/gulpfile.js index 5883790..7f22df3 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -82,6 +82,13 @@ var parts = [ 'src/boxes.js', 'src/parser.js', 'src/animator.js', + // + // + // + // TODO: ADD THESE + // + // 'src/runner.js' + // 'src/timeline.js' ] gulp.task('clean', function () { diff --git a/src/array.js b/src/array.js index ca51d8e..ebfe1b2 100644 --- a/src/array.js +++ b/src/array.js @@ -58,6 +58,12 @@ SVG.extend(SVG.Array, { return new SVG.Array(array) }, + toArray: function () { + return this.value + }, + fromArray: function (a) { + return new SVG.Array(a) + }, // Convert array to string toString: function () { return this.value.join(' ') diff --git a/src/default.js b/src/default.js index f33083c..b4f41b2 100644 --- a/src/default.js +++ b/src/default.js @@ -1,4 +1,6 @@ +SVG.void = function () {} + SVG.defaults = { // Default animation values diff --git a/src/morph.js b/src/morph.js index 930eacb..f735d4b 100644 --- a/src/morph.js +++ b/src/morph.js @@ -1,3 +1,4 @@ + SVG.Morphable = SVG.invent({ create: function (controller) { // FIXME: the default controller does not know about easing diff --git a/src/runner.js b/src/runner.js new file mode 100644 index 0000000..589e841 --- /dev/null +++ b/src/runner.js @@ -0,0 +1,118 @@ + +function Runner (timeline, duration) { + + // We store a reference to the function to run and the timeline to use + this.transforms = [] + this.functions = [] + this.done = false + + // We copy the current values from the timeline because they can change + this._duration = duration || timeline._duration + this._timeline = timeline + this._last = 0 + + // Store the state of the runner + this._active = true + this._tag = null + this._time = 0 +} + +// The runner gets the time from the timeline +Runner.prototype = { + + add: function (initFn, runFn, alwaysInitialise) { + this.functions.push({ + alwaysInitialise: alwaysInitialise || false, + initialiser: (initFn || SVG.void).bind(this._timeline), + runner: (runFn || SVG.void).bind(this._timeline), + finished: false, + }) + return this + }, + + tag: function (name) { + this._tag = name + return this + }, + + active: function (activated) { + this._active = activated + return this + }, + + time: function (time) { + let dt = time - this._time + this.step(dt) + return this + }, + + step: function (dt) { + + /** + * If we don't have a duration, we are in declarative mode + */ + if (this._duration == null) { + // TODO: Deal with declarative runs + } + + /** + * If we have a duration, we just run if we are in range + */ + + // Increment the time and read out the parameters + var duration = this._duration + var time = this._time + this._time += dt + + // Work out if we are in range to run the function + var timeInside = 0 < time && time < duration + var position = time / duration + var finished = time > duration + + // If we are on the rising edge, initialise everything, otherwise, + // initialise only what needs to be initialised on the rising edge + var justStarted = this._last < 0 && time > 0 + var justFinished = this._last < duration && finished + this._initialise(position, justStarted) + this._last = time + + // If we haven't started yet or we are over the time, just exit + if(!timeInside && !justFinished) return finished + + // Run the runner and store the last time it was run + this._run(finished ? 1 : position) + + // Work out if we are finished + return finished + }, + + _initialise: function (position, all) { + for (var i = 0, len = this.functions.length; i < len ; ++i) { + var current = this.functions[i] + var always = current.alwaysInitialise + var running = !current.finished + if ((always || all) && running) { + current.initialiser(position) + } + } + }, + + _run: function (position) { + + // Run all of the functions directly + var allfinished = false + for (var i = 0, len = this.functions.length; i < len ; ++i) { + + // Get the current function to run + var current = this.functions[i] + + // Run the function if its not finished, we keep track of the finished + // flag for the sake of declarative functions + current.finished = current.finished || (current.runner(position) === true) + allfinished = allfinished && current.finished + } + + // We report when all of the constructors are finished + return allfinished + }, +} diff --git a/src/timeline.js b/src/timeline.js index 3213f20..d6fab66 100644 --- a/src/timeline.js +++ b/src/timeline.js @@ -7,105 +7,8 @@ SVG.easing = { '<': function (pos) { return -Math.cos(pos * Math.PI / 2) + 1 } } - -function Runner (timeline) { - - // We store a reference to the function to run and the timeline to use - this.transforms = [] - this.functions = [] - this.done = false - - // We copy the current values from the timeline because they can change - this._timeline = timeline - this._startTime = timeline._startTime - this._duration = timeline._duration - this._last = 0 - this._active = false - - // TODO: Think about looping and how to use the runner -} - -// The runner gets the time from the timeline -Runner.prototype = { - - add: function (initFn, runFn, alwaysInitialise) { - this.functions.push({ - initialised: false, - alwaysInitialise: alwaysInitialise || false, - initialiser: initFn, - runner: runFn, - }) - }, - - step: function (time) { - - // If it is time to do something, act now. - var end = this._startTime + this._duration - var running = (this._startTime < time && time < end) || !this._duration - - // If its time run the animation, we do so - var allDone = time > end - if (running && !this._timeline._paused) { - - // Get the current position for the current animation - // TODO: Deal with looping - var position = (time - this._startTime) / this._duration - - // We run all of the functions - for (var i = 0, len = this.functions.length; i < len ; ++i) { - - // Get the current queued item - var current = this.functions[i] - - // Work out if we need to initialise, and do so if we do - var initialise = current.alwaysInitialise || !current.initialised - if (initialise) { - current.initialiser.call(this._timeline, position) - current.initialised = true - } - - // Run the function required - // TODO: Figure out what declarative needs that it doesn't have - var stillRunning = current.runner.call(this._timeline, position) - if (stillRunning) { - allDone = false - } - } - } - - // Tell the caller whether this animation is finished - return allDone - }, - - initialise: function (time) { - - }, - - run: function (type, time) { - - // We run all of the functions - var stillGoing = false - for (var i = 0, len = this.functions.length; i < len ; ++i) { - - // Get the current queued item - var current = this.functions[i][type] - - // Work out if we need to initialise, and do so if we do - var initialise = current.alwaysInitialise - if (initialise) { - current.initialiser(position) - } - - // Run the functions - - } - }, -} - - var time = window.performance || window.Date - SVG.Timeline = SVG.invent({ // Construct a new timeline on the given element @@ -115,8 +18,10 @@ SVG.Timeline = SVG.invent({ this._element = element // Store the timing variables - this._startTime = time.now() - this._duration = SVG.defaults.timeline.duration + this._startTime = 0 + this._lastPaused = null + this._timeAbsoluteOffset = 0 + this._duration = 0 this._ease = SVG.defaults.timeline.ease this._speed = 1.0 @@ -138,7 +43,7 @@ SVG.Timeline = SVG.invent({ extend: { - animate (duration, delay, now) { + animate (duration, delay, nowOrAbsolute) { // Clear the controller and the looping parameters this._controller = duration instanceof Function ? duration : null @@ -148,18 +53,25 @@ SVG.Timeline = SVG.invent({ // If we have an object we are declaring imperative animations if (typeof duration === 'object') { - delay = duration.delay - now = duration.now duration = duration.duration + delay = duration.delay + nowOrAbsolute = duration.absolute || duration.now } - // We start the next animation after the old one is complete - this._startTime = ( now ? time.now() : this._startTime ) + (delay || 0) + // The start time for the next animation can either be given explicitly, + // derived from the current timeline time or it can be relative to the + // last start time to chain animations direclty + var absoluteStartTime = typeof nowOrAbsolute === 'number' ? nowOrAbsolute + : nowOrAbsolute ? this._time + : this._startTime + this._duration + + // We start the next animation after the delay required + this._startTime = absoluteStartTime + (delay || 0) this._duration = duration instanceof Function ? null : (duration || SVG.defaults.timeline.duration) // Make a new runner to queue all of the animations onto - this._runner = new Runner(this) + this._runner = new Runner(this).time(this._time - this._startTime) this._runners.push(this._runner) // Step the animation @@ -178,29 +90,44 @@ SVG.Timeline = SVG.invent({ }, ease (fn) { - this._ease = SVG.easing[fn || SVG.defaults.timeline.ease] || fn + var ease = VG.easing[fn || SVG.defaults.timeline.ease] || fn + this._controller = function (from, to, pos) { + return from + (to - from) * ease(pos) + } return this }, - play () { + tag (name) { + this._runner.tag(name) + }, + + play (tags) { + + // If we were paused, shift the time by the time that we were paused + if (this._paused) { + this._timeAbsoluteOffset -= (time.now() - this._lastPaused) + } + + // Now make sure we are not paused and continue the animation this._paused = false this._continue() return this }, - pause () { + pause (tags) { + this._lastPaused = time.now() + this._nextFrame = null this._paused = true return this }, - stop () { - this.pause() - + stop (tags) { // Cancel the next animation frame for this object + this._nextFrame = null }, - finish (all=true) { + finish (tags) { }, @@ -209,7 +136,7 @@ SVG.Timeline = SVG.invent({ }, seek (dt) { - + this._timeAbsoluteOffset += dt }, persist (dtOrForever) { @@ -225,16 +152,40 @@ SVG.Timeline = SVG.invent({ return this }, + // Queue a function to run after some time + after (time, fn) { + + // If the user passes no time, just queue it + if (fn == null) { + return this.queue(time) + } + + // Otherwise make a runner to run this one time later + var runner = new Runner(this, 0).time(-time).add(fn) + this._runners.push(runner) + return this + }, + + during (fn) { + return this.queue(null, fn) + }, + + // Map an absolute time to a timeline time + timelineTime (time) { + return time + this._timeAbsoluteOffset + }, + _step (time) { // If we are paused, just exit if (this._paused) return - // Get the time delta from the last time + // Get the time delta from the last time and update the time // TODO: Deal with window.blur window.focus to pause animations // HACK: We keep the time below 16ms to avoid driving declarative crazy - var dt = this._speed * ((time - this._time) || 16) + var dt = this._speed * ((time - this._lastTime) || 16) dt = dt < 100 ? dt : 16 // If we missed alot of time, ignore + this._lastTime = time this._time += dt // Run all of the runners directly @@ -243,15 +194,19 @@ SVG.Timeline = SVG.invent({ // Get and run the current runner and figure out if its done running var runner = this._runners[i] - var finished = runner.step(this._time) + var finished = runner.step(dt) // If this runner is still going, signal that we need another animation // frame, otherwise, remove the completed runner if (!finished) { runnersLeft = true - } else { - this._runners.splice(i--, 1) } + + // TODO: Check if a runner is still healthy, and if it is, run it. + // Once a runner is complete it expires. If it has been expired for + // more than the persist amount of time, splice it out; by default + // this expiry date is zero + // this._runners.splice(i--, 1) } // Get the next animation frame to keep the simulation going @@ -369,12 +324,11 @@ SVG.extend(SVG.Timeline, { // - Note I(1) = T // 4. Now you get the delta matrix as a result: D = I * inv(M) - transform: function (o, relative, affine) { + transform: function (transforms, relative, affine) { affine = transforms.affine || affine || !!transform.a relative = transforms.relative || relative || false var morpher - var el = this.target() /** The default of relative is false @@ -400,7 +354,7 @@ SVG.extend(SVG.Timeline, { .controller(this.controller) return this.queue(function() {}, function (pos) { - el.pushRightTransform(new Matrix(morpher.at(pos))) + this.pushRightTransform(new Matrix(morpher.at(pos))) }) } @@ -426,7 +380,7 @@ SVG.extend(SVG.Timeline, { this.queue(function() {}, function (pos) { // 2. on every frame: pull the current state of all previous transforms (M - m can change) - var curr = el.currentTransform() + var curr = this.currentTransform() if(!relative) morpher.from(curr) // 3. Find the interpolated matrix I(pos) = m + pos * (t - m) @@ -437,9 +391,9 @@ SVG.extend(SVG.Timeline, { if(!relative) { // 4. Now you get the delta matrix as a result: D = I * inv(M) var delta = matrix.multiply(curr.inverse()) - el.pushLeftTransform(delta) + this.pushLeftTransform(delta) } else { - el.pushRightTransform(matrix) + this.pushRightTransform(matrix) } }) diff --git a/src/transform.js b/src/transform.js index ecb5fc4..758f40a 100644 --- a/src/transform.js +++ b/src/transform.js @@ -52,9 +52,6 @@ SVG.extend(SVG.Element, { // Add transformations transform: function (o, relative) { - // Get the bounding box of the element with no transformations applied - var bbox = this.bbox() - // Act as a getter if no object was passed if (o == null || typeof o === 'string') { var decomposed = new SVG.Matrix(this).decompose() @@ -64,6 +61,10 @@ SVG.extend(SVG.Element, { } else if (typeof o.origin === 'string' || (o.origin == null && o.ox == null && o.oy == null) ) { + + // Get the bounding box of the element with no transformations applied + var bbox = this.bbox() + // Get the bounding box and string to use in our calculations var string = typeof o.origin === 'string' ? o.origin.toLowerCase().trim() diff --git a/useCases.md b/useCases.md new file mode 100644 index 0000000..eb21068 --- /dev/null +++ b/useCases.md @@ -0,0 +1,44 @@ + + +# Tagged Animations + +The user can tag and control the runner for any animation + +```js + +var animation = element + .loop(300, true) + .tag('first') + .rotate(360) + .translate(50) + .animate(300, 200) + .tag('second') + .scale(3) + +animation.finish('first') +animation.pause('first') +animation.stop('first') +animation.play('first') + +``` + + +# Absolute Timeline Times + +The user can specify their time which is relative to the timelines time. + + +```js + +var animation = element + .animate(2000).move(200, 200) + +// after 1000 ms +animation.animate(1000, 0, 500).scale(2) + +``` + +This block of code would: +- Spend the first 1000ms moving the element +- At this time, it will snap the scale to 1.5 (halfway to 2) +- After this time, the scale and the move should go together -- 2.39.5