]> source.dussan.org Git - svg.js.git/commitdiff
Make the code in the FX module simpler 538/head
authorRémi Tétreault <tetreault.remi@gmail.com>
Thu, 20 Oct 2016 05:04:12 +0000 (01:04 -0400)
committerRémi Tétreault <tetreault.remi@gmail.com>
Wed, 2 Nov 2016 00:05:16 +0000 (20:05 -0400)
Here are the changes that have been made:
  - The loop counter is now incrementing (from 0 to loops)
  - The loop counter increment even when loops is true
  - Add absPos, the absolute position of an animation which is its position in
 the context of its complete duration (including delay and loops)
  - Make the methods speed, pause/resume affect the delay
  - The method step no longer needs to recalculate the value of situation.start
  - Add a second parameter to the method at to allow it to receive an absolute
  position
  - Fix the bug where the method at invert the passed position while the
  animation is running backward

Also, I modified the tests of the FX module that required timing to use
Jasmine Clock, so now it takes less than 1 second to run all the tests!

dist/svg.js
dist/svg.min.js
spec/spec/fx.js
src/fx.js

index dd5cb64d83253ac327ee01756333452df1ed8e13..e94fe3879d3c621dd1235c847ad00947f2a71fdb 100644 (file)
@@ -6,7 +6,7 @@
 * @copyright Wout Fierens <wout@mick-wout.com.com>
 * @license MIT
 *
