]> source.dussan.org Git - svg.js.git/commitdiff
make List return new lists on method calls, add map to array polyfill so that this... 925/head
authorUlrich-Matthias Schäfer <ulima.ums@googlemail.com>
Mon, 12 Nov 2018 11:00:03 +0000 (12:00 +0100)
committerUlrich-Matthias Schäfer <ulima.ums@googlemail.com>
Mon, 12 Nov 2018 12:01:01 +0000 (13:01 +0100)
dist/svg.js
spec/spec/sugar.js
src/animation/Runner.js
src/main.js
src/modules/optional/sugar.js
src/types/ArrayPolyfill.js
src/types/List.js
src/types/SVGArray.js

index 670b1fdce490fd64969073014362241ccd821fb1..d7dfcd1cfed9f758123654ee1b3c26b76c2f15e1 100644 (file)
@@ -6,7 +6,7 @@
 * @copyright Wout Fierens <wout@mick-wout.com>
 * @license MIT
 *
-* BUILT: Mon Nov 12 2018 12:58:46 GMT+0100 (GMT+01:00)
+* BUILT: Mon Nov 12 2018 13:00:40 GMT+0100 (GMT+01:00)
 */;
 var SVG = (function () {
   'use strict';
@@ -1222,7 +1222,7 @@ var SVG = (function () {
   var subClassArray = function () {
     try {
       // try es6 subclassing
-      return Function('name', 'baseClass', '_constructor', ['baseClass = baseClass || Array', 'return {', '[name]: class extends baseClass {', 'constructor (...args) {', 'super(...args)', '_constructor && _constructor.apply(this, args)', '}', '}', '}[name]'].join('\n'));
+      return Function('name', 'baseClass', '_constructor', ['baseClass = baseClass || Array', 'return {', '  [name]: class extends baseClass {', '    constructor (...args) {', '      super(...args)', '      _constructor && _constructor.apply(this, args)', '    }', '  }', '}[name]'].join('\n'));
     } catch (e) {
       // Use es5 approach
       return function (name) {
@@ -1237,6 +1237,13 @@ var SVG = (function () {
 
         Arr.prototype = Object.create(baseClass.prototype);
         Arr.prototype.constructor = Arr;
+
+        Arr.prototype.map = function (fn) {
+          var arr = new Arr();
+          arr.push.apply(arr, Array.prototype.map.call(this, fn));
+          return arr;
+        };
+
         return Arr;
       };
     }
@@ -1247,6 +1254,8 @@ var SVG = (function () {
   });
   extend(SVGArray, {
     init: function 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.apply(this, _toConsumableArray(this.parse(arr)));
       return this;
@@ -2729,293 +2738,581 @@ var SVG = (function () {
     }
   });
 
-  /***
-  Base Class
-  ==========
-  The base stepper class that will be
-  ***/
+  var sugar = {
+    stroke: ['color', 'width', 'opacity', 'linecap', 'linejoin', 'miterlimit', 'dasharray', 'dashoffset'],
+    fill: ['color', 'opacity', 'rule'],
+    prefix: function prefix(t, a) {
+      return a === 'color' ? t : t + '-' + a;
+    } // Add sugar for fill and stroke
+
+  };
+  ['fill', 'stroke'].forEach(function (m) {
+    var extension = {};
+    var i;
+
+    extension[m] = function (o) {
+      if (typeof o === 'undefined') {
+        return this.attr(m);
+      }
+
+      if (typeof o === 'string' || 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]]);
+          }
+        }
+      }
 
-  function makeSetterGetter(k, f) {
-    return function (v) {
-      if (v == null) return this[v];
-      this[k] = v;
-      if (f) f.call(this);
       return this;
     };
-  }
 
-  var easing = {
-    '-': function _(pos) {
-      return pos;
+    registerMethods(['Shape', 'Runner'], extension);
+  });
+  registerMethods(['Element', 'Runner'], {
+    // Let the user set the matrix directly
+    matrix: function matrix(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));
     },
-    '<>': function _(pos) {
-      return -Math.cos(pos * Math.PI) / 2 + 0.5;
+    // Map rotation to transform
+    rotate: function rotate(angle, cx, cy) {
+      return this.transform({
+        rotate: angle,
+        ox: cx,
+        oy: cy
+      }, true);
     },
-    '>': function _(pos) {
-      return Math.sin(pos * Math.PI / 2);
+    // Map skew to transform
+    skew: function skew(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);
     },
-    '<': function _(pos) {
-      return -Math.cos(pos * Math.PI / 2) + 1;
+    shear: function shear(lam, cx, cy) {
+      return this.transform({
+        shear: lam,
+        ox: cx,
+        oy: cy
+      }, true);
     },
-    bezier: function bezier(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 * Math.pow(1 - t, 2) * y1 + 3 * Math.pow(t, 2) * (1 - t) * y2 + Math.pow(t, 3);
-        }
-      };
+    // Map scale to transform
+    scale: function scale(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);
     },
-    // https://www.w3.org/TR/css-easing-1/#step-timing-function-algo
-    steps: function steps(_steps) {
-      var stepPosition = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'end';
-      // deal with "jump-" prefix
-      stepPosition = stepPosition.split('-').reverse()[0];
-      var jumps = _steps;
-
-      if (stepPosition === 'none') {
-        --jumps;
-      } else if (stepPosition === 'both') {
-        ++jumps;
-      } // The beforeFlag is essentially useless
-
-
-      return function (t) {
-        var beforeFlag = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
-        // Step is called currentStep in referenced url
-        var step = Math.floor(t * _steps);
-        var 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;
+    // Map translate to transform
+    translate: function translate(x, y) {
+      return this.transform({
+        translate: [x, y]
+      }, true);
+    },
+    // Map relative translations to transform
+    relative: function relative(x, y) {
+      return this.transform({
+        relative: [x, y]
+      }, true);
+    },
+    // Map flip to transform
+    flip: function flip(direction, around) {
+      var directionString = typeof direction === 'string' ? direction : isFinite(direction) ? 'both' : 'both';
+      var origin = direction === 'both' && isFinite(around) ? [around, around] : direction === 'x' ? [around, 0] : direction === 'y' ? [0, around] : isFinite(direction) ? [direction, direction] : [0, 0];
+      this.transform({
+        flip: directionString,
+        origin: origin
+      }, true);
+    },
+    // Opacity
+    opacity: function opacity(value) {
+      return this.attr('opacity', value);
+    },
+    // Relative move over x and y axes
+    dmove: function dmove(x, y) {
+      return this.dx(x).dy(y);
+    }
+  });
+  registerMethods('Element', {
+    // Relative move over x axis
+    dx: function dx(x) {
+      return this.x(new SVGNumber(x).plus(this.x()));
+    },
+    // Relative move over y axis
+    dy: function dy(y) {
+      return this.y(new SVGNumber(y).plus(this.y()));
+    }
+  });
+  registerMethods('radius', {
+    // Add x and y radius
+    radius: function radius(x, y) {
+      var type = (this._element || this).type;
+      return type === 'radialGradient' || type === 'radialGradient' ? this.attr('r', new SVGNumber(x)) : this.rx(x).ry(y == null ? x : y);
+    }
+  });
+  registerMethods('Path', {
+    // Get path length
+    length: function length() {
+      return this.node.getTotalLength();
+    },
+    // Get point at length
+    pointAt: function pointAt(length) {
+      return new Point(this.node.getPointAtLength(length));
+    }
+  });
+  registerMethods(['Element', 'Runner'], {
+    // Set font
+    font: function font(a, v) {
+      if (_typeof(a) === 'object') {
+        for (v in a) {
+          this.font(v, a[v]);
         }
+      }
 
-        return step / jumps;
-      };
+      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);
     }
-  };
-  var Stepper =
-  /*#__PURE__*/
-  function () {
-    function Stepper() {
-      _classCallCheck(this, Stepper);
+  });
+  registerMethods('Text', {
+    ax: function ax(x) {
+      return this.attr('x', x);
+    },
+    ay: function ay(y) {
+      return this.attr('y', y);
+    },
+    amove: function amove(x, y) {
+      return this.ax(x).ay(y);
     }
+  }); // Add events to elements
 
-    _createClass(Stepper, [{
-      key: "done",
-      value: function done() {
-        return false;
+  var methods$1 = ['click', 'dblclick', 'mousedown', 'mouseup', 'mouseover', 'mouseout', 'mousemove', 'mouseenter', 'mouseleave', 'touchstart', 'touchmove', 'touchleave', 'touchend', 'touchcancel'].reduce(function (last, event) {
+    // add event to Element
+    var fn = function fn(f) {
+      if (f === null) {
+        off(this, event);
+      } else {
+        on(this, event, f);
       }
-    }]);
 
-    return Stepper;
-  }();
-  /***
-  Easing Functions
-  ================
-  ***/
+      return this;
+    };
 
-  var Ease =
-  /*#__PURE__*/
-  function (_Stepper) {
-    _inherits(Ease, _Stepper);
+    last[event] = fn;
+    return last;
+  }, {});
+  registerMethods('Element', methods$1);
 
-    function Ease(fn) {
-      var _this;
+  function untransform() {
+    return this.attr('transform', null);
+  } // merge the whole transformation chain into one matrix and returns it
 
-      _classCallCheck(this, Ease);
+  function matrixify() {
+    var matrix = (this.attr('transform') || ''). // split transformations
+    split(transforms).slice(0, -1).map(function (str) {
+      // generate key => value pairs
+      var 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]));
+      }
 
-      _this = _possibleConstructorReturn(this, _getPrototypeOf(Ease).call(this));
-      _this.ease = easing[fn || timeline.ease] || fn;
-      return _this;
+      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) {
+    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
+
+  function toDoc() {
+    return this.toParent(this.doc());
+  } // Add transformations
+
+  function transform(o, relative) {
+    // Act as a getter if no object was passed
+    if (o == null || typeof o === 'string') {
+      var decomposed = new Matrix(this).decompose();
+      return decomposed[o] || decomposed;
     }
 
-    _createClass(Ease, [{
-      key: "step",
-      value: function step(from, to, pos) {
-        if (typeof from !== 'number') {
-          return pos < 1 ? from : to;
-        }
+    if (!Matrix.isMatrixLike(o)) {
+      // Set the origin according to the defined transform
+      o = _objectSpread({}, o, {
+        origin: getOrigin(o, this)
+      });
+    } // The user can pass a boolean, an Element or an Matrix or nothing
 
-        return from + (to - from) * this.ease(pos);
+
+    var cleanRelative = relative === true ? this : relative || false;
+    var result = new Matrix(cleanRelative).transform(o);
+    return this.attr('transform', result);
+  }
+  registerMethods('Element', {
+    untransform: untransform,
+    matrixify: matrixify,
+    toParent: toParent,
+    toDoc: toDoc,
+    transform: transform
+  });
+
+  function isNulledBox(box) {
+    return !box.w && !box.h && !box.x && !box.y;
+  }
+
+  function domContains(node) {
+    return (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 Ease;
-  }(Stepper);
-  /***
-  Controller Types
-  ================
-  ***/
+      return node === document;
+    }).call(globals.document.documentElement, node);
+  }
 
-  var Controller =
+  var Box =
   /*#__PURE__*/
-  function (_Stepper2) {
-    _inherits(Controller, _Stepper2);
+  function () {
+    function Box() {
+      _classCallCheck(this, Box);
 
-    function Controller(fn) {
-      var _this2;
+      this.init.apply(this, arguments);
+    }
 
-      _classCallCheck(this, Controller);
+    _createClass(Box, [{
+      key: "init",
+      value: function init(source) {
+        var 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
 
-      _this2 = _possibleConstructorReturn(this, _getPrototypeOf(Controller).call(this));
-      _this2.stepper = fn;
-      return _this2;
-    }
+        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;
+      } // Merge rect box with another, return a new instance
 
-    _createClass(Controller, [{
-      key: "step",
-      value: function step(current, target, dt, c) {
-        return this.stepper(current, target, dt, c);
+    }, {
+      key: "merge",
+      value: function merge(box) {
+        var x = Math.min(this.x, box.x);
+        var y = Math.min(this.y, box.y);
+        var width = Math.max(this.x + this.width, box.x + box.width) - x;
+        var height = Math.max(this.y + this.height, box.y + box.height) - y;
+        return new Box(x, y, width, height);
       }
     }, {
-      key: "done",
-      value: function done(c) {
-        return c.done;
+      key: "transform",
+      value: function transform(m) {
+        var xMin = Infinity;
+        var xMax = -Infinity;
+        var yMin = Infinity;
+        var yMax = -Infinity;
+        var 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);
+      }
+    }, {
+      key: "addOffset",
+      value: function addOffset() {
+        // offset by window scroll position, because getBoundingClientRect changes when window is scrolled
+        this.x += globals.window.pageXOffset;
+        this.y += globals.window.pageYOffset;
+        return this;
+      }
+    }, {
+      key: "toString",
+      value: function toString() {
+        return this.x + ' ' + this.y + ' ' + this.width + ' ' + this.height;
+      }
+    }, {
+      key: "toArray",
+      value: function toArray() {
+        return [this.x, this.y, this.width, this.height];
+      }
+    }, {
+      key: "isNulled",
+      value: function isNulled() {
+        return isNulledBox(this);
       }
     }]);
 
-    return Controller;
-  }(Stepper);
+    return Box;
+  }();
 
-  function recalculate() {
-    // Apply the default parameters
-    var duration = (this._duration || 500) / 1000;
-    var overshoot = this._overshoot || 0; // Calculate the PID natural response
+  function getBox(cb) {
+    var box;
 
-    var eps = 1e-10;
-    var pi = Math.PI;
-    var os = Math.log(overshoot / 100 + eps);
-    var zeta = -os / Math.sqrt(pi * pi + os * os);
-    var wn = 3.9 / (zeta * duration); // Calculate the Spring values
+    try {
+      box = cb(this.node);
 
-    this.d = 2 * zeta * wn;
-    this.k = wn * wn;
+      if (isNulledBox(box) && !domContains(this.node)) {
+        throw new Error('Element not in the dom');
+      }
+    } catch (e) {
+      try {
+        var clone = this.clone().addTo(parser().svg).show();
+        box = cb(clone.node);
+        clone.remove();
+      } catch (e) {
+        throw new Error('Getting a bounding box of element "' + this.node.nodeName + '" is not possible');
+      }
+    }
+
+    return box;
   }
 
-  var Spring =
-  /*#__PURE__*/
-  function (_Controller) {
-    _inherits(Spring, _Controller);
+  registerMethods({
+    Element: {
+      // Get bounding box
+      bbox: function bbox() {
+        return new Box(getBox.call(this, function (node) {
+          return node.getBBox();
+        }));
+      },
+      rbox: function rbox(el) {
+        var box = new Box(getBox.call(this, function (node) {
+          return node.getBoundingClientRect();
+        }));
+        if (el) return box.transform(el.screenCTM().inverse());
+        return box.addOffset();
+      }
+    },
+    viewbox: {
+      viewbox: function viewbox(x, y, width, height) {
+        // act as getter
+        if (x == null) return new Box(this.attr('viewBox')); // act as setter
 
-    function Spring(duration, overshoot) {
-      var _this3;
+        return this.attr('viewBox', new Box(x, y, width, height));
+      }
+    }
+  });
 
-      _classCallCheck(this, Spring);
+  function rx(rx) {
+    return this.attr('rx', rx);
+  } // Radius y value
 
-      _this3 = _possibleConstructorReturn(this, _getPrototypeOf(Spring).call(this));
+  function ry(ry) {
+    return this.attr('ry', ry);
+  } // Move over x-axis
 
-      _this3.duration(duration || 500).overshoot(overshoot || 0);
+  function x(x) {
+    return x == null ? this.cx() - this.rx() : this.cx(x + this.rx());
+  } // Move over y-axis
 
-      return _this3;
+  function y(y) {
+    return y == null ? this.cy() - this.ry() : this.cy(y + this.ry());
+  } // Move by center over x-axis
+
+  function cx(x) {
+    return x == null ? this.attr('cx') : this.attr('cx', x);
+  } // Move by center over y-axis
+
+  function cy(y) {
+    return y == null ? this.attr('cy') : this.attr('cy', y);
+  } // Set width of element
+
+  function width(width) {
+    return width == null ? this.rx() * 2 : this.rx(new SVGNumber(width).divide(2));
+  } // Set height of element
+
+  function height(height) {
+    return height == null ? this.ry() * 2 : this.ry(new SVGNumber(height).divide(2));
+  }
+
+  var circled = /*#__PURE__*/Object.freeze({
+    rx: rx,
+    ry: ry,
+    x: x,
+    y: y,
+    cx: cx,
+    cy: cy,
+    width: width,
+    height: height
+  });
+
+  var Shape =
+  /*#__PURE__*/
+  function (_Element) {
+    _inherits(Shape, _Element);
+
+    function Shape() {
+      _classCallCheck(this, Shape);
+
+      return _possibleConstructorReturn(this, _getPrototypeOf(Shape).apply(this, arguments));
     }
 
-    _createClass(Spring, [{
-      key: "step",
-      value: function 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
+    return Shape;
+  }(Element);
+  register(Shape);
 
-        var velocity = c.velocity || 0; // Apply the control to get the new position and store it
+  var Circle =
+  /*#__PURE__*/
+  function (_Shape) {
+    _inherits(Circle, _Shape);
 
-        var acceleration = -this.d * velocity - this.k * (current - target);
-        var newPosition = current + velocity * dt + acceleration * dt * dt / 2; // Store the velocity
+    function Circle(node) {
+      _classCallCheck(this, Circle);
 
-        c.velocity = velocity + acceleration * dt; // Figure out if we have converged, and if so, pass the value
+      return _possibleConstructorReturn(this, _getPrototypeOf(Circle).call(this, nodeOrNew('circle', node), node));
+    }
 
-        c.done = Math.abs(target - newPosition) + Math.abs(velocity) < 0.002;
-        return c.done ? target : newPosition;
+    _createClass(Circle, [{
+      key: "radius",
+      value: function radius(r) {
+        return this.attr('r', r);
+      } // Radius x value
+
+    }, {
+      key: "rx",
+      value: function rx$$1(_rx) {
+        return this.attr('r', _rx);
+      } // Alias radius x value
+
+    }, {
+      key: "ry",
+      value: function ry$$1(_ry) {
+        return this.rx(_ry);
+      }
+    }, {
+      key: "size",
+      value: function size(_size) {
+        return this.radius(new SVGNumber(_size).divide(2));
       }
     }]);
 
-    return Spring;
-  }(Controller);
-  extend(Spring, {
-    duration: makeSetterGetter('_duration', recalculate),
-    overshoot: makeSetterGetter('_overshoot', recalculate)
+    return Circle;
+  }(Shape);
+  extend(Circle, {
+    x: x,
+    y: y,
+    cx: cx,
+    cy: cy,
+    width: width,
+    height: height
   });
-  var PID =
+  registerMethods({
+    Element: {
+      // Create circle element
+      circle: wrapWithAttrCheck(function (size) {
+        return this.put(new Circle()).size(size).move(0, 0);
+      })
+    }
+  });
+  register(Circle);
+
+  var Ellipse =
   /*#__PURE__*/
-  function (_Controller2) {
-    _inherits(PID, _Controller2);
+  function (_Shape) {
+    _inherits(Ellipse, _Shape);
 
-    function PID(p, i, d, windup) {
-      var _this4;
+    function Ellipse(node) {
+      _classCallCheck(this, Ellipse);
 
-      _classCallCheck(this, PID);
+      return _possibleConstructorReturn(this, _getPrototypeOf(Ellipse).call(this, nodeOrNew('ellipse', node), node));
+    }
 
-      _this4 = _possibleConstructorReturn(this, _getPrototypeOf(PID).call(this));
-      p = p == null ? 0.1 : p;
-      i = i == null ? 0.01 : i;
-      d = d == null ? 0 : d;
-      windup = windup == null ? 1000 : windup;
+    _createClass(Ellipse, [{
+      key: "size",
+      value: function size(width$$1, height$$1) {
+        var p = proportionalSize(this, width$$1, height$$1);
+        return this.rx(new SVGNumber(p.width).divide(2)).ry(new SVGNumber(p.height).divide(2));
+      }
+    }]);
 
-      _this4.p(p).i(i).d(d).windup(windup);
+    return Ellipse;
+  }(Shape);
+  extend(Ellipse, circled);
+  registerMethods('Container', {
+    // Create an ellipse
+    ellipse: wrapWithAttrCheck(function (width$$1, height$$1) {
+      return this.put(new Ellipse()).size(width$$1, height$$1).move(0, 0);
+    })
+  });
+  register(Ellipse);
 
-      return _this4;
-    }
+  var Stop =
+  /*#__PURE__*/
+  function (_Element) {
+    _inherits(Stop, _Element);
 
-    _createClass(PID, [{
-      key: "step",
-      value: function 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;
-        var p = target - current;
-        var i = (c.integral || 0) + p * dt;
-        var d = (p - (c.error || 0)) / dt;
-        var windup = this.windup; // antiwindup
+    function Stop(node) {
+      _classCallCheck(this, Stop);
 
-        if (windup !== false) {
-          i = Math.max(-windup, Math.min(i, windup));
-        }
+      return _possibleConstructorReturn(this, _getPrototypeOf(Stop).call(this, nodeOrNew('stop', node), node));
+    } // add color stops
 
-        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);
+
+    _createClass(Stop, [{
+      key: "update",
+      value: function 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;
       }
     }]);
 
-    return PID;
-  }(Controller);
-  extend(PID, {
-    windup: makeSetterGetter('windup'),
-    p: makeSetterGetter('P'),
-    i: makeSetterGetter('I'),
-    d: makeSetterGetter('D')
+    return Stop;
+  }(Element);
+  register(Stop);
+
+  function baseFind(query, parent) {
+    return map((parent || globals.document).querySelectorAll(query), function (node) {
+      return adopt(node);
+    });
+  } // Scoped find method
+
+  function find(query) {
+    return baseFind(query, this.node);
+  }
+  registerMethods('Dom', {
+    find: find
   });
 
   function from(x, y) {
@@ -3042,3258 +3339,3004 @@ var SVG = (function () {
     to: to
   });
 
-  function rx(rx) {
-    return this.attr('rx', rx);
-  } // Radius y value
-
-  function ry(ry) {
-    return this.attr('ry', ry);
-  } // Move over x-axis
+  var Gradient =
+  /*#__PURE__*/
+  function (_Container) {
+    _inherits(Gradient, _Container);
 
-  function x(x) {
-    return x == null ? this.cx() - this.rx() : this.cx(x + this.rx());
-  } // Move over y-axis
+    function Gradient(type, attrs) {
+      _classCallCheck(this, Gradient);
 
-  function y(y) {
-    return y == null ? this.cy() - this.ry() : this.cy(y + this.ry());
-  } // Move by center over x-axis
+      return _possibleConstructorReturn(this, _getPrototypeOf(Gradient).call(this, nodeOrNew(type + 'Gradient', typeof type === 'string' ? null : type), attrs));
+    } // Add a color stop
 
-  function cx(x) {
-    return x == null ? this.attr('cx') : this.attr('cx', x);
-  } // Move by center over y-axis
 
-  function cy(y) {
-    return y == null ? this.attr('cy') : this.attr('cy', y);
-  } // Set width of element
+    _createClass(Gradient, [{
+      key: "stop",
+      value: function stop(offset, color, opacity) {
+        return this.put(new Stop()).update(offset, color, opacity);
+      } // Update gradient
 
-  function width(width) {
-    return width == null ? this.rx() * 2 : this.rx(new SVGNumber(width).divide(2));
-  } // Set height of element
+    }, {
+      key: "update",
+      value: function update(block) {
+        // remove all stops
+        this.clear(); // invoke passed block
 
-  function height(height) {
-    return height == null ? this.ry() * 2 : this.ry(new SVGNumber(height).divide(2));
-  }
+        if (typeof block === 'function') {
+          block.call(this, this);
+        }
 
-  var circled = /*#__PURE__*/Object.freeze({
-    rx: rx,
-    ry: ry,
-    x: x,
-    y: y,
-    cx: cx,
-    cy: cy,
-    width: width,
-    height: height
-  });
+        return this;
+      } // Return the fill id
 
-  var Queue =
-  /*#__PURE__*/
-  function () {
-    function Queue() {
-      _classCallCheck(this, Queue);
+    }, {
+      key: "url",
+      value: function url() {
+        return 'url(#' + this.id() + ')';
+      } // Alias string convertion to fill
 
-      this._first = null;
-      this._last = null;
+    }, {
+      key: "toString",
+      value: function toString() {
+        return this.url();
+      } // custom attr to handle transform
+
+    }, {
+      key: "attr",
+      value: function attr(a, b, c) {
+        if (a === 'transform') a = 'gradientTransform';
+        return _get(_getPrototypeOf(Gradient.prototype), "attr", this).call(this, a, b, c);
+      }
+    }, {
+      key: "targets",
+      value: function targets() {
+        return baseFind('svg [fill*="' + this.id() + '"]');
+      }
+    }, {
+      key: "bbox",
+      value: function bbox() {
+        return new Box();
+      }
+    }]);
+
+    return Gradient;
+  }(Container);
+  extend(Gradient, gradiented);
+  registerMethods({
+    Container: {
+      // Create gradient element in defs
+      gradient: wrapWithAttrCheck(function (type, block) {
+        return this.defs().gradient(type, block);
+      })
+    },
+    // define gradient
+    Defs: {
+      gradient: wrapWithAttrCheck(function (type, block) {
+        return this.put(new Gradient(type)).update(block);
+      })
     }
+  });
+  register(Gradient);
 
-    _createClass(Queue, [{
-      key: "push",
-      value: function push(value) {
-        // An item stores an id and the provided value
-        var item = value.next ? value : {
-          value: value,
-          next: null,
-          prev: null // Deal with the queue being empty or populated
+  var Pattern =
+  /*#__PURE__*/
+  function (_Container) {
+    _inherits(Pattern, _Container);
 
-        };
+    // Initialize node
+    function Pattern(node) {
+      _classCallCheck(this, Pattern);
 
-        if (this._last) {
-          item.prev = this._last;
-          this._last.next = item;
-          this._last = item;
-        } else {
-          this._last = item;
-          this._first = item;
-        } // Update the length and return the current item
+      return _possibleConstructorReturn(this, _getPrototypeOf(Pattern).call(this, nodeOrNew('pattern', node), node));
+    } // Return the fill id
 
 
-        return item;
-      }
+    _createClass(Pattern, [{
+      key: "url",
+      value: function url() {
+        return 'url(#' + this.id() + ')';
+      } // Update pattern by rebuilding
+
     }, {
-      key: "shift",
-      value: function shift() {
-        // Check if we have a value
-        var remove = this._first;
-        if (!remove) return null; // If we do, remove it and relink things
+      key: "update",
+      value: function update(block) {
+        // remove content
+        this.clear(); // invoke passed block
 
-        this._first = remove.next;
-        if (this._first) this._first.prev = null;
-        this._last = this._first ? this._last : null;
-        return remove.value;
-      } // Shows us the first item in the list
+        if (typeof block === 'function') {
+          block.call(this, this);
+        }
 
-    }, {
-      key: "first",
-      value: function first() {
-        return this._first && this._first.value;
-      } // Shows us the last item in the list
+        return this;
+      } // Alias string convertion to fill
 
     }, {
-      key: "last",
-      value: function last() {
-        return this._last && this._last.value;
-      } // Removes the item that was returned from the push
+      key: "toString",
+      value: function toString() {
+        return this.url();
+      } // custom attr to handle transform
 
     }, {
-      key: "remove",
-      value: function 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;
+      key: "attr",
+      value: function attr(a, b, c) {
+        if (a === 'transform') a = 'patternTransform';
+        return _get(_getPrototypeOf(Pattern.prototype), "attr", this).call(this, a, b, c);
+      }
+    }, {
+      key: "targets",
+      value: function targets() {
+        return baseFind('svg [fill*="' + this.id() + '"]');
+      }
+    }, {
+      key: "bbox",
+      value: function bbox() {
+        return new Box();
       }
     }]);
 
-    return Queue;
-  }();
-
-  var Animator = {
-    nextDraw: null,
-    frames: new Queue(),
-    timeouts: new Queue(),
-    timer: globals.window.performance || globals.window.Date,
-    transforms: [],
-    frame: function frame(fn) {
-      // Store the node
-      var 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;
-    },
-    transform_frame: function transform_frame(fn, id) {
-      Animator.transforms[id] = fn;
-    },
-    timeout: function timeout(fn, delay) {
-      delay = delay || 0; // Work out when the event should fire
-
-      var time = Animator.timer.now() + delay; // Add the timeout to the end of the queue
-
-      var node = Animator.timeouts.push({
-        run: fn,
-        time: time
-      }); // Request another animation frame if we need one
+    return Pattern;
+  }(Container);
+  registerMethods({
+    Container: {
+      // Create pattern element in defs
+      pattern: function pattern() {
+        var _this$defs;
 
-      if (Animator.nextDraw === null) {
-        Animator.nextDraw = globals.window.requestAnimationFrame(Animator._draw);
+        return (_this$defs = this.defs()).pattern.apply(_this$defs, arguments);
       }
-
-      return node;
-    },
-    cancelFrame: function cancelFrame(node) {
-      Animator.frames.remove(node);
-    },
-    clearTimeout: function clearTimeout(node) {
-      Animator.timeouts.remove(node);
     },
-    _draw: function _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])
-      var nextTimeout = null;
-      var lastTimeout = Animator.timeouts.last();
+    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);
 
-      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
+  var Image =
+  /*#__PURE__*/
+  function (_Shape) {
+    _inherits(Image, _Shape);
 
+    function Image(node) {
+      _classCallCheck(this, Image);
 
-        if (nextTimeout === lastTimeout) break;
-      } // Run all of the animation frames
+      return _possibleConstructorReturn(this, _getPrototypeOf(Image).call(this, nodeOrNew('image', node), node));
+    } // (re)load image
 
 
-      var nextFrame = null;
-      var lastFrame = Animator.frames.last();
+    _createClass(Image, [{
+      key: "load",
+      value: function load(url, callback) {
+        if (!url) return this;
+        var img = new globals.window.Image();
+        on(img, 'load', function (e) {
+          var p = this.parent(Pattern); // ensure image size
 
-      while (nextFrame !== lastFrame && (nextFrame = Animator.frames.shift())) {
-        nextFrame.run();
-      }
-
-      Animator.transforms.forEach(function (el) {
-        el();
-      }); // 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;
-    }
-  };
+          if (this.width() === 0 && this.height() === 0) {
+            this.size(img.width, img.height);
+          }
 
-  function isNulledBox(box) {
-    return !box.w && !box.h && !box.x && !box.y;
-  }
+          if (p instanceof Pattern) {
+            // ensure pattern size if not set
+            if (p.width() === 0 && p.height() === 0) {
+              p.size(this.width(), this.height());
+            }
+          }
 
-  function domContains(node) {
-    return (globals.document.documentElement.contains || function (node) {
-      // This is IE - it does not support contains() for top-level SVGs
-      while (node.parentNode) {
-        node = node.parentNode;
+          if (typeof callback === 'function') {
+            callback.call(this, {
+              width: img.width,
+              height: img.height,
+              ratio: img.width / img.height,
+              url: url
+            });
+          }
+        }, this);
+        on(img, 'load error', function () {
+          // dont forget to unbind memory leaking events
+          off(img);
+        });
+        return this.attr('href', img.src = url, xlink);
       }
+    }]);
 
-      return node === document;
-    }).call(globals.document.documentElement, node);
-  }
+    return Image;
+  }(Shape);
+  registerAttrHook(function (attr$$1, val, _this) {
+    // convert image fill and stroke to patterns
+    if (attr$$1 === 'fill' || attr$$1 === 'stroke') {
+      if (isImage.test(val)) {
+        val = _this.doc().defs().image(val);
+      }
+    }
 
-  var Box =
-  /*#__PURE__*/
-  function () {
-    function Box() {
-      _classCallCheck(this, Box);
+    if (val instanceof Image) {
+      val = _this.doc().defs().pattern(0, 0, function (pattern) {
+        pattern.add(val);
+      });
+    }
 
-      this.init.apply(this, arguments);
+    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);
 
-    _createClass(Box, [{
-      key: "init",
-      value: function init(source) {
-        var 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
+  var PointArray = subClassArray('PointArray', SVGArray);
+  extend(PointArray, {
+    // Convert array to string
+    toString: function toString() {
+      // convert to a poly point string
+      for (var i = 0, il = this.length, array = []; i < il; i++) {
+        array.push(this[i].join(','));
+      }
 
-        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;
-      } // Merge rect box with another, return a new instance
+      return array.join(' ');
+    },
+    // Convert array to line object
+    toLine: function toLine() {
+      return {
+        x1: this[0][0],
+        y1: this[0][1],
+        x2: this[1][0],
+        y2: this[1][1]
+      };
+    },
+    // Get morphed array at given position
+    at: function at(pos) {
+      // make sure a destination is defined
+      if (!this.destination) return this; // generate morphed point string
 
-    }, {
-      key: "merge",
-      value: function merge(box) {
-        var x = Math.min(this.x, box.x);
-        var y = Math.min(this.y, box.y);
-        var width = Math.max(this.x + this.width, box.x + box.width) - x;
-        var height = Math.max(this.y + this.height, box.y + box.height) - y;
-        return new Box(x, y, width, height);
-      }
-    }, {
-      key: "transform",
-      value: function transform(m) {
-        var xMin = Infinity;
-        var xMax = -Infinity;
-        var yMin = Infinity;
-        var yMax = -Infinity;
-        var 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);
-      }
-    }, {
-      key: "addOffset",
-      value: function addOffset() {
-        // offset by window scroll position, because getBoundingClientRect changes when window is scrolled
-        this.x += globals.window.pageXOffset;
-        this.y += globals.window.pageYOffset;
-        return this;
-      }
-    }, {
-      key: "toString",
-      value: function toString() {
-        return this.x + ' ' + this.y + ' ' + this.width + ' ' + this.height;
-      }
-    }, {
-      key: "toArray",
-      value: function toArray() {
-        return [this.x, this.y, this.width, this.height];
-      }
-    }, {
-      key: "isNulled",
-      value: function isNulled() {
-        return isNulledBox(this);
+      for (var i = 0, il = this.length, array = []; i < il; i++) {
+        array.push([this[i][0] + (this.destination[i][0] - this[i][0]) * pos, this[i][1] + (this.destination[i][1] - this[i][1]) * pos]);
       }
-    }]);
-
-    return Box;
-  }();
 
-  function getBox(cb) {
-    var box;
+      return new PointArray(array);
+    },
+    // Parse point string and flat array
+    parse: function parse() {
+      var array = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [[0, 0]];
+      var points = []; // if it is an array
 
-    try {
-      box = cb(this.node);
+      if (array instanceof Array) {
+        // and it is not flat, there is no need to parse it
+        if (array[0] instanceof Array) {
+          return 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 (isNulledBox(box) && !domContains(this.node)) {
-        throw new Error('Element not in the dom');
-      }
-    } catch (e) {
-      try {
-        var clone = this.clone().addTo(parser().svg).show();
-        box = cb(clone.node);
-        clone.remove();
-      } catch (e) {
-        throw new Error('Getting a bounding box of element "' + this.node.nodeName + '" is not possible');
-      }
-    }
 
-    return box;
-  }
+      if (array.length % 2 !== 0) array.pop(); // wrap points in two-tuples and parse points as floats
 
-  registerMethods({
-    Element: {
-      // Get bounding box
-      bbox: function bbox() {
-        return new Box(getBox.call(this, function (node) {
-          return node.getBBox();
-        }));
-      },
-      rbox: function rbox(el) {
-        var box = new Box(getBox.call(this, function (node) {
-          return node.getBoundingClientRect();
-        }));
-        if (el) return box.transform(el.screenCTM().inverse());
-        return box.addOffset();
+      for (var i = 0, len = array.length; i < len; i = i + 2) {
+        points.push([array[i], array[i + 1]]);
       }
+
+      return points;
     },
-    viewbox: {
-      viewbox: function viewbox(x, y, width, height) {
-        // act as getter
-        if (x == null) return new Box(this.attr('viewBox')); // act as setter
+    // Move point string
+    move: function move(x, y) {
+      var box = this.bbox(); // get relative offset
 
-        return this.attr('viewBox', new Box(x, y, width, height));
+      x -= box.x;
+      y -= box.y; // move every point
+
+      if (!isNaN(x) && !isNaN(y)) {
+        for (var i = this.length - 1; i >= 0; i--) {
+          this[i] = [this[i][0] + x, this[i][1] + y];
+        }
       }
-    }
-  });
 
-  var PathArray = subClassArray('PathArray', SVGArray);
-  function pathRegReplace(a, b, c, d) {
-    return c + d.replace(dots, ' .');
-  }
+      return this;
+    },
+    // Resize poly string
+    size: function size(width, height) {
+      var i;
+      var box = this.bbox(); // recalculate position of all points according to new size
 
-  function arrayToString(a) {
-    for (var i = 0, il = a.length, s = ''; i < il; i++) {
-      s += a[i][0];
+      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;
+      }
 
-      if (a[i][1] != null) {
-        s += a[i][1];
+      return this;
+    },
+    // Get bounding box of points
+    bbox: function bbox() {
+      var maxX = -Infinity;
+      var maxY = -Infinity;
+      var minX = Infinity;
+      var 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 {
+        x: minX,
+        y: minY,
+        width: maxX - minX,
+        height: maxY - minY
+      };
+    }
+  });
 
-        if (a[i][2] != null) {
-          s += ' ';
-          s += a[i][2];
+  var MorphArray = PointArray; // Move by left top corner over x-axis
 
-          if (a[i][3] != null) {
-            s += ' ';
-            s += a[i][3];
-            s += ' ';
-            s += a[i][4];
+  function x$1(x) {
+    return x == null ? this.bbox().x : this.move(x, this.bbox().y);
+  } // Move by left top corner over y-axis
 
-            if (a[i][5] != null) {
-              s += ' ';
-              s += a[i][5];
-              s += ' ';
-              s += a[i][6];
+  function y$1(y) {
+    return y == null ? this.bbox().y : this.move(this.bbox().x, y);
+  } // Set width of element
 
-              if (a[i][7] != null) {
-                s += ' ';
-                s += a[i][7];
-              }
-            }
-          }
-        }
-      }
-    }
+  function width$1(width) {
+    var b = this.bbox();
+    return width == null ? b.width : this.size(width, b.height);
+  } // Set height of element
 
-    return s + ' ';
+  function height$1(height) {
+    var b = this.bbox();
+    return height == null ? b.height : this.size(b.width, height);
   }
 
-  var pathHandlers = {
-    M: function M(c, p, p0) {
-      p.x = p0.x = c[0];
-      p.y = p0.y = c[1];
-      return ['M', p.x, p.y];
-    },
-    L: function L(c, p) {
-      p.x = c[0];
-      p.y = c[1];
-      return ['L', c[0], c[1]];
-    },
-    H: function H(c, p) {
-      p.x = c[0];
-      return ['H', c[0]];
-    },
-    V: function V(c, p) {
-      p.y = c[0];
-      return ['V', c[0]];
-    },
-    C: function C(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 S(c, p) {
-      p.x = c[2];
-      p.y = c[3];
-      return ['S', c[0], c[1], c[2], c[3]];
-    },
-    Q: function Q(c, p) {
-      p.x = c[2];
-      p.y = c[3];
-      return ['Q', c[0], c[1], c[2], c[3]];
-    },
-    T: function T(c, p) {
-      p.x = c[0];
-      p.y = c[1];
-      return ['T', c[0], c[1]];
-    },
-    Z: function Z(c, p, p0) {
-      p.x = p0.x;
-      p.y = p0.y;
-      return ['Z'];
-    },
-    A: function A(c, p) {
-      p.x = c[5];
-      p.y = c[6];
-      return ['A', c[0], c[1], c[2], c[3], c[4], c[5], c[6]];
-    }
-  };
-  var mlhvqtcsaz = 'mlhvqtcsaz'.split('');
+  var pointed = /*#__PURE__*/Object.freeze({
+    MorphArray: MorphArray,
+    x: x$1,
+    y: y$1,
+    width: width$1,
+    height: height$1
+  });
 
-  for (var i = 0, il = mlhvqtcsaz.length; i < il; ++i) {
-    pathHandlers[mlhvqtcsaz[i]] = function (i) {
-      return function (c, p, p0) {
-        if (i === 'H') c[0] = c[0] + p.x;else if (i === 'V') c[0] = c[0] + p.y;else if (i === 'A') {
-          c[5] = c[5] + p.x;
-          c[6] = c[6] + p.y;
-        } else {
-          for (var j = 0, jl = c.length; j < jl; ++j) {
-            c[j] = c[j] + (j % 2 ? p.y : p.x);
-          }
-        }
-        return pathHandlers[i](c, p, p0);
-      };
-    }(mlhvqtcsaz[i].toUpperCase());
-  }
+  var Line =
+  /*#__PURE__*/
+  function (_Shape) {
+    _inherits(Line, _Shape);
 
-  extend(PathArray, {
-    // Convert array to string
-    toString: function toString() {
-      return arrayToString(this);
-    },
-    // Move path string
-    move: function move(x, y) {
-      // get bounding box of current situation
-      var box = this.bbox(); // get relative offset
+    // Initialize node
+    function Line(node) {
+      _classCallCheck(this, Line);
 
-      x -= box.x;
-      y -= box.y;
+      return _possibleConstructorReturn(this, _getPrototypeOf(Line).call(this, nodeOrNew('line', node), node));
+    } // Get array
 
-      if (!isNaN(x) && !isNaN(y)) {
-        // move every point
-        for (var 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;
+    _createClass(Line, [{
+      key: "array",
+      value: function array() {
+        return new PointArray([[this.attr('x1'), this.attr('y1')], [this.attr('x2'), this.attr('y2')]]);
+      } // Overwrite native plot() method
 
-            if (l === 'C') {
-              this[i][5] += x;
-              this[i][6] += y;
-            }
-          } else if (l === 'A') {
-            this[i][6] += x;
-            this[i][7] += y;
-          }
+    }, {
+      key: "plot",
+      value: function plot(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 PointArray(x1).toLine();
         }
-      }
-
-      return this;
-    },
-    // Resize path string
-    size: function size(width, height) {
-      // get bounding box of current situation
-      var box = this.bbox();
-      var i, l; // recalculate position of all points according to new size
 
-      for (i = this.length - 1; i >= 0; i--) {
-        l = this[i][0];
+        return this.attr(x1);
+      } // Move by left top corner
 
-        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;
+    }, {
+      key: "move",
+      value: function move(x, y) {
+        return this.attr(this.array().move(x, y).toLine());
+      } // Set element size to given width and height
 
-          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
+    }, {
+      key: "size",
+      value: function size(width, height) {
+        var p = proportionalSize(this, width, height);
+        return this.attr(this.array().size(p.width, p.height).toLine());
+      }
+    }]);
 
-          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 Line;
+  }(Shape);
+  extend(Line, pointed);
+  registerMethods({
+    Container: {
+      // Create a line element
+      line: wrapWithAttrCheck(function () {
+        for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
+          args[_key] = arguments[_key];
         }
-      }
 
-      return this;
-    },
-    // Test if the passed path array use the same path data commands as this path array
-    equalCommands: function equalCommands(pathArray) {
-      var i, il, equalCommands;
-      pathArray = new PathArray(pathArray);
-      equalCommands = this.length === pathArray.length;
+        // 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);
 
-      for (i = 0, il = this.length; equalCommands && i < il; i++) {
-        equalCommands = this[i][0] === pathArray[i][0];
+  var List = subClassArray('List', Array, function () {
+    var arr = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [];
+    // 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.apply(this, _toConsumableArray(arr));
+  });
+  extend(List, {
+    each: function each(fnOrMethodName) {
+      for (var _len = arguments.length, args = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
+        args[_key - 1] = arguments[_key];
       }
 
-      return equalCommands;
-    },
-    // Make path array morphable
-    morph: function morph(pathArray) {
-      pathArray = new PathArray(pathArray);
-
-      if (this.equalCommands(pathArray)) {
-        this.destination = pathArray;
+      if (typeof fnOrMethodName === 'function') {
+        this.forEach(function (el) {
+          fnOrMethodName.call(el, el);
+        });
       } else {
-        this.destination = null;
+        return this.map(function (el) {
+          return el[fnOrMethodName].apply(el, args);
+        }); // this.forEach((el) => {
+        //   el[fnOrMethodName](...args)
+        // })
       }
 
       return this;
     },
-    // Get morphed path array at given position
-    at: function at(pos) {
-      // make sure a destination is defined
-      if (!this.destination) return this;
-      var sourceArray = this;
-      var destinationArray = this.destination.value;
-      var array = [];
-      var pathArray = new PathArray();
-      var i, il, j, jl; // Animate has specified in the SVG spec
-      // See: https://www.w3.org/TR/SVG11/paths.html#PathElement
-
-      for (i = 0, il = sourceArray.length; i < il; i++) {
-        array[i] = [sourceArray[i][0]];
-
-        for (j = 1, jl = sourceArray[i].length; j < jl; j++) {
-          array[i][j] = sourceArray[i][j] + (destinationArray[i][j] - sourceArray[i][j]) * pos;
-        } // For the two flags of the elliptical arc command, the SVG spec say:
-        // Flags and booleans are interpolated as fractions between zero and one, with any non-zero value considered to be a value of one/true
-        // Elliptical arc command as an array followed by corresponding indexes:
-        // ['A', rx, ry, x-axis-rotation, large-arc-flag, sweep-flag, x, y]
-        //   0    1   2        3                 4             5      6  7
-
+    toArray: function toArray() {
+      return Array.prototype.concat.apply([], this);
+    }
+  });
 
-        if (array[i][0] === 'A') {
-          array[i][4] = +(array[i][4] !== 0);
-          array[i][5] = +(array[i][5] !== 0);
+  List.extend = function (methods) {
+    methods = methods.reduce(function (obj, name) {
+      obj[name] = function () {
+        for (var _len2 = arguments.length, attrs = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
+          attrs[_key2] = arguments[_key2];
         }
-      } // 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 parse() {
-      var array = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [['M', 0, 0]];
-      // if it's already a patharray, no need to parse it
-      if (array instanceof PathArray) return array; // prepare for parsing
 
-      var s;
-      var paramCnt = {
-        'M': 2,
-        'L': 2,
-        'H': 1,
-        'V': 1,
-        'C': 6,
-        'S': 4,
-        'Q': 4,
-        'T': 2,
-        'A': 7,
-        'Z': 0
+        return this.each.apply(this, [name].concat(attrs));
       };
 
-      if (typeof array === 'string') {
-        array = array.replace(numbersWithDots, pathRegReplace) // convert 45.123.123 to 45.123 .123
-        .replace(pathLetters, ' $& ') // put some room between letters and numbers
-        .replace(hyphen, '$1 -') // add space before hyphen
-        .trim() // trim
-        .split(delimiter); // split into array
-      } else {
-        array = array.reduce(function (prev, curr) {
-          return [].concat.call(prev, curr);
-        }, []);
-      } // array now is an array containing all parts of a path e.g. ['M', '0', '0', 'L', '30', '30' ...]
-
-
-      var result = [];
-      var p = new Point();
-      var p0 = new Point();
-      var index = 0;
-      var len = array.length;
-
-      do {
-        // Test if we have a path letter
-        if (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';
-        }
+      return obj;
+    }, {});
+    extend(List, methods);
+  };
 
-        result.push(pathHandlers[s].call(null, array.slice(index, index = index + paramCnt[s.toUpperCase()]).map(parseFloat), p, p0));
-      } while (len > index);
+  var Marker =
+  /*#__PURE__*/
+  function (_Container) {
+    _inherits(Marker, _Container);
 
-      return result;
-    },
-    // Get bounding box of path
-    bbox: function bbox() {
-      parser().path.setAttribute('d', this.toString());
-      return parser.nodes.path.getBBox();
-    }
-  });
+    // Initialize node
+    function Marker(node) {
+      _classCallCheck(this, Marker);
 
-  var Morphable =
-  /*#__PURE__*/
-  function () {
-    function Morphable(stepper) {
-      _classCallCheck(this, Morphable);
+      return _possibleConstructorReturn(this, _getPrototypeOf(Marker).call(this, nodeOrNew('marker', node), node));
+    } // Set width of element
 
-      this._stepper = stepper || new Ease('-');
-      this._from = null;
-      this._to = null;
-      this._type = null;
-      this._context = null;
-      this._morphObj = null;
-    }
 
-    _createClass(Morphable, [{
-      key: "from",
-      value: function from(val) {
-        if (val == null) {
-          return this._from;
-        }
+    _createClass(Marker, [{
+      key: "width",
+      value: function width(_width) {
+        return this.attr('markerWidth', _width);
+      } // Set height of element
 
-        this._from = this._set(val);
-        return this;
-      }
     }, {
-      key: "to",
-      value: function to(val) {
-        if (val == null) {
-          return this._to;
-        }
+      key: "height",
+      value: function height(_height) {
+        return this.attr('markerHeight', _height);
+      } // Set marker refX and refY
 
-        this._to = this._set(val);
-        return this;
-      }
     }, {
-      key: "type",
-      value: function type(_type) {
-        // getter
-        if (_type == null) {
-          return this._type;
-        } // setter
-
+      key: "ref",
+      value: function ref(x, y) {
+        return this.attr('refX', x).attr('refY', y);
+      } // Update marker
 
-        this._type = _type;
-        return this;
-      }
     }, {
-      key: "_set",
-      value: function _set$$1(value) {
-        if (!this._type) {
-          var type = _typeof(value);
+      key: "update",
+      value: function update(block) {
+        // remove all content
+        this.clear(); // invoke passed block
 
-          if (type === 'number') {
-            this.type(SVGNumber);
-          } else if (type === 'string') {
-            if (Color.isColor(value)) {
-              this.type(Color);
-            } else if (delimiter.test(value)) {
-              this.type(pathLetters.test(value) ? PathArray : SVGArray);
-            } else if (numberAndUnit.test(value)) {
-              this.type(SVGNumber);
-            } else {
-              this.type(NonMorphable);
-            }
-          } else if (morphableTypes.indexOf(value.constructor) > -1) {
-            this.type(value.constructor);
-          } else if (Array.isArray(value)) {
-            this.type(SVGArray);
-          } else if (type === 'object') {
-            this.type(ObjectBag);
-          } else {
-            this.type(NonMorphable);
-          }
+        if (typeof block === 'function') {
+          block.call(this, this);
         }
 
-        var result = new this._type(value).toArray();
-        this._morphObj = this._morphObj || new this._type();
-        this._context = this._context || Array.apply(null, Array(result.length)).map(Object);
-        return result;
-      }
-    }, {
-      key: "stepper",
-      value: function stepper(_stepper) {
-        if (_stepper == null) return this._stepper;
-        this._stepper = _stepper;
         return this;
-      }
-    }, {
-      key: "done",
-      value: function done() {
-        var complete = this._context.map(this._stepper.done).reduce(function (last, curr) {
-          return last && curr;
-        }, true);
+      } // Return the fill id
 
-        return complete;
-      }
     }, {
-      key: "at",
-      value: function at(pos) {
-        var _this = this;
-
-        return this._morphObj.fromArray(this._from.map(function (i, index) {
-          return _this._stepper.step(i, _this._to[index], pos, _this._context[index], _this._context);
-        }));
+      key: "toString",
+      value: function toString() {
+        return 'url(#' + this.id() + ')';
       }
     }]);
 
-    return Morphable;
-  }();
-  var NonMorphable =
-  /*#__PURE__*/
-  function () {
-    function NonMorphable() {
-      _classCallCheck(this, NonMorphable);
-
-      this.init.apply(this, arguments);
-    }
+    return Marker;
+  }(Container);
+  registerMethods({
+    Container: {
+      marker: function marker() {
+        var _this$defs;
 
-    _createClass(NonMorphable, [{
-      key: "init",
-      value: function init(val) {
-        val = Array.isArray(val) ? val[0] : val;
-        this.value = val;
-        return this;
-      }
-    }, {
-      key: "valueOf",
-      value: function valueOf() {
-        return this.value;
-      }
-    }, {
-      key: "toArray",
-      value: function toArray() {
-        return [this.value];
+        // Create marker element in defs
+        return (_this$defs = this.defs()).marker.apply(_this$defs, arguments);
       }
-    }]);
+    },
+    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: function marker(_marker, width, height, block) {
+        var attr = ['marker']; // Build attribute name
 
-    return NonMorphable;
-  }();
-  var TransformBag =
-  /*#__PURE__*/
-  function () {
-    function TransformBag() {
-      _classCallCheck(this, TransformBag);
+        if (_marker !== 'all') attr.push(_marker);
+        attr = attr.join('-'); // Set marker attribute
 
-      this.init.apply(this, arguments);
+        _marker = arguments[1] instanceof Marker ? arguments[1] : this.defs().marker(width, height, block);
+        return this.attr(attr, _marker);
+      }
     }
+  });
+  register(Marker);
 
-    _createClass(TransformBag, [{
-      key: "init",
-      value: function 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]
-          };
+  /***
+  Base Class
+  ==========
+  The base stepper class that will be
+  ***/
+
+  function makeSetterGetter(k, f) {
+    return function (v) {
+      if (v == null) return this[v];
+      this[k] = v;
+      if (f) f.call(this);
+      return this;
+    };
+  }
+
+  var 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 bezier(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 * Math.pow(1 - t, 2) * y1 + 3 * Math.pow(t, 2) * (1 - t) * y2 + Math.pow(t, 3);
         }
+      };
+    },
+    // https://www.w3.org/TR/css-easing-1/#step-timing-function-algo
+    steps: function steps(_steps) {
+      var stepPosition = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'end';
+      // deal with "jump-" prefix
+      stepPosition = stepPosition.split('-').reverse()[0];
+      var jumps = _steps;
 
-        Object.assign(this, TransformBag.defaults, obj);
-        return this;
-      }
-    }, {
-      key: "toArray",
-      value: function toArray() {
-        var v = this;
-        return [v.scaleX, v.scaleY, v.shear, v.rotate, v.translateX, v.translateY, v.originX, v.originY];
-      }
-    }]);
+      if (stepPosition === 'none') {
+        --jumps;
+      } else if (stepPosition === 'both') {
+        ++jumps;
+      } // The beforeFlag is essentially useless
 
-    return TransformBag;
-  }();
-  TransformBag.defaults = {
-    scaleX: 1,
-    scaleY: 1,
-    shear: 0,
-    rotate: 0,
-    translateX: 0,
-    translateY: 0,
-    originX: 0,
-    originY: 0
-  };
-  var ObjectBag =
-  /*#__PURE__*/
-  function () {
-    function ObjectBag() {
-      _classCallCheck(this, ObjectBag);
 
-      this.init.apply(this, arguments);
-    }
+      return function (t) {
+        var beforeFlag = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
+        // Step is called currentStep in referenced url
+        var step = Math.floor(t * _steps);
+        var jumping = t * step % 1 === 0;
 
-    _createClass(ObjectBag, [{
-      key: "init",
-      value: function init(objOrArr) {
-        this.values = [];
+        if (stepPosition === 'start' || stepPosition === 'both') {
+          ++step;
+        }
 
-        if (Array.isArray(objOrArr)) {
-          this.values = objOrArr;
-          return;
+        if (beforeFlag && jumping) {
+          --step;
         }
 
-        var entries = Object.entries(objOrArr || {}).sort(function (a, b) {
-          return a[0] - b[0];
-        });
-        this.values = entries.reduce(function (last, curr) {
-          return last.concat(curr);
-        }, []);
-        return this;
-      }
-    }, {
-      key: "valueOf",
-      value: function valueOf() {
-        var obj = {};
-        var arr = this.values;
+        if (t >= 0 && step < 0) {
+          step = 0;
+        }
 
-        for (var i = 0, len = arr.length; i < len; i += 2) {
-          obj[arr[i]] = arr[i + 1];
+        if (t <= 1 && step > jumps) {
+          step = jumps;
         }
 
-        return obj;
-      }
-    }, {
-      key: "toArray",
-      value: function toArray() {
-        return this.values;
+        return step / jumps;
+      };
+    }
+  };
+  var Stepper =
+  /*#__PURE__*/
+  function () {
+    function Stepper() {
+      _classCallCheck(this, Stepper);
+    }
+
+    _createClass(Stepper, [{
+      key: "done",
+      value: function done() {
+        return false;
       }
     }]);
 
-    return ObjectBag;
+    return Stepper;
   }();
-  var morphableTypes = [NonMorphable, TransformBag, ObjectBag];
-  function registerMorphableType() {
-    var type = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [];
-    morphableTypes.push.apply(morphableTypes, _toConsumableArray([].concat(type)));
-  }
-  function makeMorphable() {
-    extend(morphableTypes, {
-      to: function to(val) {
-        return new Morphable().type(this.constructor).from(this.valueOf()).to(val);
-      },
-      fromArray: function fromArray(arr) {
-        this.init(arr);
-        return this;
-      }
-    });
-  }
+  /***
+  Easing Functions
+  ================
+  ***/
 
-  var time = globals.window.performance || Date;
+  var Ease =
+  /*#__PURE__*/
+  function (_Stepper) {
+    _inherits(Ease, _Stepper);
 
-  var makeSchedule = function makeSchedule(runnerInfo) {
-    var start = runnerInfo.start;
-    var duration = runnerInfo.runner.duration();
-    var end = start + duration;
-    return {
-      start: start,
-      duration: duration,
-      end: end,
-      runner: runnerInfo.runner
-    };
-  };
+    function Ease(fn) {
+      var _this;
 
-  var Timeline =
-  /*#__PURE__*/
-  function () {
-    // Construct a new timeline on the given element
-    function Timeline() {
-      _classCallCheck(this, Timeline);
+      _classCallCheck(this, Ease);
 
-      this._timeSource = function () {
-        return time.now();
-      };
+      _this = _possibleConstructorReturn(this, _getPrototypeOf(Ease).call(this));
+      _this.ease = easing[fn || timeline.ease] || fn;
+      return _this;
+    }
 
-      this._dispatcher = globals.document.createElement('div'); // Store the timing variables
+    _createClass(Ease, [{
+      key: "step",
+      value: function step(from, to, pos) {
+        if (typeof from !== 'number') {
+          return pos < 1 ? from : to;
+        }
 
-      this._startTime = 0;
-      this._speed = 1.0; // Play control variables control how the animation proceeds
+        return from + (to - from) * this.ease(pos);
+      }
+    }]);
 
-      this._reverse = false;
-      this._persist = 0; // Keep track of the running animations and their starting parameters
+    return Ease;
+  }(Stepper);
+  /***
+  Controller Types
+  ================
+  ***/
 
-      this._nextFrame = null;
-      this._paused = false;
-      this._runners = [];
-      this._order = [];
-      this._time = 0;
-      this._lastSourceTime = 0;
-      this._lastStepTime = 0;
+  var Controller =
+  /*#__PURE__*/
+  function (_Stepper2) {
+    _inherits(Controller, _Stepper2);
+
+    function Controller(fn) {
+      var _this2;
+
+      _classCallCheck(this, Controller);
+
+      _this2 = _possibleConstructorReturn(this, _getPrototypeOf(Controller).call(this));
+      _this2.stepper = fn;
+      return _this2;
     }
 
-    _createClass(Timeline, [{
-      key: "getEventTarget",
-      value: function getEventTarget() {
-        return this._dispatcher;
+    _createClass(Controller, [{
+      key: "step",
+      value: function step(current, target, dt, c) {
+        return this.stepper(current, target, dt, c);
       }
-      /**
-       *
-       */
-      // schedules a runner on the timeline
-
     }, {
-      key: "schedule",
-      value: function schedule(runner, delay, when) {
-        if (runner == null) {
-          return this._runners.map(makeSchedule).sort(function (a, b) {
-            return a.start - b.start || a.duration - b.duration;
-          });
-        }
+      key: "done",
+      value: function done(c) {
+        return c.done;
+      }
+    }]);
 
-        if (!this.active()) {
-          this._step();
+    return Controller;
+  }(Stepper);
 
-          if (when == null) {
-            when = 'now';
-          }
-        } // The start time for the next animation can either be given explicitly,
-        // derived from the current timeline time or it can be relative to the
-        // last start time to chain animations direclty
+  function recalculate() {
+    // Apply the default parameters
+    var duration = (this._duration || 500) / 1000;
+    var overshoot = this._overshoot || 0; // Calculate the PID natural response
 
+    var eps = 1e-10;
+    var pi = Math.PI;
+    var os = Math.log(overshoot / 100 + eps);
+    var zeta = -os / Math.sqrt(pi * pi + os * os);
+    var wn = 3.9 / (zeta * duration); // Calculate the Spring values
 
-        var absoluteStartTime = 0;
-        delay = delay || 0; // Work out when to start the animation
+    this.d = 2 * zeta * wn;
+    this.k = wn * wn;
+  }
 
-        if (when == null || when === 'last' || when === 'after') {
-          // Take the last time and increment
-          absoluteStartTime = this._startTime;
-        } else if (when === 'absolute' || when === 'start') {
-          absoluteStartTime = delay;
-          delay = 0;
-        } else if (when === 'now') {
-          absoluteStartTime = this._time;
-        } else if (when === 'relative') {
-          var runnerInfo = this._runners[runner.id];
+  var Spring =
+  /*#__PURE__*/
+  function (_Controller) {
+    _inherits(Spring, _Controller);
 
-          if (runnerInfo) {
-            absoluteStartTime = runnerInfo.start + delay;
-            delay = 0;
-          }
-        } else {
-          throw new Error('Invalid value for the "when" parameter');
-        } // Manage runner
+    function Spring(duration, overshoot) {
+      var _this3;
 
+      _classCallCheck(this, Spring);
 
-        runner.unschedule();
-        runner.timeline(this);
-        runner.time(-delay); // Save startTime for next runner
+      _this3 = _possibleConstructorReturn(this, _getPrototypeOf(Spring).call(this));
 
-        this._startTime = absoluteStartTime + runner.duration() + delay; // Save runnerInfo
+      _this3.duration(duration || 500).overshoot(overshoot || 0);
 
-        this._runners[runner.id] = {
-          persist: this.persist(),
-          runner: runner,
-          start: absoluteStartTime // Save order and continue
+      return _this3;
+    }
 
-        };
+    _createClass(Spring, [{
+      key: "step",
+      value: function 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
 
-        this._order.push(runner.id);
+        var velocity = c.velocity || 0; // Apply the control to get the new position and store it
 
-        this._continue();
+        var acceleration = -this.d * velocity - this.k * (current - target);
+        var newPosition = current + velocity * dt + acceleration * dt * dt / 2; // Store the velocity
 
-        return this;
-      } // Remove the runner from this timeline
+        c.velocity = velocity + acceleration * dt; // Figure out if we have converged, and if so, pass the value
 
-    }, {
-      key: "unschedule",
-      value: function unschedule(runner) {
-        var index = this._order.indexOf(runner.id);
+        c.done = Math.abs(target - newPosition) + Math.abs(velocity) < 0.002;
+        return c.done ? target : newPosition;
+      }
+    }]);
 
-        if (index < 0) return this;
-        delete this._runners[runner.id];
+    return Spring;
+  }(Controller);
+  extend(Spring, {
+    duration: makeSetterGetter('_duration', recalculate),
+    overshoot: makeSetterGetter('_overshoot', recalculate)
+  });
+  var PID =
+  /*#__PURE__*/
+  function (_Controller2) {
+    _inherits(PID, _Controller2);
 
-        this._order.splice(index, 1);
+    function PID(p, i, d, windup) {
+      var _this4;
 
-        runner.timeline(null);
-        return this;
-      }
-    }, {
-      key: "play",
-      value: function play() {
-        // Now make sure we are not paused and continue the animation
-        this._paused = false;
-        return this._continue();
-      }
-    }, {
-      key: "pause",
-      value: function pause() {
-        // Cancel the next animation frame and pause
-        this._nextFrame = null;
-        this._paused = true;
-        return this;
-      }
-    }, {
-      key: "stop",
-      value: function stop() {
-        // Cancel the next animation frame and go to start
-        this.seek(-this._time);
-        return this.pause();
-      }
-    }, {
-      key: "finish",
-      value: function finish() {
-        this.seek(Infinity);
-        return this.pause();
+      _classCallCheck(this, PID);
+
+      _this4 = _possibleConstructorReturn(this, _getPrototypeOf(PID).call(this));
+      p = p == null ? 0.1 : p;
+      i = i == null ? 0.01 : i;
+      d = d == null ? 0 : d;
+      windup = windup == null ? 1000 : windup;
+
+      _this4.p(p).i(i).d(d).windup(windup);
+
+      return _this4;
+    }
+
+    _createClass(PID, [{
+      key: "step",
+      value: function 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;
+        var p = target - current;
+        var i = (c.integral || 0) + p * dt;
+        var d = (p - (c.error || 0)) / dt;
+        var 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);
       }
-    }, {
-      key: "speed",
-      value: function speed(_speed) {
-        if (_speed == null) return this._speed;
-        this._speed = _speed;
-        return this;
-      }
-    }, {
-      key: "reverse",
-      value: function reverse(yes) {
-        var currentSpeed = this.speed();
-        if (yes == null) return this.speed(-currentSpeed);
-        var positive = Math.abs(currentSpeed);
-        return this.speed(yes ? positive : -positive);
-      }
-    }, {
-      key: "seek",
-      value: function seek(dt) {
-        this._time += dt;
-        return this._continue();
-      }
-    }, {
-      key: "time",
-      value: function time(_time) {
-        if (_time == null) return this._time;
-        this._time = _time;
-        return this;
-      }
-    }, {
-      key: "persist",
-      value: function persist(dtOrForever) {
-        if (dtOrForever == null) return this._persist;
-        this._persist = dtOrForever;
-        return this;
-      }
-    }, {
-      key: "source",
-      value: function source(fn) {
-        if (fn == null) return this._timeSource;
-        this._timeSource = fn;
-        return this;
-      }
-    }, {
-      key: "_step",
-      value: function _step() {
-        // If the timeline is paused, just do nothing
-        if (this._paused) return; // Get the time delta from the last time and update the time
-
-        var time = this._timeSource();
-
-        var dtSource = time - this._lastSourceTime;
-        var dtTime = this._speed * dtSource + (this._time - this._lastStepTime);
-        this._lastSourceTime = time; // Update the time
-
-        this._time += dtTime;
-        this._lastStepTime = this._time; // this.fire('time', this._time)
-        // Run all of the runners directly
+    }]);
 
-        var runnersLeft = false;
+    return PID;
+  }(Controller);
+  extend(PID, {
+    windup: makeSetterGetter('windup'),
+    p: makeSetterGetter('P'),
+    i: makeSetterGetter('I'),
+    d: makeSetterGetter('D')
+  });
 
-        for (var i = 0, len = this._order.length; i < len; i++) {
-          // Get and run the current runner and ignore it if its inactive
-          var runnerInfo = this._runners[this._order[i]];
-          var runner = runnerInfo.runner;
-          var dt = dtTime; // Make sure that we give the actual difference
-          // between runner start time and now
+  var PathArray = subClassArray('PathArray', SVGArray);
+  function pathRegReplace(a, b, c, d) {
+    return c + d.replace(dots, ' .');
+  }
 
-          var dtToStart = this._time - runnerInfo.start; // Dont run runner if not started yet
+  function arrayToString(a) {
+    for (var i = 0, il = a.length, s = ''; i < il; i++) {
+      s += a[i][0];
 
-          if (dtToStart < 0) {
-            runnersLeft = true;
-            continue;
-          } else if (dtToStart < dt) {
-            // Adjust dt to make sure that animation is on point
-            dt = dtToStart;
-          }
+      if (a[i][1] != null) {
+        s += a[i][1];
 
-          if (!runner.active()) continue; // If this runner is still going, signal that we need another animation
-          // frame, otherwise, remove the completed runner
+        if (a[i][2] != null) {
+          s += ' ';
+          s += a[i][2];
 
-          var finished = runner.step(dt).done;
+          if (a[i][3] != null) {
+            s += ' ';
+            s += a[i][3];
+            s += ' ';
+            s += a[i][4];
 
-          if (!finished) {
-            runnersLeft = true; // continue
-          } else if (runnerInfo.persist !== true) {
-            // runner is finished. And runner might get removed
-            var endTime = runner.duration() - runner.time() + this._time;
+            if (a[i][5] != null) {
+              s += ' ';
+              s += a[i][5];
+              s += ' ';
+              s += a[i][6];
 
-            if (endTime + this._persist < this._time) {
-              // Delete runner and correct index
-              delete this._runners[this._order[i]];
-              this._order.splice(i--, 1) && --len;
-              runner.timeline(null);
+              if (a[i][7] != null) {
+                s += ' ';
+                s += a[i][7];
+              }
             }
           }
-        } // Get the next animation frame to keep the simulation going
+        }
+      }
+    }
 
+    return s + ' ';
+  }
 
-        if (runnersLeft) {
-          this._nextFrame = Animator.frame(this._step.bind(this));
+  var pathHandlers = {
+    M: function M(c, p, p0) {
+      p.x = p0.x = c[0];
+      p.y = p0.y = c[1];
+      return ['M', p.x, p.y];
+    },
+    L: function L(c, p) {
+      p.x = c[0];
+      p.y = c[1];
+      return ['L', c[0], c[1]];
+    },
+    H: function H(c, p) {
+      p.x = c[0];
+      return ['H', c[0]];
+    },
+    V: function V(c, p) {
+      p.y = c[0];
+      return ['V', c[0]];
+    },
+    C: function C(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 S(c, p) {
+      p.x = c[2];
+      p.y = c[3];
+      return ['S', c[0], c[1], c[2], c[3]];
+    },
+    Q: function Q(c, p) {
+      p.x = c[2];
+      p.y = c[3];
+      return ['Q', c[0], c[1], c[2], c[3]];
+    },
+    T: function T(c, p) {
+      p.x = c[0];
+      p.y = c[1];
+      return ['T', c[0], c[1]];
+    },
+    Z: function Z(c, p, p0) {
+      p.x = p0.x;
+      p.y = p0.y;
+      return ['Z'];
+    },
+    A: function A(c, p) {
+      p.x = c[5];
+      p.y = c[6];
+      return ['A', c[0], c[1], c[2], c[3], c[4], c[5], c[6]];
+    }
+  };
+  var mlhvqtcsaz = 'mlhvqtcsaz'.split('');
+
+  for (var i = 0, il = mlhvqtcsaz.length; i < il; ++i) {
+    pathHandlers[mlhvqtcsaz[i]] = function (i) {
+      return function (c, p, p0) {
+        if (i === 'H') c[0] = c[0] + p.x;else if (i === 'V') c[0] = c[0] + p.y;else if (i === 'A') {
+          c[5] = c[5] + p.x;
+          c[6] = c[6] + p.y;
         } else {
-          this._nextFrame = null;
+          for (var j = 0, jl = c.length; j < jl; ++j) {
+            c[j] = c[j] + (j % 2 ? p.y : p.x);
+          }
         }
+        return pathHandlers[i](c, p, p0);
+      };
+    }(mlhvqtcsaz[i].toUpperCase());
+  }
 
-        return this;
-      } // Checks if we are running and continues the animation
+  extend(PathArray, {
+    // Convert array to string
+    toString: function toString() {
+      return arrayToString(this);
+    },
+    // Move path string
+    move: function move(x, y) {
+      // get bounding box of current situation
+      var box = this.bbox(); // get relative offset
 
-    }, {
-      key: "_continue",
-      value: function _continue() {
-        if (this._paused) return this;
+      x -= box.x;
+      y -= box.y;
 
-        if (!this._nextFrame) {
-          this._nextFrame = Animator.frame(this._step.bind(this));
-        }
+      if (!isNaN(x) && !isNaN(y)) {
+        // move every point
+        for (var l, i = this.length - 1; i >= 0; i--) {
+          l = this[i][0];
 
-        return this;
-      }
-    }, {
-      key: "active",
-      value: function active() {
-        return !!this._nextFrame;
-      }
-    }]);
+          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;
 
-    return Timeline;
-  }();
-  registerMethods({
-    Element: {
-      timeline: function timeline() {
-        this._timeline = this._timeline || new Timeline();
-        return this._timeline;
+            if (l === 'C') {
+              this[i][5] += x;
+              this[i][6] += y;
+            }
+          } else if (l === 'A') {
+            this[i][6] += x;
+            this[i][7] += y;
+          }
+        }
       }
-    }
-  });
-
-  var Runner =
-  /*#__PURE__*/
-  function (_EventTarget) {
-    _inherits(Runner, _EventTarget);
 
-    function Runner(options) {
-      var _this;
+      return this;
+    },
+    // Resize path string
+    size: function size(width, height) {
+      // get bounding box of current situation
+      var box = this.bbox();
+      var i, l; // recalculate position of all points according to new size
 
-      _classCallCheck(this, Runner);
+      for (i = this.length - 1; i >= 0; i--) {
+        l = this[i][0];
 
-      _this = _possibleConstructorReturn(this, _getPrototypeOf(Runner).call(this)); // Store a unique id on the runner, so that we can identify it later
+        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;
 
-      _this.id = Runner.id++; // Ensure a default value
+          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
 
-      options = options == null ? timeline.duration : options; // Ensure that we get a controller
+          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;
+        }
+      }
 
-      options = typeof options === 'function' ? new Controller(options) : options; // Declare all of the variables
+      return this;
+    },
+    // Test if the passed path array use the same path data commands as this path array
+    equalCommands: function equalCommands(pathArray) {
+      var i, il, equalCommands;
+      pathArray = new PathArray(pathArray);
+      equalCommands = this.length === pathArray.length;
 
-      _this._element = null;
-      _this._timeline = null;
-      _this.done = false;
-      _this._queue = []; // Work out the stepper and the duration
+      for (i = 0, il = this.length; equalCommands && i < il; i++) {
+        equalCommands = this[i][0] === pathArray[i][0];
+      }
 
-      _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
+      return equalCommands;
+    },
+    // Make path array morphable
+    morph: function morph(pathArray) {
+      pathArray = new PathArray(pathArray);
 
-      _this._history = {}; // Store the state of the runner
+      if (this.equalCommands(pathArray)) {
+        this.destination = pathArray;
+      } else {
+        this.destination = null;
+      }
 
-      _this.enabled = true;
-      _this._time = 0;
-      _this._last = 0; // Save transforms applied to this runner
+      return this;
+    },
+    // Get morphed path array at given position
+    at: function at(pos) {
+      // make sure a destination is defined
+      if (!this.destination) return this;
+      var sourceArray = this;
+      var destinationArray = this.destination.value;
+      var array = [];
+      var pathArray = new PathArray();
+      var i, il, j, jl; // Animate has specified in the SVG spec
+      // See: https://www.w3.org/TR/SVG11/paths.html#PathElement
 
-      _this.transforms = new Matrix();
-      _this.transformId = 1; // Looping variables
+      for (i = 0, il = sourceArray.length; i < il; i++) {
+        array[i] = [sourceArray[i][0]];
 
-      _this._haveReversed = false;
-      _this._reverse = false;
-      _this._loopsDone = 0;
-      _this._swing = false;
-      _this._wait = 0;
-      _this._times = 1;
-      return _this;
+        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 parse() {
+      var array = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [['M', 0, 0]];
+      // if it's already a patharray, no need to parse it
+      if (array instanceof PathArray) return array; // prepare for parsing
+
+      var s;
+      var paramCnt = {
+        'M': 2,
+        'L': 2,
+        'H': 1,
+        'V': 1,
+        'C': 6,
+        'S': 4,
+        'Q': 4,
+        'T': 2,
+        'A': 7,
+        'Z': 0
+      };
+
+      if (typeof array === 'string') {
+        array = array.replace(numbersWithDots, pathRegReplace) // convert 45.123.123 to 45.123 .123
+        .replace(pathLetters, ' $& ') // put some room between letters and numbers
+        .replace(hyphen, '$1 -') // add space before hyphen
+        .trim() // trim
+        .split(delimiter); // split into array
+      } else {
+        array = array.reduce(function (prev, curr) {
+          return [].concat.call(prev, curr);
+        }, []);
+      } // array now is an array containing all parts of a path e.g. ['M', '0', '0', 'L', '30', '30' ...]
+
+
+      var result = [];
+      var p = new Point();
+      var p0 = new Point();
+      var index = 0;
+      var len = array.length;
+
+      do {
+        // Test if we have a path letter
+        if (isPathLetter.test(array[index])) {
+          s = array[index];
+          ++index; // If last letter was a move command and we got no new, it defaults to [L]ine
+        } else if (s === 'M') {
+          s = 'L';
+        } else if (s === 'm') {
+          s = 'l';
+        }
+
+        result.push(pathHandlers[s].call(null, array.slice(index, index = index + paramCnt[s.toUpperCase()]).map(parseFloat), p, p0));
+      } while (len > index);
+
+      return result;
+    },
+    // Get bounding box of path
+    bbox: function bbox() {
+      parser().path.setAttribute('d', this.toString());
+      return parser.nodes.path.getBBox();
     }
-    /*
-    Runner Definitions
-    ==================
-    These methods help us define the runtime behaviour of the Runner or they
-    help us make new runners from the current runner
-    */
+  });
 
+  var Morphable =
+  /*#__PURE__*/
+  function () {
+    function Morphable(stepper) {
+      _classCallCheck(this, Morphable);
 
-    _createClass(Runner, [{
-      key: "element",
-      value: function element(_element) {
-        if (_element == null) return this._element;
-        this._element = _element;
+      this._stepper = stepper || new Ease('-');
+      this._from = null;
+      this._to = null;
+      this._type = null;
+      this._context = null;
+      this._morphObj = null;
+    }
 
-        _element._prepareRunner();
+    _createClass(Morphable, [{
+      key: "from",
+      value: function from(val) {
+        if (val == null) {
+          return this._from;
+        }
 
+        this._from = this._set(val);
         return this;
       }
     }, {
-      key: "timeline",
-      value: function timeline$$1(_timeline) {
-        // check explicitly for undefined so we can set the timeline to null
-        if (typeof _timeline === 'undefined') return this._timeline;
-        this._timeline = _timeline;
+      key: "to",
+      value: function to(val) {
+        if (val == null) {
+          return this._to;
+        }
+
+        this._to = this._set(val);
         return this;
       }
     }, {
-      key: "animate",
-      value: function animate(duration, delay, when) {
-        var o = Runner.sanitise(duration, delay, when);
-        var runner = new Runner(o.duration);
-        if (this._timeline) runner.timeline(this._timeline);
-        if (this._element) runner.element(this._element);
-        return runner.loop(o).schedule(delay, when);
-      }
-    }, {
-      key: "schedule",
-      value: function schedule(timeline$$1, delay, when) {
-        // The user doesn't need to pass a timeline if we already have one
-        if (!(timeline$$1 instanceof Timeline)) {
-          when = delay;
-          delay = timeline$$1;
-          timeline$$1 = this.timeline();
-        } // If there is no timeline, yell at the user...
-
-
-        if (!timeline$$1) {
-          throw Error('Runner cannot be scheduled without timeline');
-        } // Schedule the runner on the timeline provided
+      key: "type",
+      value: function type(_type) {
+        // getter
+        if (_type == null) {
+          return this._type;
+        } // setter
 
 
-        timeline$$1.schedule(this, delay, when);
-        return this;
-      }
-    }, {
-      key: "unschedule",
-      value: function unschedule() {
-        var timeline$$1 = this.timeline();
-        timeline$$1 && timeline$$1.unschedule(this);
+        this._type = _type;
         return this;
       }
     }, {
-      key: "loop",
-      value: function 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
+      key: "_set",
+      value: function _set$$1(value) {
+        if (!this._type) {
+          var type = _typeof(value);
 
+          if (type === 'number') {
+            this.type(SVGNumber);
+          } else if (type === 'string') {
+            if (Color.isColor(value)) {
+              this.type(Color);
+            } else if (delimiter.test(value)) {
+              this.type(pathLetters.test(value) ? PathArray : SVGArray);
+            } else if (numberAndUnit.test(value)) {
+              this.type(SVGNumber);
+            } else {
+              this.type(NonMorphable);
+            }
+          } else if (morphableTypes.indexOf(value.constructor) > -1) {
+            this.type(value.constructor);
+          } else if (Array.isArray(value)) {
+            this.type(SVGArray);
+          } else if (type === 'object') {
+            this.type(ObjectBag);
+          } else {
+            this.type(NonMorphable);
+          }
+        }
 
-        this._times = times || Infinity;
-        this._swing = swing || false;
-        this._wait = wait || 0;
-        return this;
-      }
-    }, {
-      key: "delay",
-      value: function delay(_delay) {
-        return this.animate(0, _delay);
+        var result = new this._type(value).toArray();
+        this._morphObj = this._morphObj || new this._type();
+        this._context = this._context || Array.apply(null, Array(result.length)).map(Object);
+        return result;
       }
-      /*
-      Basic Functionality
-      ===================
-      These methods allow us to attach basic functions to the runner directly
-      */
-
     }, {
-      key: "queue",
-      value: function queue(initFn, runFn, isTransform) {
-        this._queue.push({
-          initialiser: initFn || noop,
-          runner: runFn || noop,
-          isTransform: isTransform,
-          initialised: false,
-          finished: false
-        });
-
-        var timeline$$1 = this.timeline();
-        timeline$$1 && this.timeline()._continue();
+      key: "stepper",
+      value: function stepper(_stepper) {
+        if (_stepper == null) return this._stepper;
+        this._stepper = _stepper;
         return this;
       }
     }, {
-      key: "during",
-      value: function during(fn) {
-        return this.queue(null, fn);
+      key: "done",
+      value: function done() {
+        var complete = this._context.map(this._stepper.done).reduce(function (last, curr) {
+          return last && curr;
+        }, true);
+
+        return complete;
       }
     }, {
-      key: "after",
-      value: function after(fn) {
-        return this.on('finish', fn);
+      key: "at",
+      value: function at(pos) {
+        var _this = this;
+
+        return this._morphObj.fromArray(this._from.map(function (i, index) {
+          return _this._stepper.step(i, _this._to[index], pos, _this._context[index], _this._context);
+        }));
       }
-      /*
-      Runner animation methods
-      ========================
-      Control how the animation plays
-      */
+    }]);
 
-    }, {
-      key: "time",
-      value: function time(_time) {
-        if (_time == null) {
-          return this._time;
-        }
+    return Morphable;
+  }();
+  var NonMorphable =
+  /*#__PURE__*/
+  function () {
+    function NonMorphable() {
+      _classCallCheck(this, NonMorphable);
 
-        var dt = _time - this._time;
-        this.step(dt);
+      this.init.apply(this, arguments);
+    }
+
+    _createClass(NonMorphable, [{
+      key: "init",
+      value: function init(val) {
+        val = Array.isArray(val) ? val[0] : val;
+        this.value = val;
         return this;
       }
     }, {
-      key: "duration",
-      value: function duration() {
-        return this._times * (this._wait + this._duration) - this._wait;
+      key: "valueOf",
+      value: function valueOf() {
+        return this.value;
       }
     }, {
-      key: "loops",
-      value: function loops(p) {
-        var loopDuration = this._duration + this._wait;
+      key: "toArray",
+      value: function toArray() {
+        return [this.value];
+      }
+    }]);
 
-        if (p == null) {
-          var loopsDone = Math.floor(this._time / loopDuration);
-          var relativeTime = this._time - loopsDone * loopDuration;
-          var position = relativeTime / this._duration;
-          return Math.min(loopsDone + position, this._times);
+    return NonMorphable;
+  }();
+  var TransformBag =
+  /*#__PURE__*/
+  function () {
+    function TransformBag() {
+      _classCallCheck(this, TransformBag);
+
+      this.init.apply(this, arguments);
+    }
+
+    _createClass(TransformBag, [{
+      key: "init",
+      value: function 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]
+          };
         }
 
-        var whole = Math.floor(p);
-        var partial = p % 1;
-        var time = loopDuration * whole + this._duration * partial;
-        return this.time(time);
+        Object.assign(this, TransformBag.defaults, obj);
+        return this;
       }
     }, {
-      key: "position",
-      value: function position(p) {
-        // Get all of the variables we need
-        var x$$1 = this._time;
-        var d = this._duration;
-        var w = this._wait;
-        var t = this._times;
-        var s = this._swing;
-        var r = this._reverse;
-        var position;
+      key: "toArray",
+      value: function toArray() {
+        var v = this;
+        return [v.scaleX, v.scaleY, v.shear, v.rotate, v.translateX, v.translateY, v.originX, v.originY];
+      }
+    }]);
 
-        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
-          var f = function f(x$$1) {
-            var swinging = s * Math.floor(x$$1 % (2 * (w + d)) / (w + d));
-            var backwards = swinging && !r || !swinging && r;
-            var uncliped = Math.pow(-1, backwards) * (x$$1 % (w + d)) / d + backwards;
-            var clipped = Math.max(Math.min(uncliped, 1), 0);
-            return clipped;
-          }; // Figure out the value by incorporating the start time
+    return TransformBag;
+  }();
+  TransformBag.defaults = {
+    scaleX: 1,
+    scaleY: 1,
+    shear: 0,
+    rotate: 0,
+    translateX: 0,
+    translateY: 0,
+    originX: 0,
+    originY: 0
+  };
+  var ObjectBag =
+  /*#__PURE__*/
+  function () {
+    function ObjectBag() {
+      _classCallCheck(this, ObjectBag);
 
+      this.init.apply(this, arguments);
+    }
 
-          var endTime = t * (w + d) - w;
-          position = x$$1 <= 0 ? Math.round(f(1e-5)) : x$$1 < endTime ? f(x$$1) : Math.round(f(endTime - 1e-5));
-          return position;
-        } // Work out the loops done and add the position to the loops done
+    _createClass(ObjectBag, [{
+      key: "init",
+      value: function init(objOrArr) {
+        this.values = [];
 
+        if (Array.isArray(objOrArr)) {
+          this.values = objOrArr;
+          return;
+        }
 
-        var loopsDone = Math.floor(this.loops());
-        var swingForward = s && loopsDone % 2 === 0;
-        var forwards = swingForward && !r || r && swingForward;
-        position = loopsDone + (forwards ? p : 1 - p);
-        return this.loops(position);
+        var entries = Object.entries(objOrArr || {}).sort(function (a, b) {
+          return a[0] - b[0];
+        });
+        this.values = entries.reduce(function (last, curr) {
+          return last.concat(curr);
+        }, []);
+        return this;
       }
     }, {
-      key: "progress",
-      value: function progress(p) {
-        if (p == null) {
-          return Math.min(1, this._time / this.duration());
+      key: "valueOf",
+      value: function valueOf() {
+        var obj = {};
+        var arr = this.values;
+
+        for (var i = 0, len = arr.length; i < len; i += 2) {
+          obj[arr[i]] = arr[i + 1];
         }
 
-        return this.time(p * this.duration());
+        return obj;
       }
     }, {
-      key: "step",
-      value: function 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;
-        var position = this.position(); // Figure out if we need to run the stepper in this frame
-
-        var running = this._lastPosition !== position && this._time >= 0;
-        this._lastPosition = position; // Figure out if we just started
-
-        var duration = this.duration();
-        var justStarted = this._lastTime < 0 && this._time > 0;
-        var justFinished = this._lastTime < this._time && this.time > duration;
-        this._lastTime = this._time;
+      key: "toArray",
+      value: function toArray() {
+        return this.values;
+      }
+    }]);
 
-        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)
+    return ObjectBag;
+  }();
+  var morphableTypes = [NonMorphable, TransformBag, ObjectBag];
+  function registerMorphableType() {
+    var type = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [];
+    morphableTypes.push.apply(morphableTypes, _toConsumableArray([].concat(type)));
+  }
+  function makeMorphable() {
+    extend(morphableTypes, {
+      to: function to(val) {
+        return new Morphable().type(this.constructor).from(this.valueOf()).to(val);
+      },
+      fromArray: function fromArray(arr) {
+        this.init(arr);
+        return this;
+      }
+    });
+  }
 
+  var Path =
+  /*#__PURE__*/
+  function (_Shape) {
+    _inherits(Path, _Shape);
 
-        var declarative = this._isDeclarative;
-        this.done = !declarative && !justFinished && this._time >= duration; // Call initialise and the run function
+    // Initialize node
+    function Path(node) {
+      _classCallCheck(this, Path);
 
-        if (running || declarative) {
-          this._initialise(running); // clear the transforms on this runner so they dont get added again and again
+      return _possibleConstructorReturn(this, _getPrototypeOf(Path).call(this, nodeOrNew('path', node), node));
+    } // Get array
 
 
-          this.transforms = new Matrix();
+    _createClass(Path, [{
+      key: "array",
+      value: function array() {
+        return this._array || (this._array = new PathArray(this.attr('d')));
+      } // Plot new path
 
-          var converged = this._run(declarative ? dt : position);
+    }, {
+      key: "plot",
+      value: function plot(d) {
+        return d == null ? this.array() : this.clear().attr('d', typeof d === 'string' ? d : this._array = new PathArray(d));
+      } // Clear array cache
 
-          this.fire('step', this);
-        } // correct the done flag here
-        // declaritive animations itself know when they converged
+    }, {
+      key: "clear",
+      value: function clear() {
+        delete this._array;
+        return this;
+      } // Move by left top corner
 
+    }, {
+      key: "move",
+      value: function move(x, y) {
+        return this.attr('d', this.array().move(x, y));
+      } // Move by left top corner over x-axis
 
-        this.done = this.done || converged && declarative;
+    }, {
+      key: "x",
+      value: function x(_x) {
+        return _x == null ? this.bbox().x : this.move(_x, this.bbox().y);
+      } // Move by left top corner over y-axis
 
-        if (this.done) {
-          this.fire('finish', this);
-        }
+    }, {
+      key: "y",
+      value: function y(_y) {
+        return _y == null ? this.bbox().y : this.move(this.bbox().x, _y);
+      } // Set element size to given width and height
 
-        return this;
-      }
     }, {
-      key: "finish",
-      value: function finish() {
-        return this.step(Infinity);
-      }
+      key: "size",
+      value: function size(width, height) {
+        var p = proportionalSize(this, width, height);
+        return this.attr('d', this.array().size(p.width, p.height));
+      } // Set width of element
+
     }, {
-      key: "reverse",
-      value: function reverse(_reverse) {
-        this._reverse = _reverse == null ? !this._reverse : _reverse;
-        return this;
-      }
+      key: "width",
+      value: function width(_width) {
+        return _width == null ? this.bbox().width : this.size(_width, this.bbox().height);
+      } // Set height of element
+
     }, {
-      key: "ease",
-      value: function ease(fn) {
-        this._stepper = new Ease(fn);
-        return this;
+      key: "height",
+      value: function height(_height) {
+        return _height == null ? this.bbox().height : this.size(this.bbox().width, _height);
       }
     }, {
-      key: "active",
-      value: function active(enabled) {
-        if (enabled == null) return this.enabled;
-        this.enabled = enabled;
-        return this;
+      key: "targets",
+      value: function targets() {
+        return baseFind('svg textpath [href*="' + this.id() + '"]');
       }
-      /*
-      Private Methods
-      ===============
-      Methods that shouldn't be used externally
-      */
-      // Save a morpher to the morpher list so that we can retarget it later
+    }]);
 
-    }, {
-      key: "_rememberMorpher",
-      value: function _rememberMorpher(method, morpher) {
-        this._history[method] = {
-          morpher: morpher,
-          caller: this._queue[this._queue.length - 1]
-        };
-      } // Try to set the target for a morpher if the morpher exists, otherwise
-      // do nothing and return false
+    return Path;
+  }(Shape); // Define morphable array
+  Path.prototype.MorphArray = PathArray; // Add parent method
 
-    }, {
-      key: "_tryRetarget",
-      value: function _tryRetarget(method, target) {
-        if (this._history[method]) {
-          // if the last method wasnt even initialised, throw it away
-          if (!this._history[method].caller.initialised) {
-            var index = this._queue.indexOf(this._history[method].caller);
+  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);
 
-            this._queue.splice(index, 1);
+  function array() {
+    return this._array || (this._array = new PointArray(this.attr('points')));
+  } // Plot new path
 
-            return false;
-          } // for the case of transformations, we use the special retarget function
-          // which has access to the outer scope
+  function plot(p) {
+    return p == null ? this.array() : this.clear().attr('points', typeof p === 'string' ? p : this._array = new PointArray(p));
+  } // Clear array cache
 
+  function clear() {
+    delete this._array;
+    return this;
+  } // Move by left top corner
 
-          if (this._history[method].caller.isTransform) {
-            this._history[method].caller.isTransform(target); // for everything else a simple morpher change is sufficient
+  function move(x, y) {
+    return this.attr('points', this.array().move(x, y));
+  } // Set element size to given width and height
 
-          } else {
-            this._history[method].morpher.to(target);
-          }
+  function size(width, height) {
+    var p = proportionalSize(this, width, height);
+    return this.attr('points', this.array().size(p.width, p.height));
+  }
 
-          this._history[method].caller.finished = false;
-          var timeline$$1 = this.timeline();
-          timeline$$1 && timeline$$1._continue();
-          return true;
-        }
+  var poly = /*#__PURE__*/Object.freeze({
+    array: array,
+    plot: plot,
+    clear: clear,
+    move: move,
+    size: size
+  });
 
-        return false;
-      } // Run each initialise function in the runner if required
+  var Polygon =
+  /*#__PURE__*/
+  function (_Shape) {
+    _inherits(Polygon, _Shape);
 
-    }, {
-      key: "_initialise",
-      value: function _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
+    // Initialize node
+    function Polygon(node) {
+      _classCallCheck(this, Polygon);
 
-        for (var i = 0, len = this._queue.length; i < len; ++i) {
-          // Get the current initialiser
-          var current = this._queue[i]; // Determine whether we need to initialise
+      return _possibleConstructorReturn(this, _getPrototypeOf(Polygon).call(this, nodeOrNew('polygon', node), node));
+    }
 
-          var needsIt = this._isDeclarative || !current.initialised && running;
-          running = !current.finished; // Call the initialiser if we need to
+    return Polygon;
+  }(Shape);
+  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);
 
-          if (needsIt && running) {
-            current.initialiser.call(this);
-            current.initialised = true;
-          }
-        }
-      } // Run each run function for the position or dt given
+  var Polyline =
+  /*#__PURE__*/
+  function (_Shape) {
+    _inherits(Polyline, _Shape);
 
-    }, {
-      key: "_run",
-      value: function _run(positionOrDt) {
-        // Run all of the _queue directly
-        var allfinished = true;
+    // Initialize node
+    function Polyline(node) {
+      _classCallCheck(this, Polyline);
 
-        for (var i = 0, len = this._queue.length; i < len; ++i) {
-          // Get the current function to run
-          var current = this._queue[i]; // Run the function if its not finished, we keep track of the finished
-          // flag for the sake of declarative _queue
-
-          var 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;
-      }
-    }, {
-      key: "addTransform",
-      value: function addTransform(transform, index) {
-        this.transforms.lmultiplyO(transform);
-        return this;
-      }
-    }, {
-      key: "clearTransform",
-      value: function clearTransform() {
-        this.transforms = new Matrix();
-        return this;
-      }
-    }], [{
-      key: "sanitise",
-      value: function sanitise(duration, delay, when) {
-        // Initialise the default parameters
-        var times = 1;
-        var swing = false;
-        var 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
-        };
-      }
-    }]);
-
-    return Runner;
-  }(EventTarget);
-  Runner.id = 0;
-
-  var FakeRunner = function FakeRunner() {
-    var transforms = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : new Matrix();
-    var id = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : -1;
-    var done = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : true;
-
-    _classCallCheck(this, FakeRunner);
-
-    this.transforms = transforms;
-    this.id = id;
-    this.done = done;
-  };
-
-  extend([Runner, FakeRunner], {
-    mergeWith: function mergeWith(runner) {
-      return new FakeRunner(runner.transforms.lmultiply(this.transforms), runner.id);
+      return _possibleConstructorReturn(this, _getPrototypeOf(Polyline).call(this, nodeOrNew('polyline', node), node));
     }
-  }); // FakeRunner.emptyRunner = new FakeRunner()
 
-  var lmultiply = function lmultiply(last, curr) {
-    return last.lmultiplyO(curr);
-  };
+    return Polyline;
+  }(Shape);
+  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);
 
-  var getRunnerTransform = function getRunnerTransform(runner) {
-    return runner.transforms;
-  };
+  var Rect =
+  /*#__PURE__*/
+  function (_Shape) {
+    _inherits(Rect, _Shape);
 
-  function mergeTransforms() {
-    // Find the matrix to apply to the element and apply it
-    var runners = this._transformationRunners.runners;
-    var netTransform = runners.map(getRunnerTransform).reduce(lmultiply, new Matrix());
-    this.transform(netTransform);
+    // Initialize node
+    function Rect(node) {
+      _classCallCheck(this, Rect);
 
-    this._transformationRunners.merge();
+      return _possibleConstructorReturn(this, _getPrototypeOf(Rect).call(this, nodeOrNew('rect', node), node));
+    }
 
-    if (this._transformationRunners.length() === 1) {
-      this._frameId = null;
+    return Rect;
+  }(Shape);
+  extend(Rect, {
+    rx: rx,
+    ry: ry
+  });
+  registerMethods({
+    Container: {
+      // Create a rect element
+      rect: wrapWithAttrCheck(function (width$$1, height$$1) {
+        return this.put(new Rect()).size(width$$1, height$$1);
+      })
     }
-  }
+  });
+  register(Rect);
 
-  var RunnerArray =
+  var Queue =
   /*#__PURE__*/
   function () {
-    function RunnerArray() {
-      _classCallCheck(this, RunnerArray);
+    function Queue() {
+      _classCallCheck(this, Queue);
 
-      this.runners = [];
-      this.ids = [];
+      this._first = null;
+      this._last = null;
     }
 
-    _createClass(RunnerArray, [{
-      key: "add",
-      value: function add(runner) {
-        if (this.runners.includes(runner)) return;
-        var id = runner.id + 1;
-        var leftSibling = this.ids.reduce(function (last, curr) {
-          if (curr > last && curr < id) return curr;
-          return last;
-        }, 0);
-        var index = this.ids.indexOf(leftSibling) + 1;
-        this.ids.splice(index, 0, id);
-        this.runners.splice(index, 0, runner);
-        return this;
-      }
-    }, {
-      key: "getByID",
-      value: function getByID(id) {
-        return this.runners[this.ids.indexOf(id + 1)];
-      }
-    }, {
-      key: "remove",
-      value: function remove(id) {
-        var index = this.ids.indexOf(id + 1);
-        this.ids.splice(index, 1);
-        this.runners.splice(index, 1);
-        return this;
-      }
-    }, {
-      key: "merge",
-      value: function merge() {
-        var _this2 = this;
+    _createClass(Queue, [{
+      key: "push",
+      value: function push(value) {
+        // An item stores an id and the provided value
+        var item = value.next ? value : {
+          value: value,
+          next: null,
+          prev: null // Deal with the queue being empty or populated
 
-        var lastRunner = null;
-        this.runners.forEach(function (runner, i) {
-          if (lastRunner && runner.done && lastRunner.done) {
-            _this2.remove(runner.id);
+        };
 
-            _this2.edit(lastRunner.id, runner.mergeWith(lastRunner));
-          }
+        if (this._last) {
+          item.prev = this._last;
+          this._last.next = item;
+          this._last = item;
+        } else {
+          this._last = item;
+          this._first = item;
+        } // Update the length and return the current item
 
-          lastRunner = runner;
-        });
-        return this;
+
+        return item;
       }
     }, {
-      key: "edit",
-      value: function edit(id, newRunner) {
-        var index = this.ids.indexOf(id + 1);
-        this.ids.splice(index, 1, id);
-        this.runners.splice(index, 1, newRunner);
-        return this;
-      }
+      key: "shift",
+      value: function shift() {
+        // Check if we have a value
+        var 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;
+      } // Shows us the first item in the list
+
     }, {
-      key: "length",
-      value: function length() {
-        return this.ids.length;
-      }
+      key: "first",
+      value: function first() {
+        return this._first && this._first.value;
+      } // Shows us the last item in the list
+
     }, {
-      key: "clearBefore",
-      value: function clearBefore(id) {
-        var deleteCnt = this.ids.indexOf(id + 1) || 1;
-        this.ids.splice(0, deleteCnt, 0);
-        this.runners.splice(0, deleteCnt, new FakeRunner());
-        return this;
+      key: "last",
+      value: function last() {
+        return this._last && this._last.value;
+      } // Removes the item that was returned from the push
+
+    }, {
+      key: "remove",
+      value: function 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;
       }
     }]);
 
-    return RunnerArray;
+    return Queue;
   }();
 
-  var frameId = 0;
-  registerMethods({
-    Element: {
-      animate: function animate(duration, delay, when) {
-        var o = Runner.sanitise(duration, delay, when);
-        var timeline$$1 = this.timeline();
-        return new Runner(o.duration).loop(o).element(this).timeline(timeline$$1).schedule(delay, when);
-      },
-      delay: function 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: function _clearTransformRunnersBefore(currentRunner) {
-        this._transformationRunners.clearBefore(currentRunner.id);
-      },
-      _currentTransform: function _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(function (runner) {
-          return runner.id <= current.id;
-        }).map(getRunnerTransform).reduce(lmultiply, new Matrix());
-      },
-      addRunner: function addRunner(runner) {
-        this._transformationRunners.add(runner);
+  var Animator = {
+    nextDraw: null,
+    frames: new Queue(),
+    timeouts: new Queue(),
+    timer: globals.window.performance || globals.window.Date,
+    transforms: [],
+    frame: function frame(fn) {
+      // Store the node
+      var node = Animator.frames.push({
+        run: fn
+      }); // Request an animation frame if we don't have one
 
-        Animator.transform_frame(mergeTransforms.bind(this), this._frameId);
-      },
-      _prepareRunner: function _prepareRunner() {
-        if (this._frameId == null) {
-          this._transformationRunners = new RunnerArray().add(new FakeRunner(new Matrix(this)));
-          this._frameId = frameId++;
-        }
-      }
-    }
-  });
-  extend(Runner, {
-    attr: function attr(a, v) {
-      return this.styleAttr('attr', a, v);
+      if (Animator.nextDraw === null) {
+        Animator.nextDraw = globals.window.requestAnimationFrame(Animator._draw);
+      } // Return the node so we can remove it easily
+
+
+      return node;
     },
-    // Add animatable styles
-    css: function css(s, v) {
-      return this.styleAttr('css', s, v);
+    transform_frame: function transform_frame(fn, id) {
+      Animator.transforms[id] = fn;
     },
-    styleAttr: function styleAttr(type, name, val) {
-      // apply attributes individually
-      if (_typeof(name) === 'object') {
-        for (var key in val) {
-          this.styleAttr(type, key, val[key]);
-        }
+    timeout: function timeout(fn, delay) {
+      delay = delay || 0; // Work out when the event should fire
+
+      var time = Animator.timer.now() + delay; // Add the timeout to the end of the queue
+
+      var 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);
       }
 
-      var morpher = new Morphable(this._stepper).to(val);
-      this.queue(function () {
-        morpher = morpher.from(this.element()[type](name));
-      }, function (pos) {
-        this.element()[type](name, morpher.at(pos));
-        return morpher.done();
-      });
-      return this;
+      return node;
     },
-    zoom: function zoom(level, point) {
-      var morpher = new Morphable(this._stepper).to(new SVGNumber(level));
-      this.queue(function () {
-        morpher = morpher.from(this.zoom());
-      }, function (pos) {
-        this.element().zoom(morpher.at(pos), point);
-        return morpher.done();
-      });
-      return this;
+    cancelFrame: function cancelFrame(node) {
+      Animator.frames.remove(node);
+    },
+    clearTimeout: function clearTimeout(node) {
+      Animator.timeouts.remove(node);
     },
+    _draw: function _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])
+      var nextTimeout = null;
+      var lastTimeout = Animator.timeouts.last();
 
-    /**
-     ** 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: function transform(transforms, relative, affine) {
-      // If we have a declarative function, we should retarget it if possible
-      relative = transforms.relative || relative;
+      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 (this._isDeclarative && !relative && this._tryRetarget('transform', transforms)) {
-        return this;
-      } // Parse the parameters
 
+        if (nextTimeout === lastTimeout) break;
+      } // Run all of the animation frames
 
-      var isMatrix = Matrix.isMatrixLike(transforms);
-      affine = transforms.affine != null ? transforms.affine : affine != null ? affine : !isMatrix; // Create a morepher and set its type
 
-      var morpher = new Morphable(this._stepper).type(affine ? TransformBag : Matrix);
-      var origin;
-      var element;
-      var current;
-      var currentAngle;
-      var startTransform;
+      var nextFrame = null;
+      var lastFrame = Animator.frames.last();
 
-      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
+      while (nextFrame !== lastFrame && (nextFrame = Animator.frames.shift())) {
+        nextFrame.run();
+      }
 
-        element.addRunner(this); // Deactivate all transforms that have run so far if we are absolute
+      Animator.transforms.forEach(function (el) {
+        el();
+      }); // If we have remaining timeouts or frames, draw until we don't anymore
 
-        if (!relative) {
-          element._clearTransformRunnersBefore(this);
-        }
-      }
+      Animator.nextDraw = Animator.timeouts.first() || Animator.frames.first() ? globals.window.requestAnimationFrame(Animator._draw) : null;
+    }
+  };
 
-      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();
+  var time = globals.window.performance || Date;
 
-        var _transform = new Point(origin).transform(element._currentTransform(this)),
-            x$$1 = _transform.x,
-            y$$1 = _transform.y;
+  var makeSchedule = function makeSchedule(runnerInfo) {
+    var start = runnerInfo.start;
+    var duration = runnerInfo.runner.duration();
+    var end = start + duration;
+    return {
+      start: start,
+      duration: duration,
+      end: end,
+      runner: runnerInfo.runner
+    };
+  };
 
-        var target = new Matrix(_objectSpread({}, transforms, {
-          origin: [x$$1, y$$1]
-        }));
-        var start = this._isDeclarative && current ? current : startTransform;
+  var Timeline =
+  /*#__PURE__*/
+  function () {
+    // Construct a new timeline on the given element
+    function Timeline() {
+      _classCallCheck(this, Timeline);
 
-        if (affine) {
-          target = target.decompose(x$$1, y$$1);
-          start = start.decompose(x$$1, y$$1); // Get the current and target angle as it was set
+      this._timeSource = function () {
+        return time.now();
+      };
 
-          var rTarget = target.rotate;
-          var rCurrent = start.rotate; // Figure out the shortest path to rotate directly
+      this._dispatcher = globals.document.createElement('div'); // Store the timing variables
 
-          var possibilities = [rTarget - 360, rTarget, rTarget + 360];
-          var distances = possibilities.map(function (a) {
-            return Math.abs(a - rCurrent);
+      this._startTime = 0;
+      this._speed = 1.0; // Play control variables control how the animation proceeds
+
+      this._reverse = false;
+      this._persist = 0; // Keep track of the running animations and their starting parameters
+
+      this._nextFrame = null;
+      this._paused = false;
+      this._runners = [];
+      this._order = [];
+      this._time = 0;
+      this._lastSourceTime = 0;
+      this._lastStepTime = 0;
+    }
+
+    _createClass(Timeline, [{
+      key: "getEventTarget",
+      value: function getEventTarget() {
+        return this._dispatcher;
+      }
+      /**
+       *
+       */
+      // schedules a runner on the timeline
+
+    }, {
+      key: "schedule",
+      value: function schedule(runner, delay, when) {
+        if (runner == null) {
+          return this._runners.map(makeSchedule).sort(function (a, b) {
+            return a.start - b.start || a.duration - b.duration;
           });
-          var shortest = Math.min.apply(Math, _toConsumableArray(distances));
-          var 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.active()) {
+          this._step();
 
-          if (this._isDeclarative && currentAngle) {
-            start.rotate = currentAngle;
+          if (when == null) {
+            when = 'now';
           }
-        }
+        } // The start time for the next animation can either be given explicitly,
+        // derived from the current timeline time or it can be relative to the
+        // last start time to chain animations direclty
 
-        morpher.from(start);
-        morpher.to(target);
-        var affineParameters = morpher.at(pos);
-        currentAngle = affineParameters.rotate;
-        current = new Matrix(affineParameters);
-        this.addTransform(current);
-        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(transforms, element);
-        } // overwrite the old transformations with the new ones
+        var absoluteStartTime = 0;
+        delay = delay || 0; // Work out when to start the animation
 
+        if (when == null || when === 'last' || when === 'after') {
+          // Take the last time and increment
+          absoluteStartTime = this._startTime;
+        } else if (when === 'absolute' || when === 'start') {
+          absoluteStartTime = delay;
+          delay = 0;
+        } else if (when === 'now') {
+          absoluteStartTime = this._time;
+        } else if (when === 'relative') {
+          var runnerInfo = this._runners[runner.id];
 
-        transforms = _objectSpread({}, newTransforms, {
-          origin: origin
-        });
-      }
+          if (runnerInfo) {
+            absoluteStartTime = runnerInfo.start + delay;
+            delay = 0;
+          }
+        } else {
+          throw new Error('Invalid value for the "when" parameter');
+        } // Manage runner
 
-      this.queue(setup, run, retarget);
-      this._isDeclarative && this._rememberMorpher('transform', morpher);
-      return this;
-    },
-    // Animatable x-axis
-    x: function x$$1(_x, relative) {
-      return this._queueNumber('x', _x);
-    },
-    // Animatable y-axis
-    y: function y$$1(_y) {
-      return this._queueNumber('y', _y);
-    },
-    dx: function dx(x$$1) {
-      return this._queueNumberDelta('dx', x$$1);
-    },
-    dy: function dy(y$$1) {
-      return this._queueNumberDelta('dy', y$$1);
-    },
-    _queueNumberDelta: function _queueNumberDelta(method, to$$1) {
-      to$$1 = new SVGNumber(to$$1); // Try to change the target if we have this method already registerd
 
-      if (this._tryRetargetDelta(method, to$$1)) return this; // Make a morpher and queue the animation
+        runner.unschedule();
+        runner.timeline(this);
+        runner.time(-delay); // Save startTime for next runner
 
-      var morpher = new Morphable(this._stepper).to(to$$1);
-      this.queue(function () {
-        var from$$1 = this.element()[method]();
-        morpher.from(from$$1);
-        morpher.to(from$$1 + to$$1);
-      }, 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._startTime = absoluteStartTime + runner.duration() + delay; // Save runnerInfo
 
-      this._rememberMorpher(method, morpher);
+        this._runners[runner.id] = {
+          persist: this.persist(),
+          runner: runner,
+          start: absoluteStartTime // Save order and continue
 
-      return this;
-    },
-    _queueObject: function _queueObject(method, to$$1) {
-      // Try to change the target if we have this method already registerd
-      if (this._tryRetarget(method, to$$1)) return this; // Make a morpher and queue the animation
+        };
 
-      var morpher = new Morphable(this._stepper).to(to$$1);
-      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._order.push(runner.id);
 
-      this._rememberMorpher(method, morpher);
+        this._continue();
 
-      return this;
-    },
-    _queueNumber: function _queueNumber(method, value) {
-      return this._queueObject(method, new SVGNumber(value));
-    },
-    // Animatable center x-axis
-    cx: function cx$$1(x$$1) {
-      return this._queueNumber('cx', x$$1);
-    },
-    // Animatable center y-axis
-    cy: function cy$$1(y$$1) {
-      return this._queueNumber('cy', y$$1);
-    },
-    // Add animatable move
-    move: function move(x$$1, y$$1) {
-      return this.x(x$$1).y(y$$1);
-    },
-    // Add animatable center
-    center: function center(x$$1, y$$1) {
-      return this.cx(x$$1).cy(y$$1);
-    },
-    // Add animatable size
-    size: function size(width$$1, height$$1) {
-      // animate bbox based size for all other elements
-      var box;
+        return this;
+      } // Remove the runner from this timeline
 
-      if (!width$$1 || !height$$1) {
-        box = this._element.bbox();
-      }
+    }, {
+      key: "unschedule",
+      value: function unschedule(runner) {
+        var index = this._order.indexOf(runner.id);
 
-      if (!width$$1) {
-        width$$1 = box.width / box.height * height$$1;
-      }
+        if (index < 0) return this;
+        delete this._runners[runner.id];
 
-      if (!height$$1) {
-        height$$1 = box.height / box.width * width$$1;
-      }
+        this._order.splice(index, 1);
 
-      return this.width(width$$1).height(height$$1);
-    },
-    // Add animatable width
-    width: function width$$1(_width) {
-      return this._queueNumber('width', _width);
-    },
-    // Add animatable height
-    height: function height$$1(_height) {
-      return this._queueNumber('height', _height);
-    },
-    // Add animatable plot
-    plot: function plot(a, b, c, d) {
-      // Lines can be plotted with 4 arguments
-      if (arguments.length === 4) {
-        return this.plot([a, b, c, d]);
+        runner.timeline(null);
+        return this;
       }
-
-      var morpher = this._element.MorphArray().to(a);
-
-      this.queue(function () {
-        morpher.from(this._element.array());
-      }, function (pos) {
-        this._element.plot(morpher.at(pos));
-      });
-      return this;
-    },
-    // Add leading method
-    leading: function leading(value) {
-      return this._queueNumber('leading', value);
-    },
-    // Add animatable viewbox
-    viewbox: function viewbox(x$$1, y$$1, width$$1, height$$1) {
-      return this._queueObject('viewbox', new Box(x$$1, y$$1, width$$1, height$$1));
-    },
-    update: function update(o) {
-      if (_typeof(o) !== 'object') {
-        return this.update({
-          offset: arguments[0],
-          color: arguments[1],
-          opacity: arguments[2]
-        });
+    }, {
+      key: "play",
+      value: function play() {
+        // Now make sure we are not paused and continue the animation
+        this._paused = false;
+        return this._continue();
       }
-
-      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: rx,
-    ry: ry,
-    from: from,
-    to: to
-  });
-
-  var sugar = {
-    stroke: ['color', 'width', 'opacity', 'linecap', 'linejoin', 'miterlimit', 'dasharray', 'dashoffset'],
-    fill: ['color', 'opacity', 'rule'],
-    prefix: function prefix(t, a) {
-      return a === 'color' ? t : t + '-' + a;
-    } // Add sugar for fill and stroke
-
-  };
-  ['fill', 'stroke'].forEach(function (m) {
-    var extension = {};
-    var i;
-
-    extension[m] = function (o) {
-      if (typeof o === 'undefined') {
-        return this.attr(m);
+    }, {
+      key: "pause",
+      value: function pause() {
+        // Cancel the next animation frame and pause
+        this._nextFrame = null;
+        this._paused = true;
+        return this;
       }
-
-      if (typeof o === 'string' || 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]]);
-          }
-        }
+    }, {
+      key: "stop",
+      value: function stop() {
+        // Cancel the next animation frame and go to start
+        this.seek(-this._time);
+        return this.pause();
       }
-
-      return this;
-    };
-
-    registerMethods(['Shape', 'Runner'], extension);
-  });
-  registerMethods(['Element', 'Runner'], {
-    // Let the user set the matrix directly
-    matrix: function matrix(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 rotate(angle, cx, cy) {
-      return this.transform({
-        rotate: angle,
-        ox: cx,
-        oy: cy
-      }, true);
-    },
-    // Map skew to transform
-    skew: function skew(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 shear(lam, cx, cy) {
-      return this.transform({
-        shear: lam,
-        ox: cx,
-        oy: cy
-      }, true);
-    },
-    // Map scale to transform
-    scale: function scale(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 translate(x, y) {
-      return this.transform({
-        translate: [x, y]
-      }, true);
-    },
-    // Map relative translations to transform
-    relative: function relative(x, y) {
-      return this.transform({
-        relative: [x, y]
-      }, true);
-    },
-    // Map flip to transform
-    flip: function flip(direction, around) {
-      var directionString = typeof direction === 'string' ? direction : isFinite(direction) ? 'both' : 'both';
-      var origin = direction === 'both' && isFinite(around) ? [around, around] : direction === 'x' ? [around, 0] : direction === 'y' ? [0, around] : isFinite(direction) ? [direction, direction] : [0, 0];
-      this.transform({
-        flip: directionString,
-        origin: origin
-      }, true);
-    },
-    // Opacity
-    opacity: function opacity(value) {
-      return this.attr('opacity', value);
-    },
-    // Relative move over x axis
-    dx: function dx(x) {
-      return this.x(new SVGNumber(x).plus(this instanceof Runner ? 0 : this.x()), true);
-    },
-    // Relative move over y axis
-    dy: function dy(y) {
-      return this.y(new SVGNumber(y).plus(this instanceof Runner ? 0 : this.y()), true);
-    },
-    // Relative move over x and y axes
-    dmove: function dmove(x, y) {
-      return this.dx(x).dy(y);
-    }
-  });
-  registerMethods('radius', {
-    // Add x and y radius
-    radius: function radius(x, y) {
-      var type = (this._element || this).type;
-      return type === 'radialGradient' || type === 'radialGradient' ? this.attr('r', new SVGNumber(x)) : this.rx(x).ry(y == null ? x : y);
-    }
-  });
-  registerMethods('Path', {
-    // Get path length
-    length: function length() {
-      return this.node.getTotalLength();
-    },
-    // Get point at length
-    pointAt: function pointAt(length) {
-      return new Point(this.node.getPointAtLength(length));
-    }
-  });
-  registerMethods(['Element', 'Runner'], {
-    // Set font
-    font: function font(a, v) {
-      if (_typeof(a) === 'object') {
-        for (v in a) {
-          this.font(v, a[v]);
-        }
+    }, {
+      key: "finish",
+      value: function finish() {
+        this.seek(Infinity);
+        return this.pause();
       }
-
-      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('Text', {
-    ax: function ax(x) {
-      return this.attr('x', x);
-    },
-    ay: function ay(y) {
-      return this.attr('y', y);
-    },
-    amove: function amove(x, y) {
-      return this.ax(x).ay(y);
-    }
-  }); // Add events to elements
-
-  var methods$1 = ['click', 'dblclick', 'mousedown', 'mouseup', 'mouseover', 'mouseout', 'mousemove', 'mouseenter', 'mouseleave', 'touchstart', 'touchmove', 'touchleave', 'touchend', 'touchcancel'].reduce(function (last, event) {
-    // add event to Element
-    var fn = function fn(f) {
-      if (f === null) {
-        off(this, event);
-      } else {
-        on(this, event, f);
+    }, {
+      key: "speed",
+      value: function speed(_speed) {
+        if (_speed == null) return this._speed;
+        this._speed = _speed;
+        return this;
       }
-
-      return this;
-    };
-
-    last[event] = fn;
-    return last;
-  }, {});
-  registerMethods('Element', methods$1);
-
-  function untransform() {
-    return this.attr('transform', null);
-  } // merge the whole transformation chain into one matrix and returns it
-
-  function matrixify() {
-    var matrix = (this.attr('transform') || ''). // split transformations
-    split(transforms).slice(0, -1).map(function (str) {
-      // generate key => value pairs
-      var 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]));
+    }, {
+      key: "reverse",
+      value: function reverse(yes) {
+        var currentSpeed = this.speed();
+        if (yes == null) return this.speed(-currentSpeed);
+        var positive = Math.abs(currentSpeed);
+        return this.speed(yes ? positive : -positive);
       }
-
-      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) {
-    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
-
-  function toDoc() {
-    return this.toParent(this.doc());
-  } // Add transformations
-
-  function transform(o, relative) {
-    // Act as a getter if no object was passed
-    if (o == null || typeof o === 'string') {
-      var decomposed = new Matrix(this).decompose();
-      return decomposed[o] || decomposed;
-    }
-
-    if (!Matrix.isMatrixLike(o)) {
-      // Set the origin according to the defined transform
-      o = _objectSpread({}, o, {
-        origin: getOrigin(o, this)
-      });
-    } // The user can pass a boolean, an Element or an Matrix or nothing
-
-
-    var cleanRelative = relative === true ? this : relative || false;
-    var result = new Matrix(cleanRelative).transform(o);
-    return this.attr('transform', result);
-  }
-  registerMethods('Element', {
-    untransform: untransform,
-    matrixify: matrixify,
-    toParent: toParent,
-    toDoc: toDoc,
-    transform: transform
-  });
-
-  var List = subClassArray('List', Array, function (arr) {
-    this.length = 0;
-    this.push.apply(this, _toConsumableArray(arr));
-  });
-  extend(List, {
-    each: function each(cbOrName) {
-      for (var _len = arguments.length, args = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
-        args[_key - 1] = arguments[_key];
+    }, {
+      key: "seek",
+      value: function seek(dt) {
+        this._time += dt;
+        return this._continue();
       }
-
-      if (typeof cbOrName === 'function') {
-        this.forEach(function (el) {
-          cbOrName.call(el, el);
-        });
-      } else {
-        this.forEach(function (el) {
-          el[cbOrName].apply(el, args);
-        });
+    }, {
+      key: "time",
+      value: function time(_time) {
+        if (_time == null) return this._time;
+        this._time = _time;
+        return this;
+      }
+    }, {
+      key: "persist",
+      value: function persist(dtOrForever) {
+        if (dtOrForever == null) return this._persist;
+        this._persist = dtOrForever;
+        return this;
+      }
+    }, {
+      key: "source",
+      value: function source(fn) {
+        if (fn == null) return this._timeSource;
+        this._timeSource = fn;
+        return this;
       }
+    }, {
+      key: "_step",
+      value: function _step() {
+        // If the timeline is paused, just do nothing
+        if (this._paused) return; // Get the time delta from the last time and update the time
+
+        var time = this._timeSource();
 
-      return this;
-    },
-    toArray: function toArray() {
-      return Array.prototype.concat.apply([], this);
-    }
-  });
+        var dtSource = time - this._lastSourceTime;
+        var dtTime = this._speed * dtSource + (this._time - this._lastStepTime);
+        this._lastSourceTime = time; // Update the time
 
-  List.extend = function (methods) {
-    methods = methods.reduce(function (obj, name) {
-      obj[name] = function () {
-        for (var _len2 = arguments.length, attrs = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
-          attrs[_key2] = arguments[_key2];
-        }
+        this._time += dtTime;
+        this._lastStepTime = this._time; // this.fire('time', this._time)
+        // Run all of the runners directly
 
-        return this.each.apply(this, [name].concat(attrs));
-      };
+        var runnersLeft = false;
 
-      return obj;
-    }, {});
-    extend(List, methods);
-  };
+        for (var i = 0, len = this._order.length; i < len; i++) {
+          // Get and run the current runner and ignore it if its inactive
+          var runnerInfo = this._runners[this._order[i]];
+          var runner = runnerInfo.runner;
+          var dt = dtTime; // Make sure that we give the actual difference
+          // between runner start time and now
 
-  var Shape =
-  /*#__PURE__*/
-  function (_Element) {
-    _inherits(Shape, _Element);
+          var dtToStart = this._time - runnerInfo.start; // Dont run runner if not started yet
 
-    function Shape() {
-      _classCallCheck(this, Shape);
+          if (dtToStart < 0) {
+            runnersLeft = true;
+            continue;
+          } else if (dtToStart < dt) {
+            // Adjust dt to make sure that animation is on point
+            dt = dtToStart;
+          }
 
-      return _possibleConstructorReturn(this, _getPrototypeOf(Shape).apply(this, arguments));
-    }
+          if (!runner.active()) continue; // If this runner is still going, signal that we need another animation
+          // frame, otherwise, remove the completed runner
 
-    return Shape;
-  }(Element);
-  register(Shape);
+          var finished = runner.step(dt).done;
 
-  var Circle =
-  /*#__PURE__*/
-  function (_Shape) {
-    _inherits(Circle, _Shape);
+          if (!finished) {
+            runnersLeft = true; // continue
+          } else if (runnerInfo.persist !== true) {
+            // runner is finished. And runner might get removed
+            var endTime = runner.duration() - runner.time() + this._time;
 
-    function Circle(node) {
-      _classCallCheck(this, Circle);
+            if (endTime + this._persist < this._time) {
+              // Delete runner and correct index
+              delete this._runners[this._order[i]];
+              this._order.splice(i--, 1) && --len;
+              runner.timeline(null);
+            }
+          }
+        } // Get the next animation frame to keep the simulation going
 
-      return _possibleConstructorReturn(this, _getPrototypeOf(Circle).call(this, nodeOrNew('circle', node), node));
-    }
 
-    _createClass(Circle, [{
-      key: "radius",
-      value: function radius(r) {
-        return this.attr('r', r);
-      } // Radius x value
+        if (runnersLeft) {
+          this._nextFrame = Animator.frame(this._step.bind(this));
+        } else {
+          this._nextFrame = null;
+        }
 
-    }, {
-      key: "rx",
-      value: function rx$$1(_rx) {
-        return this.attr('r', _rx);
-      } // Alias radius x value
+        return this;
+      } // Checks if we are running and continues the animation
 
     }, {
-      key: "ry",
-      value: function ry$$1(_ry) {
-        return this.rx(_ry);
+      key: "_continue",
+      value: function _continue() {
+        if (this._paused) return this;
+
+        if (!this._nextFrame) {
+          this._nextFrame = Animator.frame(this._step.bind(this));
+        }
+
+        return this;
       }
     }, {
-      key: "size",
-      value: function size(_size) {
-        return this.radius(new SVGNumber(_size).divide(2));
+      key: "active",
+      value: function active() {
+        return !!this._nextFrame;
       }
     }]);
 
-    return Circle;
-  }(Shape);
-  extend(Circle, {
-    x: x,
-    y: y,
-    cx: cx,
-    cy: cy,
-    width: width,
-    height: height
-  });
+    return Timeline;
+  }();
   registerMethods({
     Element: {
-      // Create circle element
-      circle: wrapWithAttrCheck(function (size) {
-        return this.put(new Circle()).size(size).move(0, 0);
-      })
-    }
-  });
-  register(Circle);
-
-  var Ellipse =
-  /*#__PURE__*/
-  function (_Shape) {
-    _inherits(Ellipse, _Shape);
-
-    function Ellipse(node) {
-      _classCallCheck(this, Ellipse);
-
-      return _possibleConstructorReturn(this, _getPrototypeOf(Ellipse).call(this, nodeOrNew('ellipse', node), node));
-    }
-
-    _createClass(Ellipse, [{
-      key: "size",
-      value: function size(width$$1, height$$1) {
-        var p = proportionalSize(this, width$$1, height$$1);
-        return this.rx(new SVGNumber(p.width).divide(2)).ry(new SVGNumber(p.height).divide(2));
+      timeline: function timeline() {
+        this._timeline = this._timeline || new Timeline();
+        return this._timeline;
       }
-    }]);
-
-    return Ellipse;
-  }(Shape);
-  extend(Ellipse, circled);
-  registerMethods('Container', {
-    // Create an ellipse
-    ellipse: wrapWithAttrCheck(function (width$$1, height$$1) {
-      return this.put(new Ellipse()).size(width$$1, height$$1).move(0, 0);
-    })
+    }
   });
-  register(Ellipse);
 
-  var Stop =
+  var Runner =
   /*#__PURE__*/
-  function (_Element) {
-    _inherits(Stop, _Element);
-
-    function Stop(node) {
-      _classCallCheck(this, Stop);
-
-      return _possibleConstructorReturn(this, _getPrototypeOf(Stop).call(this, nodeOrNew('stop', node), node));
-    } // add color stops
-
-
-    _createClass(Stop, [{
-      key: "update",
-      value: function update(o) {
-        if (typeof o === 'number' || o instanceof SVGNumber) {
-          o = {
-            offset: arguments[0],
-            color: arguments[1],
-            opacity: arguments[2]
-          };
-        } // set attributes
+  function (_EventTarget) {
+    _inherits(Runner, _EventTarget);
 
+    function Runner(options) {
+      var _this;
 
-        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;
-      }
-    }]);
+      _classCallCheck(this, Runner);
 
-    return Stop;
-  }(Element);
-  register(Stop);
+      _this = _possibleConstructorReturn(this, _getPrototypeOf(Runner).call(this)); // Store a unique id on the runner, so that we can identify it later
 
-  function baseFind(query, parent) {
-    return map((parent || globals.document).querySelectorAll(query), function (node) {
-      return adopt(node);
-    });
-  } // Scoped find method
+      _this.id = Runner.id++; // Ensure a default value
 
-  function find(query) {
-    return baseFind(query, this.node);
-  }
-  registerMethods('Dom', {
-    find: find
-  });
+      options = options == null ? timeline.duration : options; // Ensure that we get a controller
 
-  var Gradient =
-  /*#__PURE__*/
-  function (_Container) {
-    _inherits(Gradient, _Container);
+      options = typeof options === 'function' ? new Controller(options) : options; // Declare all of the variables
 
-    function Gradient(type, attrs) {
-      _classCallCheck(this, Gradient);
+      _this._element = null;
+      _this._timeline = null;
+      _this.done = false;
+      _this._queue = []; // Work out the stepper and the duration
 
-      return _possibleConstructorReturn(this, _getPrototypeOf(Gradient).call(this, nodeOrNew(type + 'Gradient', typeof type === 'string' ? null : type), attrs));
-    } // Add a color stop
+      _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
 
-    _createClass(Gradient, [{
-      key: "stop",
-      value: function stop(offset, color, opacity) {
-        return this.put(new Stop()).update(offset, color, opacity);
-      } // Update gradient
+      _this.enabled = true;
+      _this._time = 0;
+      _this._last = 0; // Save transforms applied to this runner
 
-    }, {
-      key: "update",
-      value: function update(block) {
-        // remove all stops
-        this.clear(); // invoke passed block
+      _this.transforms = new Matrix();
+      _this.transformId = 1; // Looping variables
 
-        if (typeof block === 'function') {
-          block.call(this, this);
-        }
+      _this._haveReversed = false;
+      _this._reverse = false;
+      _this._loopsDone = 0;
+      _this._swing = false;
+      _this._wait = 0;
+      _this._times = 1;
+      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
+    */
 
-        return this;
-      } // Return the fill id
 
-    }, {
-      key: "url",
-      value: function url() {
-        return 'url(#' + this.id() + ')';
-      } // Alias string convertion to fill
+    _createClass(Runner, [{
+      key: "element",
+      value: function element(_element) {
+        if (_element == null) return this._element;
+        this._element = _element;
 
-    }, {
-      key: "toString",
-      value: function toString() {
-        return this.url();
-      } // custom attr to handle transform
+        _element._prepareRunner();
 
-    }, {
-      key: "attr",
-      value: function attr(a, b, c) {
-        if (a === 'transform') a = 'gradientTransform';
-        return _get(_getPrototypeOf(Gradient.prototype), "attr", this).call(this, a, b, c);
+        return this;
       }
     }, {
-      key: "targets",
-      value: function targets() {
-        return baseFind('svg [fill*="' + this.id() + '"]');
+      key: "timeline",
+      value: function timeline$$1(_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;
       }
     }, {
-      key: "bbox",
-      value: function bbox() {
-        return new Box();
+      key: "animate",
+      value: function animate(duration, delay, when) {
+        var o = Runner.sanitise(duration, delay, when);
+        var runner = new Runner(o.duration);
+        if (this._timeline) runner.timeline(this._timeline);
+        if (this._element) runner.element(this._element);
+        return runner.loop(o).schedule(delay, when);
       }
-    }]);
-
-    return Gradient;
-  }(Container);
-  extend(Gradient, gradiented);
-  registerMethods({
-    Container: {
-      // Create gradient element in defs
-      gradient: wrapWithAttrCheck(function (type, block) {
-        return this.defs().gradient(type, block);
-      })
-    },
-    // define gradient
-    Defs: {
-      gradient: wrapWithAttrCheck(function (type, block) {
-        return this.put(new Gradient(type)).update(block);
-      })
-    }
-  });
-  register(Gradient);
-
-  var Pattern =
-  /*#__PURE__*/
-  function (_Container) {
-    _inherits(Pattern, _Container);
-
-    // Initialize node
-    function Pattern(node) {
-      _classCallCheck(this, Pattern);
+    }, {
+      key: "schedule",
+      value: function schedule(timeline$$1, delay, when) {
+        // The user doesn't need to pass a timeline if we already have one
+        if (!(timeline$$1 instanceof Timeline)) {
+          when = delay;
+          delay = timeline$$1;
+          timeline$$1 = this.timeline();
+        } // If there is no timeline, yell at the user...
 
-      return _possibleConstructorReturn(this, _getPrototypeOf(Pattern).call(this, nodeOrNew('pattern', node), node));
-    } // Return the fill id
 
+        if (!timeline$$1) {
+          throw Error('Runner cannot be scheduled without timeline');
+        } // Schedule the runner on the timeline provided
 
-    _createClass(Pattern, [{
-      key: "url",
-      value: function url() {
-        return 'url(#' + this.id() + ')';
-      } // Update pattern by rebuilding
 
+        timeline$$1.schedule(this, delay, when);
+        return this;
+      }
     }, {
-      key: "update",
-      value: function update(block) {
-        // remove content
-        this.clear(); // invoke passed block
+      key: "unschedule",
+      value: function unschedule() {
+        var timeline$$1 = this.timeline();
+        timeline$$1 && timeline$$1.unschedule(this);
+        return this;
+      }
+    }, {
+      key: "loop",
+      value: function 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
 
-        if (typeof block === 'function') {
-          block.call(this, this);
-        }
 
+        this._times = times || Infinity;
+        this._swing = swing || false;
+        this._wait = wait || 0;
         return this;
-      } // Alias string convertion to fill
-
+      }
     }, {
-      key: "toString",
-      value: function toString() {
-        return this.url();
-      } // custom attr to handle transform
+      key: "delay",
+      value: function delay(_delay) {
+        return this.animate(0, _delay);
+      }
+      /*
+      Basic Functionality
+      ===================
+      These methods allow us to attach basic functions to the runner directly
+      */
 
     }, {
-      key: "attr",
-      value: function attr(a, b, c) {
-        if (a === 'transform') a = 'patternTransform';
-        return _get(_getPrototypeOf(Pattern.prototype), "attr", this).call(this, a, b, c);
+      key: "queue",
+      value: function queue(initFn, runFn, retargetFn, isTransform) {
+        this._queue.push({
+          initialiser: initFn || noop,
+          runner: runFn || noop,
+          retarget: retargetFn,
+          isTransform: isTransform,
+          initialised: false,
+          finished: false
+        });
+
+        var timeline$$1 = this.timeline();
+        timeline$$1 && this.timeline()._continue();
+        return this;
       }
     }, {
-      key: "targets",
-      value: function targets() {
-        return baseFind('svg [fill*="' + this.id() + '"]');
+      key: "during",
+      value: function during(fn) {
+        return this.queue(null, fn);
       }
     }, {
-      key: "bbox",
-      value: function bbox() {
-        return new Box();
+      key: "after",
+      value: function after(fn) {
+        return this.on('finish', fn);
       }
-    }]);
+      /*
+      Runner animation methods
+      ========================
+      Control how the animation plays
+      */
 
-    return Pattern;
-  }(Container);
-  registerMethods({
-    Container: {
-      // Create pattern element in defs
-      pattern: function pattern() {
-        var _this$defs;
+    }, {
+      key: "time",
+      value: function time(_time) {
+        if (_time == null) {
+          return this._time;
+        }
 
-        return (_this$defs = this.defs()).pattern.apply(_this$defs, arguments);
+        var dt = _time - this._time;
+        this.step(dt);
+        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);
-
-  var Image =
-  /*#__PURE__*/
-  function (_Shape) {
-    _inherits(Image, _Shape);
+    }, {
+      key: "duration",
+      value: function duration() {
+        return this._times * (this._wait + this._duration) - this._wait;
+      }
+    }, {
+      key: "loops",
+      value: function loops(p) {
+        var loopDuration = this._duration + this._wait;
 
-    function Image(node) {
-      _classCallCheck(this, Image);
+        if (p == null) {
+          var loopsDone = Math.floor(this._time / loopDuration);
+          var relativeTime = this._time - loopsDone * loopDuration;
+          var position = relativeTime / this._duration;
+          return Math.min(loopsDone + position, this._times);
+        }
 
-      return _possibleConstructorReturn(this, _getPrototypeOf(Image).call(this, nodeOrNew('image', node), node));
-    } // (re)load image
+        var whole = Math.floor(p);
+        var partial = p % 1;
+        var time = loopDuration * whole + this._duration * partial;
+        return this.time(time);
+      }
+    }, {
+      key: "position",
+      value: function position(p) {
+        // Get all of the variables we need
+        var x$$1 = this._time;
+        var d = this._duration;
+        var w = this._wait;
+        var t = this._times;
+        var s = this._swing;
+        var r = this._reverse;
+        var 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
+          var f = function f(x$$1) {
+            var swinging = s * Math.floor(x$$1 % (2 * (w + d)) / (w + d));
+            var backwards = swinging && !r || !swinging && r;
+            var uncliped = Math.pow(-1, backwards) * (x$$1 % (w + d)) / d + backwards;
+            var clipped = Math.max(Math.min(uncliped, 1), 0);
+            return clipped;
+          }; // Figure out the value by incorporating the start time
 
-    _createClass(Image, [{
-      key: "load",
-      value: function load(url, callback) {
-        if (!url) return this;
-        var img = new globals.window.Image();
-        on(img, 'load', function (e) {
-          var p = this.parent(Pattern); // ensure image size
 
-          if (this.width() === 0 && this.height() === 0) {
-            this.size(img.width, img.height);
-          }
+          var endTime = t * (w + d) - w;
+          position = x$$1 <= 0 ? Math.round(f(1e-5)) : x$$1 < endTime ? f(x$$1) : Math.round(f(endTime - 1e-5));
+          return position;
+        } // Work out the loops done and add the position to the loops done
 
-          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, {
-              width: img.width,
-              height: img.height,
-              ratio: img.width / img.height,
-              url: url
-            });
-          }
-        }, this);
-        on(img, 'load error', function () {
-          // dont forget to unbind memory leaking events
-          off(img);
-        });
-        return this.attr('href', img.src = url, xlink);
+        var loopsDone = Math.floor(this.loops());
+        var swingForward = s && loopsDone % 2 === 0;
+        var forwards = swingForward && !r || r && swingForward;
+        position = loopsDone + (forwards ? p : 1 - p);
+        return this.loops(position);
       }
-    }]);
+    }, {
+      key: "progress",
+      value: function progress(p) {
+        if (p == null) {
+          return Math.min(1, this._time / this.duration());
+        }
 
-    return Image;
-  }(Shape);
-  registerAttrHook(function (attr$$1, val, _this) {
-    // convert image fill and stroke to patterns
-    if (attr$$1 === 'fill' || attr$$1 === 'stroke') {
-      if (isImage.test(val)) {
-        val = _this.doc().defs().image(val);
+        return this.time(p * this.duration());
       }
-    }
+    }, {
+      key: "step",
+      value: function step(dt) {
+        // If we are inactive, this stepper just gets skipped
+        if (!this.enabled) return this; // Update the time and get the new position
 
-    if (val instanceof Image) {
-      val = _this.doc().defs().pattern(0, 0, function (pattern) {
-        pattern.add(val);
-      });
-    }
+        dt = dt == null ? 16 : dt;
+        this._time += dt;
+        var position = this.position(); // Figure out if we need to run the stepper in this frame
 
-    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);
+        var running = this._lastPosition !== position && this._time >= 0;
+        this._lastPosition = position; // Figure out if we just started
 
-  var PointArray = subClassArray('PointArray', SVGArray);
-  extend(PointArray, {
-    // Convert array to string
-    toString: function toString() {
-      // convert to a poly point string
-      for (var i = 0, il = this.length, array = []; i < il; i++) {
-        array.push(this[i].join(','));
-      }
+        var duration = this.duration();
+        var justStarted = this._lastTime < 0 && this._time > 0;
+        var justFinished = this._lastTime < this._time && this.time > duration;
+        this._lastTime = this._time;
 
-      return array.join(' ');
-    },
-    // Convert array to line object
-    toLine: function toLine() {
-      return {
-        x1: this[0][0],
-        y1: this[0][1],
-        x2: this[1][0],
-        y2: this[1][1]
-      };
-    },
-    // Get morphed array at given position
-    at: function at(pos) {
-      // make sure a destination is defined
-      if (!this.destination) return this; // generate morphed point string
+        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)
 
-      for (var i = 0, il = this.length, array = []; i < il; i++) {
-        array.push([this[i][0] + (this.destination[i][0] - this[i][0]) * pos, this[i][1] + (this.destination[i][1] - this[i][1]) * pos]);
-      }
 
-      return new PointArray(array);
-    },
-    // Parse point string and flat array
-    parse: function parse() {
-      var array = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [[0, 0]];
-      var points = []; // if it is an array
+        var declarative = this._isDeclarative;
+        this.done = !declarative && !justFinished && this._time >= duration; // Call initialise and the run function
 
-      if (array instanceof Array) {
-        // and it is not flat, there is no need to parse it
-        if (array[0] instanceof Array) {
-          return 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 (running || declarative) {
+          this._initialise(running); // clear the transforms on this runner so they dont get added again and again
 
 
-      if (array.length % 2 !== 0) array.pop(); // wrap points in two-tuples and parse points as floats
+          this.transforms = new Matrix();
 
-      for (var i = 0, len = array.length; i < len; i = i + 2) {
-        points.push([array[i], array[i + 1]]);
-      }
+          var converged = this._run(declarative ? dt : position);
 
-      return points;
-    },
-    // Move point string
-    move: function move(x, y) {
-      var box = this.bbox(); // get relative offset
+          this.fire('step', this);
+        } // correct the done flag here
+        // declaritive animations itself know when they converged
 
-      x -= box.x;
-      y -= box.y; // move every point
 
-      if (!isNaN(x) && !isNaN(y)) {
-        for (var i = this.length - 1; i >= 0; i--) {
-          this[i] = [this[i][0] + x, this[i][1] + y];
-        }
-      }
+        this.done = this.done || converged && declarative;
 
-      return this;
-    },
-    // Resize poly string
-    size: function size(width, height) {
-      var i;
-      var box = this.bbox(); // recalculate position of all points according to new size
+        if (this.done) {
+          this.fire('finish', this);
+        }
 
-      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;
       }
+    }, {
+      key: "finish",
+      value: function finish() {
+        return this.step(Infinity);
+      }
+    }, {
+      key: "reverse",
+      value: function reverse(_reverse) {
+        this._reverse = _reverse == null ? !this._reverse : _reverse;
+        return this;
+      }
+    }, {
+      key: "ease",
+      value: function ease(fn) {
+        this._stepper = new Ease(fn);
+        return this;
+      }
+    }, {
+      key: "active",
+      value: function active(enabled) {
+        if (enabled == null) return this.enabled;
+        this.enabled = enabled;
+        return this;
+      }
+      /*
+      Private Methods
+      ===============
+      Methods that shouldn't be used externally
+      */
+      // Save a morpher to the morpher list so that we can retarget it later
 
-      return this;
-    },
-    // Get bounding box of points
-    bbox: function bbox() {
-      var maxX = -Infinity;
-      var maxY = -Infinity;
-      var minX = Infinity;
-      var 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 {
-        x: minX,
-        y: minY,
-        width: maxX - minX,
-        height: maxY - minY
-      };
-    }
-  });
-
-  var MorphArray = PointArray; // Move by left top corner over x-axis
-
-  function x$1(x) {
-    return x == null ? this.bbox().x : this.move(x, this.bbox().y);
-  } // Move by left top corner over y-axis
-
-  function y$1(y) {
-    return y == null ? this.bbox().y : this.move(this.bbox().x, y);
-  } // Set width of element
+    }, {
+      key: "_rememberMorpher",
+      value: function _rememberMorpher(method, morpher) {
+        this._history[method] = {
+          morpher: morpher,
+          caller: this._queue[this._queue.length - 1]
+        };
+      } // Try to set the target for a morpher if the morpher exists, otherwise
+      // do nothing and return false
 
-  function width$1(width) {
-    var b = this.bbox();
-    return width == null ? b.width : this.size(width, b.height);
-  } // Set height of element
+    }, {
+      key: "_tryRetarget",
+      value: function _tryRetarget(method, target) {
+        if (this._history[method]) {
+          // if the last method wasnt even initialised, throw it away
+          if (!this._history[method].caller.initialised) {
+            var index = this._queue.indexOf(this._history[method].caller);
 
-  function height$1(height) {
-    var b = this.bbox();
-    return height == null ? b.height : this.size(b.width, height);
-  }
+            this._queue.splice(index, 1);
 
-  var pointed = /*#__PURE__*/Object.freeze({
-    MorphArray: MorphArray,
-    x: x$1,
-    y: y$1,
-    width: width$1,
-    height: height$1
-  });
+            return false;
+          } // for the case of transformations, we use the special retarget function
+          // which has access to the outer scope
 
-  var Line =
-  /*#__PURE__*/
-  function (_Shape) {
-    _inherits(Line, _Shape);
 
-    // Initialize node
-    function Line(node) {
-      _classCallCheck(this, Line);
+          if (this._history[method].caller.retarget) {
+            this._history[method].caller.retarget(target); // for everything else a simple morpher change is sufficient
 
-      return _possibleConstructorReturn(this, _getPrototypeOf(Line).call(this, nodeOrNew('line', node), node));
-    } // Get array
+          } else {
+            this._history[method].morpher.to(target);
+          }
 
+          this._history[method].caller.finished = false;
+          var timeline$$1 = this.timeline();
+          timeline$$1 && timeline$$1._continue();
+          return true;
+        }
 
-    _createClass(Line, [{
-      key: "array",
-      value: function array() {
-        return new PointArray([[this.attr('x1'), this.attr('y1')], [this.attr('x2'), this.attr('y2')]]);
-      } // Overwrite native plot() method
+        return false;
+      } // Run each initialise function in the runner if required
 
     }, {
-      key: "plot",
-      value: function plot(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 PointArray(x1).toLine();
+      key: "_initialise",
+      value: function _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 (var i = 0, len = this._queue.length; i < len; ++i) {
+          // Get the current initialiser
+          var current = this._queue[i]; // Determine whether we need to initialise
+
+          var 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;
+          }
         }
+      } // Run each run function for the position or dt given
 
-        return this.attr(x1);
-      } // Move by left top corner
+    }, {
+      key: "_run",
+      value: function _run(positionOrDt) {
+        // Run all of the _queue directly
+        var allfinished = true;
+
+        for (var i = 0, len = this._queue.length; i < len; ++i) {
+          // Get the current function to run
+          var current = this._queue[i]; // Run the function if its not finished, we keep track of the finished
+          // flag for the sake of declarative _queue
+
+          var 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;
+      }
     }, {
-      key: "move",
-      value: function move(x, y) {
-        return this.attr(this.array().move(x, y).toLine());
-      } // Set element size to given width and height
+      key: "addTransform",
+      value: function addTransform(transform, index) {
+        this.transforms.lmultiplyO(transform);
+        return this;
+      }
+    }, {
+      key: "clearTransform",
+      value: function clearTransform() {
+        this.transforms = new Matrix();
+        return this;
+      } // TODO: Keep track of all transformations so that deletion is faster
 
     }, {
-      key: "size",
-      value: function size(width, height) {
-        var p = proportionalSize(this, width, height);
-        return this.attr(this.array().size(p.width, p.height).toLine());
+      key: "clearTransformsFromQueue",
+      value: function clearTransformsFromQueue() {
+        if (!this.done) {
+          this._queue = this._queue.filter(function (item) {
+            return !item.isTransform;
+          });
+        }
       }
-    }]);
+    }], [{
+      key: "sanitise",
+      value: function sanitise(duration, delay, when) {
+        // Initialise the default parameters
+        var times = 1;
+        var swing = false;
+        var wait = 0;
+        duration = duration || timeline.duration;
+        delay = delay || timeline.delay;
+        when = when || 'last'; // If we have an object, unpack the values
 
-    return Line;
-  }(Shape);
-  extend(Line, pointed);
-  registerMethods({
-    Container: {
-      // Create a line element
-      line: wrapWithAttrCheck(function () {
-        for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
-          args[_key] = arguments[_key];
+        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;
         }
 
-        // 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);
+        return {
+          duration: duration,
+          delay: delay,
+          swing: swing,
+          times: times,
+          wait: wait,
+          when: when
+        };
+      }
+    }]);
 
-  var Marker =
+    return Runner;
+  }(EventTarget);
+  Runner.id = 0;
+
+  var FakeRunner =
   /*#__PURE__*/
-  function (_Container) {
-    _inherits(Marker, _Container);
+  function () {
+    function FakeRunner() {
+      var transforms = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : new Matrix();
+      var id = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : -1;
+      var done = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : true;
 
-    // Initialize node
-    function Marker(node) {
-      _classCallCheck(this, Marker);
+      _classCallCheck(this, FakeRunner);
 
-      return _possibleConstructorReturn(this, _getPrototypeOf(Marker).call(this, nodeOrNew('marker', node), node));
-    } // Set width of element
+      this.transforms = transforms;
+      this.id = id;
+      this.done = done;
+    }
 
+    _createClass(FakeRunner, [{
+      key: "clearTransformsFromQueue",
+      value: function clearTransformsFromQueue() {}
+    }]);
 
-    _createClass(Marker, [{
-      key: "width",
-      value: function width(_width) {
-        return this.attr('markerWidth', _width);
-      } // Set height of element
+    return FakeRunner;
+  }();
 
-    }, {
-      key: "height",
-      value: function height(_height) {
-        return this.attr('markerHeight', _height);
-      } // Set marker refX and refY
+  extend([Runner, FakeRunner], {
+    mergeWith: function mergeWith(runner) {
+      return new FakeRunner(runner.transforms.lmultiply(this.transforms), runner.id);
+    }
+  }); // FakeRunner.emptyRunner = new FakeRunner()
 
-    }, {
-      key: "ref",
-      value: function ref(x, y) {
-        return this.attr('refX', x).attr('refY', y);
-      } // Update marker
+  var lmultiply = function lmultiply(last, curr) {
+    return last.lmultiplyO(curr);
+  };
 
+  var getRunnerTransform = function getRunnerTransform(runner) {
+    return runner.transforms;
+  };
+
+  function mergeTransforms() {
+    // Find the matrix to apply to the element and apply it
+    var runners = this._transformationRunners.runners;
+    var netTransform = runners.map(getRunnerTransform).reduce(lmultiply, new Matrix());
+    this.transform(netTransform);
+
+    this._transformationRunners.merge();
+
+    if (this._transformationRunners.length() === 1) {
+      this._frameId = null;
+    }
+  }
+
+  var RunnerArray =
+  /*#__PURE__*/
+  function () {
+    function RunnerArray() {
+      _classCallCheck(this, RunnerArray);
+
+      this.runners = [];
+      this.ids = [];
+    }
+
+    _createClass(RunnerArray, [{
+      key: "add",
+      value: function add(runner) {
+        if (this.runners.includes(runner)) return;
+        var id = runner.id + 1;
+        var leftSibling = this.ids.reduce(function (last, curr) {
+          if (curr > last && curr < id) return curr;
+          return last;
+        }, 0);
+        var index = this.ids.indexOf(leftSibling) + 1;
+        this.ids.splice(index, 0, id);
+        this.runners.splice(index, 0, runner);
+        return this;
+      }
     }, {
-      key: "update",
-      value: function update(block) {
-        // remove all content
-        this.clear(); // invoke passed block
+      key: "getByID",
+      value: function getByID(id) {
+        return this.runners[this.ids.indexOf(id + 1)];
+      }
+    }, {
+      key: "remove",
+      value: function remove(id) {
+        var index = this.ids.indexOf(id + 1);
+        this.ids.splice(index, 1);
+        this.runners.splice(index, 1);
+        return this;
+      }
+    }, {
+      key: "merge",
+      value: function merge() {
+        var _this2 = this;
+
+        var lastRunner = null;
+        this.runners.forEach(function (runner, i) {
+          if (lastRunner && runner.done && lastRunner.done) {
+            _this2.remove(runner.id);
 
-        if (typeof block === 'function') {
-          block.call(this, this);
-        }
+            _this2.edit(lastRunner.id, runner.mergeWith(lastRunner));
+          }
 
+          lastRunner = runner;
+        });
         return this;
-      } // Return the fill id
-
+      }
     }, {
-      key: "toString",
-      value: function toString() {
-        return 'url(#' + this.id() + ')';
+      key: "edit",
+      value: function edit(id, newRunner) {
+        var index = this.ids.indexOf(id + 1);
+        this.ids.splice(index, 1, id);
+        this.runners.splice(index, 1, newRunner);
+        return this;
+      }
+    }, {
+      key: "length",
+      value: function length() {
+        return this.ids.length;
+      }
+    }, {
+      key: "clearBefore",
+      value: function clearBefore(id) {
+        var deleteCnt = this.ids.indexOf(id + 1) || 1;
+        this.ids.splice(0, deleteCnt, 0);
+        this.runners.splice(0, deleteCnt, new FakeRunner()).forEach(function (r) {
+          return r.clearTransformsFromQueue();
+        });
+        return this;
       }
     }]);
 
-    return Marker;
-  }(Container);
+    return RunnerArray;
+  }();
+
+  var frameId = 0;
   registerMethods({
-    Container: {
-      marker: function marker() {
-        var _this$defs;
+    Element: {
+      animate: function animate(duration, delay, when) {
+        var o = Runner.sanitise(duration, delay, when);
+        var timeline$$1 = this.timeline();
+        return new Runner(o.duration).loop(o).element(this).timeline(timeline$$1).schedule(delay, when);
+      },
+      delay: function 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: function _clearTransformRunnersBefore(currentRunner) {
+        this._transformationRunners.clearBefore(currentRunner.id);
+      },
+      _currentTransform: function _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(function (runner) {
+          return runner.id <= current.id;
+        }).map(getRunnerTransform).reduce(lmultiply, new Matrix());
+      },
+      addRunner: function addRunner(runner) {
+        this._transformationRunners.add(runner);
 
-        // Create marker element in defs
-        return (_this$defs = this.defs()).marker.apply(_this$defs, arguments);
+        Animator.transform_frame(mergeTransforms.bind(this), this._frameId);
+      },
+      _prepareRunner: function _prepareRunner() {
+        if (this._frameId == null) {
+          this._transformationRunners = new RunnerArray().add(new FakeRunner(new Matrix(this)));
+          this._frameId = frameId++;
+        }
       }
+    }
+  });
+  extend(Runner, {
+    attr: function attr(a, v) {
+      return this.styleAttr('attr', a, v);
     },
-    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);
-      })
+    // Add animatable styles
+    css: function css(s, v) {
+      return this.styleAttr('css', s, v);
     },
-    marker: {
-      // Create and attach markers
-      marker: function marker(_marker, width, height, block) {
-        var attr = ['marker']; // Build attribute name
+    styleAttr: function styleAttr(type, name, val) {
+      // apply attributes individually
+      if (_typeof(name) === 'object') {
+        for (var key in val) {
+          this.styleAttr(type, key, val[key]);
+        }
+      }
 
-        if (_marker !== 'all') attr.push(_marker);
-        attr = attr.join('-'); // Set marker attribute
+      var morpher = new Morphable(this._stepper).to(val);
+      this.queue(function () {
+        morpher = morpher.from(this.element()[type](name));
+      }, function (pos) {
+        this.element()[type](name, morpher.at(pos));
+        return morpher.done();
+      });
+      return this;
+    },
+    zoom: function zoom(level, point) {
+      var morpher = new Morphable(this._stepper).to(new SVGNumber(level));
+      this.queue(function () {
+        morpher = morpher.from(this.zoom());
+      }, function (pos) {
+        this.element().zoom(morpher.at(pos), point);
+        return morpher.done();
+      });
+      return this;
+    },
 
-        _marker = arguments[1] instanceof Marker ? arguments[1] : this.defs().marker(width, height, block);
-        return this.attr(attr, _marker);
-      }
-    }
-  });
-  register(Marker);
+    /**
+     ** 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: function transform(transforms, relative, affine) {
+      // If we have a declarative function, we should retarget it if possible
+      relative = transforms.relative || relative;
 
-  var Path =
-  /*#__PURE__*/
-  function (_Shape) {
-    _inherits(Path, _Shape);
+      if (this._isDeclarative && !relative && this._tryRetarget('transform', transforms)) {
+        return this;
+      } // Parse the parameters
 
-    // Initialize node
-    function Path(node) {
-      _classCallCheck(this, Path);
 
-      return _possibleConstructorReturn(this, _getPrototypeOf(Path).call(this, nodeOrNew('path', node), node));
-    } // Get array
+      var isMatrix = Matrix.isMatrixLike(transforms);
+      affine = transforms.affine != null ? transforms.affine : affine != null ? affine : !isMatrix; // Create a morepher and set its type
 
+      var morpher = new Morphable(this._stepper).type(affine ? TransformBag : Matrix);
+      var origin;
+      var element;
+      var current;
+      var currentAngle;
+      var startTransform;
 
-    _createClass(Path, [{
-      key: "array",
-      value: function array() {
-        return this._array || (this._array = new PathArray(this.attr('d')));
-      } // Plot new path
+      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
 
-    }, {
-      key: "plot",
-      value: function plot(d) {
-        return d == null ? this.array() : this.clear().attr('d', typeof d === 'string' ? d : this._array = new PathArray(d));
-      } // Clear array cache
+        element.addRunner(this); // Deactivate all transforms that have run so far if we are absolute
 
-    }, {
-      key: "clear",
-      value: function clear() {
-        delete this._array;
-        return this;
-      } // Move by left top corner
+        if (!relative) {
+          element._clearTransformRunnersBefore(this);
+        }
+      }
 
-    }, {
-      key: "move",
-      value: function move(x, y) {
-        return this.attr('d', this.array().move(x, y));
-      } // Move by left top corner over x-axis
+      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();
 
-    }, {
-      key: "x",
-      value: function x(_x) {
-        return _x == null ? this.bbox().x : this.move(_x, this.bbox().y);
-      } // Move by left top corner over y-axis
+        var _transform = new Point(origin).transform(element._currentTransform(this)),
+            x$$1 = _transform.x,
+            y$$1 = _transform.y;
 
-    }, {
-      key: "y",
-      value: function y(_y) {
-        return _y == null ? this.bbox().y : this.move(this.bbox().x, _y);
-      } // Set element size to given width and height
+        var target = new Matrix(_objectSpread({}, transforms, {
+          origin: [x$$1, y$$1]
+        }));
+        var start = this._isDeclarative && current ? current : startTransform;
 
-    }, {
-      key: "size",
-      value: function size(width, height) {
-        var p = proportionalSize(this, width, height);
-        return this.attr('d', this.array().size(p.width, p.height));
-      } // Set width of element
+        if (affine) {
+          target = target.decompose(x$$1, y$$1);
+          start = start.decompose(x$$1, y$$1); // Get the current and target angle as it was set
 
-    }, {
-      key: "width",
-      value: function width(_width) {
-        return _width == null ? this.bbox().width : this.size(_width, this.bbox().height);
-      } // Set height of element
+          var rTarget = target.rotate;
+          var rCurrent = start.rotate; // Figure out the shortest path to rotate directly
+
+          var possibilities = [rTarget - 360, rTarget, rTarget + 360];
+          var distances = possibilities.map(function (a) {
+            return Math.abs(a - rCurrent);
+          });
+          var shortest = Math.min.apply(Math, _toConsumableArray(distances));
+          var 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;
+          }
+        }
 
-    }, {
-      key: "height",
-      value: function height(_height) {
-        return _height == null ? this.bbox().height : this.size(this.bbox().width, _height);
-      }
-    }, {
-      key: "targets",
-      value: function targets() {
-        return baseFind('svg textpath [href*="' + this.id() + '"]');
+        morpher.from(start);
+        morpher.to(target);
+        var affineParameters = morpher.at(pos);
+        currentAngle = affineParameters.rotate;
+        current = new Matrix(affineParameters);
+        this.addTransform(current);
+        return morpher.done();
       }
-    }]);
-
-    return Path;
-  }(Shape); // 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);
+      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(transforms, element);
+        } // overwrite the old transformations with the new ones
 
-  function array() {
-    return this._array || (this._array = new PointArray(this.attr('points')));
-  } // Plot new path
 
-  function plot(p) {
-    return p == null ? this.array() : this.clear().attr('points', typeof p === 'string' ? p : this._array = new PointArray(p));
-  } // Clear array cache
+        transforms = _objectSpread({}, newTransforms, {
+          origin: origin
+        });
+      }
 
-  function clear() {
-    delete this._array;
-    return this;
-  } // Move by left top corner
+      this.queue(setup, run, retarget, true);
+      this._isDeclarative && this._rememberMorpher('transform', morpher);
+      return this;
+    },
+    // Animatable x-axis
+    x: function x$$1(_x, relative) {
+      return this._queueNumber('x', _x);
+    },
+    // Animatable y-axis
+    y: function y$$1(_y) {
+      return this._queueNumber('y', _y);
+    },
+    dx: function dx(x$$1) {
+      return this._queueNumberDelta('x', x$$1);
+    },
+    dy: function dy(y$$1) {
+      return this._queueNumberDelta('y', y$$1);
+    },
+    _queueNumberDelta: function _queueNumberDelta(method, to$$1) {
+      to$$1 = new SVGNumber(to$$1); // Try to change the target if we have this method already registerd
 
-  function move(x, y) {
-    return this.attr('points', this.array().move(x, y));
-  } // Set element size to given width and height
+      if (this._tryRetarget(method, to$$1)) return this; // Make a morpher and queue the animation
 
-  function size(width, height) {
-    var p = proportionalSize(this, width, height);
-    return this.attr('points', this.array().size(p.width, p.height));
-  }
+      var morpher = new Morphable(this._stepper).to(to$$1);
+      var from$$1 = null;
+      this.queue(function () {
+        from$$1 = this.element()[method]();
+        morpher.from(from$$1);
+        morpher.to(from$$1 + to$$1);
+      }, function (pos) {
+        this.element()[method](morpher.at(pos));
+        return morpher.done();
+      }, function (newTo) {
+        morpher.to(from$$1 + new SVGNumber(newTo));
+      }); // Register the morpher so that if it is changed again, we can retarget it
 
-  var poly = /*#__PURE__*/Object.freeze({
-    array: array,
-    plot: plot,
-    clear: clear,
-    move: move,
-    size: size
-  });
+      this._rememberMorpher(method, morpher);
 
-  var Polygon =
-  /*#__PURE__*/
-  function (_Shape) {
-    _inherits(Polygon, _Shape);
+      return this;
+    },
+    _queueObject: function _queueObject(method, to$$1) {
+      // Try to change the target if we have this method already registerd
+      if (this._tryRetarget(method, to$$1)) return this; // Make a morpher and queue the animation
 
-    // Initialize node
-    function Polygon(node) {
-      _classCallCheck(this, Polygon);
+      var morpher = new Morphable(this._stepper).to(to$$1);
+      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
 
-      return _possibleConstructorReturn(this, _getPrototypeOf(Polygon).call(this, nodeOrNew('polygon', node), node));
-    }
+      this._rememberMorpher(method, morpher);
 
-    return Polygon;
-  }(Shape);
-  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);
+      return this;
+    },
+    _queueNumber: function _queueNumber(method, value) {
+      return this._queueObject(method, new SVGNumber(value));
+    },
+    // Animatable center x-axis
+    cx: function cx$$1(x$$1) {
+      return this._queueNumber('cx', x$$1);
+    },
+    // Animatable center y-axis
+    cy: function cy$$1(y$$1) {
+      return this._queueNumber('cy', y$$1);
+    },
+    // Add animatable move
+    move: function move(x$$1, y$$1) {
+      return this.x(x$$1).y(y$$1);
+    },
+    // Add animatable center
+    center: function center(x$$1, y$$1) {
+      return this.cx(x$$1).cy(y$$1);
+    },
+    // Add animatable size
+    size: function size(width$$1, height$$1) {
+      // animate bbox based size for all other elements
+      var box;
 
-  var Polyline =
-  /*#__PURE__*/
-  function (_Shape) {
-    _inherits(Polyline, _Shape);
+      if (!width$$1 || !height$$1) {
+        box = this._element.bbox();
+      }
 
-    // Initialize node
-    function Polyline(node) {
-      _classCallCheck(this, Polyline);
+      if (!width$$1) {
+        width$$1 = box.width / box.height * height$$1;
+      }
 
-      return _possibleConstructorReturn(this, _getPrototypeOf(Polyline).call(this, nodeOrNew('polyline', node), node));
-    }
+      if (!height$$1) {
+        height$$1 = box.height / box.width * width$$1;
+      }
 
-    return Polyline;
-  }(Shape);
-  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);
+      return this.width(width$$1).height(height$$1);
+    },
+    // Add animatable width
+    width: function width$$1(_width) {
+      return this._queueNumber('width', _width);
+    },
+    // Add animatable height
+    height: function height$$1(_height) {
+      return this._queueNumber('height', _height);
+    },
+    // Add animatable plot
+    plot: function plot(a, b, c, d) {
+      // Lines can be plotted with 4 arguments
+      if (arguments.length === 4) {
+        return this.plot([a, b, c, d]);
+      }
 
-  var Rect =
-  /*#__PURE__*/
-  function (_Shape) {
-    _inherits(Rect, _Shape);
+      var morpher = this._element.MorphArray().to(a);
 
-    // Initialize node
-    function Rect(node) {
-      _classCallCheck(this, Rect);
+      this.queue(function () {
+        morpher.from(this._element.array());
+      }, function (pos) {
+        this._element.plot(morpher.at(pos));
+      });
+      return this;
+    },
+    // Add leading method
+    leading: function leading(value) {
+      return this._queueNumber('leading', value);
+    },
+    // Add animatable viewbox
+    viewbox: function viewbox(x$$1, y$$1, width$$1, height$$1) {
+      return this._queueObject('viewbox', new Box(x$$1, y$$1, width$$1, height$$1));
+    },
+    update: function update(o) {
+      if (_typeof(o) !== 'object') {
+        return this.update({
+          offset: arguments[0],
+          color: arguments[1],
+          opacity: arguments[2]
+        });
+      }
 
-      return _possibleConstructorReturn(this, _getPrototypeOf(Rect).call(this, nodeOrNew('rect', node), node));
+      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;
     }
-
-    return Rect;
-  }(Shape);
-  extend(Rect, {
-    rx: rx,
-    ry: ry
   });
-  registerMethods({
-    Container: {
-      // Create a rect element
-      rect: wrapWithAttrCheck(function (width$$1, height$$1) {
-        return this.put(new Rect()).size(width$$1, height$$1);
-      })
-    }
+  extend(Runner, {
+    rx: rx,
+    ry: ry,
+    from: from,
+    to: to
   });
-  register(Rect);
 
   function plain(text) {
     // clear if build mode is disabled
@@ -7019,6 +7062,7 @@ var SVG = (function () {
   extend(Shape, getMethodsFor('Shape')); // extend(Element, getConstructor('Memory'))
 
   extend(Container, getMethodsFor('Container'));
+  extend(Runner, getMethodsFor('Runner'));
   List.extend(getMethodNames());
   registerMorphableType([SVGNumber, Color, Box, Matrix, SVGArray, PointArray, PathArray]);
   makeMorphable();
index f79d7d522d01f12220c3f2f9afcadb23de7fd359..906dfe7a51654b2c3532a53c6553077bdaf3dbbf 100644 (file)
@@ -247,8 +247,8 @@ describe('Sugar', function() {
     it('redirects to x() / y() with adding the current value', function() {
       rect.dx(5)
       rect.dy(5)
-      expect(rect.x).toHaveBeenCalledWith(jasmine.objectContaining(new SVG.SVGNumber('5')), true)
-      expect(rect.y).toHaveBeenCalledWith(jasmine.objectContaining(new SVG.SVGNumber('5')), true)
+      expect(rect.x).toHaveBeenCalledWith(jasmine.objectContaining(new SVG.SVGNumber('5')))
+      expect(rect.y).toHaveBeenCalledWith(jasmine.objectContaining(new SVG.SVGNumber('5')))
     })
 
     it('allows to add a percentage value', function() {
@@ -257,16 +257,16 @@ describe('Sugar', function() {
       rect.dx('5%')
       rect.dy('5%')
 
-      expect(rect.x).toHaveBeenCalledWith(jasmine.objectContaining(new SVG.SVGNumber('10%')), true)
-      expect(rect.y).toHaveBeenCalledWith(jasmine.objectContaining(new SVG.SVGNumber('10%')), true)
+      expect(rect.x).toHaveBeenCalledWith(jasmine.objectContaining(new SVG.SVGNumber('10%')))
+      expect(rect.y).toHaveBeenCalledWith(jasmine.objectContaining(new SVG.SVGNumber('10%')))
     })
 
     it('allows to add a percentage value when no x/y is set', function() {
       rect.dx('5%')
       rect.dy('5%')
 
-      expect(rect.x).toHaveBeenCalledWith(jasmine.objectContaining(new SVG.SVGNumber('5%')), true)
-      expect(rect.y).toHaveBeenCalledWith(jasmine.objectContaining(new SVG.SVGNumber('5%')), true)
+      expect(rect.x).toHaveBeenCalledWith(jasmine.objectContaining(new SVG.SVGNumber('5%')))
+      expect(rect.y).toHaveBeenCalledWith(jasmine.objectContaining(new SVG.SVGNumber('5%')))
     })
   })
 
index 4de127a900109de17ba28c692e1f5dcc7729e5f8..47929fdba68a5a51b94b1c1f711aa0ef9be801ca 100644 (file)
@@ -141,10 +141,11 @@ export default class Runner extends EventTarget {
   These methods allow us to attach basic functions to the runner directly
   */
 
-  queue (initFn, runFn, isTransform) {
+  queue (initFn, runFn, retargetFn, isTransform) {
     this._queue.push({
       initialiser: initFn || noop,
       runner: runFn || noop,
+      retarget: retargetFn,
       isTransform: isTransform,
       initialised: false,
       finished: false
@@ -338,8 +339,8 @@ export default class Runner extends EventTarget {
 
       // for the case of transformations, we use the special retarget function
       // which has access to the outer scope
-      if (this._history[method].caller.isTransform) {
-        this._history[method].caller.isTransform(target)
+      if (this._history[method].caller.retarget) {
+        this._history[method].caller.retarget(target)
       // for everything else a simple morpher change is sufficient
       } else {
         this._history[method].morpher.to(target)
@@ -404,6 +405,15 @@ export default class Runner extends EventTarget {
     return this
   }
 
+  // TODO: Keep track of all transformations so that deletion is faster
+  clearTransformsFromQueue () {
+    if (!this.done) {
+      this._queue = this._queue.filter((item) => {
+        return !item.isTransform
+      })
+    }
+  }
+
   static sanitise (duration, delay, when) {
     // Initialise the default parameters
     var times = 1
@@ -442,6 +452,8 @@ class FakeRunner {
     this.id = id
     this.done = done
   }
+
+  clearTransformsFromQueue () { }
 }
 
 extend([Runner, FakeRunner], {
@@ -538,6 +550,7 @@ class RunnerArray {
     let 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
   }
 }
@@ -758,7 +771,7 @@ extend(Runner, {
       transforms = { ...newTransforms, origin }
     }
 
-    this.queue(setup, run, retarget)
+    this.queue(setup, run, retarget, true)
     this._isDeclarative && this._rememberMorpher('transform', morpher)
     return this
   },
@@ -774,28 +787,31 @@ extend(Runner, {
   },
 
   dx (x) {
-    return this._queueNumberDelta('dx', x)
+    return this._queueNumberDelta('x', x)
   },
 
   dy (y) {
-    return this._queueNumberDelta('dy', y)
+    return this._queueNumberDelta('y', y)
   },
 
   _queueNumberDelta (method, to) {
     to = new SVGNumber(to)
 
     // Try to change the target if we have this method already registerd
-    if (this._tryRetargetDelta(method, to)) return this
+    if (this._tryRetarget(method, to)) return this
 
     // Make a morpher and queue the animation
     var morpher = new Morphable(this._stepper).to(to)
+    var from = null
     this.queue(function () {
-      var from = this.element()[method]()
+      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
index a7deaaa30aa9d9ce98de447eadc443abfa830631..701b23bb294c4891b50a4e884e6fcdb66fbf4b9c 100644 (file)
@@ -7,7 +7,6 @@ import './modules/optional/memory.js'
 import './modules/optional/sugar.js'
 import './modules/optional/transform.js'
 
-import List from './types/List.js'
 import { extend } from './utils/adopter.js'
 import { getMethodNames, getMethodsFor } from './utils/methods.js'
 import Box from './types/Box.js'
@@ -23,6 +22,7 @@ import EventTarget from './types/EventTarget.js'
 import Gradient from './elements/Gradient.js'
 import Image from './elements/Image.js'
 import Line from './elements/Line.js'
+import List from './types/List.js'
 import Marker from './elements/Marker.js'
 import Matrix from './types/Matrix.js'
 import Morphable, {
@@ -39,6 +39,7 @@ import PointArray from './types/PointArray.js'
 import Polygon from './elements/Polygon.js'
 import Polyline from './elements/Polyline.js'
 import Rect from './elements/Rect.js'
+import Runner from './animation/Runner.js'
 import SVGArray from './types/SVGArray.js'
 import SVGNumber from './types/SVGNumber.js'
 import Shape from './elements/Shape.js'
@@ -154,6 +155,8 @@ extend(Shape, getMethodsFor('Shape'))
 // extend(Element, getConstructor('Memory'))
 extend(Container, getMethodsFor('Container'))
 
+extend(Runner, getMethodsFor('Runner'))
+
 List.extend(getMethodNames())
 
 registerMorphableType([
index f4c20fc848ff27eff920d8e8685ddde213298436..60016318fef853df5a1f0451e8410a687cffda77 100644 (file)
@@ -4,7 +4,6 @@ import Color from '../../types/Color.js'
 import Element from '../../elements/Element.js'
 import Matrix from '../../types/Matrix.js'
 import Point from '../../types/Point.js'
-import Runner from '../../animation/Runner.js'
 import SVGNumber from '../../types/SVGNumber.js'
 
 // Define list of available attributes for stroke and fill
@@ -105,19 +104,21 @@ registerMethods(['Element', 'Runner'], {
     return this.attr('opacity', value)
   },
 
+  // Relative move over x and y axes
+  dmove: function (x, y) {
+    return this.dx(x).dy(y)
+  }
+})
+
+registerMethods('Element', {
   // Relative move over x axis
   dx: function (x) {
-    return this.x(new SVGNumber(x).plus(this instanceof Runner ? 0 : this.x()), true)
+    return this.x(new SVGNumber(x).plus(this.x()))
   },
 
   // Relative move over y axis
   dy: function (y) {
-    return this.y(new SVGNumber(y).plus(this instanceof Runner ? 0 : this.y()), true)
-  },
-
-  // Relative move over x and y axes
-  dmove: function (x, y) {
-    return this.dx(x).dy(y)
+    return this.y(new SVGNumber(y).plus(this.y()))
   }
 })
 
index 839a970f98c7e7bdfa53d51d1b7c3108ab6826a9..4d2309fd4a541c3cbd0738f09a9ac20cabc039d4 100644 (file)
@@ -24,6 +24,12 @@ export const subClassArray = (function () {
       Arr.prototype = Object.create(baseClass.prototype)
       Arr.prototype.constructor = Arr
 
+      Arr.prototype.map = function (fn) {
+        const arr = new Arr()
+        arr.push.apply(arr, Array.prototype.map.call(this, fn))
+        return arr
+      }
+
       return Arr
     }
   }
index 193ed05b1cd09794d9d2b158def09ae5d28181f1..b50a18e97ff6c455d86397e1cce0112894236d3c 100644 (file)
@@ -1,7 +1,9 @@
 import { extend } from '../utils/adopter.js'
 import { subClassArray } from './ArrayPolyfill.js'
 
-const List = subClassArray('List', Array, function (arr) {
+const List = subClassArray('List', Array, function (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(...arr)
 })
@@ -13,9 +15,10 @@ extend(List, {
     if (typeof fnOrMethodName === 'function') {
       this.forEach((el) => { fnOrMethodName.call(el, el) })
     } else {
-      this.forEach((el) => {
-        el[fnOrMethodName](...args)
-      })
+      return this.map(el => { return el[fnOrMethodName](...args) })
+      // this.forEach((el) => {
+      //   el[fnOrMethodName](...args)
+      // })
     }
 
     return this
index 4fcb5000c24d3bd190d53b7a564bfb3a9c71121b..7f27ec48e3f3d027b4ca8adcf66f8a1e72500309 100644 (file)
@@ -10,6 +10,8 @@ export default SVGArray
 
 extend(SVGArray, {
   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