From b80991280a1d6adac5cc4925168eebfdf2e6d5ae Mon Sep 17 00:00:00 2001 From: Ziver Koc Date: Tue, 10 Jan 2023 02:58:36 +0100 Subject: [PATCH] Downgraded svg.js as many plugins did not support v3.x Now rooms can be moved and resized. --- hal-core/resources/web/css/svg.select.css | 24 +- .../resources/web/js/lib/svg.draggable.js | 370 +- .../resources/web/js/lib/svg.draggable.min.js | 7 +- hal-core/resources/web/js/lib/svg.js | 12396 +++++++--------- hal-core/resources/web/js/lib/svg.min.js | 16 +- .../web/js/lib/svg.resize.LICENSE.txt | 21 + hal-core/resources/web/js/lib/svg.resize.js | 511 + .../resources/web/js/lib/svg.resize.min.js | 1 + hal-core/resources/web/js/lib/svg.select.js | 2 +- hal-core/resources/web/js/map.js | 100 +- hal-core/resources/web/map.tmpl | 5 +- .../src/se/hal/page/api/MapApiEndpoint.java | 39 +- 12 files changed, 6266 insertions(+), 7226 deletions(-) create mode 100644 hal-core/resources/web/js/lib/svg.resize.LICENSE.txt create mode 100644 hal-core/resources/web/js/lib/svg.resize.js create mode 100644 hal-core/resources/web/js/lib/svg.resize.min.js diff --git a/hal-core/resources/web/css/svg.select.css b/hal-core/resources/web/css/svg.select.css index 2b32e1cd..18888ab3 100644 --- a/hal-core/resources/web/css/svg.select.css +++ b/hal-core/resources/web/css/svg.select.css @@ -1,24 +1,4 @@ - - -.selection_border { - fill: none; - stroke: black; - stroke-width:1; -} - -.selection_handle_shear { - fill: white; - stroke: black; - stroke-width:1; -} - -.selection_handle_rot { - fill: white; - stroke: black; - stroke-width:1; -} - -/* .svg_select_points_lt{ +.svg_select_points_lt{ cursor: nw-resize; } .svg_select_points_rt{ @@ -61,4 +41,4 @@ stroke-opacity:0.8; fill-opacity:0.1; pointer-events:none; /* This ons is needed if you want to deselect or drag the shape*/ -/*} */ \ No newline at end of file +} \ No newline at end of file diff --git a/hal-core/resources/web/js/lib/svg.draggable.js b/hal-core/resources/web/js/lib/svg.draggable.js index 247dc839..dda3807f 100644 --- a/hal-core/resources/web/js/lib/svg.draggable.js +++ b/hal-core/resources/web/js/lib/svg.draggable.js @@ -1,177 +1,235 @@ -/*! -* @svgdotjs/svg.draggable.js - An extension for svg.js which allows to drag elements with your mouse -* @version 3.0.2 +/*! svg.draggable.js - v2.2.2 - 2019-01-08 * https://github.com/svgdotjs/svg.draggable.js -* -* @copyright Wout Fierens -* @license MIT -* -* BUILT: Tue Feb 19 2019 17:12:16 GMT+0100 (GMT+01:00) -*/; -(function (svg_js) { - 'use strict'; +* Copyright (c) 2019 Wout Fierens; Licensed MIT */ +;(function() { - function _classCallCheck(instance, Constructor) { - if (!(instance instanceof Constructor)) { - throw new TypeError("Cannot call a class as a function"); - } + // creates handler, saves it + function DragHandler(el){ + el.remember('_draggable', this) + this.el = el } - function _defineProperties(target, props) { - for (var i = 0; i < props.length; i++) { - var descriptor = props[i]; - descriptor.enumerable = descriptor.enumerable || false; - descriptor.configurable = true; - if ("value" in descriptor) descriptor.writable = true; - Object.defineProperty(target, descriptor.key, descriptor); - } + + // Sets new parameter, starts dragging + DragHandler.prototype.init = function(constraint, val){ + var _this = this + this.constraint = constraint + this.value = val + this.el.on('mousedown.drag', function(e){ _this.start(e) }) + this.el.on('touchstart.drag', function(e){ _this.start(e) }) } - function _createClass(Constructor, protoProps, staticProps) { - if (protoProps) _defineProperties(Constructor.prototype, protoProps); - if (staticProps) _defineProperties(Constructor, staticProps); - return Constructor; + // transforms one point from screen to user coords + DragHandler.prototype.transformPoint = function(event, offset){ + event = event || window.event + var touches = event.changedTouches && event.changedTouches[0] || event + this.p.x = touches.clientX - (offset || 0) + this.p.y = touches.clientY + return this.p.matrixTransform(this.m) } - var getCoordsFromEvent = function getCoordsFromEvent(ev) { - if (ev.changedTouches) { - ev = ev.changedTouches[0]; + // gets elements bounding box with special handling of groups, nested and use + DragHandler.prototype.getBBox = function(){ + + var box = this.el.bbox() + + if(this.el instanceof SVG.Nested) box = this.el.rbox() + + if (this.el instanceof SVG.G || this.el instanceof SVG.Use || this.el instanceof SVG.Nested) { + box.x = this.el.x() + box.y = this.el.y() } - return { - x: ev.clientX, - y: ev.clientY - }; - }; // Creates handler, saves it + return box + } + // start dragging + DragHandler.prototype.start = function(e){ - var DragHandler = - /*#__PURE__*/ - function () { - function DragHandler(el) { - _classCallCheck(this, DragHandler); - - el.remember('_draggable', this); - this.el = el; - this.drag = this.drag.bind(this); - this.startDrag = this.startDrag.bind(this); - this.endDrag = this.endDrag.bind(this); - } // Enables or disabled drag based on input - - - _createClass(DragHandler, [{ - key: "init", - value: function init(enabled) { - if (enabled) { - this.el.on('mousedown.drag', this.startDrag); - this.el.on('touchstart.drag', this.startDrag); - } else { - this.el.off('mousedown.drag'); - this.el.off('touchstart.drag'); - } - } // Start dragging - - }, { - key: "startDrag", - value: function startDrag(ev) { - var isMouse = !ev.type.indexOf('mouse'); // Check for left button - - if (isMouse && (ev.which || ev.buttons) !== 1) { - return; - } // Fire beforedrag event - - - if (this.el.dispatch('beforedrag', { - event: ev, - handler: this - }).defaultPrevented) { - return; - } // Prevent browser drag behavior as soon as possible - - - ev.preventDefault(); // Prevent propagation to a parent that might also have dragging enabled - - ev.stopPropagation(); // Make sure that start events are unbound so that one element - // is only dragged by one input only - - this.init(false); - this.box = this.el.bbox(); - this.lastClick = this.el.point(getCoordsFromEvent(ev)); // We consider the drag done, when a touch is canceled, too - - var eventMove = (isMouse ? 'mousemove' : 'touchmove') + '.drag'; - var eventEnd = (isMouse ? 'mouseup' : 'touchcancel.drag touchend') + '.drag'; // Bind drag and end events to window - - svg_js.on(window, eventMove, this.drag); - svg_js.on(window, eventEnd, this.endDrag); // Fire dragstart event - - this.el.fire('dragstart', { - event: ev, - handler: this, - box: this.box - }); - } // While dragging - - }, { - key: "drag", - value: function drag(ev) { - var box = this.box, - lastClick = this.lastClick; - var currentClick = this.el.point(getCoordsFromEvent(ev)); - var x = box.x + (currentClick.x - lastClick.x); - var y = box.y + (currentClick.y - lastClick.y); - var newBox = new svg_js.Box(x, y, box.w, box.h); - if (this.el.dispatch('dragmove', { - event: ev, - handler: this, - box: newBox - }).defaultPrevented) return; - this.move(x, y); - return newBox; + // check for left button + if(e.type == 'click'|| e.type == 'mousedown' || e.type == 'mousemove'){ + if((e.which || e.buttons) != 1){ + return } - }, { - key: "move", - value: function move(x, y) { - // Svg elements bbox depends on their content even though they have - // x, y, width and height - strange! - // Thats why we handle them the same as groups - if (this.el.type === 'svg') { - svg_js.G.prototype.move.call(this.el, x, y); - } else { - this.el.move(x, y); + } + + var _this = this + + // fire beforedrag event + this.el.fire('beforedrag', { event: e, handler: this }) + if(this.el.event().defaultPrevented) return; + + // prevent browser drag behavior as soon as possible + e.preventDefault(); + + // prevent propagation to a parent that might also have dragging enabled + e.stopPropagation(); + + // search for parent on the fly to make sure we can call + // draggable() even when element is not in the dom currently + this.parent = this.parent || this.el.parent(SVG.Nested) || this.el.parent(SVG.Doc) + this.p = this.parent.node.createSVGPoint() + + // save current transformation matrix + this.m = this.el.node.getScreenCTM().inverse() + + var box = this.getBBox() + + var anchorOffset; + + // fix text-anchor in text-element (#37) + if(this.el instanceof SVG.Text){ + anchorOffset = this.el.node.getComputedTextLength(); + + switch(this.el.attr('text-anchor')){ + case 'middle': + anchorOffset /= 2; + break + case 'start': + anchorOffset = 0; + break; + } + } + + this.startPoints = { + // We take absolute coordinates since we are just using a delta here + point: this.transformPoint(e, anchorOffset), + box: box, + transform: this.el.transform() + } + + // add drag and end events to window + SVG.on(window, 'mousemove.drag', function(e){ _this.drag(e) }) + SVG.on(window, 'touchmove.drag', function(e){ _this.drag(e) }) + SVG.on(window, 'mouseup.drag', function(e){ _this.end(e) }) + SVG.on(window, 'touchend.drag', function(e){ _this.end(e) }) + + // fire dragstart event + this.el.fire('dragstart', {event: e, p: this.startPoints.point, m: this.m, handler: this}) + } + + // while dragging + DragHandler.prototype.drag = function(e){ + + var box = this.getBBox() + , p = this.transformPoint(e) + , x = this.startPoints.box.x + p.x - this.startPoints.point.x + , y = this.startPoints.box.y + p.y - this.startPoints.point.y + , c = this.constraint + , gx = p.x - this.startPoints.point.x + , gy = p.y - this.startPoints.point.y + + this.el.fire('dragmove', { + event: e + , p: p + , m: this.m + , handler: this + }) + + if(this.el.event().defaultPrevented) return p + + // move the element to its new position, if possible by constraint + if (typeof c == 'function') { + + var coord = c.call(this.el, x, y, this.m) + + // bool, just show us if movement is allowed or not + if (typeof coord == 'boolean') { + coord = { + x: coord, + y: coord } } - }, { - key: "endDrag", - value: function endDrag(ev) { - // final drag - var box = this.drag(ev); // fire dragend event - this.el.fire('dragend', { - event: ev, - handler: this, - box: box - }); // unbind events - - svg_js.off(window, 'mousemove.drag'); - svg_js.off(window, 'touchmove.drag'); - svg_js.off(window, 'mouseup.drag'); - svg_js.off(window, 'touchend.drag'); // Rebind initial Events - - this.init(true); + // if true, we just move. If !false its a number and we move it there + if (coord.x === true) { + this.el.x(x) + } else if (coord.x !== false) { + this.el.x(coord.x) } - }]); - return DragHandler; - }(); + if (coord.y === true) { + this.el.y(y) + } else if (coord.y !== false) { + this.el.y(coord.y) + } - svg_js.extend(svg_js.Element, { - draggable: function draggable() { - var enable = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : true; - var dragHandler = this.remember('_draggable') || new DragHandler(this); - dragHandler.init(enable); - return this; + } else if (typeof c == 'object') { + + // keep element within constrained box + if (c.minX != null && x < c.minX) { + x = c.minX + gx = x - this.startPoints.box.x + } else if (c.maxX != null && x > c.maxX - box.width) { + x = c.maxX - box.width + gx = x - this.startPoints.box.x + } if (c.minY != null && y < c.minY) { + y = c.minY + gy = y - this.startPoints.box.y + } else if (c.maxY != null && y > c.maxY - box.height) { + y = c.maxY - box.height + gy = y - this.startPoints.box.y + } + + if (c.snapToGrid != null) { + x = x - (x % c.snapToGrid) + y = y - (y % c.snapToGrid) + gx = gx - (gx % c.snapToGrid) + gy = gy - (gy % c.snapToGrid) + } + + if(this.el instanceof SVG.G) + this.el.matrix(this.startPoints.transform).transform({x:gx, y: gy}, true) + else + this.el.move(x, y) } - }); -}(SVG)); -//# sourceMappingURL=svg.draggable.js.map + // so we can use it in the end-method, too + return p + } + + DragHandler.prototype.end = function(e){ + + // final drag + var p = this.drag(e); + + // fire dragend event + this.el.fire('dragend', { event: e, p: p, m: this.m, handler: this }) + + // unbind events + SVG.off(window, 'mousemove.drag') + SVG.off(window, 'touchmove.drag') + SVG.off(window, 'mouseup.drag') + SVG.off(window, 'touchend.drag') + + } + + SVG.extend(SVG.Element, { + // Make element draggable + // Constraint might be an object (as described in readme.md) or a function in the form "function (x, y)" that gets called before every move. + // The function can return a boolean or an object of the form {x, y}, to which the element will be moved. "False" skips moving, true moves to raw x, y. + draggable: function(value, constraint) { + + // Check the parameters and reassign if needed + if (typeof value == 'function' || typeof value == 'object') { + constraint = value + value = true + } + + var dragHandler = this.remember('_draggable') || new DragHandler(this) + + // When no parameter is given, value is true + value = typeof value === 'undefined' ? true : value + + if(value) dragHandler.init(constraint || {}, value) + else { + this.off('mousedown.drag') + this.off('touchstart.drag') + } + + return this + } + + }) + +}).call(this); \ No newline at end of file diff --git a/hal-core/resources/web/js/lib/svg.draggable.min.js b/hal-core/resources/web/js/lib/svg.draggable.min.js index 67c0a8b6..8381fccd 100644 --- a/hal-core/resources/web/js/lib/svg.draggable.min.js +++ b/hal-core/resources/web/js/lib/svg.draggable.min.js @@ -1,3 +1,4 @@ -/*! @svgdotjs/svg.draggable.js v3.0.2 MIT*/; -!function(s){"use strict";function a(t,e){for(var i=0;if.maxX-b.width&&(d=f.maxX-b.width,g=d-this.startPoints.box.x),null!=f.minY&&ef.maxY-b.height&&(e=f.maxY-b.height,h=e-this.startPoints.box.y),null!=f.snapToGrid&&(d-=d%f.snapToGrid,e-=e%f.snapToGrid,g-=g%f.snapToGrid,h-=h%f.snapToGrid),this.el instanceof SVG.G?this.el.matrix(this.startPoints.transform).transform({x:g,y:h},!0):this.el.move(d,e));return c},a.prototype.end=function(a){var b=this.drag(a);this.el.fire("dragend",{event:a,p:b,m:this.m,handler:this}),SVG.off(window,"mousemove.drag"),SVG.off(window,"touchmove.drag"),SVG.off(window,"mouseup.drag"),SVG.off(window,"touchend.drag")},SVG.extend(SVG.Element,{draggable:function(b,c){("function"==typeof b||"object"==typeof b)&&(c=b,b=!0);var d=this.remember("_draggable")||new a(this);return b="undefined"==typeof b?!0:b,b?d.init(c||{},b):(this.off("mousedown.drag"),this.off("touchstart.drag")),this}})}).call(this); \ No newline at end of file diff --git a/hal-core/resources/web/js/lib/svg.js b/hal-core/resources/web/js/lib/svg.js index 733feaac..e1f97edc 100644 --- a/hal-core/resources/web/js/lib/svg.js +++ b/hal-core/resources/web/js/lib/svg.js @@ -1,7167 +1,5601 @@ /*! -* @svgdotjs/svg.js - A lightweight library for manipulating and animating SVG. -* @version 3.1.2 -* https://svgjs.dev/ +* svg.js - A lightweight library for manipulating and animating SVG. +* @version 2.7.1 +* https://svgdotjs.github.io/ * * @copyright Wout Fierens * @license MIT * -* BUILT: Wed Jan 26 2022 23:19:07 GMT+0100 (Mitteleuropäische Normalzeit) +* BUILT: Fri Nov 30 2018 10:01:55 GMT+0100 (GMT+01:00) */; -var SVG = (function () { - 'use strict'; - - const methods$1 = {}; - const names = []; - function registerMethods(name, m) { - if (Array.isArray(name)) { - for (const _name of name) { - registerMethods(_name, m); - } - - return; - } - - if (typeof name === 'object') { - for (const _name in name) { - registerMethods(_name, name[_name]); - } - - return; - } - - addMethodNames(Object.getOwnPropertyNames(m)); - methods$1[name] = Object.assign(methods$1[name] || {}, m); - } - function getMethodsFor(name) { - return methods$1[name] || {}; - } - function getMethodNames() { - return [...new Set(names)]; - } - function addMethodNames(_names) { - names.push(..._names); - } - - // Map function - function map(array, block) { - let i; - const il = array.length; - const result = []; - - for (i = 0; i < il; i++) { - result.push(block(array[i])); - } - - return result; - } // Filter function - - function filter(array, block) { - let i; - const il = array.length; - const result = []; - - for (i = 0; i < il; i++) { - if (block(array[i])) { - result.push(array[i]); - } - } - - return result; - } // Degrees to radians - - function radians(d) { - return d % 360 * Math.PI / 180; - } // Radians to degrees - - function degrees(r) { - return r * 180 / Math.PI % 360; - } // Convert dash-separated-string to camelCase - - function camelCase(s) { - return s.toLowerCase().replace(/-(.)/g, function (m, g) { - return g.toUpperCase(); - }); - } // Convert camel cased string to dash separated - - function unCamelCase(s) { - return s.replace(/([A-Z])/g, function (m, g) { - return '-' + g.toLowerCase(); - }); - } // Capitalize first letter of a string - - function capitalize(s) { - return s.charAt(0).toUpperCase() + s.slice(1); - } // Calculate proportional width and height values when necessary - - function proportionalSize(element, width, height, box) { - if (width == null || height == null) { - box = 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 - }; - } - /** - * This function adds support for string origins. - * It searches for an origin in o.origin o.ox and o.originX. - * This way, origin: {x: 'center', y: 50} can be passed as well as ox: 'center', oy: 50 - **/ - - function getOrigin(o, element) { - const origin = o.origin; // First check if origin is in ox or originX - - let ox = o.ox != null ? o.ox : o.originX != null ? o.originX : 'center'; - let oy = o.oy != null ? o.oy : o.originY != null ? o.originY : 'center'; // Then check if origin was used and overwrite in that case - - if (origin != null) { - [ox, oy] = Array.isArray(origin) ? origin : typeof origin === 'object' ? [origin.x, origin.y] : [origin, origin]; - } // Make sure to only call bbox when actually needed - - - const condX = typeof ox === 'string'; - const condY = typeof oy === 'string'; - - if (condX || condY) { - const { - height, - width, - x, - y - } = element.bbox(); // And only overwrite if string was passed for this specific axis - - if (condX) { - ox = ox.includes('left') ? x : ox.includes('right') ? x + width : x + width / 2; - } - - if (condY) { - oy = oy.includes('top') ? y : oy.includes('bottom') ? y + height : y + height / 2; - } - } // Return the origin as it is if it wasn't a string - - - return [ox, oy]; - } - - var utils = { - __proto__: null, - map: map, - filter: filter, - radians: radians, - degrees: degrees, - camelCase: camelCase, - unCamelCase: unCamelCase, - capitalize: capitalize, - proportionalSize: proportionalSize, - getOrigin: getOrigin - }; - - // Default namespaces - const svg = 'http://www.w3.org/2000/svg'; - const html = 'http://www.w3.org/1999/xhtml'; - const xmlns = 'http://www.w3.org/2000/xmlns/'; - const xlink = 'http://www.w3.org/1999/xlink'; - const svgjs = 'http://svgjs.dev/svgjs'; - - var namespaces = { - __proto__: null, - svg: svg, - html: html, - xmlns: xmlns, - xlink: xlink, - svgjs: svgjs - }; - - const globals = { - window: typeof window === 'undefined' ? null : window, - document: typeof document === 'undefined' ? null : document - }; - function registerWindow(win = null, doc = null) { - globals.window = win; - globals.document = doc; - } - const save = {}; - function saveWindow() { - save.window = globals.window; - save.document = globals.document; - } - function restoreWindow() { - globals.window = save.window; - globals.document = save.document; - } - function withWindow(win, fn) { - saveWindow(); - registerWindow(win, win.document); - fn(win, win.document); - restoreWindow(); - } - function getWindow() { - return globals.window; - } - - class Base {// constructor (node/*, {extensions = []} */) { - // // this.tags = [] - // // - // // for (let extension of extensions) { - // // extension.setup.call(this, node) - // // this.tags.push(extension.name) - // // } - // } - } - - const elements = {}; - const root = '___SYMBOL___ROOT___'; // Method for element creation - - function create(name, ns = svg) { - // create element - return globals.document.createElementNS(ns, name); - } - function makeInstance(element, isHTML = false) { - if (element instanceof Base) return element; - - if (typeof element === 'object') { - return adopter(element); - } - - if (element == null) { - return new elements[root](); - } - - if (typeof element === 'string' && element.charAt(0) !== '<') { - return adopter(globals.document.querySelector(element)); - } // Make sure, that HTML elements are created with the correct namespace - - - const wrapper = isHTML ? globals.document.createElement('div') : create('svg'); - wrapper.innerHTML = element; // We can use firstChild here because we know, - // that the first char is < and thus an element - - element = adopter(wrapper.firstChild); // make sure, that element doesnt have its wrapper attached - - wrapper.removeChild(wrapper.firstChild); - return element; - } - function nodeOrNew(name, node) { - return node && node.ownerDocument && node instanceof node.ownerDocument.defaultView.Node ? node : create(name); - } // Adopt existing svg elements - - function adopt(node) { - // check for presence of node - if (!node) return null; // make sure a node isn't already adopted - - if (node.instance instanceof Base) return node.instance; - - if (node.nodeName === '#document-fragment') { - return new elements.Fragment(node); - } // initialize variables - - - let className = capitalize(node.nodeName || 'Dom'); // Make sure that gradients are adopted correctly - - if (className === 'LinearGradient' || className === 'RadialGradient') { - className = 'Gradient'; // Fallback to Dom if element is not known - } else if (!elements[className]) { - className = 'Dom'; - } - - return new elements[className](node); - } - let adopter = adopt; - function mockAdopt(mock = adopt) { - adopter = mock; - } - function register(element, name = element.name, asRoot = false) { - elements[name] = element; - if (asRoot) elements[root] = element; - addMethodNames(Object.getOwnPropertyNames(element.prototype)); - return element; - } - function getClass(name) { - return elements[name]; - } // Element id sequence - - let did = 1000; // Get next named element id - - function eid(name) { - return 'Svgjs' + capitalize(name) + did++; - } // Deep new id assignment - - function assignNewId(node) { - // do the same for SVG child nodes as well - for (let i = node.children.length - 1; i >= 0; i--) { - assignNewId(node.children[i]); - } - - if (node.id) { - node.id = eid(node.nodeName); - return node; - } - - return node; - } // Method for extending objects - - function extend(modules, methods) { - let key, i; - modules = Array.isArray(modules) ? modules : [modules]; - - for (i = modules.length - 1; i >= 0; i--) { - for (key in methods) { - modules[i].prototype[key] = methods[key]; - } - } - } - function wrapWithAttrCheck(fn) { - return function (...args) { - const o = args[args.length - 1]; - - if (o && o.constructor === Object && !(o instanceof Array)) { - return fn.apply(this, args.slice(0, -1)).attr(o); - } else { - return fn.apply(this, args); - } - }; - } - - function siblings() { - return this.parent().children(); - } // Get the current position siblings - - function position() { - return this.parent().index(this); - } // Get the next element (will return null if there is none) - - function next() { - return this.siblings()[this.position() + 1]; - } // Get the next element (will return null if there is none) - - function prev() { - return this.siblings()[this.position() - 1]; - } // Send given element one step forward - - function forward() { - const i = this.position(); - const p = this.parent(); // move node one step forward - - p.add(this.remove(), i + 1); - return this; - } // Send given element one step backward - - function backward() { - const i = this.position(); - const p = this.parent(); - p.add(this.remove(), i ? i - 1 : 0); - return this; - } // Send given element all the way to the front - - function front() { - const p = this.parent(); // Move node forward - - p.add(this.remove()); - return this; - } // Send given element all the way to the back - - function back() { - const p = this.parent(); // Move node back - - p.add(this.remove(), 0); - return this; - } // Inserts a given element before the targeted element - - function before(element) { - element = makeInstance(element); - element.remove(); - const i = this.position(); - this.parent().add(element, i); - return this; - } // Inserts a given element after the targeted element - - function after(element) { - element = makeInstance(element); - element.remove(); - const i = this.position(); - this.parent().add(element, i + 1); - return this; - } - function insertBefore(element) { - element = makeInstance(element); - element.before(this); - return this; - } - function insertAfter(element) { - element = makeInstance(element); - element.after(this); - return this; - } - registerMethods('Dom', { - siblings, - position, - next, - prev, - forward, - backward, - front, - back, - before, - after, - insertBefore, - insertAfter - }); - - // Parse unit value - const numberAndUnit = /^([+-]?(\d+(\.\d*)?|\.\d+)(e[+-]?\d+)?)([a-z%]*)$/i; // Parse hex value - - const hex = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i; // Parse rgb value - - const rgb = /rgb\((\d+),(\d+),(\d+)\)/; // Parse reference id - - const reference = /(#[a-z_][a-z0-9\-_]*)/i; // splits a transformation chain - - const transforms = /\)\s*,?\s*/; // Whitespace - - const whitespace = /\s/g; // Test hex value - - const isHex = /^#[a-f0-9]{3}$|^#[a-f0-9]{6}$/i; // Test rgb value - - const isRgb = /^rgb\(/; // Test for blank string - - const isBlank = /^(\s+)?$/; // Test for numeric string - - const isNumber = /^[+-]?(\d+(\.\d*)?|\.\d+)(e[+-]?\d+)?$/i; // Test for image url - - const isImage = /\.(jpg|jpeg|png|gif|svg)(\?[^=]+.*)?/i; // split at whitespace and comma - - const delimiter = /[\s,]+/; // Test for path letter - - const isPathLetter = /[MLHVCSQTAZ]/i; - - var regex = { - __proto__: null, - numberAndUnit: numberAndUnit, - hex: hex, - rgb: rgb, - reference: reference, - transforms: transforms, - whitespace: whitespace, - isHex: isHex, - isRgb: isRgb, - isBlank: isBlank, - isNumber: isNumber, - isImage: isImage, - delimiter: delimiter, - isPathLetter: isPathLetter - }; - - function classes() { - const attr = this.attr('class'); - return attr == null ? [] : attr.trim().split(delimiter); - } // Return true if class exists on the node, false otherwise - - function hasClass(name) { - return this.classes().indexOf(name) !== -1; - } // Add class to the node - - function addClass(name) { - if (!this.hasClass(name)) { - const array = this.classes(); - array.push(name); - this.attr('class', array.join(' ')); - } - - return this; - } // Remove class from the node - - function removeClass(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 - - function toggleClass(name) { - return this.hasClass(name) ? this.removeClass(name) : this.addClass(name); - } - registerMethods('Dom', { - classes, - hasClass, - addClass, - removeClass, - toggleClass - }); - - function css(style, val) { - const ret = {}; - - 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) { - const t = el.split(/\s*:\s*/); - ret[t[0]] = t[1]; - }); - return ret; - } - - if (arguments.length < 2) { - // get style properties as array - if (Array.isArray(style)) { - for (const name of style) { - const cased = camelCase(name); - ret[name] = this.node.style[cased]; - } - - return ret; - } // get style for property - - - if (typeof style === 'string') { - return this.node.style[camelCase(style)]; - } // set styles in object - - - if (typeof style === 'object') { - for (const name in style) { - // set empty string if null/undefined/'' was given - this.node.style[camelCase(name)] = style[name] == null || isBlank.test(style[name]) ? '' : style[name]; - } - } - } // set style for property - - - if (arguments.length === 2) { - this.node.style[camelCase(style)] = val == null || isBlank.test(val) ? '' : val; - } - - return this; - } // Show element - - function show() { - return this.css('display', ''); - } // Hide element - - function hide() { - return this.css('display', 'none'); - } // Is element visible? - - function visible() { - return this.css('display') !== 'none'; - } - registerMethods('Dom', { - css, - show, - hide, - visible - }); - - function data(a, v, r) { - if (a == null) { - // get an object of attributes - return this.data(map(filter(this.node.attributes, el => el.nodeName.indexOf('data-') === 0), el => el.nodeName.slice(5))); - } else if (a instanceof Array) { - const data = {}; - - for (const key of a) { - data[key] = this.data(key); - } - - return data; - } else 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; - } - registerMethods('Dom', { - data - }); - - function remember(k, v) { - // remember every item in an object individually - if (typeof arguments[0] === 'object') { - for (const 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 - - function forget() { - if (arguments.length === 0) { - this._memory = {}; - } else { - for (let i = arguments.length - 1; i >= 0; i--) { - delete this.memory()[arguments[i]]; - } - } - - return this; - } // This triggers creation of a new hidden class which is not performant - // However, this function is not rarely used so it will not happen frequently - // Return local memory object - - function memory() { - return this._memory = this._memory || {}; - } - registerMethods('Dom', { - remember, - forget, - memory - }); - - function sixDigitHex(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; - } - - function componentHex(component) { - const integer = Math.round(component); - const bounded = Math.max(0, Math.min(255, integer)); - const hex = bounded.toString(16); - return hex.length === 1 ? '0' + hex : hex; - } - - function is(object, space) { - for (let i = space.length; i--;) { - if (object[space[i]] == null) { - return false; - } - } - - return true; - } - - function getParameters(a, b) { - const params = is(a, 'rgb') ? { - _a: a.r, - _b: a.g, - _c: a.b, - _d: 0, - space: 'rgb' - } : is(a, 'xyz') ? { - _a: a.x, - _b: a.y, - _c: a.z, - _d: 0, - space: 'xyz' - } : is(a, 'hsl') ? { - _a: a.h, - _b: a.s, - _c: a.l, - _d: 0, - space: 'hsl' - } : is(a, 'lab') ? { - _a: a.l, - _b: a.a, - _c: a.b, - _d: 0, - space: 'lab' - } : is(a, 'lch') ? { - _a: a.l, - _b: a.c, - _c: a.h, - _d: 0, - space: 'lch' - } : is(a, 'cmyk') ? { - _a: a.c, - _b: a.m, - _c: a.y, - _d: a.k, - space: 'cmyk' - } : { - _a: 0, - _b: 0, - _c: 0, - space: 'rgb' - }; - params.space = b || params.space; - return params; - } - - function cieSpace(space) { - if (space === 'lab' || space === 'xyz' || space === 'lch') { - return true; - } else { - return false; - } - } - - function hueToRgb(p, q, t) { - if (t < 0) t += 1; - if (t > 1) t -= 1; - if (t < 1 / 6) return p + (q - p) * 6 * t; - if (t < 1 / 2) return q; - if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6; - return p; - } - - class Color { - constructor(...inputs) { - this.init(...inputs); - } // Test if given value is a color - - - static isColor(color) { - return color && (color instanceof Color || this.isRgb(color) || this.test(color)); - } // Test if given value is an rgb object - - - static isRgb(color) { - return color && typeof color.r === 'number' && typeof color.g === 'number' && typeof color.b === 'number'; - } - /* - Generating random colors - */ - - - static random(mode = 'vibrant', t, u) { - // Get the math modules - const { - random, - round, - sin, - PI: pi - } = Math; // Run the correct generator - - if (mode === 'vibrant') { - const l = (81 - 57) * random() + 57; - const c = (83 - 45) * random() + 45; - const h = 360 * random(); - const color = new Color(l, c, h, 'lch'); - return color; - } else if (mode === 'sine') { - t = t == null ? random() : t; - const r = round(80 * sin(2 * pi * t / 0.5 + 0.01) + 150); - const g = round(50 * sin(2 * pi * t / 0.5 + 4.6) + 200); - const b = round(100 * sin(2 * pi * t / 0.5 + 2.3) + 150); - const color = new Color(r, g, b); - return color; - } else if (mode === 'pastel') { - const l = (94 - 86) * random() + 86; - const c = (26 - 9) * random() + 9; - const h = 360 * random(); - const color = new Color(l, c, h, 'lch'); - return color; - } else if (mode === 'dark') { - const l = 10 + 10 * random(); - const c = (125 - 75) * random() + 86; - const h = 360 * random(); - const color = new Color(l, c, h, 'lch'); - return color; - } else if (mode === 'rgb') { - const r = 255 * random(); - const g = 255 * random(); - const b = 255 * random(); - const color = new Color(r, g, b); - return color; - } else if (mode === 'lab') { - const l = 100 * random(); - const a = 256 * random() - 128; - const b = 256 * random() - 128; - const color = new Color(l, a, b, 'lab'); - return color; - } else if (mode === 'grey') { - const grey = 255 * random(); - const color = new Color(grey, grey, grey); - return color; - } else { - throw new Error('Unsupported random color mode'); - } - } // Test if given value is a color string - - - static test(color) { - return typeof color === 'string' && (isHex.test(color) || isRgb.test(color)); - } - - cmyk() { - // Get the rgb values for the current color - const { - _a, - _b, - _c - } = this.rgb(); - const [r, g, b] = [_a, _b, _c].map(v => v / 255); // Get the cmyk values in an unbounded format - - const k = Math.min(1 - r, 1 - g, 1 - b); - - if (k === 1) { - // Catch the black case - return new Color(0, 0, 0, 1, 'cmyk'); - } - - const c = (1 - r - k) / (1 - k); - const m = (1 - g - k) / (1 - k); - const y = (1 - b - k) / (1 - k); // Construct the new color - - const color = new Color(c, m, y, k, 'cmyk'); - return color; - } - - hsl() { - // Get the rgb values - const { - _a, - _b, - _c - } = this.rgb(); - const [r, g, b] = [_a, _b, _c].map(v => v / 255); // Find the maximum and minimum values to get the lightness - - const max = Math.max(r, g, b); - const min = Math.min(r, g, b); - const l = (max + min) / 2; // If the r, g, v values are identical then we are grey - - const isGrey = max === min; // Calculate the hue and saturation - - const delta = max - min; - const s = isGrey ? 0 : l > 0.5 ? delta / (2 - max - min) : delta / (max + min); - const h = isGrey ? 0 : max === r ? ((g - b) / delta + (g < b ? 6 : 0)) / 6 : max === g ? ((b - r) / delta + 2) / 6 : max === b ? ((r - g) / delta + 4) / 6 : 0; // Construct and return the new color - - const color = new Color(360 * h, 100 * s, 100 * l, 'hsl'); - return color; - } - - init(a = 0, b = 0, c = 0, d = 0, space = 'rgb') { - // This catches the case when a falsy value is passed like '' - a = !a ? 0 : a; // Reset all values in case the init function is rerun with new color space - - if (this.space) { - for (const component in this.space) { - delete this[this.space[component]]; - } - } - - if (typeof a === 'number') { - // Allow for the case that we don't need d... - space = typeof d === 'string' ? d : space; - d = typeof d === 'string' ? 0 : d; // Assign the values straight to the color - - Object.assign(this, { - _a: a, - _b: b, - _c: c, - _d: d, - space - }); // If the user gave us an array, make the color from it - } else if (a instanceof Array) { - this.space = b || (typeof a[3] === 'string' ? a[3] : a[4]) || 'rgb'; - Object.assign(this, { - _a: a[0], - _b: a[1], - _c: a[2], - _d: a[3] || 0 - }); - } else if (a instanceof Object) { - // Set the object up and assign its values directly - const values = getParameters(a, b); - Object.assign(this, values); - } else if (typeof a === 'string') { - if (isRgb.test(a)) { - const noWhitespace = a.replace(whitespace, ''); - const [_a, _b, _c] = rgb.exec(noWhitespace).slice(1, 4).map(v => parseInt(v)); - Object.assign(this, { - _a, - _b, - _c, - _d: 0, - space: 'rgb' - }); - } else if (isHex.test(a)) { - const hexParse = v => parseInt(v, 16); - - const [, _a, _b, _c] = hex.exec(sixDigitHex(a)).map(hexParse); - Object.assign(this, { - _a, - _b, - _c, - _d: 0, - space: 'rgb' - }); - } else throw Error('Unsupported string format, can\'t construct Color'); - } // Now add the components as a convenience - - - const { - _a, - _b, - _c, - _d - } = this; - const components = this.space === 'rgb' ? { - r: _a, - g: _b, - b: _c - } : this.space === 'xyz' ? { - x: _a, - y: _b, - z: _c - } : this.space === 'hsl' ? { - h: _a, - s: _b, - l: _c - } : this.space === 'lab' ? { - l: _a, - a: _b, - b: _c - } : this.space === 'lch' ? { - l: _a, - c: _b, - h: _c - } : this.space === 'cmyk' ? { - c: _a, - m: _b, - y: _c, - k: _d - } : {}; - Object.assign(this, components); - } - - lab() { - // Get the xyz color - const { - x, - y, - z - } = this.xyz(); // Get the lab components - - const l = 116 * y - 16; - const a = 500 * (x - y); - const b = 200 * (y - z); // Construct and return a new color - - const color = new Color(l, a, b, 'lab'); - return color; - } - - lch() { - // Get the lab color directly - const { - l, - a, - b - } = this.lab(); // Get the chromaticity and the hue using polar coordinates - - const c = Math.sqrt(a ** 2 + b ** 2); - let h = 180 * Math.atan2(b, a) / Math.PI; - - if (h < 0) { - h *= -1; - h = 360 - h; - } // Make a new color and return it - - - const color = new Color(l, c, h, 'lch'); - return color; - } - /* - Conversion Methods - */ - - - rgb() { - if (this.space === 'rgb') { - return this; - } else if (cieSpace(this.space)) { - // Convert to the xyz color space - let { - x, - y, - z - } = this; - - if (this.space === 'lab' || this.space === 'lch') { - // Get the values in the lab space - let { - l, - a, - b - } = this; - - if (this.space === 'lch') { - const { - c, - h - } = this; - const dToR = Math.PI / 180; - a = c * Math.cos(dToR * h); - b = c * Math.sin(dToR * h); - } // Undo the nonlinear function - - - const yL = (l + 16) / 116; - const xL = a / 500 + yL; - const zL = yL - b / 200; // Get the xyz values - - const ct = 16 / 116; - const mx = 0.008856; - const nm = 7.787; - x = 0.95047 * (xL ** 3 > mx ? xL ** 3 : (xL - ct) / nm); - y = 1.00000 * (yL ** 3 > mx ? yL ** 3 : (yL - ct) / nm); - z = 1.08883 * (zL ** 3 > mx ? zL ** 3 : (zL - ct) / nm); - } // Convert xyz to unbounded rgb values - - - const rU = x * 3.2406 + y * -1.5372 + z * -0.4986; - const gU = x * -0.9689 + y * 1.8758 + z * 0.0415; - const bU = x * 0.0557 + y * -0.2040 + z * 1.0570; // Convert the values to true rgb values - - const pow = Math.pow; - const bd = 0.0031308; - const r = rU > bd ? 1.055 * pow(rU, 1 / 2.4) - 0.055 : 12.92 * rU; - const g = gU > bd ? 1.055 * pow(gU, 1 / 2.4) - 0.055 : 12.92 * gU; - const b = bU > bd ? 1.055 * pow(bU, 1 / 2.4) - 0.055 : 12.92 * bU; // Make and return the color - - const color = new Color(255 * r, 255 * g, 255 * b); - return color; - } else if (this.space === 'hsl') { - // https://bgrins.github.io/TinyColor/docs/tinycolor.html - // Get the current hsl values - let { - h, - s, - l - } = this; - h /= 360; - s /= 100; - l /= 100; // If we are grey, then just make the color directly - - if (s === 0) { - l *= 255; - const color = new Color(l, l, l); - return color; - } // TODO I have no idea what this does :D If you figure it out, tell me! - - - const q = l < 0.5 ? l * (1 + s) : l + s - l * s; - const p = 2 * l - q; // Get the rgb values - - const r = 255 * hueToRgb(p, q, h + 1 / 3); - const g = 255 * hueToRgb(p, q, h); - const b = 255 * hueToRgb(p, q, h - 1 / 3); // Make a new color - - const color = new Color(r, g, b); - return color; - } else if (this.space === 'cmyk') { - // https://gist.github.com/felipesabino/5066336 - // Get the normalised cmyk values - const { - c, - m, - y, - k - } = this; // Get the rgb values - - const r = 255 * (1 - Math.min(1, c * (1 - k) + k)); - const g = 255 * (1 - Math.min(1, m * (1 - k) + k)); - const b = 255 * (1 - Math.min(1, y * (1 - k) + k)); // Form the color and return it - - const color = new Color(r, g, b); - return color; - } else { - return this; - } - } - - toArray() { - const { - _a, - _b, - _c, - _d, - space - } = this; - return [_a, _b, _c, _d, space]; - } - - toHex() { - const [r, g, b] = this._clamped().map(componentHex); - - return `#${r}${g}${b}`; - } - - toRgb() { - const [rV, gV, bV] = this._clamped(); - - const string = `rgb(${rV},${gV},${bV})`; - return string; - } - - toString() { - return this.toHex(); - } - - xyz() { - // Normalise the red, green and blue values - const { - _a: r255, - _b: g255, - _c: b255 - } = this.rgb(); - const [r, g, b] = [r255, g255, b255].map(v => v / 255); // Convert to the lab rgb space - - const rL = r > 0.04045 ? Math.pow((r + 0.055) / 1.055, 2.4) : r / 12.92; - const gL = g > 0.04045 ? Math.pow((g + 0.055) / 1.055, 2.4) : g / 12.92; - const bL = b > 0.04045 ? Math.pow((b + 0.055) / 1.055, 2.4) : b / 12.92; // Convert to the xyz color space without bounding the values - - const xU = (rL * 0.4124 + gL * 0.3576 + bL * 0.1805) / 0.95047; - const yU = (rL * 0.2126 + gL * 0.7152 + bL * 0.0722) / 1.00000; - const zU = (rL * 0.0193 + gL * 0.1192 + bL * 0.9505) / 1.08883; // Get the proper xyz values by applying the bounding - - const x = xU > 0.008856 ? Math.pow(xU, 1 / 3) : 7.787 * xU + 16 / 116; - const y = yU > 0.008856 ? Math.pow(yU, 1 / 3) : 7.787 * yU + 16 / 116; - const z = zU > 0.008856 ? Math.pow(zU, 1 / 3) : 7.787 * zU + 16 / 116; // Make and return the color - - const color = new Color(x, y, z, 'xyz'); - return color; - } - /* - Input and Output methods - */ - - - _clamped() { - const { - _a, - _b, - _c - } = this.rgb(); - const { - max, - min, - round - } = Math; - - const format = v => max(0, min(round(v), 255)); - - return [_a, _b, _c].map(format); - } - /* - Constructing colors - */ - - - } - - class Point { - // Initialize - constructor(...args) { - this.init(...args); - } // Clone point - - - clone() { - return new Point(this); - } - - init(x, y) { - const base = { - x: 0, - y: 0 - }; // ensure source as object - - const 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; - return this; - } - - toArray() { - return [this.x, this.y]; - } - - transform(m) { - return this.clone().transformO(m); - } // Transform point with matrix - - - transformO(m) { - if (!Matrix.isMatrixLike(m)) { - m = new Matrix(m); - } - - const { - x, - y - } = this; // Perform the matrix multiplication - - this.x = m.a * x + m.c * y + m.e; - this.y = m.b * x + m.d * y + m.f; - return this; - } - - } - function point(x, y) { - return new Point(x, y).transform(this.screenCTM().inverse()); - } - - function closeEnough(a, b, threshold) { - return Math.abs(b - a) < (threshold || 1e-6); - } - - class Matrix { - constructor(...args) { - this.init(...args); - } - - static formatTransforms(o) { - // Get all of the parameters required to form the matrix - const flipBoth = o.flip === 'both' || o.flip === true; - const flipX = o.flip && (flipBoth || o.flip === 'x') ? -1 : 1; - const flipY = o.flip && (flipBoth || o.flip === 'y') ? -1 : 1; - const skewX = o.skew && o.skew.length ? o.skew[0] : isFinite(o.skew) ? o.skew : isFinite(o.skewX) ? o.skewX : 0; - const skewY = o.skew && o.skew.length ? o.skew[1] : isFinite(o.skew) ? o.skew : isFinite(o.skewY) ? o.skewY : 0; - const scaleX = o.scale && o.scale.length ? o.scale[0] * flipX : isFinite(o.scale) ? o.scale * flipX : isFinite(o.scaleX) ? o.scaleX * flipX : flipX; - const scaleY = o.scale && o.scale.length ? o.scale[1] * flipY : isFinite(o.scale) ? o.scale * flipY : isFinite(o.scaleY) ? o.scaleY * flipY : flipY; - const shear = o.shear || 0; - const theta = o.rotate || o.theta || 0; - const origin = new Point(o.origin || o.around || o.ox || o.originX, o.oy || o.originY); - const ox = origin.x; - const oy = origin.y; // We need Point to be invalid if nothing was passed because we cannot default to 0 here. Thats why NaN - - const position = new Point(o.position || o.px || o.positionX || NaN, o.py || o.positionY || NaN); - const px = position.x; - const py = position.y; - const translate = new Point(o.translate || o.tx || o.translateX, o.ty || o.translateY); - const tx = translate.x; - const ty = translate.y; - const relative = new Point(o.relative || o.rx || o.relativeX, o.ry || o.relativeY); - const rx = relative.x; - const ry = relative.y; // Populate all of the values - - return { - scaleX, - scaleY, - skewX, - skewY, - shear, - theta, - rx, - ry, - tx, - ty, - ox, - oy, - px, - py - }; - } - - static fromArray(a) { - return { - a: a[0], - b: a[1], - c: a[2], - d: a[3], - e: a[4], - f: a[5] - }; - } - - static isMatrixLike(o) { - return o.a != null || o.b != null || o.c != null || o.d != null || o.e != null || o.f != null; - } // left matrix, right matrix, target matrix which is overwritten - - - static matrixMultiply(l, r, o) { - // Work out the product directly - const a = l.a * r.a + l.c * r.b; - const b = l.b * r.a + l.d * r.b; - const c = l.a * r.c + l.c * r.d; - const d = l.b * r.c + l.d * r.d; - const e = l.e + l.a * r.e + l.c * r.f; - const f = l.f + l.b * r.e + l.d * r.f; // make sure to use local variables because l/r and o could be the same - - o.a = a; - o.b = b; - o.c = c; - o.d = d; - o.e = e; - o.f = f; - return o; - } - - around(cx, cy, matrix) { - return this.clone().aroundO(cx, cy, matrix); - } // Transform around a center point - - - aroundO(cx, cy, matrix) { - const dx = cx || 0; - const dy = cy || 0; - return this.translateO(-dx, -dy).lmultiplyO(matrix).translateO(dx, dy); - } // Clones this matrix - - - clone() { - return new Matrix(this); - } // Decomposes this matrix into its affine parameters - - - decompose(cx = 0, cy = 0) { - // Get the parameters from the matrix - const a = this.a; - const b = this.b; - const c = this.c; - const d = this.d; - const e = this.e; - const f = this.f; // Figure out if the winding direction is clockwise or counterclockwise - - const determinant = a * d - b * c; - const 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 - - const sx = ccw * Math.sqrt(a * a + b * b); - const thetaRad = Math.atan2(ccw * b, ccw * a); - const theta = 180 / Math.PI * thetaRad; - const ct = Math.cos(thetaRad); - const st = Math.sin(thetaRad); // We can then solve the y basis vector simultaneously to get the other - // two affine parameters directly from these parameters - - const lam = (a * c + b * d) / determinant; - const sy = c * sx / (lam * a - b) || d * sx / (lam * b + a); // Use the translations - - const tx = e - cx + cx * ct * sx + cy * (lam * ct * sx - st * sy); - const ty = f - cy + cx * st * sx + cy * (lam * st * sx + ct * sy); // Construct the decomposition and return it - - return { - // Return the affine parameters - scaleX: sx, - scaleY: sy, - shear: lam, - rotate: theta, - translateX: tx, - translateY: ty, - originX: cx, - originY: cy, - // Return the matrix parameters - a: this.a, - b: this.b, - c: this.c, - d: this.d, - e: this.e, - f: this.f - }; - } // Check if two matrices are equal - - - equals(other) { - if (other === this) return true; - const comp = new 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); - } // Flip matrix on x or y, at a given offset - - - flip(axis, around) { - return this.clone().flipO(axis, around); - } - - flipO(axis, around) { - return axis === 'x' ? this.scaleO(-1, 1, around, 0) : axis === 'y' ? this.scaleO(1, -1, 0, around) : this.scaleO(-1, -1, axis, around || axis); // Define an x, y flip point - } // Initialize - - - init(source) { - const base = Matrix.fromArray([1, 0, 0, 1, 0, 0]); // ensure source as object - - source = source instanceof Element ? source.matrixify() : typeof source === 'string' ? Matrix.fromArray(source.split(delimiter).map(parseFloat)) : Array.isArray(source) ? Matrix.fromArray(source) : typeof source === 'object' && Matrix.isMatrixLike(source) ? source : typeof source === 'object' ? new Matrix().transform(source) : arguments.length === 6 ? Matrix.fromArray([].slice.call(arguments)) : base; // Merge the source matrix with the base matrix - - this.a = source.a != null ? source.a : base.a; - this.b = source.b != null ? source.b : base.b; - this.c = source.c != null ? source.c : base.c; - this.d = source.d != null ? source.d : base.d; - this.e = source.e != null ? source.e : base.e; - this.f = source.f != null ? source.f : base.f; - return this; - } - - inverse() { - return this.clone().inverseO(); - } // Inverses matrix - - - inverseO() { - // Get the current parameters out of the matrix - const a = this.a; - const b = this.b; - const c = this.c; - const d = this.d; - const e = this.e; - const f = this.f; // Invert the 2x2 matrix in the top left - - const det = a * d - b * c; - if (!det) throw new Error('Cannot invert ' + this); // Calculate the top 2x2 matrix - - const na = d / det; - const nb = -b / det; - const nc = -c / det; - const nd = a / det; // Apply the inverted matrix to the top right - - const ne = -(na * e + nc * f); - const nf = -(nb * e + nd * f); // Construct the inverted matrix - - this.a = na; - this.b = nb; - this.c = nc; - this.d = nd; - this.e = ne; - this.f = nf; - return this; - } - - lmultiply(matrix) { - return this.clone().lmultiplyO(matrix); - } - - lmultiplyO(matrix) { - const r = this; - const l = matrix instanceof Matrix ? matrix : new Matrix(matrix); - return Matrix.matrixMultiply(l, r, this); - } // Left multiplies by the given matrix - - - multiply(matrix) { - return this.clone().multiplyO(matrix); - } - - multiplyO(matrix) { - // Get the matrices - const l = this; - const r = matrix instanceof Matrix ? matrix : new Matrix(matrix); - return Matrix.matrixMultiply(l, r, this); - } // Rotate matrix - - - rotate(r, cx, cy) { - return this.clone().rotateO(r, cx, cy); - } - - rotateO(r, cx = 0, cy = 0) { - // Convert degrees to radians - r = radians(r); - const cos = Math.cos(r); - const sin = Math.sin(r); - const { - a, - b, - c, - d, - e, - f - } = this; - this.a = a * cos - b * sin; - this.b = b * cos + a * sin; - this.c = c * cos - d * sin; - this.d = d * cos + c * sin; - this.e = e * cos - f * sin + cy * sin - cx * cos + cx; - this.f = f * cos + e * sin - cx * sin - cy * cos + cy; - return this; - } // Scale matrix - - - scale(x, y, cx, cy) { - return this.clone().scaleO(...arguments); - } - - scaleO(x, y = x, cx = 0, cy = 0) { - // Support uniform scaling - if (arguments.length === 3) { - cy = cx; - cx = y; - y = x; - } - - const { - a, - b, - c, - d, - e, - f - } = this; - this.a = a * x; - this.b = b * y; - this.c = c * x; - this.d = d * y; - this.e = e * x - cx * x + cx; - this.f = f * y - cy * y + cy; - return this; - } // Shear matrix - - - shear(a, cx, cy) { - return this.clone().shearO(a, cx, cy); - } - - shearO(lx, cx = 0, cy = 0) { - const { - a, - b, - c, - d, - e, - f - } = this; - this.a = a + b * lx; - this.c = c + d * lx; - this.e = e + f * lx - cy * lx; - return this; - } // Skew Matrix - - - skew(x, y, cx, cy) { - return this.clone().skewO(...arguments); - } - - skewO(x, y = x, cx = 0, cy = 0) { - // support uniformal skew - if (arguments.length === 3) { - cy = cx; - cx = y; - y = x; - } // Convert degrees to radians - - - x = radians(x); - y = radians(y); - const lx = Math.tan(x); - const ly = Math.tan(y); - const { - a, - b, - c, - d, - e, - f - } = this; - this.a = a + b * lx; - this.b = b + a * ly; - this.c = c + d * lx; - this.d = d + c * ly; - this.e = e + f * lx - cy * lx; - this.f = f + e * ly - cx * ly; - return this; - } // SkewX - - - skewX(x, cx, cy) { - return this.skew(x, 0, cx, cy); - } // SkewY - - - skewY(y, cx, cy) { - return this.skew(0, y, cx, cy); - } - - toArray() { - return [this.a, this.b, this.c, this.d, this.e, this.f]; - } // Convert matrix to string - - - toString() { - return 'matrix(' + this.a + ',' + this.b + ',' + this.c + ',' + this.d + ',' + this.e + ',' + this.f + ')'; - } // Transform a matrix into another matrix by manipulating the space - - - transform(o) { - // Check if o is a matrix and then left multiply it directly - if (Matrix.isMatrixLike(o)) { - const matrix = new Matrix(o); - return matrix.multiplyO(this); - } // Get the proposed transformations and the current transformations - - - const t = Matrix.formatTransforms(o); - const current = this; - const { - x: ox, - y: oy - } = new Point(t.ox, t.oy).transform(current); // Construct the resulting matrix - - const transformer = new Matrix().translateO(t.rx, t.ry).lmultiplyO(current).translateO(-ox, -oy).scaleO(t.scaleX, t.scaleY).skewO(t.skewX, t.skewY).shearO(t.shear).rotateO(t.theta).translateO(ox, oy); // If we want the origin at a particular place, we force it there - - if (isFinite(t.px) || isFinite(t.py)) { - const origin = new Point(ox, oy).transform(transformer); // TODO: Replace t.px with isFinite(t.px) - // Doesnt work because t.px is also 0 if it wasnt passed - - const dx = isFinite(t.px) ? t.px - origin.x : 0; - const dy = isFinite(t.py) ? t.py - origin.y : 0; - transformer.translateO(dx, dy); - } // Translate now after positioning - - - transformer.translateO(t.tx, t.ty); - return transformer; - } // Translate matrix - - - translate(x, y) { - return this.clone().translateO(x, y); - } - - translateO(x, y) { - this.e += x || 0; - this.f += y || 0; - return this; - } - - valueOf() { - return { - a: this.a, - b: this.b, - c: this.c, - d: this.d, - e: this.e, - f: this.f - }; - } - - } - function ctm() { - return new Matrix(this.node.getCTM()); - } - function screenCTM() { - /* 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 (typeof this.isRoot === 'function' && !this.isRoot()) { - const rect = this.rect(1, 1); - const m = rect.node.getScreenCTM(); - rect.remove(); - return new Matrix(m); - } - - return new Matrix(this.node.getScreenCTM()); - } - register(Matrix, 'Matrix'); - - function parser() { - // Reuse cached element if possible - if (!parser.nodes) { - const svg = makeInstance().size(2, 0); - svg.node.style.cssText = ['opacity: 0', 'position: absolute', 'left: -100%', 'top: -100%', 'overflow: hidden'].join(';'); - svg.attr('focusable', 'false'); - svg.attr('aria-hidden', 'true'); - const path = svg.path().node; - parser.nodes = { - svg, - path - }; - } - - if (!parser.nodes.svg.node.parentNode) { - const b = globals.document.body || globals.document.documentElement; - parser.nodes.svg.addTo(b); - } - - return parser.nodes; - } - - function isNulledBox(box) { - return !box.width && !box.height && !box.x && !box.y; - } - function domContains(node) { - return node === globals.document || (globals.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 === globals.document; - }).call(globals.document.documentElement, node); - } - class Box { - constructor(...args) { - this.init(...args); - } - - addOffset() { - // offset by window scroll position, because getBoundingClientRect changes when window is scrolled - this.x += globals.window.pageXOffset; - this.y += globals.window.pageYOffset; - return new Box(this); - } - - init(source) { - const base = [0, 0, 0, 0]; - source = typeof source === 'string' ? source.split(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] || 0; - this.y = source[1] || 0; - this.width = this.w = source[2] || 0; - this.height = this.h = source[3] || 0; // Add more bounding box properties - - this.x2 = this.x + this.w; - this.y2 = this.y + this.h; - this.cx = this.x + this.w / 2; - this.cy = this.y + this.h / 2; - return this; - } - - isNulled() { - return isNulledBox(this); - } // Merge rect box with another, return a new instance - - - merge(box) { - const x = Math.min(this.x, box.x); - const y = Math.min(this.y, box.y); - const width = Math.max(this.x + this.width, box.x + box.width) - x; - const height = Math.max(this.y + this.height, box.y + box.height) - y; - return new Box(x, y, width, height); - } - - toArray() { - return [this.x, this.y, this.width, this.height]; - } - - toString() { - return this.x + ' ' + this.y + ' ' + this.width + ' ' + this.height; - } - - transform(m) { - if (!(m instanceof Matrix)) { - m = new Matrix(m); - } - - let xMin = Infinity; - let xMax = -Infinity; - let yMin = Infinity; - let yMax = -Infinity; - const pts = [new Point(this.x, this.y), new Point(this.x2, this.y), new Point(this.x, this.y2), new 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 Box(xMin, yMin, xMax - xMin, yMax - yMin); - } - - } - - function getBox(el, getBBoxFn, retry) { - let box; - - try { - // Try to get the box with the provided function - box = getBBoxFn(el.node); // If the box is worthless and not even in the dom, retry - // by throwing an error here... - - if (isNulledBox(box) && !domContains(el.node)) { - throw new Error('Element not in the dom'); - } - } catch (e) { - // ... and calling the retry handler here - box = retry(el); - } - - return box; - } - - function bbox() { - // Function to get bbox is getBBox() - const getBBox = node => node.getBBox(); // Take all measures so that a stupid browser renders the element - // so we can get the bbox from it when we try again - - - const retry = el => { - try { - const clone = el.clone().addTo(parser().svg).show(); - const box = clone.node.getBBox(); - clone.remove(); - return box; - } catch (e) { - // We give up... - throw new Error(`Getting bbox of element "${el.node.nodeName}" is not possible: ${e.toString()}`); - } - }; - - const box = getBox(this, getBBox, retry); - const bbox = new Box(box); - return bbox; - } - function rbox(el) { - const getRBox = node => node.getBoundingClientRect(); - - const retry = el => { - // There is no point in trying tricks here because if we insert the element into the dom ourselves - // it obviously will be at the wrong position - throw new Error(`Getting rbox of element "${el.node.nodeName}" is not possible`); - }; - - const box = getBox(this, getRBox, retry); - const rbox = new Box(box); // If an element was passed, we want the bbox in the coordinate system of that element - - if (el) { - return rbox.transform(el.screenCTM().inverseO()); - } // Else we want it in absolute screen coordinates - // Therefore we need to add the scrollOffset - - - return rbox.addOffset(); - } // Checks whether the given point is inside the bounding box - - function inside(x, y) { - const box = this.bbox(); - return x > box.x && y > box.y && x < box.x + box.width && y < box.y + box.height; - } - registerMethods({ - viewbox: { - viewbox(x, y, width, height) { - // act as getter - if (x == null) return new Box(this.attr('viewBox')); // act as setter - - return this.attr('viewBox', new Box(x, y, width, height)); - }, - - zoom(level, point) { - // Its best to rely on the attributes here and here is why: - // clientXYZ: Doesn't work on non-root svgs because they dont have a CSSBox (silly!) - // getBoundingClientRect: Doesn't work because Chrome just ignores width and height of nested svgs completely - // that means, their clientRect is always as big as the content. - // Furthermore this size is incorrect if the element is further transformed by its parents - // computedStyle: Only returns meaningful values if css was used with px. We dont go this route here! - // getBBox: returns the bounding box of its content - that doesnt help! - let { - width, - height - } = this.attr(['width', 'height']); // Width and height is a string when a number with a unit is present which we can't use - // So we try clientXYZ - - if (!width && !height || typeof width === 'string' || typeof height === 'string') { - width = this.node.clientWidth; - height = this.node.clientHeight; - } // Giving up... - - - if (!width || !height) { - throw new Error('Impossible to get absolute width and height. Please provide an absolute width and height attribute on the zooming element'); - } - - const v = this.viewbox(); - const zoomX = width / v.width; - const zoomY = height / v.height; - const zoom = Math.min(zoomX, zoomY); - - if (level == null) { - return zoom; - } - - let zoomAmount = zoom / level; // Set the zoomAmount to the highest value which is safe to process and recover from - // The * 100 is a bit of wiggle room for the matrix transformation - - if (zoomAmount === Infinity) zoomAmount = Number.MAX_SAFE_INTEGER / 100; - point = point || new Point(width / 2 / zoomX + v.x, height / 2 / zoomY + v.y); - const box = new Box(v).transform(new Matrix({ - scale: zoomAmount, - origin: point - })); - return this.viewbox(box); - } - - } - }); - register(Box, 'Box'); - - class List extends Array { - constructor(arr = [], ...args) { - super(arr, ...args); - if (typeof arr === 'number') return this; - this.length = 0; - this.push(...arr); - } - - } - extend([List], { - each(fnOrMethodName, ...args) { - if (typeof fnOrMethodName === 'function') { - return this.map((el, i, arr) => { - return fnOrMethodName.call(el, el, i, arr); - }); - } else { - return this.map(el => { - return el[fnOrMethodName](...args); - }); - } - }, - - toArray() { - return Array.prototype.concat.apply([], this); - } - - }); - const reserved = ['toArray', 'constructor', 'each']; - - List.extend = function (methods) { - methods = methods.reduce((obj, name) => { - // Don't overwrite own methods - if (reserved.includes(name)) return obj; // Don't add private methods - - if (name[0] === '_') return obj; // Relay every call to each() - - obj[name] = function (...attrs) { - return this.each(name, ...attrs); - }; - - return obj; - }, {}); - extend([List], methods); - }; - - function baseFind(query, parent) { - return new List(map((parent || globals.document).querySelectorAll(query), function (node) { - return adopt(node); - })); - } // Scoped find method - - function find(query) { - return baseFind(query, this.node); - } - function findOne(query) { - return adopt(this.node.querySelector(query)); - } - - let listenerId = 0; - const windowEvents = {}; - function getEvents(instance) { - let n = instance.getEventHolder(); // We dont want to save events in global space - - if (n === globals.window) n = windowEvents; - if (!n.events) n.events = {}; - return n.events; - } - function getEventTarget(instance) { - return instance.getEventTarget(); - } - function clearEvents(instance) { - let n = instance.getEventHolder(); - if (n === globals.window) n = windowEvents; - if (n.events) n.events = {}; - } // Add event binder in the SVG namespace - - function on(node, events, listener, binding, options) { - const l = listener.bind(binding || node); - const instance = makeInstance(node); - const bag = getEvents(instance); - const n = getEventTarget(instance); // events can be an array of events or a string of events - - events = Array.isArray(events) ? events : events.split(delimiter); // add id to listener - - if (!listener._svgjsListenerId) { - listener._svgjsListenerId = ++listenerId; - } - - events.forEach(function (event) { - const ev = event.split('.')[0]; - const 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 - - function off(node, events, listener, options) { - const instance = makeInstance(node); - const bag = getEvents(instance); - const n = getEventTarget(instance); // listener can be a function or a number - - if (typeof listener === 'function') { - listener = listener._svgjsListenerId; - if (!listener) return; - } // events can be an array of events or a string or undefined - - - events = Array.isArray(events) ? events : (events || '').split(delimiter); - events.forEach(function (event) { - const ev = event && event.split('.')[0]; - const ns = event && event.split('.')[1]; - let 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]) { - 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) { - off(n, [event, ns].join('.')); - } - } - } - } else if (ev) { - // remove all listeners for the event - if (bag[ev]) { - for (namespace in bag[ev]) { - off(n, [ev, namespace].join('.')); - } - - delete bag[ev]; - } - } else { - // remove all listeners on a given node - for (event in bag) { - off(n, event); - } - - clearEvents(instance); - } - }); - } - function dispatch(node, event, data, options) { - const n = getEventTarget(node); // Dispatch event - - if (event instanceof globals.window.Event) { - n.dispatchEvent(event); - } else { - event = new globals.window.CustomEvent(event, { - detail: data, - cancelable: true, - ...options - }); - n.dispatchEvent(event); - } - - return event; - } - - class EventTarget extends Base { - addEventListener() {} - - dispatch(event, data, options) { - return dispatch(this, event, data, options); - } - - dispatchEvent(event) { - const bag = this.getEventHolder().events; - if (!bag) return true; - const events = bag[event.type]; - - for (const i in events) { - for (const j in events[i]) { - events[i][j](event); - } - } - - return !event.defaultPrevented; - } // Fire given event - - - fire(event, data, options) { - this.dispatch(event, data, options); - return this; - } - - getEventHolder() { - return this; - } - - getEventTarget() { - return this; - } // Unbind event from listener - - - off(event, listener, options) { - off(this, event, listener, options); - return this; - } // Bind given event to listener - - - on(event, listener, binding, options) { - on(this, event, listener, binding, options); - return this; - } - - removeEventListener() {} - - } - register(EventTarget, 'EventTarget'); - - function noop() {} // Default animation values - - const timeline = { - duration: 400, - ease: '>', - delay: 0 - }; // Default attribute values - - const 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 - 'text-anchor': 'start' - }; - - var defaults = { - __proto__: null, - noop: noop, - timeline: timeline, - attrs: attrs - }; - - class SVGArray extends Array { - constructor(...args) { - super(...args); - this.init(...args); - } - - clone() { - return new this.constructor(this); - } - - init(arr) { - // This catches the case, that native map tries to create an array with new Array(1) - if (typeof arr === 'number') return this; - this.length = 0; - this.push(...this.parse(arr)); - return this; - } // Parse whitespace separated string - - - parse(array = []) { - // If already is an array, no need to parse it - if (array instanceof Array) return array; - return array.trim().split(delimiter).map(parseFloat); - } - - toArray() { - return Array.prototype.concat.apply([], this); - } - - toSet() { - return new Set(this); - } - - toString() { - return this.join(' '); - } // Flattens the array if needed - - - valueOf() { - const ret = []; - ret.push(...this); - return ret; - } - - } - - class SVGNumber { - // Initialize - constructor(...args) { - this.init(...args); - } - - convert(unit) { - return new SVGNumber(this.value, unit); - } // Divide number - - - divide(number) { - number = new SVGNumber(number); - return new SVGNumber(this / number, this.unit || number.unit); - } - - init(value, unit) { - unit = Array.isArray(value) ? value[1] : unit; - value = Array.isArray(value) ? value[0] : value; // 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(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 SVGNumber) { - this.value = value.valueOf(); - this.unit = value.unit; - } - } - - return this; - } // Subtract number - - - minus(number) { - number = new SVGNumber(number); - return new SVGNumber(this - number, this.unit || number.unit); - } // Add number - - - plus(number) { - number = new SVGNumber(number); - return new SVGNumber(this + number, this.unit || number.unit); - } // Multiply number - - - times(number) { - number = new SVGNumber(number); - return new SVGNumber(this * number, this.unit || number.unit); - } - - toArray() { - return [this.value, this.unit]; - } - - toJSON() { - return this.toString(); - } - - toString() { - return (this.unit === '%' ? ~~(this.value * 1e8) / 1e6 : this.unit === 's' ? this.value / 1e3 : this.value) + this.unit; - } - - valueOf() { - return this.value; - } - - } - - const hooks = []; - function registerAttrHook(fn) { - hooks.push(fn); - } // Set svg element attribute - - function attr(attr, val, ns) { - // act as full getter - if (attr == null) { - // get an object of attributes - attr = {}; - val = this.node.attributes; - - for (const node of val) { - attr[node.nodeName] = isNumber.test(node.nodeValue) ? parseFloat(node.nodeValue) : node.nodeValue; - } - - return attr; - } else if (attr instanceof Array) { - // loop through array and get all values - return attr.reduce((last, curr) => { - last[curr] = this.attr(curr); - return last; - }, {}); - } else if (typeof attr === 'object' && attr.constructor === Object) { - // apply every attribute individually if an object is passed - for (val in attr) this.attr(val, attr[val]); - } else if (val === null) { - // remove value - this.node.removeAttribute(attr); - } else if (val == null) { - // act as a getter if the first and only argument is not an object - val = this.node.getAttribute(attr); - return val == null ? attrs[attr] : isNumber.test(val) ? parseFloat(val) : val; - } else { - // Loop through hooks and execute them to convert value - val = hooks.reduce((_val, hook) => { - return hook(attr, _val, this); - }, val); // ensure correct numeric values (also accepts NaN and Infinity) - - if (typeof val === 'number') { - val = new SVGNumber(val); - } else if (Color.isColor(val)) { - // ensure full hex color - val = new Color(val); - } else if (val.constructor === Array) { - // Check for plain arrays and parse array values - val = new SVGArray(val); - } // if the passed attribute is leading... - - - if (attr === 'leading') { - // ... call the leading method instead - if (this.leading) { - this.leading(val); - } - } else { - // set given attribute on node - typeof ns === 'string' ? this.node.setAttributeNS(ns, attr, val.toString()) : this.node.setAttribute(attr, val.toString()); - } // rebuild if required - - - if (this.rebuild && (attr === 'font-size' || attr === 'x')) { - this.rebuild(); - } - } - - return this; - } - - class Dom extends EventTarget { - constructor(node, attrs) { - super(); - this.node = node; - this.type = node.nodeName; - - if (attrs && node !== attrs) { - this.attr(attrs); - } - } // Add given element at a position - - - add(element, i) { - element = makeInstance(element); // If non-root svg nodes are added we have to remove their namespaces - - if (element.removeNamespace && this.node instanceof globals.window.SVGElement) { - element.removeNamespace(); - } - - if (i == null) { - this.node.appendChild(element.node); - } else if (element.node !== this.node.childNodes[i]) { - this.node.insertBefore(element.node, this.node.childNodes[i]); - } - - return this; - } // Add element to given container and return self - - - addTo(parent, i) { - return makeInstance(parent).put(this, i); - } // Returns all child elements - - - children() { - return new List(map(this.node.children, function (node) { - return adopt(node); - })); - } // Remove all elements in this container - - - clear() { - // remove children - while (this.node.hasChildNodes()) { - this.node.removeChild(this.node.lastChild); - } - - return this; - } // Clone element - - - clone(deep = true) { - // write dom data to the dom so the clone can pickup the data - this.writeDataToDom(); // clone element and assign new id - - return new this.constructor(assignNewId(this.node.cloneNode(deep))); - } // Iterates over all children and invokes a given block - - - each(block, deep) { - const children = this.children(); - let i, il; - - for (i = 0, il = children.length; i < il; i++) { - block.apply(children[i], [i, children]); - - if (deep) { - children[i].each(block, deep); - } - } - - return this; - } - - element(nodeName, attrs) { - return this.put(new Dom(create(nodeName), attrs)); - } // Get first child - - - first() { - return adopt(this.node.firstChild); - } // Get a element at the given index - - - get(i) { - return adopt(this.node.childNodes[i]); - } - - getEventHolder() { - return this.node; - } - - getEventTarget() { - return this.node; - } // Checks if the given element is a child - - - has(element) { - return this.index(element) >= 0; - } - - html(htmlOrFn, outerHTML) { - return this.xml(htmlOrFn, outerHTML, html); - } // Get / set id - - - id(id) { - // generate new id if no id set - if (typeof id === 'undefined' && !this.node.id) { - this.node.id = eid(this.type); - } // don't set directly with this.node.id to make `null` work correctly - - - return this.attr('id', id); - } // Gets index of given element - - - index(element) { - return [].slice.call(this.node.childNodes).indexOf(element.node); - } // Get the last child - - - last() { - return adopt(this.node.lastChild); - } // matches the element vs a css selector - - - matches(selector) { - const el = this.node; - const matcher = el.matches || el.matchesSelector || el.msMatchesSelector || el.mozMatchesSelector || el.webkitMatchesSelector || el.oMatchesSelector || null; - return matcher && matcher.call(el, selector); - } // Returns the parent element instance - - - parent(type) { - let parent = this; // check for parent - - if (!parent.node.parentNode) return null; // get parent element - - parent = adopt(parent.node.parentNode); - if (!type) return parent; // loop trough ancestors if type is given - - do { - if (typeof type === 'string' ? parent.matches(type) : parent instanceof type) return parent; - } while (parent = adopt(parent.node.parentNode)); - - return parent; - } // Basically does the same as `add()` but returns the added element instead - - - put(element, i) { - element = makeInstance(element); - this.add(element, i); - return element; - } // Add element to given container and return container - - - putIn(parent, i) { - return makeInstance(parent).add(this, i); - } // Remove element - - - remove() { - if (this.parent()) { - this.parent().removeElement(this); - } - - return this; - } // Remove a given child - - - removeElement(element) { - this.node.removeChild(element.node); - return this; - } // Replace this with element - - - replace(element) { - element = makeInstance(element); - - if (this.node.parentNode) { - this.node.parentNode.replaceChild(element.node, this.node); - } - - return element; - } - - round(precision = 2, map = null) { - const factor = 10 ** precision; - const attrs = this.attr(map); - - for (const i in attrs) { - if (typeof attrs[i] === 'number') { - attrs[i] = Math.round(attrs[i] * factor) / factor; - } - } - - this.attr(attrs); - return this; - } // Import / Export raw svg - - - svg(svgOrFn, outerSVG) { - return this.xml(svgOrFn, outerSVG, svg); - } // Return id on string conversion - - - toString() { - return this.id(); - } - - words(text) { - // This is faster than removing all children and adding a new one - this.node.textContent = text; - return this; - } - - wrap(node) { - const parent = this.parent(); - - if (!parent) { - return this.addTo(node); - } - - const position = parent.index(this); - return parent.put(node, position).put(this); - } // write svgjs data to the dom - - - writeDataToDom() { - // dump variables recursively - this.each(function () { - this.writeDataToDom(); - }); - return this; - } // Import / Export raw svg - - - xml(xmlOrFn, outerXML, ns) { - if (typeof xmlOrFn === 'boolean') { - ns = outerXML; - outerXML = xmlOrFn; - xmlOrFn = null; - } // act as getter if no svg string is given - - - if (xmlOrFn == null || typeof xmlOrFn === 'function') { - // The default for exports is, that the outerNode is included - outerXML = outerXML == null ? true : outerXML; // write svgjs data to the dom - - this.writeDataToDom(); - let current = this; // An export modifier was passed - - if (xmlOrFn != null) { - current = adopt(current.node.cloneNode(true)); // If the user wants outerHTML we need to process this node, too - - if (outerXML) { - const result = xmlOrFn(current); - current = result || current; // The user does not want this node? Well, then he gets nothing - - if (result === false) return ''; - } // Deep loop through all children and apply modifier - - - current.each(function () { - const result = xmlOrFn(this); - - const _this = result || this; // If modifier returns false, discard node - - - if (result === false) { - this.remove(); // If modifier returns new node, use it - } else if (result && this !== _this) { - this.replace(_this); - } - }, true); - } // Return outer or inner content - - - return outerXML ? current.node.outerHTML : current.node.innerHTML; - } // Act as setter if we got a string - // The default for import is, that the current node is not replaced - - - outerXML = outerXML == null ? false : outerXML; // Create temporary holder - - const well = create('wrapper', ns); - const fragment = globals.document.createDocumentFragment(); // Dump raw svg - - well.innerHTML = xmlOrFn; // Transplant nodes into the fragment - - for (let len = well.children.length; len--;) { - fragment.appendChild(well.firstElementChild); - } - - const parent = this.parent(); // Add the whole fragment at once - - return outerXML ? this.replace(fragment) && parent : this.add(fragment); - } - - } - extend(Dom, { - attr, - find, - findOne - }); - register(Dom, 'Dom'); - - class Element extends Dom { - constructor(node, attrs) { - super(node, attrs); // initialize data object - - this.dom = {}; // create circular reference - - this.node.instance = this; - - 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')) || {}); - } - } // Move element by its center - - - center(x, y) { - return this.cx(x).cy(y); - } // Move by center over x-axis - - - cx(x) { - return x == null ? this.x() + this.width() / 2 : this.x(x - this.width() / 2); - } // Move by center over y-axis - - - cy(y) { - return y == null ? this.y() + this.height() / 2 : this.y(y - this.height() / 2); - } // Get defs - - - defs() { - const root = this.root(); - return root && root.defs(); - } // Relative move over x and y axes - - - dmove(x, y) { - return this.dx(x).dy(y); - } // Relative move over x axis - - - dx(x = 0) { - return this.x(new SVGNumber(x).plus(this.x())); - } // Relative move over y axis - - - dy(y = 0) { - return this.y(new SVGNumber(y).plus(this.y())); - } - - getEventHolder() { - return this; - } // Set height of element - - - height(height) { - return this.attr('height', height); - } // Move element to given x and y values - - - move(x, y) { - return this.x(x).y(y); - } // return array of all ancestors of given type up to the root svg - - - parents(until = this.root()) { - const isSelector = typeof until === 'string'; - - if (!isSelector) { - until = makeInstance(until); - } - - const parents = new List(); - let parent = this; - - while ((parent = parent.parent()) && parent.node !== globals.document && parent.nodeName !== '#document-fragment') { - parents.push(parent); - - if (!isSelector && parent.node === until.node) { - break; - } - - if (isSelector && parent.matches(until)) { - break; - } - - if (parent.node === this.root().node) { - // We worked our way to the root and didn't match `until` - return null; - } - } - - return parents; - } // Get referenced element form attribute value - - - reference(attr) { - attr = this.attr(attr); - if (!attr) return null; - const m = (attr + '').match(reference); - return m ? makeInstance(m[1]) : null; - } // Get parent document - - - root() { - const p = this.parent(getClass(root)); - return p && p.root(); - } // set given data to the elements data property - - - setData(o) { - this.dom = o; - return this; - } // Set element size to given width and height - - - size(width, height) { - const p = proportionalSize(this, width, height); - return this.width(new SVGNumber(p.width)).height(new SVGNumber(p.height)); - } // Set width of element - - - width(width) { - return this.attr('width', width); - } // write svgjs data to the dom - - - 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 super.writeDataToDom(); - } // Move over x-axis - - - x(x) { - return this.attr('x', x); - } // Move over y-axis - - - y(y) { - return this.attr('y', y); - } - - } - extend(Element, { - bbox, - rbox, - inside, - point, - ctm, - screenCTM - }); - register(Element, 'Element'); - - const 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) { - const extension = {}; - let i; - - extension[m] = function (o) { - if (typeof o === 'undefined') { - return this.attr(m); - } - - if (typeof o === 'string' || o instanceof Color || Color.isRgb(o) || o instanceof Element) { - 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; - }; - - registerMethods(['Element', 'Runner'], extension); - }); - registerMethods(['Element', 'Runner'], { - // Let the user set the matrix directly - matrix: function (mat, b, c, d, e, f) { - // Act as a getter - if (mat == null) { - return new Matrix(this); - } // Act as a setter, the user can pass a matrix or a set of numbers - - - return this.attr('transform', new 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 = 'both', origin = 'center') { - if ('xybothtrue'.indexOf(direction) === -1) { - origin = direction; - direction = 'both'; - } - - return this.transform({ - flip: direction, - origin: origin - }, true); - }, - // Opacity - opacity: function (value) { - return this.attr('opacity', value); - } - }); - registerMethods('radius', { - // Add x and y radius - radius: function (x, y = x) { - const type = (this._element || this).type; - return type === 'radialGradient' ? this.attr('r', new SVGNumber(x)) : this.rx(x).ry(y); - } - }); - registerMethods('Path', { - // Get path length - length: function () { - return this.node.getTotalLength(); - }, - // Get point at length - pointAt: function (length) { - return new Point(this.node.getPointAtLength(length)); - } - }); - registerMethods(['Element', 'Runner'], { - // Set font - font: function (a, v) { - if (typeof a === 'object') { - for (v in a) this.font(v, a[v]); - - return this; - } - - 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); - } - }); // Add events to elements - - const methods = ['click', 'dblclick', 'mousedown', 'mouseup', 'mouseover', 'mouseout', 'mousemove', 'mouseenter', 'mouseleave', 'touchstart', 'touchmove', 'touchleave', 'touchend', 'touchcancel'].reduce(function (last, event) { - // add event to Element - const fn = function (f) { - if (f === null) { - this.off(event); - } else { - this.on(event, f); - } - - return this; - }; - - last[event] = fn; - return last; - }, {}); - registerMethods('Element', methods); - - function untransform() { - return this.attr('transform', null); - } // merge the whole transformation chain into one matrix and returns it - - function matrixify() { - const matrix = (this.attr('transform') || '' // split transformations - ).split(transforms).slice(0, -1).map(function (str) { - // generate key => value pairs - const kv = str.trim().split('('); - return [kv[0], kv[1].split(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(Matrix.fromArray(transform[1])); - } - - return matrix[transform[0]].apply(matrix, transform[1]); - }, new Matrix()); - return matrix; - } // add an element to another parent without changing the visual representation on the screen - - function toParent(parent, i) { - if (this === parent) return this; - const ctm = this.screenCTM(); - const pCtm = parent.screenCTM().inverse(); - this.addTo(parent, i).untransform().transform(pCtm.multiply(ctm)); - return this; - } // same as above with parent equals root-svg - - function toRoot(i) { - return this.toParent(this.root(), i); - } // Add transformations - - function transform(o, relative) { - // Act as a getter if no object was passed - if (o == null || typeof o === 'string') { - const decomposed = new Matrix(this).decompose(); - return o == null ? decomposed : decomposed[o]; - } - - if (!Matrix.isMatrixLike(o)) { - // Set the origin according to the defined transform - o = { ...o, - origin: getOrigin(o, this) - }; - } // The user can pass a boolean, an Element or an Matrix or nothing - - - const cleanRelative = relative === true ? this : relative || false; - const result = new Matrix(cleanRelative).transform(o); - return this.attr('transform', result); - } - registerMethods('Element', { - untransform, - matrixify, - toParent, - toRoot, - transform - }); - - class Container extends Element { - flatten(parent = this, index) { - this.each(function () { - if (this instanceof Container) { - return this.flatten().ungroup(); - } - }); - return this; - } - - ungroup(parent = this.parent(), index = parent.index(this)) { - // when parent != this, we want append all elements to the end - index = index === -1 ? parent.children().length : index; - this.each(function (i, children) { - // reverse each - return children[children.length - i - 1].toParent(parent, index); - }); - return this.remove(); - } - - } - register(Container, 'Container'); - - class Defs extends Container { - constructor(node, attrs = node) { - super(nodeOrNew('defs', node), attrs); - } - - flatten() { - return this; - } - - ungroup() { - return this; - } - - } - register(Defs, 'Defs'); - - class Shape extends Element {} - register(Shape, 'Shape'); - - function rx(rx) { - return this.attr('rx', rx); - } // Radius y value - - function ry(ry) { - return this.attr('ry', ry); - } // Move over x-axis - - function x$3(x) { - return x == null ? this.cx() - this.rx() : this.cx(x + this.rx()); - } // Move over y-axis - - function y$3(y) { - return y == null ? this.cy() - this.ry() : this.cy(y + this.ry()); - } // Move by center over x-axis - - function cx$1(x) { - return this.attr('cx', x); - } // Move by center over y-axis - - function cy$1(y) { - return this.attr('cy', y); - } // Set width of element - - function width$2(width) { - return width == null ? this.rx() * 2 : this.rx(new SVGNumber(width).divide(2)); - } // Set height of element - - function height$2(height) { - return height == null ? this.ry() * 2 : this.ry(new SVGNumber(height).divide(2)); - } - - var circled = { - __proto__: null, - rx: rx, - ry: ry, - x: x$3, - y: y$3, - cx: cx$1, - cy: cy$1, - width: width$2, - height: height$2 - }; - - class Ellipse extends Shape { - constructor(node, attrs = node) { - super(nodeOrNew('ellipse', node), attrs); - } - - size(width, height) { - const p = proportionalSize(this, width, height); - return this.rx(new SVGNumber(p.width).divide(2)).ry(new SVGNumber(p.height).divide(2)); - } - - } - extend(Ellipse, circled); - registerMethods('Container', { - // Create an ellipse - ellipse: wrapWithAttrCheck(function (width = 0, height = width) { - return this.put(new Ellipse()).size(width, height).move(0, 0); +(function(root, factory) { + /* istanbul ignore next */ + if (typeof define === 'function' && define.amd) { + define(function(){ + return factory(root, root.document) }) - }); - register(Ellipse, 'Ellipse'); + } 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) { - class Fragment extends Dom { - constructor(node = globals.document.createDocumentFragment()) { - super(node); - } // Import / Export raw xml +// Find global reference - uses 'this' by default when available, +// falls back to 'window' otherwise (for bundlers like Webpack) +var globalRef = (typeof this !== "undefined") ? this : window; +// The main wrapping element +var SVG = globalRef.SVG = function(element) { + if (SVG.supported) { + element = new SVG.Doc(element) - xml(xmlOrFn, outerXML, ns) { - if (typeof xmlOrFn === 'boolean') { - ns = outerXML; - outerXML = xmlOrFn; - xmlOrFn = null; - } // because this is a fragment we have to put all elements into a wrapper first - // before we can get the innerXML from it + if(!SVG.parser.draw) + SVG.prepare() + return element + } +} - if (xmlOrFn == null || typeof xmlOrFn === 'function') { - const wrapper = new Dom(create('wrapper', ns)); - wrapper.add(this.node.cloneNode(true)); - return wrapper.xml(false, ns); - } // Act as setter if we got a string +// 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' +// Svg support test +SVG.supported = (function() { + return !! document.createElementNS && + !! document.createElementNS(SVG.ns,'svg').createSVGRect +})() - return super.xml(xmlOrFn, false, ns); +// Don't bother to continue if SVG is not supported +if (!SVG.supported) return false + +// 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 + var element = document.createElementNS(this.ns, name) + + // apply unique id + element.setAttribute('id', this.eid(name)) + + return element +} + +// Method for extending objects +SVG.extend = function() { + var modules, methods, key, i + + // Get list of modules + modules = [].slice.call(arguments) + + // Get object with extensions + methods = modules.pop() + + for (i = modules.length - 1; i >= 0; i--) + if (modules[i]) + for (key in methods) + modules[i].prototype[key] = methods[key] + + // Make sure SVG.Set inherits any newly added methods + if (SVG.Set && SVG.Set.inherit) + SVG.Set.inherit() +} + +// Invent new element +SVG.invent = function(config) { + // Create element initializer + var initializer = typeof config.create == 'function' ? + config.create : + function() { + this.constructor.call(this, SVG.create(config.create)) } + // Inherit prototype + if (config.inherit) + initializer.prototype = new config.inherit + + // 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) return node.instance + + // initialize variables + var element + + // adopt with element-specific settings + if (node.nodeName == 'svg') + element = node.parentNode instanceof window.SVGElement ? new SVG.Nested : new SVG.Doc + else if (node.nodeName == 'linearGradient') + element = new SVG.Gradient('linear') + else if (node.nodeName == 'radialGradient') + element = new SVG.Gradient('radial') + else if (SVG[capitalize(node.nodeName)]) + element = new SVG[capitalize(node.nodeName)] + else + element = new SVG.Element(node) + + // ensure references + element.type = node.nodeName + element.node = node + node.instance = element + + // SVG.Class specific preparations + if (element instanceof SVG.Doc) + element.namespace().defs() + + // pull svgjs data from the dom (getAttributeNS doesn't work in html5) + element.setData(JSON.parse(node.getAttribute('svgjs:data')) || {}) + + return element +} + +// Initialize parsing element +SVG.prepare = function() { + // Select document body and create invisible svg element + var body = document.getElementsByTagName('body')[0] + , draw = (body ? new SVG.Doc(body) : SVG.adopt(document.documentElement).nested()).size(2, 0) + + // Create parser object + SVG.parser = { + body: body || document.documentElement + , draw: draw.style('opacity:0;position:absolute;left:-100%;top:-100%;overflow:hidden').attr('focusable', 'false').node + , poly: draw.polyline().node + , path: draw.path().node + , native: SVG.create('svg') + } +} + +SVG.parser = { + native: SVG.create('svg') +} + +document.addEventListener('DOMContentLoaded', function() { + if(!SVG.parser.draw) + SVG.prepare() +}, false) + +// 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 + , il = array.length + , result = [] + + for (i = 0; i < il; i++) + result.push(block(array[i])) + + return result + } + + // Filter function +, filter: function(array, block) { + var i + , il = array.length + , 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 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' + } + +} +// 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 + } - register(Fragment, 'Fragment'); +} - function from(x, y) { - return (this._element || this).type === 'radialGradient' ? this.attr({ - fx: new SVGNumber(x), - fy: new SVGNumber(y) - }) : this.attr({ - x1: new SVGNumber(x), - y1: new SVGNumber(y) - }); +SVG.extend(SVG.Color, { + // Default to hex conversion + toString: function() { + return this.toHex() } - function to(x, y) { - return (this._element || this).type === 'radialGradient' ? this.attr({ - cx: new SVGNumber(x), - cy: new SVGNumber(y) - }) : this.attr({ - x2: new SVGNumber(x), - y2: new SVGNumber(y) - }); + // 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) + }) } - var gradiented = { - __proto__: null, - from: from, - to: to - }; +}) - class Gradient extends Container { - constructor(type, attrs) { - super(nodeOrNew(type + 'Gradient', typeof type === 'string' ? null : type), attrs); - } // custom attr to handle transform +// 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) +} - attr(a, b, c) { - if (a === 'transform') a = 'gradientTransform'; - return super.attr(a, b, c); - } - - bbox() { - return new Box(); - } - - targets() { - return baseFind('svg [fill*="' + this.id() + '"]'); - } // Alias string conversion to fill - - - toString() { - return this.url(); - } // Update gradient - - - update(block) { - // remove all stops - this.clear(); // invoke passed block - - if (typeof block === 'function') { - block.call(this, this); - } - - return this; - } // Return the fill id - - - url() { - return 'url("#' + this.id() + '")'; +// 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) +} +// 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] + , 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 } - extend(Gradient, gradiented); - registerMethods({ - Container: { - // Create gradient element in defs - gradient(...args) { - return this.defs().gradient(...args); - } - - }, - // define gradient - Defs: { - gradient: wrapWithAttrCheck(function (type, block) { - return this.put(new Gradient(type)).update(block); - }) - } - }); - register(Gradient, 'Gradient'); - - class Pattern extends Container { - // Initialize node - constructor(node, attrs = node) { - super(nodeOrNew('pattern', node), attrs); - } // custom attr to handle transform - - - attr(a, b, c) { - if (a === 'transform') a = 'patternTransform'; - return super.attr(a, b, c); - } - - bbox() { - return new Box(); - } - - targets() { - return baseFind('svg [fill*="' + this.id() + '"]'); - } // Alias string conversion to fill - - - toString() { - return this.url(); - } // Update pattern by rebuilding - - - update(block) { - // remove content - this.clear(); // invoke passed block - - if (typeof block === 'function') { - block.call(this, this); - } - - return this; - } // Return the fill id - - - url() { - return 'url("#' + this.id() + '")'; - } + // 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 + return this.value = seen } - registerMethods({ - Container: { - // Create pattern element in defs - pattern(...args) { - return this.defs().pattern(...args); - } + // Get morphed array at given position +, at: function(pos) { + // make sure a destination is defined + if (!this.destination) return this - }, - Defs: { - pattern: wrapWithAttrCheck(function (width, height, block) { - return this.put(new Pattern()).update(block).attr({ - x: 0, - y: 0, - width: width, - height: height, - patternUnits: 'userSpaceOnUse' - }); - }) - } - }); - register(Pattern, 'Pattern'); - - class Image extends Shape { - constructor(node, attrs = node) { - super(nodeOrNew('image', node), attrs); - } // (re)load image - - - load(url, callback) { - if (!url) return this; - const img = new globals.window.Image(); - on(img, 'load', function (e) { - const p = this.parent(Pattern); // ensure image size - - if (this.width() === 0 && this.height() === 0) { - this.size(img.width, img.height); - } - - if (p instanceof 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, e); - } - }, this); - on(img, 'load error', function () { - // dont forget to unbind memory leaking events - off(img); - }); - return this.attr('href', img.src = url, xlink); - } + // 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) } - registerAttrHook(function (attr, val, _this) { - // convert image fill and stroke to patterns - if (attr === 'fill' || attr === 'stroke') { - if (isImage.test(val)) { - val = _this.root().defs().image(val); - } - } - - if (val instanceof Image) { - val = _this.root().defs().pattern(0, 0, pattern => { - pattern.add(val); - }); - } - - return val; - }); - registerMethods({ - Container: { - // create image element, load image and set its size - image: wrapWithAttrCheck(function (source, callback) { - return this.put(new Image()).size(0, 0).load(source, callback); - }) - } - }); - register(Image, 'Image'); - - class PointArray extends SVGArray { - // Get bounding box of points - bbox() { - let maxX = -Infinity; - let maxY = -Infinity; - let minX = Infinity; - let minY = Infinity; - this.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 new Box(minX, minY, maxX - minX, maxY - minY); - } // Move point string - - - move(x, y) { - const box = this.bbox(); // get relative offset - - x -= box.x; - y -= box.y; // move every point - - if (!isNaN(x) && !isNaN(y)) { - for (let i = this.length - 1; i >= 0; i--) { - this[i] = [this[i][0] + x, this[i][1] + y]; - } - } - - return this; - } // Parse point string and flat array - - - parse(array = [0, 0]) { - const points = []; // if it is an array, we flatten it and therefore clone it to 1 depths - - if (array instanceof Array) { - array = Array.prototype.concat.apply([], array); - } else { - // Else, it is considered as a string - // parse points - array = array.trim().split(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 - - for (let i = 0, len = array.length; i < len; i = i + 2) { - points.push([array[i], array[i + 1]]); - } - - return points; - } // Resize poly string - - - size(width, height) { - let i; - const box = this.bbox(); // recalculate position of all points according to new size - - for (i = this.length - 1; i >= 0; i--) { - if (box.width) this[i][0] = (this[i][0] - box.x) * width / box.width + box.x; - if (box.height) this[i][1] = (this[i][1] - box.y) * height / box.height + box.y; - } - - return this; - } // Convert array to line object - - - toLine() { - return { - x1: this[0][0], - y1: this[0][1], - x2: this[1][0], - y2: this[1][1] - }; - } // Convert array to string - - - toString() { - const array = []; // convert to a poly point string - - for (let i = 0, il = this.length; i < il; i++) { - array.push(this[i].join(',')); - } - - return array.join(' '); - } - - transform(m) { - return this.clone().transformO(m); - } // transform points with matrix (similar to Point.transform) - - - transformO(m) { - if (!Matrix.isMatrixLike(m)) { - m = new Matrix(m); - } - - for (let i = this.length; i--;) { - // Perform the matrix multiplication - const [x, y] = this[i]; - this[i][0] = m.a * x + m.c * y + m.e; - this[i][1] = m.b * x + m.d * y + m.f; - } - - return this; - } - + // Convert array to string +, toString: function() { + return this.value.join(' ') } - - const MorphArray = PointArray; // Move by left top corner over x-axis - - function x$2(x) { - return x == null ? this.bbox().x : this.move(x, this.bbox().y); - } // Move by left top corner over y-axis - - function y$2(y) { - return y == null ? this.bbox().y : this.move(this.bbox().x, y); - } // Set width of element - - function width$1(width) { - const b = this.bbox(); - return width == null ? b.width : this.size(width, b.height); - } // Set height of element - - function height$1(height) { - const b = this.bbox(); - return height == null ? b.height : this.size(b.width, height); + // Real value +, valueOf: function() { + return this.value } + // Parse whitespace separated string +, parse: function(array) { + array = array.valueOf() - var pointed = { - __proto__: null, - MorphArray: MorphArray, - x: x$2, - y: y$2, - width: width$1, - height: height$1 - }; - - class Line extends Shape { - // Initialize node - constructor(node, attrs = node) { - super(nodeOrNew('line', node), attrs); - } // Get array - - - array() { - return new PointArray([[this.attr('x1'), this.attr('y1')], [this.attr('x2'), this.attr('y2')]]); - } // Move by left top corner - - - move(x, y) { - return this.attr(this.array().move(x, y).toLine()); - } // Overwrite native plot() method - - - plot(x1, y1, x2, y2) { - if (x1 == null) { - return this.array(); - } else if (typeof y1 !== 'undefined') { - x1 = { - x1, - y1, - x2, - y2 - }; - } else { - x1 = new PointArray(x1).toLine(); - } - - return this.attr(x1); - } // Set element size to given width and height - - - size(width, height) { - const p = proportionalSize(this, width, height); - return this.attr(this.array().size(p.width, p.height).toLine()); - } + // if already is an array, no need to parse it + if (Array.isArray(array)) return array + return this.split(array) } - extend(Line, pointed); - registerMethods({ - Container: { - // Create a line element - line: wrapWithAttrCheck(function (...args) { - // make sure plot is called as a setter - // x1 is not necessarily a number, it can also be an array, a string and a PointArray - return Line.prototype.plot.apply(this.put(new Line()), args[0] != null ? args : [0, 0, 0, 0]); - }) - } - }); - register(Line, 'Line'); - - class Marker extends Container { - // Initialize node - constructor(node, attrs = node) { - super(nodeOrNew('marker', node), attrs); - } // Set height of element - - - height(height) { - return this.attr('markerHeight', height); - } - - orient(orient) { - return this.attr('orient', orient); - } // Set marker refX and refY - - - ref(x, y) { - return this.attr('refX', x).attr('refY', y); - } // Return the fill id - - - toString() { - return 'url(#' + this.id() + ')'; - } // Update marker - - - update(block) { - // remove all content - this.clear(); // invoke passed block - - if (typeof block === 'function') { - block.call(this, this); - } - - return this; - } // Set width of element - - - width(width) { - return this.attr('markerWidth', width); - } - + // Strip unnecessary whitespace +, split: function(string) { + return string.trim().split(SVG.regex.delimiter).map(parseFloat) } - registerMethods({ - Container: { - marker(...args) { - // Create marker element in defs - return this.defs().marker(...args); - } + // Reverse array +, reverse: function() { + this.value.reverse() - }, - Defs: { - // Create marker - marker: wrapWithAttrCheck(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 Marker()).size(width, height).ref(width / 2, height / 2).viewbox(0, 0, width, height).attr('orient', 'auto').update(block); - }) - }, - marker: { - // Create and attach markers - marker(marker, width, height, block) { - let attr = ['marker']; // Build attribute name - - if (marker !== 'all') attr.push(marker); - attr = attr.join('-'); // Set marker attribute - - marker = arguments[1] instanceof Marker ? arguments[1] : this.defs().marker(width, height, block); - return this.attr(attr, marker); - } - - } - }); - register(Marker, 'Marker'); - - /*** - Base Class - ========== - The base stepper class that will be - ***/ - - function makeSetterGetter(k, f) { - return function (v) { - if (v == null) return this[k]; - this[k] = v; - if (f) f.call(this); - return this; - }; + return this } - - const easing = { - '-': function (pos) { - return pos; - }, - '<>': function (pos) { - return -Math.cos(pos * Math.PI) / 2 + 0.5; - }, - '>': function (pos) { - return Math.sin(pos * Math.PI / 2); - }, - '<': function (pos) { - return -Math.cos(pos * Math.PI / 2) + 1; - }, - bezier: function (x1, y1, x2, y2) { - // see https://www.w3.org/TR/css-easing-1/#cubic-bezier-algo - return function (t) { - if (t < 0) { - if (x1 > 0) { - return y1 / x1 * t; - } else if (x2 > 0) { - return y2 / x2 * t; - } else { - return 0; - } - } else if (t > 1) { - if (x2 < 1) { - return (1 - y2) / (1 - x2) * t + (y2 - x2) / (1 - x2); - } else if (x1 < 1) { - return (1 - y1) / (1 - x1) * t + (y1 - x1) / (1 - x1); - } else { - return 1; - } - } else { - return 3 * t * (1 - t) ** 2 * y1 + 3 * t ** 2 * (1 - t) * y2 + t ** 3; - } - }; - }, - // see https://www.w3.org/TR/css-easing-1/#step-timing-function-algo - steps: function (steps, stepPosition = 'end') { - // deal with "jump-" prefix - stepPosition = stepPosition.split('-').reverse()[0]; - let jumps = steps; - - if (stepPosition === 'none') { - --jumps; - } else if (stepPosition === 'both') { - ++jumps; - } // The beforeFlag is essentially useless - - - return (t, beforeFlag = false) => { - // Step is called currentStep in referenced url - let step = Math.floor(t * steps); - const jumping = t * step % 1 === 0; - - if (stepPosition === 'start' || stepPosition === 'both') { - ++step; - } - - if (beforeFlag && jumping) { - --step; - } - - if (t >= 0 && step < 0) { - step = 0; - } - - if (t <= 1 && step > jumps) { - step = jumps; - } - - return step / jumps; - }; - } - }; - class Stepper { - done() { - return false; - } - +, clone: function() { + var clone = new this.constructor() + clone.value = array_clone(this.value) + return clone } - /*** - Easing Functions - ================ - ***/ +}) +// Poly points array +SVG.PointArray = function(array, fallback) { + SVG.Array.call(this, array, fallback || [[0,0]]) +} - class Ease extends Stepper { - constructor(fn = timeline.ease) { - super(); - this.ease = easing[fn] || fn; - } +// Inherit from SVG.Array +SVG.PointArray.prototype = new SVG.Array +SVG.PointArray.prototype.constructor = SVG.PointArray - step(from, to, pos) { - if (typeof from !== 'number') { - return pos < 1 ? from : to; - } - - return from + (to - from) * this.ease(pos); - } +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(' ') } - /*** - Controller Types - ================ - ***/ - - class Controller extends Stepper { - constructor(fn) { - super(); - this.stepper = fn; - } - - done(c) { - return c.done; - } - - step(current, target, dt, c) { - return this.stepper(current, target, dt, c); - } - - } - - function recalculate() { - // Apply the default parameters - const duration = (this._duration || 500) / 1000; - const overshoot = this._overshoot || 0; // Calculate the PID natural response - - const eps = 1e-10; - const pi = Math.PI; - const os = Math.log(overshoot / 100 + eps); - const zeta = -os / Math.sqrt(pi * pi + os * os); - const wn = 3.9 / (zeta * duration); // Calculate the Spring values - - this.d = 2 * zeta * wn; - this.k = wn * wn; - } - - class Spring extends Controller { - constructor(duration = 500, overshoot = 0) { - super(); - this.duration(duration).overshoot(overshoot); - } - - step(current, target, dt, c) { - if (typeof current === 'string') return current; - c.done = dt === Infinity; - if (dt === Infinity) return target; - if (dt === 0) return current; - if (dt > 100) dt = 16; - dt /= 1000; // Get the previous velocity - - const velocity = c.velocity || 0; // Apply the control to get the new position and store it - - const acceleration = -this.d * velocity - this.k * (current - target); - const newPosition = current + velocity * dt + acceleration * dt * dt / 2; // Store the velocity - - c.velocity = velocity + acceleration * dt; // Figure out if we have converged, and if so, pass the value - - c.done = Math.abs(target - newPosition) + Math.abs(velocity) < 0.002; - return c.done ? target : newPosition; - } - - } - extend(Spring, { - duration: makeSetterGetter('_duration', recalculate), - overshoot: makeSetterGetter('_overshoot', recalculate) - }); - class PID extends Controller { - constructor(p = 0.1, i = 0.01, d = 0, windup = 1000) { - super(); - this.p(p).i(i).d(d).windup(windup); - } - - step(current, target, dt, c) { - if (typeof current === 'string') return current; - c.done = dt === Infinity; - if (dt === Infinity) return target; - if (dt === 0) return current; - const p = target - current; - let i = (c.integral || 0) + p * dt; - const d = (p - (c.error || 0)) / dt; - const windup = this._windup; // antiwindup - - if (windup !== false) { - i = Math.max(-windup, Math.min(i, windup)); - } - - c.error = p; - c.integral = i; - c.done = Math.abs(p) < 0.001; - return c.done ? target : current + (this.P * p + this.I * i + this.D * d); - } - - } - extend(PID, { - windup: makeSetterGetter('_windup'), - p: makeSetterGetter('P'), - i: makeSetterGetter('I'), - d: makeSetterGetter('D') - }); - - const segmentParameters = { - M: 2, - L: 2, - H: 1, - V: 1, - C: 6, - S: 4, - Q: 4, - T: 2, - A: 7, - Z: 0 - }; - const 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]]; - } - }; - const mlhvqtcsaz = 'mlhvqtcsaz'.split(''); - - for (let 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 (let 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()); - } - - function makeAbsolut(parser) { - const command = parser.segment[0]; - return pathHandlers[command](parser.segment.slice(1), parser.p, parser.p0); - } - - function segmentComplete(parser) { - return parser.segment.length && parser.segment.length - 1 === segmentParameters[parser.segment[0].toUpperCase()]; - } - - function startNewSegment(parser, token) { - parser.inNumber && finalizeNumber(parser, false); - const pathLetter = isPathLetter.test(token); - - if (pathLetter) { - parser.segment = [token]; - } else { - const lastCommand = parser.lastCommand; - const small = lastCommand.toLowerCase(); - const isSmall = lastCommand === small; - parser.segment = [small === 'm' ? isSmall ? 'l' : 'L' : lastCommand]; - } - - parser.inSegment = true; - parser.lastCommand = parser.segment[0]; - return pathLetter; - } - - function finalizeNumber(parser, inNumber) { - if (!parser.inNumber) throw new Error('Parser Error'); - parser.number && parser.segment.push(parseFloat(parser.number)); - parser.inNumber = inNumber; - parser.number = ''; - parser.pointSeen = false; - parser.hasExponent = false; - - if (segmentComplete(parser)) { - finalizeSegment(parser); - } - } - - function finalizeSegment(parser) { - parser.inSegment = false; - - if (parser.absolute) { - parser.segment = makeAbsolut(parser); - } - - parser.segments.push(parser.segment); - } - - function isArcFlag(parser) { - if (!parser.segment.length) return false; - const isArc = parser.segment[0].toUpperCase() === 'A'; - const length = parser.segment.length; - return isArc && (length === 4 || length === 5); - } - - function isExponential(parser) { - return parser.lastToken.toUpperCase() === 'E'; - } - - function pathParser(d, toAbsolute = true) { - let index = 0; - let token = ''; - const parser = { - segment: [], - inNumber: false, - number: '', - lastToken: '', - inSegment: false, - segments: [], - pointSeen: false, - hasExponent: false, - absolute: toAbsolute, - p0: new Point(), - p: new Point() - }; - - while (parser.lastToken = token, token = d.charAt(index++)) { - if (!parser.inSegment) { - if (startNewSegment(parser, token)) { - continue; - } - } - - if (token === '.') { - if (parser.pointSeen || parser.hasExponent) { - finalizeNumber(parser, false); - --index; - continue; - } - - parser.inNumber = true; - parser.pointSeen = true; - parser.number += token; - continue; - } - - if (!isNaN(parseInt(token))) { - if (parser.number === '0' || isArcFlag(parser)) { - parser.inNumber = true; - parser.number = token; - finalizeNumber(parser, true); - continue; - } - - parser.inNumber = true; - parser.number += token; - continue; - } - - if (token === ' ' || token === ',') { - if (parser.inNumber) { - finalizeNumber(parser, false); - } - - continue; - } - - if (token === '-') { - if (parser.inNumber && !isExponential(parser)) { - finalizeNumber(parser, false); - --index; - continue; - } - - parser.number += token; - parser.inNumber = true; - continue; - } - - if (token.toUpperCase() === 'E') { - parser.number += token; - parser.hasExponent = true; - continue; - } - - if (isPathLetter.test(token)) { - if (parser.inNumber) { - finalizeNumber(parser, false); - } else if (!segmentComplete(parser)) { - throw new Error('parser Error'); - } else { - finalizeSegment(parser); - } - - --index; - } - } - - if (parser.inNumber) { - finalizeNumber(parser, false); - } - - if (parser.inSegment && segmentComplete(parser)) { - finalizeSegment(parser); - } - - return parser.segments; - } - - function arrayToString(a) { - let s = ''; - - for (let i = 0, il = a.length; 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 + ' '; - } - - class PathArray extends SVGArray { - // Get bounding box of path - bbox() { - parser().path.setAttribute('d', this.toString()); - return new Box(parser.nodes.path.getBBox()); - } // Move path string - - - move(x, y) { - // get bounding box of current situation - const box = this.bbox(); // get relative offset - - x -= box.x; - y -= box.y; - - if (!isNaN(x) && !isNaN(y)) { - // move every point - for (let l, i = this.length - 1; i >= 0; i--) { - l = this[i][0]; - - if (l === 'M' || l === 'L' || l === 'T') { - this[i][1] += x; - this[i][2] += y; - } else if (l === 'H') { - this[i][1] += x; - } else if (l === 'V') { - this[i][1] += y; - } else if (l === 'C' || l === 'S' || l === 'Q') { - this[i][1] += x; - this[i][2] += y; - this[i][3] += x; - this[i][4] += y; - - if (l === 'C') { - this[i][5] += x; - this[i][6] += y; - } - } else if (l === 'A') { - this[i][6] += x; - this[i][7] += y; - } - } - } - - return this; - } // Absolutize and parse path to array - - - parse(d = 'M0 0') { - if (Array.isArray(d)) { - d = Array.prototype.concat.apply([], d).toString(); - } - - return pathParser(d); - } // Resize path string - - - size(width, height) { - // get bounding box of current situation - const box = this.bbox(); - let i, l; // If the box width or height is 0 then we ignore - // transformations on the respective axis - - box.width = box.width === 0 ? 1 : box.width; - box.height = box.height === 0 ? 1 : box.height; // recalculate position of all points according to new size - - for (i = this.length - 1; i >= 0; i--) { - l = this[i][0]; - - if (l === 'M' || l === 'L' || l === 'T') { - this[i][1] = (this[i][1] - box.x) * width / box.width + box.x; - this[i][2] = (this[i][2] - box.y) * height / box.height + box.y; - } else if (l === 'H') { - this[i][1] = (this[i][1] - box.x) * width / box.width + box.x; - } else if (l === 'V') { - this[i][1] = (this[i][1] - box.y) * height / box.height + box.y; - } else if (l === 'C' || l === 'S' || l === 'Q') { - this[i][1] = (this[i][1] - box.x) * width / box.width + box.x; - this[i][2] = (this[i][2] - box.y) * height / box.height + box.y; - this[i][3] = (this[i][3] - box.x) * width / box.width + box.x; - this[i][4] = (this[i][4] - box.y) * height / box.height + box.y; - - if (l === 'C') { - this[i][5] = (this[i][5] - box.x) * width / box.width + box.x; - this[i][6] = (this[i][6] - box.y) * height / box.height + box.y; - } - } else if (l === 'A') { - // resize radii - this[i][1] = this[i][1] * width / box.width; - this[i][2] = this[i][2] * height / box.height; // move position values - - this[i][6] = (this[i][6] - box.x) * width / box.width + box.x; - this[i][7] = (this[i][7] - box.y) * height / box.height + box.y; - } - } - - return this; - } // Convert array to string - - - toString() { - return arrayToString(this); - } - - } - - const getClassForType = value => { - const type = typeof value; - - if (type === 'number') { - return SVGNumber; - } else if (type === 'string') { - if (Color.isColor(value)) { - return Color; - } else if (delimiter.test(value)) { - return isPathLetter.test(value) ? PathArray : SVGArray; - } else if (numberAndUnit.test(value)) { - return SVGNumber; - } else { - return NonMorphable; - } - } else if (morphableTypes.indexOf(value.constructor) > -1) { - return value.constructor; - } else if (Array.isArray(value)) { - return SVGArray; - } else if (type === 'object') { - return ObjectBag; - } else { - return NonMorphable; - } - }; - - class Morphable { - constructor(stepper) { - this._stepper = stepper || new Ease('-'); - this._from = null; - this._to = null; - this._type = null; - this._context = null; - this._morphObj = null; - } - - at(pos) { - return this._morphObj.morph(this._from, this._to, pos, this._stepper, this._context); - } - - done() { - const complete = this._context.map(this._stepper.done).reduce(function (last, curr) { - return last && curr; - }, true); - - return complete; - } - - from(val) { - if (val == null) { - return this._from; - } - - this._from = this._set(val); - return this; - } - - stepper(stepper) { - if (stepper == null) return this._stepper; - this._stepper = stepper; - return this; - } - - to(val) { - if (val == null) { - return this._to; - } - - this._to = this._set(val); - return this; - } - - type(type) { - // getter - if (type == null) { - return this._type; - } // setter - - - this._type = type; - return this; - } - - _set(value) { - if (!this._type) { - this.type(getClassForType(value)); - } - - let result = new this._type(value); - - if (this._type === Color) { - result = this._to ? result[this._to[4]]() : this._from ? result[this._from[4]]() : result; - } - - if (this._type === ObjectBag) { - result = this._to ? result.align(this._to) : this._from ? result.align(this._from) : result; - } - - result = result.toConsumable(); - this._morphObj = this._morphObj || new this._type(); - this._context = this._context || Array.apply(null, Array(result.length)).map(Object).map(function (o) { - o.done = true; - return o; - }); - return result; - } - - } - class NonMorphable { - constructor(...args) { - this.init(...args); - } - - init(val) { - val = Array.isArray(val) ? val[0] : val; - this.value = val; - return this; - } - - toArray() { - return [this.value]; - } - - valueOf() { - return this.value; - } - - } - class TransformBag { - constructor(...args) { - this.init(...args); - } - - init(obj) { - if (Array.isArray(obj)) { - obj = { - scaleX: obj[0], - scaleY: obj[1], - shear: obj[2], - rotate: obj[3], - translateX: obj[4], - translateY: obj[5], - originX: obj[6], - originY: obj[7] - }; - } - - Object.assign(this, TransformBag.defaults, obj); - return this; - } - - toArray() { - const v = this; - return [v.scaleX, v.scaleY, v.shear, v.rotate, v.translateX, v.translateY, v.originX, v.originY]; - } - - } - TransformBag.defaults = { - scaleX: 1, - scaleY: 1, - shear: 0, - rotate: 0, - translateX: 0, - translateY: 0, - originX: 0, - originY: 0 - }; - - const sortByKey = (a, b) => { - return a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0; - }; - - class ObjectBag { - constructor(...args) { - this.init(...args); - } - - align(other) { - const values = this.values; - - for (let i = 0, il = values.length; i < il; ++i) { - // If the type is the same we only need to check if the color is in the correct format - if (values[i + 1] === other[i + 1]) { - if (values[i + 1] === Color && other[i + 7] !== values[i + 7]) { - const space = other[i + 7]; - const color = new Color(this.values.splice(i + 3, 5))[space]().toArray(); - this.values.splice(i + 3, 0, ...color); - } - - i += values[i + 2] + 2; - continue; - } - - if (!other[i + 1]) { - return this; - } // The types differ, so we overwrite the new type with the old one - // And initialize it with the types default (e.g. black for color or 0 for number) - - - const defaultObject = new other[i + 1]().toArray(); // Than we fix the values array - - const toDelete = values[i + 2] + 3; - values.splice(i, toDelete, other[i], other[i + 1], other[i + 2], ...defaultObject); - i += values[i + 2] + 2; - } - - return this; - } - - init(objOrArr) { - this.values = []; - - if (Array.isArray(objOrArr)) { - this.values = objOrArr.slice(); - return; - } - - objOrArr = objOrArr || {}; - const entries = []; - - for (const i in objOrArr) { - const Type = getClassForType(objOrArr[i]); - const val = new Type(objOrArr[i]).toArray(); - entries.push([i, Type, val.length, ...val]); - } - - entries.sort(sortByKey); - this.values = entries.reduce((last, curr) => last.concat(curr), []); - return this; - } - - toArray() { - return this.values; - } - - valueOf() { - const obj = {}; - const arr = this.values; // for (var i = 0, len = arr.length; i < len; i += 2) { - - while (arr.length) { - const key = arr.shift(); - const Type = arr.shift(); - const num = arr.shift(); - const values = arr.splice(0, num); - obj[key] = new Type(values); // .valueOf() - } - - return obj; - } - - } - const morphableTypes = [NonMorphable, TransformBag, ObjectBag]; - function registerMorphableType(type = []) { - morphableTypes.push(...[].concat(type)); - } - function makeMorphable() { - extend(morphableTypes, { - to(val) { - return new Morphable().type(this.constructor).from(this.toArray()) // this.valueOf()) - .to(val); - }, - - fromArray(arr) { - this.init(arr); - return this; - }, - - toConsumable() { - return this.toArray(); - }, - - morph(from, to, pos, stepper, context) { - const mapper = function (i, index) { - return stepper.step(i, to[index], pos, context[index], context); - }; - - return this.fromArray(from.map(mapper)); - } - - }); - } - - class Path extends Shape { - // Initialize node - constructor(node, attrs = node) { - super(nodeOrNew('path', node), attrs); - } // Get array - - - array() { - return this._array || (this._array = new PathArray(this.attr('d'))); - } // Clear array cache - - - clear() { - delete this._array; - return this; - } // Set height of element - - - height(height) { - return height == null ? this.bbox().height : this.size(this.bbox().width, height); - } // Move by left top corner - - - move(x, y) { - return this.attr('d', this.array().move(x, y)); - } // Plot new path - - - plot(d) { - return d == null ? this.array() : this.clear().attr('d', typeof d === 'string' ? d : this._array = new PathArray(d)); - } // Set element size to given width and height - - - size(width, height) { - const p = proportionalSize(this, width, height); - return this.attr('d', this.array().size(p.width, p.height)); - } // Set width of element - - - width(width) { - return width == null ? this.bbox().width : this.size(width, this.bbox().height); - } // Move by left top corner over x-axis - - - x(x) { - return x == null ? this.bbox().x : this.move(x, this.bbox().y); - } // Move by left top corner over y-axis - - - y(y) { - return y == null ? this.bbox().y : this.move(this.bbox().x, y); - } - - } // Define morphable array - - Path.prototype.MorphArray = PathArray; // Add parent method - - registerMethods({ - Container: { - // Create a wrapped path element - path: wrapWithAttrCheck(function (d) { - // make sure plot is called as a setter - return this.put(new Path()).plot(d || new PathArray()); - }) - } - }); - register(Path, 'Path'); - - function array() { - return this._array || (this._array = new PointArray(this.attr('points'))); - } // Clear array cache - - function clear() { - delete this._array; - return this; - } // Move by left top corner - - function move$2(x, y) { - return this.attr('points', this.array().move(x, y)); - } // Plot new path - - function plot(p) { - return p == null ? this.array() : this.clear().attr('points', typeof p === 'string' ? p : this._array = new PointArray(p)); - } // Set element size to given width and height - - function size$1(width, height) { - const p = proportionalSize(this, width, height); - return this.attr('points', this.array().size(p.width, p.height)); - } - - var poly = { - __proto__: null, - array: array, - clear: clear, - move: move$2, - plot: plot, - size: size$1 - }; - - class Polygon extends Shape { - // Initialize node - constructor(node, attrs = node) { - super(nodeOrNew('polygon', node), attrs); - } - - } - registerMethods({ - Container: { - // Create a wrapped polygon element - polygon: wrapWithAttrCheck(function (p) { - // make sure plot is called as a setter - return this.put(new Polygon()).plot(p || new PointArray()); - }) - } - }); - extend(Polygon, pointed); - extend(Polygon, poly); - register(Polygon, 'Polygon'); - - class Polyline extends Shape { - // Initialize node - constructor(node, attrs = node) { - super(nodeOrNew('polyline', node), attrs); - } - - } - registerMethods({ - Container: { - // Create a wrapped polygon element - polyline: wrapWithAttrCheck(function (p) { - // make sure plot is called as a setter - return this.put(new Polyline()).plot(p || new PointArray()); - }) - } - }); - extend(Polyline, pointed); - extend(Polyline, poly); - register(Polyline, 'Polyline'); - - class Rect extends Shape { - // Initialize node - constructor(node, attrs = node) { - super(nodeOrNew('rect', node), attrs); - } - - } - extend(Rect, { - rx, - ry - }); - registerMethods({ - Container: { - // Create a rect element - rect: wrapWithAttrCheck(function (width, height) { - return this.put(new Rect()).size(width, height); - }) - } - }); - register(Rect, 'Rect'); - - class Queue { - constructor() { - this._first = null; - this._last = null; - } // Shows us the first item in the list - - - first() { - return this._first && this._first.value; - } // Shows us the last item in the list - - - last() { - return this._last && this._last.value; - } - - push(value) { - // An item stores an id and the provided value - const item = typeof value.next !== 'undefined' ? value : { - value: value, - next: null, - prev: null - }; // Deal with the queue being empty or populated - - if (this._last) { - item.prev = this._last; - this._last.next = item; - this._last = item; - } else { - this._last = item; - this._first = item; - } // Return the current item - - - return item; - } // Removes the item that was returned from the push - - - remove(item) { - // Relink the previous item - if (item.prev) item.prev.next = item.next; - if (item.next) item.next.prev = item.prev; - if (item === this._last) this._last = item.prev; - if (item === this._first) this._first = item.next; // Invalidate item - - item.prev = null; - item.next = null; - } - - shift() { - // Check if we have a value - const remove = this._first; - if (!remove) return null; // If we do, remove it and relink things - - this._first = remove.next; - if (this._first) this._first.prev = null; - this._last = this._first ? this._last : null; - return remove.value; - } - - } - - const Animator = { - nextDraw: null, - frames: new Queue(), - timeouts: new Queue(), - immediates: new Queue(), - timer: () => globals.window.performance || globals.window.Date, - transforms: [], - - frame(fn) { - // Store the node - const node = Animator.frames.push({ - run: fn - }); // Request an animation frame if we don't have one - - if (Animator.nextDraw === null) { - Animator.nextDraw = globals.window.requestAnimationFrame(Animator._draw); - } // Return the node so we can remove it easily - - - return node; - }, - - timeout(fn, delay) { - delay = delay || 0; // Work out when the event should fire - - const time = Animator.timer().now() + delay; // Add the timeout to the end of the queue - - const node = Animator.timeouts.push({ - run: fn, - time: time - }); // Request another animation frame if we need one - - if (Animator.nextDraw === null) { - Animator.nextDraw = globals.window.requestAnimationFrame(Animator._draw); - } - - return node; - }, - - immediate(fn) { - // Add the immediate fn to the end of the queue - const node = Animator.immediates.push(fn); // Request another animation frame if we need one - - if (Animator.nextDraw === null) { - Animator.nextDraw = globals.window.requestAnimationFrame(Animator._draw); - } - - return node; - }, - - cancelFrame(node) { - node != null && Animator.frames.remove(node); - }, - - clearTimeout(node) { - node != null && Animator.timeouts.remove(node); - }, - - cancelImmediate(node) { - node != null && Animator.immediates.remove(node); - }, - - _draw(now) { - // Run all the timeouts we can run, if they are not ready yet, add them - // to the end of the queue immediately! (bad timeouts!!! [sarcasm]) - let nextTimeout = null; - const lastTimeout = Animator.timeouts.last(); - - while (nextTimeout = Animator.timeouts.shift()) { - // Run the timeout if its time, or push it to the end - if (now >= nextTimeout.time) { - nextTimeout.run(); - } else { - Animator.timeouts.push(nextTimeout); - } // If we hit the last item, we should stop shifting out more items - - - if (nextTimeout === lastTimeout) break; - } // Run all of the animation frames - - - let nextFrame = null; - const lastFrame = Animator.frames.last(); - - while (nextFrame !== lastFrame && (nextFrame = Animator.frames.shift())) { - nextFrame.run(now); - } - - let nextImmediate = null; - - while (nextImmediate = Animator.immediates.shift()) { - nextImmediate(); - } // If we have remaining timeouts or frames, draw until we don't anymore - - - Animator.nextDraw = Animator.timeouts.first() || Animator.frames.first() ? globals.window.requestAnimationFrame(Animator._draw) : null; - } - - }; - - const makeSchedule = function (runnerInfo) { - const start = runnerInfo.start; - const duration = runnerInfo.runner.duration(); - const end = start + duration; + // Convert array to line object +, toLine: function() { return { - start: start, - duration: duration, - end: end, - runner: runnerInfo.runner - }; - }; - - const defaultSource = function () { - const w = globals.window; - return (w.performance || w.Date).now(); - }; - - class Timeline extends EventTarget { - // Construct a new timeline on the given element - constructor(timeSource = defaultSource) { - super(); - this._timeSource = timeSource; // Store the timing variables - - this._startTime = 0; - this._speed = 1.0; // Determines how long a runner is hold in memory. Can be a dt or true/false - - this._persist = 0; // Keep track of the running animations and their starting parameters - - this._nextFrame = null; - this._paused = true; - this._runners = []; - this._runnerIds = []; - this._lastRunnerId = -1; - this._time = 0; - this._lastSourceTime = 0; - this._lastStepTime = 0; // Make sure that step is always called in class context - - this._step = this._stepFn.bind(this, false); - this._stepImmediate = this._stepFn.bind(this, true); + 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 - active() { - return !!this._nextFrame; - } + // 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 + ]) - finish() { - // Go to end and pause - this.time(this.getEndTimeOfTimeline() + 1); - return this.pause(); - } // Calculates the end of the timeline + return new SVG.PointArray(array) + } + // Parse point string and flat array +, parse: function(array) { + var points = [] + array = array.valueOf() - getEndTime() { - const lastRunnerInfo = this.getLastRunnerInfo(); - const lastDuration = lastRunnerInfo ? lastRunnerInfo.runner.duration() : 0; - const lastStartTime = lastRunnerInfo ? lastRunnerInfo.start : this._time; - return lastStartTime + lastDuration; - } - - getEndTimeOfTimeline() { - const endTimes = this._runners.map(i => i.start + i.runner.duration()); - - return Math.max(0, ...endTimes); - } - - getLastRunnerInfo() { - return this.getRunnerInfoById(this._lastRunnerId); - } - - getRunnerInfoById(id) { - return this._runners[this._runnerIds.indexOf(id)] || null; - } - - pause() { - this._paused = true; - return this._continue(); - } - - persist(dtOrForever) { - if (dtOrForever == null) return this._persist; - this._persist = dtOrForever; - return this; - } - - play() { - // Now make sure we are not paused and continue the animation - this._paused = false; - return this.updateTime()._continue(); - } - - reverse(yes) { - const currentSpeed = this.speed(); - if (yes == null) return this.speed(-currentSpeed); - const positive = Math.abs(currentSpeed); - return this.speed(yes ? -positive : positive); - } // schedules a runner on the timeline - - - schedule(runner, delay, when) { - if (runner == null) { - return this._runners.map(makeSchedule); - } // 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 directly - - - let absoluteStartTime = 0; - const endTime = this.getEndTime(); - delay = delay || 0; // Work out when to start the animation - - if (when == null || when === 'last' || when === 'after') { - // Take the last time and increment - absoluteStartTime = endTime; - } else if (when === 'absolute' || when === 'start') { - absoluteStartTime = delay; - delay = 0; - } else if (when === 'now') { - absoluteStartTime = this._time; - } else if (when === 'relative') { - const runnerInfo = this.getRunnerInfoById(runner.id); - - if (runnerInfo) { - absoluteStartTime = runnerInfo.start + delay; - delay = 0; - } - } else if (when === 'with-last') { - const lastRunnerInfo = this.getLastRunnerInfo(); - const lastStartTime = lastRunnerInfo ? lastRunnerInfo.start : this._time; - absoluteStartTime = lastStartTime; - } else { - throw new Error('Invalid value for the "when" parameter'); - } // Manage runner - - - runner.unschedule(); - runner.timeline(this); - const persist = runner.persist(); - const runnerInfo = { - persist: persist === null ? this._persist : persist, - start: absoluteStartTime + delay, - runner - }; - this._lastRunnerId = runner.id; - - this._runners.push(runnerInfo); - - this._runners.sort((a, b) => a.start - b.start); - - this._runnerIds = this._runners.map(info => info.runner.id); - - this.updateTime()._continue(); - - return this; - } - - seek(dt) { - return this.time(this._time + dt); - } - - source(fn) { - if (fn == null) return this._timeSource; - this._timeSource = fn; - return this; - } - - speed(speed) { - if (speed == null) return this._speed; - this._speed = speed; - return this; - } - - stop() { - // Go to start and pause - this.time(0); - return this.pause(); - } - - time(time) { - if (time == null) return this._time; - this._time = time; - return this._continue(true); - } // Remove the runner from this timeline - - - unschedule(runner) { - const index = this._runnerIds.indexOf(runner.id); - - if (index < 0) return this; - - this._runners.splice(index, 1); - - this._runnerIds.splice(index, 1); - - runner.timeline(null); - return this; - } // Makes sure, that after pausing the time doesn't jump - - - updateTime() { - if (!this.active()) { - this._lastSourceTime = this._timeSource(); + // 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])) { + // make sure to use a clone + return array.map(function (el) { return el.slice() }) + } else if (array[0].x != null){ + // allow point objects to be passed + return array.map(function (el) { return [el.x, el.y] }) } - - return this; - } // Checks if we are running and continues the animation - - - _continue(immediateStep = false) { - Animator.cancelFrame(this._nextFrame); - this._nextFrame = null; - if (immediateStep) return this._stepImmediate(); - if (this._paused) return this; - this._nextFrame = Animator.frame(this._step); - return this; + } else { // Else, it is considered as a string + // parse points + array = array.trim().split(SVG.regex.delimiter).map(parseFloat) } - _stepFn(immediateStep = false) { - // Get the time delta from the last time and update the time - const time = this._timeSource(); + // 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() - let dtSource = time - this._lastSourceTime; - if (immediateStep) dtSource = 0; - const dtTime = this._speed * dtSource + (this._time - this._lastStepTime); - this._lastSourceTime = time; // Only update the time if we use the timeSource. - // Otherwise use the current time + // 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] ]) - if (!immediateStep) { - // Update the time - this._time += dtTime; - this._time = this._time < 0 ? 0 : this._time; + 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, 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() { + SVG.parser.poly.setAttribute('points', this.toString()) + + return SVG.parser.poly.getBBox() + } +}) + +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 mlhvqtcsa = 'mlhvqtcsaz'.split('') + +for(var i = 0, il = mlhvqtcsa.length; i < il; ++i){ + pathHandlers[mlhvqtcsa[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 } - - this._lastStepTime = this._time; - this.fire('time', this._time); // This is for the case that the timeline was seeked so that the time - // is now before the startTime of the runner. Thats why we need to set - // the runner to position 0 - // FIXME: - // However, reseting in insertion order leads to bugs. Considering the case, - // where 2 runners change the same attribute but in different times, - // reseting both of them will lead to the case where the later defined - // runner always wins the reset even if the other runner started earlier - // and therefore should win the attribute battle - // this can be solved by reseting them backwards - - for (let k = this._runners.length; k--;) { - // Get and run the current runner and ignore it if its inactive - const runnerInfo = this._runners[k]; - const runner = runnerInfo.runner; // Make sure that we give the actual difference - // between runner start time and now - - const dtToStart = this._time - runnerInfo.start; // Dont run runner if not started yet - // and try to reset it - - if (dtToStart <= 0) { - runner.reset(); - } - } // Run all of the runners directly - - - let runnersLeft = false; - - for (let i = 0, len = this._runners.length; i < len; i++) { - // Get and run the current runner and ignore it if its inactive - const runnerInfo = this._runners[i]; - const runner = runnerInfo.runner; - let dt = dtTime; // Make sure that we give the actual difference - // between runner start time and now - - const dtToStart = this._time - runnerInfo.start; // Dont run runner if not started yet - - if (dtToStart <= 0) { - runnersLeft = true; - continue; - } else if (dtToStart < dt) { - // Adjust dt to make sure that animation is on point - dt = dtToStart; + else + for(var j = 0, jl = c.length; j < jl; ++j) { + c[j] = c[j] + (j%2 ? p.y : p.x) } - if (!runner.active()) continue; // If this runner is still going, signal that we need another animation - // frame, otherwise, remove the completed runner + return pathHandlers[i](c, p, p0) + } + })(mlhvqtcsa[i].toUpperCase()) +} - const finished = runner.step(dt).done; +// Path points array +SVG.PathArray = function(array, fallback) { + SVG.Array.call(this, array, fallback || [['M', 0, 0]]) +} - if (!finished) { - runnersLeft = true; // continue - } else if (runnerInfo.persist !== true) { - // runner is finished. And runner might get removed - const endTime = runner.duration() - runner.time() + this._time; +// Inherit from SVG.Array +SVG.PathArray.prototype = new SVG.Array +SVG.PathArray.prototype.constructor = SVG.PathArray - if (endTime + runnerInfo.persist < this._time) { - // Delete runner and correct index - runner.unschedule(); - --i; - --len; +SVG.extend(SVG.PathArray, { + // Convert array to string + toString: function() { + return arrayToString(this.value) + } + // 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 } - } // Basically: we continue when there are runners right from us in time - // when -->, and when runners are left from us when <-- + + } + } + + return this + } + // Resize path string +, size: function(width, height) { + // get bounding box of current situation + var i, l, box = this.bbox() + + // 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 + , destinationArray = this.destination.value + , array = [], pathArray = new SVG.PathArray() + , 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 i, x0, y0, s, seg, arr + , x = 0 + , y = 0 + , 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 arr = [] + , p = new SVG.Point() + , p0 = new SVG.Point() + , index = 0 + , 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' + } + + arr.push(pathHandlers[s].call(null, + array.slice(index, (index = index + paramCnt[s.toUpperCase()])).map(parseFloat), + p, p0 + ) + ) + + }while(len > index) + + return arr + + } + // Get bounding box of path +, bbox: function() { + SVG.parser.path.setAttribute('d', this.toString()) + + return SVG.parser.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 + 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) + } + + } +}) - if (runnersLeft && !(this._speed < 0 && this._time === 0) || this._runnerIds.length && this._speed < 0 && this._time > 0) { - this._continue(); +SVG.Element = SVG.invent({ + // Initialize node + create: function(node) { + // make stroke value accessible dynamically + this._stroke = SVG.defaults.attrs.stroke + this._event = null + this._events = {} + + // initialize data object + this.dom = {} + + // create circular reference + if (this.node = node) { + this.type = node.nodeName + this.node.instance = this + this._events = node._events || {} + + // store current attribute value + this._stroke = node.getAttribute('stroke') || this._stroke + } + } + + // 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 parent.put(this) + } + // Add element to given container and return container + , putIn: function(parent) { + return parent.add(this) + } + // Get / set id + , id: function(id) { + 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.style('display', '') + } + // Hide element + , hide: function() { + return this.style('display', 'none') + } + // Is element visible? + , visible: function() { + return this.style('display') != 'none' + } + // Return id on string conversion + , toString: function() { + return this.attr('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 + if(!parent.node.parentNode || parent.node.parentNode.nodeName == '#document' || parent.node.parentNode.nodeName == '#document-fragment') return null // #759, #720 + parent = SVG.adopt(parent.node.parentNode) + } + } + // Get parent document + , doc: function() { + return this instanceof SVG.Doc ? this : this.parent(SVG.Doc) + } + // return array of all ancestors of given type up to the root svg + , parents: function(type) { + var parents = [], 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) { + // create temporary holder + var well = document.createElement('svg') + + // act as a setter if svg is given + if (svg && this instanceof SVG.Parent) { + // dump raw svg + well.innerHTML = '' + svg.replace(/\n/, '').replace(/<([\w:-]+)([^<]+?)\/>/g, '<$1$2>') + '' + + // transplant nodes + for (var i = 0, il = well.firstChild.childNodes.length; i < il; i++) + this.node.appendChild(well.firstChild.firstChild) + + // otherwise act as a getter } else { - this.pause(); - this.fire('finished'); + // create a wrapping svg element in case of partial content + well.appendChild(svg = document.createElement('svg')) + + // write svgjs data to the dom + this.writeDataToDom() + + // insert a copy of this node + svg.appendChild(this.node.cloneNode(true)) + + // return target element + return well.innerHTML.replace(/^/, '').replace(/<\/svg>$/, '') } - return this; + return this + } + // write svgjs data to the dom + , writeDataToDom: function() { + + // dump variables recursively + if(this.each || this.lines){ + var fn = this.each ? this : this.lines(); + fn.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) + } + } +}) + +SVG.easing = { + '-': function(pos){return pos} +, '<>':function(pos){return -Math.cos(pos * Math.PI) / 2 + 0.5} +, '>': function(pos){return Math.sin(pos * Math.PI / 2)} +, '<': function(pos){return -Math.cos(pos * Math.PI / 2) + 1} +} + +SVG.morph = function(pos){ + return function(from, to) { + return new SVG.MorphObj(from, to).at(pos) + } +} + +SVG.Situation = SVG.invent({ + + create: function(o){ + this.init = false + this.reversed = false + this.reversing = false + + this.duration = new SVG.Number(o.duration).valueOf() + this.delay = new SVG.Number(o.delay).valueOf() + + this.start = +new Date() + this.delay + this.finish = this.start + this.duration + this.ease = o.ease + + // this.loop is incremented from 0 to this.loops + // it is also incremented when in an infinite loop (when this.loops is true) + this.loop = 0 + this.loops = false + + this.animations = { + // functionToCall: [list of morphable objects] + // e.g. move: [SVG.Number, SVG.Number] + } + + this.attrs = { + // holds all attributes which are not represented from a function svg.js provides + // e.g. someAttr: SVG.Number + } + + this.styles = { + // holds all styles which should be animated + // e.g. fill-color: SVG.Color + } + + this.transforms = [ + // holds all transformations as transformation objects + // e.g. [SVG.Rotate, SVG.Translate, SVG.Matrix] + ] + + this.once = { + // functions to fire at a specific position + // e.g. "0.5": function foo(){} } } - registerMethods({ - Element: { - timeline: function (timeline) { - if (timeline == null) { - this._timeline = this._timeline || new Timeline(); - return this._timeline; - } else { - this._timeline = timeline; - return this; - } - } - } - }); - class Runner extends EventTarget { - constructor(options) { - super(); // Store a unique id on the runner, so that we can identify it later +}) - this.id = Runner.id++; // Ensure a default value - options = options == null ? timeline.duration : options; // Ensure that we get a controller +SVG.FX = SVG.invent({ - options = typeof options === 'function' ? new Controller(options) : options; // Declare all of the variables - - this._element = null; - this._timeline = null; - this.done = false; - this._queue = []; // Work out the stepper and the duration - - this._duration = typeof options === 'number' && options; - this._isDeclarative = options instanceof Controller; - this._stepper = this._isDeclarative ? options : new Ease(); // We copy the current values from the timeline because they can change - - this._history = {}; // Store the state of the runner - - this.enabled = true; - this._time = 0; - this._lastTime = 0; // At creation, the runner is in reseted state - - this._reseted = true; // Save transforms applied to this runner - - this.transforms = new Matrix(); - this.transformId = 1; // Looping variables - - this._haveReversed = false; - this._reverse = false; - this._loopsDone = 0; - this._swing = false; - this._wait = 0; - this._times = 1; - this._frameId = null; // Stores how long a runner is stored after beeing done - - this._persist = this._isDeclarative ? true : null; - } - - static sanitise(duration, delay, when) { - // Initialise the default parameters - let times = 1; - let swing = false; - let wait = 0; - duration = duration || timeline.duration; - delay = delay || timeline.delay; - when = when || 'last'; // If we have an object, unpack the values - - if (typeof duration === 'object' && !(duration instanceof Stepper)) { - delay = duration.delay || delay; - when = duration.when || when; - swing = duration.swing || swing; - times = duration.times || times; - wait = duration.wait || wait; - duration = duration.duration || timeline.duration; - } - - return { - duration: duration, - delay: delay, - swing: swing, - times: times, - wait: wait, - when: when - }; - } - - active(enabled) { - if (enabled == null) return this.enabled; - this.enabled = enabled; - return this; - } - /* - Private Methods - =============== - Methods that shouldn't be used externally - */ - - - addTransform(transform, index) { - this.transforms.lmultiplyO(transform); - return this; - } - - after(fn) { - return this.on('finished', fn); - } - - animate(duration, delay, when) { - const o = Runner.sanitise(duration, delay, when); - const runner = new Runner(o.duration); - if (this._timeline) runner.timeline(this._timeline); - if (this._element) runner.element(this._element); - return runner.loop(o).schedule(o.delay, o.when); - } - - clearTransform() { - this.transforms = new Matrix(); - return this; - } // TODO: Keep track of all transformations so that deletion is faster - - - clearTransformsFromQueue() { - if (!this.done || !this._timeline || !this._timeline._runnerIds.includes(this.id)) { - this._queue = this._queue.filter(item => { - return !item.isTransform; - }); - } - } - - delay(delay) { - return this.animate(0, delay); - } - - duration() { - return this._times * (this._wait + this._duration) - this._wait; - } - - during(fn) { - return this.queue(null, fn); - } - - ease(fn) { - this._stepper = new Ease(fn); - return this; - } - /* - Runner Definitions - ================== - These methods help us define the runtime behaviour of the Runner or they - help us make new runners from the current runner - */ - - - element(element) { - if (element == null) return this._element; - this._element = element; - - element._prepareRunner(); - - return this; - } - - finish() { - return this.step(Infinity); - } - - loop(times, swing, wait) { - // Deal with the user passing in an object - if (typeof times === 'object') { - swing = times.swing; - wait = times.wait; - times = times.times; - } // Sanitise the values and store them - - - this._times = times || Infinity; - this._swing = swing || false; - this._wait = wait || 0; // Allow true to be passed - - if (this._times === true) { - this._times = Infinity; - } - - return this; - } - - loops(p) { - const loopDuration = this._duration + this._wait; - - if (p == null) { - const loopsDone = Math.floor(this._time / loopDuration); - const relativeTime = this._time - loopsDone * loopDuration; - const position = relativeTime / this._duration; - return Math.min(loopsDone + position, this._times); - } - - const whole = Math.floor(p); - const partial = p % 1; - const time = loopDuration * whole + this._duration * partial; - return this.time(time); - } - - persist(dtOrForever) { - if (dtOrForever == null) return this._persist; - this._persist = dtOrForever; - return this; - } - - position(p) { - // Get all of the variables we need - const x = this._time; - const d = this._duration; - const w = this._wait; - const t = this._times; - const s = this._swing; - const r = this._reverse; - let position; - - if (p == null) { - /* - This function converts a time to a position in the range [0, 1] - The full explanation can be found in this desmos demonstration - https://www.desmos.com/calculator/u4fbavgche - The logic is slightly simplified here because we can use booleans - */ - // Figure out the value without thinking about the start or end time - const f = function (x) { - const swinging = s * Math.floor(x % (2 * (w + d)) / (w + d)); - const backwards = swinging && !r || !swinging && r; - const uncliped = Math.pow(-1, backwards) * (x % (w + d)) / d + backwards; - const clipped = Math.max(Math.min(uncliped, 1), 0); - return clipped; - }; // Figure out the value by incorporating the start time - - - const endTime = t * (w + d) - w; - position = x <= 0 ? Math.round(f(1e-5)) : x < endTime ? f(x) : Math.round(f(endTime - 1e-5)); - return position; - } // Work out the loops done and add the position to the loops done - - - const loopsDone = Math.floor(this.loops()); - const swingForward = s && loopsDone % 2 === 0; - const forwards = swingForward && !r || r && swingForward; - position = loopsDone + (forwards ? p : 1 - p); - return this.loops(position); - } - - progress(p) { - if (p == null) { - return Math.min(1, this._time / this.duration()); - } - - return this.time(p * this.duration()); - } - /* - Basic Functionality - =================== - These methods allow us to attach basic functions to the runner directly - */ - - - queue(initFn, runFn, retargetFn, isTransform) { - this._queue.push({ - initialiser: initFn || noop, - runner: runFn || noop, - retarget: retargetFn, - isTransform: isTransform, - initialised: false, - finished: false - }); - - const timeline = this.timeline(); - timeline && this.timeline()._continue(); - return this; - } - - reset() { - if (this._reseted) return this; - this.time(0); - this._reseted = true; - return this; - } - - reverse(reverse) { - this._reverse = reverse == null ? !this._reverse : reverse; - return this; - } - - schedule(timeline, delay, when) { - // The user doesn't need to pass a timeline if we already have one - if (!(timeline instanceof Timeline)) { - when = delay; - delay = timeline; - timeline = this.timeline(); - } // If there is no timeline, yell at the user... - - - if (!timeline) { - throw Error('Runner cannot be scheduled without timeline'); - } // Schedule the runner on the timeline provided - - - timeline.schedule(this, delay, when); - return this; - } - - step(dt) { - // If we are inactive, this stepper just gets skipped - if (!this.enabled) return this; // Update the time and get the new position - - dt = dt == null ? 16 : dt; - this._time += dt; - const position = this.position(); // Figure out if we need to run the stepper in this frame - - const running = this._lastPosition !== position && this._time >= 0; - this._lastPosition = position; // Figure out if we just started - - const duration = this.duration(); - const justStarted = this._lastTime <= 0 && this._time > 0; - const justFinished = this._lastTime < duration && this._time >= duration; - this._lastTime = this._time; - - if (justStarted) { - this.fire('start', this); - } // Work out if the runner is finished set the done flag here so animations - // know, that they are running in the last step (this is good for - // transformations which can be merged) - - - const declarative = this._isDeclarative; - this.done = !declarative && !justFinished && this._time >= duration; // Runner is running. So its not in reseted state anymore - - this._reseted = false; - let converged = false; // Call initialise and the run function - - if (running || declarative) { - this._initialise(running); // clear the transforms on this runner so they dont get added again and again - - - this.transforms = new Matrix(); - converged = this._run(declarative ? dt : position); - this.fire('step', this); - } // correct the done flag here - // declaritive animations itself know when they converged - - - this.done = this.done || converged && declarative; - - if (justFinished) { - this.fire('finished', this); - } - - return this; - } - /* - Runner animation methods - ======================== - Control how the animation plays - */ - - - time(time) { - if (time == null) { - return this._time; - } - - const dt = time - this._time; - this.step(dt); - return this; - } - - timeline(timeline) { - // check explicitly for undefined so we can set the timeline to null - if (typeof timeline === 'undefined') return this._timeline; - this._timeline = timeline; - return this; - } - - unschedule() { - const timeline = this.timeline(); - timeline && timeline.unschedule(this); - return this; - } // Run each initialise function in the runner if required - - - _initialise(running) { - // If we aren't running, we shouldn't initialise when not declarative - if (!running && !this._isDeclarative) return; // Loop through all of the initialisers - - for (let i = 0, len = this._queue.length; i < len; ++i) { - // Get the current initialiser - const current = this._queue[i]; // Determine whether we need to initialise - - const needsIt = this._isDeclarative || !current.initialised && running; - running = !current.finished; // Call the initialiser if we need to - - if (needsIt && running) { - current.initialiser.call(this); - current.initialised = true; - } - } - } // Save a morpher to the morpher list so that we can retarget it later - - - _rememberMorpher(method, morpher) { - this._history[method] = { - morpher: morpher, - caller: this._queue[this._queue.length - 1] - }; // We have to resume the timeline in case a controller - // is already done without being ever run - // This can happen when e.g. this is done: - // anim = el.animate(new SVG.Spring) - // and later - // anim.move(...) - - if (this._isDeclarative) { - const timeline = this.timeline(); - timeline && timeline.play(); - } - } // Try to set the target for a morpher if the morpher exists, otherwise - // Run each run function for the position or dt given - - - _run(positionOrDt) { - // Run all of the _queue directly - let allfinished = true; - - for (let i = 0, len = this._queue.length; i < len; ++i) { - // Get the current function to run - const current = this._queue[i]; // Run the function if its not finished, we keep track of the finished - // flag for the sake of declarative _queue - - const converged = current.runner.call(this, positionOrDt); - current.finished = current.finished || converged === true; - allfinished = allfinished && current.finished; - } // We report when all of the constructors are finished - - - return allfinished; - } // do nothing and return false - - - _tryRetarget(method, target, extra) { - if (this._history[method]) { - // if the last method wasnt even initialised, throw it away - if (!this._history[method].caller.initialised) { - const index = this._queue.indexOf(this._history[method].caller); - - this._queue.splice(index, 1); - - return false; - } // for the case of transformations, we use the special retarget function - // which has access to the outer scope - - - if (this._history[method].caller.retarget) { - this._history[method].caller.retarget.call(this, target, extra); // for everything else a simple morpher change is sufficient - - } else { - this._history[method].morpher.to(target); - } - - this._history[method].caller.finished = false; - const timeline = this.timeline(); - timeline && timeline.play(); - return true; - } - - return false; - } - - } - Runner.id = 0; - class FakeRunner { - constructor(transforms = new Matrix(), id = -1, done = true) { - this.transforms = transforms; - this.id = id; - this.done = done; - } - - clearTransformsFromQueue() {} - - } - extend([Runner, FakeRunner], { - mergeWith(runner) { - return new FakeRunner(runner.transforms.lmultiply(this.transforms), runner.id); - } - - }); // FakeRunner.emptyRunner = new FakeRunner() - - const lmultiply = (last, curr) => last.lmultiplyO(curr); - - const getRunnerTransform = runner => runner.transforms; - - function mergeTransforms() { - // Find the matrix to apply to the element and apply it - const runners = this._transformationRunners.runners; - const netTransform = runners.map(getRunnerTransform).reduce(lmultiply, new Matrix()); - this.transform(netTransform); - - this._transformationRunners.merge(); - - if (this._transformationRunners.length() === 1) { - this._frameId = null; - } + create: function(element) { + this._target = element + this.situations = [] + this.active = false + this.situation = null + this.paused = false + this.lastPos = 0 + this.pos = 0 + // The absolute position of an animation is its position in the context of its complete duration (including delay and loops) + // When performing a delay, absPos is below 0 and when performing a loop, its value is above 1 + this.absPos = 0 + this._speed = 1 } - class RunnerArray { - constructor() { - this.runners = []; - this.ids = []; - } - - add(runner) { - if (this.runners.includes(runner)) return; - const id = runner.id + 1; - this.runners.push(runner); - this.ids.push(id); - return this; - } - - clearBefore(id) { - const deleteCnt = this.ids.indexOf(id + 1) || 1; - this.ids.splice(0, deleteCnt, 0); - this.runners.splice(0, deleteCnt, new FakeRunner()).forEach(r => r.clearTransformsFromQueue()); - return this; - } - - edit(id, newRunner) { - const index = this.ids.indexOf(id + 1); - this.ids.splice(index, 1, id + 1); - this.runners.splice(index, 1, newRunner); - return this; - } - - getByID(id) { - return this.runners[this.ids.indexOf(id + 1)]; - } - - length() { - return this.ids.length; - } - - merge() { - let lastRunner = null; - - for (let i = 0; i < this.runners.length; ++i) { - const runner = this.runners[i]; - const condition = lastRunner && runner.done && lastRunner.done // don't merge runner when persisted on timeline - && (!runner._timeline || !runner._timeline._runnerIds.includes(runner.id)) && (!lastRunner._timeline || !lastRunner._timeline._runnerIds.includes(lastRunner.id)); - - if (condition) { - // the +1 happens in the function - this.remove(runner.id); - const newRunner = runner.mergeWith(lastRunner); - this.edit(lastRunner.id, newRunner); - lastRunner = newRunner; - --i; - } else { - lastRunner = runner; - } - } - - return this; - } - - remove(id) { - const index = this.ids.indexOf(id + 1); - this.ids.splice(index, 1); - this.runners.splice(index, 1); - return this; - } - - } - registerMethods({ - Element: { - animate(duration, delay, when) { - const o = Runner.sanitise(duration, delay, when); - const timeline = this.timeline(); - return new Runner(o.duration).loop(o).element(this).timeline(timeline.play()).schedule(o.delay, o.when); - }, - - delay(by, when) { - return this.animate(0, by, when); - }, - - // this function searches for all runners on the element and deletes the ones - // which run before the current one. This is because absolute transformations - // overwfrite anything anyway so there is no need to waste time computing - // other runners - _clearTransformRunnersBefore(currentRunner) { - this._transformationRunners.clearBefore(currentRunner.id); - }, - - _currentTransform(current) { - return this._transformationRunners.runners // we need the equal sign here to make sure, that also transformations - // on the same runner which execute before the current transformation are - // taken into account - .filter(runner => runner.id <= current.id).map(getRunnerTransform).reduce(lmultiply, new Matrix()); - }, - - _addRunner(runner) { - this._transformationRunners.add(runner); // Make sure that the runner merge is executed at the very end of - // all Animator functions. Thats why we use immediate here to execute - // the merge right after all frames are run - - - Animator.cancelImmediate(this._frameId); - this._frameId = Animator.immediate(mergeTransforms.bind(this)); - }, - - _prepareRunner() { - if (this._frameId == null) { - this._transformationRunners = new RunnerArray().add(new FakeRunner(new Matrix(this))); - } - } - - } - }); // Will output the elements from array A that are not in the array B - - const difference = (a, b) => a.filter(x => !b.includes(x)); - - extend(Runner, { - attr(a, v) { - return this.styleAttr('attr', a, v); - }, - - // Add animatable styles - css(s, v) { - return this.styleAttr('css', s, v); - }, - - styleAttr(type, nameOrAttrs, val) { - if (typeof nameOrAttrs === 'string') { - return this.styleAttr(type, { - [nameOrAttrs]: val - }); - } - - let attrs = nameOrAttrs; - if (this._tryRetarget(type, attrs)) return this; - let morpher = new Morphable(this._stepper).to(attrs); - let keys = Object.keys(attrs); - this.queue(function () { - morpher = morpher.from(this.element()[type](keys)); - }, function (pos) { - this.element()[type](morpher.at(pos).valueOf()); - return morpher.done(); - }, function (newToAttrs) { - // Check if any new keys were added - const newKeys = Object.keys(newToAttrs); - const differences = difference(newKeys, keys); // If their are new keys, initialize them and add them to morpher - - if (differences.length) { - // Get the values - const addedFromAttrs = this.element()[type](differences); // Get the already initialized values - - const oldFromAttrs = new ObjectBag(morpher.from()).valueOf(); // Merge old and new - - Object.assign(oldFromAttrs, addedFromAttrs); - morpher.from(oldFromAttrs); - } // Get the object from the morpher - - - const oldToAttrs = new ObjectBag(morpher.to()).valueOf(); // Merge in new attributes - - Object.assign(oldToAttrs, newToAttrs); // Change morpher target - - morpher.to(oldToAttrs); // Make sure that we save the work we did so we don't need it to do again - - keys = newKeys; - attrs = newToAttrs; - }); - - this._rememberMorpher(type, morpher); - - return this; - }, - - zoom(level, point) { - if (this._tryRetarget('zoom', level, point)) return this; - let morpher = new Morphable(this._stepper).to(new SVGNumber(level)); - this.queue(function () { - morpher = morpher.from(this.element().zoom()); - }, function (pos) { - this.element().zoom(morpher.at(pos), point); - return morpher.done(); - }, function (newLevel, newPoint) { - point = newPoint; - morpher.to(newLevel); - }); - - this._rememberMorpher('zoom', morpher); - - return this; - }, +, extend: { /** - ** absolute transformations - **/ - // - // M v -----|-----(D M v = F v)------|-----> T v - // - // 1. define the final state (T) and decompose it (once) - // t = [tx, ty, the, lam, sy, sx] - // 2. on every frame: pull the current state of all previous transforms - // (M - m can change) - // and then write this as m = [tx0, ty0, the0, lam0, sy0, sx0] - // 3. Find the interpolated matrix F(pos) = m + pos * (t - m) - // - Note F(0) = M - // - Note F(1) = T - // 4. Now you get the delta matrix as a result: D = F * inv(M) - transform(transforms, relative, affine) { - // If we have a declarative function, we should retarget it if possible - relative = transforms.relative || relative; + * sets or returns the target of this animation + * @param o object || number In case of Object it holds all parameters. In case of number its the duration of the animation + * @param ease function || string Function which should be used for easing or easing keyword + * @param delay Number indicating the delay before the animation starts + * @return target || this + */ + animate: function(o, ease, delay){ - if (this._isDeclarative && !relative && this._tryRetarget('transform', transforms)) { - return this; - } // Parse the parameters - - - const isMatrix = Matrix.isMatrixLike(transforms); - affine = transforms.affine != null ? transforms.affine : affine != null ? affine : !isMatrix; // Create a morepher and set its type - - const morpher = new Morphable(this._stepper).type(affine ? TransformBag : Matrix); - let origin; - let element; - let current; - let currentAngle; - let startTransform; - - function setup() { - // make sure element and origin is defined - element = element || this.element(); - origin = origin || getOrigin(transforms, element); - startTransform = new Matrix(relative ? undefined : element); // add the runner to the element so it can merge transformations - - element._addRunner(this); // Deactivate all transforms that have run so far if we are absolute - - - if (!relative) { - element._clearTransformRunnersBefore(this); - } + if(typeof o == 'object'){ + ease = o.ease + delay = o.delay + o = o.duration } - function run(pos) { - // clear all other transforms before this in case something is saved - // on this runner. We are absolute. We dont need these! - if (!relative) this.clearTransform(); - const { - x, - y - } = new Point(origin).transform(element._currentTransform(this)); - let target = new Matrix({ ...transforms, - origin: [x, y] - }); - let start = this._isDeclarative && current ? current : startTransform; - - if (affine) { - target = target.decompose(x, y); - start = start.decompose(x, y); // Get the current and target angle as it was set - - const rTarget = target.rotate; - const rCurrent = start.rotate; // Figure out the shortest path to rotate directly - - const possibilities = [rTarget - 360, rTarget, rTarget + 360]; - const distances = possibilities.map(a => Math.abs(a - rCurrent)); - const shortest = Math.min(...distances); - const index = distances.indexOf(shortest); - target.rotate = possibilities[index]; - } - - if (relative) { - // we have to be careful here not to overwrite the rotation - // with the rotate method of Matrix - if (!isMatrix) { - target.rotate = transforms.rotate || 0; - } - - if (this._isDeclarative && currentAngle) { - start.rotate = currentAngle; - } - } - - morpher.from(start); - morpher.to(target); - const affineParameters = morpher.at(pos); - currentAngle = affineParameters.rotate; - current = new Matrix(affineParameters); - this.addTransform(current); - - element._addRunner(this); - - return morpher.done(); - } - - function retarget(newTransforms) { - // only get a new origin if it changed since the last call - if ((newTransforms.origin || 'center').toString() !== (transforms.origin || 'center').toString()) { - origin = getOrigin(newTransforms, element); - } // overwrite the old transformations with the new ones - - - transforms = { ...newTransforms, - origin - }; - } - - this.queue(setup, run, retarget, true); - this._isDeclarative && this._rememberMorpher('transform', morpher); - return this; - }, - - // Animatable x-axis - x(x, relative) { - return this._queueNumber('x', x); - }, - - // Animatable y-axis - y(y) { - return this._queueNumber('y', y); - }, - - dx(x = 0) { - return this._queueNumberDelta('x', x); - }, - - dy(y = 0) { - return this._queueNumberDelta('y', y); - }, - - dmove(x, y) { - return this.dx(x).dy(y); - }, - - _queueNumberDelta(method, to) { - to = new SVGNumber(to); // Try to change the target if we have this method already registerd - - if (this._tryRetarget(method, to)) return this; // Make a morpher and queue the animation - - const morpher = new Morphable(this._stepper).to(to); - let from = null; - this.queue(function () { - from = this.element()[method](); - morpher.from(from); - morpher.to(from + to); - }, function (pos) { - this.element()[method](morpher.at(pos)); - return morpher.done(); - }, function (newTo) { - morpher.to(from + new SVGNumber(newTo)); - }); // Register the morpher so that if it is changed again, we can retarget it - - this._rememberMorpher(method, morpher); - - return this; - }, - - _queueObject(method, to) { - // Try to change the target if we have this method already registerd - if (this._tryRetarget(method, to)) return this; // Make a morpher and queue the animation - - const morpher = new Morphable(this._stepper).to(to); - this.queue(function () { - morpher.from(this.element()[method]()); - }, function (pos) { - this.element()[method](morpher.at(pos)); - return morpher.done(); - }); // Register the morpher so that if it is changed again, we can retarget it - - this._rememberMorpher(method, morpher); - - return this; - }, - - _queueNumber(method, value) { - return this._queueObject(method, new SVGNumber(value)); - }, - - // Animatable center x-axis - cx(x) { - return this._queueNumber('cx', x); - }, - - // Animatable center y-axis - cy(y) { - return this._queueNumber('cy', y); - }, - - // Add animatable move - move(x, y) { - return this.x(x).y(y); - }, - - // Add animatable center - center(x, y) { - return this.cx(x).cy(y); - }, - - // Add animatable size - size(width, height) { - // animate bbox based size for all other elements - let box; - - if (!width || !height) { - box = this._element.bbox(); - } - - if (!width) { - width = box.width / box.height * height; - } - - if (!height) { - height = box.height / box.width * width; - } - - return this.width(width).height(height); - }, - - // Add animatable width - width(width) { - return this._queueNumber('width', width); - }, - - // Add animatable height - height(height) { - return this._queueNumber('height', height); - }, - - // Add animatable plot - plot(a, b, c, d) { - // Lines can be plotted with 4 arguments - if (arguments.length === 4) { - return this.plot([a, b, c, d]); - } - - if (this._tryRetarget('plot', a)) return this; - const morpher = new Morphable(this._stepper).type(this._element.MorphArray).to(a); - this.queue(function () { - morpher.from(this._element.array()); - }, function (pos) { - this._element.plot(morpher.at(pos)); - - return morpher.done(); - }); - - this._rememberMorpher('plot', morpher); - - return this; - }, - - // Add leading method - leading(value) { - return this._queueNumber('leading', value); - }, - - // Add animatable viewbox - viewbox(x, y, width, height) { - return this._queueObject('viewbox', new Box(x, y, width, height)); - }, - - update(o) { - if (typeof o !== 'object') { - return this.update({ - offset: arguments[0], - color: arguments[1], - opacity: arguments[2] - }); - } - - 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', o.offset); - return this; - } - - }); - extend(Runner, { - rx, - ry, - from, - to - }); - register(Runner, 'Runner'); - - class Svg extends Container { - constructor(node, attrs = node) { - super(nodeOrNew('svg', node), attrs); - this.namespace(); - } // Creates and returns defs element - - - defs() { - if (!this.isRoot()) return this.root().defs(); - return adopt(this.node.querySelector('defs')) || this.put(new Defs()); - } - - isRoot() { - return !this.node.parentNode || !(this.node.parentNode instanceof globals.window.SVGElement) && this.node.parentNode.nodeName !== '#document-fragment'; - } // Add namespaces - - - namespace() { - if (!this.isRoot()) return this.root().namespace(); - return this.attr({ - xmlns: svg, - version: '1.1' - }).attr('xmlns:xlink', xlink, xmlns).attr('xmlns:svgjs', svgjs, xmlns); - } - - removeNamespace() { - return this.attr({ - xmlns: null, - version: null - }).attr('xmlns:xlink', null, xmlns).attr('xmlns:svgjs', null, xmlns); - } // Check if this is a root svg - // If not, call root() from this element - - - root() { - if (this.isRoot()) return this; - return super.root(); - } - - } - registerMethods({ - Container: { - // Create nested svg document - nested: wrapWithAttrCheck(function () { - return this.put(new Svg()); + var situation = new SVG.Situation({ + duration: o || 1000, + delay: delay || 0, + ease: SVG.easing[ease || '-'] || ease }) - } - }); - register(Svg, 'Svg', true); - class Symbol extends Container { - // Initialize node - constructor(node, attrs = node) { - super(nodeOrNew('symbol', node), attrs); + this.queue(situation) + + return this } - } - registerMethods({ - Container: { - symbol: wrapWithAttrCheck(function () { - return this.put(new Symbol()); + /** + * sets a delay before the next element of the queue is called + * @param delay Duration of delay in milliseconds + * @return this.target() + */ + , delay: function(delay){ + // The delay is performed by an empty situation with its duration + // attribute set to the duration of the delay + var situation = new SVG.Situation({ + duration: delay, + delay: 0, + ease: SVG.easing['-'] }) - } - }); - register(Symbol, 'Symbol'); - function plain(text) { - // clear if build mode is disabled - if (this._build === false) { - this.clear(); - } // create text node - - - this.node.appendChild(globals.document.createTextNode(text)); - return this; - } // Get length of text element - - function length() { - return this.node.getComputedTextLength(); - } // Move over x-axis - // Text is moved by its bounding box - // text-anchor does NOT matter - - function x$1(x, box = this.bbox()) { - if (x == null) { - return box.x; + return this.queue(situation) } - return this.attr('x', this.attr('x') + x - box.x); - } // Move over y-axis + /** + * sets or returns the target of this animation + * @param null || target SVG.Element which should be set as new target + * @return target || this + */ + , target: function(target){ + if(target && target instanceof SVG.Element){ + this._target = target + return this + } - function y$1(y, box = this.bbox()) { - if (y == null) { - return box.y; + return this._target } - return this.attr('y', this.attr('y') + y - box.y); - } - function move$1(x, y, box = this.bbox()) { - return this.x(x, box).y(y, box); - } // Move center over x-axis - - function cx(x, box = this.bbox()) { - if (x == null) { - return box.cx; + // returns the absolute position at a given time + , timeToAbsPos: function(timestamp){ + return (timestamp - this.situation.start) / (this.situation.duration/this._speed) } - return this.attr('x', this.attr('x') + x - box.cx); - } // Move center over y-axis - - function cy(y, box = this.bbox()) { - if (y == null) { - return box.cy; + // returns the timestamp from a given absolute positon + , absPosToTime: function(absPos){ + return this.situation.duration/this._speed * absPos + this.situation.start } - return this.attr('y', this.attr('y') + y - box.cy); - } - function center(x, y, box = this.bbox()) { - return this.cx(x, box).cy(y, box); - } - function ax(x) { - return this.attr('x', x); - } - function ay(y) { - return this.attr('y', y); - } - function amove(x, y) { - return this.ax(x).ay(y); - } // Enable / disable build mode + // starts the animationloop + , startAnimFrame: function(){ + this.stopAnimFrame() + this.animationFrame = window.requestAnimationFrame(function(){ this.step() }.bind(this)) + } - function build(build) { - this._build = !!build; - return this; - } + // cancels the animationframe + , stopAnimFrame: function(){ + window.cancelAnimationFrame(this.animationFrame) + } - var textable = { - __proto__: null, - plain: plain, - length: length, - x: x$1, - y: y$1, - move: move$1, - cx: cx, - cy: cy, - center: center, - ax: ax, - ay: ay, - amove: amove, - build: build - }; + // kicks off the animation - only does something when the queue is currently not active and at least one situation is set + , start: function(){ + // dont start if already started + if(!this.active && this.situation){ + this.active = true + this.startCurrent() + } - class Text extends Shape { - // Initialize node - constructor(node, attrs = node) { - super(nodeOrNew('text', node), attrs); - this.dom.leading = new SVGNumber(1.3); // store leading value for rebuilding + return this + } - this._rebuild = true; // enable automatic updating of dy values + // start the current situation + , startCurrent: function(){ + this.situation.start = +new Date + this.situation.delay/this._speed + this.situation.finish = this.situation.start + this.situation.duration/this._speed + return this.initAnimations().step() + } - this._build = false; // disable build mode for adding multiple lines - } // Set / get leading + /** + * adds a function / Situation to the animation queue + * @param fn function / situation to add + * @return this + */ + , queue: function(fn){ + if(typeof fn == 'function' || fn instanceof SVG.Situation) + this.situations.push(fn) + + if(!this.situation) this.situation = this.situations.shift() + + return this + } + + /** + * pulls next element from the queue and execute it + * @return this + */ + , dequeue: function(){ + // stop current animation + this.stop() + + // get next animation from queue + this.situation = this.situations.shift() + + if(this.situation){ + if(this.situation instanceof SVG.Situation) { + this.start() + } else { + // If it is not a SVG.Situation, then it is a function, we execute it + this.situation.call(this) + } + } + + return this + } + + // updates all animations to the current state of the element + // this is important when one property could be changed from another property + , initAnimations: function() { + var i, j, source + var s = this.situation + + if(s.init) return this + + for(i in s.animations){ + source = this.target()[i]() + + if(!Array.isArray(source)) { + source = [source] + } + + if(!Array.isArray(s.animations[i])) { + s.animations[i] = [s.animations[i]] + } + + //if(s.animations[i].length > source.length) { + // source.concat = source.concat(s.animations[i].slice(source.length, s.animations[i].length)) + //} + + for(j = source.length; j--;) { + // The condition is because some methods return a normal number instead + // of a SVG.Number + if(s.animations[i][j] instanceof SVG.Number) + source[j] = new SVG.Number(source[j]) + + s.animations[i][j] = source[j].morph(s.animations[i][j]) + } + } + + for(i in s.attrs){ + s.attrs[i] = new SVG.MorphObj(this.target().attr(i), s.attrs[i]) + } + + for(i in s.styles){ + s.styles[i] = new SVG.MorphObj(this.target().style(i), s.styles[i]) + } + + s.initialTransformation = this.target().matrixify() + + s.init = true + return this + } + , clearQueue: function(){ + this.situations = [] + return this + } + , clearCurrent: function(){ + this.situation = null + return this + } + /** stops the animation immediately + * @param jumpToEnd A Boolean indicating whether to complete the current animation immediately. + * @param clearQueue A Boolean indicating whether to remove queued animation as well. + * @return this + */ + , stop: function(jumpToEnd, clearQueue){ + var active = this.active + this.active = false + + if(clearQueue){ + this.clearQueue() + } + + if(jumpToEnd && this.situation){ + // initialize the situation if it was not + !active && this.startCurrent() + this.atEnd() + } + + this.stopAnimFrame() + + return this.clearCurrent() + } + + /** resets the element to the state where the current element has started + * @return this + */ + , reset: function(){ + if(this.situation){ + var temp = this.situation + this.stop() + this.situation = temp + this.atStart() + } + return this + } + + // Stop the currently-running animation, remove all queued animations, and complete all animations for the element. + , finish: function(){ + + this.stop(true, false) + + while(this.dequeue().situation && this.stop(true, false)); + + this.clearQueue().clearCurrent() + + return this + } + + // set the internal animation pointer at the start position, before any loops, and updates the visualisation + , atStart: function() { + return this.at(0, true) + } + + // set the internal animation pointer at the end position, after all the loops, and updates the visualisation + , atEnd: function() { + if (this.situation.loops === true) { + // If in a infinite loop, we end the current iteration + this.situation.loops = this.situation.loop + 1 + } + + if(typeof this.situation.loops == 'number') { + // If performing a finite number of loops, we go after all the loops + return this.at(this.situation.loops, true) + } else { + // If no loops, we just go at the end + return this.at(1, true) + } + } + + // set the internal animation pointer to the specified position and updates the visualisation + // if isAbsPos is true, pos is treated as an absolute position + , at: function(pos, isAbsPos){ + var durDivSpd = this.situation.duration/this._speed + + this.absPos = pos + // If pos is not an absolute position, we convert it into one + if (!isAbsPos) { + if (this.situation.reversed) this.absPos = 1 - this.absPos + this.absPos += this.situation.loop + } + + this.situation.start = +new Date - this.absPos * durDivSpd + this.situation.finish = this.situation.start + durDivSpd + + return this.step(true) + } + + /** + * sets or returns the speed of the animations + * @param speed null || Number The new speed of the animations + * @return Number || this + */ + , speed: function(speed){ + if (speed === 0) return this.pause() + + if (speed) { + this._speed = speed + // We use an absolute position here so that speed can affect the delay before the animation + return this.at(this.absPos, true) + } else return this._speed + } + + // Make loopable + , loop: function(times, reverse) { + var c = this.last() + + // store total loops + c.loops = (times != null) ? times : true + c.loop = 0 + + if(reverse) c.reversing = true + return this + } + + // pauses the animation + , pause: function(){ + this.paused = true + this.stopAnimFrame() + + return this + } + + // unpause the animation + , play: function(){ + if(!this.paused) return this + this.paused = false + // We use an absolute position here so that the delay before the animation can be paused + return this.at(this.absPos, true) + } + + /** + * toggle or set the direction of the animation + * true sets direction to backwards while false sets it to forwards + * @param reversed Boolean indicating whether to reverse the animation or not (default: toggle the reverse status) + * @return this + */ + , reverse: function(reversed){ + var c = this.last() + + if(typeof reversed == 'undefined') c.reversed = !c.reversed + else c.reversed = reversed + + return this + } - leading(value) { - // act as getter - if (value == null) { - return this.dom.leading; - } // act as setter + /** + * returns a float from 0-1 indicating the progress of the current animation + * @param eased Boolean indicating whether the returned position should be eased or not + * @return number + */ + , progress: function(easeIt){ + return easeIt ? this.situation.ease(this.pos) : this.pos + } - - this.dom.leading = new SVGNumber(value); - return this.rebuild(); - } // Rebuild appearance type - - - rebuild(rebuild) { - // store new rebuild flag if given - if (typeof rebuild === 'boolean') { - this._rebuild = rebuild; - } // define position of all lines - - - if (this._rebuild) { - const self = this; - let blankLineOffset = 0; - const leading = this.dom.leading; - this.each(function (i) { - const fontSize = globals.window.getComputedStyle(this.node).getPropertyValue('font-size'); - const dy = leading * new SVGNumber(fontSize); - - if (this.dom.newLined) { - this.attr('x', self.attr('x')); - - if (this.text() === '\n') { - blankLineOffset += dy; - } else { - this.attr('dy', i ? dy + blankLineOffset : 0); - blankLineOffset = 0; + /** + * adds a callback function which is called when the current animation is finished + * @param fn Function which should be executed as callback + * @return number + */ + , after: function(fn){ + var c = this.last() + , wrapper = function wrapper(e){ + if(e.detail.situation == c){ + fn.call(this, c) + this.off('finished.fx', wrapper) // prevent memory leak } } - }); - this.fire('rebuild'); - } - return this; - } // overwrite method from parent to set data properly + this.target().on('finished.fx', wrapper) + return this._callStart() + } - setData(o) { - this.dom = o; - this.dom.leading = new SVGNumber(o.leading || 1.3); - return this; - } // Set the text content + // adds a callback which is called whenever one animation step is performed + , during: function(fn){ + var c = this.last() + , wrapper = function(e){ + if(e.detail.situation == c){ + fn.call(this, e.detail.pos, SVG.morph(e.detail.pos), e.detail.eased, c) + } + } + // see above + this.target().off('during.fx', wrapper).on('during.fx', wrapper) - text(text) { - // act as getter - if (text === undefined) { - const children = this.node.childNodes; - let firstLine = 0; - text = ''; + this.after(function(){ + this.off('during.fx', wrapper) + }) - for (let 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 + return this._callStart() + } + // calls after ALL animations in the queue are finished + , afterAll: function(fn){ + var wrapper = function wrapper(e){ + fn.call(this) + this.off('allfinished.fx', wrapper) + } - if (i !== firstLine && children[i].nodeType !== 3 && adopt(children[i]).dom.newLined === true) { - text += '\n'; - } // add content of this node + // see above + this.target().off('allfinished.fx', wrapper).on('allfinished.fx', wrapper) + return this._callStart() + } - text += children[i].textContent; + // calls on every animation step for all animations + , duringAll: function(fn){ + var wrapper = function(e){ + fn.call(this, e.detail.pos, SVG.morph(e.detail.pos), e.detail.eased, e.detail.situation) + } + + this.target().off('during.fx', wrapper).on('during.fx', wrapper) + + this.afterAll(function(){ + this.off('during.fx', wrapper) + }) + + return this._callStart() + } + + , last: function(){ + return this.situations.length ? this.situations[this.situations.length-1] : this.situation + } + + // adds one property to the animations + , add: function(method, args, type){ + this.last()[type || 'animations'][method] = args + return this._callStart() + } + + /** perform one step of the animation + * @param ignoreTime Boolean indicating whether to ignore time and use position directly or recalculate position based on time + * @return this + */ + , step: function(ignoreTime){ + + // convert current time to an absolute position + if(!ignoreTime) this.absPos = this.timeToAbsPos(+new Date) + + // This part convert an absolute position to a position + if(this.situation.loops !== false) { + var absPos, absPosInt, lastLoop + + // If the absolute position is below 0, we just treat it as if it was 0 + absPos = Math.max(this.absPos, 0) + absPosInt = Math.floor(absPos) + + if(this.situation.loops === true || absPosInt < this.situation.loops) { + this.pos = absPos - absPosInt + lastLoop = this.situation.loop + this.situation.loop = absPosInt + } else { + this.absPos = this.situation.loops + this.pos = 1 + // The -1 here is because we don't want to toggle reversed when all the loops have been completed + lastLoop = this.situation.loop - 1 + this.situation.loop = this.situation.loops } - return text; - } // remove existing content + if(this.situation.reversing) { + // Toggle reversed if an odd number of loops as occured since the last call of step + this.situation.reversed = this.situation.reversed != Boolean((this.situation.loop - lastLoop) % 2) + } + + } else { + // If there are no loop, the absolute position must not be above 1 + this.absPos = Math.min(this.absPos, 1) + this.pos = this.absPos + } + + // while the absolute position can be below 0, the position must not be below 0 + if(this.pos < 0) this.pos = 0 + + if(this.situation.reversed) this.pos = 1 - this.pos - this.clear().build(true); + // apply easing + var eased = this.situation.ease(this.pos) + + // call once-callbacks + for(var i in this.situation.once){ + if(i > this.lastPos && i <= eased){ + this.situation.once[i].call(this.target(), this.pos, eased) + delete this.situation.once[i] + } + } + + // fire during callback with position, eased position and current situation as parameter + if(this.active) this.target().fire('during', {pos: this.pos, eased: eased, fx: this, situation: this.situation}) + + // the user may call stop or finish in the during callback + // so make sure that we still have a valid situation + if(!this.situation){ + return this + } + + // apply the actual animation to every property + this.eachAt() + + // do final code when situation is finished + if((this.pos == 1 && !this.situation.reversed) || (this.situation.reversed && this.pos == 0)){ + + // stop animation callback + this.stopAnimFrame() + + // fire finished callback with current situation as parameter + this.target().fire('finished', {fx:this, situation: this.situation}) + + if(!this.situations.length){ + this.target().fire('allfinished') + + // Recheck the length since the user may call animate in the afterAll callback + if(!this.situations.length){ + this.target().off('.fx') // there shouldnt be any binding left, but to make sure... + this.active = false + } + } + + // start next animation + if(this.active) this.dequeue() + else this.clearCurrent() + + }else if(!this.paused && this.active){ + // we continue animating when we are not at the end + this.startAnimFrame() + } + + // save last eased position for once callback triggering + this.lastPos = eased + return this + + } + + // calculates the step for every property and calls block with it + , eachAt: function(){ + var i, len, at, self = this, target = this.target(), s = this.situation + + // apply animations which can be called trough a method + for(i in s.animations){ + + at = [].concat(s.animations[i]).map(function(el){ + return typeof el !== 'string' && el.at ? el.at(s.ease(self.pos), self.pos) : el + }) + + target[i].apply(target, at) + + } + + // apply animation which has to be applied with attr() + for(i in s.attrs){ + + at = [i].concat(s.attrs[i]).map(function(el){ + return typeof el !== 'string' && el.at ? el.at(s.ease(self.pos), self.pos) : el + }) + + target.attr.apply(target, at) + + } + + // apply animation which has to be applied with style() + for(i in s.styles){ + + at = [i].concat(s.styles[i]).map(function(el){ + return typeof el !== 'string' && el.at ? el.at(s.ease(self.pos), self.pos) : el + }) + + target.style.apply(target, at) + + } + + // animate initialTransformation which has to be chained + if(s.transforms.length){ + + // get initial initialTransformation + at = s.initialTransformation + for(i = 0, len = s.transforms.length; i < len; i++){ + + // get next transformation in chain + var a = s.transforms[i] + + // multiply matrix directly + if(a instanceof SVG.Matrix){ + + if(a.relative){ + at = at.multiply(new SVG.Matrix().morph(a).at(s.ease(this.pos))) + }else{ + at = at.morph(a).at(s.ease(this.pos)) + } + continue + } + + // when transformation is absolute we have to reset the needed transformation first + if(!a.relative) + a.undo(at.extract()) + + // and reapply it after + at = at.multiply(a.at(s.ease(this.pos))) + + } + + // set new matrix on element + target.matrix(at) + } + + return this + + } + + + // adds an once-callback which is called at a specific position and never again + , once: function(pos, fn, isEased){ + var c = this.last() + if(!isEased) pos = c.ease(pos) + + c.once[pos] = fn + + return this + } + + , _callStart: function() { + setTimeout(function(){this.start()}.bind(this), 0) + return this + } + + } + +, parent: SVG.Element + + // Add method to parent elements +, construct: { + // Get fx module or create a new one, then animate with given duration and ease + animate: function(o, ease, delay) { + return (this.fx || (this.fx = new SVG.FX(this))).animate(o, ease, delay) + } + , delay: function(delay){ + return (this.fx || (this.fx = new SVG.FX(this))).delay(delay) + } + , stop: function(jumpToEnd, clearQueue) { + if (this.fx) + this.fx.stop(jumpToEnd, clearQueue) + + return this + } + , finish: function() { + if (this.fx) + this.fx.finish() + + return this + } + // Pause current animation + , pause: function() { + if (this.fx) + this.fx.pause() + + return this + } + // Play paused current animation + , play: function() { + if (this.fx) + this.fx.play() + + return this + } + // Set/Get the speed of the animations + , speed: function(speed) { + if (this.fx) + if (speed == null) + return this.fx.speed() + else + this.fx.speed(speed) + + return this + } + } + +}) + +// MorphObj is used whenever no morphable object is given +SVG.MorphObj = SVG.invent({ + + create: function(from, to){ + // prepare color for morphing + if(SVG.Color.isColor(to)) return new SVG.Color(from).morph(to) + // check if we have a list of values + if(SVG.regex.delimiter.test(from)) { + // prepare path for morphing + if(SVG.regex.pathLetters.test(from)) return new SVG.PathArray(from).morph(to) + // prepare value list for morphing + else return new SVG.Array(from).morph(to) + } + // prepare number for morphing + if(SVG.regex.numberAndUnit.test(to)) return new SVG.Number(from).morph(to) + + // prepare for plain morphing + this.value = from + this.destination = to + } + +, extend: { + at: function(pos, real){ + return real < 1 ? this.value : this.destination + }, + + valueOf: function(){ + return this.value + } + } + +}) + +SVG.extend(SVG.FX, { + // Add animatable attributes + attr: function(a, v, relative) { + // apply attributes individually + if (typeof a == 'object') { + for (var key in a) + this.attr(key, a[key]) + + } else { + this.add(a, v, 'attrs') + } + + return this + } + // Add animatable styles +, style: function(s, v) { + if (typeof s == 'object') + for (var key in s) + this.style(key, s[key]) + + else + this.add(s, v, 'styles') + + return this + } + // Animatable x-axis +, x: function(x, relative) { + if(this.target() instanceof SVG.G){ + this.transform({x:x}, relative) + return this + } + + var num = new SVG.Number(x) + num.relative = relative + return this.add('x', num) + } + // Animatable y-axis +, y: function(y, relative) { + if(this.target() instanceof SVG.G){ + this.transform({y:y}, relative) + return this + } + + var num = new SVG.Number(y) + num.relative = relative + return this.add('y', num) + } + // Animatable center x-axis +, cx: function(x) { + return this.add('cx', new SVG.Number(x)) + } + // Animatable center y-axis +, cy: function(y) { + return this.add('cy', new SVG.Number(y)) + } + // Add animatable move +, move: function(x, y) { + return this.x(x).y(y) + } + // Add animatable center +, center: function(x, y) { + return this.cx(x).cy(y) + } + // Add animatable size +, size: function(width, height) { + if (this.target() instanceof SVG.Text) { + // animate font size for Text elements + this.attr('font-size', width) + + } else { + // animate bbox based size for all other elements + var box + + if(!width || !height){ + box = this.target().bbox() + } + + if(!width){ + width = box.width / box.height * height + } + + if(!height){ + height = box.height / box.width * width + } + + this.add('width' , new SVG.Number(width)) + .add('height', new SVG.Number(height)) + + } + + return this + } + // Add animatable width +, width: function(width) { + return this.add('width', new SVG.Number(width)) + } + // Add animatable height +, height: function(height) { + return this.add('height', new SVG.Number(height)) + } + // Add animatable plot +, plot: function(a, b, c, d) { + // Lines can be plotted with 4 arguments + if(arguments.length == 4) { + return this.plot([a, b, c, d]) + } + + return this.add('plot', new (this.target().morphArray)(a)) + } + // Add leading method +, leading: function(value) { + return this.target().leading ? + this.add('leading', new SVG.Number(value)) : + this + } + // Add animatable viewbox +, viewbox: function(x, y, width, height) { + if (this.target() instanceof SVG.Container) { + this.add('viewbox', new SVG.ViewBox(x, y, width, height)) + } + + return this + } +, update: function(o) { + if (this.target() instanceof SVG.Stop) { + if (typeof o == 'number' || o instanceof SVG.Number) { + return this.update({ + offset: arguments[0] + , color: arguments[1] + , opacity: arguments[2] + }) + } + + 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', o.offset) + } + + return this + } +}) + +SVG.Box = SVG.invent({ + create: function(x, y, width, height) { + if (typeof x == 'object' && !(x instanceof SVG.Element)) { + // chromes getBoundingClientRect has no x and y property + return SVG.Box.call(this, x.left != null ? x.left : x.x , x.top != null ? x.top : x.y, x.width, x.height) + } else if (arguments.length == 4) { + this.x = x + this.y = y + this.width = width + this.height = height + } + + // add center, right, bottom... + fullBox(this) + } +, extend: { + // Merge rect box with another, return a new instance + merge: function(box) { + var b = new this.constructor() + + // merge boxes + b.x = Math.min(this.x, box.x) + b.y = Math.min(this.y, box.y) + b.width = Math.max(this.x + this.width, box.x + box.width) - b.x + b.height = Math.max(this.y + this.height, box.y + box.height) - b.y + + return fullBox(b) + } + + , transform: function(m) { + var xMin = Infinity, xMax = -Infinity, yMin = Infinity, yMax = -Infinity, p, bbox + + 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) + }) + + bbox = new this.constructor() + bbox.x = xMin + bbox.width = xMax-xMin + bbox.y = yMin + bbox.height = yMax-yMin + + fullBox(bbox) + + return bbox + } + } +}) + +SVG.BBox = SVG.invent({ + // Initialize + create: function(element) { + SVG.Box.apply(this, [].slice.call(arguments)) + + // get values if element is given + if (element instanceof SVG.Element) { + var box + + // yes this is ugly, but Firefox can be a pain when it comes to elements that are not yet rendered + try { + + if (!document.documentElement.contains){ + // This is IE - it does not support contains() for top-level SVGs + var topParent = element.node + while (topParent.parentNode){ + topParent = topParent.parentNode + } + if (topParent != document) throw new Exception('Element not in the dom') + } else { + // the element is NOT in the dom, throw error + if(!document.documentElement.contains(element.node)) throw new Exception('Element not in the dom') + } + + // find native bbox + box = element.node.getBBox() + } catch(e) { + if(element instanceof SVG.Shape){ + var clone = element.clone(SVG.parser.draw.instance).show() + box = clone.node.getBBox() + clone.remove() + }else{ + box = { + x: element.node.clientLeft + , y: element.node.clientTop + , width: element.node.clientWidth + , height: element.node.clientHeight + } + } + } + + SVG.Box.call(this, box) + } + + } + + // Define ancestor +, inherit: SVG.Box + + // Define Parent +, parent: SVG.Element + + // Constructor +, construct: { + // Get bounding box + bbox: function() { + return new SVG.BBox(this) + } + } + +}) + +SVG.BBox.prototype.constructor = SVG.BBox + + +SVG.extend(SVG.Element, { + tbox: function(){ + console.warn('Use of TBox is deprecated and mapped to RBox. Use .rbox() instead.') + return this.rbox(this.doc()) + } +}) + +SVG.RBox = SVG.invent({ + // Initialize + create: function(element) { + SVG.Box.apply(this, [].slice.call(arguments)) + + if (element instanceof SVG.Element) { + SVG.Box.call(this, element.node.getBoundingClientRect()) + } + } + +, inherit: SVG.Box + + // define Parent +, parent: SVG.Element + +, extend: { + addOffset: function() { + // offset by window scroll position, because getBoundingClientRect changes when window is scrolled + this.x += window.pageXOffset + this.y += window.pageYOffset + return this + } + } + + // Constructor +, construct: { + // Get rect box + rbox: function(el) { + if (el) return new SVG.RBox(this).transform(el.screenCTM().inverse()) + return new SVG.RBox(this).addOffset() + } + } + +}) + +SVG.RBox.prototype.constructor = SVG.RBox + +SVG.Matrix = SVG.invent({ + // Initialize + create: function(source) { + var i, base = arrayToMatrix([1, 0, 0, 1, 0, 0]) + + // ensure source as object + source = source instanceof SVG.Element ? + source.matrixify() : + typeof source === 'string' ? + arrayToMatrix(source.split(SVG.regex.delimiter).map(parseFloat)) : + arguments.length == 6 ? + arrayToMatrix([].slice.call(arguments)) : + Array.isArray(source) ? + arrayToMatrix(source) : + typeof source === 'object' ? + source : 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: { + // Extract individual transformations + extract: function() { + // find delta transform points + var px = deltaTransformPoint(this, 0, 1) + , py = deltaTransformPoint(this, 1, 0) + , skewX = 180 / Math.PI * Math.atan2(px.y, px.x) - 90 + + return { + // translation + x: this.e + , y: this.f + , transformedX:(this.e * Math.cos(skewX * Math.PI / 180) + this.f * Math.sin(skewX * Math.PI / 180)) / Math.sqrt(this.a * this.a + this.b * this.b) + , transformedY:(this.f * Math.cos(skewX * Math.PI / 180) + this.e * Math.sin(-skewX * Math.PI / 180)) / Math.sqrt(this.c * this.c + this.d * this.d) + // skew + , skewX: -skewX + , skewY: 180 / Math.PI * Math.atan2(py.y, py.x) + // scale + , scaleX: Math.sqrt(this.a * this.a + this.b * this.b) + , scaleY: Math.sqrt(this.c * this.c + this.d * this.d) + // rotation + , rotation: skewX + , a: this.a + , b: this.b + , c: this.c + , d: this.d + , e: this.e + , f: this.f + , matrix: new SVG.Matrix(this) + } + } + // Clone matrix + , clone: function() { + return new SVG.Matrix(this) + } + // 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 + } + // Multiplies by given matrix + , multiply: function(matrix) { + return new SVG.Matrix(this.native().multiply(parseMatrix(matrix).native())) + } + // Inverses matrix + , inverse: function() { + return new SVG.Matrix(this.native().inverse()) + } + // Translate matrix + , translate: function(x, y) { + return new SVG.Matrix(this.native().translate(x || 0, y || 0)) + } + // Scale matrix + , scale: function(x, y, cx, cy) { + // support uniformal scale + if (arguments.length == 1) { + y = x + } else if (arguments.length == 3) { + cy = cx + cx = y + y = x + } + + return this.around(cx, cy, new SVG.Matrix(x, 0, 0, y, 0, 0)) + } + // Rotate matrix + , rotate: function(r, cx, cy) { + // convert degrees to radians + r = SVG.utils.radians(r) + + return this.around(cx, cy, new SVG.Matrix(Math.cos(r), Math.sin(r), -Math.sin(r), Math.cos(r), 0, 0)) + } + // Flip matrix on x or y, at a given offset + , flip: function(a, o) { + return a == 'x' ? + this.scale(-1, 1, o, 0) : + a == 'y' ? + this.scale(1, -1, 0, o) : + this.scale(-1, -1, a, o != null ? o : a) + } + // Skew + , 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) + + return this.around(cx, cy, new SVG.Matrix(1, Math.tan(y), Math.tan(x), 1, 0, 0)) + } + // 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) { + return this + .multiply(new SVG.Matrix(1, 0, 0, 1, cx || 0, cy || 0)) + .multiply(matrix) + .multiply(new SVG.Matrix(1, 0, 0, 1, -cx || 0, -cy || 0)) + } + // Convert to native SVGMatrix + , native: function() { + // create new matrix + var matrix = SVG.parser.native.createSVGMatrix() + + // update with current values + for (var i = abcdef.length - 1; i >= 0; i--) + matrix[abcdef[i]] = this[abcdef[i]] + + return matrix + } + // Convert matrix to string + , toString: function() { + // Construct the matrix directly, avoid values that are too small + return 'matrix(' + float32String(this.a) + ',' + float32String(this.b) + + ',' + float32String(this.c) + ',' + float32String(this.d) + + ',' + float32String(this.e) + ',' + float32String(this.f) + + ')' + } + } + + // 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.Nested) { + 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) { + var i, source, 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 != null ? + {x:x, y:(y != null ? y : x)} : base // If y has no value, then x is used has its value + + // merge source + this.x = source.x + this.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.native.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 { + // BUG FIX: some browsers will render a stroke if a color is given even though stroke width is 0 + if (a == 'stroke-width') + this.attr('stroke', parseFloat(v) > 0 ? this._stroke : null) + else if (a == 'stroke') + this._stroke = v + + // convert image fill and stroke to patterns + if (a == 'fill' || a == 'stroke') { + if (SVG.regex.isImage.test(v)) + v = this.doc().defs().image(v, 0, 0) + + 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) + + // ensure full hex color + else if (SVG.Color.isColor(v)) + v = new SVG.Color(v) + + // parse array values + else if (Array.isArray(v)) + 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 + } +}) +SVG.extend(SVG.Element, { + // Add transformations + transform: function(o, relative) { + // get target in case of the fx module, otherwise reference this + var target = this + , 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 + } + + // get current matrix + matrix = new SVG.Matrix(target) + + // ensure relative flag + relative = !!relative || !!o.relative + + // act on matrix + if (o.a != null) { + matrix = relative ? + // relative + matrix.multiply(new SVG.Matrix(o)) : + // absolute + new SVG.Matrix(o) + + // act on rotation + } else if (o.rotation != null) { + // ensure centre point + ensureCentre(o, target) + + // apply transformation + matrix = relative ? + // relative + matrix.rotate(o.rotation, o.cx, o.cy) : + // absolute + matrix.rotate(o.rotation - matrix.extract().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 + + if (!relative) { + // absolute; multiply inversed values + var e = matrix.extract() + o.scaleX = o.scaleX * 1 / e.scaleX + o.scaleY = o.scaleY * 1 / e.scaleY + } + + matrix = matrix.scale(o.scaleX, o.scaleY, o.cx, o.cy) + + // act on skew + } else if (o.skew != null || o.skewX != null || o.skewY != null) { + // ensure centre point + ensureCentre(o, target) + + // ensure skew values on both axes + o.skewX = o.skew != null ? o.skew : o.skewX != null ? o.skewX : 0 + o.skewY = o.skew != null ? o.skew : o.skewY != null ? o.skewY : 0 + + if (!relative) { + // absolute; reset skew values + var e = matrix.extract() + matrix = matrix.multiply(new SVG.Matrix().skew(e.skewX, e.skewY, o.cx, o.cy).inverse()) + } + + matrix = matrix.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) { + if (relative) { + // relative + matrix = matrix.translate(o.x, o.y) + } else { + // absolute + if (o.x != null) matrix.e = o.x + if (o.y != null) matrix.f = o.y + } + } + + return this.attr('transform', matrix) + } +}) + +SVG.extend(SVG.FX, { + transform: function(o, relative) { + // 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() + } +}) + +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) })] + }) + // merge every transformation into one matrix + .reduce(function(matrix, transform){ + + if(transform[0] == 'matrix') return matrix.multiply(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.Transformation = SVG.invent({ + + create: function(source, inversed){ + + if(arguments.length > 1 && typeof inversed != 'boolean'){ + return this.constructor.call(this, [].slice.call(arguments)) + } + + if(Array.isArray(source)){ + for(var i = 0, len = this.arguments.length; i < len; ++i){ + this[this.arguments[i]] = source[i] + } + } else if(typeof source == 'object'){ + for(var i = 0, len = this.arguments.length; i < len; ++i){ + this[this.arguments[i]] = source[this.arguments[i]] + } + } + + this.inversed = false + + if(inversed === true){ + this.inversed = true + } + + } + +, extend: { + + arguments: [] + , method: '' + + , at: function(pos){ + + var params = [] + + for(var i = 0, len = this.arguments.length; i < len; ++i){ + params.push(this[this.arguments[i]]) + } + + var m = this._undo || new SVG.Matrix() + + m = new SVG.Matrix().morph(SVG.Matrix.prototype[this.method].apply(m, params)).at(pos) + + return this.inversed ? m.inverse() : m + + } + + , undo: function(o){ + for(var i = 0, len = this.arguments.length; i < len; ++i){ + o[this.arguments[i]] = typeof this[this.arguments[i]] == 'undefined' ? 0 : o[this.arguments[i]] + } + + // The method SVG.Matrix.extract which was used before calling this + // method to obtain a value for the parameter o doesn't return a cx and + // a cy so we use the ones that were provided to this object at its creation + o.cx = this.cx + o.cy = this.cy + + this._undo = new SVG[capitalize(this.method)](o, true).at(1) + + return this + } + + } + +}) + +SVG.Translate = SVG.invent({ + + parent: SVG.Matrix +, inherit: SVG.Transformation + +, create: function(source, inversed){ + this.constructor.apply(this, [].slice.call(arguments)) + } + +, extend: { + arguments: ['transformedX', 'transformedY'] + , method: 'translate' + } + +}) + +SVG.Rotate = SVG.invent({ + + parent: SVG.Matrix +, inherit: SVG.Transformation + +, create: function(source, inversed){ + this.constructor.apply(this, [].slice.call(arguments)) + } + +, extend: { + arguments: ['rotation', 'cx', 'cy'] + , method: 'rotate' + , at: function(pos){ + var m = new SVG.Matrix().rotate(new SVG.Number().morph(this.rotation - (this._undo ? this._undo.rotation : 0)).at(pos), this.cx, this.cy) + return this.inversed ? m.inverse() : m + } + , undo: function(o){ + this._undo = o + return this + } + } + +}) + +SVG.Scale = SVG.invent({ + + parent: SVG.Matrix +, inherit: SVG.Transformation + +, create: function(source, inversed){ + this.constructor.apply(this, [].slice.call(arguments)) + } + +, extend: { + arguments: ['scaleX', 'scaleY', 'cx', 'cy'] + , method: 'scale' + } + +}) + +SVG.Skew = SVG.invent({ + + parent: SVG.Matrix +, inherit: SVG.Transformation + +, create: function(source, inversed){ + this.constructor.apply(this, [].slice.call(arguments)) + } + +, extend: { + arguments: ['skewX', 'skewY', 'cx', 'cy'] + , method: 'skew' + } + +}) + +SVG.extend(SVG.Element, { + // Dynamic style generator + style: function(s, v) { + if (arguments.length == 0) { + // get full style + return this.node.style.cssText || '' + + } else if (arguments.length < 2) { + // apply every style individually if an object is passed + if (typeof s == 'object') { + for (v in s) this.style(v, s[v]) + + } else if (SVG.regex.isCss.test(s)) { + // parse css string + s = s.split(/\s*;\s*/) + // filter out suffix ; and stuff like ;; + .filter(function(e) { return !!e }) + .map(function(e){ return e.split(/\s*:\s*/) }) + + // apply every definition individually + while (v = s.pop()) { + this.style(v[0], v[1]) + } + } else { + // act as a getter if the first and only argument is not an object + return this.node.style[camelCase(s)] + } + + } else { + this.node.style[camelCase(s)] = v === null || SVG.regex.isBlank.test(v) ? '' : v + } + + return this + } +}) +SVG.Parent = SVG.invent({ + // Initialize node + create: function(element) { + this.constructor.call(this, element) + } + + // Inherit from +, inherit: SVG.Element + + // Add class methods +, extend: { + // Returns all child elements + children: function() { + return SVG.utils.map(SVG.utils.filterSVGElements(this.node.childNodes), function(node) { + return SVG.adopt(node) + }) + } + // Add given element at a position + , add: function(element, i) { + if (i == null) + this.node.appendChild(element.node) + else if (element.node != this.node.childNodes[i]) + this.node.insertBefore(element.node, this.node.childNodes[i]) + + return this + } + // Basically does the same as `add()` but returns the added element instead + , put: function(element, i) { + this.add(element, i) + return 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.childNodes).indexOf(element.node) + } + // Get a element at the given index + , get: function(i) { + return SVG.adopt(this.node.childNodes[i]) + } + // Get first child + , first: function() { + return this.get(0) + } + // Get the last child + , last: function() { + return this.get(this.node.childNodes.length - 1) + } + // Iterates over all children and invokes a given block + , each: function(block, deep) { + var i, il + , children = this.children() + + 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.Container)) + 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 + } + , // Get defs + defs: function() { + return this.doc().defs() + } + } + +}) + +SVG.extend(SVG.Parent, { + + ungroup: function(parent, depth) { + if(depth === 0 || this instanceof SVG.Defs || this.node == SVG.parser.draw) return this + + parent = parent || (this instanceof SVG.Doc ? this : this.parent(SVG.Parent)) + depth = depth || Infinity + + this.each(function(){ + if(this instanceof SVG.Defs) return this + if(this instanceof SVG.Parent) return this.ungroup(parent, depth-1) + return this.toParent(parent) + }) + + this.node.firstChild || this.remove() + + return this + }, + + flatten: function(parent, depth) { + return this.ungroup(parent, depth) + } + +}) +SVG.Container = SVG.invent({ + // Initialize node + create: function(element) { + this.constructor.call(this, element) + } + + // Inherit from +, inherit: SVG.Parent + +}) + +SVG.ViewBox = SVG.invent({ + + create: function(source) { + var i, base = [0, 0, 0, 0] + + var x, y, width, height, box, view, we, he + , wm = 1 // width multiplier + , hm = 1 // height multiplier + , reg = /[+-]?(?:\d+(?:\.\d*)?|\.\d+)(?:e[+-]?\d+)?/gi + + if(source instanceof SVG.Element){ + + we = source + he = source + view = (source.attr('viewBox') || '').match(reg) + box = source.bbox + + // get dimensions of current node + width = new SVG.Number(source.width()) + height = new SVG.Number(source.height()) + + // find nearest non-percentual dimensions + while (width.unit == '%') { + wm *= width.value + width = new SVG.Number(we instanceof SVG.Doc ? we.parent().offsetWidth : we.parent().width()) + we = we.parent() + } + while (height.unit == '%') { + hm *= height.value + height = new SVG.Number(he instanceof SVG.Doc ? he.parent().offsetHeight : he.parent().height()) + he = he.parent() + } + + // ensure defaults + this.x = 0 + this.y = 0 + this.width = width * wm + this.height = height * hm + this.zoom = 1 + + if (view) { + // get width and height from viewbox + x = parseFloat(view[0]) + y = parseFloat(view[1]) + width = parseFloat(view[2]) + height = parseFloat(view[3]) + + // calculate zoom accoring to viewbox + this.zoom = ((this.width / this.height) > (width / height)) ? + this.height / height : + this.width / width + + // calculate real pixel dimensions on parent SVG.Doc element + this.x = x + this.y = y + this.width = width + this.height = height + + } + + }else{ + + // ensure source as object + source = typeof source === 'string' ? + source.match(reg).map(function(el){ return parseFloat(el) }) : + Array.isArray(source) ? + source : + typeof source == 'object' ? + [source.x, 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] + } + + + } + +, extend: { + + toString: function() { + return this.x + ' ' + this.y + ' ' + this.width + ' ' + this.height + } + , morph: function(x, y, width, height){ + this.destination = new SVG.ViewBox(x, y, width, height) + return this + } + + , at: function(pos) { + + if(!this.destination) return this + + return new SVG.ViewBox([ + 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.Container + + // Add parent method +, construct: { + + // get/set viewbox + viewbox: function(x, y, width, height) { + if (arguments.length == 0) + // act as a getter if there are no arguments + return new SVG.ViewBox(this) + + // otherwise act as a setter + return this.attr('viewBox', new SVG.ViewBox(x, y, width, height)) + } + + } + +}) +// 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 + if (f == null) { + SVG.off(this, event) + } else { + 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 + }, + fire: function (event, data) { + // Dispatch event + if (event instanceof window.Event) { + this.node.dispatchEvent(event) + } else { + this.node.dispatchEvent(event = new SVG.CustomEvent(event, {detail: data, cancelable: true})) + } + this._event = event + return this + }, + event: function() { + return this._event + } +}) + + +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: { + // Move over x-axis + x: function(x) { + return x == null ? this.transform('x') : this.transform({ x: x - this.x() }, true) + } + // Move over y-axis + , y: function(y) { + return y == null ? this.transform('y') : this.transform({ y: y - this.y() }, true) + } + // Move by center over x-axis + , cx: function(x) { + return x == null ? this.gbox().cx : this.x(x - this.gbox().width / 2) + } + // Move by center over y-axis + , cy: function(y) { + return y == null ? this.gbox().cy : this.y(y - this.gbox().height / 2) + } + , gbox: function() { + + var bbox = this.bbox() + , trans = this.transform() + + bbox.x += trans.x + bbox.x2 += trans.x + bbox.cx += trans.x + + bbox.y += trans.y + bbox.y2 += trans.y + bbox.cy += trans.y + + return bbox + } + } + + // Add parent method +, construct: { + // Create a group element + group: function() { + return this.put(new SVG.G) + } + } +}) + +SVG.Doc = SVG.invent({ + // Initialize node + create: function(element) { + if (element) { + // ensure the presence of a dom element + element = typeof element == 'string' ? + document.getElementById(element) : + element + + // If the target is an svg element, use that element as the main wrapper. + // This allows svg.js to work with svg documents as well. + if (element.nodeName == 'svg') { + this.constructor.call(this, element) + } else { + this.constructor.call(this, SVG.create('svg')) + element.appendChild(this.node) + this.size('100%', '100%') + } + + // set svg element attributes and ensure defs node + this.namespace().defs() + } + } + + // Inherit from +, inherit: SVG.Container + + // Add class methods +, extend: { + // Add namespaces + namespace: function() { + 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._defs) { + var defs + + // Find or create a defs element in this instance + if (defs = this.node.getElementsByTagName('defs')[0]) + this._defs = SVG.adopt(defs) + else + this._defs = new SVG.Defs + + // Make sure the defs node is at the end of the stack + this.node.appendChild(this._defs.node) + } + + return this._defs + } + // custom parent method + , parent: function() { + if(!this.node.parentNode || this.node.parentNode.nodeName == '#document' || this.node.parentNode.nodeName == '#document-fragment') return null + return this.node.parentNode + } + // Fix for possible sub-pixel offset. See: + // https://bugzilla.mozilla.org/show_bug.cgi?id=608812 + , spof: function() { + var pos = this.node.getScreenCTM() + + if (pos) + this + .style('left', (-pos.e % 1) + 'px') + .style('top', (-pos.f % 1) + 'px') + + return this + } + + // Removes the doc from the DOM + , remove: function() { + if(this.parent()) { + this.parent().removeChild(this.node) + } + + return this + } + , clear: function() { + // remove children + while(this.node.hasChildNodes()) + this.node.removeChild(this.node.lastChild) + + // remove defs reference + delete this._defs + + // add back parser + if(!SVG.parser.draw.parentNode) + this.node.appendChild(SVG.parser.draw) + + return this + } + , clone: function (parent) { + // write dom data to the dom so the clone can pickup the data + this.writeDataToDom() + + // get reference to node + var node = this.node + + // clone element and assign new id + var clone = assignNewId(node.cloneNode(true)) + + // insert the clone in the given parent or after myself + if(parent) { + (parent.node || parent).appendChild(clone.node) + } else { + node.parentNode.insertBefore(clone.node, node.nextSibling) + } + + return clone + } + } + +}) + +// ### 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) +, previous: function() { + return this.siblings()[this.position() - 1] + } + // Send given element one step forward +, forward: function() { + var i = this.position() + 1 + , 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: function() { + this.constructor.call(this, SVG.create('mask')) + + // keep references to masked elements + this.targets = [] + } + + // Inherit from +, inherit: SVG.Container + + // Add class methods +, extend: { + // Unmask all masked elements and remove itself + remove: function() { + // unmask all targets + for (var i = this.targets.length - 1; i >= 0; i--) + if (this.targets[i]) + this.targets[i].unmask() + this.targets = [] + + // remove mask from parent + SVG.Element.prototype.remove.call(this) + + return this + } + } + + // 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 + this.masker = element instanceof SVG.Mask ? element : this.parent().mask().add(element) + + // store reverence on self in mask + this.masker.targets.push(this) + + // apply mask + return this.attr('mask', 'url("#' + this.masker.attr('id') + '")') + } + // Unmask element +, unmask: function() { + delete this.masker + return this.attr('mask', null) + } + +}) + +SVG.ClipPath = SVG.invent({ + // Initialize node + create: function() { + this.constructor.call(this, SVG.create('clipPath')) + + // keep references to clipped elements + this.targets = [] + } + + // Inherit from +, inherit: SVG.Container + + // Add class methods +, extend: { + // Unclip all clipped elements and remove itself + remove: function() { + // unclip all targets + for (var i = this.targets.length - 1; i >= 0; i--) + if (this.targets[i]) + this.targets[i].unclip() + this.targets = [] + + // remove clipPath from parent + this.parent().removeElement(this) + + return this + } + } + + // 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 + this.clipper = element instanceof SVG.ClipPath ? element : this.parent().clip().add(element) + + // store reverence on self in mask + this.clipper.targets.push(this) + + // apply mask + return this.attr('clip-path', 'url("#' + this.clipper.attr('id') + '")') + } + // Unclip element +, unclip: function() { + delete this.clipper + return this.attr('clip-path', null) + } + +}) +SVG.Gradient = SVG.invent({ + // Initialize node + create: function(type) { + this.constructor.call(this, SVG.create(type + 'Gradient')) + + // store type + this.type = type + } + + // Inherit from +, inherit: SVG.Container + + // Add class methods +, extend: { + // Add a color stop + at: 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 + , fill: function() { + return 'url(#' + this.id() + ')' + } + // Alias string convertion to fill + , toString: function() { + return this.fill() + } + // 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.FX, { + // From position + from: function(x, y) { + return (this._target || this).type == 'radial' ? + 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 == 'radial' ? + 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 + fill: 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.fill() + } + // 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.Shape = SVG.invent({ + // Initialize node + create: function(element) { + this.constructor.call(this, element) + } + + // Inherit from +, inherit: SVG.Element + +}) + +SVG.Bare = SVG.invent({ + // Initialize + create: function(element, inherit) { + // construct element + this.constructor.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) + } + } +}) +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.FX, { + // 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.FX, { + // 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)) + } +}) +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] + ) + } + } +}) + +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) + } +}) +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) { + if (!url) return this + + var self = this + , img = new window.Image() + + // preload image + SVG.on(img, 'load', function() { + SVG.off(img) + + var p = self.parent(SVG.Pattern) + + if(p === null) return + + // ensure image size + if (self.width() == 0 && self.height() == 0) + self.size(img.width, img.height) + + // ensure pattern size if not set + if (p && p.width() == 0 && p.height() == 0) + p.size(self.width(), self.height()) + + // callback + if (typeof self._loaded === 'function') + self._loaded.call(self, { + width: img.width + , height: img.height + , ratio: img.width / img.height + , url: url + }) + }) + + SVG.on(img, 'error', function(e){ + SVG.off(img) + + if (typeof self._error === 'function'){ + self._error.call(self, e) + } + }) + + return this.attr('href', (img.src = this.src = url), SVG.xlink) + } + // Add loaded callback + , loaded: function(loaded) { + this._loaded = loaded + return this + } + + , error: function(error) { + this._error = error + return this + } + } + + // Add parent method +, construct: { + // create image element, load image and set its size + image: function(source, width, height) { + return this.put(new SVG.Image).load(source).size(width || 0, height || width || 0) + } + } + +}) +SVG.Text = SVG.invent({ + // Initialize node + create: function() { + this.constructor.call(this, 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.Shape + + // 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') + , 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.valueOf() === '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 (typeof text === 'undefined'){ + var text = '' + var children = this.node.childNodes + for(var i = 0, len = children.length; i < len; ++i){ + + // add newline if its not the first child and newLined is set to true + if(i != 0 && 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); + text.call(this, this) + } else { // store text and make sure text is not blank - text = (text + '').split('\n'); // build new lines + text = text.split('\n') - for (let j = 0, jl = text.length; j < jl; j++) { - this.newLine(text[j]); - } - } // disable build mode and rebuild lines + // build new lines + for (var i = 0, il = text.length; i < il; i++) + this.tspan(text[i]).newLine() + } - - return this.build(false).rebuild(); + // disable build mode and rebuild lines + return this.build(false).rebuild() } + // Set font size + , size: function(size) { + return this.attr('font-size', size).rebuild() + } + // Set / get leading + , leading: function(value) { + // act as getter + if (value == null) + return this.dom.leading - } - extend(Text, textable); - registerMethods({ - Container: { - // Create text element - text: wrapWithAttrCheck(function (text = '') { - return this.put(new Text()).text(text); - }), - // Create plain text element - plain: wrapWithAttrCheck(function (text = '') { - return this.put(new Text()).plain(text); + // act as setter + this.dom.leading = new SVG.Number(value) + + return this.rebuild() + } + // Get all the first level lines + , lines: function() { + var node = (this.textPath && this.textPath() || this).node + + // filter tspans and map them to SVG.js instances + var lines = SVG.utils.map(SVG.utils.filterSVGElements(node.childNodes), function(el){ + return SVG.adopt(el) }) + + // return an instance of SVG.set + return new SVG.Set(lines) } - }); - register(Text, 'Text'); + // Rebuild appearance type + , rebuild: function(rebuild) { + // store new rebuild flag if given + if (typeof rebuild == 'boolean') + this._rebuild = rebuild - class Tspan extends Shape { - // Initialize node - constructor(node, attrs = node) { - super(nodeOrNew('tspan', node), attrs); - this._build = false; // disable build mode for adding multiple lines - } // Shortcut dx + // define position of all lines + if (this._rebuild) { + var self = this + , blankLineOffset = 0 + , dy = this.dom.leading * new SVG.Number(this.attr('font-size')) + this.lines().each(function() { + if (this.dom.newLined) { + if (!self.textPath()) + this.attr('x', self.attr('x')) + if(this.text() == '\n') { + blankLineOffset += dy + }else{ + this.attr('dy', dy + blankLineOffset) + blankLineOffset = 0 + } + } + }) - dx(dx) { - return this.attr('dx', dx); - } // Shortcut dy + 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 + } + } - dy(dy) { - return this.attr('dy', dy); - } // Create new line + // 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.Shape + + // 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) - newLine() { // mark new line - this.dom.newLined = true; // fetch parent + this.dom.newLined = true - const text = this.parent(); // early return in case we are not in a text element - - if (!(text instanceof Text)) { - return this; - } - - const i = text.index(this); - const fontSize = globals.window.getComputedStyle(this.node).getPropertyValue('font-size'); - const dy = text.dom.leading * new SVGNumber(fontSize); // apply new position - - return this.dy(i ? dy : 0).attr('x', text.x()); - } // Set text content - - - text(text) { - if (text == null) return this.node.textContent + (this.dom.newLined ? '\n' : ''); - - if (typeof text === 'function') { - this.clear().build(true); - text.call(this, this); - this.build(false); - } else { - this.plain(text); - } - - return this; + // apply new hy¡n + return this.dy(t.dom.leading * t.attr('font-size')).attr('x', t.x()) } - - } - extend(Tspan, textable); - registerMethods({ - Tspan: { - tspan: wrapWithAttrCheck(function (text = '') { - const tspan = new Tspan(); // clear if build mode is disabled - - if (!this._build) { - this.clear(); - } // add new tspan - - - return this.put(tspan).text(text); - }) - }, - Text: { - newLine: function (text = '') { - return this.tspan(text).newLine(); - } - } - }); - register(Tspan, 'Tspan'); - - class Circle extends Shape { - constructor(node, attrs = node) { - super(nodeOrNew('circle', node), attrs); - } - - radius(r) { - return this.attr('r', r); - } // Radius x value - - - rx(rx) { - return this.attr('r', rx); - } // Alias radius x value - - - ry(ry) { - return this.rx(ry); - } - - size(size) { - return this.radius(new SVGNumber(size).divide(2)); - } - - } - extend(Circle, { - x: x$3, - y: y$3, - cx: cx$1, - cy: cy$1, - width: width$2, - height: height$2 - }); - registerMethods({ - Container: { - // Create circle element - circle: wrapWithAttrCheck(function (size = 0) { - return this.put(new Circle()).size(size).move(0, 0); - }) - } - }); - register(Circle, 'Circle'); - - class ClipPath extends Container { - constructor(node, attrs = node) { - super(nodeOrNew('clipPath', node), attrs); - } // Unclip all clipped elements and remove itself - - - remove() { - // unclip all targets - this.targets().forEach(function (el) { - el.unclip(); - }); // remove clipPath from parent - - return super.remove(); - } - - targets() { - return baseFind('svg [clip-path*="' + this.id() + '"]'); - } - - } - registerMethods({ - Container: { - // Create clipping element - clip: wrapWithAttrCheck(function () { - return this.defs().put(new ClipPath()); - }) - }, - Element: { - // Distribute clipPath to svg element - clipper() { - return this.reference('clip-path'); - }, - - clipWith(element) { - // use given clip or create a new one - const clipper = element instanceof ClipPath ? element : this.parent().clip().add(element); // apply mask - - return this.attr('clip-path', 'url("#' + clipper.id() + '")'); - }, - - // Unclip element - unclip() { - return this.attr('clip-path', null); - } - - } - }); - register(ClipPath, 'ClipPath'); - - class ForeignObject extends Element { - constructor(node, attrs = node) { - super(nodeOrNew('foreignObject', node), attrs); - } - - } - registerMethods({ - Container: { - foreignObject: wrapWithAttrCheck(function (width, height) { - return this.put(new ForeignObject()).size(width, height); - }) - } - }); - register(ForeignObject, 'ForeignObject'); - - function dmove(dx, dy) { - this.children().forEach((child, i) => { - let bbox; // We have to wrap this for elements that dont have a bbox - // e.g. title and other descriptive elements - - try { - // Get the childs bbox - bbox = child.bbox(); - } catch (e) { - return; - } // Get childs matrix - - - const m = new Matrix(child); // Translate childs matrix by amount and - // transform it back into parents space - - const matrix = m.translate(dx, dy).transform(m.inverse()); // Calculate new x and y from old box - - const p = new Point(bbox.x, bbox.y).transform(matrix); // Move element - - child.move(p.x, p.y); - }); - return this; - } - function dx(dx) { - return this.dmove(dx, 0); - } - function dy(dy) { - return this.dmove(0, dy); - } - function height(height, box = this.bbox()) { - if (height == null) return box.height; - return this.size(box.width, height, box); - } - function move(x = 0, y = 0, box = this.bbox()) { - const dx = x - box.x; - const dy = y - box.y; - return this.dmove(dx, dy); - } - function size(width, height, box = this.bbox()) { - const p = proportionalSize(this, width, height, box); - const scaleX = p.width / box.width; - const scaleY = p.height / box.height; - this.children().forEach((child, i) => { - const o = new Point(box).transform(new Matrix(child).inverse()); - child.scale(scaleX, scaleY, o.x, o.y); - }); - return this; - } - function width(width, box = this.bbox()) { - if (width == null) return box.width; - return this.size(width, box.height, box); - } - function x(x, box = this.bbox()) { - if (x == null) return box.x; - return this.move(x, box.y, box); - } - function y(y, box = this.bbox()) { - if (y == null) return box.y; - return this.move(box.x, y, box); } - var containerGeometry = { - __proto__: null, - dmove: dmove, - dx: dx, - dy: dy, - height: height, - move: move, - size: size, - width: width, - x: x, - y: y - }; +}) - class G extends Container { - constructor(node, attrs = node) { - super(nodeOrNew('g', node), attrs); - } +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 } - extend(G, containerGeometry); - registerMethods({ - Container: { - // Create a group element - group: wrapWithAttrCheck(function () { - return this.put(new G()); - }) - } - }); - register(G, 'G'); + // Create a tspan +, tspan: function(text) { + var node = (this.textPath && this.textPath() || this).node + , tspan = new SVG.Tspan - class A extends Container { - constructor(node, attrs = node) { - super(nodeOrNew('a', node), attrs); - } // Link target attribute + // clear if build mode is disabled + if (this._build === false) + this.clear() + // add new tspan + node.appendChild(tspan.node) - target(target) { - return this.attr('target', target); - } // Link url - - - to(url) { - return this.attr('href', url, xlink); - } - + return tspan.text(text) } - extend(A, containerGeometry); - registerMethods({ - Container: { - // Create a hyperlink element - link: wrapWithAttrCheck(function (url) { - return this.put(new A()).to(url); - }) - }, - Element: { - unlink() { - const link = this.linker(); - if (!link) return this; - const parent = link.parent(); + // Clear all lines +, clear: function() { + var node = (this.textPath && this.textPath() || this).node - if (!parent) { - return this.remove(); - } - - const index = parent.index(link); - parent.add(this, index); - link.remove(); - return this; - }, - - linkTo(url) { - // reuse old link if possible - let link = this.linker(); - - if (!link) { - link = new A(); - this.wrap(link); - } - - if (typeof url === 'function') { - url.call(link, link); - } else { - link.to(url); - } - - return this; - }, - - linker() { - const link = this.parent(); - - if (link && link.node.nodeName.toLowerCase() === 'a') { - return link; - } - - return null; - } - - } - }); - register(A, 'A'); - - class Mask extends Container { - // Initialize node - constructor(node, attrs = node) { - super(nodeOrNew('mask', node), attrs); - } // Unmask all masked elements and remove itself - - - remove() { - // unmask all targets - this.targets().forEach(function (el) { - el.unmask(); - }); // remove mask from parent - - return super.remove(); - } - - targets() { - return baseFind('svg [mask*="' + this.id() + '"]'); - } + // remove existing child nodes + while (node.hasChildNodes()) + node.removeChild(node.lastChild) + return this } - registerMethods({ - Container: { - mask: wrapWithAttrCheck(function () { - return this.defs().put(new Mask()); - }) - }, - Element: { - // Distribute mask to svg element - masker() { - return this.reference('mask'); - }, - - maskWith(element) { - // use given mask or create a new one - const masker = element instanceof Mask ? element : this.parent().mask().add(element); // apply mask - - return this.attr('mask', 'url("#' + masker.id() + '")'); - }, - - // Unmask element - unmask() { - return this.attr('mask', null); - } - - } - }); - register(Mask, 'Mask'); - - class Stop extends Element { - constructor(node, attrs = node) { - super(nodeOrNew('stop', node), attrs); - } // add color stops - - - update(o) { - if (typeof o === 'number' || o instanceof SVGNumber) { - 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 SVGNumber(o.offset)); - return this; - } - + // Get length of text element +, length: function() { + return this.node.getComputedTextLength() } - registerMethods({ - Gradient: { - // Add a color stop - stop: function (offset, color, opacity) { - return this.put(new Stop()).update(offset, color, opacity); - } +}) + +SVG.TextPath = SVG.invent({ + // Initialize node + create: 'textPath' + + // Inherit from +, inherit: SVG.Parent + + // Define parent class +, parent: SVG.Text + + // Add parent method +, construct: { + morphArray: SVG.PathArray + // Create path for text to run on + , path: function(d) { + // create textPath element + var path = new SVG.TextPath + , track = this.doc().defs().path(d) + + // move lines to textpath + while (this.node.hasChildNodes()) + path.node.appendChild(this.node.firstChild) + + // add textPath element as child node + this.node.appendChild(path.node) + + // link textPath to path and add content + path.attr('href', '#' + track, SVG.xlink) + + return this } - }); - register(Stop, 'Stop'); + // return the array of the path track element + , array: function() { + var track = this.track() - function cssRule(selector, rule) { - if (!selector) return ''; - if (!rule) return selector; - let ret = selector + '{'; - - for (const i in rule) { - ret += unCamelCase(i) + ':' + rule[i] + ';'; + return track ? track.array() : null } - - ret += '}'; - return ret; - } - - class Style extends Element { - constructor(node, attrs = node) { - super(nodeOrNew('style', node), attrs); - } - - addText(w = '') { - this.node.textContent += w; - return this; - } - - font(name, src, params = {}) { - return this.rule('@font-face', { - fontFamily: name, - src: src, - ...params - }); - } - - rule(selector, obj) { - return this.addText(cssRule(selector, obj)); - } - - } - registerMethods('Dom', { - style(selector, obj) { - return this.put(new Style()).rule(selector, obj); - }, - - fontface(name, src, params) { - return this.put(new Style()).font(name, src, params); - } - - }); - register(Style, 'Style'); - - class TextPath extends Text { - // Initialize node - constructor(node, attrs = node) { - super(nodeOrNew('textPath', node), attrs); - } // return the array of the path track element - - - array() { - const track = this.track(); - return track ? track.array() : null; - } // Plot path if any - - - plot(d) { - const track = this.track(); - let pathArray = null; + // Plot path if any + , plot: function(d) { + var track = this.track() + , pathArray = null if (track) { - pathArray = track.plot(d); + pathArray = track.plot(d) } - return d == null ? pathArray : this; - } // Get the path element + return (d == null) ? pathArray : this + } + // Get the path track element + , track: function() { + var path = this.textPath() + if (path) + return path.reference('href') + } + // Get the textPath child + , textPath: function() { + if (this.node.firstChild && this.node.firstChild.nodeName == 'textPath') + return SVG.adopt(this.node.firstChild) + } + } +}) - track() { - return this.reference('href'); +SVG.Nested = SVG.invent({ + // Initialize node + create: function() { + this.constructor.call(this, SVG.create('svg')) + + this.style('overflow', 'visible') + } + + // Inherit from +, inherit: SVG.Container + + // Add parent method +, construct: { + // Create nested svg document + nested: function() { + return this.put(new SVG.Nested) + } + } +}) +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 show attribute + , show: function(target) { + return this.attr('show', target, 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 i, extension = {} + + 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.FX, extension) + +}) + +SVG.extend(SVG.Element, SVG.FX, { + // Map rotation to transform + rotate: function(d, cx, cy) { + return this.transform({ rotation: d, cx: cx, cy: cy }) + } + // Map skew to transform +, skew: function(x, y, cx, cy) { + return arguments.length == 1 || arguments.length == 3 ? + this.transform({ skew: x, cx: y, cy: cx }) : + this.transform({ skewX: x, skewY: y, cx: cx, cy: cy }) + } + // Map scale to transform +, scale: function(x, y, cx, cy) { + return arguments.length == 1 || arguments.length == 3 ? + this.transform({ scale: x, cx: y, cy: cx }) : + this.transform({ scaleX: x, scaleY: y, cx: cx, cy: cy }) + } + // Map translate to transform +, translate: function(x, y) { + return this.transform({ x: x, y: y }) + } + // Map flip to transform +, flip: function(a, o) { + o = typeof a == 'number' ? a : o + return this.transform({ flip: a || 'both', offset: o }) + } + // Map matrix to transform +, matrix: function(m) { + return this.attr('transform', new SVG.Matrix(arguments.length == 6 ? [].slice.call(arguments) : m)) + } + // 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.FX ? 0 : this.x()), true) + } + // Relative move over y axis +, dy: function(y) { + return this.y(new SVG.Number(y).plus(this instanceof SVG.FX ? 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.FX, { + // Add x and y radius + radius: function(x, y) { + var type = (this._target || this).type; + return type == 'radial' || type == 'circle' ? + 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 this.node.getPointAtLength(length) + } +}) + +SVG.extend(SVG.Parent, SVG.Text, SVG.Tspan, SVG.FX, { + // 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) } - registerMethods({ - Container: { - textPath: wrapWithAttrCheck(function (text, path) { - // Convert text to instance if needed - if (!(text instanceof Text)) { - text = this.text(text); - } +}) - return text.path(path); +SVG.Set = SVG.invent({ + // Initialize + create: function(members) { + if (members instanceof SVG.Set) { + this.members = members.members.slice() + } else { + Array.isArray(members) ? this.members = members : this.clear() + } + } + + // Add class methods +, extend: { + // Add element to set + add: function() { + var i, il, elements = [].slice.call(arguments) + + for (i = 0, il = elements.length; i < il; i++) + this.members.push(elements[i]) + + return this + } + // Remove element from set + , remove: function(element) { + var i = this.index(element) + + // remove given child + if (i > -1) + this.members.splice(i, 1) + + return this + } + // Iterate over all members + , each: function(block) { + for (var i = 0, il = this.members.length; i < il; i++) + block.apply(this.members[i], [i, this.members]) + + return this + } + // Restore to defaults + , clear: function() { + // initialize store + this.members = [] + + return this + } + // Get the length of a set + , length: function() { + return this.members.length + } + // Checks if a given element is present in set + , has: function(element) { + return this.index(element) >= 0 + } + // retuns index of given element in set + , index: function(element) { + return this.members.indexOf(element) + } + // Get member at given index + , get: function(i) { + return this.members[i] + } + // Get first member + , first: function() { + return this.get(0) + } + // Get last member + , last: function() { + return this.get(this.members.length - 1) + } + // Default value + , valueOf: function() { + return this.members + } + // Get the bounding box of all members included or empty box if set has no items + , bbox: function(){ + // return an empty box of there are no members + if (this.members.length == 0) + return new SVG.RBox() + + // get the first rbox and update the target bbox + var rbox = this.members[0].rbox(this.members[0].doc()) + + this.each(function() { + // user rbox for correct position and visual representation + rbox = rbox.merge(this.rbox(this.doc())) }) - }, - Text: { - // Create path for text to run on - path: wrapWithAttrCheck(function (track, importNodes = true) { - const textPath = new TextPath(); // if track is a path, reuse it - if (!(track instanceof Path)) { - // create path element - track = this.defs().path(track); - } // link textPath to path and add content + return rbox + } + } + + // Add parent method +, construct: { + // Create a new set + set: function(members) { + return new SVG.Set(members) + } + } +}) + +SVG.FX.Set = SVG.invent({ + // Initialize node + create: function(set) { + // store reference to set + this.set = set + } + +}) + +// Alias methods +SVG.Set.inherit = function() { + var m + , methods = [] + + // gather shape methods + for(var m in SVG.Shape.prototype) + if (typeof SVG.Shape.prototype[m] == 'function' && typeof SVG.Set.prototype[m] != 'function') + methods.push(m) + + // apply shape aliasses + methods.forEach(function(method) { + SVG.Set.prototype[method] = function() { + for (var i = 0, il = this.members.length; i < il; i++) + if (this.members[i] && typeof this.members[i][method] == 'function') + this.members[i][method].apply(this.members[i], arguments) + + return method == 'animate' ? (this.fx || (this.fx = new SVG.FX.Set(this))) : this + } + }) + + // clear methods for the next round + methods = [] + + // gather fx methods + for(var m in SVG.FX.prototype) + if (typeof SVG.FX.prototype[m] == 'function' && typeof SVG.FX.Set.prototype[m] != 'function') + methods.push(m) + + // apply fx aliasses + methods.forEach(function(method) { + SVG.FX.Set.prototype[method] = function() { + for (var i = 0, il = this.set.members.length; i < il; i++) + this.set.members[i].fx[method].apply(this.set.members[i].fx, arguments) + + return this + } + }) +} - textPath.attr('href', '#' + track, xlink); // Transplant all nodes from text to textPath +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]) - let node; + } else if (arguments.length < 2) { + try { + return JSON.parse(this.attr('data-' + a)) + } catch(e) { + return this.attr('data-' + a) + } - if (importNodes) { - while (node = this.node.firstChild) { - textPath.node.appendChild(node); + } 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 v in k) + this.remember(v, k[v]) + + // retrieve memory + else if (arguments.length == 1) + return this.memory()[k] + + // store memory + else + 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 = {}) + } + +}) +// 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 new SVG.Set( + SVG.utils.map((parent || document).querySelectorAll(query), function(node) { + return SVG.adopt(node) + }) + ) +} + +SVG.extend(SVG.Parent, { + // Scoped select method + select: function(query) { + return SVG.select(query, this.node) + } + +}) +function pathRegReplace(a, b, c, d) { + return c + d.replace(SVG.regex.dots, ' .') +} + +// creates deep clone of array +function array_clone(arr){ + var clone = arr.slice(0) + for(var i = clone.length; i--;){ + if(Array.isArray(clone[i])){ + clone[i] = array_clone(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 + } +} + +// Delta transform point +function deltaTransformPoint(matrix, x, y) { + return { + x: x * matrix.a + y * matrix.c + 0 + , y: x * matrix.b + y * matrix.d + 0 + } +} + +// 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] } +} + +// Parse matrix if required +function parseMatrix(matrix) { + if (!(matrix instanceof SVG.Matrix)) + matrix = new SVG.Matrix(matrix) + + return matrix +} + +// 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] + } } - } // add textPath element as child node and return textPath - - - return this.put(textPath); - }), - - // Get the textPath children - textPath() { - return this.findOne('textPath'); + } } - - }, - Path: { - // creates a textPath from this path - text: wrapWithAttrCheck(function (text) { - // Convert text to instance if needed - if (!(text instanceof Text)) { - text = new Text().addTo(this.parent()).text(text); - } // Create textPath from text and path and return - - - return text.path(this); - }), - - targets() { - return baseFind('svg textPath').filter(node => { - return (node.attr('href') || '').includes(this.id()); - }); // Does not work in IE11. Use when IE support is dropped - // return baseFind('svg textPath[*|href*="' + this.id() + '"]') - } - } - }); - TextPath.prototype.MorphArray = PathArray; - register(TextPath, 'TextPath'); - - class Use extends Shape { - constructor(node, attrs = node) { - super(nodeOrNew('use', node), attrs); - } // Use element as a reference - - - use(element, file) { - // Set lined element - return this.attr('href', (file || '') + '#' + element, xlink); - } - } - registerMethods({ - Container: { - // Create a use element - use: wrapWithAttrCheck(function (element, file) { - return this.put(new Use()).use(element, file); - }) - } - }); - register(Use, 'Use'); - /* Optional Modules */ - const SVG$1 = makeInstance; - extend([Svg, Symbol, Image, Pattern, Marker], getMethodsFor('viewbox')); - extend([Line, Polyline, Polygon, Path], getMethodsFor('marker')); - extend(Text, getMethodsFor('Text')); - extend(Path, getMethodsFor('Path')); - extend(Defs, getMethodsFor('Defs')); - extend([Text, Tspan], getMethodsFor('Tspan')); - extend([Rect, Ellipse, Gradient, Runner], getMethodsFor('radius')); - extend(EventTarget, getMethodsFor('EventTarget')); - extend(Dom, getMethodsFor('Dom')); - extend(Element, getMethodsFor('Element')); - extend(Shape, getMethodsFor('Shape')); - extend([Container, Fragment], getMethodsFor('Container')); - extend(Gradient, getMethodsFor('Gradient')); - extend(Runner, getMethodsFor('Runner')); - List.extend(getMethodNames()); - registerMorphableType([SVGNumber, Color, Box, Matrix, SVGArray, PointArray, PathArray, Point]); - makeMorphable(); + return s + ' ' +} - var svgMembers = { - __proto__: null, - Morphable: Morphable, - registerMorphableType: registerMorphableType, - makeMorphable: makeMorphable, - TransformBag: TransformBag, - ObjectBag: ObjectBag, - NonMorphable: NonMorphable, - defaults: defaults, - utils: utils, - namespaces: namespaces, - regex: regex, - SVG: SVG$1, - parser: parser, - find: baseFind, - getWindow: getWindow, - registerWindow: registerWindow, - restoreWindow: restoreWindow, - saveWindow: saveWindow, - withWindow: withWindow, - Animator: Animator, - Controller: Controller, - Ease: Ease, - PID: PID, - Spring: Spring, - easing: easing, - Queue: Queue, - Runner: Runner, - Timeline: Timeline, - Array: SVGArray, - Box: Box, - Color: Color, - EventTarget: EventTarget, - Matrix: Matrix, - Number: SVGNumber, - PathArray: PathArray, - Point: Point, - PointArray: PointArray, - List: List, - Circle: Circle, - ClipPath: ClipPath, - Container: Container, - Defs: Defs, - Dom: Dom, - Element: Element, - Ellipse: Ellipse, - ForeignObject: ForeignObject, - Fragment: Fragment, - Gradient: Gradient, - G: G, - A: A, - Image: Image, - Line: Line, - Marker: Marker, - Mask: Mask, - Path: Path, - Pattern: Pattern, - Polygon: Polygon, - Polyline: Polyline, - Rect: Rect, - Shape: Shape, - Stop: Stop, - Style: Style, - Svg: Svg, - Symbol: Symbol, - Text: Text, - TextPath: TextPath, - Tspan: Tspan, - Use: Use, - windowEvents: windowEvents, - getEvents: getEvents, - getEventTarget: getEventTarget, - clearEvents: clearEvents, - on: on, - off: off, - dispatch: dispatch, - root: root, - create: create, - makeInstance: makeInstance, - nodeOrNew: nodeOrNew, - adopt: adopt, - mockAdopt: mockAdopt, - register: register, - getClass: getClass, - eid: eid, - assignNewId: assignNewId, - extend: extend, - wrapWithAttrCheck: wrapWithAttrCheck - }; +// Deep new id assignment +function assignNewId(node) { + // do the same for SVG child nodes as well + for (var i = node.childNodes.length - 1; i >= 0; i--) + if (node.childNodes[i] instanceof window.SVGElement) + assignNewId(node.childNodes[i]) - function SVG(element, isHTML) { - return makeInstance(element, isHTML); + return SVG.adopt(node).id(SVG.eid(node.nodeName)) +} + +// Add more bounding box properties +function fullBox(b) { + if (b.x == null) { + b.x = 0 + b.y = 0 + b.width = 0 + b.height = 0 } - Object.assign(SVG, svgMembers); - return SVG; + 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 -})(); -//# sourceMappingURL=svg.js.map \ No newline at end of file + return b +} + +// Get id from reference string +function idFromReference(url) { + var m = (url || '').toString().match(SVG.regex.reference) + + if (m) return m[1] +} + +// If values like 1e-88 are passed, this is not a valid 32 bit float, +// but in those cases, we are so close to 0 that 0 works well! +function float32String(v) { + return Math.abs(v) > 1e-37 ? v : 0 +} + +// Create matrix array for looping +var abcdef = 'abcdef'.split('') + +// Add CustomEvent to IE9 and IE10 +if (typeof window.CustomEvent !== 'function') { + // Code from: https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent + var CustomEventPoly = function(event, options) { + options = options || { bubbles: false, cancelable: false, detail: undefined } + var e = document.createEvent('CustomEvent') + e.initCustomEvent(event, options.bubbles, options.cancelable, options.detail) + return e + } + + CustomEventPoly.prototype = window.Event.prototype + + SVG.CustomEvent = CustomEventPoly +} else { + SVG.CustomEvent = window.CustomEvent +} + +// requestAnimationFrame / cancelAnimationFrame Polyfill with fallback based on Paul Irish +(function(w) { + var lastTime = 0 + var vendors = ['moz', 'webkit'] + + for(var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) { + w.requestAnimationFrame = w[vendors[x] + 'RequestAnimationFrame'] + w.cancelAnimationFrame = w[vendors[x] + 'CancelAnimationFrame'] || + w[vendors[x] + 'CancelRequestAnimationFrame'] + } + + w.requestAnimationFrame = w.requestAnimationFrame || + function(callback) { + var currTime = new Date().getTime() + var timeToCall = Math.max(0, 16 - (currTime - lastTime)) + + var id = w.setTimeout(function() { + callback(currTime + timeToCall) + }, timeToCall) + + lastTime = currTime + timeToCall + return id + } + + w.cancelAnimationFrame = w.cancelAnimationFrame || w.clearTimeout; + +}(window)) + +return SVG + +})); \ No newline at end of file diff --git a/hal-core/resources/web/js/lib/svg.min.js b/hal-core/resources/web/js/lib/svg.min.js index b96dd147..d4b36bd4 100644 --- a/hal-core/resources/web/js/lib/svg.min.js +++ b/hal-core/resources/web/js/lib/svg.min.js @@ -1,13 +1,3 @@ -/*! @svgdotjs/svg.js v3.1.2 MIT*/; -/*! -* @svgdotjs/svg.js - A lightweight library for manipulating and animating SVG. -* @version 3.1.2 -* https://svgjs.dev/ -* -* @copyright Wout Fierens -* @license MIT -* -* BUILT: Wed Jan 26 2022 23:19:07 GMT+0100 (Mitteleuropäische Normalzeit) -*/ -var SVG=function(){"use strict";const t={},e=[];function n(e,i){if(Array.isArray(e))for(const t of e)n(t,i);else if("object"!=typeof e)r(Object.getOwnPropertyNames(i)),t[e]=Object.assign(t[e]||{},i);else for(const t in e)n(t,e[t])}function i(e){return t[e]||{}}function r(t){e.push(...t)}function s(t,e){let n;const i=t.length,r=[];for(n=0;n=0;e--)P(t.children[e]);return t.id?(t.id=L(t.nodeName),t):t}function F(t,e){let n,i;for(i=(t=Array.isArray(t)?t:[t]).length-1;i>=0;i--)for(n in e)t[i].prototype[n]=e[n]}function q(t){return function(...e){const n=e[e.length-1];return!n||n.constructor!==Object||n instanceof Array?t.apply(this,e):t.apply(this,e.slice(0,-1)).attr(n)}}n("Dom",{siblings:function(){return this.parent().children()},position:function(){return this.parent().index(this)},next:function(){return this.siblings()[this.position()+1]},prev:function(){return this.siblings()[this.position()-1]},forward:function(){const t=this.position();return this.parent().add(this.remove(),t+1),this},backward:function(){const t=this.position();return this.parent().add(this.remove(),t?t-1:0),this},front:function(){return this.parent().add(this.remove()),this},back:function(){return this.parent().add(this.remove(),0),this},before:function(t){(t=S(t)).remove();const e=this.position();return this.parent().add(t,e),this},after:function(t){(t=S(t)).remove();const e=this.position();return this.parent().add(t,e+1),this},insertBefore:function(t){return(t=S(t)).before(this),this},insertAfter:function(t){return(t=S(t)).after(this),this}});const X=/^([+-]?(\d+(\.\d*)?|\.\d+)(e[+-]?\d+)?)([a-z%]*)$/i,Y=/^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i,B=/rgb\((\d+),(\d+),(\d+)\)/,H=/(#[a-z_][a-z0-9\-_]*)/i,V=/\)\s*,?\s*/,$=/\s/g,U=/^#[a-f0-9]{3}$|^#[a-f0-9]{6}$/i,W=/^rgb\(/,Q=/^(\s+)?$/,J=/^[+-]?(\d+(\.\d*)?|\.\d+)(e[+-]?\d+)?$/i,Z=/\.(jpg|jpeg|png|gif|svg)(\?[^=]+.*)?/i,K=/[\s,]+/,tt=/[MLHVCSQTAZ]/i;var et={__proto__:null,numberAndUnit:X,hex:Y,rgb:B,reference:H,transforms:V,whitespace:$,isHex:U,isRgb:W,isBlank:Q,isNumber:J,isImage:Z,delimiter:K,isPathLetter:tt};function nt(t){const e=Math.round(t),n=Math.max(0,Math.min(255,e)).toString(16);return 1===n.length?"0"+n:n}function it(t,e){for(let n=e.length;n--;)if(null==t[e[n]])return!1;return!0}function rt(t,e,n){return n<0&&(n+=1),n>1&&(n-=1),n<1/6?t+6*(e-t)*n:n<.5?e:n<2/3?t+(e-t)*(2/3-n)*6:t}n("Dom",{classes:function(){const t=this.attr("class");return null==t?[]:t.trim().split(K)},hasClass:function(t){return-1!==this.classes().indexOf(t)},addClass:function(t){if(!this.hasClass(t)){const e=this.classes();e.push(t),this.attr("class",e.join(" "))}return this},removeClass:function(t){return this.hasClass(t)&&this.attr("class",this.classes().filter((function(e){return e!==t})).join(" ")),this},toggleClass:function(t){return this.hasClass(t)?this.removeClass(t):this.addClass(t)}}),n("Dom",{css:function(t,e){const n={};if(0===arguments.length)return this.node.style.cssText.split(/\s*;\s*/).filter((function(t){return!!t.length})).forEach((function(t){const e=t.split(/\s*:\s*/);n[e[0]]=e[1]})),n;if(arguments.length<2){if(Array.isArray(t)){for(const e of t){const t=u(e);n[e]=this.node.style[t]}return n}if("string"==typeof t)return this.node.style[u(t)];if("object"==typeof t)for(const e in t)this.node.style[u(e)]=null==t[e]||Q.test(t[e])?"":t[e]}return 2===arguments.length&&(this.node.style[u(t)]=null==e||Q.test(e)?"":e),this},show:function(){return this.css("display","")},hide:function(){return this.css("display","none")},visible:function(){return"none"!==this.css("display")}}),n("Dom",{data:function(t,e,n){if(null==t)return this.data(s(o(this.node.attributes,(t=>0===t.nodeName.indexOf("data-"))),(t=>t.nodeName.slice(5))));if(t instanceof Array){const e={};for(const n of t)e[n]=this.data(n);return e}if("object"==typeof t)for(e in t)this.data(e,t[e]);else if(arguments.length<2)try{return JSON.parse(this.attr("data-"+t))}catch(e){return this.attr("data-"+t)}else this.attr("data-"+t,null===e?null:!0===n||"string"==typeof e||"number"==typeof e?e:JSON.stringify(e));return this}}),n("Dom",{remember:function(t,e){if("object"==typeof arguments[0])for(const e in t)this.remember(e,t[e]);else{if(1===arguments.length)return this.memory()[t];this.memory()[t]=e}return this},forget:function(){if(0===arguments.length)this._memory={};else for(let t=arguments.length-1;t>=0;t--)delete this.memory()[arguments[t]];return this},memory:function(){return this._memory=this._memory||{}}});class st{constructor(...t){this.init(...t)}static isColor(t){return t&&(t instanceof st||this.isRgb(t)||this.test(t))}static isRgb(t){return t&&"number"==typeof t.r&&"number"==typeof t.g&&"number"==typeof t.b}static random(t="vibrant",e,n){const{random:i,round:r,sin:s,PI:o}=Math;if("vibrant"===t){const t=24*i()+57,e=38*i()+45,n=360*i();return new st(t,e,n,"lch")}if("sine"===t){const t=r(80*s(2*o*(e=null==e?i():e)/.5+.01)+150),n=r(50*s(2*o*e/.5+4.6)+200),h=r(100*s(2*o*e/.5+2.3)+150);return new st(t,n,h)}if("pastel"===t){const t=8*i()+86,e=17*i()+9,n=360*i();return new st(t,e,n,"lch")}if("dark"===t){const t=10+10*i(),e=50*i()+86,n=360*i();return new st(t,e,n,"lch")}if("rgb"===t){const t=255*i(),e=255*i(),n=255*i();return new st(t,e,n)}if("lab"===t){const t=100*i(),e=256*i()-128,n=256*i()-128;return new st(t,e,n,"lab")}if("grey"===t){const t=255*i();return new st(t,t,t)}throw new Error("Unsupported random color mode")}static test(t){return"string"==typeof t&&(U.test(t)||W.test(t))}cmyk(){const{_a:t,_b:e,_c:n}=this.rgb(),[i,r,s]=[t,e,n].map((t=>t/255)),o=Math.min(1-i,1-r,1-s);if(1===o)return new st(0,0,0,1,"cmyk");return new st((1-i-o)/(1-o),(1-r-o)/(1-o),(1-s-o)/(1-o),o,"cmyk")}hsl(){const{_a:t,_b:e,_c:n}=this.rgb(),[i,r,s]=[t,e,n].map((t=>t/255)),o=Math.max(i,r,s),h=Math.min(i,r,s),u=(o+h)/2,a=o===h,l=o-h;return new st(360*(a?0:o===i?((r-s)/l+(r.5?l/(2-o-h):l/(o+h)),100*u,"hsl")}init(t=0,e=0,n=0,i=0,r="rgb"){if(t=t||0,this.space)for(const t in this.space)delete this[this.space[t]];if("number"==typeof t)r="string"==typeof i?i:r,i="string"==typeof i?0:i,Object.assign(this,{_a:t,_b:e,_c:n,_d:i,space:r});else if(t instanceof Array)this.space=e||("string"==typeof t[3]?t[3]:t[4])||"rgb",Object.assign(this,{_a:t[0],_b:t[1],_c:t[2],_d:t[3]||0});else if(t instanceof Object){const n=function(t,e){const n=it(t,"rgb")?{_a:t.r,_b:t.g,_c:t.b,_d:0,space:"rgb"}:it(t,"xyz")?{_a:t.x,_b:t.y,_c:t.z,_d:0,space:"xyz"}:it(t,"hsl")?{_a:t.h,_b:t.s,_c:t.l,_d:0,space:"hsl"}:it(t,"lab")?{_a:t.l,_b:t.a,_c:t.b,_d:0,space:"lab"}:it(t,"lch")?{_a:t.l,_b:t.c,_c:t.h,_d:0,space:"lch"}:it(t,"cmyk")?{_a:t.c,_b:t.m,_c:t.y,_d:t.k,space:"cmyk"}:{_a:0,_b:0,_c:0,space:"rgb"};return n.space=e||n.space,n}(t,e);Object.assign(this,n)}else if("string"==typeof t)if(W.test(t)){const e=t.replace($,""),[n,i,r]=B.exec(e).slice(1,4).map((t=>parseInt(t)));Object.assign(this,{_a:n,_b:i,_c:r,_d:0,space:"rgb"})}else{if(!U.test(t))throw Error("Unsupported string format, can't construct Color");{const e=t=>parseInt(t,16),[,n,i,r]=Y.exec(function(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}(t)).map(e);Object.assign(this,{_a:n,_b:i,_c:r,_d:0,space:"rgb"})}}const{_a:s,_b:o,_c:h,_d:u}=this,a="rgb"===this.space?{r:s,g:o,b:h}:"xyz"===this.space?{x:s,y:o,z:h}:"hsl"===this.space?{h:s,s:o,l:h}:"lab"===this.space?{l:s,a:o,b:h}:"lch"===this.space?{l:s,c:o,h:h}:"cmyk"===this.space?{c:s,m:o,y:h,k:u}:{};Object.assign(this,a)}lab(){const{x:t,y:e,z:n}=this.xyz();return new st(116*e-16,500*(t-e),200*(e-n),"lab")}lch(){const{l:t,a:e,b:n}=this.lab(),i=Math.sqrt(e**2+n**2);let r=180*Math.atan2(n,e)/Math.PI;r<0&&(r*=-1,r=360-r);return new st(t,i,r,"lch")}rgb(){if("rgb"===this.space)return this;if("lab"===(t=this.space)||"xyz"===t||"lch"===t){let{x:t,y:e,z:n}=this;if("lab"===this.space||"lch"===this.space){let{l:i,a:r,b:s}=this;if("lch"===this.space){const{c:t,h:e}=this,n=Math.PI/180;r=t*Math.cos(n*e),s=t*Math.sin(n*e)}const o=(i+16)/116,h=r/500+o,u=o-s/200,a=16/116,l=.008856,c=7.787;t=.95047*(h**3>l?h**3:(h-a)/c),e=1*(o**3>l?o**3:(o-a)/c),n=1.08883*(u**3>l?u**3:(u-a)/c)}const i=3.2406*t+-1.5372*e+-.4986*n,r=-.9689*t+1.8758*e+.0415*n,s=.0557*t+-.204*e+1.057*n,o=Math.pow,h=.0031308,u=i>h?1.055*o(i,1/2.4)-.055:12.92*i,a=r>h?1.055*o(r,1/2.4)-.055:12.92*r,l=s>h?1.055*o(s,1/2.4)-.055:12.92*s;return new st(255*u,255*a,255*l)}if("hsl"===this.space){let{h:t,s:e,l:n}=this;if(t/=360,e/=100,n/=100,0===e){n*=255;return new st(n,n,n)}const i=n<.5?n*(1+e):n+e-n*e,r=2*n-i,s=255*rt(r,i,t+1/3),o=255*rt(r,i,t),h=255*rt(r,i,t-1/3);return new st(s,o,h)}if("cmyk"===this.space){const{c:t,m:e,y:n,k:i}=this,r=255*(1-Math.min(1,t*(1-i)+i)),s=255*(1-Math.min(1,e*(1-i)+i)),o=255*(1-Math.min(1,n*(1-i)+i));return new st(r,s,o)}return this;var t}toArray(){const{_a:t,_b:e,_c:n,_d:i,space:r}=this;return[t,e,n,i,r]}toHex(){const[t,e,n]=this._clamped().map(nt);return`#${t}${e}${n}`}toRgb(){const[t,e,n]=this._clamped();return`rgb(${t},${e},${n})`}toString(){return this.toHex()}xyz(){const{_a:t,_b:e,_c:n}=this.rgb(),[i,r,s]=[t,e,n].map((t=>t/255)),o=i>.04045?Math.pow((i+.055)/1.055,2.4):i/12.92,h=r>.04045?Math.pow((r+.055)/1.055,2.4):r/12.92,u=s>.04045?Math.pow((s+.055)/1.055,2.4):s/12.92,a=(.4124*o+.3576*h+.1805*u)/.95047,l=(.2126*o+.7152*h+.0722*u)/1,c=(.0193*o+.1192*h+.9505*u)/1.08883,f=a>.008856?Math.pow(a,1/3):7.787*a+16/116,d=l>.008856?Math.pow(l,1/3):7.787*l+16/116,m=c>.008856?Math.pow(c,1/3):7.787*c+16/116;return new st(f,d,m,"xyz")}_clamped(){const{_a:t,_b:e,_c:n}=this.rgb(),{max:i,min:r,round:s}=Math;return[t,e,n].map((t=>i(0,r(s(t),255))))}}class ot{constructor(...t){this.init(...t)}clone(){return new ot(this)}init(t,e){const n=0,i=0,r=Array.isArray(t)?{x:t[0],y:t[1]}:"object"==typeof t?{x:t.x,y:t.y}:{x:t,y:e};return this.x=null==r.x?n:r.x,this.y=null==r.y?i:r.y,this}toArray(){return[this.x,this.y]}transform(t){return this.clone().transformO(t)}transformO(t){ut.isMatrixLike(t)||(t=new ut(t));const{x:e,y:n}=this;return this.x=t.a*e+t.c*n+t.e,this.y=t.b*e+t.d*n+t.f,this}}function ht(t,e,n){return Math.abs(e-t)<(n||1e-6)}class ut{constructor(...t){this.init(...t)}static formatTransforms(t){const 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,h=t.scale&&t.scale.length?t.scale[1]*i:isFinite(t.scale)?t.scale*i:isFinite(t.scaleY)?t.scaleY*i:i,u=t.shear||0,a=t.rotate||t.theta||0,l=new ot(t.origin||t.around||t.ox||t.originX,t.oy||t.originY),c=l.x,f=l.y,d=new ot(t.position||t.px||t.positionX||NaN,t.py||t.positionY||NaN),m=d.x,p=d.y,y=new ot(t.translate||t.tx||t.translateX,t.ty||t.translateY),w=y.x,_=y.y,g=new ot(t.relative||t.rx||t.relativeX,t.ry||t.relativeY);return{scaleX:o,scaleY:h,skewX:r,skewY:s,shear:u,theta:a,rx:g.x,ry:g.y,tx:w,ty:_,ox:c,oy:f,px:m,py:p}}static fromArray(t){return{a:t[0],b:t[1],c:t[2],d:t[3],e:t[4],f:t[5]}}static isMatrixLike(t){return null!=t.a||null!=t.b||null!=t.c||null!=t.d||null!=t.e||null!=t.f}static matrixMultiply(t,e,n){const i=t.a*e.a+t.c*e.b,r=t.b*e.a+t.d*e.b,s=t.a*e.c+t.c*e.d,o=t.b*e.c+t.d*e.d,h=t.e+t.a*e.e+t.c*e.f,u=t.f+t.b*e.e+t.d*e.f;return n.a=i,n.b=r,n.c=s,n.d=o,n.e=h,n.f=u,n}around(t,e,n){return this.clone().aroundO(t,e,n)}aroundO(t,e,n){const i=t||0,r=e||0;return this.translateO(-i,-r).lmultiplyO(n).translateO(i,r)}clone(){return new ut(this)}decompose(t=0,e=0){const n=this.a,i=this.b,r=this.c,s=this.d,o=this.e,h=this.f,u=n*s-i*r,a=u>0?1:-1,l=a*Math.sqrt(n*n+i*i),c=Math.atan2(a*i,a*n),f=180/Math.PI*c,d=Math.cos(c),m=Math.sin(c),p=(n*r+i*s)/u,y=r*l/(p*n-i)||s*l/(p*i+n);return{scaleX:l,scaleY:y,shear:p,rotate:f,translateX:o-t+t*d*l+e*(p*d*l-m*y),translateY:h-e+t*m*l+e*(p*m*l+d*y),originX:t,originY:e,a:this.a,b:this.b,c:this.c,d:this.d,e:this.e,f:this.f}}equals(t){if(t===this)return!0;const e=new ut(t);return ht(this.a,e.a)&&ht(this.b,e.b)&&ht(this.c,e.c)&&ht(this.d,e.d)&&ht(this.e,e.e)&&ht(this.f,e.f)}flip(t,e){return this.clone().flipO(t,e)}flipO(t,e){return"x"===t?this.scaleO(-1,1,e,0):"y"===t?this.scaleO(1,-1,0,e):this.scaleO(-1,-1,t,e||t)}init(t){const e=ut.fromArray([1,0,0,1,0,0]);return t=t instanceof Element?t.matrixify():"string"==typeof t?ut.fromArray(t.split(K).map(parseFloat)):Array.isArray(t)?ut.fromArray(t):"object"==typeof t&&ut.isMatrixLike(t)?t:"object"==typeof t?(new ut).transform(t):6===arguments.length?ut.fromArray([].slice.call(arguments)):e,this.a=null!=t.a?t.a:e.a,this.b=null!=t.b?t.b:e.b,this.c=null!=t.c?t.c:e.c,this.d=null!=t.d?t.d:e.d,this.e=null!=t.e?t.e:e.e,this.f=null!=t.f?t.f:e.f,this}inverse(){return this.clone().inverseO()}inverseO(){const 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);const h=i/o,u=-e/o,a=-n/o,l=t/o,c=-(h*r+a*s),f=-(u*r+l*s);return this.a=h,this.b=u,this.c=a,this.d=l,this.e=c,this.f=f,this}lmultiply(t){return this.clone().lmultiplyO(t)}lmultiplyO(t){const e=t instanceof ut?t:new ut(t);return ut.matrixMultiply(e,this,this)}multiply(t){return this.clone().multiplyO(t)}multiplyO(t){const e=t instanceof ut?t:new ut(t);return ut.matrixMultiply(this,e,this)}rotate(t,e,n){return this.clone().rotateO(t,e,n)}rotateO(t,e=0,n=0){t=h(t);const i=Math.cos(t),r=Math.sin(t),{a:s,b:o,c:u,d:a,e:l,f:c}=this;return this.a=s*i-o*r,this.b=o*i+s*r,this.c=u*i-a*r,this.d=a*i+u*r,this.e=l*i-c*r+n*r-e*i+e,this.f=c*i+l*r-e*r-n*i+n,this}scale(t,e,n,i){return this.clone().scaleO(...arguments)}scaleO(t,e=t,n=0,i=0){3===arguments.length&&(i=n,n=e,e=t);const{a:r,b:s,c:o,d:h,e:u,f:a}=this;return this.a=r*t,this.b=s*e,this.c=o*t,this.d=h*e,this.e=u*t-n*t+n,this.f=a*e-i*e+i,this}shear(t,e,n){return this.clone().shearO(t,e,n)}shearO(t,e=0,n=0){const{a:i,b:r,c:s,d:o,e:h,f:u}=this;return this.a=i+r*t,this.c=s+o*t,this.e=h+u*t-n*t,this}skew(t,e,n,i){return this.clone().skewO(...arguments)}skewO(t,e=t,n=0,i=0){3===arguments.length&&(i=n,n=e,e=t),t=h(t),e=h(e);const r=Math.tan(t),s=Math.tan(e),{a:o,b:u,c:a,d:l,e:c,f:f}=this;return this.a=o+u*r,this.b=u+o*s,this.c=a+l*r,this.d=l+a*s,this.e=c+f*r-i*r,this.f=f+c*s-n*s,this}skewX(t,e,n){return this.skew(t,0,e,n)}skewY(t,e,n){return this.skew(0,t,e,n)}toArray(){return[this.a,this.b,this.c,this.d,this.e,this.f]}toString(){return"matrix("+this.a+","+this.b+","+this.c+","+this.d+","+this.e+","+this.f+")"}transform(t){if(ut.isMatrixLike(t)){return new ut(t).multiplyO(this)}const e=ut.formatTransforms(t),{x:n,y:i}=new ot(e.ox,e.oy).transform(this),r=(new ut).translateO(e.rx,e.ry).lmultiplyO(this).translateO(-n,-i).scaleO(e.scaleX,e.scaleY).skewO(e.skewX,e.skewY).shearO(e.shear).rotateO(e.theta).translateO(n,i);if(isFinite(e.px)||isFinite(e.py)){const t=new ot(n,i).transform(r),s=isFinite(e.px)?e.px-t.x:0,o=isFinite(e.py)?e.py-t.y:0;r.translateO(s,o)}return r.translateO(e.tx,e.ty),r}translate(t,e){return this.clone().translateO(t,e)}translateO(t,e){return this.e+=t||0,this.f+=e||0,this}valueOf(){return{a:this.a,b:this.b,c:this.c,d:this.d,e:this.e,f:this.f}}}function at(){if(!at.nodes){const t=S().size(2,0);t.node.style.cssText=["opacity: 0","position: absolute","left: -100%","top: -100%","overflow: hidden"].join(";"),t.attr("focusable","false"),t.attr("aria-hidden","true");const e=t.path().node;at.nodes={svg:t,path:e}}if(!at.nodes.svg.node.parentNode){const t=x.document.body||x.document.documentElement;at.nodes.svg.addTo(t)}return at.nodes}function lt(t){return!(t.width||t.height||t.x||t.y)}I(ut,"Matrix");class ct{constructor(...t){this.init(...t)}addOffset(){return this.x+=x.window.pageXOffset,this.y+=x.window.pageYOffset,new ct(this)}init(t){return t="string"==typeof t?t.split(K).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):[0,0,0,0],this.x=t[0]||0,this.y=t[1]||0,this.width=this.w=t[2]||0,this.height=this.h=t[3]||0,this.x2=this.x+this.w,this.y2=this.y+this.h,this.cx=this.x+this.w/2,this.cy=this.y+this.h/2,this}isNulled(){return lt(this)}merge(t){const e=Math.min(this.x,t.x),n=Math.min(this.y,t.y),i=Math.max(this.x+this.width,t.x+t.width)-e,r=Math.max(this.y+this.height,t.y+t.height)-n;return new ct(e,n,i,r)}toArray(){return[this.x,this.y,this.width,this.height]}toString(){return this.x+" "+this.y+" "+this.width+" "+this.height}transform(t){t instanceof ut||(t=new ut(t));let e=1/0,n=-1/0,i=1/0,r=-1/0;return[new ot(this.x,this.y),new ot(this.x2,this.y),new ot(this.x,this.y2),new ot(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 ct(e,i,n-e,r-i)}}function ft(t,e,n){let i;try{if(i=e(t.node),lt(i)&&((r=t.node)!==x.document&&!(x.document.documentElement.contains||function(t){for(;t.parentNode;)t=t.parentNode;return t===x.document}).call(x.document.documentElement,r)))throw new Error("Element not in the dom")}catch(e){i=n(t)}var r;return i}n({viewbox:{viewbox(t,e,n,i){return null==t?new ct(this.attr("viewBox")):this.attr("viewBox",new ct(t,e,n,i))},zoom(t,e){let{width:n,height:i}=this.attr(["width","height"]);if((n||i)&&"string"!=typeof n&&"string"!=typeof i||(n=this.node.clientWidth,i=this.node.clientHeight),!n||!i)throw new Error("Impossible to get absolute width and height. Please provide an absolute width and height attribute on the zooming element");const r=this.viewbox(),s=n/r.width,o=i/r.height,h=Math.min(s,o);if(null==t)return h;let u=h/t;u===1/0&&(u=Number.MAX_SAFE_INTEGER/100),e=e||new ot(n/2/s+r.x,i/2/o+r.y);const a=new ct(r).transform(new ut({scale:u,origin:e}));return this.viewbox(a)}}}),I(ct,"Box");class dt extends Array{constructor(t=[],...e){if(super(t,...e),"number"==typeof t)return this;this.length=0,this.push(...t)}}F([dt],{each(t,...e){return"function"==typeof t?this.map(((e,n,i)=>t.call(e,e,n,i))):this.map((n=>n[t](...e)))},toArray(){return Array.prototype.concat.apply([],this)}});const mt=["toArray","constructor","each"];function pt(t,e){return new dt(s((e||x.document).querySelectorAll(t),(function(t){return j(t)})))}dt.extend=function(t){t=t.reduce(((t,e)=>(mt.includes(e)||"_"===e[0]||(t[e]=function(...t){return this.each(e,...t)}),t)),{}),F([dt],t)};let yt=0;const wt={};function _t(t){let e=t.getEventHolder();return e===x.window&&(e=wt),e.events||(e.events={}),e.events}function gt(t){return t.getEventTarget()}function xt(t){let e=t.getEventHolder();e===x.window&&(e=wt),e.events&&(e.events={})}function bt(t,e,n,i,r){const s=n.bind(i||t),o=S(t),h=_t(o),u=gt(o);e=Array.isArray(e)?e:e.split(K),n._svgjsListenerId||(n._svgjsListenerId=++yt),e.forEach((function(t){const e=t.split(".")[0],i=t.split(".")[1]||"*";h[e]=h[e]||{},h[e][i]=h[e][i]||{},h[e][i][n._svgjsListenerId]=s,u.addEventListener(e,s,r||!1)}))}function vt(t,e,n,i){const r=S(t),s=_t(r),o=gt(r);("function"!=typeof n||(n=n._svgjsListenerId))&&(e=Array.isArray(e)?e:(e||"").split(K)).forEach((function(t){const e=t&&t.split(".")[0],h=t&&t.split(".")[1];let u,a;if(n)s[e]&&s[e][h||"*"]&&(o.removeEventListener(e,s[e][h||"*"][n],i||!1),delete s[e][h||"*"][n]);else if(e&&h){if(s[e]&&s[e][h]){for(a in s[e][h])vt(o,[e,h].join("."),a);delete s[e][h]}}else if(h)for(t in s)for(u in s[t])h===u&&vt(o,[t,h].join("."));else if(e){if(s[e]){for(u in s[e])vt(o,[e,u].join("."));delete s[e]}}else{for(t in s)vt(o,t);xt(r)}}))}function Mt(t,e,n,i){const r=gt(t);return e instanceof x.window.Event||(e=new x.window.CustomEvent(e,{detail:n,cancelable:!0,...i})),r.dispatchEvent(e),e}class At extends k{addEventListener(){}dispatch(t,e,n){return Mt(this,t,e,n)}dispatchEvent(t){const e=this.getEventHolder().events;if(!e)return!0;const n=e[t.type];for(const e in n)for(const i in n[e])n[e][i](t);return!t.defaultPrevented}fire(t,e,n){return this.dispatch(t,e,n),this}getEventHolder(){return this}getEventTarget(){return this}off(t,e,n){return vt(this,t,e,n),this}on(t,e,n,i){return bt(this,t,e,n,i),this}removeEventListener(){}}function Ot(){}I(At,"EventTarget");const kt={duration:400,ease:">",delay:0},Ct={"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","text-anchor":"start"};var Tt={__proto__:null,noop:Ot,timeline:kt,attrs:Ct};class Nt extends Array{constructor(...t){super(...t),this.init(...t)}clone(){return new this.constructor(this)}init(t){return"number"==typeof t||(this.length=0,this.push(...this.parse(t))),this}parse(t=[]){return t instanceof Array?t:t.trim().split(K).map(parseFloat)}toArray(){return Array.prototype.concat.apply([],this)}toSet(){return new Set(this)}toString(){return this.join(" ")}valueOf(){const t=[];return t.push(...this),t}}class St{constructor(...t){this.init(...t)}convert(t){return new St(this.value,t)}divide(t){return t=new St(t),new St(this/t,this.unit||t.unit)}init(t,e){return e=Array.isArray(t)?t[1]:e,t=Array.isArray(t)?t[0]:t,this.value=0,this.unit=e||"","number"==typeof t?this.value=isNaN(t)?0:isFinite(t)?t:t<0?-34e37:34e37:"string"==typeof t?(e=t.match(X))&&(this.value=parseFloat(e[1]),"%"===e[5]?this.value/=100:"s"===e[5]&&(this.value*=1e3),this.unit=e[5]):t instanceof St&&(this.value=t.valueOf(),this.unit=t.unit),this}minus(t){return t=new St(t),new St(this-t,this.unit||t.unit)}plus(t){return t=new St(t),new St(this+t,this.unit||t.unit)}times(t){return t=new St(t),new St(this*t,this.unit||t.unit)}toArray(){return[this.value,this.unit]}toJSON(){return this.toString()}toString(){return("%"===this.unit?~~(1e8*this.value)/1e6:"s"===this.unit?this.value/1e3:this.value)+this.unit}valueOf(){return this.value}}const Et=[];class Dom extends At{constructor(t,e){super(),this.node=t,this.type=t.nodeName,e&&t!==e&&this.attr(e)}add(t,e){return(t=S(t)).removeNamespace&&this.node instanceof x.window.SVGElement&&t.removeNamespace(),null==e?this.node.appendChild(t.node):t.node!==this.node.childNodes[e]&&this.node.insertBefore(t.node,this.node.childNodes[e]),this}addTo(t,e){return S(t).put(this,e)}children(){return new dt(s(this.node.children,(function(t){return j(t)})))}clear(){for(;this.node.hasChildNodes();)this.node.removeChild(this.node.lastChild);return this}clone(t=!0){return this.writeDataToDom(),new this.constructor(P(this.node.cloneNode(t)))}each(t,e){const n=this.children();let i,r;for(i=0,r=n.length;i=0}html(t,e){return this.xml(t,e,p)}id(t){return void 0!==t||this.node.id||(this.node.id=L(this.type)),this.attr("id",t)}index(t){return[].slice.call(this.node.childNodes).indexOf(t.node)}last(){return j(this.node.lastChild)}matches(t){const e=this.node,n=e.matches||e.matchesSelector||e.msMatchesSelector||e.mozMatchesSelector||e.webkitMatchesSelector||e.oMatchesSelector||null;return n&&n.call(e,t)}parent(t){let e=this;if(!e.node.parentNode)return null;if(e=j(e.node.parentNode),!t)return e;do{if("string"==typeof t?e.matches(t):e instanceof t)return e}while(e=j(e.node.parentNode));return e}put(t,e){return t=S(t),this.add(t,e),t}putIn(t,e){return S(t).add(this,e)}remove(){return this.parent()&&this.parent().removeElement(this),this}removeElement(t){return this.node.removeChild(t.node),this}replace(t){return t=S(t),this.node.parentNode&&this.node.parentNode.replaceChild(t.node,this.node),t}round(t=2,e=null){const n=10**t,i=this.attr(e);for(const t in i)"number"==typeof i[t]&&(i[t]=Math.round(i[t]*n)/n);return this.attr(i),this}svg(t,e){return this.xml(t,e,m)}toString(){return this.id()}words(t){return this.node.textContent=t,this}wrap(t){const e=this.parent();if(!e)return this.addTo(t);const n=e.index(this);return e.put(t,n).put(this)}writeDataToDom(){return this.each((function(){this.writeDataToDom()})),this}xml(t,e,n){if("boolean"==typeof t&&(n=e,e=t,t=null),null==t||"function"==typeof t){e=null==e||e,this.writeDataToDom();let n=this;if(null!=t){if(n=j(n.node.cloneNode(!0)),e){const e=t(n);if(n=e||n,!1===e)return""}n.each((function(){const e=t(this),n=e||this;!1===e?this.remove():e&&this!==n&&this.replace(n)}),!0)}return e?n.node.outerHTML:n.node.innerHTML}e=null!=e&&e;const i=N("wrapper",n),r=x.document.createDocumentFragment();i.innerHTML=t;for(let t=i.children.length;t--;)r.appendChild(i.firstElementChild);const s=this.parent();return e?this.replace(r)&&s:this.add(r)}}F(Dom,{attr:function(t,e,n){if(null==t){t={},e=this.node.attributes;for(const n of e)t[n.nodeName]=J.test(n.nodeValue)?parseFloat(n.nodeValue):n.nodeValue;return t}if(t instanceof Array)return t.reduce(((t,e)=>(t[e]=this.attr(e),t)),{});if("object"==typeof t&&t.constructor===Object)for(e in t)this.attr(e,t[e]);else if(null===e)this.node.removeAttribute(t);else{if(null==e)return null==(e=this.node.getAttribute(t))?Ct[t]:J.test(e)?parseFloat(e):e;"number"==typeof(e=Et.reduce(((e,n)=>n(t,e,this)),e))?e=new St(e):st.isColor(e)?e=new st(e):e.constructor===Array&&(e=new Nt(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()}return this},find:function(t){return pt(t,this.node)},findOne:function(t){return j(this.node.querySelector(t))}}),I(Dom,"Dom");class Element extends Dom{constructor(t,e){super(t,e),this.dom={},this.node.instance=this,t.hasAttribute("svgjs:data")&&this.setData(JSON.parse(t.getAttribute("svgjs:data"))||{})}center(t,e){return this.cx(t).cy(e)}cx(t){return null==t?this.x()+this.width()/2:this.x(t-this.width()/2)}cy(t){return null==t?this.y()+this.height()/2:this.y(t-this.height()/2)}defs(){const t=this.root();return t&&t.defs()}dmove(t,e){return this.dx(t).dy(e)}dx(t=0){return this.x(new St(t).plus(this.x()))}dy(t=0){return this.y(new St(t).plus(this.y()))}getEventHolder(){return this}height(t){return this.attr("height",t)}move(t,e){return this.x(t).y(e)}parents(t=this.root()){const e="string"==typeof t;e||(t=S(t));const n=new dt;let i=this;for(;(i=i.parent())&&i.node!==x.document&&"#document-fragment"!==i.nodeName&&(n.push(i),e||i.node!==t.node)&&(!e||!i.matches(t));)if(i.node===this.root().node)return null;return n}reference(t){if(!(t=this.attr(t)))return null;const e=(t+"").match(H);return e?S(e[1]):null}root(){const t=this.parent(z(T));return t&&t.root()}setData(t){return this.dom=t,this}size(t,e){const n=c(this,t,e);return this.width(new St(n.width)).height(new St(n.height))}width(t){return this.attr("width",t)}writeDataToDom(){return this.node.removeAttribute("svgjs:data"),Object.keys(this.dom).length&&this.node.setAttribute("svgjs:data",JSON.stringify(this.dom)),super.writeDataToDom()}x(t){return this.attr("x",t)}y(t){return this.attr("y",t)}}F(Element,{bbox:function(){const t=ft(this,(t=>t.getBBox()),(t=>{try{const e=t.clone().addTo(at().svg).show(),n=e.node.getBBox();return e.remove(),n}catch(e){throw new Error(`Getting bbox of element "${t.node.nodeName}" is not possible: ${e.toString()}`)}}));return new ct(t)},rbox:function(t){const e=ft(this,(t=>t.getBoundingClientRect()),(t=>{throw new Error(`Getting rbox of element "${t.node.nodeName}" is not possible`)})),n=new ct(e);return t?n.transform(t.screenCTM().inverseO()):n.addOffset()},inside:function(t,e){const n=this.bbox();return t>n.x&&e>n.y&&t=0;i--)null!=e[jt[t][i]]&&this.attr(jt.prefix(t,jt[t][i]),e[jt[t][i]]);return this},n(["Element","Runner"],e)})),n(["Element","Runner"],{matrix:function(t,e,n,i,r,s){return null==t?new ut(this):this.attr("transform",new ut(t,e,n,i,r,s))},rotate:function(t,e,n){return this.transform({rotate:t,ox:e,oy:n},!0)},skew:function(t,e,n,i){return 1===arguments.length||3===arguments.length?this.transform({skew:t,ox:e,oy:n},!0):this.transform({skew:[t,e],ox:n,oy:i},!0)},shear:function(t,e,n){return this.transform({shear:t,ox:e,oy:n},!0)},scale:function(t,e,n,i){return 1===arguments.length||3===arguments.length?this.transform({scale:t,ox:e,oy:n},!0):this.transform({scale:[t,e],ox:n,oy:i},!0)},translate:function(t,e){return this.transform({translate:[t,e]},!0)},relative:function(t,e){return this.transform({relative:[t,e]},!0)},flip:function(t="both",e="center"){return-1==="xybothtrue".indexOf(t)&&(e=t,t="both"),this.transform({flip:t,origin:e},!0)},opacity:function(t){return this.attr("opacity",t)}}),n("radius",{radius:function(t,e=t){return"radialGradient"===(this._element||this).type?this.attr("r",new St(t)):this.rx(t).ry(e)}}),n("Path",{length:function(){return this.node.getTotalLength()},pointAt:function(t){return new ot(this.node.getPointAtLength(t))}}),n(["Element","Runner"],{font:function(t,e){if("object"==typeof t){for(e in t)this.font(e,t[e]);return this}return"leading"===t?this.leading(e):"anchor"===t?this.attr("text-anchor",e):"size"===t||"family"===t||"weight"===t||"stretch"===t||"variant"===t||"style"===t?this.attr("font-"+t,e):this.attr(t,e)}});n("Element",["click","dblclick","mousedown","mouseup","mouseover","mouseout","mousemove","mouseenter","mouseleave","touchstart","touchmove","touchleave","touchend","touchcancel"].reduce((function(t,e){return t[e]=function(t){return null===t?this.off(e):this.on(e,t),this},t}),{})),n("Element",{untransform:function(){return this.attr("transform",null)},matrixify:function(){const t=(this.attr("transform")||"").split(V).slice(0,-1).map((function(t){const e=t.trim().split("(");return[e[0],e[1].split(K).map((function(t){return parseFloat(t)}))]})).reverse().reduce((function(t,e){return"matrix"===e[0]?t.lmultiply(ut.fromArray(e[1])):t[e[0]].apply(t,e[1])}),new ut);return t},toParent:function(t,e){if(this===t)return this;const n=this.screenCTM(),i=t.screenCTM().inverse();return this.addTo(t,e).untransform().transform(i.multiply(n)),this},toRoot:function(t){return this.toParent(this.root(),t)},transform:function(t,e){if(null==t||"string"==typeof t){const e=new ut(this).decompose();return null==t?e:e[t]}ut.isMatrixLike(t)||(t={...t,origin:f(t,this)});const n=new ut(!0===e?this:e||!1).transform(t);return this.attr("transform",n)}});class Container extends Element{flatten(t=this,e){return this.each((function(){if(this instanceof Container)return this.flatten().ungroup()})),this}ungroup(t=this.parent(),e=t.index(this)){return e=-1===e?t.children().length:e,this.each((function(n,i){return i[i.length-n-1].toParent(t,e)})),this.remove()}}I(Container,"Container");class Defs extends Container{constructor(t,e=t){super(E("defs",t),e)}flatten(){return this}ungroup(){return this}}I(Defs,"Defs");class Shape extends Element{}function Dt(t){return this.attr("rx",t)}function It(t){return this.attr("ry",t)}function zt(t){return null==t?this.cx()-this.rx():this.cx(t+this.rx())}function Rt(t){return null==t?this.cy()-this.ry():this.cy(t+this.ry())}function Lt(t){return this.attr("cx",t)}function Pt(t){return this.attr("cy",t)}function Ft(t){return null==t?2*this.rx():this.rx(new St(t).divide(2))}function qt(t){return null==t?2*this.ry():this.ry(new St(t).divide(2))}I(Shape,"Shape");var Xt={__proto__:null,rx:Dt,ry:It,x:zt,y:Rt,cx:Lt,cy:Pt,width:Ft,height:qt};class Ellipse extends Shape{constructor(t,e=t){super(E("ellipse",t),e)}size(t,e){const n=c(this,t,e);return this.rx(new St(n.width).divide(2)).ry(new St(n.height).divide(2))}}F(Ellipse,Xt),n("Container",{ellipse:q((function(t=0,e=t){return this.put(new Ellipse).size(t,e).move(0,0)}))}),I(Ellipse,"Ellipse");class Yt extends Dom{constructor(t=x.document.createDocumentFragment()){super(t)}xml(t,e,n){if("boolean"==typeof t&&(n=e,e=t,t=null),null==t||"function"==typeof t){const t=new Dom(N("wrapper",n));return t.add(this.node.cloneNode(!0)),t.xml(!1,n)}return super.xml(t,!1,n)}}function Bt(t,e){return"radialGradient"===(this._element||this).type?this.attr({fx:new St(t),fy:new St(e)}):this.attr({x1:new St(t),y1:new St(e)})}function Ht(t,e){return"radialGradient"===(this._element||this).type?this.attr({cx:new St(t),cy:new St(e)}):this.attr({x2:new St(t),y2:new St(e)})}I(Yt,"Fragment");var Gt,Vt={__proto__:null,from:Bt,to:Ht};class Gradient extends Container{constructor(t,e){super(E(t+"Gradient","string"==typeof t?null:t),e)}attr(t,e,n){return"transform"===t&&(t="gradientTransform"),super.attr(t,e,n)}bbox(){return new ct}targets(){return pt('svg [fill*="'+this.id()+'"]')}toString(){return this.url()}update(t){return this.clear(),"function"==typeof t&&t.call(this,this),this}url(){return'url("#'+this.id()+'")'}}F(Gradient,Vt),n({Container:{gradient(...t){return this.defs().gradient(...t)}},Defs:{gradient:q((function(t,e){return this.put(new Gradient(t)).update(e)}))}}),I(Gradient,"Gradient");class Pattern extends Container{constructor(t,e=t){super(E("pattern",t),e)}attr(t,e,n){return"transform"===t&&(t="patternTransform"),super.attr(t,e,n)}bbox(){return new ct}targets(){return pt('svg [fill*="'+this.id()+'"]')}toString(){return this.url()}update(t){return this.clear(),"function"==typeof t&&t.call(this,this),this}url(){return'url("#'+this.id()+'")'}}n({Container:{pattern(...t){return this.defs().pattern(...t)}},Defs:{pattern:q((function(t,e,n){return this.put(new Pattern).update(n).attr({x:0,y:0,width:t,height:e,patternUnits:"userSpaceOnUse"})}))}}),I(Pattern,"Pattern");class Image extends Shape{constructor(t,e=t){super(E("image",t),e)}load(t,e){if(!t)return this;const n=new x.window.Image;return bt(n,"load",(function(t){const i=this.parent(Pattern);0===this.width()&&0===this.height()&&this.size(n.width,n.height),i instanceof Pattern&&0===i.width()&&0===i.height()&&i.size(this.width(),this.height()),"function"==typeof e&&e.call(this,t)}),this),bt(n,"load error",(function(){vt(n)})),this.attr("href",n.src=t,w)}}Gt=function(t,e,n){return"fill"!==t&&"stroke"!==t||Z.test(e)&&(e=n.root().defs().image(e)),e instanceof Image&&(e=n.root().defs().pattern(0,0,(t=>{t.add(e)}))),e},Et.push(Gt),n({Container:{image:q((function(t,e){return this.put(new Image).size(0,0).load(t,e)}))}}),I(Image,"Image");class $t extends Nt{bbox(){let t=-1/0,e=-1/0,n=1/0,i=1/0;return this.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)})),new ct(n,i,t-n,e-i)}move(t,e){const n=this.bbox();if(t-=n.x,e-=n.y,!isNaN(t)&&!isNaN(e))for(let n=this.length-1;n>=0;n--)this[n]=[this[n][0]+t,this[n][1]+e];return this}parse(t=[0,0]){const e=[];(t=t instanceof Array?Array.prototype.concat.apply([],t):t.trim().split(K).map(parseFloat)).length%2!=0&&t.pop();for(let n=0,i=t.length;n=0;n--)i.width&&(this[n][0]=(this[n][0]-i.x)*t/i.width+i.x),i.height&&(this[n][1]=(this[n][1]-i.y)*e/i.height+i.y);return this}toLine(){return{x1:this[0][0],y1:this[0][1],x2:this[1][0],y2:this[1][1]}}toString(){const t=[];for(let e=0,n=this.length;e":function(t){return-Math.cos(t*Math.PI)/2+.5},">":function(t){return Math.sin(t*Math.PI/2)},"<":function(t){return 1-Math.cos(t*Math.PI/2)},bezier:function(t,e,n,i){return function(r){return r<0?t>0?e/t*r:n>0?i/n*r:0:r>1?n<1?(1-i)/(1-n)*r+(i-n)/(1-n):t<1?(1-e)/(1-t)*r+(e-t)/(1-t):1:3*r*(1-r)**2*e+3*r**2*(1-r)*i+r**3}},steps:function(t,e="end"){e=e.split("-").reverse()[0];let n=t;return"none"===e?--n:"both"===e&&++n,(i,r=!1)=>{let s=Math.floor(i*t);const o=i*s%1==0;return"start"!==e&&"both"!==e||++s,r&&o&&--s,i>=0&&s<0&&(s=0),i<=1&&s>n&&(s=n),s/n}}};class Jt{done(){return!1}}class Zt extends Jt{constructor(t=kt.ease){super(),this.ease=Qt[t]||t}step(t,e,n){return"number"!=typeof t?n<1?t:e:t+(e-t)*this.ease(n)}}class Kt extends Jt{constructor(t){super(),this.stepper=t}done(t){return t.done}step(t,e,n,i){return this.stepper(t,e,n,i)}}function te(){const t=(this._duration||500)/1e3,e=this._overshoot||0,n=Math.PI,i=Math.log(e/100+1e-10),r=-i/Math.sqrt(n*n+i*i),s=3.9/(r*t);this.d=2*r*s,this.k=s*s}class ee extends Kt{constructor(t=500,e=0){super(),this.duration(t).overshoot(e)}step(t,e,n,i){if("string"==typeof t)return t;if(i.done=n===1/0,n===1/0)return e;if(0===n)return t;n>100&&(n=16),n/=1e3;const r=i.velocity||0,s=-this.d*r-this.k*(t-e),o=t+r*n+s*n*n/2;return i.velocity=r+s*n,i.done=Math.abs(e-o)+Math.abs(r)<.002,i.done?e:o}}F(ee,{duration:Wt("_duration",te),overshoot:Wt("_overshoot",te)});class ne extends Kt{constructor(t=.1,e=.01,n=0,i=1e3){super(),this.p(t).i(e).d(n).windup(i)}step(t,e,n,i){if("string"==typeof t)return t;if(i.done=n===1/0,n===1/0)return e;if(0===n)return t;const r=e-t;let s=(i.integral||0)+r*n;const o=(r-(i.error||0))/n,h=this._windup;return!1!==h&&(s=Math.max(-h,Math.min(s,h))),i.error=r,i.integral=s,i.done=Math.abs(r)<.001,i.done?e:t+(this.P*r+this.I*s+this.D*o)}}F(ne,{windup:Wt("_windup"),p:Wt("P"),i:Wt("I"),d:Wt("D")});const ie={M:2,L:2,H:1,V:1,C:6,S:4,Q:4,T:2,A:7,Z:0},re={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]]}},se="mlhvqtcsaz".split("");for(let t=0,e=se.length;t=0;i--)n=this[i][0],"M"===n||"L"===n||"T"===n?(this[i][1]+=t,this[i][2]+=e):"H"===n?this[i][1]+=t:"V"===n?this[i][1]+=e:"C"===n||"S"===n||"Q"===n?(this[i][1]+=t,this[i][2]+=e,this[i][3]+=t,this[i][4]+=e,"C"===n&&(this[i][5]+=t,this[i][6]+=e)):"A"===n&&(this[i][6]+=t,this[i][7]+=e);return this}parse(t="M0 0"){return Array.isArray(t)&&(t=Array.prototype.concat.apply([],t).toString()),function(t,e=!0){let n=0,i="";const r={segment:[],inNumber:!1,number:"",lastToken:"",inSegment:!1,segments:[],pointSeen:!1,hasExponent:!1,absolute:e,p0:new ot,p:new ot};for(;r.lastToken=i,i=t.charAt(n++);)if(r.inSegment||!he(r,i))if("."!==i)if(isNaN(parseInt(i)))if(" "!==i&&","!==i)if("-"!==i)if("E"!==i.toUpperCase()){if(tt.test(i)){if(r.inNumber)ue(r,!1);else{if(!oe(r))throw new Error("parser Error");ae(r)}--n}}else r.number+=i,r.hasExponent=!0;else{if(r.inNumber&&!ce(r)){ue(r,!1),--n;continue}r.number+=i,r.inNumber=!0}else r.inNumber&&ue(r,!1);else{if("0"===r.number||le(r)){r.inNumber=!0,r.number=i,ue(r,!0);continue}r.inNumber=!0,r.number+=i}else{if(r.pointSeen||r.hasExponent){ue(r,!1),--n;continue}r.inNumber=!0,r.pointSeen=!0,r.number+=i}return r.inNumber&&ue(r,!1),r.inSegment&&oe(r)&&ae(r),r.segments}(t)}size(t,e){const n=this.bbox();let i,r;for(n.width=0===n.width?1:n.width,n.height=0===n.height?1:n.height,i=this.length-1;i>=0;i--)r=this[i][0],"M"===r||"L"===r||"T"===r?(this[i][1]=(this[i][1]-n.x)*t/n.width+n.x,this[i][2]=(this[i][2]-n.y)*e/n.height+n.y):"H"===r?this[i][1]=(this[i][1]-n.x)*t/n.width+n.x:"V"===r?this[i][1]=(this[i][1]-n.y)*e/n.height+n.y:"C"===r||"S"===r||"Q"===r?(this[i][1]=(this[i][1]-n.x)*t/n.width+n.x,this[i][2]=(this[i][2]-n.y)*e/n.height+n.y,this[i][3]=(this[i][3]-n.x)*t/n.width+n.x,this[i][4]=(this[i][4]-n.y)*e/n.height+n.y,"C"===r&&(this[i][5]=(this[i][5]-n.x)*t/n.width+n.x,this[i][6]=(this[i][6]-n.y)*e/n.height+n.y)):"A"===r&&(this[i][1]=this[i][1]*t/n.width,this[i][2]=this[i][2]*e/n.height,this[i][6]=(this[i][6]-n.x)*t/n.width+n.x,this[i][7]=(this[i][7]-n.y)*e/n.height+n.y);return this}toString(){return function(t){let e="";for(let n=0,i=t.length;n{const e=typeof t;return"number"===e?St:"string"===e?st.isColor(t)?st:K.test(t)?tt.test(t)?fe:Nt:X.test(t)?St:pe:ge.indexOf(t.constructor)>-1?t.constructor:Array.isArray(t)?Nt:"object"===e?_e:pe};class me{constructor(t){this._stepper=t||new Zt("-"),this._from=null,this._to=null,this._type=null,this._context=null,this._morphObj=null}at(t){return this._morphObj.morph(this._from,this._to,t,this._stepper,this._context)}done(){return this._context.map(this._stepper.done).reduce((function(t,e){return t&&e}),!0)}from(t){return null==t?this._from:(this._from=this._set(t),this)}stepper(t){return null==t?this._stepper:(this._stepper=t,this)}to(t){return null==t?this._to:(this._to=this._set(t),this)}type(t){return null==t?this._type:(this._type=t,this)}_set(t){this._type||this.type(de(t));let e=new this._type(t);return this._type===st&&(e=this._to?e[this._to[4]]():this._from?e[this._from[4]]():e),this._type===_e&&(e=this._to?e.align(this._to):this._from?e.align(this._from):e),e=e.toConsumable(),this._morphObj=this._morphObj||new this._type,this._context=this._context||Array.apply(null,Array(e.length)).map(Object).map((function(t){return t.done=!0,t})),e}}class pe{constructor(...t){this.init(...t)}init(t){return t=Array.isArray(t)?t[0]:t,this.value=t,this}toArray(){return[this.value]}valueOf(){return this.value}}class ye{constructor(...t){this.init(...t)}init(t){return Array.isArray(t)&&(t={scaleX:t[0],scaleY:t[1],shear:t[2],rotate:t[3],translateX:t[4],translateY:t[5],originX:t[6],originY:t[7]}),Object.assign(this,ye.defaults,t),this}toArray(){const t=this;return[t.scaleX,t.scaleY,t.shear,t.rotate,t.translateX,t.translateY,t.originX,t.originY]}}ye.defaults={scaleX:1,scaleY:1,shear:0,rotate:0,translateX:0,translateY:0,originX:0,originY:0};const we=(t,e)=>t[0]e[0]?1:0;class _e{constructor(...t){this.init(...t)}align(t){const e=this.values;for(let n=0,i=e.length;nt.concat(e)),[]),this}toArray(){return this.values}valueOf(){const t={},e=this.values;for(;e.length;){const n=e.shift(),i=e.shift(),r=e.shift(),s=e.splice(0,r);t[n]=new i(s)}return t}}const ge=[pe,ye,_e];function xe(t=[]){ge.push(...[].concat(t))}function be(){F(ge,{to(t){return(new me).type(this.constructor).from(this.toArray()).to(t)},fromArray(t){return this.init(t),this},toConsumable(){return this.toArray()},morph(t,e,n,i,r){return this.fromArray(t.map((function(t,s){return i.step(t,e[s],n,r[s],r)})))}})}class Path extends Shape{constructor(t,e=t){super(E("path",t),e)}array(){return this._array||(this._array=new fe(this.attr("d")))}clear(){return delete this._array,this}height(t){return null==t?this.bbox().height:this.size(this.bbox().width,t)}move(t,e){return this.attr("d",this.array().move(t,e))}plot(t){return null==t?this.array():this.clear().attr("d","string"==typeof t?t:this._array=new fe(t))}size(t,e){const n=c(this,t,e);return this.attr("d",this.array().size(n.width,n.height))}width(t){return null==t?this.bbox().width:this.size(t,this.bbox().height)}x(t){return null==t?this.bbox().x:this.move(t,this.bbox().y)}y(t){return null==t?this.bbox().y:this.move(this.bbox().x,t)}}Path.prototype.MorphArray=fe,n({Container:{path:q((function(t){return this.put(new Path).plot(t||new fe)}))}}),I(Path,"Path");var ve={__proto__:null,array:function(){return this._array||(this._array=new $t(this.attr("points")))},clear:function(){return delete this._array,this},move:function(t,e){return this.attr("points",this.array().move(t,e))},plot:function(t){return null==t?this.array():this.clear().attr("points","string"==typeof t?t:this._array=new $t(t))},size:function(t,e){const n=c(this,t,e);return this.attr("points",this.array().size(n.width,n.height))}};class Polygon extends Shape{constructor(t,e=t){super(E("polygon",t),e)}}n({Container:{polygon:q((function(t){return this.put(new Polygon).plot(t||new $t)}))}}),F(Polygon,Ut),F(Polygon,ve),I(Polygon,"Polygon");class Polyline extends Shape{constructor(t,e=t){super(E("polyline",t),e)}}n({Container:{polyline:q((function(t){return this.put(new Polyline).plot(t||new $t)}))}}),F(Polyline,Ut),F(Polyline,ve),I(Polyline,"Polyline");class Rect extends Shape{constructor(t,e=t){super(E("rect",t),e)}}F(Rect,{rx:Dt,ry:It}),n({Container:{rect:q((function(t,e){return this.put(new Rect).size(t,e)}))}}),I(Rect,"Rect");class Me{constructor(){this._first=null,this._last=null}first(){return this._first&&this._first.value}last(){return this._last&&this._last.value}push(t){const e=void 0!==t.next?t:{value:t,next:null,prev:null};return this._last?(e.prev=this._last,this._last.next=e,this._last=e):(this._last=e,this._first=e),e}remove(t){t.prev&&(t.prev.next=t.next),t.next&&(t.next.prev=t.prev),t===this._last&&(this._last=t.prev),t===this._first&&(this._first=t.next),t.prev=null,t.next=null}shift(){const t=this._first;return t?(this._first=t.next,this._first&&(this._first.prev=null),this._last=this._first?this._last:null,t.value):null}}const Ae={nextDraw:null,frames:new Me,timeouts:new Me,immediates:new Me,timer:()=>x.window.performance||x.window.Date,transforms:[],frame(t){const e=Ae.frames.push({run:t});return null===Ae.nextDraw&&(Ae.nextDraw=x.window.requestAnimationFrame(Ae._draw)),e},timeout(t,e){e=e||0;const n=Ae.timer().now()+e,i=Ae.timeouts.push({run:t,time:n});return null===Ae.nextDraw&&(Ae.nextDraw=x.window.requestAnimationFrame(Ae._draw)),i},immediate(t){const e=Ae.immediates.push(t);return null===Ae.nextDraw&&(Ae.nextDraw=x.window.requestAnimationFrame(Ae._draw)),e},cancelFrame(t){null!=t&&Ae.frames.remove(t)},clearTimeout(t){null!=t&&Ae.timeouts.remove(t)},cancelImmediate(t){null!=t&&Ae.immediates.remove(t)},_draw(t){let e=null;const n=Ae.timeouts.last();for(;(e=Ae.timeouts.shift())&&(t>=e.time?e.run():Ae.timeouts.push(e),e!==n););let i=null;const r=Ae.frames.last();for(;i!==r&&(i=Ae.frames.shift());)i.run(t);let s=null;for(;s=Ae.immediates.shift();)s();Ae.nextDraw=Ae.timeouts.first()||Ae.frames.first()?x.window.requestAnimationFrame(Ae._draw):null}},Oe=function(t){const e=t.start,n=t.runner.duration();return{start:e,duration:n,end:e+n,runner:t.runner}},ke=function(){const t=x.window;return(t.performance||t.Date).now()};class Ce extends At{constructor(t=ke){super(),this._timeSource=t,this._startTime=0,this._speed=1,this._persist=0,this._nextFrame=null,this._paused=!0,this._runners=[],this._runnerIds=[],this._lastRunnerId=-1,this._time=0,this._lastSourceTime=0,this._lastStepTime=0,this._step=this._stepFn.bind(this,!1),this._stepImmediate=this._stepFn.bind(this,!0)}active(){return!!this._nextFrame}finish(){return this.time(this.getEndTimeOfTimeline()+1),this.pause()}getEndTime(){const t=this.getLastRunnerInfo(),e=t?t.runner.duration():0;return(t?t.start:this._time)+e}getEndTimeOfTimeline(){const t=this._runners.map((t=>t.start+t.runner.duration()));return Math.max(0,...t)}getLastRunnerInfo(){return this.getRunnerInfoById(this._lastRunnerId)}getRunnerInfoById(t){return this._runners[this._runnerIds.indexOf(t)]||null}pause(){return this._paused=!0,this._continue()}persist(t){return null==t?this._persist:(this._persist=t,this)}play(){return this._paused=!1,this.updateTime()._continue()}reverse(t){const e=this.speed();if(null==t)return this.speed(-e);const n=Math.abs(e);return this.speed(t?-n:n)}schedule(t,e,n){if(null==t)return this._runners.map(Oe);let i=0;const r=this.getEndTime();if(e=e||0,null==n||"last"===n||"after"===n)i=r;else if("absolute"===n||"start"===n)i=e,e=0;else if("now"===n)i=this._time;else if("relative"===n){const n=this.getRunnerInfoById(t.id);n&&(i=n.start+e,e=0)}else{if("with-last"!==n)throw new Error('Invalid value for the "when" parameter');{const t=this.getLastRunnerInfo();i=t?t.start:this._time}}t.unschedule(),t.timeline(this);const s=t.persist(),o={persist:null===s?this._persist:s,start:i+e,runner:t};return this._lastRunnerId=t.id,this._runners.push(o),this._runners.sort(((t,e)=>t.start-e.start)),this._runnerIds=this._runners.map((t=>t.runner.id)),this.updateTime()._continue(),this}seek(t){return this.time(this._time+t)}source(t){return null==t?this._timeSource:(this._timeSource=t,this)}speed(t){return null==t?this._speed:(this._speed=t,this)}stop(){return this.time(0),this.pause()}time(t){return null==t?this._time:(this._time=t,this._continue(!0))}unschedule(t){const e=this._runnerIds.indexOf(t.id);return e<0||(this._runners.splice(e,1),this._runnerIds.splice(e,1),t.timeline(null)),this}updateTime(){return this.active()||(this._lastSourceTime=this._timeSource()),this}_continue(t=!1){return Ae.cancelFrame(this._nextFrame),this._nextFrame=null,t?this._stepImmediate():(this._paused||(this._nextFrame=Ae.frame(this._step)),this)}_stepFn(t=!1){const e=this._timeSource();let n=e-this._lastSourceTime;t&&(n=0);const i=this._speed*n+(this._time-this._lastStepTime);this._lastSourceTime=e,t||(this._time+=i,this._time=this._time<0?0:this._time),this._lastStepTime=this._time,this.fire("time",this._time);for(let t=this._runners.length;t--;){const e=this._runners[t],n=e.runner;this._time-e.start<=0&&n.reset()}let r=!1;for(let t=0,e=this._runners.length;t0?this._continue():(this.pause(),this.fire("finished")),this}}n({Element:{timeline:function(t){return null==t?(this._timeline=this._timeline||new Ce,this._timeline):(this._timeline=t,this)}}});class Te extends At{constructor(t){super(),this.id=Te.id++,t="function"==typeof(t=null==t?kt.duration:t)?new Kt(t):t,this._element=null,this._timeline=null,this.done=!1,this._queue=[],this._duration="number"==typeof t&&t,this._isDeclarative=t instanceof Kt,this._stepper=this._isDeclarative?t:new Zt,this._history={},this.enabled=!0,this._time=0,this._lastTime=0,this._reseted=!0,this.transforms=new ut,this.transformId=1,this._haveReversed=!1,this._reverse=!1,this._loopsDone=0,this._swing=!1,this._wait=0,this._times=1,this._frameId=null,this._persist=!!this._isDeclarative||null}static sanitise(t,e,n){let i=1,r=!1,s=0;return e=e||kt.delay,n=n||"last","object"!=typeof(t=t||kt.duration)||t instanceof Jt||(e=t.delay||e,n=t.when||n,r=t.swing||r,i=t.times||i,s=t.wait||s,t=t.duration||kt.duration),{duration:t,delay:e,swing:r,times:i,wait:s,when:n}}active(t){return null==t?this.enabled:(this.enabled=t,this)}addTransform(t,e){return this.transforms.lmultiplyO(t),this}after(t){return this.on("finished",t)}animate(t,e,n){const i=Te.sanitise(t,e,n),r=new Te(i.duration);return this._timeline&&r.timeline(this._timeline),this._element&&r.element(this._element),r.loop(i).schedule(i.delay,i.when)}clearTransform(){return this.transforms=new ut,this}clearTransformsFromQueue(){this.done&&this._timeline&&this._timeline._runnerIds.includes(this.id)||(this._queue=this._queue.filter((t=>!t.isTransform)))}delay(t){return this.animate(0,t)}duration(){return this._times*(this._wait+this._duration)-this._wait}during(t){return this.queue(null,t)}ease(t){return this._stepper=new Zt(t),this}element(t){return null==t?this._element:(this._element=t,t._prepareRunner(),this)}finish(){return this.step(1/0)}loop(t,e,n){return"object"==typeof t&&(e=t.swing,n=t.wait,t=t.times),this._times=t||1/0,this._swing=e||!1,this._wait=n||0,!0===this._times&&(this._times=1/0),this}loops(t){const e=this._duration+this._wait;if(null==t){const t=Math.floor(this._time/e),n=(this._time-t*e)/this._duration;return Math.min(t+n,this._times)}const n=t%1,i=e*Math.floor(t)+this._duration*n;return this.time(i)}persist(t){return null==t?this._persist:(this._persist=t,this)}position(t){const e=this._time,n=this._duration,i=this._wait,r=this._times,s=this._swing,o=this._reverse;let h;if(null==t){const t=function(t){const e=s*Math.floor(t%(2*(i+n))/(i+n)),r=e&&!o||!e&&o,h=Math.pow(-1,r)*(t%(i+n))/n+r;return Math.max(Math.min(h,1),0)},u=r*(i+n)-i;return h=e<=0?Math.round(t(1e-5)):e=0;this._lastPosition=e;const i=this.duration(),r=this._lastTime<=0&&this._time>0,s=this._lastTime=i;this._lastTime=this._time,r&&this.fire("start",this);const o=this._isDeclarative;this.done=!o&&!s&&this._time>=i,this._reseted=!1;let h=!1;return(n||o)&&(this._initialise(n),this.transforms=new ut,h=this._run(o?t:e),this.fire("step",this)),this.done=this.done||h&&o,s&&this.fire("finished",this),this}time(t){if(null==t)return this._time;const e=t-this._time;return this.step(e),this}timeline(t){return void 0===t?this._timeline:(this._timeline=t,this)}unschedule(){const t=this.timeline();return t&&t.unschedule(this),this}_initialise(t){if(t||this._isDeclarative)for(let e=0,n=this._queue.length;et.lmultiplyO(e),Ee=t=>t.transforms;function je(){const t=this._transformationRunners.runners.map(Ee).reduce(Se,new ut);this.transform(t),this._transformationRunners.merge(),1===this._transformationRunners.length()&&(this._frameId=null)}class De{constructor(){this.runners=[],this.ids=[]}add(t){if(this.runners.includes(t))return;const e=t.id+1;return this.runners.push(t),this.ids.push(e),this}clearBefore(t){const e=this.ids.indexOf(t+1)||1;return this.ids.splice(0,e,0),this.runners.splice(0,e,new Ne).forEach((t=>t.clearTransformsFromQueue())),this}edit(t,e){const n=this.ids.indexOf(t+1);return this.ids.splice(n,1,t+1),this.runners.splice(n,1,e),this}getByID(t){return this.runners[this.ids.indexOf(t+1)]}length(){return this.ids.length}merge(){let t=null;for(let e=0;ee.id<=t.id)).map(Ee).reduce(Se,new ut)},_addRunner(t){this._transformationRunners.add(t),Ae.cancelImmediate(this._frameId),this._frameId=Ae.immediate(je.bind(this))},_prepareRunner(){null==this._frameId&&(this._transformationRunners=(new De).add(new Ne(new ut(this))))}}});F(Te,{attr(t,e){return this.styleAttr("attr",t,e)},css(t,e){return this.styleAttr("css",t,e)},styleAttr(t,e,n){if("string"==typeof e)return this.styleAttr(t,{[e]:n});let i=e;if(this._tryRetarget(t,i))return this;let r=new me(this._stepper).to(i),s=Object.keys(i);return this.queue((function(){r=r.from(this.element()[t](s))}),(function(e){return this.element()[t](r.at(e).valueOf()),r.done()}),(function(e){const n=Object.keys(e),o=(h=s,n.filter((t=>!h.includes(t))));var h;if(o.length){const e=this.element()[t](o),n=new _e(r.from()).valueOf();Object.assign(n,e),r.from(n)}const u=new _e(r.to()).valueOf();Object.assign(u,e),r.to(u),s=n,i=e})),this._rememberMorpher(t,r),this},zoom(t,e){if(this._tryRetarget("zoom",t,e))return this;let n=new me(this._stepper).to(new St(t));return this.queue((function(){n=n.from(this.element().zoom())}),(function(t){return this.element().zoom(n.at(t),e),n.done()}),(function(t,i){e=i,n.to(t)})),this._rememberMorpher("zoom",n),this},transform(t,e,n){if(e=t.relative||e,this._isDeclarative&&!e&&this._tryRetarget("transform",t))return this;const i=ut.isMatrixLike(t);n=null!=t.affine?t.affine:null!=n?n:!i;const r=new me(this._stepper).type(n?ye:ut);let s,o,h,u,a;return this.queue((function(){o=o||this.element(),s=s||f(t,o),a=new ut(e?void 0:o),o._addRunner(this),e||o._clearTransformRunnersBefore(this)}),(function(l){e||this.clearTransform();const{x:c,y:f}=new ot(s).transform(o._currentTransform(this));let d=new ut({...t,origin:[c,f]}),m=this._isDeclarative&&h?h:a;if(n){d=d.decompose(c,f),m=m.decompose(c,f);const t=d.rotate,e=m.rotate,n=[t-360,t,t+360],i=n.map((t=>Math.abs(t-e))),r=Math.min(...i),s=i.indexOf(r);d.rotate=n[s]}e&&(i||(d.rotate=t.rotate||0),this._isDeclarative&&u&&(m.rotate=u)),r.from(m),r.to(d);const p=r.at(l);return u=p.rotate,h=new ut(p),this.addTransform(h),o._addRunner(this),r.done()}),(function(e){(e.origin||"center").toString()!==(t.origin||"center").toString()&&(s=f(e,o)),t={...e,origin:s}}),!0),this._isDeclarative&&this._rememberMorpher("transform",r),this},x(t,e){return this._queueNumber("x",t)},y(t){return this._queueNumber("y",t)},dx(t=0){return this._queueNumberDelta("x",t)},dy(t=0){return this._queueNumberDelta("y",t)},dmove(t,e){return this.dx(t).dy(e)},_queueNumberDelta(t,e){if(e=new St(e),this._tryRetarget(t,e))return this;const n=new me(this._stepper).to(e);let i=null;return this.queue((function(){i=this.element()[t](),n.from(i),n.to(i+e)}),(function(e){return this.element()[t](n.at(e)),n.done()}),(function(t){n.to(i+new St(t))})),this._rememberMorpher(t,n),this},_queueObject(t,e){if(this._tryRetarget(t,e))return this;const n=new me(this._stepper).to(e);return this.queue((function(){n.from(this.element()[t]())}),(function(e){return this.element()[t](n.at(e)),n.done()})),this._rememberMorpher(t,n),this},_queueNumber(t,e){return this._queueObject(t,new St(e))},cx(t){return this._queueNumber("cx",t)},cy(t){return this._queueNumber("cy",t)},move(t,e){return this.x(t).y(e)},center(t,e){return this.cx(t).cy(e)},size(t,e){let n;return t&&e||(n=this._element.bbox()),t||(t=n.width/n.height*e),e||(e=n.height/n.width*t),this.width(t).height(e)},width(t){return this._queueNumber("width",t)},height(t){return this._queueNumber("height",t)},plot(t,e,n,i){if(4===arguments.length)return this.plot([t,e,n,i]);if(this._tryRetarget("plot",t))return this;const r=new me(this._stepper).type(this._element.MorphArray).to(t);return this.queue((function(){r.from(this._element.array())}),(function(t){return this._element.plot(r.at(t)),r.done()})),this._rememberMorpher("plot",r),this},leading(t){return this._queueNumber("leading",t)},viewbox(t,e,n,i){return this._queueObject("viewbox",new ct(t,e,n,i))},update(t){return"object"!=typeof t?this.update({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",t.offset),this)}}),F(Te,{rx:Dt,ry:It,from:Bt,to:Ht}),I(Te,"Runner");class Svg extends Container{constructor(t,e=t){super(E("svg",t),e),this.namespace()}defs(){return this.isRoot()?j(this.node.querySelector("defs"))||this.put(new Defs):this.root().defs()}isRoot(){return!this.node.parentNode||!(this.node.parentNode instanceof x.window.SVGElement)&&"#document-fragment"!==this.node.parentNode.nodeName}namespace(){return this.isRoot()?this.attr({xmlns:m,version:"1.1"}).attr("xmlns:xlink",w,y).attr("xmlns:svgjs",_,y):this.root().namespace()}removeNamespace(){return this.attr({xmlns:null,version:null}).attr("xmlns:xlink",null,y).attr("xmlns:svgjs",null,y)}root(){return this.isRoot()?this:super.root()}}n({Container:{nested:q((function(){return this.put(new Svg)}))}}),I(Svg,"Svg",!0);class Symbol extends Container{constructor(t,e=t){super(E("symbol",t),e)}}n({Container:{symbol:q((function(){return this.put(new Symbol)}))}}),I(Symbol,"Symbol");var Ie={__proto__:null,plain:function(t){return!1===this._build&&this.clear(),this.node.appendChild(x.document.createTextNode(t)),this},length:function(){return this.node.getComputedTextLength()},x:function(t,e=this.bbox()){return null==t?e.x:this.attr("x",this.attr("x")+t-e.x)},y:function(t,e=this.bbox()){return null==t?e.y:this.attr("y",this.attr("y")+t-e.y)},move:function(t,e,n=this.bbox()){return this.x(t,n).y(e,n)},cx:function(t,e=this.bbox()){return null==t?e.cx:this.attr("x",this.attr("x")+t-e.cx)},cy:function(t,e=this.bbox()){return null==t?e.cy:this.attr("y",this.attr("y")+t-e.cy)},center:function(t,e,n=this.bbox()){return this.cx(t,n).cy(e,n)},ax:function(t){return this.attr("x",t)},ay:function(t){return this.attr("y",t)},amove:function(t,e){return this.ax(t).ay(e)},build:function(t){return this._build=!!t,this}};class Text extends Shape{constructor(t,e=t){super(E("text",t),e),this.dom.leading=new St(1.3),this._rebuild=!0,this._build=!1}leading(t){return null==t?this.dom.leading:(this.dom.leading=new St(t),this.rebuild())}rebuild(t){if("boolean"==typeof t&&(this._rebuild=t),this._rebuild){const t=this;let e=0;const n=this.dom.leading;this.each((function(i){const r=x.window.getComputedStyle(this.node).getPropertyValue("font-size"),s=n*new St(r);this.dom.newLined&&(this.attr("x",t.attr("x")),"\n"===this.text()?e+=s:(this.attr("dy",i?s+e:0),e=0))})),this.fire("rebuild")}return this}setData(t){return this.dom=t,this.dom.leading=new St(t.leading||1.3),this}text(t){if(void 0===t){const e=this.node.childNodes;let n=0;t="";for(let i=0,r=e.length;i{let r;try{r=n.bbox()}catch(t){return}const s=new ut(n),o=s.translate(t,e).transform(s.inverse()),h=new ot(r.x,r.y).transform(o);n.move(h.x,h.y)})),this},dx:function(t){return this.dmove(t,0)},dy:function(t){return this.dmove(0,t)},height:function(t,e=this.bbox()){return null==t?e.height:this.size(e.width,t,e)},move:function(t=0,e=0,n=this.bbox()){const i=t-n.x,r=e-n.y;return this.dmove(i,r)},size:function(t,e,n=this.bbox()){const i=c(this,t,e,n),r=i.width/n.width,s=i.height/n.height;return this.children().forEach(((t,e)=>{const i=new ot(n).transform(new ut(t).inverse());t.scale(r,s,i.x,i.y)})),this},width:function(t,e=this.bbox()){return null==t?e.width:this.size(t,e.height,e)},x:function(t,e=this.bbox()){return null==t?e.x:this.move(t,e.y,e)},y:function(t,e=this.bbox()){return null==t?e.y:this.move(e.x,t,e)}};class G extends Container{constructor(t,e=t){super(E("g",t),e)}}F(G,Re),n({Container:{group:q((function(){return this.put(new G)}))}}),I(G,"G");class A extends Container{constructor(t,e=t){super(E("a",t),e)}target(t){return this.attr("target",t)}to(t){return this.attr("href",t,w)}}F(A,Re),n({Container:{link:q((function(t){return this.put(new A).to(t)}))},Element:{unlink(){const t=this.linker();if(!t)return this;const e=t.parent();if(!e)return this.remove();const n=e.index(t);return e.add(this,n),t.remove(),this},linkTo(t){let e=this.linker();return e||(e=new A,this.wrap(e)),"function"==typeof t?t.call(e,e):e.to(t),this},linker(){const t=this.parent();return t&&"a"===t.node.nodeName.toLowerCase()?t:null}}}),I(A,"A");class Mask extends Container{constructor(t,e=t){super(E("mask",t),e)}remove(){return this.targets().forEach((function(t){t.unmask()})),super.remove()}targets(){return pt('svg [mask*="'+this.id()+'"]')}}n({Container:{mask:q((function(){return this.defs().put(new Mask)}))},Element:{masker(){return this.reference("mask")},maskWith(t){const e=t instanceof Mask?t:this.parent().mask().add(t);return this.attr("mask",'url("#'+e.id()+'")')},unmask(){return this.attr("mask",null)}}}),I(Mask,"Mask");class Stop extends Element{constructor(t,e=t){super(E("stop",t),e)}update(t){return("number"==typeof t||t instanceof St)&&(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 St(t.offset)),this}}n({Gradient:{stop:function(t,e,n){return this.put(new Stop).update(t,e,n)}}}),I(Stop,"Stop");class Style extends Element{constructor(t,e=t){super(E("style",t),e)}addText(t=""){return this.node.textContent+=t,this}font(t,e,n={}){return this.rule("@font-face",{fontFamily:t,src:e,...n})}rule(t,e){return this.addText(function(t,e){if(!t)return"";if(!e)return t;let n=t+"{";for(const t in e)n+=a(t)+":"+e[t]+";";return n+="}",n}(t,e))}}n("Dom",{style(t,e){return this.put(new Style).rule(t,e)},fontface(t,e,n){return this.put(new Style).font(t,e,n)}}),I(Style,"Style");class TextPath extends Text{constructor(t,e=t){super(E("textPath",t),e)}array(){const t=this.track();return t?t.array():null}plot(t){const e=this.track();let n=null;return e&&(n=e.plot(t)),null==t?n:this}track(){return this.reference("href")}}n({Container:{textPath:q((function(t,e){return t instanceof Text||(t=this.text(t)),t.path(e)}))},Text:{path:q((function(t,e=!0){const n=new TextPath;let i;if(t instanceof Path||(t=this.defs().path(t)),n.attr("href","#"+t,w),e)for(;i=this.node.firstChild;)n.node.appendChild(i);return this.put(n)})),textPath(){return this.findOne("textPath")}},Path:{text:q((function(t){return t instanceof Text||(t=(new Text).addTo(this.parent()).text(t)),t.path(this)})),targets(){return pt("svg textPath").filter((t=>(t.attr("href")||"").includes(this.id())))}}}),TextPath.prototype.MorphArray=fe,I(TextPath,"TextPath");class Use extends Shape{constructor(t,e=t){super(E("use",t),e)}use(t,e){return this.attr("href",(e||"")+"#"+t,w)}}n({Container:{use:q((function(t,e){return this.put(new Use).use(t,e)}))}}),I(Use,"Use");const Le=S;F([Svg,Symbol,Image,Pattern,Marker],i("viewbox")),F([Line,Polyline,Polygon,Path],i("marker")),F(Text,i("Text")),F(Path,i("Path")),F(Defs,i("Defs")),F([Text,Tspan],i("Tspan")),F([Rect,Ellipse,Gradient,Te],i("radius")),F(At,i("EventTarget")),F(Dom,i("Dom")),F(Element,i("Element")),F(Shape,i("Shape")),F([Container,Yt],i("Container")),F(Gradient,i("Gradient")),F(Te,i("Runner")),dt.extend([...new Set(e)]),xe([St,st,ct,ut,Nt,$t,fe,ot]),be();var Pe={__proto__:null,Morphable:me,registerMorphableType:xe,makeMorphable:be,TransformBag:ye,ObjectBag:_e,NonMorphable:pe,defaults:Tt,utils:d,namespaces:g,regex:et,SVG:Le,parser:at,find:pt,getWindow:function(){return x.window},registerWindow:b,restoreWindow:O,saveWindow:M,withWindow:function(t,e){M(),b(t,t.document),e(t,t.document),O()},Animator:Ae,Controller:Kt,Ease:Zt,PID:ne,Spring:ee,easing:Qt,Queue:Me,Runner:Te,Timeline:Ce,Array:Nt,Box:ct,Color:st,EventTarget:At,Matrix:ut,Number:St,PathArray:fe,Point:ot,PointArray:$t,List:dt,Circle:Circle,ClipPath:ClipPath,Container:Container,Defs:Defs,Dom:Dom,Element:Element,Ellipse:Ellipse,ForeignObject:ze,Fragment:Yt,Gradient:Gradient,G:G,A:A,Image:Image,Line:Line,Marker:Marker,Mask:Mask,Path:Path,Pattern:Pattern,Polygon:Polygon,Polyline:Polyline,Rect:Rect,Shape:Shape,Stop:Stop,Style:Style,Svg:Svg,Symbol:Symbol,Text:Text,TextPath:TextPath,Tspan:Tspan,Use:Use,windowEvents:wt,getEvents:_t,getEventTarget:gt,clearEvents:xt,on:bt,off:vt,dispatch:Mt,root:T,create:N,makeInstance:S,nodeOrNew:E,adopt:j,mockAdopt:function(t=j){D=t},register:I,getClass:z,eid:L,assignNewId:P,extend:F,wrapWithAttrCheck:q};function Fe(t,e){return S(t,e)}return Object.assign(Fe,Pe),Fe}(); -//# sourceMappingURL=svg.min.js.map \ No newline at end of file +/*! svg.js v2.7.1 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 i(t,e,i,n){return i+n.replace(b.regex.dots," .")}function n(t){for(var e=t.slice(0),i=e.length;i--;)Array.isArray(e[i])&&(e[i]=n(e[i]));return e}function r(t,e){return t instanceof e}function s(t,e){return(t.matches||t.matchesSelector||t.msMatchesSelector||t.mozMatchesSelector||t.webkitMatchesSelector||t.oMatchesSelector).call(t,e)}function o(t){return t.toLowerCase().replace(/-(.)/g,function(t,e){return e.toUpperCase()})}function a(t){return t.charAt(0).toUpperCase()+t.slice(1)}function h(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 u(t){var e=t.toString(16);return 1==e.length?"0"+e:e}function l(t,e,i){if(null==e||null==i){var n=t.bbox();null==e?e=n.width/n.height*i:null==i&&(i=n.height/n.width*e)}return{width:e,height:i}}function c(t,e,i){return{x:e*t.a+i*t.c+0,y:e*t.b+i*t.d+0}}function f(t){return{a:t[0],b:t[1],c:t[2],d:t[3],e:t[4],f:t[5]}}function d(t){return t instanceof b.Matrix||(t=new b.Matrix(t)),t}function p(t,e){t.cx=null==t.cx?e.bbox().cx:t.cx,t.cy=null==t.cy?e.bbox().cy:t.cy}function m(t){for(var e=0,i=t.length,n="";e=0;i--)e.childNodes[i]instanceof t.SVGElement&&x(e.childNodes[i]);return b.adopt(e).id(b.eid(e.nodeName))}function y(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 v(t){var e=(t||"").toString().match(b.regex.reference);if(e)return e[1]}function g(t){return Math.abs(t)>1e-37?t:0}var w=void 0!==this?this:t,b=w.SVG=function(t){if(b.supported)return t=new b.Doc(t),b.parser.draw||b.prepare(),t};if(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.supported=function(){return!!e.createElementNS&&!!e.createElementNS(b.ns,"svg").createSVGRect}(),!b.supported)return!1;b.did=1e3,b.eid=function(t){return"Svgjs"+a(t)+b.did++},b.create=function(t){var i=e.createElementNS(this.ns,t);return i.setAttribute("id",this.eid(t)),i},b.extend=function(){var t,e,i,n;for(t=[].slice.call(arguments),e=t.pop(),n=t.length-1;n>=0;n--)if(t[n])for(i in e)t[n].prototype[i]=e[i];b.Set&&b.Set.inherit&&b.Set.inherit()},b.invent=function(t){var e="function"==typeof t.create?t.create:function(){this.constructor.call(this,b.create(t.create))};return t.inherit&&(e.prototype=new t.inherit),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)return e.instance;var i;return i="svg"==e.nodeName?e.parentNode instanceof t.SVGElement?new b.Nested:new b.Doc:"linearGradient"==e.nodeName?new b.Gradient("linear"):"radialGradient"==e.nodeName?new b.Gradient("radial"):b[a(e.nodeName)]?new(b[a(e.nodeName)]):new b.Element(e),i.type=e.nodeName,i.node=e,e.instance=i,i instanceof b.Doc&&i.namespace().defs(),i.setData(JSON.parse(e.getAttribute("svgjs:data"))||{}),i},b.prepare=function(){var t=e.getElementsByTagName("body")[0],i=(t?new b.Doc(t):b.adopt(e.documentElement).nested()).size(2,0);b.parser={body:t||e.documentElement,draw:i.style("opacity:0;position:absolute;left:-100%;top:-100%;overflow:hidden").attr("focusable","false").node,poly:i.polyline().node,path:i.path().node,native:b.create("svg")}},b.parser={native:b.create("svg")},e.addEventListener("DOMContentLoaded",function(){b.parser.draw||b.prepare()},!1),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 i,n=t.length,r=[];for(i=0;i1?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],i=this.destination[this.destination.length-1];this.value.length>this.destination.length;)this.destination.push(i);for(;this.value.length=0;n--)this.value[n]=[this.value[n][0]+t,this.value[n][1]+e];return this},size:function(t,e){var i,n=this.bbox();for(i=this.value.length-1;i>=0;i--)n.width&&(this.value[i][0]=(this.value[i][0]-n.x)*t/n.width+n.x),n.height&&(this.value[i][1]=(this.value[i][1]-n.y)*e/n.height+n.y);return this},bbox:function(){return b.parser.poly.setAttribute("points",this.toString()),b.parser.poly.getBBox()}});for(var C={M:function(t,e,i){return e.x=i.x=t[0],e.y=i.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,i){return e.x=i.x,e.y=i.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]]}},N="mlhvqtcsaz".split(""),A=0,P=N.length;A=0;r--)n=this.value[r][0],"M"==n||"L"==n||"T"==n?(this.value[r][1]+=t,this.value[r][2]+=e):"H"==n?this.value[r][1]+=t:"V"==n?this.value[r][1]+=e:"C"==n||"S"==n||"Q"==n?(this.value[r][1]+=t,this.value[r][2]+=e,this.value[r][3]+=t,this.value[r][4]+=e,"C"==n&&(this.value[r][5]+=t,this.value[r][6]+=e)):"A"==n&&(this.value[r][6]+=t,this.value[r][7]+=e);return this},size:function(t,e){var i,n,r=this.bbox();for(i=this.value.length-1;i>=0;i--)n=this.value[i][0],"M"==n||"L"==n||"T"==n?(this.value[i][1]=(this.value[i][1]-r.x)*t/r.width+r.x,this.value[i][2]=(this.value[i][2]-r.y)*e/r.height+r.y):"H"==n?this.value[i][1]=(this.value[i][1]-r.x)*t/r.width+r.x:"V"==n?this.value[i][1]=(this.value[i][1]-r.y)*e/r.height+r.y:"C"==n||"S"==n||"Q"==n?(this.value[i][1]=(this.value[i][1]-r.x)*t/r.width+r.x,this.value[i][2]=(this.value[i][2]-r.y)*e/r.height+r.y,this.value[i][3]=(this.value[i][3]-r.x)*t/r.width+r.x,this.value[i][4]=(this.value[i][4]-r.y)*e/r.height+r.y,"C"==n&&(this.value[i][5]=(this.value[i][5]-r.x)*t/r.width+r.x,this.value[i][6]=(this.value[i][6]-r.y)*e/r.height+r.y)):"A"==n&&(this.value[i][1]=this.value[i][1]*t/r.width,this.value[i][2]=this.value[i][2]*e/r.height,this.value[i][6]=(this.value[i][6]-r.x)*t/r.width+r.x,this.value[i][7]=(this.value[i][7]-r.y)*e/r.height+r.y);return this},equalCommands:function(t){var e,i,n;for(t=new b.PathArray(t),n=this.value.length===t.value.length,e=0,i=this.value.length;n&&ea);return n},bbox:function(){return b.parser.path.setAttribute("d",this.toString()),b.parser.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()},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.Element=b.invent({create:function(t){this._stroke=b.defaults.attrs.stroke,this._event=null,this._events={},this.dom={},(this.node=t)&&(this.type=t.nodeName,this.node.instance=this,this._events=t._events||{},this._stroke=t.getAttribute("stroke")||this._stroke)},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 i=l(this,t,e);return this.width(new b.Number(i.width)).height(new b.Number(i.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 t.put(this)},putIn:function(t){return t.add(this)},id:function(t){return this.attr("id",t)},inside:function(t,e){var i=this.bbox();return t>i.x&&e>i.y&&t/,"").replace(/<\/svg>$/,"");i.innerHTML=""+t.replace(/\n/,"").replace(/<([\w:-]+)([^<]+?)\/>/g,"<$1$2>")+"";for(var n=0,r=i.firstChild.childNodes.length;n":function(t){return-Math.cos(t*Math.PI)/2+.5},">":function(t){return Math.sin(t*Math.PI/2)},"<":function(t){return 1-Math.cos(t*Math.PI/2)}},b.morph=function(t){return function(e,i){return new b.MorphObj(e,i).at(t)}},b.Situation=b.invent({create:function(t){this.init=!1,this.reversed=!1,this.reversing=!1,this.duration=new b.Number(t.duration).valueOf(),this.delay=new b.Number(t.delay).valueOf(),this.start=+new Date+this.delay,this.finish=this.start+this.duration,this.ease=t.ease,this.loop=0,this.loops=!1,this.animations={},this.attrs={},this.styles={},this.transforms=[],this.once={}}}),b.FX=b.invent({create:function(t){this._target=t,this.situations=[],this.active=!1,this.situation=null,this.paused=!1,this.lastPos=0,this.pos=0,this.absPos=0,this._speed=1},extend:{animate:function(t,e,i){"object"==typeof t&&(e=t.ease,i=t.delay,t=t.duration);var n=new b.Situation({duration:t||1e3,delay:i||0,ease:b.easing[e||"-"]||e});return this.queue(n),this},delay:function(t){var e=new b.Situation({duration:t,delay:0,ease:b.easing["-"]});return this.queue(e)},target:function(t){return t&&t instanceof b.Element?(this._target=t,this):this._target},timeToAbsPos:function(t){return(t-this.situation.start)/(this.situation.duration/this._speed)},absPosToTime:function(t){return this.situation.duration/this._speed*t+this.situation.start},startAnimFrame:function(){this.stopAnimFrame(),this.animationFrame=t.requestAnimationFrame(function(){this.step()}.bind(this))},stopAnimFrame:function(){t.cancelAnimationFrame(this.animationFrame)},start:function(){return!this.active&&this.situation&&(this.active=!0,this.startCurrent()),this},startCurrent:function(){return this.situation.start=+new Date+this.situation.delay/this._speed,this.situation.finish=this.situation.start+this.situation.duration/this._speed,this.initAnimations().step()},queue:function(t){return("function"==typeof t||t instanceof b.Situation)&&this.situations.push(t),this.situation||(this.situation=this.situations.shift()),this},dequeue:function(){return this.stop(),this.situation=this.situations.shift(),this.situation&&(this.situation instanceof b.Situation?this.start():this.situation.call(this)),this},initAnimations:function(){var t,e,i,n=this.situation;if(n.init)return this;for(t in n.animations)for(i=this.target()[t](),Array.isArray(i)||(i=[i]),Array.isArray(n.animations[t])||(n.animations[t]=[n.animations[t]]),e=i.length;e--;)n.animations[t][e]instanceof b.Number&&(i[e]=new b.Number(i[e])),n.animations[t][e]=i[e].morph(n.animations[t][e]);for(t in n.attrs)n.attrs[t]=new b.MorphObj(this.target().attr(t),n.attrs[t]);for(t in n.styles)n.styles[t]=new b.MorphObj(this.target().style(t),n.styles[t]);return n.initialTransformation=this.target().matrixify(),n.init=!0,this},clearQueue:function(){return this.situations=[],this},clearCurrent:function(){return this.situation=null,this},stop:function(t,e){var i=this.active;return this.active=!1,e&&this.clearQueue(),t&&this.situation&&(!i&&this.startCurrent(),this.atEnd()),this.stopAnimFrame(),this.clearCurrent()},reset:function(){if(this.situation){var t=this.situation;this.stop(),this.situation=t,this.atStart()}return this},finish:function(){for(this.stop(!0,!1);this.dequeue().situation&&this.stop(!0,!1););return this.clearQueue().clearCurrent(),this},atStart:function(){return this.at(0,!0)},atEnd:function(){return!0===this.situation.loops&&(this.situation.loops=this.situation.loop+1),"number"==typeof this.situation.loops?this.at(this.situation.loops,!0):this.at(1,!0)},at:function(t,e){var i=this.situation.duration/this._speed;return this.absPos=t,e||(this.situation.reversed&&(this.absPos=1-this.absPos),this.absPos+=this.situation.loop),this.situation.start=+new Date-this.absPos*i,this.situation.finish=this.situation.start+i,this.step(!0)},speed:function(t){return 0===t?this.pause():t?(this._speed=t,this.at(this.absPos,!0)):this._speed},loop:function(t,e){var i=this.last();return i.loops=null==t||t,i.loop=0,e&&(i.reversing=!0),this},pause:function(){return this.paused=!0,this.stopAnimFrame(),this},play:function(){return this.paused?(this.paused=!1,this.at(this.absPos,!0)):this},reverse:function(t){var e=this.last();return e.reversed=void 0===t?!e.reversed:t,this},progress:function(t){return t?this.situation.ease(this.pos):this.pos},after:function(t){var e=this.last(),i=function i(n){n.detail.situation==e&&(t.call(this,e),this.off("finished.fx",i))};return this.target().on("finished.fx",i),this._callStart()},during:function(t){var e=this.last(),i=function(i){i.detail.situation==e&&t.call(this,i.detail.pos,b.morph(i.detail.pos),i.detail.eased,e)};return this.target().off("during.fx",i).on("during.fx",i),this.after(function(){this.off("during.fx",i)}),this._callStart()},afterAll:function(t){var e=function e(i){t.call(this),this.off("allfinished.fx",e)};return this.target().off("allfinished.fx",e).on("allfinished.fx",e),this._callStart()},duringAll:function(t){var e=function(e){t.call(this,e.detail.pos,b.morph(e.detail.pos),e.detail.eased,e.detail.situation)};return this.target().off("during.fx",e).on("during.fx",e),this.afterAll(function(){this.off("during.fx",e)}),this._callStart()},last:function(){return this.situations.length?this.situations[this.situations.length-1]:this.situation},add:function(t,e,i){return this.last()[i||"animations"][t]=e,this._callStart()},step:function(t){if(t||(this.absPos=this.timeToAbsPos(+new Date)),!1!==this.situation.loops){var e,i,n;e=Math.max(this.absPos,0),i=Math.floor(e),!0===this.situation.loops||ithis.lastPos&&s<=r&&(this.situation.once[s].call(this.target(),this.pos,r),delete this.situation.once[s]);return this.active&&this.target().fire("during",{pos:this.pos,eased:r,fx:this,situation:this.situation}),this.situation?(this.eachAt(),1==this.pos&&!this.situation.reversed||this.situation.reversed&&0==this.pos?(this.stopAnimFrame(),this.target().fire("finished",{fx:this,situation:this.situation}),this.situations.length||(this.target().fire("allfinished"),this.situations.length||(this.target().off(".fx"),this.active=!1)),this.active?this.dequeue():this.clearCurrent()):!this.paused&&this.active&&this.startAnimFrame(),this.lastPos=r,this):this},eachAt:function(){var t,e,i,n=this,r=this.target(),s=this.situation;for(t in s.animations)i=[].concat(s.animations[t]).map(function(t){return"string"!=typeof t&&t.at?t.at(s.ease(n.pos),n.pos):t}),r[t].apply(r,i);for(t in s.attrs)i=[t].concat(s.attrs[t]).map(function(t){return"string"!=typeof t&&t.at?t.at(s.ease(n.pos),n.pos):t}),r.attr.apply(r,i);for(t in s.styles)i=[t].concat(s.styles[t]).map(function(t){return"string"!=typeof t&&t.at?t.at(s.ease(n.pos),n.pos):t}),r.style.apply(r,i);if(s.transforms.length){for(i=s.initialTransformation,t=0,e=s.transforms.length;t=0;--e)this[k[e]]=null!=t[k[e]]?t[k[e]]:i[k[e]]},extend:{extract:function(){var t=c(this,0,1),e=c(this,1,0),i=180/Math.PI*Math.atan2(t.y,t.x)-90;return{x:this.e,y:this.f,transformedX:(this.e*Math.cos(i*Math.PI/180)+this.f*Math.sin(i*Math.PI/180))/Math.sqrt(this.a*this.a+this.b*this.b),transformedY:(this.f*Math.cos(i*Math.PI/180)+this.e*Math.sin(-i*Math.PI/180))/Math.sqrt(this.c*this.c+this.d*this.d),skewX:-i,skewY:180/Math.PI*Math.atan2(e.y,e.x),scaleX:Math.sqrt(this.a*this.a+this.b*this.b),scaleY:Math.sqrt(this.c*this.c+this.d*this.d),rotation:i,a:this.a,b:this.b,c:this.c,d:this.d,e:this.e,f:this.f,matrix:new b.Matrix(this)}},clone:function(){return new b.Matrix(this)},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){return new b.Matrix(this.native().multiply(d(t).native()))},inverse:function(){return new b.Matrix(this.native().inverse())},translate:function(t,e){return new b.Matrix(this.native().translate(t||0,e||0))},scale:function(t,e,i,n){return 1==arguments.length?e=t:3==arguments.length&&(n=i,i=e,e=t),this.around(i,n,new b.Matrix(t,0,0,e,0,0))},rotate:function(t,e,i){return t=b.utils.radians(t),this.around(e,i,new b.Matrix(Math.cos(t),Math.sin(t),-Math.sin(t),Math.cos(t),0,0))},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,null!=e?e:t)},skew:function(t,e,i,n){ +return 1==arguments.length?e=t:3==arguments.length&&(n=i,i=e,e=t),t=b.utils.radians(t),e=b.utils.radians(e),this.around(i,n,new b.Matrix(1,Math.tan(e),Math.tan(t),1,0,0))},skewX:function(t,e,i){return this.skew(t,0,e,i)},skewY:function(t,e,i){return this.skew(0,t,e,i)},around:function(t,e,i){return this.multiply(new b.Matrix(1,0,0,1,t||0,e||0)).multiply(i).multiply(new b.Matrix(1,0,0,1,-t||0,-e||0))},native:function(){for(var t=b.parser.native.createSVGMatrix(),e=k.length-1;e>=0;e--)t[k[e]]=this[k[e]];return t},toString:function(){return"matrix("+g(this.a)+","+g(this.b)+","+g(this.c)+","+g(this.d)+","+g(this.e)+","+g(this.f)+")"}},parent:b.Element,construct:{ctm:function(){return new b.Matrix(this.node.getCTM())},screenCTM:function(){if(this instanceof b.Nested){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){var i,n={x:0,y:0};i=Array.isArray(t)?{x:t[0],y:t[1]}:"object"==typeof t?{x:t.x,y:t.y}:null!=t?{x:t,y:null!=e?e:t}:n,this.x=i.x,this.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.native.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,i){if(null==t){for(t={},e=this.node.attributes,i=e.length-1;i>=0;i--)t[e[i].nodeName]=b.regex.isNumber.test(e[i].nodeValue)?parseFloat(e[i].nodeValue):e[i].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;"stroke-width"==t?this.attr("stroke",parseFloat(e)>0?this._stroke:null):"stroke"==t&&(this._stroke=e),"fill"!=t&&"stroke"!=t||(b.regex.isImage.test(e)&&(e=this.doc().defs().image(e,0,0)),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 i?this.node.setAttributeNS(i,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,{transform:function(t,e){var i,n,r=this;if("object"!=typeof t)return i=new b.Matrix(r).extract(),"string"==typeof t?i[t]:i;if(i=new b.Matrix(r),e=!!e||!!t.relative,null!=t.a)i=e?i.multiply(new b.Matrix(t)):new b.Matrix(t);else if(null!=t.rotation)p(t,r),i=e?i.rotate(t.rotation,t.cx,t.cy):i.rotate(t.rotation-i.extract().rotation,t.cx,t.cy);else if(null!=t.scale||null!=t.scaleX||null!=t.scaleY){if(p(t,r),t.scaleX=null!=t.scale?t.scale:null!=t.scaleX?t.scaleX:1,t.scaleY=null!=t.scale?t.scale:null!=t.scaleY?t.scaleY:1,!e){var s=i.extract();t.scaleX=1*t.scaleX/s.scaleX,t.scaleY=1*t.scaleY/s.scaleY}i=i.scale(t.scaleX,t.scaleY,t.cx,t.cy)}else if(null!=t.skew||null!=t.skewX||null!=t.skewY){if(p(t,r),t.skewX=null!=t.skew?t.skew:null!=t.skewX?t.skewX:0,t.skewY=null!=t.skew?t.skew:null!=t.skewY?t.skewY:0,!e){var s=i.extract();i=i.multiply((new b.Matrix).skew(s.skewX,s.skewY,t.cx,t.cy).inverse())}i=i.skew(t.skewX,t.skewY,t.cx,t.cy)}else t.flip?("x"==t.flip||"y"==t.flip?t.offset=null==t.offset?r.bbox()["c"+t.flip]:t.offset:null==t.offset?(n=r.bbox(),t.flip=n.cx,t.offset=n.cy):t.flip=t.offset,i=(new b.Matrix).flip(t.flip,t.offset)):null==t.x&&null==t.y||(e?i=i.translate(t.x,t.y):(null!=t.x&&(i.e=t.x),null!=t.y&&(i.f=t.y)));return this.attr("transform",i)}}),b.extend(b.FX,{transform:function(t,e){var i,n,r=this.target();return"object"!=typeof t?(i=new b.Matrix(r).extract(),"string"==typeof t?i[t]:i):(e=!!e||!!t.relative,null!=t.a?i=new b.Matrix(t):null!=t.rotation?(p(t,r),i=new b.Rotate(t.rotation,t.cx,t.cy)):null!=t.scale||null!=t.scaleX||null!=t.scaleY?(p(t,r),t.scaleX=null!=t.scale?t.scale:null!=t.scaleX?t.scaleX:1,t.scaleY=null!=t.scale?t.scale:null!=t.scaleY?t.scaleY:1,i=new b.Scale(t.scaleX,t.scaleY,t.cx,t.cy)):null!=t.skewX||null!=t.skewY?(p(t,r),t.skewX=null!=t.skewX?t.skewX:0,t.skewY=null!=t.skewY?t.skewY:0,i=new b.Skew(t.skewX,t.skewY,t.cx,t.cy)):t.flip?("x"==t.flip||"y"==t.flip?t.offset=null==t.offset?r.bbox()["c"+t.flip]:t.offset:null==t.offset?(n=r.bbox(),t.flip=n.cx,t.offset=n.cy):t.flip=t.offset,i=(new b.Matrix).flip(t.flip,t.offset)):null==t.x&&null==t.y||(i=new b.Translate(t.x,t.y)),i?(i.relative=e,this.last().transforms.push(i),this._callStart()):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)})]}).reduce(function(t,e){return"matrix"==e[0]?t.multiply(f(e[1])):t[e[0]].apply(t,e[1])},new b.Matrix)},toParent:function(t){if(this==t)return this;var e=this.screenCTM(),i=t.screenCTM().inverse();return this.addTo(t).untransform().transform(i.multiply(e)),this},toDoc:function(){return this.toParent(this.doc())}}),b.Transformation=b.invent({create:function(t,e){if(arguments.length>1&&"boolean"!=typeof e)return this.constructor.call(this,[].slice.call(arguments));if(Array.isArray(t))for(var i=0,n=this.arguments.length;i=0},index:function(t){return[].slice.call(this.node.childNodes).indexOf(t.node)},get:function(t){return b.adopt(this.node.childNodes[t])},first:function(){return this.get(0)},last:function(){return this.get(this.node.childNodes.length-1)},each:function(t,e){var i,n,r=this.children();for(i=0,n=r.length;in/r?this.height/r:this.width/n,this.x=e,this.y=i,this.width=n,this.height=r)}else t="string"==typeof t?t.match(c).map(function(t){return parseFloat(t)}):Array.isArray(t)?t:"object"==typeof t?[t.x,t.y,t.width,t.height]:4==arguments.length?[].slice.call(arguments):h,this.x=t[0],this.y=t[1],this.width=t[2],this.height=t[3]},extend:{toString:function(){return this.x+" "+this.y+" "+this.width+" "+this.height},morph:function(t,e,i,n){return this.destination=new b.ViewBox(t,e,i,n),this},at:function(t){return this.destination?new b.ViewBox([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.Container,construct:{viewbox:function(t,e,i,n){return 0==arguments.length?new b.ViewBox(this):this.attr("viewBox",new b.ViewBox(t,e,i,n))}}}),["click","dblclick","mousedown","mouseup","mouseover","mouseout","mousemove","mouseenter","mouseleave","touchstart","touchmove","touchleave","touchend","touchcancel"].forEach(function(t){b.Element.prototype[t]=function(e){return null==e?b.off(this,t):b.on(this,t,e),this}}),b.listenerId=0,b.on=function(t,e,i,n,r){var s=i.bind(n||t),o=t instanceof b.Element?t.node:t;o.instance=o.instance||{_events:{}};var a=o.instance._events;i._svgjsListenerId||(i._svgjsListenerId=++b.listenerId),e.split(b.regex.delimiter).forEach(function(t){var e=t.split(".")[0],n=t.split(".")[1]||"*";a[e]=a[e]||{},a[e][n]=a[e][n]||{},a[e][n][i._svgjsListenerId]=s,o.addEventListener(e,s,r||!1)})},b.off=function(t,e,i,n){var r=t instanceof b.Element?t.node:t;if(r.instance&&("function"!=typeof i||(i=i._svgjsListenerId))){var s=r.instance._events;(e||"").split(b.regex.delimiter).forEach(function(t){var e,o,a=t&&t.split(".")[0],h=t&&t.split(".")[1];if(i)s[a]&&s[a][h||"*"]&&(r.removeEventListener(a,s[a][h||"*"][i],n||!1),delete s[a][h||"*"][i]);else if(a&&h){if(s[a]&&s[a][h]){for(o in s[a][h])b.off(r,[a,h].join("."),o);delete s[a][h]}}else if(h)for(t in s)for(e in s[t])h===e&&b.off(r,[t,h].join("."));else if(a){if(s[a]){for(e in s[a])b.off(r,[a,e].join("."));delete s[a]}}else{for(t in s)b.off(r,t);r.instance._events={}}})}},b.extend(b.Element,{on:function(t,e,i,n){return b.on(this,t,e,i,n),this},off:function(t,e){return b.off(this.node,t,e),this},fire:function(e,i){return e instanceof t.Event?this.node.dispatchEvent(e):this.node.dispatchEvent(e=new b.CustomEvent(e,{detail:i,cancelable:!0})),this._event=e,this},event:function(){return this._event}}),b.Defs=b.invent({create:"defs",inherit:b.Container}),b.G=b.invent({create:"g",inherit:b.Container,extend:{x:function(t){return null==t?this.transform("x"):this.transform({x:t-this.x()},!0)},y:function(t){return null==t?this.transform("y"):this.transform({y:t-this.y()},!0)},cx:function(t){return null==t?this.gbox().cx:this.x(t-this.gbox().width/2)},cy:function(t){return null==t?this.gbox().cy:this.y(t-this.gbox().height/2)},gbox:function(){var t=this.bbox(),e=this.transform();return t.x+=e.x,t.x2+=e.x,t.cx+=e.x,t.y+=e.y,t.y2+=e.y,t.cy+=e.y,t}},construct:{group:function(){return this.put(new b.G)}}}),b.Doc=b.invent({create:function(t){t&&(t="string"==typeof t?e.getElementById(t):t,"svg"==t.nodeName?this.constructor.call(this,t):(this.constructor.call(this,b.create("svg")),t.appendChild(this.node),this.size("100%","100%")),this.namespace().defs())},inherit:b.Container,extend:{namespace:function(){return this.attr({xmlns:b.ns,version:"1.1"}).attr("xmlns:xlink",b.xlink,b.xmlns).attr("xmlns:svgjs",b.svgjs,b.xmlns)},defs:function(){if(!this._defs){var t;(t=this.node.getElementsByTagName("defs")[0])?this._defs=b.adopt(t):this._defs=new b.Defs,this.node.appendChild(this._defs.node)}return this._defs},parent:function(){return this.node.parentNode&&"#document"!=this.node.parentNode.nodeName&&"#document-fragment"!=this.node.parentNode.nodeName?this.node.parentNode:null},spof:function(){var t=this.node.getScreenCTM();return t&&this.style("left",-t.e%1+"px").style("top",-t.f%1+"px"),this},remove:function(){return this.parent()&&this.parent().removeChild(this.node),this},clear:function(){for(;this.node.hasChildNodes();)this.node.removeChild(this.node.lastChild);return delete this._defs,b.parser.draw.parentNode||this.node.appendChild(b.parser.draw),this},clone:function(t){this.writeDataToDom();var e=this.node,i=x(e.cloneNode(!0));return t?(t.node||t).appendChild(i.node):e.parentNode.insertBefore(i.node,e.nextSibling),i}}}),b.extend(b.Element,{siblings:function(){return this.parent().children()},position:function(){return this.parent().index(this)},next:function(){return this.siblings()[this.position()+1]},previous:function(){return this.siblings()[this.position()-1]},forward:function(){var t=this.position()+1,e=this.parent();return e.removeElement(this).add(this,t),e instanceof b.Doc&&e.node.appendChild(e.defs().node),this},backward:function(){var t=this.position();return t>0&&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:function(){this.constructor.call(this,b.create("mask")),this.targets=[]},inherit:b.Container,extend:{remove:function(){for(var t=this.targets.length-1;t>=0;t--)this.targets[t]&&this.targets[t].unmask();return this.targets=[],b.Element.prototype.remove.call(this),this}},construct:{mask:function(){return this.defs().put(new b.Mask)}}}),b.extend(b.Element,{maskWith:function(t){return this.masker=t instanceof b.Mask?t:this.parent().mask().add(t),this.masker.targets.push(this),this.attr("mask",'url("#'+this.masker.attr("id")+'")')},unmask:function(){return delete this.masker,this.attr("mask",null)}}),b.ClipPath=b.invent({create:function(){this.constructor.call(this,b.create("clipPath")),this.targets=[]},inherit:b.Container,extend:{remove:function(){for(var t=this.targets.length-1;t>=0;t--)this.targets[t]&&this.targets[t].unclip();return this.targets=[],this.parent().removeElement(this),this}},construct:{clip:function(){return this.defs().put(new b.ClipPath)}}}),b.extend(b.Element,{clipWith:function(t){return this.clipper=t instanceof b.ClipPath?t:this.parent().clip().add(t),this.clipper.targets.push(this),this.attr("clip-path",'url("#'+this.clipper.attr("id")+'")')},unclip:function(){return delete this.clipper,this.attr("clip-path",null)}}),b.Gradient=b.invent({create:function(t){this.constructor.call(this,b.create(t+"Gradient")),this.type=t},inherit:b.Container,extend:{at:function(t,e,i){return this.put(new b.Stop).update(t,e,i)},update:function(t){return this.clear(),"function"==typeof t&&t.call(this,this),this},fill:function(){return"url(#"+this.id()+")"},toString:function(){return this.fill()},attr:function(t,e,i){return"transform"==t&&(t="gradientTransform"),b.Container.prototype.attr.call(this,t,e,i)}},construct:{gradient:function(t,e){return this.defs().gradient(t,e)}}}),b.extend(b.Gradient,b.FX,{from:function(t,e){return"radial"==(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"radial"==(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:{fill:function(){return"url(#"+this.id()+")"},update:function(t){return this.clear(),"function"==typeof t&&t.call(this,this),this},toString:function(){return this.fill()},attr:function(t,e,i){return"transform"==t&&(t="patternTransform"),b.Container.prototype.attr.call(this,t,e,i)}},construct:{pattern:function(t,e,i){return this.defs().pattern(t,e,i)}}}),b.extend(b.Defs,{pattern:function(t,e,i){return this.put(new b.Pattern).update(i).attr({x:0,y:0,width:t,height:e,patternUnits:"userSpaceOnUse"})}}),b.Shape=b.invent({create:function(t){this.constructor.call(this,t)},inherit:b.Element}),b.Bare=b.invent({create:function(t,e){if(this.constructor.call(this,b.create(t)),e)for(var i in e.prototype)"function"==typeof e.prototype[i]&&(this[i]=e.prototype[i])},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.FX,{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.FX,{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 i=l(this,t,e);return this.rx(new b.Number(i.width).divide(2)).ry(new b.Number(i.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,i,n){return null==t?this.array():(t=void 0!==e?{x1:t,y1:e,x2:i,y2:n}: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 i=l(this,t,e);return this.attr(this.array().size(i.width,i.height).toLine())}},construct:{line:function(t,e,i,n){return b.Line.prototype.plot.apply(this.put(new b.Line),null!=t?[t,e,i,n]:[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 i=l(this,t,e);return this.attr("points",this.array().size(i.width,i.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 i=l(this,t,e);return this.attr("d",this.array().size(i.width,i.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){if(!e)return this;var i=this,n=new t.Image;return b.on(n,"load",function(){b.off(n);var t=i.parent(b.Pattern);null!==t&&(0==i.width()&&0==i.height()&&i.size(n.width,n.height),t&&0==t.width()&&0==t.height()&&t.size(i.width(),i.height()),"function"==typeof i._loaded&&i._loaded.call(i,{width:n.width,height:n.height,ratio:n.width/n.height,url:e}))}),b.on(n,"error",function(t){b.off(n),"function"==typeof i._error&&i._error.call(i,t)}),this.attr("href",n.src=this.src=e,b.xlink)},loaded:function(t){return this._loaded=t,this},error:function(t){return this._error=t,this}},construct:{image:function(t,e,i){return this.put(new b.Image).load(t).size(e||0,i||e||0)}}}),b.Text=b.invent({create:function(){this.constructor.call(this,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.Shape,extend:{x:function(t){return null==t?this.attr("x"):this.attr("x",t)},y:function(t){var e=this.attr("y"),i="number"==typeof e?e-this.bbox().y:0;return null==t?"number"==typeof e?e-i:e:this.attr("y","number"==typeof t.valueOf()?t+i: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){for(var t="",e=this.node.childNodes,i=0,n=e.length;i=0;e--)null!=i[M[t][e]]&&this.attr(M.prefix(t,M[t][e]),i[M[t][e]]);return this},b.extend(b.Element,b.FX,i)}),b.extend(b.Element,b.FX,{rotate:function(t,e,i){return this.transform({rotation:t,cx:e,cy:i})},skew:function(t,e,i,n){return 1==arguments.length||3==arguments.length?this.transform({skew:t,cx:e,cy:i}):this.transform({skewX:t,skewY:e,cx:i,cy:n})},scale:function(t,e,i,n){return 1==arguments.length||3==arguments.length?this.transform({scale:t,cx:e,cy:i}):this.transform({scaleX:t,scaleY:e,cx:i,cy:n})},translate:function(t,e){return this.transform({x:t,y:e})},flip:function(t,e){return e="number"==typeof t?t:e,this.transform({flip:t||"both",offset:e})},matrix:function(t){return this.attr("transform",new b.Matrix(6==arguments.length?[].slice.call(arguments):t))},opacity:function(t){return this.attr("opacity",t)},dx:function(t){return this.x(new b.Number(t).plus(this instanceof b.FX?0:this.x()),!0)},dy:function(t){return this.y(new b.Number(t).plus(this instanceof b.FX?0:this.y()),!0)},dmove:function(t,e){return this.dx(t).dy(e)}}),b.extend(b.Rect,b.Ellipse,b.Circle,b.Gradient,b.FX,{radius:function(t,e){var i=(this._target||this).type;return"radial"==i||"circle"==i?this.attr("r",new b.Number(t)):this.rx(t).ry(null==e?t:e)}}),b.extend(b.Path,{length:function(){return this.node.getTotalLength()},pointAt:function(t){return this.node.getPointAtLength(t)}}),b.extend(b.Parent,b.Text,b.Tspan,b.FX,{font:function(t,e){if("object"==typeof t)for(e in t)this.font(e,t[e]);return"leading"==t?this.leading(e):"anchor"==t?this.attr("text-anchor",e):"size"==t||"family"==t||"weight"==t||"stretch"==t||"variant"==t||"style"==t?this.attr("font-"+t,e):this.attr(t,e)}}),b.Set=b.invent({create:function(t){t instanceof b.Set?this.members=t.members.slice():Array.isArray(t)?this.members=t:this.clear()},extend:{add:function(){var t,e,i=[].slice.call(arguments);for(t=0,e=i.length;t-1&&this.members.splice(e,1),this},each:function(t){for(var e=0,i=this.members.length;e=0},index:function(t){return this.members.indexOf(t)},get:function(t){return this.members[t]},first:function(){return this.get(0)},last:function(){return this.get(this.members.length-1)},valueOf:function(){return this.members},bbox:function(){if(0==this.members.length)return new b.RBox;var t=this.members[0].rbox(this.members[0].doc());return this.each(function(){t=t.merge(this.rbox(this.doc()))}),t}},construct:{set:function(t){ +return new b.Set(t)}}}),b.FX.Set=b.invent({create:function(t){this.set=t}}),b.Set.inherit=function(){var t,e=[];for(var t in b.Shape.prototype)"function"==typeof b.Shape.prototype[t]&&"function"!=typeof b.Set.prototype[t]&&e.push(t);e.forEach(function(t){b.Set.prototype[t]=function(){for(var e=0,i=this.members.length;e=0;t--)delete this.memory()[arguments[t]];return this},memory:function(){return this._memory||(this._memory={})}}),b.get=function(t){var i=e.getElementById(v(t)||t);return b.adopt(i)},b.select=function(t,i){return new b.Set(b.utils.map((i||e).querySelectorAll(t),function(t){return b.adopt(t)}))},b.extend(b.Parent,{select:function(t){return b.select(t,this.node)}});var k="abcdef".split("");if("function"!=typeof t.CustomEvent){var S=function(t,i){i=i||{bubbles:!1,cancelable:!1,detail:void 0};var n=e.createEvent("CustomEvent");return n.initCustomEvent(t,i.bubbles,i.cancelable,i.detail),n};S.prototype=t.Event.prototype,b.CustomEvent=S}else b.CustomEvent=t.CustomEvent;return function(e){for(var i=0,n=["moz","webkit"],r=0;r= this.resizeLimits.width) { + if (checkAspectRatio) { + snap = this.checkAspectRatio(snap, checkAspectRatioReverse); + } + + if (this.parameters.type === "text") { + if (resizeFont) { + this.el.move(this.parameters.box.x + snap[0], this.parameters.box.y); + this.el.attr("font-size", this.parameters.fontSize - snap[0]); + } + return; + } + + this.el.width(this.parameters.box.width - snap[0]); + + if (updateOnlyChanges) { + this.el.x(this.parameters.box.x + snap[0]); + } else { + this.el.move(this.parameters.box.x + snap[0], this.parameters.box.y); + } + } + }; + + this._resizeRight = function (snap, resizeFont, checkAspectRatio, checkAspectRatioReverse) { + if (this.parameters.box.width + snap[0] >= this.resizeLimits.width) { + if (checkAspectRatio) { + snap = this.checkAspectRatio(snap, checkAspectRatioReverse); + } + + if (this.parameters.type === "text") { + if (resizeFont) { + this.el.move(this.parameters.box.x - snap[0], this.parameters.box.y); + this.el.attr("font-size", this.parameters.fontSize + snap[0]); + } + return; + } + + this.el.x(this.parameters.box.x).width(this.parameters.box.width + snap[0]); + } + }; + + this._resizeTop = function (snap, checkAspectRatio, checkAspectRatioReverse, updateOnlyChanges) { + if (this.parameters.box.height - snap[1] >= this.resizeLimits.height) { + if (checkAspectRatio) { + snap = this.checkAspectRatio(snap, checkAspectRatioReverse); + } + + // Disable the font-resizing if it is not from the corner of bounding-box + if (this.parameters.type === "text") { + return; + } + + this.el.height(this.parameters.box.height - snap[1]); + + if (updateOnlyChanges) { + this.el.y(this.parameters.box.y + snap[1]) + } else { + this.el.move(this.parameters.box.x, this.parameters.box.y + snap[1]); + } + } + }; + + this._resizeBottom = function (snap, checkAspectRatio, checkAspectRatioReverse) { + if (this.parameters.box.height + snap[1] >= this.resizeLimits.height) { + if (checkAspectRatio) { + snap = this.checkAspectRatio(snap, checkAspectRatioReverse); + } + + if (this.parameters.type === "text") { + return; + } + + this.el.y(this.parameters.box.y).height(this.parameters.box.height + snap[1]); + } + }; + + // Lets check which edge of the bounding-box was clicked and resize the this.el according to this + switch (event.type) { + + // Left-Top-Edge + case 'lt': + // We build a calculating function for every case which gives us the new position of the this.el + this.calc = function (diffX, diffY) { + // The procedure is always the same + // First we snap the edge to the given grid (snapping to 1px grid is normal resizing) + var snap = this.snapToGrid(diffX, diffY); + + this._resizeTop(snap, true, false, true); + this._resizeLeft(snap, true, true, false, true); + }; + break; + + // Right-Top + case 'rt': + // s.a. + this.calc = function (diffX, diffY) { + var snap = this.snapToGrid(diffX, diffY, 1 << 1); + + this._resizeTop(snap, true, true, true); + this._resizeRight(snap, true, true, true); + }; + break; + + // Right-Bottom + case 'rb': + // s.a. + this.calc = function (diffX, diffY) { + var snap = this.snapToGrid(diffX, diffY, 0); + + this._resizeBottom(snap, true); + this._resizeRight(snap, true, true); + }; + break; + + // Left-Bottom + case 'lb': + // s.a. + this.calc = function (diffX, diffY) { + var snap = this.snapToGrid(diffX, diffY, 1); + + this._resizeBottom(snap, true, true); + this._resizeLeft(snap, true, true, true, true); + }; + break; + + // Top + case 't': + // s.a. + this.calc = function (diffX, diffY) { + var snap = this.snapToGrid(diffX, diffY, 1 << 1); + + this._resizeTop(snap); + }; + break; + + // Right + case 'r': + // s.a. + this.calc = function (diffX, diffY) { + var snap = this.snapToGrid(diffX, diffY, 0); + + this._resizeRight(snap); + }; + break; + + // Bottom + case 'b': + // s.a. + this.calc = function (diffX, diffY) { + var snap = this.snapToGrid(diffX, diffY, 0); + + this._resizeBottom(snap); + }; + break; + + // Left + case 'l': + // s.a. + this.calc = function (diffX, diffY) { + var snap = this.snapToGrid(diffX, diffY, 1); + + this._resizeLeft(snap); + }; + break; + + // Rotation + case 'rot': + // s.a. + this.calc = function (diffX, diffY) { + + // yes this is kinda stupid but we need the mouse coords back... + var current = {x: diffX + this.parameters.p.x, y: diffY + this.parameters.p.y}; + + // start minus middle + var sAngle = Math.atan2((this.parameters.p.y - this.parameters.box.y - this.parameters.box.height / 2), (this.parameters.p.x - this.parameters.box.x - this.parameters.box.width / 2)); + + // end minus middle + var pAngle = Math.atan2((current.y - this.parameters.box.y - this.parameters.box.height / 2), (current.x - this.parameters.box.x - this.parameters.box.width / 2)); + + var angle = this.parameters.rotation + (pAngle - sAngle) * 180 / Math.PI + this.options.snapToAngle / 2; + + // We have to move the element to the center of the box first and change the rotation afterwards + // because rotation always works around a rotation-center, which is changed when moving the element + // We also set the new rotation center to the center of the box. + this.el.center(this.parameters.box.cx, this.parameters.box.cy).rotate(angle - (angle % this.options.snapToAngle), this.parameters.box.cx, this.parameters.box.cy); + }; + break; + + // Moving one single Point (needed when an element is deepSelected which means you can move every single point of the object) + case 'point': + this.calc = function (diffX, diffY) { + + // Snapping the point to the grid + var snap = this.snapToGrid(diffX, diffY, this.parameters.pointCoords[0], this.parameters.pointCoords[1]); + + // Get the point array + var array = this.el.array().valueOf(); + + // Changing the moved point in the array + array[this.parameters.i][0] = this.parameters.pointCoords[0] + snap[0]; + array[this.parameters.i][1] = this.parameters.pointCoords[1] + snap[1]; + + // And plot the new this.el + this.el.plot(array); + }; + } + + this.el.fire('resizestart', {dx: this.parameters.x, dy: this.parameters.y, event: event}); + // When resizing started, we have to register events for... + // Touches. + SVG.on(window, 'touchmove.resize', function(e) { + _this.update(e || window.event); + }); + SVG.on(window, 'touchend.resize', function() { + _this.done(); + }); + // Mouse. + SVG.on(window, 'mousemove.resize', function (e) { + _this.update(e || window.event); + }); + SVG.on(window, 'mouseup.resize', function () { + _this.done(); + }); + + }; + + // The update-function redraws the element every time the mouse is moving + ResizeHandler.prototype.update = function (event) { + + if (!event) { + if (this.lastUpdateCall) { + this.calc(this.lastUpdateCall[0], this.lastUpdateCall[1]); + } + return; + } + + // Calculate the difference between the mouseposition at start and now + var txPt = this._extractPosition(event); + var p = this.transformPoint(txPt.x, txPt.y); + + var diffX = p.x - this.parameters.p.x, + diffY = p.y - this.parameters.p.y; + + this.lastUpdateCall = [diffX, diffY]; + + // Calculate the new position and height / width of the element + this.calc(diffX, diffY); + + // Emit an event to say we have changed. + this.el.fire('resizing', {dx: diffX, dy: diffY, event: event}); + }; + + // Is called on mouseup. + // Removes the update-function from the mousemove event + ResizeHandler.prototype.done = function () { + this.lastUpdateCall = null; + SVG.off(window, 'mousemove.resize'); + SVG.off(window, 'mouseup.resize'); + SVG.off(window, 'touchmove.resize'); + SVG.off(window, 'touchend.resize'); + this.el.fire('resizedone'); + }; + + // The flag is used to determine whether the resizing is used with a left-Point (first bit) and top-point (second bit) + // In this cases the temp-values are calculated differently + ResizeHandler.prototype.snapToGrid = function (diffX, diffY, flag, pointCoordsY) { + + var temp; + + // If `pointCoordsY` is given, a single Point has to be snapped (deepSelect). That's why we need a different temp-value + if (typeof pointCoordsY !== 'undefined') { + // Note that flag = pointCoordsX in this case + temp = [(flag + diffX) % this.options.snapToGrid, (pointCoordsY + diffY) % this.options.snapToGrid]; + } else { + // We check if the flag is set and if not we set a default-value (both bits set - which means upper-left-edge) + flag = flag == null ? 1 | 1 << 1 : flag; + temp = [(this.parameters.box.x + diffX + (flag & 1 ? 0 : this.parameters.box.width)) % this.options.snapToGrid, (this.parameters.box.y + diffY + (flag & (1 << 1) ? 0 : this.parameters.box.height)) % this.options.snapToGrid]; + } + + if(diffX < 0) { + temp[0] -= this.options.snapToGrid; + } + if(diffY < 0) { + temp[1] -= this.options.snapToGrid; + } + + diffX -= (Math.abs(temp[0]) < this.options.snapToGrid / 2 ? + temp[0] : + temp[0] - (diffX < 0 ? -this.options.snapToGrid : this.options.snapToGrid)); + diffY -= (Math.abs(temp[1]) < this.options.snapToGrid / 2 ? + temp[1] : + temp[1] - (diffY < 0 ? -this.options.snapToGrid : this.options.snapToGrid)); + + return this.constraintToBox(diffX, diffY, flag, pointCoordsY); + + }; + + // keep element within constrained box + ResizeHandler.prototype.constraintToBox = function (diffX, diffY, flag, pointCoordsY) { + //return [diffX, diffY] + var c = this.options.constraint || {}; + var orgX, orgY; + + if (typeof pointCoordsY !== 'undefined') { + orgX = flag; + orgY = pointCoordsY; + } else { + orgX = this.parameters.box.x + (flag & 1 ? 0 : this.parameters.box.width); + orgY = this.parameters.box.y + (flag & (1<<1) ? 0 : this.parameters.box.height); + } + + if (typeof c.minX !== 'undefined' && orgX + diffX < c.minX) { + diffX = c.minX - orgX; + } + + if (typeof c.maxX !== 'undefined' && orgX + diffX > c.maxX) { + diffX = c.maxX - orgX; + } + + if (typeof c.minY !== 'undefined' && orgY + diffY < c.minY) { + diffY = c.minY - orgY; + } + + if (typeof c.maxY !== 'undefined' && orgY + diffY > c.maxY) { + diffY = c.maxY - orgY; + } + + return [diffX, diffY]; + }; + + ResizeHandler.prototype.checkAspectRatio = function (snap, isReverse) { + if (!this.options.saveAspectRatio) { + return snap; + } + + var updatedSnap = snap.slice(); + var aspectRatio = this.parameters.box.width / this.parameters.box.height; + var newW = this.parameters.box.width + snap[0]; + var newH = this.parameters.box.height - snap[1]; + var newAspectRatio = newW / newH; + + if (newAspectRatio < aspectRatio) { + // Height is too big. Adapt it + updatedSnap[1] = newW / aspectRatio - this.parameters.box.height; + isReverse && (updatedSnap[1] = -updatedSnap[1]); + } else if (newAspectRatio > aspectRatio) { + // Width is too big. Adapt it + updatedSnap[0] = this.parameters.box.width - newH * aspectRatio; + isReverse && (updatedSnap[0] = -updatedSnap[0]); + } + + return updatedSnap; + }; + + SVG.extend(SVG.Element, { + // Resize element with mouse + resize: function (options) { + + (this.remember('_resizeHandler') || new ResizeHandler(this)).init(options || {}); + + return this; + + } + + }); + + SVG.Element.prototype.resize.defaults = { + snapToAngle: 0.1, // Specifies the speed the rotation is happening when moving the mouse + snapToGrid: 1, // Snaps to a grid of `snapToGrid` Pixels + constraint: {}, // keep element within constrained box + resizeLimits: { width: 0, height: 0 }, // rect limit size on resize + saveAspectRatio: false // Save aspect ratio when resizing using lt, rt, rb or lb points + }; + +}).call(this); +}()); diff --git a/hal-core/resources/web/js/lib/svg.resize.min.js b/hal-core/resources/web/js/lib/svg.resize.min.js new file mode 100644 index 00000000..aef5e88a --- /dev/null +++ b/hal-core/resources/web/js/lib/svg.resize.min.js @@ -0,0 +1 @@ +/*! svg.resize.js v1.4.3 MIT*/;!function(){"use strict";(function(){function t(t){t.remember("_resizeHandler",this),this.el=t,this.parameters={},this.lastUpdateCall=null,this.p=t.doc().node.createSVGPoint()}t.prototype.transformPoint=function(t,e,i){return this.p.x=t-(this.offset.x-window.pageXOffset),this.p.y=e-(this.offset.y-window.pageYOffset),this.p.matrixTransform(i||this.m)},t.prototype._extractPosition=function(t){return{x:null!=t.clientX?t.clientX:t.touches[0].clientX,y:null!=t.clientY?t.clientY:t.touches[0].clientY}},t.prototype.init=function(t){var e=this;if(this.stop(),"stop"!==t){this.options={};for(var i in this.el.resize.defaults)this.options[i]=this.el.resize.defaults[i],void 0!==t[i]&&(this.options[i]=t[i]);this.el.on("lt.resize",function(t){e.resize(t||window.event)}),this.el.on("rt.resize",function(t){e.resize(t||window.event)}),this.el.on("rb.resize",function(t){e.resize(t||window.event)}),this.el.on("lb.resize",function(t){e.resize(t||window.event)}),this.el.on("t.resize",function(t){e.resize(t||window.event)}),this.el.on("r.resize",function(t){e.resize(t||window.event)}),this.el.on("b.resize",function(t){e.resize(t||window.event)}),this.el.on("l.resize",function(t){e.resize(t||window.event)}),this.el.on("rot.resize",function(t){e.resize(t||window.event)}),this.el.on("point.resize",function(t){e.resize(t||window.event)}),this.update()}},t.prototype.stop=function(){return this.el.off("lt.resize"),this.el.off("rt.resize"),this.el.off("rb.resize"),this.el.off("lb.resize"),this.el.off("t.resize"),this.el.off("r.resize"),this.el.off("b.resize"),this.el.off("l.resize"),this.el.off("rot.resize"),this.el.off("point.resize"),this},t.prototype.resize=function(t){var e=this;this.m=this.el.node.getScreenCTM().inverse(),this.offset={x:window.pageXOffset,y:window.pageYOffset};var i=this._extractPosition(t.detail.event);if(this.parameters={type:this.el.type,p:this.transformPoint(i.x,i.y),x:t.detail.x,y:t.detail.y,box:this.el.bbox(),rotation:this.el.transform().rotation},this.resizeLimits=this.options.resizeLimits||this.resize.defaults.resizeLimits,"text"===this.el.type&&(this.parameters.fontSize=this.el.attr()["font-size"]),void 0!==t.detail.i){var s=this.el.array().valueOf();this.parameters.i=t.detail.i,this.parameters.pointCoords=[s[t.detail.i][0],s[t.detail.i][1]]}switch(this._resizeLeft=function(t,e,i,s,r){if(this.parameters.box.width-t[0]>=this.resizeLimits.width){if(i&&(t=this.checkAspectRatio(t,s)),"text"===this.parameters.type)return void(e&&(this.el.move(this.parameters.box.x+t[0],this.parameters.box.y),this.el.attr("font-size",this.parameters.fontSize-t[0])));this.el.width(this.parameters.box.width-t[0]),r?this.el.x(this.parameters.box.x+t[0]):this.el.move(this.parameters.box.x+t[0],this.parameters.box.y)}},this._resizeRight=function(t,e,i,s){if(this.parameters.box.width+t[0]>=this.resizeLimits.width){if(i&&(t=this.checkAspectRatio(t,s)),"text"===this.parameters.type)return void(e&&(this.el.move(this.parameters.box.x-t[0],this.parameters.box.y),this.el.attr("font-size",this.parameters.fontSize+t[0])));this.el.x(this.parameters.box.x).width(this.parameters.box.width+t[0])}},this._resizeTop=function(t,e,i,s){if(this.parameters.box.height-t[1]>=this.resizeLimits.height){if(e&&(t=this.checkAspectRatio(t,i)),"text"===this.parameters.type)return;this.el.height(this.parameters.box.height-t[1]),s?this.el.y(this.parameters.box.y+t[1]):this.el.move(this.parameters.box.x,this.parameters.box.y+t[1])}},this._resizeBottom=function(t,e,i){if(this.parameters.box.height+t[1]>=this.resizeLimits.height){if(e&&(t=this.checkAspectRatio(t,i)),"text"===this.parameters.type)return;this.el.y(this.parameters.box.y).height(this.parameters.box.height+t[1])}},t.type){case"lt":this.calc=function(t,e){var i=this.snapToGrid(t,e);this._resizeTop(i,!0,!1,!0),this._resizeLeft(i,!0,!0,!1,!0)};break;case"rt":this.calc=function(t,e){var i=this.snapToGrid(t,e,2);this._resizeTop(i,!0,!0,!0),this._resizeRight(i,!0,!0,!0)};break;case"rb":this.calc=function(t,e){var i=this.snapToGrid(t,e,0);this._resizeBottom(i,!0),this._resizeRight(i,!0,!0)};break;case"lb":this.calc=function(t,e){var i=this.snapToGrid(t,e,1);this._resizeBottom(i,!0,!0),this._resizeLeft(i,!0,!0,!0,!0)};break;case"t":this.calc=function(t,e){var i=this.snapToGrid(t,e,2);this._resizeTop(i)};break;case"r":this.calc=function(t,e){var i=this.snapToGrid(t,e,0);this._resizeRight(i)};break;case"b":this.calc=function(t,e){var i=this.snapToGrid(t,e,0);this._resizeBottom(i)};break;case"l":this.calc=function(t,e){var i=this.snapToGrid(t,e,1);this._resizeLeft(i)};break;case"rot":this.calc=function(t,e){var i={x:t+this.parameters.p.x,y:e+this.parameters.p.y},s=Math.atan2(this.parameters.p.y-this.parameters.box.y-this.parameters.box.height/2,this.parameters.p.x-this.parameters.box.x-this.parameters.box.width/2),r=Math.atan2(i.y-this.parameters.box.y-this.parameters.box.height/2,i.x-this.parameters.box.x-this.parameters.box.width/2),o=this.parameters.rotation+180*(r-s)/Math.PI+this.options.snapToAngle/2;this.el.center(this.parameters.box.cx,this.parameters.box.cy).rotate(o-o%this.options.snapToAngle,this.parameters.box.cx,this.parameters.box.cy)};break;case"point":this.calc=function(t,e){var i=this.snapToGrid(t,e,this.parameters.pointCoords[0],this.parameters.pointCoords[1]),s=this.el.array().valueOf();s[this.parameters.i][0]=this.parameters.pointCoords[0]+i[0],s[this.parameters.i][1]=this.parameters.pointCoords[1]+i[1],this.el.plot(s)}}this.el.fire("resizestart",{dx:this.parameters.x,dy:this.parameters.y,event:t}),SVG.on(window,"touchmove.resize",function(t){e.update(t||window.event)}),SVG.on(window,"touchend.resize",function(){e.done()}),SVG.on(window,"mousemove.resize",function(t){e.update(t||window.event)}),SVG.on(window,"mouseup.resize",function(){e.done()})},t.prototype.update=function(t){if(!t)return void(this.lastUpdateCall&&this.calc(this.lastUpdateCall[0],this.lastUpdateCall[1]));var e=this._extractPosition(t),i=this.transformPoint(e.x,e.y),s=i.x-this.parameters.p.x,r=i.y-this.parameters.p.y;this.lastUpdateCall=[s,r],this.calc(s,r),this.el.fire("resizing",{dx:s,dy:r,event:t})},t.prototype.done=function(){this.lastUpdateCall=null,SVG.off(window,"mousemove.resize"),SVG.off(window,"mouseup.resize"),SVG.off(window,"touchmove.resize"),SVG.off(window,"touchend.resize"),this.el.fire("resizedone")},t.prototype.snapToGrid=function(t,e,i,s){var r;return void 0!==s?r=[(i+t)%this.options.snapToGrid,(s+e)%this.options.snapToGrid]:(i=null==i?3:i,r=[(this.parameters.box.x+t+(1&i?0:this.parameters.box.width))%this.options.snapToGrid,(this.parameters.box.y+e+(2&i?0:this.parameters.box.height))%this.options.snapToGrid]),t<0&&(r[0]-=this.options.snapToGrid),e<0&&(r[1]-=this.options.snapToGrid),t-=Math.abs(r[0])a.maxX&&(t=a.maxX-r),void 0!==a.minY&&o+ea.maxY&&(e=a.maxY-o),[t,e]},t.prototype.checkAspectRatio=function(t,e){if(!this.options.saveAspectRatio)return t;var i=t.slice(),s=this.parameters.box.width/this.parameters.box.height,r=this.parameters.box.width+t[0],o=this.parameters.box.height-t[1],a=r/o;return as&&(i[0]=this.parameters.box.width-o*s,e&&(i[0]=-i[0])),i},SVG.extend(SVG.Element,{resize:function(e){return(this.remember("_resizeHandler")||new t(this)).init(e||{}),this}}),SVG.Element.prototype.resize.defaults={snapToAngle:.1,snapToGrid:1,constraint:{},resizeLimits:{width:0,height:0},saveAspectRatio:!1}}).call(this)}(); \ No newline at end of file diff --git a/hal-core/resources/web/js/lib/svg.select.js b/hal-core/resources/web/js/lib/svg.select.js index 9786f1a4..cfd8acb5 100644 --- a/hal-core/resources/web/js/lib/svg.select.js +++ b/hal-core/resources/web/js/lib/svg.select.js @@ -422,4 +422,4 @@ SVG.Element.prototype.selectize.defaults = { pointFill: "#000", // Point fill color pointStroke: { width: 1, color: "#000" } // Point stroke properties }; -}()); +}()); \ No newline at end of file diff --git a/hal-core/resources/web/js/map.js b/hal-core/resources/web/js/map.js index 8296e77d..49e704a1 100644 --- a/hal-core/resources/web/js/map.js +++ b/hal-core/resources/web/js/map.js @@ -13,7 +13,7 @@ $(function(){ // Setup map // ------------------------------------------ - svg = SVG("#map").size("100%", "700").viewbox(0, 0, 1000, 700); + svg = SVG('map'); // Initialize events @@ -23,11 +23,11 @@ $(function(){ $("#button-save").click(function() { saveMap(); editMode(false); - drawMap(); + fetchData(drawMap); }); $("#button-cancel").click(function() { editMode(false); - drawMap(); + fetchData(drawMap); }); // Initialize background image uploader @@ -92,14 +92,23 @@ function editMode(enable){ editModeEnabled = enable; if (editModeEnabled) { - $(".edit-mode").show(); - $(".view-mode").hide(); - $("#map").css("border-color", "#6eb16e"); + $('.edit-mode').show(); + $('.view-mode').hide(); + $('#map').css('border-color', '#6eb16e'); + + svg.select('.draggable').draggable(true); + //svg.select('.resizable').on('click', selectEvent, false); + svg.select('.resizable').selectize({ + points: ['rt', 'lb', 'rb'], // Add selection points on the corners + rotationPoint: false + }).resize() } else { - $(".room").selectize(false) - $(".edit-mode").hide(); - $(".view-mode").show(); - $("#map").css("border-color", ""); + $('.edit-mode').hide(); + $('.view-mode').show(); + $('#map').css('border-color', ''); + + svg.select('.draggable').draggable(false); + svg.select('resizable').selectize(false); } } @@ -109,9 +118,12 @@ function beforeDragEvent(e) { } } -function selectEntity(e) { - if (editModeEnabled == false) { - e.target.selectize(); +function selectEvent(e) { + if (editModeEnabled == true) { + e.target.selectize({ + points: ['rt', 'lb', 'rb'], // Add selection points on the corners + rotationPoint: false + }).resize(); } } @@ -121,11 +133,11 @@ function selectEntity(e) { function drawMap() { // Reset map - //svg.clear(); + svg.clear(); // Background - if (svg.find(".bg-image").length <= 0) { + if (svg.select(".bg-image").length() <= 0) { var bgImage = svg.image("?bgimage").addClass("bg-image") .x(0) .y(0) @@ -137,21 +149,25 @@ function drawMap() { if (data.rooms != null) { $.each(data.rooms, function(i, room) { - svg.find("#room-" + room.id).remove(); + svg.select("#room-" + room.id).remove(); var group = svg.group(); group.text(room.name).move(5, 5).fill('#999'); - group.rect(room.map.width, room.map.height).selectize(); + var rect = group.rect(room.map.width, room.map.height); + rect.fill('none').stroke({ + color: '#000', + opacity: 0.6, + width: 3 + }); + rect.addClass("resizable"); group.addClass("room") .attr("id", "room-" + room.id) .attr("room-id", room.id) .x(room.map.x) - .y(room.map.y); - - group.draggable().on('beforedrag', beforeDragEvent); - //group.on('mousedown', selectEntity, false); + .y(room.map.y) + .addClass("draggable"); }); } @@ -159,7 +175,7 @@ function drawMap() { if (data.sensors != null) { $.each(data.sensors, function(i, sensor) { - svg.find("#sensor-" + sensor.id).remove(); + svg.select("#sensor-" + sensor.id).remove(); var group = svg.group(); group.element('title').words(sensor.name); @@ -171,8 +187,8 @@ function drawMap() { .attr("id", "sensor-" + sensor.id) .attr("sensor-id", sensor.id) .x(sensor.map.x) - .y(sensor.map.y); - group.draggable().on('beforedrag', beforeDragEvent); + .y(sensor.map.y) + .addClass("draggable"); }); } @@ -180,7 +196,7 @@ function drawMap() { if (data.events != null) { $.each(data.events, function(i, event) { - svg.find("#event-" + event.id).remove(); + svg.select("#event-" + event.id).remove(); var group = svg.group(); group.element('title').words(event.name); @@ -194,8 +210,8 @@ function drawMap() { .attr("id", "event-" + event.id) .attr("event-id", event.id) .x(event.map.x) - .y(event.map.y); - group.draggable().on('beforedrag', beforeDragEvent); + .y(event.map.y) + .addClass("draggable"); }); } } @@ -227,27 +243,35 @@ async function fetchData(callback) { } function saveMap(){ - svg.find(".room").each(function(){ + svg.select(".room").each(function(){ saveDevice(this, "room", "room-id"); }); - svg.find(".sensor").each(function(){ + svg.select(".sensor").each(function(){ saveDevice(this, "sensor", "sensor-id"); }); - svg.find(".event").each(function(){ + svg.select(".event").each(function(){ saveDevice(this, "event", "event-id"); }); } -function saveDevice(element, type, id){ +function saveDevice(element, type, id) { + var data = { + action: "save", + id: element.attr(id), + type: type, + x: element.x(), + y: element.y() + }; + + var resizable = element.select(".resizable"); + if (resizable.length() > 0) { + data.width = resizable.get(0).width(); + data.height = resizable.get(0).height(); + } + $.ajax({ async: false, dataType: "json", url: "/api/map?", - data: { - action: "save", - id: element.attr(id), - type: type, - x: element.x(), - y: element.y() - }, + data: data }); } diff --git a/hal-core/resources/web/map.tmpl b/hal-core/resources/web/map.tmpl index 40435388..a8762d44 100644 --- a/hal-core/resources/web/map.tmpl +++ b/hal-core/resources/web/map.tmpl @@ -13,7 +13,7 @@
- +
@@ -43,11 +43,12 @@
- + + diff --git a/hal-core/src/se/hal/page/api/MapApiEndpoint.java b/hal-core/src/se/hal/page/api/MapApiEndpoint.java index b878d01c..d94ecee9 100644 --- a/hal-core/src/se/hal/page/api/MapApiEndpoint.java +++ b/hal-core/src/se/hal/page/api/MapApiEndpoint.java @@ -4,6 +4,7 @@ import se.hal.HalContext; import se.hal.intf.HalAbstractDevice; import se.hal.intf.HalApiEndpoint; import se.hal.struct.Event; +import se.hal.struct.Room; import se.hal.struct.Sensor; import zutil.db.DBConnection; import zutil.log.LogUtil; @@ -35,20 +36,38 @@ public class MapApiEndpoint extends HalApiEndpoint { if ("save".equals(request.get("action"))) { int id = Integer.parseInt(request.get("id")); - HalAbstractDevice device = null; - logger.info("Saving Sensor coordinates."); + logger.info("Saving map coordinates."); - if ("sensor".equals(request.get("type"))) - device = Sensor.getSensor(db, id); - else if ("event".equals(request.get("type"))) - device = Event.getEvent(db, id); + switch (request.get("type")) { + case "room": + Room room = Room.getRoom(db, id); + room.setMapCoordinates( + Float.parseFloat(request.get("x")), + Float.parseFloat(request.get("y")), + Float.parseFloat(request.get("width")), + Float.parseFloat(request.get("height"))); + room.save(db); + break; - device.setMapCoordinates( - Float.parseFloat(request.get("x")), - Float.parseFloat(request.get("y"))); - device.save(db); + case "sensor": + Sensor sensor = Sensor.getSensor(db, id); + sensor.setMapCoordinates( + Float.parseFloat(request.get("x")), + Float.parseFloat(request.get("y"))); + sensor.save(db); + break; + + case "event": + Event event = Event.getEvent(db, id); + event.setMapCoordinates( + Float.parseFloat(request.get("x")), + Float.parseFloat(request.get("y"))); + event.save(db); + break; + } } + return root; } }