-* BUILT: Tue Nov 01 2016 17:15:59 GMT-0400 (EDT)
+* BUILT: Tue Nov 01 2016 20:04:39 GMT-0400 (EDT)
 */;
 (function(root, factory) {
   if (typeof define === 'function' && define.amd) {
@@ -1240,7 +1240,9 @@ SVG.Situation = SVG.invent({
     this.finish = this.start + this.duration
     this.ease = o.ease
 
-    this.loop = false
+    // this.loop is incremented from 0 to this.loops
+    // it is also incremented when in an infinite loop (when this.loops is true)
+    this.loop = 0
     this.loops = false
 
     this.animations = {
@@ -1286,6 +1288,9 @@ SVG.FX = SVG.invent({
     this.paused = false
     this.lastPos = 0
     this.pos = 0
+    // The absolute position of an animation is its position in the context of its complete duration (including delay and loops)
+    // When performing a delay, absPos is below 0 and when performing a loop, its value is above 1
+    this.absPos = 0
     this._speed = 1
   }
 
@@ -1342,14 +1347,14 @@ SVG.FX = SVG.invent({
       return this._target
     }
 
-    // returns the position at a given time
-  , timeToPos: function(timestamp){
+    // returns the absolute position at a given time
+  , timeToAbsPos: function(timestamp){
       return (timestamp - this.situation.start) / (this.situation.duration/this._speed)
     }
 
-    // returns the timestamp from a given positon
-  , posToTime: function(pos){
-      return this.situation.duration/this._speed * pos + this.situation.start
+    // returns the timestamp from a given absolute positon
+  , absPosToTime: function(absPos){
+      return this.situation.duration/this._speed * absPos + this.situation.start
     }
 
     // starts the animationloop
@@ -1367,7 +1372,7 @@ SVG.FX = SVG.invent({
   , start: function(){
       // dont start if already started
       if(!this.active && this.situation){
-        this.situation.start = +new Date + this.situation.delay
+        this.situation.start = +new Date + this.situation.delay/this._speed
         this.situation.finish = this.situation.start + this.situation.duration/this._speed
 
         this.initAnimations()
@@ -1407,7 +1412,7 @@ SVG.FX = SVG.invent({
 
         var fn = function(){
           if(this.situation instanceof SVG.Situation)
-            this.initAnimations().at(0)
+            this.initAnimations().atStart()
           else if(this.situation instanceof SVG.Delay)
             this.dequeue()
           else
@@ -1498,15 +1503,7 @@ SVG.FX = SVG.invent({
       this.active = false
 
       if(jumpToEnd && this.situation){
-
-        this.situation.loop = false
-
-        if(this.situation.loops % 2 == 0 && this.situation.reversing){
-          this.situation.reversed = true
-        }
-
-        this.at(1)
-
+        this.atEnd()
       }
 
       this.stopAnimFrame()
@@ -1523,7 +1520,7 @@ SVG.FX = SVG.invent({
         var temp = this.situation
         this.stop()
         this.situation = temp
-        this.at(0)
+        this.atStart()
       }
       return this
     }
@@ -1540,13 +1537,40 @@ SVG.FX = SVG.invent({
       return this
     }
 
+    // set the internal animation pointer at the start position, before any loops, and updates the visualisation
+  , atStart: function() {
+    return this.at(0, true)
+  }
+
+    // set the internal animation pointer at the end position, after all the loops, and updates the visualisation
+  , atEnd: function() {
+    if (this.situation.loops === true) {
+      // If in a infinite loop, we end the current iteration
+      return this.at(this.situation.loop+1, true)
+    } else if(typeof this.situation.loops == 'number') {
+      // If performing a finite number of loops, we go after all the loops
+      return this.at(this.situation.loops, true)
+    } else {
+      // If no loops, we just go at the end
+      return this.at(1, true)
+    }
+  }
+
     // set the internal animation pointer to the specified position and updates the visualisation
-  , at: function(pos){
+    // if isAbsPos is true, pos is treated as an absolute position
+  , at: function(pos, isAbsPos){
       var durDivSpd = this.situation.duration/this._speed
 
-      this.pos = pos
-      this.situation.start = +new Date - pos * durDivSpd
+      this.absPos = pos
+      // If pos is not an absolute position, we convert it into one
+      if (!isAbsPos) {
+        if (this.situation.reversed) this.absPos = 1 - this.absPos
+        this.absPos += this.situation.loop
+      }
+
+      this.situation.start = +new Date - this.absPos * durDivSpd
       this.situation.finish = this.situation.start + durDivSpd
+
       return this.step(true)
     }
 
@@ -1560,7 +1584,8 @@ SVG.FX = SVG.invent({
 
       if (speed) {
         this._speed = speed
-        return this.at(this.situation.reversed ? 1-this.pos : this.pos)
+        // We use an absolute position here so that speed can affect the delay before the animation
+        return this.at(this.absPos, true)
       } else return this._speed
     }
 
@@ -1568,8 +1593,9 @@ SVG.FX = SVG.invent({
   , loop: function(times, reverse) {
       var c = this.last()
 
-      // store current loop and total loops
-      c.loop = c.loops = times || true
+      // store total loops
+      c.loops = (times != null) ? times : true
+      c.loop = 0
 
       if(reverse) c.reversing = true
       return this
@@ -1587,7 +1613,8 @@ SVG.FX = SVG.invent({
   , play: function(){
       if(!this.paused) return this
       this.paused = false
-      return this.at(this.pos)
+      // We use an absolute position here so that the delay before the animation can be paused
+      return this.at(this.absPos, true)
     }
 
     /**
@@ -1692,22 +1719,45 @@ SVG.FX = SVG.invent({
      */
   , step: function(ignoreTime){
 
-      // convert current time to position
-      if(!ignoreTime) this.pos = this.timeToPos(+new Date)
-
-      if(this.pos >= 1 && (this.situation.loop === true || (typeof this.situation.loop == 'number' && --this.situation.loop))){
+      // convert current time to an absolute position
+      if(!ignoreTime) this.absPos = this.timeToAbsPos(+new Date)
+
+      // This part convert an absolute position to a position
+      if(this.situation.loops !== false) {
+        var absPos, absPosInt, lastLoop
+
+        // If the absolute position is below 0, we just treat it as if it was 0
+        absPos = Math.max(this.absPos, 0)
+        absPosInt = Math.floor(absPos)
+
+        if(this.situation.loops === true || absPosInt < this.situation.loops) {
+          this.pos = absPos - absPosInt
+          lastLoop = this.situation.loop
+          this.situation.loop = absPosInt
+        } else {
+          this.absPos = this.situation.loops
+          this.pos = 1
+          // The -1 here is because we don't want to toggle reversed when all the loops have been completed
+          lastLoop = this.situation.loop - 1
+          this.situation.loop = this.situation.loops
+        }
 
-        if(this.situation.reversing){
-          this.situation.reversed = !this.situation.reversed
+        if(this.situation.reversing) {
+          // Toggle reversed if an odd number of loops as occured since the last call of step
+          this.situation.reversed = this.situation.reversed != Boolean((this.situation.loop - lastLoop) % 2)
         }
-        return this.at(this.pos-1)
+
+      } else {
+        // If there are no loop, the absolute position must not be above 1
+        this.absPos = Math.min(this.absPos, 1)
+        this.pos = this.absPos
       }
 
+      // while the absolute position can be below 0, the position must not be below 0
+      if(this.pos < 0) this.pos = 0
+
       if(this.situation.reversed) this.pos = 1 - this.pos
 
-      // correct position
-      if(this.pos > 1)this.pos = 1
-      if(this.pos < 0)this.pos = 0
 
       // apply easing
       var eased = this.situation.ease(this.pos)
index d6b4ca4bbd53af0ad34454df26ed0b750c240220..2d157616ba04660a81d9c416e23ed039a19e1309 100644 (file)
@@ -1,2 +1,2 @@
-/*! svg.js v2.3.6 MIT*/;!function(t,e){"function"==typeof define&&define.amd?define(function(){return e(t,t.document)}):"object"==typeof exports?module.exports=t.document?e(t,t.document):function(t){return e(t,t.document)}:t.SVG=e(t,t.document)}("undefined"!=typeof window?window:this,function(t,e){function i(t,e){return t instanceof e}function n(t,e){return(t.matches||t.matchesSelector||t.msMatchesSelector||t.mozMatchesSelector||t.webkitMatchesSelector||t.oMatchesSelector).call(t,e)}function r(t){return t.toLowerCase().replace(/-(.)/g,function(t,e){return e.toUpperCase()})}function s(t){return t.charAt(0).toUpperCase()+t.slice(1)}function a(t){return 4==t.length?["#",t.substring(1,2),t.substring(1,2),t.substring(2,3),t.substring(2,3),t.substring(3,4),t.substring(3,4)].join(""):t}function o(t){var e=t.toString(16);return 1==e.length?"0"+e:e}function h(t,e,i){if(null==e||null==i){var n=t.bbox();null==e?e=n.width/n.height*i:null==i&&(i=n.height/n.width*e)}return{width:e,height:i}}function u(t,e,i){return{x:e*t.a+i*t.c+0,y:e*t.b+i*t.d+0}}function l(t){return{a:t[0],b:t[1],c:t[2],d:t[3],e:t[4],f:t[5]}}function c(t){return t instanceof y.Matrix||(t=new y.Matrix(t)),t}function f(t,e){t.cx=null==t.cx?e.bbox().cx:t.cx,t.cy=null==t.cy?e.bbox().cy:t.cy}function d(t){return t=t.replace(y.regex.whitespace,"").replace(y.regex.matrix,"").split(y.regex.matrixElements),l(y.utils.map(t,function(t){return parseFloat(t)}))}function p(t){for(var e=0,i=t.length,n="";e<i;e++)n+=t[e][0],null!=t[e][1]&&(n+=t[e][1],null!=t[e][2]&&(n+=" ",n+=t[e][2],null!=t[e][3]&&(n+=" ",n+=t[e][3],n+=" ",n+=t[e][4],null!=t[e][5]&&(n+=" ",n+=t[e][5],n+=" ",n+=t[e][6],null!=t[e][7]&&(n+=" ",n+=t[e][7])))));return n+" "}function m(t){for(var e=t.childNodes.length-1;e>=0;e--)t.childNodes[e]instanceof SVGElement&&m(t.childNodes[e]);return y.adopt(t).id(y.eid(t.nodeName))}function x(t){return null==t.x&&(t.x=0,t.y=0,t.width=0,t.height=0),t.w=t.width,t.h=t.height,t.x2=t.x+t.width,t.y2=t.y+t.height,t.cx=t.x+t.width/2,t.cy=t.y+t.height/2,t}function v(t){var e=t.toString().match(y.regex.reference);if(e)return e[1]}var y=this.SVG=function(t){if(y.supported)return t=new y.Doc(t),y.parser.draw||y.prepare(),t};if(y.ns="http://www.w3.org/2000/svg",y.xmlns="http://www.w3.org/2000/xmlns/",y.xlink="http://www.w3.org/1999/xlink",y.svgjs="http://svgjs.com/svgjs",y.supported=function(){return!!e.createElementNS&&!!e.createElementNS(y.ns,"svg").createSVGRect}(),!y.supported)return!1;y.did=1e3,y.eid=function(t){return"Svgjs"+s(t)+y.did++},y.create=function(t){var i=e.createElementNS(this.ns,t);return i.setAttribute("id",this.eid(t)),i},y.extend=function(){var t,e,i,n;for(t=[].slice.call(arguments),e=t.pop(),n=t.length-1;n>=0;n--)if(t[n])for(i in e)t[n].prototype[i]=e[i];y.Set&&y.Set.inherit&&y.Set.inherit()},y.invent=function(t){var e="function"==typeof t.create?t.create:function(){this.constructor.call(this,y.create(t.create))};return t.inherit&&(e.prototype=new t.inherit),t.extend&&y.extend(e,t.extend),t.construct&&y.extend(t.parent||y.Container,t.construct),e},y.adopt=function(t){if(!t)return null;if(t.instance)return t.instance;var e;return e="svg"==t.nodeName?t.parentNode instanceof SVGElement?new y.Nested:new y.Doc:"linearGradient"==t.nodeName?new y.Gradient("linear"):"radialGradient"==t.nodeName?new y.Gradient("radial"):y[s(t.nodeName)]?new(y[s(t.nodeName)]):new y.Element(t),e.type=t.nodeName,e.node=t,t.instance=e,e instanceof y.Doc&&e.namespace().defs(),e.setData(JSON.parse(t.getAttribute("svgjs:data"))||{}),e},y.prepare=function(){var t=e.getElementsByTagName("body")[0],i=(t?new y.Doc(t):new y.Doc(e.documentElement).nested()).size(2,0);y.parser={body:t||e.documentElement,draw:i.style("opacity:0;position:fixed;left:100%;top:100%;overflow:hidden"),poly:i.polyline().node,path:i.path().node,native:y.create("svg")}},y.parser={native:y.create("svg")},e.addEventListener("DOMContentLoaded",function(){y.parser.draw||y.prepare()},!1),y.regex={numberAndUnit:/^([+-]?(\d+(\.\d*)?|\.\d+)(e[+-]?\d+)?)([a-z%]*)$/i,hex:/^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i,rgb:/rgb\((\d+),(\d+),(\d+)\)/,reference:/#([a-z0-9\-_]+)/i,matrix:/matrix\(|\)/g,matrixElements:/,*\s+|,/,whitespace:/\s/g,isHex:/^#[a-f0-9]{3,6}$/i,isRgb:/^rgb\(/,isCss:/[^:]+:[^;]+;?/,isBlank:/^(\s+)?$/,isNumber:/^[+-]?(\d+(\.\d*)?|\.\d+)(e[+-]?\d+)?$/i,isPercent:/^-?[\d\.]+%$/,isImage:/\.(jpg|jpeg|png|gif|svg)(\?[^=]+.*)?/i,negExp:/e\-/gi,comma:/,/g,hyphen:/\-/g,pathLetters:/[MLHVCSQTAZ]/gi,isPathLetter:/[MLHVCSQTAZ]/i,whitespaces:/\s+/,X:/X/g},y.utils={map:function(t,e){var i,n=t.length,r=[];for(i=0;i<n;i++)r.push(e(t[i]));return r},filter:function(t,e){var i,n=t.length,r=[];for(i=0;i<n;i++)e(t[i])&&r.push(t[i]);return r},radians:function(t){return t%360*Math.PI/180},degrees:function(t){return 180*t/Math.PI%360},filterSVGElements:function(t){return this.filter(t,function(t){return t instanceof SVGElement})}},y.defaults={attrs:{"fill-opacity":1,"stroke-opacity":1,"stroke-width":0,"stroke-linejoin":"miter","stroke-linecap":"butt",fill:"#000000",stroke:"#000000",opacity:1,x:0,y:0,cx:0,cy:0,width:0,height:0,r:0,rx:0,ry:0,offset:0,"stop-opacity":1,"stop-color":"#000000","font-size":16,"font-family":"Helvetica, Arial, sans-serif","text-anchor":"start"}},y.Color=function(t){var e;this.r=0,this.g=0,this.b=0,t&&("string"==typeof t?y.regex.isRgb.test(t)?(e=y.regex.rgb.exec(t.replace(/\s/g,"")),this.r=parseInt(e[1]),this.g=parseInt(e[2]),this.b=parseInt(e[3])):y.regex.isHex.test(t)&&(e=y.regex.hex.exec(a(t)),this.r=parseInt(e[1],16),this.g=parseInt(e[2],16),this.b=parseInt(e[3],16)):"object"==typeof t&&(this.r=t.r,this.g=t.g,this.b=t.b))},y.extend(y.Color,{toString:function(){return this.toHex()},toHex:function(){return"#"+o(this.r)+o(this.g)+o(this.b)},toRgb:function(){return"rgb("+[this.r,this.g,this.b].join()+")"},brightness:function(){return this.r/255*.3+this.g/255*.59+this.b/255*.11},morph:function(t){return this.destination=new y.Color(t),this},at:function(t){return this.destination?(t=t<0?0:t>1?1:t,new y.Color({r:~~(this.r+(this.destination.r-this.r)*t),g:~~(this.g+(this.destination.g-this.g)*t),b:~~(this.b+(this.destination.b-this.b)*t)})):this}}),y.Color.test=function(t){return t+="",y.regex.isHex.test(t)||y.regex.isRgb.test(t)},y.Color.isRgb=function(t){return t&&"number"==typeof t.r&&"number"==typeof t.g&&"number"==typeof t.b},y.Color.isColor=function(t){return y.Color.isRgb(t)||y.Color.test(t)},y.Array=function(t,e){t=(t||[]).valueOf(),0==t.length&&e&&(t=e.valueOf()),this.value=this.parse(t)},y.extend(y.Array,{morph:function(t){if(this.destination=this.parse(t),this.value.length!=this.destination.length){for(var e=this.value[this.value.length-1],i=this.destination[this.destination.length-1];this.value.length>this.destination.length;)this.destination.push(i);for(;this.value.length<this.destination.length;)this.value.push(e)}return this},settle:function(){for(var t=0,e=this.value.length,i=[];t<e;t++)i.indexOf(this.value[t])==-1&&i.push(this.value[t]);return this.value=i},at:function(t){if(!this.destination)return this;for(var e=0,i=this.value.length,n=[];e<i;e++)n.push(this.value[e]+(this.destination[e]-this.value[e])*t);return new y.Array(n)},toString:function(){return this.value.join(" ")},valueOf:function(){return this.value},parse:function(t){return t=t.valueOf(),Array.isArray(t)?t:this.split(t)},split:function(t){return t.trim().split(/\s+/)},reverse:function(){return this.value.reverse(),this}}),y.PointArray=function(t,e){this.constructor.call(this,t,e||[[0,0]])},y.PointArray.prototype=new y.Array,y.extend(y.PointArray,{toString:function(){for(var t=0,e=this.value.length,i=[];t<e;t++)i.push(this.value[t].join(","));return i.join(" ")},toLine:function(){return{x1:this.value[0][0],y1:this.value[0][1],x2:this.value[1][0],y2:this.value[1][1]}},at:function(t){if(!this.destination)return this;for(var e=0,i=this.value.length,n=[];e<i;e++)n.push([this.value[e][0]+(this.destination[e][0]-this.value[e][0])*t,this.value[e][1]+(this.destination[e][1]-this.value[e][1])*t]);return new y.PointArray(n)},parse:function(t){var e=[];if(t=t.valueOf(),Array.isArray(t))return t;t=t.trim().split(/\s+|,/),t.length%2!==0&&t.pop();for(var i=0,n=t.length;i<n;i+=2)e.push([parseFloat(t[i]),parseFloat(t[i+1])]);return e},move:function(t,e){var i=this.bbox();if(t-=i.x,e-=i.y,!isNaN(t)&&!isNaN(e))for(var n=this.value.length-1;n>=0;n--)this.value[n]=[this.value[n][0]+t,this.value[n][1]+e];return this},size:function(t,e){var i,n=this.bbox();for(i=this.value.length-1;i>=0;i--)this.value[i][0]=(this.value[i][0]-n.x)*t/n.width+n.x,this.value[i][1]=(this.value[i][1]-n.y)*e/n.height+n.y;return this},bbox:function(){return y.parser.poly.setAttribute("points",this.toString()),y.parser.poly.getBBox()}}),y.PathArray=function(t,e){this.constructor.call(this,t,e||[["M",0,0]])},y.PathArray.prototype=new y.Array,y.extend(y.PathArray,{toString:function(){return p(this.value)},move:function(t,e){var i=this.bbox();if(t-=i.x,e-=i.y,!isNaN(t)&&!isNaN(e))for(var n,r=this.value.length-1;r>=0;r--)n=this.value[r][0],"M"==n||"L"==n||"T"==n?(this.value[r][1]+=t,this.value[r][2]+=e):"H"==n?this.value[r][1]+=t:"V"==n?this.value[r][1]+=e:"C"==n||"S"==n||"Q"==n?(this.value[r][1]+=t,this.value[r][2]+=e,this.value[r][3]+=t,this.value[r][4]+=e,"C"==n&&(this.value[r][5]+=t,this.value[r][6]+=e)):"A"==n&&(this.value[r][6]+=t,this.value[r][7]+=e);return this},size:function(t,e){var i,n,r=this.bbox();for(i=this.value.length-1;i>=0;i--)n=this.value[i][0],"M"==n||"L"==n||"T"==n?(this.value[i][1]=(this.value[i][1]-r.x)*t/r.width+r.x,this.value[i][2]=(this.value[i][2]-r.y)*e/r.height+r.y):"H"==n?this.value[i][1]=(this.value[i][1]-r.x)*t/r.width+r.x:"V"==n?this.value[i][1]=(this.value[i][1]-r.y)*e/r.height+r.y:"C"==n||"S"==n||"Q"==n?(this.value[i][1]=(this.value[i][1]-r.x)*t/r.width+r.x,this.value[i][2]=(this.value[i][2]-r.y)*e/r.height+r.y,this.value[i][3]=(this.value[i][3]-r.x)*t/r.width+r.x,this.value[i][4]=(this.value[i][4]-r.y)*e/r.height+r.y,"C"==n&&(this.value[i][5]=(this.value[i][5]-r.x)*t/r.width+r.x,this.value[i][6]=(this.value[i][6]-r.y)*e/r.height+r.y)):"A"==n&&(this.value[i][1]=this.value[i][1]*t/r.width,this.value[i][2]=this.value[i][2]*e/r.height,this.value[i][6]=(this.value[i][6]-r.x)*t/r.width+r.x,this.value[i][7]=(this.value[i][7]-r.y)*e/r.height+r.y);return this},parse:function(t){if(t instanceof y.PathArray)return t.valueOf();var e,i,n,r,s,a,o=0,h=0,u={M:2,L:2,H:1,V:1,C:6,S:4,Q:4,T:2,A:7};if("string"==typeof t){for(t=t.replace(y.regex.negExp,"X").replace(y.regex.pathLetters," $& ").replace(y.regex.hyphen," -").replace(y.regex.comma," ").replace(y.regex.X,"e-").trim().split(y.regex.whitespaces),e=t.length;--e;)if(t[e].indexOf(".")!=t[e].lastIndexOf(".")){var l=t[e].split("."),c=[l.shift(),l.shift()].join(".");t.splice.apply(t,[e,1].concat(c,l.map(function(t){return"."+t})))}}else t=t.reduce(function(t,e){return[].concat.apply(t,e)},[]);var a=[];do{for(y.regex.isPathLetter.test(t[0])?(r=t[0],t.shift()):"M"==r?r="L":"m"==r&&(r="l"),s=[r.toUpperCase()],e=0;e<u[s[0]];++e)s.push(parseFloat(t.shift()));r==s[0]?"M"==r||"L"==r||"C"==r||"Q"==r||"S"==r||"T"==r?(o=s[u[s[0]]-1],h=s[u[s[0]]]):"V"==r?h=s[1]:"H"==r?o=s[1]:"A"==r&&(o=s[6],h=s[7]):"m"==r||"l"==r||"c"==r||"s"==r||"q"==r||"t"==r?(s[1]+=o,s[2]+=h,null!=s[3]&&(s[3]+=o,s[4]+=h),null!=s[5]&&(s[5]+=o,s[6]+=h),o=s[u[s[0]]-1],h=s[u[s[0]]]):"v"==r?(s[1]+=h,h=s[1]):"h"==r?(s[1]+=o,o=s[1]):"a"==r&&(s[6]+=o,s[7]+=h,o=s[6],h=s[7]),"M"==s[0]&&(i=o,n=h),"Z"==s[0]&&(o=i,h=n),a.push(s)}while(t.length);return a},bbox:function(){return y.parser.path.setAttribute("d",this.toString()),y.parser.path.getBBox()}}),y.Number=y.invent({create:function(t,e){this.value=0,this.unit=e||"","number"==typeof t?this.value=isNaN(t)?0:isFinite(t)?t:t<0?-3.4e38:3.4e38:"string"==typeof t?(e=t.match(y.regex.numberAndUnit),e&&(this.value=parseFloat(e[1]),"%"==e[5]?this.value/=100:"s"==e[5]&&(this.value*=1e3),this.unit=e[5])):t instanceof y.Number&&(this.value=t.valueOf(),this.unit=t.unit)},extend:{toString:function(){return("%"==this.unit?~~(1e8*this.value)/1e6:"s"==this.unit?this.value/1e3:this.value)+this.unit},toJSON:function(){return this.toString()},valueOf:function(){return this.value},plus:function(t){return new y.Number(this+new y.Number(t),this.unit)},minus:function(t){return this.plus(-new y.Number(t))},times:function(t){return new y.Number(this*new y.Number(t),this.unit)},divide:function(t){return new y.Number(this/new y.Number(t),this.unit)},to:function(t){var e=new y.Number(this);return"string"==typeof t&&(e.unit=t),e},morph:function(t){return this.destination=new y.Number(t),this},at:function(t){return this.destination?new y.Number(this.destination).minus(this).times(t).plus(this):this}}}),y.Element=y.invent({create:function(t){this._stroke=y.defaults.attrs.stroke,this.dom={},(this.node=t)&&(this.type=t.nodeName,this.node.instance=this,this._stroke=t.getAttribute("stroke")||this._stroke)},extend:{x:function(t){return this.attr("x",t)},y:function(t){return this.attr("y",t)},cx:function(t){return null==t?this.x()+this.width()/2:this.x(t-this.width()/2)},cy:function(t){return null==t?this.y()+this.height()/2:this.y(t-this.height()/2)},move:function(t,e){return this.x(t).y(e)},center:function(t,e){return this.cx(t).cy(e)},width:function(t){return this.attr("width",t)},height:function(t){return this.attr("height",t)},size:function(t,e){var i=h(this,t,e);return this.width(new y.Number(i.width)).height(new y.Number(i.height))},clone:function(t){var e=m(this.node.cloneNode(!0));return t?t.add(e):this.after(e),e},remove:function(){return this.parent()&&this.parent().removeElement(this),this},replace:function(t){return this.after(t).remove(),t},addTo:function(t){return t.put(this)},putIn:function(t){return t.add(this)},id:function(t){return this.attr("id",t)},inside:function(t,e){var i=this.bbox();return t>i.x&&e>i.y&&t<i.x+i.width&&e<i.y+i.height},show:function(){return this.style("display","")},hide:function(){return this.style("display","none")},visible:function(){return"none"!=this.style("display")},toString:function(){return this.attr("id")},classes:function(){var t=this.attr("class");return null==t?[]:t.trim().split(/\s+/)},hasClass:function(t){return this.classes().indexOf(t)!=-1},addClass:function(t){if(!this.hasClass(t)){var e=this.classes();e.push(t),this.attr("class",e.join(" "))}return this},removeClass:function(t){return this.hasClass(t)&&this.attr("class",this.classes().filter(function(e){return e!=t}).join(" ")),this},toggleClass:function(t){return this.hasClass(t)?this.removeClass(t):this.addClass(t)},reference:function(t){return y.get(this.attr(t))},parent:function(t){var e=this;if(!e.node.parentNode)return null;if(e=y.adopt(e.node.parentNode),!t)return e;for(;e&&e.node instanceof SVGElement;){if("string"==typeof t?e.matches(t):e instanceof t)return e;e=y.adopt(e.node.parentNode)}},doc:function(){return this instanceof y.Doc?this:this.parent(y.Doc)},parents:function(t){var e=[],i=this;do{if(i=i.parent(t),!i||!i.node)break;e.push(i)}while(i.parent);return e},matches:function(t){return n(this.node,t)},native:function(){return this.node},svg:function(t){var i=e.createElement("svg");if(!(t&&this instanceof y.Parent))return i.appendChild(t=e.createElement("svg")),this.writeDataToDom(),t.appendChild(this.node.cloneNode(!0)),i.innerHTML.replace(/^<svg>/,"").replace(/<\/svg>$/,"");i.innerHTML="<svg>"+t.replace(/\n/,"").replace(/<(\w+)([^<]+?)\/>/g,"<$1$2></$1>")+"</svg>";for(var n=0,r=i.firstChild.childNodes.length;n<r;n++)this.node.appendChild(i.firstChild.firstChild);return this},writeDataToDom:function(){if(this.each||this.lines){var t=this.each?this:this.lines();t.each(function(){this.writeDataToDom()})}return this.node.removeAttribute("svgjs:data"),Object.keys(this.dom).length&&this.node.setAttribute("svgjs:data",JSON.stringify(this.dom)),this},setData:function(t){return this.dom=t,this},is:function(t){return i(this,t)}}}),y.easing={"-":function(t){return t},"<>":function(t){return-Math.cos(t*Math.PI)/2+.5},">":function(t){return Math.sin(t*Math.PI/2)},"<":function(t){return-Math.cos(t*Math.PI/2)+1}},y.morph=function(t){return function(e,i){return new y.MorphObj(e,i).at(t)}},y.Situation=y.invent({create:function(t){this.init=!1,this.reversed=!1,this.reversing=!1,this.duration=new y.Number(t.duration).valueOf(),this.delay=new y.Number(t.delay).valueOf(),this.start=+new Date+this.delay,this.finish=this.start+this.duration,this.ease=t.ease,this.loop=!1,this.loops=!1,this.animations={},this.attrs={},this.styles={},this.transforms=[],this.once={}}}),y.Delay=function(t){this.delay=new y.Number(t).valueOf()},y.FX=y.invent({create:function(t){this._target=t,this.situations=[],this.active=!1,this.situation=null,this.paused=!1,this.lastPos=0,this.pos=0,this._speed=1},extend:{animate:function(t,e,i){"object"==typeof t&&(e=t.ease,i=t.delay,t=t.duration);var n=new y.Situation({duration:t||1e3,delay:i||0,ease:y.easing[e||"-"]||e});return this.queue(n),this},delay:function(t){var t=new y.Delay(t);return this.queue(t)},target:function(t){return t&&t instanceof y.Element?(this._target=t,this):this._target},timeToPos:function(t){return(t-this.situation.start)/(this.situation.duration/this._speed)},posToTime:function(t){return this.situation.duration/this._speed*t+this.situation.start},startAnimFrame:function(){this.stopAnimFrame(),this.animationFrame=requestAnimationFrame(function(){this.step()}.bind(this))},stopAnimFrame:function(){cancelAnimationFrame(this.animationFrame)},start:function(){return!this.active&&this.situation&&(this.situation.start=+new Date+this.situation.delay,this.situation.finish=this.situation.start+this.situation.duration/this._speed,this.initAnimations(),this.active=!0,this.startAnimFrame()),this},queue:function(t){return("function"==typeof t||t instanceof y.Situation||t instanceof y.Delay)&&this.situations.push(t),this.situation||(this.situation=this.situations.shift()),this},dequeue:function(){if(this.situation&&this.situation.stop&&this.situation.stop(),this.situation=this.situations.shift(),this.situation){var t=function(){this.situation instanceof y.Situation?this.initAnimations().at(0):this.situation instanceof y.Delay?this.dequeue():this.situation.call(this)}.bind(this);this.situation.delay?setTimeout(function(){t()},this.situation.delay):t()}return this},initAnimations:function(){var t,e=this.situation;if(e.init)return this;for(t in e.animations)"viewbox"==t?e.animations[t]=this.target().viewbox().morph(e.animations[t]):(e.animations[t].value="plot"==t?this.target().array().value:this.target()[t](),e.animations[t].value.value&&(e.animations[t].value=e.animations[t].value.value),e.animations[t].relative&&(e.animations[t].destination.value=e.animations[t].destination.value+e.animations[t].value));for(t in e.attrs)if(e.attrs[t]instanceof y.Color){var i=new y.Color(this.target().attr(t));e.attrs[t].r=i.r,e.attrs[t].g=i.g,e.attrs[t].b=i.b}else e.attrs[t].value=this.target().attr(t);for(t in e.styles)e.styles[t].value=this.target().style(t);return e.initialTransformation=this.target().matrixify(),e.init=!0,this},clearQueue:function(){return this.situations=[],this},clearCurrent:function(){return this.situation=null,this},stop:function(t,e){return this.active||this.start(),e&&this.clearQueue(),this.active=!1,t&&this.situation&&(this.situation.loop=!1,this.situation.loops%2==0&&this.situation.reversing&&(this.situation.reversed=!0),this.at(1)),this.stopAnimFrame(),clearTimeout(this.timeout),this.clearCurrent()},reset:function(){if(this.situation){var t=this.situation;this.stop(),this.situation=t,this.at(0)}return this},finish:function(){for(this.stop(!0,!1);this.dequeue().situation&&this.stop(!0,!1););return this.clearQueue().clearCurrent(),this},at:function(t){var e=this.situation.duration/this._speed;return this.pos=t,this.situation.start=+new Date-t*e,this.situation.finish=this.situation.start+e,this.step(!0)},speed:function(t){return 0===t?this.pause():t?(this._speed=t,this.at(this.situation.reversed?1-this.pos:this.pos)):this._speed},loop:function(t,e){var i=this.last();return i.loop=i.loops=t||!0,e&&(i.reversing=!0),this},pause:function(){return this.paused=!0,this.stopAnimFrame(),clearTimeout(this.timeout),this},play:function(){return this.paused?(this.paused=!1,this.at(this.pos)):this},reverse:function(t){var e=this.last();return"undefined"==typeof t?e.reversed=!e.reversed:e.reversed=t,this},progress:function(t){return t?this.situation.ease(this.pos):this.pos},after:function(t){var e=this.last(),i=function i(n){n.detail.situation==e&&(t.call(this,e),this.off("finished.fx",i))};return this.target().on("finished.fx",i),this},during:function(t){var e=this.last(),i=function(i){i.detail.situation==e&&t.call(this,i.detail.pos,y.morph(i.detail.pos),i.detail.eased,e)};return this.target().off("during.fx",i).on("during.fx",i),this.after(function(){this.off("during.fx",i)})},afterAll:function(t){var e=function e(i){t.call(this),this.off("allfinished.fx",e)};return this.target().off("allfinished.fx",e).on("allfinished.fx",e),this},duringAll:function(t){var e=function(e){t.call(this,e.detail.pos,y.morph(e.detail.pos),e.detail.eased,e.detail.situation)};return this.target().off("during.fx",e).on("during.fx",e),this.afterAll(function(){this.off("during.fx",e)})},last:function(){return this.situations.length?this.situations[this.situations.length-1]:this.situation},add:function(t,e,i){return this.last()[i||"animations"][t]=e,setTimeout(function(){this.start()}.bind(this),0),this},step:function(t){if(t||(this.pos=this.timeToPos(+new Date)),this.pos>=1&&(this.situation.loop===!0||"number"==typeof this.situation.loop&&--this.situation.loop))return this.situation.reversing&&(this.situation.reversed=!this.situation.reversed),this.at(this.pos-1);this.situation.reversed&&(this.pos=1-this.pos),this.pos>1&&(this.pos=1),this.pos<0&&(this.pos=0);var e=this.situation.ease(this.pos);for(var i in this.situation.once)i>this.lastPos&&i<=e&&(this.situation.once[i].call(this.target(),this.pos,e),delete this.situation.once[i]);return this.active&&this.target().fire("during",{pos:this.pos,eased:e,fx:this,situation:this.situation}),this.situation?(this.eachAt(),1==this.pos&&!this.situation.reversed||this.situation.reversed&&0==this.pos?(this.stopAnimFrame(),this.target().fire("finished",{fx:this,situation:this.situation}),this.situations.length||(this.target().fire("allfinished"),this.target().off(".fx"),this.active=!1),this.active?this.dequeue():this.clearCurrent()):!this.paused&&this.active&&this.startAnimFrame(),this.lastPos=e,this):this},eachAt:function(){var t,e,i=this,n=this.target(),r=this.situation;for(t in r.animations)e=[].concat(r.animations[t]).map(function(t){return t.at?t.at(r.ease(i.pos),i.pos):t}),n[t].apply(n,e);for(t in r.attrs)e=[t].concat(r.attrs[t]).map(function(t){return t.at?t.at(r.ease(i.pos),i.pos):t}),n.attr.apply(n,e);for(t in r.styles)e=[t].concat(r.styles[t]).map(function(t){return t.at?t.at(r.ease(i.pos),i.pos):t}),n.style.apply(n,e);if(r.transforms.length){e=r.initialTransformation;for(t in r.transforms){var s=r.transforms[t];s instanceof y.Matrix?e=s.relative?e.multiply(s.at(r.ease(this.pos))):e.morph(s).at(r.ease(this.pos)):(s.relative||s.undo(e.extract()),e=e.multiply(s.at(r.ease(this.pos))))}n.matrix(e)}return this},once:function(t,e,i){return i||(t=this.situation.ease(t)),this.situation.once[t]=e,this}},parent:y.Element,construct:{animate:function(t,e,i){return(this.fx||(this.fx=new y.FX(this))).animate(t,e,i)},delay:function(t){return(this.fx||(this.fx=new y.FX(this))).delay(t)},stop:function(t,e){return this.fx&&this.fx.stop(t,e),this},finish:function(){return this.fx&&this.fx.finish(),this},pause:function(){return this.fx&&this.fx.pause(),this},play:function(){return this.fx&&this.fx.play(),this},speed:function(t){if(this.fx){if(null==t)return this.fx.speed();this.fx.speed(t)}return this}}}),y.MorphObj=y.invent({create:function(t,e){return y.Color.isColor(e)?new y.Color(t).morph(e):y.regex.numberAndUnit.test(e)?new y.Number(t).morph(e):(this.value=0,void(this.destination=e))},extend:{at:function(t,e){return e<1?this.value:this.destination},valueOf:function(){return this.value}}}),y.extend(y.FX,{attr:function(t,e,i){if("object"==typeof t)for(var n in t)this.attr(n,t[n]);else this.add(t,new y.MorphObj(null,e),"attrs");return this},style:function(t,e){if("object"==typeof t)for(var i in t)this.style(i,t[i]);else this.add(t,new y.MorphObj(null,e),"styles");return this},x:function(t,e){if(this.target()instanceof y.G)return this.transform({x:t},e),this;var i=(new y.Number).morph(t);return i.relative=e,this.add("x",i)},y:function(t,e){if(this.target()instanceof y.G)return this.transform({y:t},e),this;var i=(new y.Number).morph(t);return i.relative=e,this.add("y",i)},cx:function(t){return this.add("cx",(new y.Number).morph(t))},cy:function(t){return this.add("cy",(new y.Number).morph(t))},move:function(t,e){return this.x(t).y(e)},center:function(t,e){return this.cx(t).cy(e)},size:function(t,e){if(this.target()instanceof y.Text)this.attr("font-size",t);else{var i;t&&e||(i=this.target().bbox()),t||(t=i.width/i.height*e),e||(e=i.height/i.width*t),this.add("width",(new y.Number).morph(t)).add("height",(new y.Number).morph(e))}return this},plot:function(t){return this.add("plot",this.target().array().morph(t))},leading:function(t){return this.target().leading?this.add("leading",(new y.Number).morph(t)):this},viewbox:function(t,e,i,n){return this.target()instanceof y.Container&&this.add("viewbox",new y.ViewBox(t,e,i,n)),this},update:function(t){if(this.target()instanceof y.Stop){if("number"==typeof t||t instanceof y.Number)return this.update({offset:arguments[0],color:arguments[1],opacity:arguments[2]});null!=t.opacity&&this.attr("stop-opacity",t.opacity),null!=t.color&&this.attr("stop-color",t.color),null!=t.offset&&this.attr("offset",t.offset)}return this}}),y.BBox=y.invent({create:function(t){if(t){var i;try{if(!e.documentElement.contains(t.node))throw new Exception("Element not in the dom");i=t.node.getBBox()}catch(e){if(t instanceof y.Shape){var n=t.clone(y.parser.draw).show();i=n.bbox(),n.remove()}else i={x:t.node.clientLeft,y:t.node.clientTop,width:t.node.clientWidth,height:t.node.clientHeight}}this.x=i.x,this.y=i.y,this.width=i.width,this.height=i.height}x(this)},parent:y.Element,construct:{bbox:function(){return new y.BBox(this)}}}),y.TBox=y.invent({create:function(t){if(t){var e=t.ctm().extract(),i=t.bbox();this.width=i.width*e.scaleX,this.height=i.height*e.scaleY,this.x=i.x+e.x,this.y=i.y+e.y}x(this)},parent:y.Element,construct:{tbox:function(){return new y.TBox(this)}}}),y.RBox=y.invent({create:function(e){if(e){var i=e.doc().parent(),n=e.node.getBoundingClientRect(),r=1;for(this.x=n.left,this.y=n.top,this.x-=i.offsetLeft,this.y-=i.offsetTop;i=i.offsetParent;)this.x-=i.offsetLeft,this.y-=i.offsetTop;for(i=e;i.parent&&(i=i.parent());)i.viewbox&&(r*=i.viewbox().zoom,this.x-=i.x()||0,this.y-=i.y()||0);this.width=n.width/=r,this.height=n.height/=r}x(this),this.x+=t.pageXOffset,this.y+=t.pageYOffset},parent:y.Element,construct:{rbox:function(){return new y.RBox(this)}}}),[y.BBox,y.TBox,y.RBox].forEach(function(t){y.extend(t,{merge:function(e){var i=new t;return i.x=Math.min(this.x,e.x),i.y=Math.min(this.y,e.y),i.width=Math.max(this.x+this.width,e.x+e.width)-i.x,i.height=Math.max(this.y+this.height,e.y+e.height)-i.y,x(i)}})}),y.Matrix=y.invent({create:function(t){var e,i=l([1,0,0,1,0,0]);for(t=t instanceof y.Element?t.matrixify():"string"==typeof t?d(t):6==arguments.length?l([].slice.call(arguments)):"object"==typeof t?t:i,e=w.length-1;e>=0;--e)this[w[e]]=t&&"number"==typeof t[w[e]]?t[w[e]]:i[w[e]]},extend:{extract:function(){var t=u(this,0,1),e=u(this,1,0),i=180/Math.PI*Math.atan2(t.y,t.x)-90;return{x:this.e,y:this.f,transformedX:(this.e*Math.cos(i*Math.PI/180)+this.f*Math.sin(i*Math.PI/180))/Math.sqrt(this.a*this.a+this.b*this.b),transformedY:(this.f*Math.cos(i*Math.PI/180)+this.e*Math.sin(-i*Math.PI/180))/Math.sqrt(this.c*this.c+this.d*this.d),skewX:-i,skewY:180/Math.PI*Math.atan2(e.y,e.x),scaleX:Math.sqrt(this.a*this.a+this.b*this.b),scaleY:Math.sqrt(this.c*this.c+this.d*this.d),rotation:i,a:this.a,b:this.b,c:this.c,d:this.d,e:this.e,f:this.f,matrix:new y.Matrix(this)}},clone:function(){return new y.Matrix(this)},morph:function(t){return this.destination=new y.Matrix(t),this},at:function(t){if(!this.destination)return this;var e=new y.Matrix({a:this.a+(this.destination.a-this.a)*t,b:this.b+(this.destination.b-this.b)*t,c:this.c+(this.destination.c-this.c)*t,d:this.d+(this.destination.d-this.d)*t,e:this.e+(this.destination.e-this.e)*t,f:this.f+(this.destination.f-this.f)*t});if(this.param&&this.param.to){var i={rotation:this.param.from.rotation+(this.param.to.rotation-this.param.from.rotation)*t,cx:this.param.from.cx,cy:this.param.from.cy};e=e.rotate((this.param.to.rotation-2*this.param.from.rotation)*t,i.cx,i.cy),e.param=i}return e},multiply:function(t){return new y.Matrix(this.native().multiply(c(t).native()))},inverse:function(){return new y.Matrix(this.native().inverse())},translate:function(t,e){return new y.Matrix(this.native().translate(t||0,e||0))},scale:function(t,e,i,n){return 1!=arguments.length&&3!=arguments.length||(e=t),3==arguments.length&&(n=i,i=e),this.around(i,n,new y.Matrix(t,0,0,e,0,0))},rotate:function(t,e,i){return t=y.utils.radians(t),this.around(e,i,new y.Matrix(Math.cos(t),Math.sin(t),(-Math.sin(t)),Math.cos(t),0,0))},flip:function(t,e){return"x"==t?this.scale(-1,1,e,0):this.scale(1,-1,0,e)},skew:function(t,e,i,n){return this.around(i,n,this.native().skewX(t||0).skewY(e||0))},skewX:function(t,e,i){return this.around(e,i,this.native().skewX(t||0))},skewY:function(t,e,i){return this.around(e,i,this.native().skewY(t||0))},around:function(t,e,i){return this.multiply(new y.Matrix(1,0,0,1,t||0,e||0)).multiply(i).multiply(new y.Matrix(1,0,0,1,-t||0,-e||0))},native:function(){for(var t=y.parser.native.createSVGMatrix(),e=w.length-1;e>=0;e--)t[w[e]]=this[w[e]];return t},toString:function(){return"matrix("+this.a+","+this.b+","+this.c+","+this.d+","+this.e+","+this.f+")"}},parent:y.Element,construct:{ctm:function(){return new y.Matrix(this.node.getCTM())},screenCTM:function(){return new y.Matrix(this.node.getScreenCTM())}}}),y.Point=y.invent({create:function(t,e){var i,n={x:0,y:0};i=Array.isArray(t)?{x:t[0],y:t[1]}:"object"==typeof t?{x:t.x,y:t.y}:null!=e?{x:t,y:e}:n,this.x=i.x,this.y=i.y},extend:{clone:function(){return new y.Point(this)},morph:function(t){return this.destination=new y.Point(t),this},at:function(t){if(!this.destination)return this;var e=new y.Point({x:this.x+(this.destination.x-this.x)*t,y:this.y+(this.destination.y-this.y)*t});return e},native:function(){var t=y.parser.native.createSVGPoint();return t.x=this.x,t.y=this.y,t},transform:function(t){return new y.Point(this.native().matrixTransform(t.native()))}}}),y.extend(y.Element,{point:function(t,e){return new y.Point(t,e).transform(this.screenCTM().inverse())}}),y.extend(y.Element,{attr:function(t,e,i){if(null==t){for(t={},e=this.node.attributes,i=e.length-1;i>=0;i--)t[e[i].nodeName]=y.regex.isNumber.test(e[i].nodeValue)?parseFloat(e[i].nodeValue):e[i].nodeValue;return t}if("object"==typeof t)for(e in t)this.attr(e,t[e]);else if(null===e)this.node.removeAttribute(t);else{if(null==e)return e=this.node.getAttribute(t),null==e?y.defaults.attrs[t]:y.regex.isNumber.test(e)?parseFloat(e):e;"stroke-width"==t?this.attr("stroke",parseFloat(e)>0?this._stroke:null):"stroke"==t&&(this._stroke=e),"fill"!=t&&"stroke"!=t||(y.regex.isImage.test(e)&&(e=this.doc().defs().image(e,0,0)),e instanceof y.Image&&(e=this.doc().defs().pattern(0,0,function(){this.add(e)}))),"number"==typeof e?e=new y.Number(e):y.Color.isColor(e)?e=new y.Color(e):Array.isArray(e)?e=new y.Array(e):e instanceof y.Matrix&&e.param&&(this.param=e.param),"leading"==t?this.leading&&this.leading(e):"string"==typeof i?this.node.setAttributeNS(i,t,e.toString()):this.node.setAttribute(t,e.toString()),!this.rebuild||"font-size"!=t&&"x"!=t||this.rebuild(t,e)}return this}}),y.extend(y.Element,{transform:function(t,e){
-var i,n=this;if("object"!=typeof t)return i=new y.Matrix(n).extract(),"string"==typeof t?i[t]:i;if(i=new y.Matrix(n),e=!!e||!!t.relative,null!=t.a)i=e?i.multiply(new y.Matrix(t)):new y.Matrix(t);else if(null!=t.rotation)f(t,n),i=e?i.rotate(t.rotation,t.cx,t.cy):i.rotate(t.rotation-i.extract().rotation,t.cx,t.cy);else if(null!=t.scale||null!=t.scaleX||null!=t.scaleY){if(f(t,n),t.scaleX=null!=t.scale?t.scale:null!=t.scaleX?t.scaleX:1,t.scaleY=null!=t.scale?t.scale:null!=t.scaleY?t.scaleY:1,!e){var r=i.extract();t.scaleX=1*t.scaleX/r.scaleX,t.scaleY=1*t.scaleY/r.scaleY}i=i.scale(t.scaleX,t.scaleY,t.cx,t.cy)}else if(null!=t.skewX||null!=t.skewY){if(f(t,n),t.skewX=null!=t.skewX?t.skewX:0,t.skewY=null!=t.skewY?t.skewY:0,!e){var r=i.extract();i=i.multiply((new y.Matrix).skew(r.skewX,r.skewY,t.cx,t.cy).inverse())}i=i.skew(t.skewX,t.skewY,t.cx,t.cy)}else t.flip?i=i.flip(t.flip,null==t.offset?n.bbox()["c"+t.flip]:t.offset):null==t.x&&null==t.y||(e?i=i.translate(t.x,t.y):(null!=t.x&&(i.e=t.x),null!=t.y&&(i.f=t.y)));return this.attr("transform",i)}}),y.extend(y.FX,{transform:function(t,e){var i,n=this.target();return"object"!=typeof t?(i=new y.Matrix(n).extract(),"string"==typeof t?i[t]:i):(e=!!e||!!t.relative,null!=t.a?i=new y.Matrix(t):null!=t.rotation?(f(t,n),i=new y.Rotate(t.rotation,t.cx,t.cy)):null!=t.scale||null!=t.scaleX||null!=t.scaleY?(f(t,n),t.scaleX=null!=t.scale?t.scale:null!=t.scaleX?t.scaleX:1,t.scaleY=null!=t.scale?t.scale:null!=t.scaleY?t.scaleY:1,i=new y.Scale(t.scaleX,t.scaleY,t.cx,t.cy)):null!=t.skewX||null!=t.skewY?(f(t,n),t.skewX=null!=t.skewX?t.skewX:0,t.skewY=null!=t.skewY?t.skewY:0,i=new y.Skew(t.skewX,t.skewY,t.cx,t.cy)):t.flip?i=(new y.Matrix).morph((new y.Matrix).flip(t.flip,null==t.offset?n.bbox()["c"+t.flip]:t.offset)):null==t.x&&null==t.y||(i=new y.Translate(t.x,t.y)),i?(i.relative=e,this.last().transforms.push(i),setTimeout(function(){this.start()}.bind(this),0),this):this)}}),y.extend(y.Element,{untransform:function(){return this.attr("transform",null)},matrixify:function(){var t=(this.attr("transform")||"").split(/\)\s*,?\s*/).slice(0,-1).map(function(t){var e=t.trim().split("(");return[e[0],e[1].split(y.regex.matrixElements).map(function(t){return parseFloat(t)})]}).reduce(function(t,e){return"matrix"==e[0]?t.multiply(l(e[1])):t[e[0]].apply(t,e[1])},new y.Matrix);return t},toParent:function(t){if(this==t)return this;var e=this.screenCTM(),i=t.rect(1,1),n=i.screenCTM().inverse();return i.remove(),this.addTo(t).untransform().transform(n.multiply(e)),this},toDoc:function(){return this.toParent(this.doc())}}),y.Transformation=y.invent({create:function(t,e){if(arguments.length>1&&"boolean"!=typeof e)return this.create([].slice.call(arguments));if("object"==typeof t)for(var i=0,n=this.arguments.length;i<n;++i)this[this.arguments[i]]=t[this.arguments[i]];if(Array.isArray(t))for(var i=0,n=this.arguments.length;i<n;++i)this[this.arguments[i]]=t[i];this.inversed=!1,e===!0&&(this.inversed=!0)},extend:{at:function(t){for(var e=[],i=0,n=this.arguments.length;i<n;++i)e.push(this[this.arguments[i]]);var r=this._undo||new y.Matrix;return r=(new y.Matrix).morph(y.Matrix.prototype[this.method].apply(r,e)).at(t),this.inversed?r.inverse():r},undo:function(t){for(var e=0,i=this.arguments.length;e<i;++e)t[this.arguments[e]]="undefined"==typeof this[this.arguments[e]]?0:t[this.arguments[e]];return this._undo=new(y[s(this.method)])(t,(!0)).at(1),this}}}),y.Translate=y.invent({parent:y.Matrix,inherit:y.Transformation,create:function(t,e){"object"==typeof t?this.constructor.call(this,t,e):this.constructor.call(this,[].slice.call(arguments))},extend:{arguments:["transformedX","transformedY"],method:"translate"}}),y.Rotate=y.invent({parent:y.Matrix,inherit:y.Transformation,create:function(t,e){"object"==typeof t?this.constructor.call(this,t,e):this.constructor.call(this,[].slice.call(arguments))},extend:{arguments:["rotation","cx","cy"],method:"rotate",at:function(t){var e=(new y.Matrix).rotate((new y.Number).morph(this.rotation-(this._undo?this._undo.rotation:0)).at(t),this.cx,this.cy);return this.inversed?e.inverse():e},undo:function(t){this._undo=t}}}),y.Scale=y.invent({parent:y.Matrix,inherit:y.Transformation,create:function(t,e){"object"==typeof t?this.constructor.call(this,t,e):this.constructor.call(this,[].slice.call(arguments))},extend:{arguments:["scaleX","scaleY","cx","cy"],method:"scale"}}),y.Skew=y.invent({parent:y.Matrix,inherit:y.Transformation,create:function(t,e){"object"==typeof t?this.constructor.call(this,t,e):this.constructor.call(this,[].slice.call(arguments))},extend:{arguments:["skewX","skewY","cx","cy"],method:"skew"}}),y.extend(y.Element,{style:function(t,e){if(0==arguments.length)return this.node.style.cssText||"";if(arguments.length<2)if("object"==typeof t)for(e in t)this.style(e,t[e]);else{if(!y.regex.isCss.test(t))return this.node.style[r(t)];t=t.split(";");for(var i=0;i<t.length;i++)e=t[i].split(":"),this.style(e[0].replace(/\s+/g,""),e[1])}else this.node.style[r(t)]=null===e||y.regex.isBlank.test(e)?"":e;return this}}),y.Parent=y.invent({create:function(t){this.constructor.call(this,t)},inherit:y.Element,extend:{children:function(){return y.utils.map(y.utils.filterSVGElements(this.node.childNodes),function(t){return y.adopt(t)})},add:function(t,e){return null==e?this.node.appendChild(t.node):t.node!=this.node.childNodes[e]&&this.node.insertBefore(t.node,this.node.childNodes[e]),this},put:function(t,e){return this.add(t,e),t},has:function(t){return this.index(t)>=0},index:function(t){return[].slice.call(this.node.childNodes).indexOf(t.node)},get:function(t){return y.adopt(this.node.childNodes[t])},first:function(){return this.get(0)},last:function(){return this.get(this.node.childNodes.length-1)},each:function(t,e){var i,n,r=this.children();for(i=0,n=r.length;i<n;i++)r[i]instanceof y.Element&&t.apply(r[i],[i,r]),e&&r[i]instanceof y.Container&&r[i].each(t,e);return this},removeElement:function(t){return this.node.removeChild(t.node),this},clear:function(){for(;this.node.hasChildNodes();)this.node.removeChild(this.node.lastChild);return delete this._defs,this},defs:function(){return this.doc().defs()}}}),y.extend(y.Parent,{ungroup:function(t,e){return 0===e||this instanceof y.Defs?this:(t=t||(this instanceof y.Doc?this:this.parent(y.Parent)),e=e||1/0,this.each(function(){return this instanceof y.Defs?this:this instanceof y.Parent?this.ungroup(t,e-1):this.toParent(t)}),this.node.firstChild||this.remove(),this)},flatten:function(t,e){return this.ungroup(t,e)}}),y.Container=y.invent({create:function(t){this.constructor.call(this,t)},inherit:y.Parent}),y.ViewBox=y.invent({create:function(t){var e,i,n,r,s,a,o,h,u=[0,0,0,0],l=1,c=1,f=/[+-]?(?:\d+(?:\.\d*)?|\.\d+)(?:e[+-]?\d+)?/gi;if(t instanceof y.Element){for(o=t,h=t,a=(t.attr("viewBox")||"").match(f),s=t.bbox,n=new y.Number(t.width()),r=new y.Number(t.height());"%"==n.unit;)l*=n.value,n=new y.Number(o instanceof y.Doc?o.parent().offsetWidth:o.parent().width()),o=o.parent();for(;"%"==r.unit;)c*=r.value,r=new y.Number(h instanceof y.Doc?h.parent().offsetHeight:h.parent().height()),h=h.parent();this.x=0,this.y=0,this.width=n*l,this.height=r*c,this.zoom=1,a&&(e=parseFloat(a[0]),i=parseFloat(a[1]),n=parseFloat(a[2]),r=parseFloat(a[3]),this.zoom=this.width/this.height>n/r?this.height/r:this.width/n,this.x=e,this.y=i,this.width=n,this.height=r)}else t="string"==typeof t?t.match(f).map(function(t){return parseFloat(t)}):Array.isArray(t)?t:"object"==typeof t?[t.x,t.y,t.width,t.height]:4==arguments.length?[].slice.call(arguments):u,this.x=t[0],this.y=t[1],this.width=t[2],this.height=t[3]},extend:{toString:function(){return this.x+" "+this.y+" "+this.width+" "+this.height},morph:function(t){var t=1==arguments.length?[t.x,t.y,t.width,t.height]:[].slice.call(arguments);return this.destination=new y.ViewBox(t),this},at:function(t){return this.destination?new y.ViewBox([this.x+(this.destination.x-this.x)*t,this.y+(this.destination.y-this.y)*t,this.width+(this.destination.width-this.width)*t,this.height+(this.destination.height-this.height)*t]):this}},parent:y.Container,construct:{viewbox:function(t){return 0==arguments.length?new y.ViewBox(this):(t=1==arguments.length?[t.x,t.y,t.width,t.height]:[].slice.call(arguments),this.attr("viewBox",t))}}}),["click","dblclick","mousedown","mouseup","mouseover","mouseout","mousemove","touchstart","touchmove","touchleave","touchend","touchcancel"].forEach(function(t){y.Element.prototype[t]=function(e){var i=this;return this.node["on"+t]="function"==typeof e?function(){return e.apply(i,arguments)}:null,this}}),y.listeners=[],y.handlerMap=[],y.listenerId=0,y.on=function(t,e,i,n){var r=i.bind(n||t.instance||t),s=(y.handlerMap.indexOf(t)+1||y.handlerMap.push(t))-1,a=e.split(".")[0],o=e.split(".")[1]||"*";y.listeners[s]=y.listeners[s]||{},y.listeners[s][a]=y.listeners[s][a]||{},y.listeners[s][a][o]=y.listeners[s][a][o]||{},i._svgjsListenerId||(i._svgjsListenerId=++y.listenerId),y.listeners[s][a][o][i._svgjsListenerId]=r,t.addEventListener(a,r,!1)},y.off=function(t,e,i){var n=y.handlerMap.indexOf(t),r=e&&e.split(".")[0],s=e&&e.split(".")[1];if(n!=-1)if(i){if("function"==typeof i&&(i=i._svgjsListenerId),!i)return;y.listeners[n][r]&&y.listeners[n][r][s||"*"]&&(t.removeEventListener(r,y.listeners[n][r][s||"*"][i],!1),delete y.listeners[n][r][s||"*"][i])}else if(s&&r){if(y.listeners[n][r]&&y.listeners[n][r][s]){for(i in y.listeners[n][r][s])y.off(t,[r,s].join("."),i);delete y.listeners[n][r][s]}}else if(s)for(e in y.listeners[n])for(namespace in y.listeners[n][e])s===namespace&&y.off(t,[e,s].join("."));else if(r){if(y.listeners[n][r]){for(namespace in y.listeners[n][r])y.off(t,[r,namespace].join("."));delete y.listeners[n][r]}}else{for(e in y.listeners[n])y.off(t,e);delete y.listeners[n]}},y.extend(y.Element,{on:function(t,e,i){return y.on(this.node,t,e,i),this},off:function(t,e){return y.off(this.node,t,e),this},fire:function(t,e){return t instanceof Event?this.node.dispatchEvent(t):this.node.dispatchEvent(new b(t,{detail:e})),this}}),y.Defs=y.invent({create:"defs",inherit:y.Container}),y.G=y.invent({create:"g",inherit:y.Container,extend:{x:function(t){return null==t?this.transform("x"):this.transform({x:t-this.x()},!0)},y:function(t){return null==t?this.transform("y"):this.transform({y:t-this.y()},!0)},cx:function(t){return null==t?this.gbox().cx:this.x(t-this.gbox().width/2)},cy:function(t){return null==t?this.gbox().cy:this.y(t-this.gbox().height/2)},gbox:function(){var t=this.bbox(),e=this.transform();return t.x+=e.x,t.x2+=e.x,t.cx+=e.x,t.y+=e.y,t.y2+=e.y,t.cy+=e.y,t}},construct:{group:function(){return this.put(new y.G)}}}),y.extend(y.Element,{siblings:function(){return this.parent().children()},position:function(){return this.parent().index(this)},next:function(){return this.siblings()[this.position()+1]},previous:function(){return this.siblings()[this.position()-1]},forward:function(){var t=this.position()+1,e=this.parent();return e.removeElement(this).add(this,t),e instanceof y.Doc&&e.node.appendChild(e.defs().node),this},backward:function(){var t=this.position();return t>0&&this.parent().removeElement(this).add(this,t-1),this},front:function(){var t=this.parent();return t.node.appendChild(this.node),t instanceof y.Doc&&t.node.appendChild(t.defs().node),this},back:function(){return this.position()>0&&this.parent().removeElement(this).add(this,0),this},before:function(t){t.remove();var e=this.position();return this.parent().add(t,e),this},after:function(t){t.remove();var e=this.position();return this.parent().add(t,e+1),this}}),y.Mask=y.invent({create:function(){this.constructor.call(this,y.create("mask")),this.targets=[]},inherit:y.Container,extend:{remove:function(){for(var t=this.targets.length-1;t>=0;t--)this.targets[t]&&this.targets[t].unmask();return this.targets=[],this.parent().removeElement(this),this}},construct:{mask:function(){return this.defs().put(new y.Mask)}}}),y.extend(y.Element,{maskWith:function(t){return this.masker=t instanceof y.Mask?t:this.parent().mask().add(t),this.masker.targets.push(this),this.attr("mask",'url("#'+this.masker.attr("id")+'")')},unmask:function(){return delete this.masker,this.attr("mask",null)}}),y.ClipPath=y.invent({create:function(){this.constructor.call(this,y.create("clipPath")),this.targets=[]},inherit:y.Container,extend:{remove:function(){for(var t=this.targets.length-1;t>=0;t--)this.targets[t]&&this.targets[t].unclip();return this.targets=[],this.parent().removeElement(this),this}},construct:{clip:function(){return this.defs().put(new y.ClipPath)}}}),y.extend(y.Element,{clipWith:function(t){return this.clipper=t instanceof y.ClipPath?t:this.parent().clip().add(t),this.clipper.targets.push(this),this.attr("clip-path",'url("#'+this.clipper.attr("id")+'")')},unclip:function(){return delete this.clipper,this.attr("clip-path",null)}}),y.Gradient=y.invent({create:function(t){this.constructor.call(this,y.create(t+"Gradient")),this.type=t},inherit:y.Container,extend:{at:function(t,e,i){return this.put(new y.Stop).update(t,e,i)},update:function(t){return this.clear(),"function"==typeof t&&t.call(this,this),this},fill:function(){return"url(#"+this.id()+")"},toString:function(){return this.fill()},attr:function(t,e,i){return"transform"==t&&(t="gradientTransform"),y.Container.prototype.attr.call(this,t,e,i)}},construct:{gradient:function(t,e){return this.defs().gradient(t,e)}}}),y.extend(y.Gradient,y.FX,{from:function(t,e){return"radial"==(this._target||this).type?this.attr({fx:new y.Number(t),fy:new y.Number(e)}):this.attr({x1:new y.Number(t),y1:new y.Number(e)})},to:function(t,e){return"radial"==(this._target||this).type?this.attr({cx:new y.Number(t),cy:new y.Number(e)}):this.attr({x2:new y.Number(t),y2:new y.Number(e)})}}),y.extend(y.Defs,{gradient:function(t,e){return this.put(new y.Gradient(t)).update(e)}}),y.Stop=y.invent({create:"stop",inherit:y.Element,extend:{update:function(t){return("number"==typeof t||t instanceof y.Number)&&(t={offset:arguments[0],color:arguments[1],opacity:arguments[2]}),null!=t.opacity&&this.attr("stop-opacity",t.opacity),null!=t.color&&this.attr("stop-color",t.color),null!=t.offset&&this.attr("offset",new y.Number(t.offset)),this}}}),y.Pattern=y.invent({create:"pattern",inherit:y.Container,extend:{fill:function(){return"url(#"+this.id()+")"},update:function(t){return this.clear(),"function"==typeof t&&t.call(this,this),this},toString:function(){return this.fill()},attr:function(t,e,i){return"transform"==t&&(t="patternTransform"),y.Container.prototype.attr.call(this,t,e,i)}},construct:{pattern:function(t,e,i){return this.defs().pattern(t,e,i)}}}),y.extend(y.Defs,{pattern:function(t,e,i){return this.put(new y.Pattern).update(i).attr({x:0,y:0,width:t,height:e,patternUnits:"userSpaceOnUse"})}}),y.Doc=y.invent({create:function(t){t&&(t="string"==typeof t?e.getElementById(t):t,"svg"==t.nodeName?this.constructor.call(this,t):(this.constructor.call(this,y.create("svg")),t.appendChild(this.node),this.size("100%","100%")),this.namespace().defs())},inherit:y.Container,extend:{namespace:function(){return this.attr({xmlns:y.ns,version:"1.1"}).attr("xmlns:xlink",y.xlink,y.xmlns).attr("xmlns:svgjs",y.svgjs,y.xmlns)},defs:function(){if(!this._defs){var t;(t=this.node.getElementsByTagName("defs")[0])?this._defs=y.adopt(t):this._defs=new y.Defs,this.node.appendChild(this._defs.node)}return this._defs},parent:function(){return"#document"==this.node.parentNode.nodeName?null:this.node.parentNode},spof:function(t){var e=this.node.getScreenCTM();return e&&this.style("left",-e.e%1+"px").style("top",-e.f%1+"px"),this},remove:function(){return this.parent()&&this.parent().removeChild(this.node),this}}}),y.Shape=y.invent({create:function(t){this.constructor.call(this,t)},inherit:y.Element}),y.Bare=y.invent({create:function(t,e){if(this.constructor.call(this,y.create(t)),e)for(var i in e.prototype)"function"==typeof e.prototype[i]&&(this[i]=e.prototype[i])},inherit:y.Element,extend:{words:function(t){for(;this.node.hasChildNodes();)this.node.removeChild(this.node.lastChild);return this.node.appendChild(e.createTextNode(t)),this}}}),y.extend(y.Parent,{element:function(t,e){return this.put(new y.Bare(t,e))},symbol:function(){return this.defs().element("symbol",y.Container)}}),y.Use=y.invent({create:"use",inherit:y.Shape,extend:{element:function(t,e){return this.attr("href",(e||"")+"#"+t,y.xlink)}},construct:{use:function(t,e){return this.put(new y.Use).element(t,e)}}}),y.Rect=y.invent({create:"rect",inherit:y.Shape,construct:{rect:function(t,e){return this.put(new y.Rect).size(t,e)}}}),y.Circle=y.invent({create:"circle",inherit:y.Shape,construct:{circle:function(t){return this.put(new y.Circle).rx(new y.Number(t).divide(2)).move(0,0)}}}),y.extend(y.Circle,y.FX,{rx:function(t){return this.attr("r",t)},ry:function(t){return this.rx(t)}}),y.Ellipse=y.invent({create:"ellipse",inherit:y.Shape,construct:{ellipse:function(t,e){return this.put(new y.Ellipse).size(t,e).move(0,0)}}}),y.extend(y.Ellipse,y.Rect,y.FX,{rx:function(t){return this.attr("rx",t)},ry:function(t){return this.attr("ry",t)}}),y.extend(y.Circle,y.Ellipse,{x:function(t){return null==t?this.cx()-this.rx():this.cx(t+this.rx())},y:function(t){return null==t?this.cy()-this.ry():this.cy(t+this.ry())},cx:function(t){return null==t?this.attr("cx"):this.attr("cx",t)},cy:function(t){return null==t?this.attr("cy"):this.attr("cy",t)},width:function(t){return null==t?2*this.rx():this.rx(new y.Number(t).divide(2))},height:function(t){return null==t?2*this.ry():this.ry(new y.Number(t).divide(2))},size:function(t,e){var i=h(this,t,e);return this.rx(new y.Number(i.width).divide(2)).ry(new y.Number(i.height).divide(2))}}),y.Line=y.invent({create:"line",inherit:y.Shape,extend:{array:function(){return new y.PointArray([[this.attr("x1"),this.attr("y1")],[this.attr("x2"),this.attr("y2")]])},plot:function(t,e,i,n){return t="undefined"!=typeof e?{x1:t,y1:e,x2:i,y2:n}:new y.PointArray(t).toLine(),this.attr(t)},move:function(t,e){return this.attr(this.array().move(t,e).toLine())},size:function(t,e){var i=h(this,t,e);return this.attr(this.array().size(i.width,i.height).toLine())}},construct:{line:function(t,e,i,n){return this.put(new y.Line).plot(t,e,i,n)}}}),y.Polyline=y.invent({create:"polyline",inherit:y.Shape,construct:{polyline:function(t){return this.put(new y.Polyline).plot(t)}}}),y.Polygon=y.invent({create:"polygon",inherit:y.Shape,construct:{polygon:function(t){return this.put(new y.Polygon).plot(t)}}}),y.extend(y.Polyline,y.Polygon,{array:function(){return this._array||(this._array=new y.PointArray(this.attr("points")))},plot:function(t){return this.attr("points",this._array=new y.PointArray(t))},move:function(t,e){return this.attr("points",this.array().move(t,e))},size:function(t,e){var i=h(this,t,e);return this.attr("points",this.array().size(i.width,i.height))}}),y.extend(y.Line,y.Polyline,y.Polygon,{morphArray:y.PointArray,x:function(t){return null==t?this.bbox().x:this.move(t,this.bbox().y)},y:function(t){return null==t?this.bbox().y:this.move(this.bbox().x,t)},width:function(t){var e=this.bbox();return null==t?e.width:this.size(t,e.height)},height:function(t){var e=this.bbox();return null==t?e.height:this.size(e.width,t)}}),y.Path=y.invent({create:"path",inherit:y.Shape,extend:{morphArray:y.PathArray,array:function(){return this._array||(this._array=new y.PathArray(this.attr("d")))},plot:function(t){return this.attr("d",this._array=new y.PathArray(t))},move:function(t,e){return this.attr("d",this.array().move(t,e))},x:function(t){return null==t?this.bbox().x:this.move(t,this.bbox().y)},y:function(t){return null==t?this.bbox().y:this.move(this.bbox().x,t)},size:function(t,e){var i=h(this,t,e);return this.attr("d",this.array().size(i.width,i.height))},width:function(t){return null==t?this.bbox().width:this.size(t,this.bbox().height)},height:function(t){return null==t?this.bbox().height:this.size(this.bbox().width,t)}},construct:{path:function(t){return this.put(new y.Path).plot(t)}}}),y.Image=y.invent({create:"image",inherit:y.Shape,extend:{load:function(t){if(!t)return this;var i=this,n=e.createElement("img");return n.onload=function(){var e=i.parent(y.Pattern);null!==e&&(0==i.width()&&0==i.height()&&i.size(n.width,n.height),e&&0==e.width()&&0==e.height()&&e.size(i.width(),i.height()),"function"==typeof i._loaded&&i._loaded.call(i,{width:n.width,height:n.height,ratio:n.width/n.height,url:t}))},n.onerror=function(t){"function"==typeof i._error&&i._error.call(i,t)},this.attr("href",n.src=this.src=t,y.xlink)},loaded:function(t){return this._loaded=t,this},error:function(t){return this._error=t,this}},construct:{image:function(t,e,i){return this.put(new y.Image).load(t).size(e||0,i||e||0)}}}),y.Text=y.invent({create:function(){this.constructor.call(this,y.create("text")),this.dom.leading=new y.Number(1.3),this._rebuild=!0,this._build=!1,this.attr("font-family",y.defaults.attrs["font-family"])},inherit:y.Shape,extend:{x:function(t){return null==t?this.attr("x"):(this.textPath||this.lines().each(function(){this.dom.newLined&&this.x(t)}),this.attr("x",t))},y:function(t){var e=this.attr("y"),i="number"==typeof e?e-this.bbox().y:0;return null==t?"number"==typeof e?e-i:e:this.attr("y","number"==typeof t?t+i:t)},cx:function(t){return null==t?this.bbox().cx:this.x(t-this.bbox().width/2)},cy:function(t){return null==t?this.bbox().cy:this.y(t-this.bbox().height/2)},text:function(t){if("undefined"==typeof t){for(var t="",e=this.node.childNodes,i=0,n=e.length;i<n;++i)0!=i&&3!=e[i].nodeType&&1==y.adopt(e[i]).dom.newLined&&(t+="\n"),t+=e[i].textContent;return t}if(this.clear().build(!0),"function"==typeof t)t.call(this,this);else{t=t.split("\n");for(var i=0,r=t.length;i<r;i++)this.tspan(t[i]).newLine()}return this.build(!1).rebuild()},size:function(t){return this.attr("font-size",t).rebuild()},leading:function(t){return null==t?this.dom.leading:(this.dom.leading=new y.Number(t),this.rebuild())},lines:function(){var t=(this.textPath&&this.textPath()||this).node,e=y.utils.map(y.utils.filterSVGElements(t.childNodes),function(t){return y.adopt(t)});return new y.Set(e)},rebuild:function(t){if("boolean"==typeof t&&(this._rebuild=t),this._rebuild){var e=this,i=0,n=this.dom.leading*new y.Number(this.attr("font-size"));this.lines().each(function(){this.dom.newLined&&(this.textPath||this.attr("x",e.attr("x")),"\n"==this.text()?i+=n:(this.attr("dy",n+i),i=0))}),this.fire("rebuild")}return this},build:function(t){return this._build=!!t,this},setData:function(t){return this.dom=t,this.dom.leading=new y.Number(t.leading||1.3),this}},construct:{text:function(t){return this.put(new y.Text).text(t)},plain:function(t){return this.put(new y.Text).plain(t)}}}),y.Tspan=y.invent({create:"tspan",inherit:y.Shape,extend:{text:function(t){return null==t?this.node.textContent+(this.dom.newLined?"\n":""):("function"==typeof t?t.call(this,this):this.plain(t),this)},dx:function(t){return this.attr("dx",t)},dy:function(t){return this.attr("dy",t)},newLine:function(){var t=this.parent(y.Text);return this.dom.newLined=!0,this.dy(t.dom.leading*t.attr("font-size")).attr("x",t.x())}}}),y.extend(y.Text,y.Tspan,{plain:function(t){return this._build===!1&&this.clear(),this.node.appendChild(e.createTextNode(t)),this},tspan:function(t){var e=(this.textPath&&this.textPath()||this).node,i=new y.Tspan;return this._build===!1&&this.clear(),e.appendChild(i.node),i.text(t)},clear:function(){for(var t=(this.textPath&&this.textPath()||this).node;t.hasChildNodes();)t.removeChild(t.lastChild);return this},length:function(){return this.node.getComputedTextLength()}}),y.TextPath=y.invent({create:"textPath",inherit:y.Parent,parent:y.Text,construct:{path:function(t){for(var e=new y.TextPath,i=this.doc().defs().path(t);this.node.hasChildNodes();)e.node.appendChild(this.node.firstChild);return this.node.appendChild(e.node),e.attr("href","#"+i,y.xlink),this},plot:function(t){var e=this.track();return e&&e.plot(t),this},track:function(){var t=this.textPath();if(t)return t.reference("href")},textPath:function(){if(this.node.firstChild&&"textPath"==this.node.firstChild.nodeName)return y.adopt(this.node.firstChild)}}}),y.Nested=y.invent({create:function(){this.constructor.call(this,y.create("svg")),this.style("overflow","visible")},inherit:y.Container,construct:{nested:function(){return this.put(new y.Nested)}}}),y.A=y.invent({create:"a",inherit:y.Container,extend:{to:function(t){return this.attr("href",t,y.xlink)},show:function(t){return this.attr("show",t,y.xlink)},target:function(t){return this.attr("target",t)}},construct:{link:function(t){return this.put(new y.A).to(t)}}}),y.extend(y.Element,{linkTo:function(t){var e=new y.A;return"function"==typeof t?t.call(e,e):e.to(t),this.parent().put(e).put(this)}}),y.Marker=y.invent({create:"marker",inherit:y.Container,extend:{width:function(t){return this.attr("markerWidth",t)},height:function(t){return this.attr("markerHeight",t)},ref:function(t,e){return this.attr("refX",t).attr("refY",e)},update:function(t){return this.clear(),"function"==typeof t&&t.call(this,this),this},toString:function(){return"url(#"+this.id()+")"}},construct:{marker:function(t,e,i){return this.defs().marker(t,e,i)}}}),y.extend(y.Defs,{marker:function(t,e,i){return this.put(new y.Marker).size(t,e).ref(t/2,e/2).viewbox(0,0,t,e).attr("orient","auto").update(i)}}),y.extend(y.Line,y.Polyline,y.Polygon,y.Path,{marker:function(t,e,i,n){var r=["marker"];return"all"!=t&&r.push(t),r=r.join("-"),t=arguments[1]instanceof y.Marker?arguments[1]:this.doc().marker(e,i,n),this.attr(r,t)}});var g={stroke:["color","width","opacity","linecap","linejoin","miterlimit","dasharray","dashoffset"],fill:["color","opacity","rule"],prefix:function(t,e){return"color"==e?t:t+"-"+e}};["fill","stroke"].forEach(function(t){var e,i={};i[t]=function(i){if("undefined"==typeof i)return this;if("string"==typeof i||y.Color.isRgb(i)||i&&"function"==typeof i.fill)this.attr(t,i);else for(e=g[t].length-1;e>=0;e--)null!=i[g[t][e]]&&this.attr(g.prefix(t,g[t][e]),i[g[t][e]]);return this},y.extend(y.Element,y.FX,i)}),y.extend(y.Element,y.FX,{rotate:function(t,e,i){return this.transform({rotation:t,cx:e,cy:i})},skew:function(t,e,i,n){return this.transform({skewX:t,skewY:e,cx:i,cy:n})},scale:function(t,e,i,n){return 1==arguments.length||3==arguments.length?this.transform({scale:t,cx:e,cy:i}):this.transform({scaleX:t,scaleY:e,cx:i,cy:n})},translate:function(t,e){return this.transform({x:t,y:e})},flip:function(t,e){return this.transform({flip:t,offset:e})},matrix:function(t){return this.attr("transform",new y.Matrix(t))},opacity:function(t){return this.attr("opacity",t)},dx:function(t){return this.x((this instanceof y.FX?0:this.x())+t,!0)},dy:function(t){return this.y((this instanceof y.FX?0:this.y())+t,!0)},dmove:function(t,e){return this.dx(t).dy(e)}}),y.extend(y.Rect,y.Ellipse,y.Circle,y.Gradient,y.FX,{radius:function(t,e){var i=(this._target||this).type;return"radial"==i||"circle"==i?this.attr("r",new y.Number(t)):this.rx(t).ry(null==e?t:e)}}),y.extend(y.Path,{length:function(){return this.node.getTotalLength()},pointAt:function(t){return this.node.getPointAtLength(t)}}),y.extend(y.Parent,y.Text,y.FX,{font:function(t){for(var e in t)"leading"==e?this.leading(t[e]):"anchor"==e?this.attr("text-anchor",t[e]):"size"==e||"family"==e||"weight"==e||"stretch"==e||"variant"==e||"style"==e?this.attr("font-"+e,t[e]):this.attr(e,t[e]);return this}}),y.Set=y.invent({create:function(t){Array.isArray(t)?this.members=t:this.clear()},extend:{add:function(){var t,e,i=[].slice.call(arguments);for(t=0,e=i.length;t<e;t++)this.members.push(i[t]);return this},remove:function(t){var e=this.index(t);return e>-1&&this.members.splice(e,1),this},each:function(t){for(var e=0,i=this.members.length;e<i;e++)t.apply(this.members[e],[e,this.members]);return this},clear:function(){return this.members=[],this},length:function(){return this.members.length},has:function(t){return this.index(t)>=0},index:function(t){return this.members.indexOf(t)},get:function(t){return this.members[t]},first:function(){return this.get(0)},last:function(){return this.get(this.members.length-1)},valueOf:function(){return this.members},bbox:function(){var t=new y.BBox;if(0==this.members.length)return t;var e=this.members[0].rbox();return t.x=e.x,t.y=e.y,t.width=e.width,t.height=e.height,this.each(function(){t=t.merge(this.rbox())}),t}},construct:{set:function(t){return new y.Set(t)}}}),y.FX.Set=y.invent({create:function(t){this.set=t}}),y.Set.inherit=function(){var t,e=[];for(var t in y.Shape.prototype)"function"==typeof y.Shape.prototype[t]&&"function"!=typeof y.Set.prototype[t]&&e.push(t);e.forEach(function(t){y.Set.prototype[t]=function(){for(var e=0,i=this.members.length;e<i;e++)this.members[e]&&"function"==typeof this.members[e][t]&&this.members[e][t].apply(this.members[e],arguments);return"animate"==t?this.fx||(this.fx=new y.FX.Set(this)):this}}),e=[];for(var t in y.FX.prototype)"function"==typeof y.FX.prototype[t]&&"function"!=typeof y.FX.Set.prototype[t]&&e.push(t);e.forEach(function(t){y.FX.Set.prototype[t]=function(){for(var e=0,i=this.set.members.length;e<i;e++)this.set.members[e].fx[t].apply(this.set.members[e].fx,arguments);return this}})},y.extend(y.Element,{data:function(t,e,i){if("object"==typeof t)for(e in t)this.data(e,t[e]);else if(arguments.length<2)try{return JSON.parse(this.attr("data-"+t))}catch(e){return this.attr("data-"+t)}else this.attr("data-"+t,null===e?null:i===!0||"string"==typeof e||"number"==typeof e?e:JSON.stringify(e));return this}}),y.extend(y.Element,{remember:function(t,e){if("object"==typeof arguments[0])for(var e in t)this.remember(e,t[e]);else{if(1==arguments.length)return this.memory()[t];this.memory()[t]=e}return this},forget:function(){if(0==arguments.length)this._memory={};else for(var t=arguments.length-1;t>=0;t--)delete this.memory()[arguments[t]];return this},memory:function(){return this._memory||(this._memory={})}}),y.get=function(t){var i=e.getElementById(v(t)||t);return y.adopt(i)},y.select=function(t,i){return new y.Set(y.utils.map((i||e).querySelectorAll(t),function(t){return y.adopt(t)}))},y.extend(y.Parent,{select:function(t){return y.select(t,this.node)}});var w="abcdef".split("");if("function"!=typeof b){var b=function(t,i){i=i||{bubbles:!1,cancelable:!1,detail:void 0};var n=e.createEvent("CustomEvent");return n.initCustomEvent(t,i.bubbles,i.cancelable,i.detail),n};b.prototype=t.Event.prototype,t.CustomEvent=b}return function(e){for(var i=0,n=["moz","webkit"],r=0;r<n.length&&!t.requestAnimationFrame;++r)e.requestAnimationFrame=e[n[r]+"RequestAnimationFrame"],e.cancelAnimationFrame=e[n[r]+"CancelAnimationFrame"]||e[n[r]+"CancelRequestAnimationFrame"];e.requestAnimationFrame=e.requestAnimationFrame||function(t){var n=(new Date).getTime(),r=Math.max(0,16-(n-i)),s=e.setTimeout(function(){t(n+r)},r);return i=n+r,s},e.cancelAnimationFrame=e.cancelAnimationFrame||e.clearTimeout}(t),y});
\ No newline at end of file
+/*! svg.js v2.3.6 MIT*/;!function(t,e){"function"==typeof define&&define.amd?define(function(){return e(t,t.document)}):"object"==typeof exports?module.exports=t.document?e(t,t.document):function(t){return e(t,t.document)}:t.SVG=e(t,t.document)}("undefined"!=typeof window?window:this,function(t,e){function i(t,e){return t instanceof e}function n(t,e){return(t.matches||t.matchesSelector||t.msMatchesSelector||t.mozMatchesSelector||t.webkitMatchesSelector||t.oMatchesSelector).call(t,e)}function s(t){return t.toLowerCase().replace(/-(.)/g,function(t,e){return e.toUpperCase()})}function r(t){return t.charAt(0).toUpperCase()+t.slice(1)}function a(t){return 4==t.length?["#",t.substring(1,2),t.substring(1,2),t.substring(2,3),t.substring(2,3),t.substring(3,4),t.substring(3,4)].join(""):t}function o(t){var e=t.toString(16);return 1==e.length?"0"+e:e}function h(t,e,i){if(null==e||null==i){var n=t.bbox();null==e?e=n.width/n.height*i:null==i&&(i=n.height/n.width*e)}return{width:e,height:i}}function u(t,e,i){return{x:e*t.a+i*t.c+0,y:e*t.b+i*t.d+0}}function l(t){return{a:t[0],b:t[1],c:t[2],d:t[3],e:t[4],f:t[5]}}function c(t){return t instanceof y.Matrix||(t=new y.Matrix(t)),t}function f(t,e){t.cx=null==t.cx?e.bbox().cx:t.cx,t.cy=null==t.cy?e.bbox().cy:t.cy}function d(t){return t=t.replace(y.regex.whitespace,"").replace(y.regex.matrix,"").split(y.regex.matrixElements),l(y.utils.map(t,function(t){return parseFloat(t)}))}function p(t){for(var e=0,i=t.length,n="";e<i;e++)n+=t[e][0],null!=t[e][1]&&(n+=t[e][1],null!=t[e][2]&&(n+=" ",n+=t[e][2],null!=t[e][3]&&(n+=" ",n+=t[e][3],n+=" ",n+=t[e][4],null!=t[e][5]&&(n+=" ",n+=t[e][5],n+=" ",n+=t[e][6],null!=t[e][7]&&(n+=" ",n+=t[e][7])))));return n+" "}function m(t){for(var e=t.childNodes.length-1;e>=0;e--)t.childNodes[e]instanceof SVGElement&&m(t.childNodes[e]);return y.adopt(t).id(y.eid(t.nodeName))}function x(t){return null==t.x&&(t.x=0,t.y=0,t.width=0,t.height=0),t.w=t.width,t.h=t.height,t.x2=t.x+t.width,t.y2=t.y+t.height,t.cx=t.x+t.width/2,t.cy=t.y+t.height/2,t}function v(t){var e=t.toString().match(y.regex.reference);if(e)return e[1]}var y=this.SVG=function(t){if(y.supported)return t=new y.Doc(t),y.parser.draw||y.prepare(),t};if(y.ns="http://www.w3.org/2000/svg",y.xmlns="http://www.w3.org/2000/xmlns/",y.xlink="http://www.w3.org/1999/xlink",y.svgjs="http://svgjs.com/svgjs",y.supported=function(){return!!e.createElementNS&&!!e.createElementNS(y.ns,"svg").createSVGRect}(),!y.supported)return!1;y.did=1e3,y.eid=function(t){return"Svgjs"+r(t)+y.did++},y.create=function(t){var i=e.createElementNS(this.ns,t);return i.setAttribute("id",this.eid(t)),i},y.extend=function(){var t,e,i,n;for(t=[].slice.call(arguments),e=t.pop(),n=t.length-1;n>=0;n--)if(t[n])for(i in e)t[n].prototype[i]=e[i];y.Set&&y.Set.inherit&&y.Set.inherit()},y.invent=function(t){var e="function"==typeof t.create?t.create:function(){this.constructor.call(this,y.create(t.create))};return t.inherit&&(e.prototype=new t.inherit),t.extend&&y.extend(e,t.extend),t.construct&&y.extend(t.parent||y.Container,t.construct),e},y.adopt=function(t){if(!t)return null;if(t.instance)return t.instance;var e;return e="svg"==t.nodeName?t.parentNode instanceof SVGElement?new y.Nested:new y.Doc:"linearGradient"==t.nodeName?new y.Gradient("linear"):"radialGradient"==t.nodeName?new y.Gradient("radial"):y[r(t.nodeName)]?new(y[r(t.nodeName)]):new y.Element(t),e.type=t.nodeName,e.node=t,t.instance=e,e instanceof y.Doc&&e.namespace().defs(),e.setData(JSON.parse(t.getAttribute("svgjs:data"))||{}),e},y.prepare=function(){var t=e.getElementsByTagName("body")[0],i=(t?new y.Doc(t):new y.Doc(e.documentElement).nested()).size(2,0);y.parser={body:t||e.documentElement,draw:i.style("opacity:0;position:fixed;left:100%;top:100%;overflow:hidden"),poly:i.polyline().node,path:i.path().node,native:y.create("svg")}},y.parser={native:y.create("svg")},e.addEventListener("DOMContentLoaded",function(){y.parser.draw||y.prepare()},!1),y.regex={numberAndUnit:/^([+-]?(\d+(\.\d*)?|\.\d+)(e[+-]?\d+)?)([a-z%]*)$/i,hex:/^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i,rgb:/rgb\((\d+),(\d+),(\d+)\)/,reference:/#([a-z0-9\-_]+)/i,matrix:/matrix\(|\)/g,matrixElements:/,*\s+|,/,whitespace:/\s/g,isHex:/^#[a-f0-9]{3,6}$/i,isRgb:/^rgb\(/,isCss:/[^:]+:[^;]+;?/,isBlank:/^(\s+)?$/,isNumber:/^[+-]?(\d+(\.\d*)?|\.\d+)(e[+-]?\d+)?$/i,isPercent:/^-?[\d\.]+%$/,isImage:/\.(jpg|jpeg|png|gif|svg)(\?[^=]+.*)?/i,negExp:/e\-/gi,comma:/,/g,hyphen:/\-/g,pathLetters:/[MLHVCSQTAZ]/gi,isPathLetter:/[MLHVCSQTAZ]/i,whitespaces:/\s+/,X:/X/g},y.utils={map:function(t,e){var i,n=t.length,s=[];for(i=0;i<n;i++)s.push(e(t[i]));return s},filter:function(t,e){var i,n=t.length,s=[];for(i=0;i<n;i++)e(t[i])&&s.push(t[i]);return s},radians:function(t){return t%360*Math.PI/180},degrees:function(t){return 180*t/Math.PI%360},filterSVGElements:function(t){return this.filter(t,function(t){return t instanceof SVGElement})}},y.defaults={attrs:{"fill-opacity":1,"stroke-opacity":1,"stroke-width":0,"stroke-linejoin":"miter","stroke-linecap":"butt",fill:"#000000",stroke:"#000000",opacity:1,x:0,y:0,cx:0,cy:0,width:0,height:0,r:0,rx:0,ry:0,offset:0,"stop-opacity":1,"stop-color":"#000000","font-size":16,"font-family":"Helvetica, Arial, sans-serif","text-anchor":"start"}},y.Color=function(t){var e;this.r=0,this.g=0,this.b=0,t&&("string"==typeof t?y.regex.isRgb.test(t)?(e=y.regex.rgb.exec(t.replace(/\s/g,"")),this.r=parseInt(e[1]),this.g=parseInt(e[2]),this.b=parseInt(e[3])):y.regex.isHex.test(t)&&(e=y.regex.hex.exec(a(t)),this.r=parseInt(e[1],16),this.g=parseInt(e[2],16),this.b=parseInt(e[3],16)):"object"==typeof t&&(this.r=t.r,this.g=t.g,this.b=t.b))},y.extend(y.Color,{toString:function(){return this.toHex()},toHex:function(){return"#"+o(this.r)+o(this.g)+o(this.b)},toRgb:function(){return"rgb("+[this.r,this.g,this.b].join()+")"},brightness:function(){return this.r/255*.3+this.g/255*.59+this.b/255*.11},morph:function(t){return this.destination=new y.Color(t),this},at:function(t){return this.destination?(t=t<0?0:t>1?1:t,new y.Color({r:~~(this.r+(this.destination.r-this.r)*t),g:~~(this.g+(this.destination.g-this.g)*t),b:~~(this.b+(this.destination.b-this.b)*t)})):this}}),y.Color.test=function(t){return t+="",y.regex.isHex.test(t)||y.regex.isRgb.test(t)},y.Color.isRgb=function(t){return t&&"number"==typeof t.r&&"number"==typeof t.g&&"number"==typeof t.b},y.Color.isColor=function(t){return y.Color.isRgb(t)||y.Color.test(t)},y.Array=function(t,e){t=(t||[]).valueOf(),0==t.length&&e&&(t=e.valueOf()),this.value=this.parse(t)},y.extend(y.Array,{morph:function(t){if(this.destination=this.parse(t),this.value.length!=this.destination.length){for(var e=this.value[this.value.length-1],i=this.destination[this.destination.length-1];this.value.length>this.destination.length;)this.destination.push(i);for(;this.value.length<this.destination.length;)this.value.push(e)}return this},settle:function(){for(var t=0,e=this.value.length,i=[];t<e;t++)i.indexOf(this.value[t])==-1&&i.push(this.value[t]);return this.value=i},at:function(t){if(!this.destination)return this;for(var e=0,i=this.value.length,n=[];e<i;e++)n.push(this.value[e]+(this.destination[e]-this.value[e])*t);return new y.Array(n)},toString:function(){return this.value.join(" ")},valueOf:function(){return this.value},parse:function(t){return t=t.valueOf(),Array.isArray(t)?t:this.split(t)},split:function(t){return t.trim().split(/\s+/)},reverse:function(){return this.value.reverse(),this}}),y.PointArray=function(t,e){this.constructor.call(this,t,e||[[0,0]])},y.PointArray.prototype=new y.Array,y.extend(y.PointArray,{toString:function(){for(var t=0,e=this.value.length,i=[];t<e;t++)i.push(this.value[t].join(","));return i.join(" ")},toLine:function(){return{x1:this.value[0][0],y1:this.value[0][1],x2:this.value[1][0],y2:this.value[1][1]}},at:function(t){if(!this.destination)return this;for(var e=0,i=this.value.length,n=[];e<i;e++)n.push([this.value[e][0]+(this.destination[e][0]-this.value[e][0])*t,this.value[e][1]+(this.destination[e][1]-this.value[e][1])*t]);return new y.PointArray(n)},parse:function(t){var e=[];if(t=t.valueOf(),Array.isArray(t))return t;t=t.trim().split(/\s+|,/),t.length%2!==0&&t.pop();for(var i=0,n=t.length;i<n;i+=2)e.push([parseFloat(t[i]),parseFloat(t[i+1])]);return e},move:function(t,e){var i=this.bbox();if(t-=i.x,e-=i.y,!isNaN(t)&&!isNaN(e))for(var n=this.value.length-1;n>=0;n--)this.value[n]=[this.value[n][0]+t,this.value[n][1]+e];return this},size:function(t,e){var i,n=this.bbox();for(i=this.value.length-1;i>=0;i--)this.value[i][0]=(this.value[i][0]-n.x)*t/n.width+n.x,this.value[i][1]=(this.value[i][1]-n.y)*e/n.height+n.y;return this},bbox:function(){return y.parser.poly.setAttribute("points",this.toString()),y.parser.poly.getBBox()}}),y.PathArray=function(t,e){this.constructor.call(this,t,e||[["M",0,0]])},y.PathArray.prototype=new y.Array,y.extend(y.PathArray,{toString:function(){return p(this.value)},move:function(t,e){var i=this.bbox();if(t-=i.x,e-=i.y,!isNaN(t)&&!isNaN(e))for(var n,s=this.value.length-1;s>=0;s--)n=this.value[s][0],"M"==n||"L"==n||"T"==n?(this.value[s][1]+=t,this.value[s][2]+=e):"H"==n?this.value[s][1]+=t:"V"==n?this.value[s][1]+=e:"C"==n||"S"==n||"Q"==n?(this.value[s][1]+=t,this.value[s][2]+=e,this.value[s][3]+=t,this.value[s][4]+=e,"C"==n&&(this.value[s][5]+=t,this.value[s][6]+=e)):"A"==n&&(this.value[s][6]+=t,this.value[s][7]+=e);return this},size:function(t,e){var i,n,s=this.bbox();for(i=this.value.length-1;i>=0;i--)n=this.value[i][0],"M"==n||"L"==n||"T"==n?(this.value[i][1]=(this.value[i][1]-s.x)*t/s.width+s.x,this.value[i][2]=(this.value[i][2]-s.y)*e/s.height+s.y):"H"==n?this.value[i][1]=(this.value[i][1]-s.x)*t/s.width+s.x:"V"==n?this.value[i][1]=(this.value[i][1]-s.y)*e/s.height+s.y:"C"==n||"S"==n||"Q"==n?(this.value[i][1]=(this.value[i][1]-s.x)*t/s.width+s.x,this.value[i][2]=(this.value[i][2]-s.y)*e/s.height+s.y,this.value[i][3]=(this.value[i][3]-s.x)*t/s.width+s.x,this.value[i][4]=(this.value[i][4]-s.y)*e/s.height+s.y,"C"==n&&(this.value[i][5]=(this.value[i][5]-s.x)*t/s.width+s.x,this.value[i][6]=(this.value[i][6]-s.y)*e/s.height+s.y)):"A"==n&&(this.value[i][1]=this.value[i][1]*t/s.width,this.value[i][2]=this.value[i][2]*e/s.height,this.value[i][6]=(this.value[i][6]-s.x)*t/s.width+s.x,this.value[i][7]=(this.value[i][7]-s.y)*e/s.height+s.y);return this},parse:function(t){if(t instanceof y.PathArray)return t.valueOf();var e,i,n,s,r,a,o=0,h=0,u={M:2,L:2,H:1,V:1,C:6,S:4,Q:4,T:2,A:7};if("string"==typeof t){for(t=t.replace(y.regex.negExp,"X").replace(y.regex.pathLetters," $& ").replace(y.regex.hyphen," -").replace(y.regex.comma," ").replace(y.regex.X,"e-").trim().split(y.regex.whitespaces),e=t.length;--e;)if(t[e].indexOf(".")!=t[e].lastIndexOf(".")){var l=t[e].split("."),c=[l.shift(),l.shift()].join(".");t.splice.apply(t,[e,1].concat(c,l.map(function(t){return"."+t})))}}else t=t.reduce(function(t,e){return[].concat.apply(t,e)},[]);var a=[];do{for(y.regex.isPathLetter.test(t[0])?(s=t[0],t.shift()):"M"==s?s="L":"m"==s&&(s="l"),r=[s.toUpperCase()],e=0;e<u[r[0]];++e)r.push(parseFloat(t.shift()));s==r[0]?"M"==s||"L"==s||"C"==s||"Q"==s||"S"==s||"T"==s?(o=r[u[r[0]]-1],h=r[u[r[0]]]):"V"==s?h=r[1]:"H"==s?o=r[1]:"A"==s&&(o=r[6],h=r[7]):"m"==s||"l"==s||"c"==s||"s"==s||"q"==s||"t"==s?(r[1]+=o,r[2]+=h,null!=r[3]&&(r[3]+=o,r[4]+=h),null!=r[5]&&(r[5]+=o,r[6]+=h),o=r[u[r[0]]-1],h=r[u[r[0]]]):"v"==s?(r[1]+=h,h=r[1]):"h"==s?(r[1]+=o,o=r[1]):"a"==s&&(r[6]+=o,r[7]+=h,o=r[6],h=r[7]),"M"==r[0]&&(i=o,n=h),"Z"==r[0]&&(o=i,h=n),a.push(r)}while(t.length);return a},bbox:function(){return y.parser.path.setAttribute("d",this.toString()),y.parser.path.getBBox()}}),y.Number=y.invent({create:function(t,e){this.value=0,this.unit=e||"","number"==typeof t?this.value=isNaN(t)?0:isFinite(t)?t:t<0?-3.4e38:3.4e38:"string"==typeof t?(e=t.match(y.regex.numberAndUnit),e&&(this.value=parseFloat(e[1]),"%"==e[5]?this.value/=100:"s"==e[5]&&(this.value*=1e3),this.unit=e[5])):t instanceof y.Number&&(this.value=t.valueOf(),this.unit=t.unit)},extend:{toString:function(){return("%"==this.unit?~~(1e8*this.value)/1e6:"s"==this.unit?this.value/1e3:this.value)+this.unit},toJSON:function(){return this.toString()},valueOf:function(){return this.value},plus:function(t){return new y.Number(this+new y.Number(t),this.unit)},minus:function(t){return this.plus(-new y.Number(t))},times:function(t){return new y.Number(this*new y.Number(t),this.unit)},divide:function(t){return new y.Number(this/new y.Number(t),this.unit)},to:function(t){var e=new y.Number(this);return"string"==typeof t&&(e.unit=t),e},morph:function(t){return this.destination=new y.Number(t),this},at:function(t){return this.destination?new y.Number(this.destination).minus(this).times(t).plus(this):this}}}),y.Element=y.invent({create:function(t){this._stroke=y.defaults.attrs.stroke,this.dom={},(this.node=t)&&(this.type=t.nodeName,this.node.instance=this,this._stroke=t.getAttribute("stroke")||this._stroke)},extend:{x:function(t){return this.attr("x",t)},y:function(t){return this.attr("y",t)},cx:function(t){return null==t?this.x()+this.width()/2:this.x(t-this.width()/2)},cy:function(t){return null==t?this.y()+this.height()/2:this.y(t-this.height()/2)},move:function(t,e){return this.x(t).y(e)},center:function(t,e){return this.cx(t).cy(e)},width:function(t){return this.attr("width",t)},height:function(t){return this.attr("height",t)},size:function(t,e){var i=h(this,t,e);return this.width(new y.Number(i.width)).height(new y.Number(i.height))},clone:function(t){var e=m(this.node.cloneNode(!0));return t?t.add(e):this.after(e),e},remove:function(){return this.parent()&&this.parent().removeElement(this),this},replace:function(t){return this.after(t).remove(),t},addTo:function(t){return t.put(this)},putIn:function(t){return t.add(this)},id:function(t){return this.attr("id",t)},inside:function(t,e){var i=this.bbox();return t>i.x&&e>i.y&&t<i.x+i.width&&e<i.y+i.height},show:function(){return this.style("display","")},hide:function(){return this.style("display","none")},visible:function(){return"none"!=this.style("display")},toString:function(){return this.attr("id")},classes:function(){var t=this.attr("class");return null==t?[]:t.trim().split(/\s+/)},hasClass:function(t){return this.classes().indexOf(t)!=-1},addClass:function(t){if(!this.hasClass(t)){var e=this.classes();e.push(t),this.attr("class",e.join(" "))}return this},removeClass:function(t){return this.hasClass(t)&&this.attr("class",this.classes().filter(function(e){return e!=t}).join(" ")),this},toggleClass:function(t){return this.hasClass(t)?this.removeClass(t):this.addClass(t)},reference:function(t){return y.get(this.attr(t))},parent:function(t){var e=this;if(!e.node.parentNode)return null;if(e=y.adopt(e.node.parentNode),!t)return e;for(;e&&e.node instanceof SVGElement;){if("string"==typeof t?e.matches(t):e instanceof t)return e;e=y.adopt(e.node.parentNode)}},doc:function(){return this instanceof y.Doc?this:this.parent(y.Doc)},parents:function(t){var e=[],i=this;do{if(i=i.parent(t),!i||!i.node)break;e.push(i)}while(i.parent);return e},matches:function(t){return n(this.node,t)},native:function(){return this.node},svg:function(t){var i=e.createElement("svg");if(!(t&&this instanceof y.Parent))return i.appendChild(t=e.createElement("svg")),this.writeDataToDom(),t.appendChild(this.node.cloneNode(!0)),i.innerHTML.replace(/^<svg>/,"").replace(/<\/svg>$/,"");i.innerHTML="<svg>"+t.replace(/\n/,"").replace(/<(\w+)([^<]+?)\/>/g,"<$1$2></$1>")+"</svg>";for(var n=0,s=i.firstChild.childNodes.length;n<s;n++)this.node.appendChild(i.firstChild.firstChild);return this},writeDataToDom:function(){if(this.each||this.lines){var t=this.each?this:this.lines();t.each(function(){this.writeDataToDom()})}return this.node.removeAttribute("svgjs:data"),Object.keys(this.dom).length&&this.node.setAttribute("svgjs:data",JSON.stringify(this.dom)),this},setData:function(t){return this.dom=t,this},is:function(t){return i(this,t)}}}),y.easing={"-":function(t){return t},"<>":function(t){return-Math.cos(t*Math.PI)/2+.5},">":function(t){return Math.sin(t*Math.PI/2)},"<":function(t){return-Math.cos(t*Math.PI/2)+1}},y.morph=function(t){return function(e,i){return new y.MorphObj(e,i).at(t)}},y.Situation=y.invent({create:function(t){this.init=!1,this.reversed=!1,this.reversing=!1,this.duration=new y.Number(t.duration).valueOf(),this.delay=new y.Number(t.delay).valueOf(),this.start=+new Date+this.delay,this.finish=this.start+this.duration,this.ease=t.ease,this.loop=0,this.loops=!1,this.animations={},this.attrs={},this.styles={},this.transforms=[],this.once={}}}),y.Delay=function(t){this.delay=new y.Number(t).valueOf()},y.FX=y.invent({create:function(t){this._target=t,this.situations=[],this.active=!1,this.situation=null,this.paused=!1,this.lastPos=0,this.pos=0,this.absPos=0,this._speed=1},extend:{animate:function(t,e,i){"object"==typeof t&&(e=t.ease,i=t.delay,t=t.duration);var n=new y.Situation({duration:t||1e3,delay:i||0,ease:y.easing[e||"-"]||e});return this.queue(n),this},delay:function(t){var t=new y.Delay(t);return this.queue(t)},target:function(t){return t&&t instanceof y.Element?(this._target=t,this):this._target},timeToAbsPos:function(t){return(t-this.situation.start)/(this.situation.duration/this._speed)},absPosToTime:function(t){return this.situation.duration/this._speed*t+this.situation.start},startAnimFrame:function(){this.stopAnimFrame(),this.animationFrame=requestAnimationFrame(function(){this.step()}.bind(this))},stopAnimFrame:function(){cancelAnimationFrame(this.animationFrame)},start:function(){return!this.active&&this.situation&&(this.situation.start=+new Date+this.situation.delay/this._speed,this.situation.finish=this.situation.start+this.situation.duration/this._speed,this.initAnimations(),this.active=!0,this.startAnimFrame()),this},queue:function(t){return("function"==typeof t||t instanceof y.Situation||t instanceof y.Delay)&&this.situations.push(t),this.situation||(this.situation=this.situations.shift()),this},dequeue:function(){if(this.situation&&this.situation.stop&&this.situation.stop(),this.situation=this.situations.shift(),this.situation){var t=function(){this.situation instanceof y.Situation?this.initAnimations().atStart():this.situation instanceof y.Delay?this.dequeue():this.situation.call(this)}.bind(this);this.situation.delay?setTimeout(function(){t()},this.situation.delay):t()}return this},initAnimations:function(){var t,e=this.situation;if(e.init)return this;for(t in e.animations)"viewbox"==t?e.animations[t]=this.target().viewbox().morph(e.animations[t]):(e.animations[t].value="plot"==t?this.target().array().value:this.target()[t](),e.animations[t].value.value&&(e.animations[t].value=e.animations[t].value.value),e.animations[t].relative&&(e.animations[t].destination.value=e.animations[t].destination.value+e.animations[t].value));for(t in e.attrs)if(e.attrs[t]instanceof y.Color){var i=new y.Color(this.target().attr(t));e.attrs[t].r=i.r,e.attrs[t].g=i.g,e.attrs[t].b=i.b}else e.attrs[t].value=this.target().attr(t);for(t in e.styles)e.styles[t].value=this.target().style(t);return e.initialTransformation=this.target().matrixify(),e.init=!0,this},clearQueue:function(){return this.situations=[],this},clearCurrent:function(){return this.situation=null,this},stop:function(t,e){return this.active||this.start(),e&&this.clearQueue(),this.active=!1,t&&this.situation&&this.atEnd(),this.stopAnimFrame(),clearTimeout(this.timeout),this.clearCurrent()},reset:function(){if(this.situation){var t=this.situation;this.stop(),this.situation=t,this.atStart()}return this},finish:function(){for(this.stop(!0,!1);this.dequeue().situation&&this.stop(!0,!1););return this.clearQueue().clearCurrent(),this},atStart:function(){return this.at(0,!0)},atEnd:function(){return this.situation.loops===!0?this.at(this.situation.loop+1,!0):"number"==typeof this.situation.loops?this.at(this.situation.loops,!0):this.at(1,!0)},at:function(t,e){var i=this.situation.duration/this._speed;return this.absPos=t,e||(this.situation.reversed&&(this.absPos=1-this.absPos),this.absPos+=this.situation.loop),this.situation.start=+new Date-this.absPos*i,this.situation.finish=this.situation.start+i,this.step(!0)},speed:function(t){return 0===t?this.pause():t?(this._speed=t,this.at(this.absPos,!0)):this._speed},loop:function(t,e){var i=this.last();return i.loops=null==t||t,i.loop=0,e&&(i.reversing=!0),this},pause:function(){return this.paused=!0,this.stopAnimFrame(),clearTimeout(this.timeout),this},play:function(){return this.paused?(this.paused=!1,this.at(this.absPos,!0)):this},reverse:function(t){var e=this.last();return"undefined"==typeof t?e.reversed=!e.reversed:e.reversed=t,this},progress:function(t){return t?this.situation.ease(this.pos):this.pos},after:function(t){var e=this.last(),i=function i(n){n.detail.situation==e&&(t.call(this,e),this.off("finished.fx",i))};return this.target().on("finished.fx",i),this},during:function(t){var e=this.last(),i=function(i){i.detail.situation==e&&t.call(this,i.detail.pos,y.morph(i.detail.pos),i.detail.eased,e)};return this.target().off("during.fx",i).on("during.fx",i),this.after(function(){this.off("during.fx",i)})},afterAll:function(t){var e=function e(i){t.call(this),this.off("allfinished.fx",e)};return this.target().off("allfinished.fx",e).on("allfinished.fx",e),this},duringAll:function(t){var e=function(e){t.call(this,e.detail.pos,y.morph(e.detail.pos),e.detail.eased,e.detail.situation)};return this.target().off("during.fx",e).on("during.fx",e),this.afterAll(function(){this.off("during.fx",e)})},last:function(){return this.situations.length?this.situations[this.situations.length-1]:this.situation},add:function(t,e,i){return this.last()[i||"animations"][t]=e,setTimeout(function(){this.start()}.bind(this),0),this},step:function(t){if(t||(this.absPos=this.timeToAbsPos(+new Date)),this.situation.loops!==!1){var e,i,n;e=Math.max(this.absPos,0),i=Math.floor(e),this.situation.loops===!0||i<this.situation.loops?(this.pos=e-i,n=this.situation.loop,this.situation.loop=i):(this.absPos=this.situation.loops,this.pos=1,n=this.situation.loop-1,this.situation.loop=this.situation.loops),this.situation.reversing&&(this.situation.reversed=this.situation.reversed!=Boolean((this.situation.loop-n)%2))}else this.absPos=Math.min(this.absPos,1),this.pos=this.absPos;this.pos<0&&(this.pos=0),this.situation.reversed&&(this.pos=1-this.pos);var s=this.situation.ease(this.pos);for(var r in this.situation.once)r>this.lastPos&&r<=s&&(this.situation.once[r].call(this.target(),this.pos,s),delete this.situation.once[r]);return this.active&&this.target().fire("during",{pos:this.pos,eased:s,fx:this,situation:this.situation}),this.situation?(this.eachAt(),1==this.pos&&!this.situation.reversed||this.situation.reversed&&0==this.pos?(this.stopAnimFrame(),this.target().fire("finished",{fx:this,situation:this.situation}),this.situations.length||(this.target().fire("allfinished"),this.target().off(".fx"),this.active=!1),this.active?this.dequeue():this.clearCurrent()):!this.paused&&this.active&&this.startAnimFrame(),this.lastPos=s,this):this},eachAt:function(){var t,e,i=this,n=this.target(),s=this.situation;for(t in s.animations)e=[].concat(s.animations[t]).map(function(t){return t.at?t.at(s.ease(i.pos),i.pos):t}),n[t].apply(n,e);for(t in s.attrs)e=[t].concat(s.attrs[t]).map(function(t){return t.at?t.at(s.ease(i.pos),i.pos):t}),n.attr.apply(n,e);for(t in s.styles)e=[t].concat(s.styles[t]).map(function(t){return t.at?t.at(s.ease(i.pos),i.pos):t}),n.style.apply(n,e);if(s.transforms.length){e=s.initialTransformation;for(t in s.transforms){var r=s.transforms[t];r instanceof y.Matrix?e=r.relative?e.multiply(r.at(s.ease(this.pos))):e.morph(r).at(s.ease(this.pos)):(r.relative||r.undo(e.extract()),e=e.multiply(r.at(s.ease(this.pos))))}n.matrix(e)}return this},once:function(t,e,i){return i||(t=this.situation.ease(t)),this.situation.once[t]=e,this}},parent:y.Element,construct:{animate:function(t,e,i){return(this.fx||(this.fx=new y.FX(this))).animate(t,e,i)},delay:function(t){return(this.fx||(this.fx=new y.FX(this))).delay(t)},stop:function(t,e){return this.fx&&this.fx.stop(t,e),this},finish:function(){return this.fx&&this.fx.finish(),this},pause:function(){return this.fx&&this.fx.pause(),this},play:function(){return this.fx&&this.fx.play(),this},speed:function(t){if(this.fx){if(null==t)return this.fx.speed();this.fx.speed(t)}return this}}}),y.MorphObj=y.invent({create:function(t,e){return y.Color.isColor(e)?new y.Color(t).morph(e):y.regex.numberAndUnit.test(e)?new y.Number(t).morph(e):(this.value=0,void(this.destination=e))},extend:{at:function(t,e){return e<1?this.value:this.destination},valueOf:function(){return this.value}}}),y.extend(y.FX,{attr:function(t,e,i){if("object"==typeof t)for(var n in t)this.attr(n,t[n]);else this.add(t,new y.MorphObj(null,e),"attrs");return this},style:function(t,e){if("object"==typeof t)for(var i in t)this.style(i,t[i]);else this.add(t,new y.MorphObj(null,e),"styles");return this},x:function(t,e){if(this.target()instanceof y.G)return this.transform({x:t},e),this;var i=(new y.Number).morph(t);return i.relative=e,this.add("x",i)},y:function(t,e){if(this.target()instanceof y.G)return this.transform({y:t},e),this;var i=(new y.Number).morph(t);return i.relative=e,this.add("y",i)},cx:function(t){return this.add("cx",(new y.Number).morph(t))},cy:function(t){return this.add("cy",(new y.Number).morph(t))},move:function(t,e){return this.x(t).y(e)},center:function(t,e){return this.cx(t).cy(e)},size:function(t,e){if(this.target()instanceof y.Text)this.attr("font-size",t);else{var i;t&&e||(i=this.target().bbox()),t||(t=i.width/i.height*e),e||(e=i.height/i.width*t),this.add("width",(new y.Number).morph(t)).add("height",(new y.Number).morph(e))}return this},plot:function(t){return this.add("plot",this.target().array().morph(t))},leading:function(t){return this.target().leading?this.add("leading",(new y.Number).morph(t)):this},viewbox:function(t,e,i,n){return this.target()instanceof y.Container&&this.add("viewbox",new y.ViewBox(t,e,i,n)),this},update:function(t){if(this.target()instanceof y.Stop){if("number"==typeof t||t instanceof y.Number)return this.update({offset:arguments[0],color:arguments[1],opacity:arguments[2]});null!=t.opacity&&this.attr("stop-opacity",t.opacity),null!=t.color&&this.attr("stop-color",t.color),null!=t.offset&&this.attr("offset",t.offset)}return this}}),y.BBox=y.invent({create:function(t){if(t){var i;try{if(!e.documentElement.contains(t.node))throw new Exception("Element not in the dom");i=t.node.getBBox()}catch(e){if(t instanceof y.Shape){var n=t.clone(y.parser.draw).show();i=n.bbox(),n.remove()}else i={x:t.node.clientLeft,y:t.node.clientTop,width:t.node.clientWidth,height:t.node.clientHeight}}this.x=i.x,this.y=i.y,this.width=i.width,this.height=i.height}x(this)},parent:y.Element,construct:{bbox:function(){return new y.BBox(this)}}}),y.TBox=y.invent({create:function(t){if(t){var e=t.ctm().extract(),i=t.bbox();this.width=i.width*e.scaleX,this.height=i.height*e.scaleY,this.x=i.x+e.x,this.y=i.y+e.y}x(this)},parent:y.Element,construct:{tbox:function(){return new y.TBox(this)}}}),y.RBox=y.invent({create:function(e){if(e){var i=e.doc().parent(),n=e.node.getBoundingClientRect(),s=1;for(this.x=n.left,this.y=n.top,this.x-=i.offsetLeft,this.y-=i.offsetTop;i=i.offsetParent;)this.x-=i.offsetLeft,this.y-=i.offsetTop;for(i=e;i.parent&&(i=i.parent());)i.viewbox&&(s*=i.viewbox().zoom,this.x-=i.x()||0,this.y-=i.y()||0);this.width=n.width/=s,this.height=n.height/=s}x(this),this.x+=t.pageXOffset,this.y+=t.pageYOffset},parent:y.Element,construct:{rbox:function(){return new y.RBox(this)}}}),[y.BBox,y.TBox,y.RBox].forEach(function(t){y.extend(t,{merge:function(e){var i=new t;return i.x=Math.min(this.x,e.x),i.y=Math.min(this.y,e.y),i.width=Math.max(this.x+this.width,e.x+e.width)-i.x,i.height=Math.max(this.y+this.height,e.y+e.height)-i.y,x(i)}})}),y.Matrix=y.invent({create:function(t){var e,i=l([1,0,0,1,0,0]);for(t=t instanceof y.Element?t.matrixify():"string"==typeof t?d(t):6==arguments.length?l([].slice.call(arguments)):"object"==typeof t?t:i,e=w.length-1;e>=0;--e)this[w[e]]=t&&"number"==typeof t[w[e]]?t[w[e]]:i[w[e]]},extend:{extract:function(){var t=u(this,0,1),e=u(this,1,0),i=180/Math.PI*Math.atan2(t.y,t.x)-90;return{x:this.e,y:this.f,transformedX:(this.e*Math.cos(i*Math.PI/180)+this.f*Math.sin(i*Math.PI/180))/Math.sqrt(this.a*this.a+this.b*this.b),transformedY:(this.f*Math.cos(i*Math.PI/180)+this.e*Math.sin(-i*Math.PI/180))/Math.sqrt(this.c*this.c+this.d*this.d),skewX:-i,skewY:180/Math.PI*Math.atan2(e.y,e.x),scaleX:Math.sqrt(this.a*this.a+this.b*this.b),scaleY:Math.sqrt(this.c*this.c+this.d*this.d),rotation:i,a:this.a,b:this.b,c:this.c,d:this.d,e:this.e,f:this.f,matrix:new y.Matrix(this)}},clone:function(){return new y.Matrix(this)},morph:function(t){return this.destination=new y.Matrix(t),this},at:function(t){if(!this.destination)return this;var e=new y.Matrix({a:this.a+(this.destination.a-this.a)*t,b:this.b+(this.destination.b-this.b)*t,c:this.c+(this.destination.c-this.c)*t,d:this.d+(this.destination.d-this.d)*t,e:this.e+(this.destination.e-this.e)*t,f:this.f+(this.destination.f-this.f)*t});if(this.param&&this.param.to){var i={rotation:this.param.from.rotation+(this.param.to.rotation-this.param.from.rotation)*t,cx:this.param.from.cx,cy:this.param.from.cy};e=e.rotate((this.param.to.rotation-2*this.param.from.rotation)*t,i.cx,i.cy),e.param=i}return e},multiply:function(t){return new y.Matrix(this.native().multiply(c(t).native()))},inverse:function(){return new y.Matrix(this.native().inverse())},translate:function(t,e){return new y.Matrix(this.native().translate(t||0,e||0))},scale:function(t,e,i,n){return 1!=arguments.length&&3!=arguments.length||(e=t),3==arguments.length&&(n=i,i=e),this.around(i,n,new y.Matrix(t,0,0,e,0,0))},rotate:function(t,e,i){return t=y.utils.radians(t),this.around(e,i,new y.Matrix(Math.cos(t),Math.sin(t),(-Math.sin(t)),Math.cos(t),0,0))},flip:function(t,e){return"x"==t?this.scale(-1,1,e,0):this.scale(1,-1,0,e)},skew:function(t,e,i,n){return this.around(i,n,this.native().skewX(t||0).skewY(e||0))},skewX:function(t,e,i){return this.around(e,i,this.native().skewX(t||0))},skewY:function(t,e,i){return this.around(e,i,this.native().skewY(t||0))},around:function(t,e,i){return this.multiply(new y.Matrix(1,0,0,1,t||0,e||0)).multiply(i).multiply(new y.Matrix(1,0,0,1,-t||0,-e||0))},native:function(){for(var t=y.parser.native.createSVGMatrix(),e=w.length-1;e>=0;e--)t[w[e]]=this[w[e]];return t},toString:function(){return"matrix("+this.a+","+this.b+","+this.c+","+this.d+","+this.e+","+this.f+")"}},parent:y.Element,construct:{ctm:function(){return new y.Matrix(this.node.getCTM())},screenCTM:function(){return new y.Matrix(this.node.getScreenCTM())}}}),y.Point=y.invent({create:function(t,e){var i,n={x:0,y:0};i=Array.isArray(t)?{x:t[0],y:t[1]}:"object"==typeof t?{x:t.x,y:t.y}:null!=e?{x:t,y:e}:n,this.x=i.x,this.y=i.y},extend:{clone:function(){return new y.Point(this)},morph:function(t){return this.destination=new y.Point(t),this},at:function(t){if(!this.destination)return this;var e=new y.Point({x:this.x+(this.destination.x-this.x)*t,y:this.y+(this.destination.y-this.y)*t});return e},native:function(){var t=y.parser.native.createSVGPoint();return t.x=this.x,t.y=this.y,t},transform:function(t){return new y.Point(this.native().matrixTransform(t.native()))}}}),y.extend(y.Element,{point:function(t,e){return new y.Point(t,e).transform(this.screenCTM().inverse())}}),y.extend(y.Element,{attr:function(t,e,i){if(null==t){for(t={},e=this.node.attributes,i=e.length-1;i>=0;i--)t[e[i].nodeName]=y.regex.isNumber.test(e[i].nodeValue)?parseFloat(e[i].nodeValue):e[i].nodeValue;return t}if("object"==typeof t)for(e in t)this.attr(e,t[e]);else if(null===e)this.node.removeAttribute(t);else{if(null==e)return e=this.node.getAttribute(t),null==e?y.defaults.attrs[t]:y.regex.isNumber.test(e)?parseFloat(e):e;"stroke-width"==t?this.attr("stroke",parseFloat(e)>0?this._stroke:null):"stroke"==t&&(this._stroke=e),"fill"!=t&&"stroke"!=t||(y.regex.isImage.test(e)&&(e=this.doc().defs().image(e,0,0)),
+e instanceof y.Image&&(e=this.doc().defs().pattern(0,0,function(){this.add(e)}))),"number"==typeof e?e=new y.Number(e):y.Color.isColor(e)?e=new y.Color(e):Array.isArray(e)?e=new y.Array(e):e instanceof y.Matrix&&e.param&&(this.param=e.param),"leading"==t?this.leading&&this.leading(e):"string"==typeof i?this.node.setAttributeNS(i,t,e.toString()):this.node.setAttribute(t,e.toString()),!this.rebuild||"font-size"!=t&&"x"!=t||this.rebuild(t,e)}return this}}),y.extend(y.Element,{transform:function(t,e){var i,n=this;if("object"!=typeof t)return i=new y.Matrix(n).extract(),"string"==typeof t?i[t]:i;if(i=new y.Matrix(n),e=!!e||!!t.relative,null!=t.a)i=e?i.multiply(new y.Matrix(t)):new y.Matrix(t);else if(null!=t.rotation)f(t,n),i=e?i.rotate(t.rotation,t.cx,t.cy):i.rotate(t.rotation-i.extract().rotation,t.cx,t.cy);else if(null!=t.scale||null!=t.scaleX||null!=t.scaleY){if(f(t,n),t.scaleX=null!=t.scale?t.scale:null!=t.scaleX?t.scaleX:1,t.scaleY=null!=t.scale?t.scale:null!=t.scaleY?t.scaleY:1,!e){var s=i.extract();t.scaleX=1*t.scaleX/s.scaleX,t.scaleY=1*t.scaleY/s.scaleY}i=i.scale(t.scaleX,t.scaleY,t.cx,t.cy)}else if(null!=t.skewX||null!=t.skewY){if(f(t,n),t.skewX=null!=t.skewX?t.skewX:0,t.skewY=null!=t.skewY?t.skewY:0,!e){var s=i.extract();i=i.multiply((new y.Matrix).skew(s.skewX,s.skewY,t.cx,t.cy).inverse())}i=i.skew(t.skewX,t.skewY,t.cx,t.cy)}else t.flip?i=i.flip(t.flip,null==t.offset?n.bbox()["c"+t.flip]:t.offset):null==t.x&&null==t.y||(e?i=i.translate(t.x,t.y):(null!=t.x&&(i.e=t.x),null!=t.y&&(i.f=t.y)));return this.attr("transform",i)}}),y.extend(y.FX,{transform:function(t,e){var i,n=this.target();return"object"!=typeof t?(i=new y.Matrix(n).extract(),"string"==typeof t?i[t]:i):(e=!!e||!!t.relative,null!=t.a?i=new y.Matrix(t):null!=t.rotation?(f(t,n),i=new y.Rotate(t.rotation,t.cx,t.cy)):null!=t.scale||null!=t.scaleX||null!=t.scaleY?(f(t,n),t.scaleX=null!=t.scale?t.scale:null!=t.scaleX?t.scaleX:1,t.scaleY=null!=t.scale?t.scale:null!=t.scaleY?t.scaleY:1,i=new y.Scale(t.scaleX,t.scaleY,t.cx,t.cy)):null!=t.skewX||null!=t.skewY?(f(t,n),t.skewX=null!=t.skewX?t.skewX:0,t.skewY=null!=t.skewY?t.skewY:0,i=new y.Skew(t.skewX,t.skewY,t.cx,t.cy)):t.flip?i=(new y.Matrix).morph((new y.Matrix).flip(t.flip,null==t.offset?n.bbox()["c"+t.flip]:t.offset)):null==t.x&&null==t.y||(i=new y.Translate(t.x,t.y)),i?(i.relative=e,this.last().transforms.push(i),setTimeout(function(){this.start()}.bind(this),0),this):this)}}),y.extend(y.Element,{untransform:function(){return this.attr("transform",null)},matrixify:function(){var t=(this.attr("transform")||"").split(/\)\s*,?\s*/).slice(0,-1).map(function(t){var e=t.trim().split("(");return[e[0],e[1].split(y.regex.matrixElements).map(function(t){return parseFloat(t)})]}).reduce(function(t,e){return"matrix"==e[0]?t.multiply(l(e[1])):t[e[0]].apply(t,e[1])},new y.Matrix);return t},toParent:function(t){if(this==t)return this;var e=this.screenCTM(),i=t.rect(1,1),n=i.screenCTM().inverse();return i.remove(),this.addTo(t).untransform().transform(n.multiply(e)),this},toDoc:function(){return this.toParent(this.doc())}}),y.Transformation=y.invent({create:function(t,e){if(arguments.length>1&&"boolean"!=typeof e)return this.create([].slice.call(arguments));if("object"==typeof t)for(var i=0,n=this.arguments.length;i<n;++i)this[this.arguments[i]]=t[this.arguments[i]];if(Array.isArray(t))for(var i=0,n=this.arguments.length;i<n;++i)this[this.arguments[i]]=t[i];this.inversed=!1,e===!0&&(this.inversed=!0)},extend:{at:function(t){for(var e=[],i=0,n=this.arguments.length;i<n;++i)e.push(this[this.arguments[i]]);var s=this._undo||new y.Matrix;return s=(new y.Matrix).morph(y.Matrix.prototype[this.method].apply(s,e)).at(t),this.inversed?s.inverse():s},undo:function(t){for(var e=0,i=this.arguments.length;e<i;++e)t[this.arguments[e]]="undefined"==typeof this[this.arguments[e]]?0:t[this.arguments[e]];return this._undo=new(y[r(this.method)])(t,(!0)).at(1),this}}}),y.Translate=y.invent({parent:y.Matrix,inherit:y.Transformation,create:function(t,e){"object"==typeof t?this.constructor.call(this,t,e):this.constructor.call(this,[].slice.call(arguments))},extend:{arguments:["transformedX","transformedY"],method:"translate"}}),y.Rotate=y.invent({parent:y.Matrix,inherit:y.Transformation,create:function(t,e){"object"==typeof t?this.constructor.call(this,t,e):this.constructor.call(this,[].slice.call(arguments))},extend:{arguments:["rotation","cx","cy"],method:"rotate",at:function(t){var e=(new y.Matrix).rotate((new y.Number).morph(this.rotation-(this._undo?this._undo.rotation:0)).at(t),this.cx,this.cy);return this.inversed?e.inverse():e},undo:function(t){this._undo=t}}}),y.Scale=y.invent({parent:y.Matrix,inherit:y.Transformation,create:function(t,e){"object"==typeof t?this.constructor.call(this,t,e):this.constructor.call(this,[].slice.call(arguments))},extend:{arguments:["scaleX","scaleY","cx","cy"],method:"scale"}}),y.Skew=y.invent({parent:y.Matrix,inherit:y.Transformation,create:function(t,e){"object"==typeof t?this.constructor.call(this,t,e):this.constructor.call(this,[].slice.call(arguments))},extend:{arguments:["skewX","skewY","cx","cy"],method:"skew"}}),y.extend(y.Element,{style:function(t,e){if(0==arguments.length)return this.node.style.cssText||"";if(arguments.length<2)if("object"==typeof t)for(e in t)this.style(e,t[e]);else{if(!y.regex.isCss.test(t))return this.node.style[s(t)];t=t.split(";");for(var i=0;i<t.length;i++)e=t[i].split(":"),this.style(e[0].replace(/\s+/g,""),e[1])}else this.node.style[s(t)]=null===e||y.regex.isBlank.test(e)?"":e;return this}}),y.Parent=y.invent({create:function(t){this.constructor.call(this,t)},inherit:y.Element,extend:{children:function(){return y.utils.map(y.utils.filterSVGElements(this.node.childNodes),function(t){return y.adopt(t)})},add:function(t,e){return null==e?this.node.appendChild(t.node):t.node!=this.node.childNodes[e]&&this.node.insertBefore(t.node,this.node.childNodes[e]),this},put:function(t,e){return this.add(t,e),t},has:function(t){return this.index(t)>=0},index:function(t){return[].slice.call(this.node.childNodes).indexOf(t.node)},get:function(t){return y.adopt(this.node.childNodes[t])},first:function(){return this.get(0)},last:function(){return this.get(this.node.childNodes.length-1)},each:function(t,e){var i,n,s=this.children();for(i=0,n=s.length;i<n;i++)s[i]instanceof y.Element&&t.apply(s[i],[i,s]),e&&s[i]instanceof y.Container&&s[i].each(t,e);return this},removeElement:function(t){return this.node.removeChild(t.node),this},clear:function(){for(;this.node.hasChildNodes();)this.node.removeChild(this.node.lastChild);return delete this._defs,this},defs:function(){return this.doc().defs()}}}),y.extend(y.Parent,{ungroup:function(t,e){return 0===e||this instanceof y.Defs?this:(t=t||(this instanceof y.Doc?this:this.parent(y.Parent)),e=e||1/0,this.each(function(){return this instanceof y.Defs?this:this instanceof y.Parent?this.ungroup(t,e-1):this.toParent(t)}),this.node.firstChild||this.remove(),this)},flatten:function(t,e){return this.ungroup(t,e)}}),y.Container=y.invent({create:function(t){this.constructor.call(this,t)},inherit:y.Parent}),y.ViewBox=y.invent({create:function(t){var e,i,n,s,r,a,o,h,u=[0,0,0,0],l=1,c=1,f=/[+-]?(?:\d+(?:\.\d*)?|\.\d+)(?:e[+-]?\d+)?/gi;if(t instanceof y.Element){for(o=t,h=t,a=(t.attr("viewBox")||"").match(f),r=t.bbox,n=new y.Number(t.width()),s=new y.Number(t.height());"%"==n.unit;)l*=n.value,n=new y.Number(o instanceof y.Doc?o.parent().offsetWidth:o.parent().width()),o=o.parent();for(;"%"==s.unit;)c*=s.value,s=new y.Number(h instanceof y.Doc?h.parent().offsetHeight:h.parent().height()),h=h.parent();this.x=0,this.y=0,this.width=n*l,this.height=s*c,this.zoom=1,a&&(e=parseFloat(a[0]),i=parseFloat(a[1]),n=parseFloat(a[2]),s=parseFloat(a[3]),this.zoom=this.width/this.height>n/s?this.height/s:this.width/n,this.x=e,this.y=i,this.width=n,this.height=s)}else t="string"==typeof t?t.match(f).map(function(t){return parseFloat(t)}):Array.isArray(t)?t:"object"==typeof t?[t.x,t.y,t.width,t.height]:4==arguments.length?[].slice.call(arguments):u,this.x=t[0],this.y=t[1],this.width=t[2],this.height=t[3]},extend:{toString:function(){return this.x+" "+this.y+" "+this.width+" "+this.height},morph:function(t){var t=1==arguments.length?[t.x,t.y,t.width,t.height]:[].slice.call(arguments);return this.destination=new y.ViewBox(t),this},at:function(t){return this.destination?new y.ViewBox([this.x+(this.destination.x-this.x)*t,this.y+(this.destination.y-this.y)*t,this.width+(this.destination.width-this.width)*t,this.height+(this.destination.height-this.height)*t]):this}},parent:y.Container,construct:{viewbox:function(t){return 0==arguments.length?new y.ViewBox(this):(t=1==arguments.length?[t.x,t.y,t.width,t.height]:[].slice.call(arguments),this.attr("viewBox",t))}}}),["click","dblclick","mousedown","mouseup","mouseover","mouseout","mousemove","touchstart","touchmove","touchleave","touchend","touchcancel"].forEach(function(t){y.Element.prototype[t]=function(e){var i=this;return this.node["on"+t]="function"==typeof e?function(){return e.apply(i,arguments)}:null,this}}),y.listeners=[],y.handlerMap=[],y.listenerId=0,y.on=function(t,e,i,n){var s=i.bind(n||t.instance||t),r=(y.handlerMap.indexOf(t)+1||y.handlerMap.push(t))-1,a=e.split(".")[0],o=e.split(".")[1]||"*";y.listeners[r]=y.listeners[r]||{},y.listeners[r][a]=y.listeners[r][a]||{},y.listeners[r][a][o]=y.listeners[r][a][o]||{},i._svgjsListenerId||(i._svgjsListenerId=++y.listenerId),y.listeners[r][a][o][i._svgjsListenerId]=s,t.addEventListener(a,s,!1)},y.off=function(t,e,i){var n=y.handlerMap.indexOf(t),s=e&&e.split(".")[0],r=e&&e.split(".")[1];if(n!=-1)if(i){if("function"==typeof i&&(i=i._svgjsListenerId),!i)return;y.listeners[n][s]&&y.listeners[n][s][r||"*"]&&(t.removeEventListener(s,y.listeners[n][s][r||"*"][i],!1),delete y.listeners[n][s][r||"*"][i])}else if(r&&s){if(y.listeners[n][s]&&y.listeners[n][s][r]){for(i in y.listeners[n][s][r])y.off(t,[s,r].join("."),i);delete y.listeners[n][s][r]}}else if(r)for(e in y.listeners[n])for(namespace in y.listeners[n][e])r===namespace&&y.off(t,[e,r].join("."));else if(s){if(y.listeners[n][s]){for(namespace in y.listeners[n][s])y.off(t,[s,namespace].join("."));delete y.listeners[n][s]}}else{for(e in y.listeners[n])y.off(t,e);delete y.listeners[n]}},y.extend(y.Element,{on:function(t,e,i){return y.on(this.node,t,e,i),this},off:function(t,e){return y.off(this.node,t,e),this},fire:function(t,e){return t instanceof Event?this.node.dispatchEvent(t):this.node.dispatchEvent(new b(t,{detail:e})),this}}),y.Defs=y.invent({create:"defs",inherit:y.Container}),y.G=y.invent({create:"g",inherit:y.Container,extend:{x:function(t){return null==t?this.transform("x"):this.transform({x:t-this.x()},!0)},y:function(t){return null==t?this.transform("y"):this.transform({y:t-this.y()},!0)},cx:function(t){return null==t?this.gbox().cx:this.x(t-this.gbox().width/2)},cy:function(t){return null==t?this.gbox().cy:this.y(t-this.gbox().height/2)},gbox:function(){var t=this.bbox(),e=this.transform();return t.x+=e.x,t.x2+=e.x,t.cx+=e.x,t.y+=e.y,t.y2+=e.y,t.cy+=e.y,t}},construct:{group:function(){return this.put(new y.G)}}}),y.extend(y.Element,{siblings:function(){return this.parent().children()},position:function(){return this.parent().index(this)},next:function(){return this.siblings()[this.position()+1]},previous:function(){return this.siblings()[this.position()-1]},forward:function(){var t=this.position()+1,e=this.parent();return e.removeElement(this).add(this,t),e instanceof y.Doc&&e.node.appendChild(e.defs().node),this},backward:function(){var t=this.position();return t>0&&this.parent().removeElement(this).add(this,t-1),this},front:function(){var t=this.parent();return t.node.appendChild(this.node),t instanceof y.Doc&&t.node.appendChild(t.defs().node),this},back:function(){return this.position()>0&&this.parent().removeElement(this).add(this,0),this},before:function(t){t.remove();var e=this.position();return this.parent().add(t,e),this},after:function(t){t.remove();var e=this.position();return this.parent().add(t,e+1),this}}),y.Mask=y.invent({create:function(){this.constructor.call(this,y.create("mask")),this.targets=[]},inherit:y.Container,extend:{remove:function(){for(var t=this.targets.length-1;t>=0;t--)this.targets[t]&&this.targets[t].unmask();return this.targets=[],this.parent().removeElement(this),this}},construct:{mask:function(){return this.defs().put(new y.Mask)}}}),y.extend(y.Element,{maskWith:function(t){return this.masker=t instanceof y.Mask?t:this.parent().mask().add(t),this.masker.targets.push(this),this.attr("mask",'url("#'+this.masker.attr("id")+'")')},unmask:function(){return delete this.masker,this.attr("mask",null)}}),y.ClipPath=y.invent({create:function(){this.constructor.call(this,y.create("clipPath")),this.targets=[]},inherit:y.Container,extend:{remove:function(){for(var t=this.targets.length-1;t>=0;t--)this.targets[t]&&this.targets[t].unclip();return this.targets=[],this.parent().removeElement(this),this}},construct:{clip:function(){return this.defs().put(new y.ClipPath)}}}),y.extend(y.Element,{clipWith:function(t){return this.clipper=t instanceof y.ClipPath?t:this.parent().clip().add(t),this.clipper.targets.push(this),this.attr("clip-path",'url("#'+this.clipper.attr("id")+'")')},unclip:function(){return delete this.clipper,this.attr("clip-path",null)}}),y.Gradient=y.invent({create:function(t){this.constructor.call(this,y.create(t+"Gradient")),this.type=t},inherit:y.Container,extend:{at:function(t,e,i){return this.put(new y.Stop).update(t,e,i)},update:function(t){return this.clear(),"function"==typeof t&&t.call(this,this),this},fill:function(){return"url(#"+this.id()+")"},toString:function(){return this.fill()},attr:function(t,e,i){return"transform"==t&&(t="gradientTransform"),y.Container.prototype.attr.call(this,t,e,i)}},construct:{gradient:function(t,e){return this.defs().gradient(t,e)}}}),y.extend(y.Gradient,y.FX,{from:function(t,e){return"radial"==(this._target||this).type?this.attr({fx:new y.Number(t),fy:new y.Number(e)}):this.attr({x1:new y.Number(t),y1:new y.Number(e)})},to:function(t,e){return"radial"==(this._target||this).type?this.attr({cx:new y.Number(t),cy:new y.Number(e)}):this.attr({x2:new y.Number(t),y2:new y.Number(e)})}}),y.extend(y.Defs,{gradient:function(t,e){return this.put(new y.Gradient(t)).update(e)}}),y.Stop=y.invent({create:"stop",inherit:y.Element,extend:{update:function(t){return("number"==typeof t||t instanceof y.Number)&&(t={offset:arguments[0],color:arguments[1],opacity:arguments[2]}),null!=t.opacity&&this.attr("stop-opacity",t.opacity),null!=t.color&&this.attr("stop-color",t.color),null!=t.offset&&this.attr("offset",new y.Number(t.offset)),this}}}),y.Pattern=y.invent({create:"pattern",inherit:y.Container,extend:{fill:function(){return"url(#"+this.id()+")"},update:function(t){return this.clear(),"function"==typeof t&&t.call(this,this),this},toString:function(){return this.fill()},attr:function(t,e,i){return"transform"==t&&(t="patternTransform"),y.Container.prototype.attr.call(this,t,e,i)}},construct:{pattern:function(t,e,i){return this.defs().pattern(t,e,i)}}}),y.extend(y.Defs,{pattern:function(t,e,i){return this.put(new y.Pattern).update(i).attr({x:0,y:0,width:t,height:e,patternUnits:"userSpaceOnUse"})}}),y.Doc=y.invent({create:function(t){t&&(t="string"==typeof t?e.getElementById(t):t,"svg"==t.nodeName?this.constructor.call(this,t):(this.constructor.call(this,y.create("svg")),t.appendChild(this.node),this.size("100%","100%")),this.namespace().defs())},inherit:y.Container,extend:{namespace:function(){return this.attr({xmlns:y.ns,version:"1.1"}).attr("xmlns:xlink",y.xlink,y.xmlns).attr("xmlns:svgjs",y.svgjs,y.xmlns)},defs:function(){if(!this._defs){var t;(t=this.node.getElementsByTagName("defs")[0])?this._defs=y.adopt(t):this._defs=new y.Defs,this.node.appendChild(this._defs.node)}return this._defs},parent:function(){return"#document"==this.node.parentNode.nodeName?null:this.node.parentNode},spof:function(t){var e=this.node.getScreenCTM();return e&&this.style("left",-e.e%1+"px").style("top",-e.f%1+"px"),this},remove:function(){return this.parent()&&this.parent().removeChild(this.node),this}}}),y.Shape=y.invent({create:function(t){this.constructor.call(this,t)},inherit:y.Element}),y.Bare=y.invent({create:function(t,e){if(this.constructor.call(this,y.create(t)),e)for(var i in e.prototype)"function"==typeof e.prototype[i]&&(this[i]=e.prototype[i])},inherit:y.Element,extend:{words:function(t){for(;this.node.hasChildNodes();)this.node.removeChild(this.node.lastChild);return this.node.appendChild(e.createTextNode(t)),this}}}),y.extend(y.Parent,{element:function(t,e){return this.put(new y.Bare(t,e))},symbol:function(){return this.defs().element("symbol",y.Container)}}),y.Use=y.invent({create:"use",inherit:y.Shape,extend:{element:function(t,e){return this.attr("href",(e||"")+"#"+t,y.xlink)}},construct:{use:function(t,e){return this.put(new y.Use).element(t,e)}}}),y.Rect=y.invent({create:"rect",inherit:y.Shape,construct:{rect:function(t,e){return this.put(new y.Rect).size(t,e)}}}),y.Circle=y.invent({create:"circle",inherit:y.Shape,construct:{circle:function(t){return this.put(new y.Circle).rx(new y.Number(t).divide(2)).move(0,0)}}}),y.extend(y.Circle,y.FX,{rx:function(t){return this.attr("r",t)},ry:function(t){return this.rx(t)}}),y.Ellipse=y.invent({create:"ellipse",inherit:y.Shape,construct:{ellipse:function(t,e){return this.put(new y.Ellipse).size(t,e).move(0,0)}}}),y.extend(y.Ellipse,y.Rect,y.FX,{rx:function(t){return this.attr("rx",t)},ry:function(t){return this.attr("ry",t)}}),y.extend(y.Circle,y.Ellipse,{x:function(t){return null==t?this.cx()-this.rx():this.cx(t+this.rx())},y:function(t){return null==t?this.cy()-this.ry():this.cy(t+this.ry())},cx:function(t){return null==t?this.attr("cx"):this.attr("cx",t)},cy:function(t){return null==t?this.attr("cy"):this.attr("cy",t)},width:function(t){return null==t?2*this.rx():this.rx(new y.Number(t).divide(2))},height:function(t){return null==t?2*this.ry():this.ry(new y.Number(t).divide(2))},size:function(t,e){var i=h(this,t,e);return this.rx(new y.Number(i.width).divide(2)).ry(new y.Number(i.height).divide(2))}}),y.Line=y.invent({create:"line",inherit:y.Shape,extend:{array:function(){return new y.PointArray([[this.attr("x1"),this.attr("y1")],[this.attr("x2"),this.attr("y2")]])},plot:function(t,e,i,n){return t="undefined"!=typeof e?{x1:t,y1:e,x2:i,y2:n}:new y.PointArray(t).toLine(),this.attr(t)},move:function(t,e){return this.attr(this.array().move(t,e).toLine())},size:function(t,e){var i=h(this,t,e);return this.attr(this.array().size(i.width,i.height).toLine())}},construct:{line:function(t,e,i,n){return this.put(new y.Line).plot(t,e,i,n)}}}),y.Polyline=y.invent({create:"polyline",inherit:y.Shape,construct:{polyline:function(t){return this.put(new y.Polyline).plot(t)}}}),y.Polygon=y.invent({create:"polygon",inherit:y.Shape,construct:{polygon:function(t){return this.put(new y.Polygon).plot(t)}}}),y.extend(y.Polyline,y.Polygon,{array:function(){return this._array||(this._array=new y.PointArray(this.attr("points")))},plot:function(t){return this.attr("points",this._array=new y.PointArray(t))},move:function(t,e){return this.attr("points",this.array().move(t,e))},size:function(t,e){var i=h(this,t,e);return this.attr("points",this.array().size(i.width,i.height))}}),y.extend(y.Line,y.Polyline,y.Polygon,{morphArray:y.PointArray,x:function(t){return null==t?this.bbox().x:this.move(t,this.bbox().y)},y:function(t){return null==t?this.bbox().y:this.move(this.bbox().x,t)},width:function(t){var e=this.bbox();return null==t?e.width:this.size(t,e.height)},height:function(t){var e=this.bbox();return null==t?e.height:this.size(e.width,t)}}),y.Path=y.invent({create:"path",inherit:y.Shape,extend:{morphArray:y.PathArray,array:function(){return this._array||(this._array=new y.PathArray(this.attr("d")))},plot:function(t){return this.attr("d",this._array=new y.PathArray(t))},move:function(t,e){return this.attr("d",this.array().move(t,e))},x:function(t){return null==t?this.bbox().x:this.move(t,this.bbox().y)},y:function(t){return null==t?this.bbox().y:this.move(this.bbox().x,t)},size:function(t,e){var i=h(this,t,e);return this.attr("d",this.array().size(i.width,i.height))},width:function(t){return null==t?this.bbox().width:this.size(t,this.bbox().height)},height:function(t){return null==t?this.bbox().height:this.size(this.bbox().width,t)}},construct:{path:function(t){return this.put(new y.Path).plot(t)}}}),y.Image=y.invent({create:"image",inherit:y.Shape,extend:{load:function(t){if(!t)return this;var i=this,n=e.createElement("img");return n.onload=function(){var e=i.parent(y.Pattern);null!==e&&(0==i.width()&&0==i.height()&&i.size(n.width,n.height),e&&0==e.width()&&0==e.height()&&e.size(i.width(),i.height()),"function"==typeof i._loaded&&i._loaded.call(i,{width:n.width,height:n.height,ratio:n.width/n.height,url:t}))},n.onerror=function(t){"function"==typeof i._error&&i._error.call(i,t)},this.attr("href",n.src=this.src=t,y.xlink)},loaded:function(t){return this._loaded=t,this},error:function(t){return this._error=t,this}},construct:{image:function(t,e,i){return this.put(new y.Image).load(t).size(e||0,i||e||0)}}}),y.Text=y.invent({create:function(){this.constructor.call(this,y.create("text")),this.dom.leading=new y.Number(1.3),this._rebuild=!0,this._build=!1,this.attr("font-family",y.defaults.attrs["font-family"])},inherit:y.Shape,extend:{x:function(t){return null==t?this.attr("x"):(this.textPath||this.lines().each(function(){this.dom.newLined&&this.x(t)}),this.attr("x",t))},y:function(t){var e=this.attr("y"),i="number"==typeof e?e-this.bbox().y:0;return null==t?"number"==typeof e?e-i:e:this.attr("y","number"==typeof t?t+i:t)},cx:function(t){return null==t?this.bbox().cx:this.x(t-this.bbox().width/2)},cy:function(t){return null==t?this.bbox().cy:this.y(t-this.bbox().height/2)},text:function(t){if("undefined"==typeof t){for(var t="",e=this.node.childNodes,i=0,n=e.length;i<n;++i)0!=i&&3!=e[i].nodeType&&1==y.adopt(e[i]).dom.newLined&&(t+="\n"),t+=e[i].textContent;return t}if(this.clear().build(!0),"function"==typeof t)t.call(this,this);else{t=t.split("\n");for(var i=0,s=t.length;i<s;i++)this.tspan(t[i]).newLine()}return this.build(!1).rebuild()},size:function(t){return this.attr("font-size",t).rebuild()},leading:function(t){return null==t?this.dom.leading:(this.dom.leading=new y.Number(t),this.rebuild())},lines:function(){var t=(this.textPath&&this.textPath()||this).node,e=y.utils.map(y.utils.filterSVGElements(t.childNodes),function(t){return y.adopt(t)});return new y.Set(e)},rebuild:function(t){if("boolean"==typeof t&&(this._rebuild=t),this._rebuild){var e=this,i=0,n=this.dom.leading*new y.Number(this.attr("font-size"));this.lines().each(function(){this.dom.newLined&&(this.textPath||this.attr("x",e.attr("x")),"\n"==this.text()?i+=n:(this.attr("dy",n+i),i=0))}),this.fire("rebuild")}return this},build:function(t){return this._build=!!t,this},setData:function(t){return this.dom=t,this.dom.leading=new y.Number(t.leading||1.3),this}},construct:{text:function(t){return this.put(new y.Text).text(t)},plain:function(t){return this.put(new y.Text).plain(t)}}}),y.Tspan=y.invent({create:"tspan",inherit:y.Shape,extend:{text:function(t){return null==t?this.node.textContent+(this.dom.newLined?"\n":""):("function"==typeof t?t.call(this,this):this.plain(t),this)},dx:function(t){return this.attr("dx",t)},dy:function(t){return this.attr("dy",t)},newLine:function(){var t=this.parent(y.Text);return this.dom.newLined=!0,this.dy(t.dom.leading*t.attr("font-size")).attr("x",t.x())}}}),y.extend(y.Text,y.Tspan,{plain:function(t){return this._build===!1&&this.clear(),this.node.appendChild(e.createTextNode(t)),this},tspan:function(t){var e=(this.textPath&&this.textPath()||this).node,i=new y.Tspan;return this._build===!1&&this.clear(),e.appendChild(i.node),i.text(t)},clear:function(){for(var t=(this.textPath&&this.textPath()||this).node;t.hasChildNodes();)t.removeChild(t.lastChild);return this},length:function(){return this.node.getComputedTextLength()}}),y.TextPath=y.invent({create:"textPath",inherit:y.Parent,parent:y.Text,construct:{path:function(t){for(var e=new y.TextPath,i=this.doc().defs().path(t);this.node.hasChildNodes();)e.node.appendChild(this.node.firstChild);return this.node.appendChild(e.node),e.attr("href","#"+i,y.xlink),this},plot:function(t){var e=this.track();return e&&e.plot(t),this},track:function(){var t=this.textPath();if(t)return t.reference("href")},textPath:function(){if(this.node.firstChild&&"textPath"==this.node.firstChild.nodeName)return y.adopt(this.node.firstChild)}}}),y.Nested=y.invent({create:function(){this.constructor.call(this,y.create("svg")),this.style("overflow","visible")},inherit:y.Container,construct:{nested:function(){return this.put(new y.Nested)}}}),y.A=y.invent({create:"a",inherit:y.Container,extend:{to:function(t){return this.attr("href",t,y.xlink)},show:function(t){return this.attr("show",t,y.xlink)},target:function(t){return this.attr("target",t)}},construct:{link:function(t){return this.put(new y.A).to(t)}}}),y.extend(y.Element,{linkTo:function(t){var e=new y.A;return"function"==typeof t?t.call(e,e):e.to(t),this.parent().put(e).put(this)}}),y.Marker=y.invent({create:"marker",inherit:y.Container,extend:{width:function(t){return this.attr("markerWidth",t)},height:function(t){return this.attr("markerHeight",t)},ref:function(t,e){return this.attr("refX",t).attr("refY",e)},update:function(t){return this.clear(),"function"==typeof t&&t.call(this,this),this},toString:function(){return"url(#"+this.id()+")"}},construct:{marker:function(t,e,i){return this.defs().marker(t,e,i)}}}),y.extend(y.Defs,{marker:function(t,e,i){return this.put(new y.Marker).size(t,e).ref(t/2,e/2).viewbox(0,0,t,e).attr("orient","auto").update(i)}}),y.extend(y.Line,y.Polyline,y.Polygon,y.Path,{marker:function(t,e,i,n){var s=["marker"];return"all"!=t&&s.push(t),s=s.join("-"),t=arguments[1]instanceof y.Marker?arguments[1]:this.doc().marker(e,i,n),this.attr(s,t)}});var g={stroke:["color","width","opacity","linecap","linejoin","miterlimit","dasharray","dashoffset"],fill:["color","opacity","rule"],prefix:function(t,e){return"color"==e?t:t+"-"+e}};["fill","stroke"].forEach(function(t){var e,i={};i[t]=function(i){if("undefined"==typeof i)return this;if("string"==typeof i||y.Color.isRgb(i)||i&&"function"==typeof i.fill)this.attr(t,i);else for(e=g[t].length-1;e>=0;e--)null!=i[g[t][e]]&&this.attr(g.prefix(t,g[t][e]),i[g[t][e]]);return this},y.extend(y.Element,y.FX,i)}),y.extend(y.Element,y.FX,{rotate:function(t,e,i){return this.transform({rotation:t,cx:e,cy:i})},skew:function(t,e,i,n){return this.transform({skewX:t,skewY:e,cx:i,cy:n})},scale:function(t,e,i,n){return 1==arguments.length||3==arguments.length?this.transform({scale:t,cx:e,cy:i}):this.transform({scaleX:t,scaleY:e,cx:i,cy:n})},translate:function(t,e){return this.transform({x:t,y:e})},flip:function(t,e){return this.transform({flip:t,offset:e})},matrix:function(t){return this.attr("transform",new y.Matrix(t))},opacity:function(t){return this.attr("opacity",t)},dx:function(t){return this.x((this instanceof y.FX?0:this.x())+t,!0)},dy:function(t){return this.y((this instanceof y.FX?0:this.y())+t,!0)},dmove:function(t,e){return this.dx(t).dy(e)}}),y.extend(y.Rect,y.Ellipse,y.Circle,y.Gradient,y.FX,{radius:function(t,e){var i=(this._target||this).type;return"radial"==i||"circle"==i?this.attr("r",new y.Number(t)):this.rx(t).ry(null==e?t:e)}}),y.extend(y.Path,{length:function(){return this.node.getTotalLength()},pointAt:function(t){return this.node.getPointAtLength(t)}}),y.extend(y.Parent,y.Text,y.FX,{font:function(t){for(var e in t)"leading"==e?this.leading(t[e]):"anchor"==e?this.attr("text-anchor",t[e]):"size"==e||"family"==e||"weight"==e||"stretch"==e||"variant"==e||"style"==e?this.attr("font-"+e,t[e]):this.attr(e,t[e]);return this}}),y.Set=y.invent({create:function(t){Array.isArray(t)?this.members=t:this.clear()},extend:{add:function(){var t,e,i=[].slice.call(arguments);for(t=0,e=i.length;t<e;t++)this.members.push(i[t]);return this},remove:function(t){var e=this.index(t);return e>-1&&this.members.splice(e,1),this},each:function(t){for(var e=0,i=this.members.length;e<i;e++)t.apply(this.members[e],[e,this.members]);return this},clear:function(){return this.members=[],this},length:function(){return this.members.length},has:function(t){return this.index(t)>=0},index:function(t){return this.members.indexOf(t)},get:function(t){return this.members[t]},first:function(){return this.get(0)},last:function(){return this.get(this.members.length-1)},valueOf:function(){return this.members},bbox:function(){var t=new y.BBox;if(0==this.members.length)return t;var e=this.members[0].rbox();return t.x=e.x,t.y=e.y,t.width=e.width,t.height=e.height,this.each(function(){t=t.merge(this.rbox())}),t}},construct:{set:function(t){return new y.Set(t)}}}),y.FX.Set=y.invent({create:function(t){this.set=t}}),y.Set.inherit=function(){var t,e=[];for(var t in y.Shape.prototype)"function"==typeof y.Shape.prototype[t]&&"function"!=typeof y.Set.prototype[t]&&e.push(t);e.forEach(function(t){y.Set.prototype[t]=function(){for(var e=0,i=this.members.length;e<i;e++)this.members[e]&&"function"==typeof this.members[e][t]&&this.members[e][t].apply(this.members[e],arguments);return"animate"==t?this.fx||(this.fx=new y.FX.Set(this)):this}}),e=[];for(var t in y.FX.prototype)"function"==typeof y.FX.prototype[t]&&"function"!=typeof y.FX.Set.prototype[t]&&e.push(t);e.forEach(function(t){y.FX.Set.prototype[t]=function(){for(var e=0,i=this.set.members.length;e<i;e++)this.set.members[e].fx[t].apply(this.set.members[e].fx,arguments);return this}})},y.extend(y.Element,{data:function(t,e,i){if("object"==typeof t)for(e in t)this.data(e,t[e]);else if(arguments.length<2)try{return JSON.parse(this.attr("data-"+t))}catch(e){return this.attr("data-"+t)}else this.attr("data-"+t,null===e?null:i===!0||"string"==typeof e||"number"==typeof e?e:JSON.stringify(e));return this}}),y.extend(y.Element,{remember:function(t,e){if("object"==typeof arguments[0])for(var e in t)this.remember(e,t[e]);else{if(1==arguments.length)return this.memory()[t];this.memory()[t]=e}return this},forget:function(){if(0==arguments.length)this._memory={};else for(var t=arguments.length-1;t>=0;t--)delete this.memory()[arguments[t]];return this},memory:function(){return this._memory||(this._memory={})}}),y.get=function(t){var i=e.getElementById(v(t)||t);return y.adopt(i)},y.select=function(t,i){return new y.Set(y.utils.map((i||e).querySelectorAll(t),function(t){return y.adopt(t)}))},y.extend(y.Parent,{select:function(t){return y.select(t,this.node)}});var w="abcdef".split("");if("function"!=typeof b){var b=function(t,i){i=i||{bubbles:!1,cancelable:!1,detail:void 0};var n=e.createEvent("CustomEvent");return n.initCustomEvent(t,i.bubbles,i.cancelable,i.detail),n};b.prototype=t.Event.prototype,t.CustomEvent=b}return function(e){for(var i=0,n=["moz","webkit"],s=0;s<n.length&&!t.requestAnimationFrame;++s)e.requestAnimationFrame=e[n[s]+"RequestAnimationFrame"],e.cancelAnimationFrame=e[n[s]+"CancelAnimationFrame"]||e[n[s]+"CancelRequestAnimationFrame"];e.requestAnimationFrame=e.requestAnimationFrame||function(t){var n=(new Date).getTime(),s=Math.max(0,16-(n-i)),r=e.setTimeout(function(){t(n+s)},s);return i=n+s,r},e.cancelAnimationFrame=e.cancelAnimationFrame||e.clearTimeout}(t),y});
\ No newline at end of file
index a0c7fd7a4833e02777b9f990ae99465bf0c80f11..0bd014e5c2e85b4bf79b4ac26fd696f3f57a693f 100644 (file)
@@ -4,21 +4,32 @@ describe('FX', function() {
   beforeEach(function() {
     rect = draw.rect(100,100).move(100,100)
     fx = rect.animate(500)
+
+    jasmine.clock().install()
+    jasmine.clock().mockDate() // This freeze the Date
+  })
+
+  afterEach(function() {
+    jasmine.clock().uninstall()
   })
 
+
   it('creates an instance of SVG.FX and sets parameter', function() {
     expect(fx instanceof SVG.FX).toBe(true)
     expect(fx._target).toBe(rect)
+    expect(fx.absPos).toBe(0)
     expect(fx.pos).toBe(0)
     expect(fx.lastPos).toBe(0)
-    expect(fx.paused).toBeFalsy()
-    expect(fx.active).toBeFalsy()
+    expect(fx.paused).toBe(false)
+    expect(fx.active).toBe(false)
     expect(fx._speed).toBe(1)
     expect(fx.situations).toEqual([])
-    expect(fx.situation.init).toBeFalsy()
-    expect(fx.situation.reversed).toBeFalsy()
+    expect(fx.situation.init).toBe(false)
+    expect(fx.situation.reversed).toBe(false)
     expect(fx.situation.duration).toBe(500)
     expect(fx.situation.delay).toBe(0)
+    expect(fx.situation.loops).toBe(false)
+    expect(fx.situation.loop).toBe(0)
     expect(fx.situation.animations).toEqual({})
     expect(fx.situation.attrs).toEqual({})
     expect(fx.situation.styles).toEqual({})
@@ -37,9 +48,10 @@ describe('FX', function() {
     })
   })
 
-  describe('timeToPos()', function() {
-    it('converts a timestamp to a progress', function() {
-      expect(fx.timeToPos( fx.situation.start + fx.situation.duration*0.5 )).toBe(0.5)
+
+  describe('timeToAbsPos()', function() {
+    it('converts a timestamp to an absolute progress', function() {
+      expect(fx.timeToAbsPos( fx.situation.start + fx.situation.duration*0.5 )).toBe(0.5)
     })
 
     it('should take speed into consideration', function() {
@@ -47,17 +59,18 @@ describe('FX', function() {
 
       spd = 4
       fx.speed(spd)
-      expect(fx.timeToPos( fx.situation.start + (fx.situation.duration/spd)*0.5 )).toBe(0.5)
+      expect(fx.timeToAbsPos( fx.situation.start + (fx.situation.duration/spd)*0.5 )).toBe(0.5)
 
       spd = 0.5
       fx.speed(spd)
-      expect(fx.timeToPos( fx.situation.start + (fx.situation.duration/spd)*0.25 )).toBe(0.25)
+      expect(fx.timeToAbsPos( fx.situation.start + (fx.situation.duration/spd)*0.25 )).toBe(0.25)
     })
   })
 
-  describe('posToTime()', function() {
-    it('converts a progress to a timestamp', function() {
-      expect(fx.posToTime(0.5)).toBe( fx.situation.start + fx.situation.duration*0.5 )
+
+  describe('absPosToTime()', function() {
+    it('converts an absolute progress to a timestamp', function() {
+      expect(fx.absPosToTime(0.5)).toBe( fx.situation.start + fx.situation.duration*0.5 )
     })
 
     it('should take speed into consideration', function() {
@@ -65,21 +78,367 @@ describe('FX', function() {
 
       spd = 4
       fx.speed(spd)
-      expect(fx.posToTime(0.5)).toBe( fx.situation.start + (fx.situation.duration/spd)*0.5 )
+      expect(fx.absPosToTime(0.5)).toBe( fx.situation.start + (fx.situation.duration/spd)*0.5 )
 
       spd = 0.5
       fx.speed(spd)
-      expect(fx.posToTime(0.25)).toBe( fx.situation.start + (fx.situation.duration/spd)*0.25 )
+      expect(fx.absPosToTime(0.25)).toBe( fx.situation.start + (fx.situation.duration/spd)*0.25 )
+    })
+  })
+
+
+  describe('atStart()', function () {
+    it('sets the animation at the start', function() {
+      // When the animation is running forward, the start position is 0
+      this.pos = 0.5
+      expect(fx.atStart().pos).toBe(0)
+
+      // When the animation is running backward, the start position is 1
+      this.pos = 0.5
+      expect(fx.reverse(true).atStart().pos).toBe(1)
+    })
+
+    it('sets the animation at the start, before any loops', function() {
+      fx.loop(true)
+
+      // When the animation is running forward, the start position is 0
+      fx.at(3.7, true)
+      expect(fx.absPos).toBe(3.7)
+      expect(fx.pos).toBeCloseTo(0.7)
+      expect(fx.situation.loop).toBe(3)
+
+      fx.atStart()
+      expect(fx.absPos).toBe(0)
+      expect(fx.pos).toBe(0)
+      expect(fx.situation.loop).toBe(0)
+
+      // When the animation is running backward, the start position is 1
+      fx.reverse(true).at(2.14, true)
+      expect(fx.absPos).toBe(2.14)
+      expect(fx.pos).toBeCloseTo(1 - 0.14)
+      expect(fx.situation.loop).toBe(2)
+      expect(fx.situation.reversed).toBe(true)
+
+      fx.atStart()
+      expect(fx.absPos).toBe(0)
+      expect(fx.pos).toBe(1)
+      expect(fx.situation.loop).toBe(0)
+      expect(fx.situation.reversed).toBe(true)
+    })
+
+    it('sets the animation at the start, before any loops when reversing is true', function() {
+      fx.loop(true, true) // Set reversing to true
+
+      // When the animation is running forward, the start position is 0
+      fx.at(11.21, true)
+      expect(fx.absPos).toBe(11.21)
+      expect(fx.pos).toBeCloseTo(1 - 0.21)
+      expect(fx.situation.loop).toBe(11)
+      expect(fx.situation.reversed).toBe(true)
+
+      fx.atStart()
+      expect(fx.absPos).toBe(0)
+      expect(fx.pos).toBe(0)
+      expect(fx.situation.loop).toBe(0)
+      expect(fx.situation.reversed).toBe(false)
+
+      // When the animation is running backward, the start position is 1
+      fx.reverse(true).at(14.10, true)
+      expect(fx.absPos).toBe(14.10)
+      expect(fx.pos).toBeCloseTo(1 - 0.10)
+      expect(fx.situation.loop).toBe(14)
+      expect(fx.situation.reversed).toBe(true)
+
+      fx.atStart()
+      expect(fx.absPos).toBe(0)
+      expect(fx.pos).toBe(1)
+      expect(fx.situation.loop).toBe(0)
+      expect(fx.situation.reversed).toBe(true)
+    })
+  })
+
+
+  describe('atEnd()', function () {
+    it('sets the animation at the end', function() {
+      // When the animation is running forward, the end position is 1
+      this.pos = 0.5
+      expect(fx.atEnd().pos).toBe(1)
+      expect(fx.situation).toBeNull()
+
+      // Recreate an animation since the other one was ended
+      fx.animate()
+
+      // When the animation is running backward, the end position is 0
+      this.pos = 0.5
+      expect(fx.reverse(true).atEnd().pos).toBe(0)
+      expect(fx.situation).toBeNull()
+    })
+
+    it('sets the animation at the end, after all loops', function() {
+      var loops
+
+      // When the animation is running forward, the end position is 1
+      loops = 12
+      fx.loop(loops).start().step()
+      expect(fx.absPos).toBe(0)
+      expect(fx.pos).toBe(0)
+      expect(fx.active).toBe(true)
+      expect(fx.situation.loop).toBe(0)
+      expect(fx.situation.loops).toBe(loops)
+
+      fx.atEnd()
+      expect(fx.absPos).toBe(loops)
+      expect(fx.pos).toBe(1)
+      expect(fx.active).toBe(false)
+      expect(fx.situation).toBeNull()
+
+      // Recreate an animation since the other one was ended
+      fx.animate()
+
+
+      // When the animation is running backward, the end position is 0
+      loops = 21
+      fx.reverse(true).loop(loops).start().step()
+      expect(fx.absPos).toBe(0)
+      expect(fx.pos).toBe(1)
+      expect(fx.active).toBe(true)
+      expect(fx.situation.loop).toBe(0)
+      expect(fx.situation.loops).toBe(loops)
+      expect(fx.situation.reversed).toBe(true)
+
+      fx.atEnd()
+      expect(fx.absPos).toBe(loops)
+      expect(fx.pos).toBe(0)
+      expect(fx.active).toBe(false)
+      expect(fx.situation).toBeNull()
+    })
+
+    it('sets the animation at the end, after all loops when reversing is true', function() {
+      var loops
+
+      // When reversing is true, the end position equal the start position when
+      // loops is even
+
+      // The animation is running forward
+      loops = 6
+      fx.loop(loops, true).start().step()
+      expect(fx.absPos).toBe(0)
+      expect(fx.pos).toBe(0)
+      expect(fx.active).toBe(true)
+      expect(fx.situation.loop).toBe(0)
+      expect(fx.situation.loops).toBe(loops)
+      expect(fx.situation.reversed).toBe(false)
+
+      fx.atEnd()
+      expect(fx.absPos).toBe(loops)
+      expect(fx.pos).toBe(0) // Equal start position because loops is even
+      expect(fx.active).toBe(false)
+      expect(fx.situation).toBeNull()
+
+      // Recreate an animation since the other one was ended
+      fx.animate()
+
+
+      // The animation is running backward
+      loops = 3
+      fx.reverse(true).loop(loops, true).start().step()
+      expect(fx.absPos).toBe(0)
+      expect(fx.pos).toBe(1)
+      expect(fx.active).toBe(true)
+      expect(fx.situation.loop).toBe(0)
+      expect(fx.situation.loops).toBe(loops)
+      expect(fx.situation.reversed).toBe(true)
+
+      fx.atEnd()
+      expect(fx.absPos).toBe(loops)
+      expect(fx.pos).toBe(0) // Not equal to the start position because loops is odd
+      expect(fx.active).toBe(false)
+      expect(fx.situation).toBeNull()
     })
   })
 
+
   describe('at()', function() {
     it('sets the progress to the specified position', function() {
-      var start = fx.situation.start
-      expect(fx.at(0.5).pos).toBe(0.5)
-      // time is running so we cant compare it directly
-      expect(fx.situation.start).toBeLessThan(start - fx.situation.duration * 0.5 + 1, 0.0001)
-      expect(fx.situation.start).toBeGreaterThan(start - fx.situation.duration * 0.5 - 10)
+      var pos
+
+      // Animation running forward
+      pos = 0.5
+      expect(fx.at(pos).pos).toBe(pos)
+      expect(fx.situation.start).toBe(+new Date - fx.situation.duration * pos)
+
+      // Animation running backward
+      pos = 0.4
+      expect(fx.reverse(true).at(pos).pos).toBe(pos)
+      expect(fx.situation.start).toBe(+new Date - fx.situation.duration * (1-pos))
+    })
+
+    it('should convert a position to an absolute position', function () {
+      var pos, loop, absPos
+
+      fx.loop(true)
+
+      // Animation running forward
+      pos = 0.7
+      loop = 4
+      absPos = pos+loop
+      fx.situation.loop = loop
+      expect(fx.at(pos).absPos).toBe(absPos)
+      expect(fx.situation.start).toBe(+new Date - fx.situation.duration * absPos)
+
+      // Animation running backward
+      pos = 0.23
+      loop = 9
+      absPos = (1-pos)+loop
+      fx.situation.loop = loop
+      fx.situation.reversed = true
+      expect(fx.at(pos).absPos).toBe(absPos)
+      expect(fx.situation.start).toBe(+new Date - fx.situation.duration * absPos)
+
+    })
+
+    it('should end the animation when the end position is passed', function() {
+      var pos
+
+      fx.start()
+      expect(fx.active).toBe(true)
+      expect(fx.situation).not.toBeNull()
+
+      // When running forward, the end position is 1
+      pos = 1
+      expect(fx.at(pos).pos).toBe(pos)
+      expect(fx.active).toBe(false)
+      expect(fx.situation).toBeNull()
+
+      // Recreate an animation since the other one was ended
+      fx.animate().start()
+      expect(fx.active).toBe(true)
+      expect(fx.situation).not.toBeNull()
+
+      // When running backward, the end position is 0
+      pos = 0
+      expect(fx.reverse(true).at(pos).pos).toBe(pos)
+      expect(fx.active).toBe(false)
+      expect(fx.situation).toBeNull()
+    })
+
+    it('correct the passed position when it is out of [0,1] and the animation is not looping', function () {
+      var pos
+
+      pos = -0.7
+      expect(fx.at(pos).pos).toBe(0)
+
+      pos = 1.3
+      expect(fx.at(pos).pos).toBe(1)
+
+      // Recreate an animation since the other one was ended
+      fx.animate()
+
+      // Should work even when animation is running backward
+      pos = 1.3
+      expect(fx.reverse(true).at(pos).pos).toBe(1)
+
+      pos = -0.7
+      expect(fx.reverse(true).at(pos).pos).toBe(0)
+    })
+
+    it('should, when the animation is looping and the passed position is out of [0,1], use the integer part of postion to update the loop counter and set position to its fractional part', function(){
+      var loop, pos, posFrac, posInt
+
+      // Without the reverse flag
+      fx.loop(10)
+      expect(fx.situation.loops).toBe(10)
+      expect(fx.situation.loop).toBe(loop = 0)
+
+      pos = 1.3
+      posFrac = pos % 1
+      posInt = pos - posFrac
+      expect(fx.at(pos).pos).toBeCloseTo(posFrac)
+      expect(fx.situation.loop).toBe(loop += posInt)
+
+      pos = 7.723
+      posFrac = pos % 1
+      posInt = pos - posFrac
+      expect(fx.at(pos).pos).toBeCloseTo(posFrac)
+      expect(fx.situation.loop).toBe(loop += posInt)
+
+      // In this case, pos is above the remaining number of loops, so we expect
+      // the position to be set to 1 and the animation to be ended
+      pos = 4.3
+      posFrac = pos % 1
+      posInt = pos - posFrac
+      expect(fx.at(pos).pos).toBe(1)
+      expect(fx.situation).toBeNull()
+
+      // Recreate an animation since the other one was ended
+      fx.animate()
+
+      // With the reverse flag, the position is reversed each time loop is odd
+      fx.loop(10, true)
+      expect(fx.situation.loops).toBe(10)
+      expect(fx.situation.loop).toBe(loop = 0)
+      expect(fx.situation.reversed).toBe(false)
+
+      pos = 3.3
+      posFrac = pos % 1
+      posInt = pos - posFrac
+      expect(fx.at(pos).pos).toBeCloseTo(1-posFrac) // Animation is reversed because 0+3 is odd
+      expect(fx.situation.loop).toBe(loop += posInt)
+      expect(fx.situation.reversed).toBe(true)
+
+      // When the passed position is below 0, the integer part of position is
+      // substracted from 1, so, in this case, -0.6 has 1 as is integer part
+      // This is necessary so we can add something to the loop counter
+      pos = -0.645
+      posFrac = (1-pos) % 1
+      posInt = (1-pos) - posFrac
+      expect(fx.at(pos).pos).toBeCloseTo(posFrac)
+      expect(fx.situation.loop).toBe(loop += posInt)
+      expect(fx.situation.reversed).toBe(false)
+
+      // In this case, pos is above the remaining number of loop, so we expect
+      // the position to be set to 0 (since we end reversed) and the animation to
+      // be ended
+      pos = 7.2
+      posFrac = pos % 1
+      posInt = pos - posFrac
+      expect(fx.at(pos).pos).toBe(0)
+      expect(fx.situation).toBeNull()
+    })
+
+    it('should, when the animation is in a infinite loop and the passed position is out of [0,1], use the integer part of postion to update the loop counter and set position to its fractional part', function(){
+      var loop, pos, posFrac, posInt
+
+      // Without the reverse flag
+      fx.loop(true)
+      expect(fx.situation.loops).toBe(true)
+      expect(fx.situation.loop).toBe(loop = 0)
+
+      pos = 10.34
+      posFrac = pos % 1
+      posInt = pos - posFrac
+      expect(fx.at(pos).pos).toBeCloseTo(posFrac)
+      expect(fx.situation.loop).toBe(loop += posInt)
+
+      // With the reverse flag, the position is reversed each time loop is odd
+      fx.loop(true, true)
+      expect(fx.situation.loops).toBe(true)
+      expect(fx.situation.loop).toBe(loop = 0)
+      expect(fx.situation.reversed).toBe(false)
+
+      pos = 3.3
+      posFrac = pos % 1
+      posInt = pos - posFrac
+      expect(fx.at(pos).pos).toBeCloseTo(1-posFrac) // Animation is reversed because 3+0 is odd
+      expect(fx.situation.loop).toBe(loop += posInt)
+      expect(fx.situation.reversed).toBe(true)
+
+      pos = -8.41
+      posFrac = (1-pos) % 1
+      posInt = (1-pos) - posFrac
+      expect(fx.at(pos).pos).toBeCloseTo(posFrac)
+      expect(fx.situation.loop).toBe(loop += posInt)
+      expect(fx.situation.reversed).toBe(false)
     })
 
     it('should take speed into consideration', function() {
@@ -103,28 +462,68 @@ describe('FX', function() {
       fx.speed(spd).at(0.83)
       expect(fx.situation.finish-fx.situation.start).toBe(dur/spd)
     })
+
+    it('should consider the first parameter as an absolute position when the second parameter is true', function() {
+      var absPos
+
+      fx.loop(true)
+
+      absPos = 3.2
+      expect(fx.at(absPos, true).absPos).toBe(absPos)
+
+      absPos = -4.27
+      expect(fx.at(absPos, true).absPos).toBe(absPos)
+
+      absPos = 0
+      expect(fx.at(absPos, true).absPos).toBe(absPos)
+
+      absPos = 1
+      expect(fx.at(absPos, true).absPos).toBe(absPos)
+    })
   })
 
+
   describe('start()', function(){
-    it('starts the animation', function(done) {
+    it('starts the animation', function() {
       fx.start()
       expect(fx.active).toBe(true)
       expect(fx.timeout).not.toBe(0)
-      setTimeout(function(){
-        expect(fx.pos).toBeGreaterThan(0)
-        done()
-      }, 200)
+
+      jasmine.clock().tick(201)
+      fx.step() // Call step to update the animation
+
+      expect(fx.pos).toBeGreaterThan(0)
     })
 
     it('should take speed into consideration', function() {
-      var dur = fx.situation.duration
+      var dur = 500
+        , delay = 300
         , spd = 4
 
-      fx.speed(spd).start()
-      expect(fx.situation.finish-fx.situation.start).toBe(dur/spd)
+
+      fx.stop().animate(dur, '-', delay).speed(spd).start()
+      expect(fx.situation.finish - new Date).toBe(delay/spd + dur/spd)
+    })
+
+    it('should do the delay', function() {
+      fx.situation.delay = 1000
+      expect(fx.start().active).toBe(true)
+
+      jasmine.clock().tick(501)
+      fx.step() // Call step to update the animation
+      expect(fx.active).toBe(true)
+
+      jasmine.clock().tick(501)
+      fx.step() // Call step to update the animation
+      expect(fx.active).toBe(true)
+
+      jasmine.clock().tick(501)
+      fx.step() // Call step to update the animation
+      expect(fx.active).toBe(false)
     })
   })
 
+
   describe('pause()', function() {
     it('pause the animation', function() {
       expect(fx.pause().paused).toBe(true)
@@ -132,16 +531,64 @@ describe('FX', function() {
   })
 
   describe('play()', function() {
-    it('unpause the animation', function(done) {
+    it('unpause the animation', function() {
       var start = fx.start().pause().situation.start
-      setTimeout(function(){
-        expect(fx.play().paused).toBe(false)
-        expect(fx.situation.start).not.toBe(start)
-        done()
-      }, 200)
+
+      jasmine.clock().tick(200)
+
+      expect(fx.situation.start).toBe(start)
+      expect(fx.play().paused).toBe(false)
+      expect(fx.situation.start).not.toBe(start)
+    })
+
+    it('should not change the position when the animation is unpaused while it is set to run backward', function(){
+      var pos = 0.4
+
+      expect(fx.reverse(true).at(pos).pause().play().pos).toBe(pos)
+    })
+
+    it('should be able to unpause the delay', function () {
+      fx.stop().animate(500, '-', 300).start().step()
+      expect(fx.pos).toBe(0)
+      expect(fx.absPos).toBeCloseTo(-0.6)
+
+      // At this point, we should have an animation of 500 ms with a delay of
+      // 300 ms that should be running.
+
+      jasmine.clock().tick(150)
+
+      // Should be halfway through the delay
+      fx.step()
+      expect(fx.pos).toBe(0)
+      expect(fx.absPos).toBe(-0.3)
+
+      expect(fx.pause().paused).toBe(true) // Pause the delay
+
+      jasmine.clock().tick(150)
+
+      // Unpause, should still be halfway through the delay
+      expect(fx.play().paused).toBe(false)
+      expect(fx.pos).toBe(0)
+      expect(fx.absPos).toBe(-0.3)
+
+      jasmine.clock().tick(150)
+
+      // Delay should be done
+      fx.step()
+      expect(fx.pos).toBe(0)
+      expect(fx.absPos).toBe(0)
+
+      jasmine.clock().tick(500)
+
+      // Animation and delay should be done
+      fx.step()
+      expect(fx.active).toBe(false)
+      expect(fx.pos).toBe(1)
+      expect(fx.absPos).toBe(1)
     })
   })
 
+
   describe('speed()', function() {
     it('set the speed of the animation', function(){
       var dur, spd
@@ -172,7 +619,7 @@ describe('FX', function() {
     it('should not change the position when the animation is run backward', function(){
       var pos = 0.4
 
-      expect(fx.at(pos).reverse(true).speed(2).pos).toBe(pos)
+      expect(fx.reverse(true).at(pos).speed(2).pos).toBe(pos)
     })
 
     it('return the current speed with no argument given', function(){
@@ -199,7 +646,7 @@ describe('FX', function() {
       expect(fx.paused).toBe(true)
     })
 
-    it('should affect all animations in the queue', function(done){
+    it('should affect all animations in the queue', function(){
       fx.speed(2).animate(300)
       expect(fx.situations.length).not.toBe(0)
       expect(fx.pos).not.toBe(1)
@@ -209,25 +656,64 @@ describe('FX', function() {
       // we just added. Normally, it would take 800ms before both of these
       // animations are done, but because we set the speed to 2, it should
       // only take 400ms to do both animations.
-      fx.start()
+      fx.start().step()
 
-      // We expect this timeout to happen while the second animation is running
-      setTimeout(function(){
-        expect(fx.active).toBe(true)
-        expect(fx.situations.length).toBe(0)
-        expect(fx.pos).not.toBe(1)
-      }, 300)
+      jasmine.clock().tick(250)
 
-      // Here, we expect that all the animations are done
-      setTimeout(function(){
-        expect(fx.active).toBe(false)
-        expect(fx.situations.length).toBe(0)
-        expect(fx.pos).toBe(1)
-        done()
-      }, 450)
+      // Should be playing the second animation
+      fx.step()
+      expect(fx.active).toBe(true)
+      expect(fx.situations.length).toBe(0)
+      expect(fx.pos).not.toBe(1)
+
+      jasmine.clock().tick(150) // 400ms have passed
+
+      // All animations should be done
+      fx.step()
+      expect(fx.active).toBe(false)
+      expect(fx.situations.length).toBe(0)
+      expect(fx.pos).toBe(1)
+    })
+
+    it('should affect the delay', function() {
+      fx.stop().animate(500, '-', 300).start().step()
+      expect(fx.pos).toBe(0)
+      expect(fx.absPos).toBeCloseTo(-0.6)
+
+      fx.speed(2)
+      expect(fx.pos).toBe(0)
+      expect(fx.absPos).toBeCloseTo(-0.6)
+
+      // At this point, we should have an animation of 500 ms with a delay of
+      // 300 ms that should be running. Normally, it would take 800 ms for the
+      // animation and its delay to complete, but because the speed is set to 2
+      // , it should only take 400ms
+
+      jasmine.clock().tick(75)
+
+      // Should be halfway through the delay
+      fx.step()
+      expect(fx.pos).toBe(0)
+      expect(fx.absPos).toBe(-0.3)
+
+      jasmine.clock().tick(75)
+
+      // Delay should be done
+      fx.step()
+      expect(fx.pos).toBe(0)
+      expect(fx.absPos).toBe(0)
+
+      jasmine.clock().tick(250)
+
+      // Animation and delay should be done
+      fx.step()
+      expect(fx.active).toBe(false)
+      expect(fx.pos).toBe(1)
+      expect(fx.absPos).toBe(1)
     })
   })
 
+
   describe('reverse()', function() {
     it('toggles the direction of the animation without a parameter', function() {
       expect(fx.reverse().situation.reversed).toBe(true)
@@ -246,20 +732,36 @@ describe('FX', function() {
     })
   })
 
+
+  describe('dequeue()', function() {
+    it('initialize the animation pulled from the queue to its start position', function() {
+      // When the animation is forward, the start position is 0
+      fx.animate()
+      fx.pos = 0.5
+      expect(fx.dequeue().pos).toBe(0)
+
+      // When the animation backward, the start position is 1
+      fx.animate().reverse(true)
+      fx.pos = 0.5
+      expect(fx.dequeue().pos).toBe(1)
+    })
+  })
+
+
   describe('stop()', function() {
     it('stops the animation immediately without a parameter', function() {
-      fx.animate(500)
+      fx.animate(500).start()
       expect(fx.stop().situation).toBeNull()
-      expect(fx.active).toBeFalsy()
+      expect(fx.active).toBe(false)
       expect(fx.situations.length).toBe(1)
     })
   })
 
   describe('stop()', function() {
     it('stops the animation immediately and fullfill it if first parameter true', function() {
-      fx.animate(500)
+      fx.animate(500).start()
       expect(fx.stop(true).situation).toBeNull()
-      expect(fx.active).toBeFalsy()
+      expect(fx.active).toBe(false)
       expect(fx.pos).toBe(1)
       expect(fx.situations.length).toBe(1)
     })
@@ -267,13 +769,38 @@ describe('FX', function() {
 
   describe('stop()', function() {
     it('stops the animation immediately and remove all items from queue when second parameter true', function() {
-      fx.animate(500)
+      fx.animate(500).start()
       expect(fx.stop(false, true).situation).toBeNull()
-      expect(fx.active).toBeFalsy()
+      expect(fx.active).toBe(false)
       expect(fx.situations.length).toBe(0)
     })
   })
 
+
+  describe('reset()', function() {
+    it('resets the element to the state it was when the current animation was started', function() {
+      var loops = 4
+        , situation = fx.situation
+
+      // These settings make the animations run backward
+      fx.situation.loop = 2
+      fx.situation.loops = loops
+      fx.situation.reversed = true
+      fx.pos = 0.5
+      fx.absPos = 2.5
+
+      fx.reset()
+
+      expect(fx.situation).toBe(situation)
+      expect(fx.situation.loops).toBe(loops)
+      expect(fx.situation.loop).toBe(0)
+      expect(fx.situation.reversed).toBe(true) // True because the animation is backward
+      expect(fx.pos).toBe(1)
+      expect(fx.absPos).toBe(0)
+    })
+  })
+
+
   describe('finish()', function() {
     it('finish the whole animation by fullfilling every single one', function() {
       fx.animate(500)
@@ -283,6 +810,7 @@ describe('FX', function() {
     })
   })
 
+
   describe('progress()', function() {
     it('returns the current position', function() {
       expect(fx.progress()).toBe(0)
@@ -290,46 +818,108 @@ describe('FX', function() {
     })
   })
 
+
   describe('after()', function() {
-    it('adds a callback which is called when the current animation is finished', function(done) {
+    it('adds a callback which is called when the current animation is finished', function() {
+      var called = false
+
       fx.start().after(function(situation){
         expect(fx.situation).toBe(situation)
         expect(fx.pos).toBe(1)
-        done()
+        called = true
       })
+
+      jasmine.clock().tick(500)
+      fx.step()
+      expect(called).toBe(true)
     })
   })
 
+
   describe('afterAll()', function() {
-    it('adds a callback which is called when all animations are finished', function(done) {
+    it('adds a callback which is called when all animations are finished', function() {
+      var called = false
+
       fx.animate(150).animate(125).start().afterAll(function(){
         expect(fx.pos).toBe(1)
         expect(fx.situations.length).toBe(0)
-        done()
+        called = true
       })
+
+      expect(fx.situations.length).toBe(2)
+
+      // End of the first animation
+      jasmine.clock().tick(500)
+      fx.step()
+      expect(fx.situations.length).toBe(1)
+      expect(called).toBe(false)
+
+      // End of the second animation
+      jasmine.clock().tick(150)
+      fx.step()
+      expect(fx.situations.length).toBe(0)
+      expect(called).toBe(false)
+
+      // End of the third and last animation
+      jasmine.clock().tick(125)
+      fx.step()
+      expect(fx.situation).toBeNull()
+      expect(called).toBe(true)
     })
   })
 
+
   describe('during()', function() {
-    it('adds a callback which is called on every animation step', function(done) {
+    it('adds a callback which is called on every animation step', function() {
+      var called = 0
 
       fx.start().during(function(pos, morph, eased, situation){
 
         expect(fx.situation).toBe(situation)
-        expect(morph(0, 100)).toBeCloseTo(pos*100)
 
-        if(fx.pos > 0.9){
-          rect.off('.fx')
-          fx.stop()
+        switch(++called) {
+          case 1:
+            expect(pos).toBeCloseTo(0.25)
+            break
+
+          case 2:
+            expect(pos).toBeCloseTo(0.5)
+            break
+
+          case 3:
+            expect(pos).toBeCloseTo(0.65)
+            break
 
-          done()
+          case 4:
+            expect(pos).toBe(1)
+            break
         }
+
+        expect(morph(0, 100)).toBeCloseTo(pos*100)
+
       })
+
+      jasmine.clock().tick(125)
+      fx.step()
+      expect(called).toBe(1)
+
+      jasmine.clock().tick(125) // 250 ms have passed
+      fx.step()
+      expect(called).toBe(2)
+
+      jasmine.clock().tick(75) // 325 ms have passed
+      fx.step()
+      expect(called).toBe(3)
+
+      jasmine.clock().tick(175) // 500 ms have passed
+      fx.step()
+      expect(called).toBe(4)
     })
   })
 
+
   describe('duringAll()', function() {
-    it('adds a callback which is called on every animation step for the whole chain', function(done) {
+    it('adds a callback which is called on every animation step for the whole chain', function() {
 
       fx.finish()
       rect.off('.fx')
@@ -341,218 +931,523 @@ describe('FX', function() {
       var pos1 = false
       var pos2 = false
 
-      setTimeout(function(){
-        pos1 = true
-      }, 300)
-
-      setTimeout(function(){
-        pos2 = true
-      }, 800)
-
       fx.duringAll(function(pos, morph, eased, situation){
 
         if(pos1){
           pos1 = false
           sit = situation
-          expect(this.fx.pos).toBeGreaterThan(0.5)
+          expect(this.fx.pos).toBeCloseTo(0.6)
         }
 
         if(pos2){
           pos2 = null
           expect(situation).not.toBe(sit)
-          expect(this.fx.pos).toBeGreaterThan(0.5)
-          done()
+          expect(this.fx.pos).toBeCloseTo(0.75)
         }
       })
 
-      setTimeout(function(){
-        if(pos2 === null) return
+      pos1 = true
+      jasmine.clock().tick(300)
+      fx.step()
+
+      jasmine.clock().tick(200) // End of the first animation
+      fx.step()
+
+      pos2 = true
+      jasmine.clock().tick(375)
+      fx.step()
+
+      if(pos1 || pos2) {
         fail('Not enough situations called')
-        done()
-      }, 1200)
+      }
     })
   })
 
+
   describe('once()', function() {
-    it('adds a callback which is called once at the specified position', function(done) {
+    it('adds a callback which is called once at the specified position', function() {
+      var called = false
 
       fx.start().once(0.5, function(pos, eased){
-        expect(pos).toBeGreaterThan(0.49)
-        done()
+        called = true
+        expect(pos).toBeCloseTo(0.5)
       })
+
+      jasmine.clock().tick(125)
+      fx.step()
+      expect(called).toBe(false)
+
+      jasmine.clock().tick(125) // 250 ms have passed
+      fx.step()
+      expect(called).toBe(true)
     })
   })
 
+
   describe('loop()', function() {
-    it('should create an eternal loop when no arguments are given', function(done) {
+    it('should create an eternal loop when no arguments are given', function() {
+      var time = 10523, dur = fx.situation.duration
+
       fx.loop()
-      expect(fx.situation.loop).toBe(true)
+      expect(fx.situation.loop).toBe(0)
       expect(fx.situation.loops).toBe(true)
+      expect(fx.pos).toBe(0)
+      expect(fx.absPos).toBe(0)
 
-      fx.start()
-      setTimeout(function(){
-        expect(fx.active).toBe(true)
-        expect(fx.situation.loop).toBe(true)
-        expect(fx.situation.loops).toBe(true)
-        expect(fx.pos).toBeCloseTo(0.6, 1)
-        done()
-      }, 800)
+      fx.start().step()
+      jasmine.clock().tick(time)
+      fx.step()
+
+      expect(fx.active).toBe(true)
+      expect(fx.situation.loop).toBe( Math.floor(time/dur) )
+      expect(fx.situation.loops).toBe(true)
+      expect(fx.pos).toBeCloseTo((time/dur) % 1)
+      expect(fx.absPos).toBeCloseTo(time/dur)
     })
 
-    it('should create an eternal loop when the first argument is true', function(done) {
+    it('should create an eternal loop when the first argument is true', function() {
+      var time = 850452, dur = fx.situation.duration
+
       fx.loop(true)
-      expect(fx.situation.loop).toBe(true)
+      expect(fx.situation.loop).toBe(0)
       expect(fx.situation.loops).toBe(true)
+      expect(fx.pos).toBe(0)
+      expect(fx.absPos).toBe(0)
 
-      fx.start()
-      setTimeout(function(){
-        expect(fx.active).toBe(true)
-        expect(fx.situation.loop).toBe(true)
-        expect(fx.situation.loops).toBe(true)
-        expect(fx.pos).toBeCloseTo(0.3, 1)
-        done()
-      }, 650)
+      fx.start().step()
+      jasmine.clock().tick(time)
+      fx.step()
+
+      expect(fx.active).toBe(true)
+      expect(fx.situation.loop).toBe( Math.floor(time/dur) )
+      expect(fx.situation.loops).toBe(true)
+      expect(fx.pos).toBeCloseTo((time/dur) % 1)
+      expect(fx.absPos).toBeCloseTo(time/dur)
     })
 
-    it('should loop for the specified number of times', function(done) {
+    it('should loop for the specified number of times', function() {
+      var time = 0, dur = fx.situation.duration
+
       fx.loop(3)
-      expect(fx.situation.loop).toBe(3)
+      expect(fx.situation.loop).toBe(0)
       expect(fx.situation.loops).toBe(3)
+      expect(fx.pos).toBe(0)
+      expect(fx.absPos).toBe(0)
 
-      fx.start()
-      setTimeout(function(){
-        expect(fx.active).toBe(true)
-        expect(fx.situation.loop).toBe(3)
-        expect(fx.situation.loops).toBe(3)
-        expect(fx.pos).toBeCloseTo(0.4, 1)
-      }, 200)
-
-      setTimeout(function(){
-        expect(fx.active).toBe(true)
-        expect(fx.situation.loop).toBe(2)
-        expect(fx.situation.loops).toBe(3)
-        expect(fx.pos).toBeCloseTo(0.5, 1)
-      }, 750)
-
-      setTimeout(function(){
-        expect(fx.active).toBe(true)
-        expect(fx.situation.loop).toBe(1)
-        expect(fx.situation.loops).toBe(3)
-        expect(fx.pos).toBeCloseTo(0.64, 1)
-      }, 1320)
-
-      setTimeout(function(){
-        expect(fx.active).toBe(false)
-        expect(fx.situation).toBeNull()
-        expect(fx.pos).toBe(1)
-        done()
-      }, 1600)
+      fx.start().step()
+      jasmine.clock().tick(200)
+      time = 200
+
+      fx.step()
+      expect(fx.active).toBe(true)
+      expect(fx.situation.loop).toBe(0)
+      expect(fx.situation.loops).toBe(3)
+      expect(fx.pos).toBeCloseTo((time/dur) % 1)
+      expect(fx.absPos).toBeCloseTo(time/dur)
+
+      jasmine.clock().tick(550)
+      time += 550 // time at 750
+
+      fx.step()
+      expect(fx.active).toBe(true)
+      expect(fx.situation.loop).toBe(1)
+      expect(fx.situation.loops).toBe(3)
+      expect(fx.pos).toBeCloseTo((time/dur) % 1)
+      expect(fx.absPos).toBeCloseTo(time/dur)
+
+      jasmine.clock().tick(570)
+      time += 570 // time at 1320
+
+      fx.step()
+      expect(fx.active).toBe(true)
+      expect(fx.situation.loop).toBe(2)
+      expect(fx.situation.loops).toBe(3)
+      expect(fx.pos).toBeCloseTo((time/dur) % 1)
+      expect(fx.absPos).toBeCloseTo(time/dur)
+
+      jasmine.clock().tick(180)
+      time += 180 // time at 1500
+
+      fx.step()
+      expect(fx.active).toBe(false)
+      expect(fx.situation).toBeNull()
+      expect(fx.pos).toBe(1)
+      expect(fx.absPos).toBe(3)
     })
 
-    it('should go from beginning to end and start over again (0->1.0->1.0->1.) by default', function(done) {
+    it('should go from beginning to end and start over again (0->1.0->1.0->1.) by default', function() {
+      var time = 0, dur = fx.situation.duration
+
       fx.loop(2)
-      expect(fx.situation.loop).toBe(2)
+      expect(fx.situation.loop).toBe(0)
       expect(fx.situation.loops).toBe(2)
       expect(fx.situation.reversing).toBe(false)
       expect(fx.situation.reversed).toBe(false)
+      expect(fx.pos).toBe(0)
+      expect(fx.absPos).toBe(0)
 
-      fx.start()
-      setTimeout(function(){
-        expect(fx.active).toBe(true)
-        expect(fx.situation.loop).toBe(2)
-        expect(fx.situation.loops).toBe(2)
-        expect(fx.situation.reversing).toBe(false)
-        expect(fx.situation.reversed).toBe(false)
-        expect(fx.pos).toBeCloseTo(0.65, 1)
-      }, 325)
-
-      setTimeout(function(){
-        expect(fx.active).toBe(true)
-        expect(fx.situation.loop).toBe(1)
-        expect(fx.situation.loops).toBe(2)
-        expect(fx.situation.reversing).toBe(false)
-        expect(fx.situation.reversed).toBe(false)
-        expect(fx.pos).toBeCloseTo(0.8, 1)
-      }, 900)
+      fx.start().step()
+      jasmine.clock().tick(325)
+      time = 325
 
-      setTimeout(function(){
-        expect(fx.active).toBe(false)
-        expect(fx.situation).toBeNull()
-        expect(fx.pos).toBe(1)
-        done()
-      }, 1100)
+      fx.step()
+      expect(fx.active).toBe(true)
+      expect(fx.situation.loop).toBe(0)
+      expect(fx.situation.loops).toBe(2)
+      expect(fx.situation.reversing).toBe(false)
+      expect(fx.situation.reversed).toBe(false)
+      expect(fx.pos).toBeCloseTo((time/dur) % 1)
+      expect(fx.absPos).toBeCloseTo(time/dur)
+
+      jasmine.clock().tick(575)
+      time += 575 // time at 900
+
+      fx.step()
+      expect(fx.active).toBe(true)
+      expect(fx.situation.loop).toBe(1)
+      expect(fx.situation.loops).toBe(2)
+      expect(fx.situation.reversing).toBe(false)
+      expect(fx.situation.reversed).toBe(false)
+      expect(fx.pos).toBeCloseTo((time/dur) % 1)
+      expect(fx.absPos).toBeCloseTo(time/dur)
+
+      jasmine.clock().tick(200)
+      time += 200 // time at 1100
+
+      fx.step()
+      expect(fx.active).toBe(false)
+      expect(fx.situation).toBeNull()
+      expect(fx.pos).toBe(1)
+      expect(fx.absPos).toBe(2)
     })
 
-    it('should be completely reversed before starting over (0->1->0->1->0->1.) when the reverse flag is passed', function(done) {
+    it('should be completely reversed before starting over (0->1->0->1->0->1.) when the reverse flag is passed', function() {
+      var time = 0, dur = fx.situation.duration
+
       fx.loop(2, true)
-      expect(fx.situation.loop).toBe(2)
+      expect(fx.situation.loop).toBe(0)
       expect(fx.situation.loops).toBe(2)
       expect(fx.situation.reversing).toBe(true)
       expect(fx.situation.reversed).toBe(false)
+      expect(fx.pos).toBe(0)
+      expect(fx.absPos).toBe(0)
 
-      fx.start()
-      setTimeout(function(){
-        expect(fx.active).toBe(true)
-        expect(fx.situation.loop).toBe(2)
-        expect(fx.situation.loops).toBe(2)
-        expect(fx.situation.reversing).toBe(true)
-        expect(fx.situation.reversed).toBe(false)
-        expect(fx.pos).toBeCloseTo(0.65, 1)
-      }, 325)
-
-      setTimeout(function(){
-        expect(fx.active).toBe(true)
-        expect(fx.situation.loop).toBe(1)
-        expect(fx.situation.loops).toBe(2)
-        expect(fx.situation.reversing).toBe(true)
-        expect(fx.situation.reversed).toBe(true)
-        expect(fx.pos).toBeCloseTo(0.2, 1)
-      }, 900)
+      fx.start().step()
+      jasmine.clock().tick(325)
+      time = 325
 
-      setTimeout(function(){
-        expect(fx.active).toBe(false)
-        expect(fx.situation).toBeNull()
-        expect(fx.pos).toBe(0)
-        done()
-      }, 1100)
+      fx.step()
+      expect(fx.active).toBe(true)
+      expect(fx.situation.loop).toBe(0)
+      expect(fx.situation.loops).toBe(2)
+      expect(fx.situation.reversing).toBe(true)
+      expect(fx.situation.reversed).toBe(false)
+      expect(fx.pos).toBeCloseTo((time/dur) % 1)
+      expect(fx.absPos).toBeCloseTo(time/dur)
+
+      jasmine.clock().tick(575)
+      time += 575 // time at 900
+
+      fx.step()
+      expect(fx.active).toBe(true)
+      expect(fx.situation.loop).toBe(1)
+      expect(fx.situation.loops).toBe(2)
+      expect(fx.situation.reversing).toBe(true)
+      expect(fx.situation.reversed).toBe(true)
+      expect(fx.pos).toBeCloseTo(1 - (time/dur) % 1)
+      expect(fx.absPos).toBeCloseTo(time/dur)
+
+      jasmine.clock().tick(200)
+      time += 200 // time at 1100
+
+      fx.step()
+      expect(fx.active).toBe(false)
+      expect(fx.situation).toBeNull()
+      expect(fx.pos).toBe(0)
+      expect(fx.absPos).toBe(2)
     })
 
     it('should be applied on the last situation', function() {
       fx.loop(5)
-      expect(fx.situation.loop).toBe(5)
+      expect(fx.situation.loop).toBe(0)
       expect(fx.situation.loops).toBe(5)
       expect(fx.situation.reversing).toBe(false)
 
       fx.animate().loop(3, true)
-      expect(fx.situation.loop).toBe(5)
+      expect(fx.situation.loop).toBe(0)
       expect(fx.situation.loops).toBe(5)
       expect(fx.situation.reversing).toBe(false)
 
       var c = fx.last()
-      expect(c.loop).toBe(3)
+      expect(c.loop).toBe(0)
       expect(c.loops).toBe(3)
       expect(c.reversing).toBe(true)
     })
+
+    it('should be possible to call it with false as the first argument', function() {
+      fx.situation.loops = true
+      fx.loop(false)
+      expect(fx.situation.loops).toBe(false)
+    })
   })
 
-  it('animates the x/y-attr', function(done) {
+
+  describe('step()', function() {
+    it('should not recalculate the absolute position if the first parameter is true', function() {
+      var absPos
+
+      // We shift start to help us see if the absolute position get recalculated
+      // If it get recalculated, the result would be 0.5
+      fx.situation.start -= 250
+
+      absPos = 0.4
+      fx.absPos = absPos
+      expect(fx.step(true).absPos).toBe(absPos)
+
+      absPos = 0
+      fx.absPos = absPos
+      expect(fx.step(true).absPos).toBe(absPos)
+
+      absPos = -3.7
+      fx.absPos = absPos
+      expect(fx.step(true).absPos).toBe(absPos)
+
+      absPos = 1
+      fx.absPos = absPos
+      expect(fx.step(true).absPos).toBe(absPos)
+    })
+
+    it('should not allow an absolute position to be above the end', function() {
+      var absPos, loops
+
+      // With no loops, absolute position should not go above 1
+      absPos = 4.26
+      fx.absPos = absPos
+      expect(fx.step(true).absPos).toBe(1)
+      expect(fx.situation).toBeNull()
+
+      fx.animate() // Recreate an animation since the other one was ended
+
+      // With loops, absolute position should not go above loops
+      loops = 4
+      absPos = 7.42
+      fx.absPos = absPos
+      expect(fx.loop(loops).step(true).absPos).toBe(loops)
+      expect(fx.situation).toBeNull()
+    })
+
+    describe('when converting an absolute position to a position', function() {
+      it('should, when the absolute position is below the maximum number of loops, use the integer part of the absolute position to set the loop counter and use its fractional part to set the position', function(){
+        var absPos, absPosFrac, absPosInt, loops
+
+        // Without the reverse flag
+        loops = 12
+        absPos = 4.52
+        absPosInt = Math.floor(absPos)
+        absPosFrac = absPos - absPosInt
+        fx.absPos = absPos
+        fx.loop(loops).step(true)
+        expect(fx.pos).toBe(absPosFrac)
+        expect(fx.situation.loop).toBe(absPosInt)
+
+        fx.stop().animate()
+
+        loops = true
+        absPos = 2.57
+        absPosInt = Math.floor(absPos)
+        absPosFrac = absPos - absPosInt
+        fx.absPos = absPos
+        fx.loop(loops).step(true)
+        expect(fx.pos).toBe(absPosFrac)
+        expect(fx.situation.loop).toBe(absPosInt)
+
+        fx.stop().animate()
+
+        // With the reverse flag, the position is reversed at each odd loop
+        loops = 412
+        absPos = 6.14
+        absPosInt = Math.floor(absPos)
+        absPosFrac = absPos - absPosInt
+        fx.absPos = absPos
+        fx.loop(loops, true).step(true)
+        expect(fx.pos).toBe(absPosFrac)
+        expect(fx.situation.loop).toBe(absPosInt)
+        expect(fx.situation.reversed).toBe(false)
+
+        fx.stop().animate()
+
+        loops = true
+        absPos = 5.12
+        absPosInt = Math.floor(absPos)
+        absPosFrac = absPos - absPosInt
+        fx.absPos = absPos
+        fx.loop(loops, true).step(true)
+        expect(fx.pos).toBe(1-absPosFrac) // Odd loop, so it is reversed
+        expect(fx.situation.loop).toBe(absPosInt)
+        expect(fx.situation.reversed).toBe(true)
+
+        fx.stop().animate()
+
+        // When the animation is set to run backward, it is the opposite, the position is reversed at each even loop
+        loops = 14
+        absPos = 8.46
+        absPosInt = Math.floor(absPos)
+        absPosFrac = absPos - absPosInt
+        fx.absPos = absPos
+        fx.reverse(true).loop(loops, true).step(true)
+        expect(fx.pos).toBe(1-absPosFrac) // Even loop, so it is reversed
+        expect(fx.situation.loop).toBe(absPosInt)
+        expect(fx.situation.reversed).toBe(true)
+
+        fx.stop().animate()
+
+        loops = true
+        absPos = 3.12
+        absPosInt = Math.floor(absPos)
+        absPosFrac = absPos - absPosInt
+        fx.absPos = absPos
+        fx.reverse(true).loop(loops, true).step(true)
+        expect(fx.pos).toBe(absPosFrac)
+        expect(fx.situation.loop).toBe(absPosInt)
+        expect(fx.situation.reversed).toBe(false)
+      })
+
+      it('should, when the absolute position is above or equal to the the maximum number of loops, set the position to its end value and end the animation', function() {
+        var absPos, loops
+
+        // Without the reverse flag, the end value of position is 1
+        loops = 6
+        absPos = 13.52
+        fx.absPos = absPos
+        fx.loop(loops).step(true)
+        expect(fx.pos).toBe(1)
+        expect(fx.situation).toBeNull()
+
+        fx.animate() // Recreate an animation since the other one was ended
+
+        loops = false
+        absPos = 146.22
+        fx.absPos = absPos
+        fx.loop(loops).step(true)
+        expect(fx.pos).toBe(1)
+        expect(fx.situation).toBeNull()
+
+        fx.animate() // Recreate an animation since the other one was ended
+
+        // With the reverse flag, the end value of position is 0 when loops is even and 1 when loops is an odd number or false
+        loops = 6
+        absPos = 6
+        fx.absPos = absPos
+        fx.loop(loops, true).step(true)
+        expect(fx.pos).toBe(0) // Even loops
+        expect(fx.situation).toBeNull()
+
+        fx.animate() // Recreate an animation since the other one was ended
+
+        loops = false
+        absPos = 4.47
+        fx.absPos = absPos
+        fx.loop(loops, true).step(true)
+        expect(fx.pos).toBe(1) // 1 since loops is false
+        expect(fx.situation).toBeNull()
+
+        fx.animate() // Recreate an animation since the other one was ended
+
+        // When the animation is set to run backward, it is the opposite, the end value of position is 1 when loops is even and 0 when loops is an odd number or false
+        loops = 8
+        absPos = 12.65
+        fx.absPos = absPos
+        fx.reverse(true).loop(loops, true).step(true)
+        expect(fx.pos).toBe(1) // Even loops
+        expect(fx.situation).toBeNull()
+
+        fx.animate() // Recreate an animation since the other one was ended
+
+        loops = 11
+        absPos = 12.41
+        fx.absPos = absPos
+        fx.reverse(true).loop(loops, true).step(true)
+        expect(fx.pos).toBe(0) // Odd loops
+        expect(fx.situation).toBeNull()
+      })
+
+      it('should set the position to its start value when the absolute position is below 0', function() {
+        var absPos
+
+        // When the animation is not set to run backward the start value is 0
+        absPos = -2.27
+        fx.loop(7)
+        fx.situation.loop = 3
+        fx.absPos = absPos
+        fx.step(true)
+        expect(fx.pos).toBe(0)
+        expect(fx.absPos).toBe(absPos)
+        expect(fx.situation.loop).toBe(0)
+
+        fx.stop().animate()
+
+        // When the animation is set to run backward the start value is 1
+        absPos = -4.12
+        fx.absPos = absPos
+        fx.reverse(true).step(true)
+        expect(fx.pos).toBe(1)
+        expect(fx.absPos).toBe(absPos)
+      })
+
+      it('should, when looping with the reverse flag, toggle reversed only when the difference between the new value of loop counter and its old value is odd', function() {
+        // The new value of the loop counter is the integer part of absPos
+
+        fx.loop(9, true)
+        expect(fx.situation.loop).toBe(0)
+        expect(fx.pos).toBe(0)
+        expect(fx.situation.reversed).toBe(false)
+
+        fx.absPos = 3
+        fx.step(true)
+        expect(fx.situation.reversed).toBe(true) // (3-0) is odd
+
+        fx.absPos = 1
+        fx.step(true)
+        expect(fx.situation.reversed).toBe(true) // (1-3) is even
+
+        fx.absPos = 6
+        fx.step(true)
+        expect(fx.situation.reversed).toBe(false) // (6-1) is odd
+
+        fx.absPos = 9
+        fx.step(true)
+        expect(fx.situation).toBeNull()
+        expect(fx.pos).toBe(1) // It should end not reversed, which mean the position is expected to be 1
+                               // ((9-1)-6) is even, the -1 is because we do not want reversed to be toggled after the last loop
+      })
+    })
+  })
+
+
+  it('animates the x/y-attr', function() {
+    var called = false
 
     fx.move(200,200).after(function(){
 
       expect(rect.x()).toBe(200)
       expect(rect.y()).toBe(200)
-      done()
+      called = true
 
-    });
+    })
 
-    setTimeout(function(){
-      expect(rect.x()).toBeGreaterThan(100)
-      expect(rect.y()).toBeGreaterThan(100)
-    }, 250)
+    jasmine.clock().tick(250)
+    fx.step()
+    expect(rect.x()).toBeGreaterThan(100)
+    expect(rect.y()).toBeGreaterThan(100)
 
+    jasmine.clock().tick(250)
+    fx.step()
+    expect(called).toBe(true)
   })
 
-  it('animates matrix', function(done) {
+
+  it('animates matrix', function() {
+    var ctm, called = false
 
     fx.transform({a:0.8, b:0.4, c:-0.15, d:0.7, e: 90.3, f: 27.07}).after(function(){
 
@@ -563,22 +1458,23 @@ describe('FX', function() {
       expect(ctm.d).toBeCloseTo(0.7)
       expect(ctm.e).toBeCloseTo(90.3)
       expect(ctm.f).toBeCloseTo(27.07)
-
-      done()
+      called = true
 
     })
 
-    setTimeout(function(){
-
-      var ctm = rect.ctm();
-      expect(ctm.a).toBeLessThan(1)
-      expect(ctm.b).toBeGreaterThan(0)
-      expect(ctm.c).toBeLessThan(0)
-      expect(ctm.d).toBeGreaterThan(0)
-      expect(ctm.e).toBeGreaterThan(0)
-      expect(ctm.f).toBeGreaterThan(0)
-    }, 250)
-
+    jasmine.clock().tick(250)
+    fx.step()
+    ctm = rect.ctm()
+    expect(ctm.a).toBeLessThan(1)
+    expect(ctm.b).toBeGreaterThan(0)
+    expect(ctm.c).toBeLessThan(0)
+    expect(ctm.d).toBeGreaterThan(0)
+    expect(ctm.e).toBeGreaterThan(0)
+    expect(ctm.f).toBeGreaterThan(0)
+
+    jasmine.clock().tick(250)
+    fx.step()
+    expect(called).toBe(true)
   })
 
 })
index 58272c40898b865fbedeb986739b873959175c27..0746ba12c363368b4f008dead82481f29225d3df 100644 (file)
--- a/src/fx.js
+++ b/src/fx.js
@@ -25,7 +25,9 @@ SVG.Situation = SVG.invent({
     this.finish = this.start + this.duration
     this.ease = o.ease
 
-    this.loop = false
+    // this.loop is incremented from 0 to this.loops
+    // it is also incremented when in an infinite loop (when this.loops is true)
+    this.loop = 0
     this.loops = false
 
     this.animations = {
@@ -71,6 +73,9 @@ SVG.FX = SVG.invent({
     this.paused = false
     this.lastPos = 0
     this.pos = 0
+    // The absolute position of an animation is its position in the context of its complete duration (including delay and loops)
+    // When performing a delay, absPos is below 0 and when performing a loop, its value is above 1
+    this.absPos = 0
     this._speed = 1
   }
 
@@ -127,14 +132,14 @@ SVG.FX = SVG.invent({
       return this._target
     }
 
-    // returns the position at a given time
-  , timeToPos: function(timestamp){
+    // returns the absolute position at a given time
+  , timeToAbsPos: function(timestamp){
       return (timestamp - this.situation.start) / (this.situation.duration/this._speed)
     }
 
-    // returns the timestamp from a given positon
-  , posToTime: function(pos){
-      return this.situation.duration/this._speed * pos + this.situation.start
+    // returns the timestamp from a given absolute positon
+  , absPosToTime: function(absPos){
+      return this.situation.duration/this._speed * absPos + this.situation.start
     }
 
     // starts the animationloop
@@ -152,7 +157,7 @@ SVG.FX = SVG.invent({
   , start: function(){
       // dont start if already started
       if(!this.active && this.situation){
-        this.situation.start = +new Date + this.situation.delay
+        this.situation.start = +new Date + this.situation.delay/this._speed
         this.situation.finish = this.situation.start + this.situation.duration/this._speed
 
         this.initAnimations()
@@ -192,7 +197,7 @@ SVG.FX = SVG.invent({
 
         var fn = function(){
           if(this.situation instanceof SVG.Situation)
-            this.initAnimations().at(0)
+            this.initAnimations().atStart()
           else if(this.situation instanceof SVG.Delay)
             this.dequeue()
           else
@@ -283,15 +288,7 @@ SVG.FX = SVG.invent({
       this.active = false
 
       if(jumpToEnd && this.situation){
-
-        this.situation.loop = false
-
-        if(this.situation.loops % 2 == 0 && this.situation.reversing){
-          this.situation.reversed = true
-        }
-
-        this.at(1)
-
+        this.atEnd()
       }
 
       this.stopAnimFrame()
@@ -308,7 +305,7 @@ SVG.FX = SVG.invent({
         var temp = this.situation
         this.stop()
         this.situation = temp
-        this.at(0)
+        this.atStart()
       }
       return this
     }
@@ -325,13 +322,40 @@ SVG.FX = SVG.invent({
       return this
     }
 
+    // set the internal animation pointer at the start position, before any loops, and updates the visualisation
+  , atStart: function() {
+    return this.at(0, true)
+  }
+
+    // set the internal animation pointer at the end position, after all the loops, and updates the visualisation
+  , atEnd: function() {
+    if (this.situation.loops === true) {
+      // If in a infinite loop, we end the current iteration
+      return this.at(this.situation.loop+1, true)
+    } else if(typeof this.situation.loops == 'number') {
+      // If performing a finite number of loops, we go after all the loops
+      return this.at(this.situation.loops, true)
+    } else {
+      // If no loops, we just go at the end
+      return this.at(1, true)
+    }
+  }
+
     // set the internal animation pointer to the specified position and updates the visualisation
-  , at: function(pos){
+    // if isAbsPos is true, pos is treated as an absolute position
+  , at: function(pos, isAbsPos){
       var durDivSpd = this.situation.duration/this._speed
 
-      this.pos = pos
-      this.situation.start = +new Date - pos * durDivSpd
+      this.absPos = pos
+      // If pos is not an absolute position, we convert it into one
+      if (!isAbsPos) {
+        if (this.situation.reversed) this.absPos = 1 - this.absPos
+        this.absPos += this.situation.loop
+      }
+
+      this.situation.start = +new Date - this.absPos * durDivSpd
       this.situation.finish = this.situation.start + durDivSpd
+
       return this.step(true)
     }
 
@@ -345,7 +369,8 @@ SVG.FX = SVG.invent({
 
       if (speed) {
         this._speed = speed
-        return this.at(this.situation.reversed ? 1-this.pos : this.pos)
+        // We use an absolute position here so that speed can affect the delay before the animation
+        return this.at(this.absPos, true)
       } else return this._speed
     }
 
@@ -353,8 +378,9 @@ SVG.FX = SVG.invent({
   , loop: function(times, reverse) {
       var c = this.last()
 
-      // store current loop and total loops
-      c.loop = c.loops = times || true
+      // store total loops
+      c.loops = (times != null) ? times : true
+      c.loop = 0
 
       if(reverse) c.reversing = true
       return this
@@ -372,7 +398,8 @@ SVG.FX = SVG.invent({
   , play: function(){
       if(!this.paused) return this
       this.paused = false
-      return this.at(this.pos)
+      // We use an absolute position here so that the delay before the animation can be paused
+      return this.at(this.absPos, true)
     }
 
     /**
@@ -477,22 +504,45 @@ SVG.FX = SVG.invent({
      */
   , step: function(ignoreTime){
 
-      // convert current time to position
-      if(!ignoreTime) this.pos = this.timeToPos(+new Date)
-
-      if(this.pos >= 1 && (this.situation.loop === true || (typeof this.situation.loop == 'number' && --this.situation.loop))){
+      // convert current time to an absolute position
+      if(!ignoreTime) this.absPos = this.timeToAbsPos(+new Date)
+
+      // This part convert an absolute position to a position
+      if(this.situation.loops !== false) {
+        var absPos, absPosInt, lastLoop
+
+        // If the absolute position is below 0, we just treat it as if it was 0
+        absPos = Math.max(this.absPos, 0)
+        absPosInt = Math.floor(absPos)
+
+        if(this.situation.loops === true || absPosInt < this.situation.loops) {
+          this.pos = absPos - absPosInt
+          lastLoop = this.situation.loop
+          this.situation.loop = absPosInt
+        } else {
+          this.absPos = this.situation.loops
+          this.pos = 1
+          // The -1 here is because we don't want to toggle reversed when all the loops have been completed
+          lastLoop = this.situation.loop - 1
+          this.situation.loop = this.situation.loops
+        }
 
-        if(this.situation.reversing){
-          this.situation.reversed = !this.situation.reversed
+        if(this.situation.reversing) {
+          // Toggle reversed if an odd number of loops as occured since the last call of step
+          this.situation.reversed = this.situation.reversed != Boolean((this.situation.loop - lastLoop) % 2)
         }
-        return this.at(this.pos-1)
+
+      } else {
+        // If there are no loop, the absolute position must not be above 1
+        this.absPos = Math.min(this.absPos, 1)
+        this.pos = this.absPos
       }
 
+      // while the absolute position can be below 0, the position must not be below 0
+      if(this.pos < 0) this.pos = 0
+
       if(this.situation.reversed) this.pos = 1 - this.pos
 
-      // correct position
-      if(this.pos > 1)this.pos = 1
-      if(this.pos < 0)this.pos = 0
 
       // apply easing
       var eased = this.situation.ease(this.pos)