]> source.dussan.org Git - svg.js.git/commitdiff
Added the SVG.FX module for animations
authorwout <wout@impinc.co.uk>
Thu, 3 Jan 2013 20:15:32 +0000 (21:15 +0100)
committerwout <wout@impinc.co.uk>
Thu, 3 Jan 2013 20:15:32 +0000 (21:15 +0100)
README.md
Rakefile
dist/svg.js
dist/svg.min.js
src/element.js
src/fx.js [new file with mode: 0644]
src/sugar.js

index f5b929430bb06c4ab8ddd0c5b13930fa73e6f169..6cf54acb358d0320bb931148d86fe745fdca4896 100644 (file)
--- a/README.md
+++ b/README.md
@@ -1,9 +1,10 @@
 # svg.js
 
-A lightweight (3.5k gzipped) library for manipulating SVG.
+A lightweight library for manipulating SVG.
 
 Svg.js is licensed under the terms of the MIT License.
 
+The base library is 2.7k gzipped, with all bells and whistles just under 5k.
 
 ## Usage
 
@@ -267,7 +268,82 @@ draw.each(function(i, children) {
 ```
 
 
+## Animating elements
+
+Animating elements is very much the same as manipulating elements, the only difference is you have to include the `animate()` method:
+
+```javascript
+rect.animate().move(150, 150);
+```
+
+The `animate()` method will take two arguments. The first is `milliseconds`, the second `ease`:
+
+```javascript
+rect.animate(2000, '>').fill({ color: '#f03' });
+```
+
+By default `milliseconds` will be set to `1000`, `ease` will be set to `<>`. All available ease types are:
+
+- `<>`: ease in and out
+- `>`: ease out
+- `<`: ease in
+- `-`: linear
+- a function
+
+For the latter, here is an example of the default `<>` function:
+
+```javascript
+function(pos) { return (-Math.cos(pos * Math.PI) / 2) + 0.5; };
+```
+
+Note that the `animate()` method will not return the targeted element but an instance of SVG.FX which will take the following methods:
+
+Of course `attr()`:
+```javascript
+rect.animate().attr({ fill: '#f03' });
+```
+
+The `move()` method:
+```javascript
+rect.animate().move(100, 100);
+```
+
+And the `center()` method:
+```javascript
+rect.animate().center(200, 200);
+```
+
+If you include the sugar.js module, `fill()`, `stroke()`, `animate()` and `skwe()` will be available as well:
+```javascript
+rect.animate().fill({ color: '#f03' }).stroke({ width: 10 });
+```
+
+Animations can be stopped in to ways.
+
+By calling the `stop()` method:
+```javascript
+var rectfx = rect.animate().move(200, 200);
+
+rectfx.stop();
+```
+
+Or by invoking another animation:
+```javascript
+var rectfx = rect.animate().move(200, 200);
+
+rect.animate().center(200, 200);
+```
+
+Finally, you can add callback methods using `after()`:
+```javascript
+rect.animate(3000).move(100, 100).after(function() {
+  this.animate().fill({ color: '#f06' });
+});
+```
+
+
 ## Syntax sugar
+
 Fill and stroke are used quite often. Therefore two convenience methods are provided:
 
 ### Fill
@@ -291,13 +367,6 @@ The `rotate()` method will automatically rotate elements according to the center
 rect.rotate(45);
 ```
 
-Unless you also define a rotation point:
-
-```javascript
-// rotate(degrees, cx, cy)
-rect.rotate(45, 100, 100);
-```
-
 ### Skew
 The `skew()` method will take an `x` and `y` value:
 
@@ -556,7 +625,7 @@ SVG.extend(SVG.Doc, {
 
 
 ## Building
-Starting out with the default distribution of svg.js is good. Although you might want to remove some modules to keep the size at minimum. 
+Starting out with the default distribution of svg.js is good. Although you might want to remove some modules to keep the size at minimum.
 
 You will need ruby, RubyGems, and rake installed on your system.
 
@@ -570,13 +639,13 @@ $ rake -V
 $ gem install uglifier
 ```
 
-Build Zepto by running `rake`:
+Build svg.js by running `rake`:
 
 ``` sh
 $ rake
-Original version: 17.010k
-Minified: 9.083k
-Minified and gzipped: 2.760k, compression factor 6.163
+Original version: 32.165k
+Minified: 14.757k
+Minified and gzipped: 4.413k, compression factor 7.289
 ```
 
 The resulting files are:
@@ -587,10 +656,16 @@ The resulting files are:
 To include optional modules and remove default ones, use the `concat` task. In
 this example, 'clip' is removed, but 'group' and 'arrange' are added:
 
-```
+``` sh
 $ rake concat[-clip:group:arrange] dist
 ```
 
+To build the base library only including shapes:
+
+``` sh
+rake concat[-fx:-event:-group:-arrange:-mask:-gradient:-nested:-sugar] dist
+```
+
 _The Rakefile has been borrowed from [madrobby's](https://github.com/madrobby) [Zepto](https://github.com/madrobby/zepto)_
 
 
index 880e97ec62acf3970c8a3841cabe32f4b00072ab..a237b8c5d6b2c3f968e0f65d5a377719cfdb6585 100644 (file)
--- a/Rakefile
+++ b/Rakefile
@@ -1,7 +1,7 @@
 SVGJS_VERSION = '0.1a'
 
 # all available modules in the correct loading order
-MODULES = %w[ svg container element event group arrange defs mask gradient doc shape wrap rect ellipse poly path image text nested sugar ]
+MODULES = %w[ svg container element fx event group arrange defs mask gradient doc shape wrap rect ellipse poly path image text nested sugar ]
 
 # how many bytes in a "kilobyte"
 KILO = 1024
index c7943c27333866562fe553cc8f2921de306504a9..016a5682ee4f43e9f85439954d73ac6d48c1a14c 100644 (file)
@@ -1,4 +1,4 @@
-/* svg.js v0.1-54-g88987d6 - svg container element event group arrange defs mask gradient doc shape wrap rect ellipse poly path image text nested sugar - svgjs.com/license */
+/* svg.js v0.1-55-g0da8f1d - svg container element fx event group arrange defs mask gradient doc shape wrap rect ellipse poly path image text nested sugar - svgjs.com/license */
 (function() {
 
   this.SVG = {
     this.node = n;
     
     // initialize attribute store
-    this.attrs = {};
+    this.attrs = {
+      'fill-opacity':   1,
+      'stroke-opacity': 1,
+      'stroke-width':   0
+    };
     
-    // initialize transformations store
+    // initialize transformation store
     this.trans = {
       x:        0,
       y:        0,
   });
 
 
+  SVG.FX = function FX(e) {
+    // store target element
+    this.target = e;
+  };
+  
+  // add FX methods
+  SVG.extend(SVG.FX, {
+    
+    // animation parameters and animate
+    animate: function(duration, ease) {
+      // ensure default duration adn easing
+      duration = duration || 1000;
+      ease = ease || '<>';
+      
+      var akeys, tkeys, tvalues,
+          element   = this.target,
+          fx        = this,
+          start     = (new Date).getTime(),
+          finish    = start + duration;
+      
+      // start animation
+      this.interval = setInterval(function(){
+        var i,
+            time = (new Date).getTime(),
+            pos = time > finish ? 1 : (time - start) / duration;
+        
+        // collect attribute keys
+        if (akeys == null) {
+          akeys = [];
+          for (var k in fx.attrs)
+            akeys.push(k);
+        };
+        
+        // collect transformation keys
+        if (tkeys == null) {
+          tkeys = [];
+          for (var k in fx.trans)
+            tkeys.push(k);
+          
+          tvalues = {};
+        };
+        
+        // apply easing
+        pos = ease == '<>' ?
+          (-Math.cos(pos * Math.PI) / 2) + 0.5 :
+        ease == '>' ?
+          Math.sin(pos * Math.PI / 2) :
+        ease == '<' ?
+          -Math.cos(pos * Math.PI / 2) + 1 :
+        ease == '-' ?
+          pos :
+        typeof ease == 'function' ?
+          ease(pos) :
+          pos;
+        
+        // run all position properties
+        if (fx._move)
+          element.move(fx._at(fx._move.x, pos), fx._at(fx._move.y, pos));
+        else if (fx._center)
+          element.move(fx._at(fx._center.x, pos), fx._at(fx._center.y, pos));
+        
+        // run all size properties
+        if (fx._size)
+          element.size(fx._at(fx._size.width, pos), fx._at(fx._size.height, pos));
+        
+        // animate attributes
+        for (i = akeys.length - 1; i >= 0; i--)
+          element.attr(akeys[i], fx._at(fx.attrs[akeys[i]], pos));
+        
+        // animate transformations
+        if (tkeys.length > 0) {
+          for (i = tkeys.length - 1; i >= 0; i--)
+            tvalues[tkeys[i]] = fx._at(fx.trans[tkeys[i]], pos);
+          
+          element.transform(tvalues);
+        }
+        
+        // finish off animation
+        if (time > finish) {
+          clearInterval(fx.interval);
+          fx._after ? fx._after.apply(element) : fx.stop();
+        }
+          
+      }, 10);
+      
+      return this;
+    },
+    
+    // animated attributes
+    attr: function(a, v, n) {
+      if (typeof a == 'object')
+        for (var k in a)
+          this.attr(k, a[k]);
+      
+      else
+        this.attrs[a] = { from: this.target.attr(a), to: v };
+    },
+    
+    // animated transformations
+    transform: function(o) {
+      for (var k in o)
+        this.trans[k] = { from: this.target.trans[k], to: o[k] };
+      
+      return this;
+    },
+    
+    // animated move
+    move: function(x, y) {
+      var b = this.target.bbox();
+      
+      this._move = {
+        x: { from: b.x, to: x },
+        y: { from: b.y, to: y }
+      };
+      
+      return this;
+    },
+    
+    // animated size
+    size: function(w, h) {
+      var b = this.target.bbox();
+      
+      this._size = {
+        width:  { from: b.width,  to: w },
+        height: { from: b.height, to: h }
+      };
+      
+      return this;
+    },
+    
+    // animated center
+    center: function(x, y) {
+      var b = this.target.bbox();
+      
+      this._move = {
+        x: { from: b.cx, to: x },
+        y: { from: b.cy, to: y }
+      };
+      
+      return this;
+    },
+    
+    // stop animation
+    stop: function() {
+      // stop current animation
+      clearInterval(this.interval);
+      
+      // create / reset storage for properties that need animation
+      this.attrs  = {};
+      this.trans  = {};
+      this._move  = null;
+      this._size  = null;
+      this._after = null;
+      
+      return this;
+    },
+    
+    // private: at position according to from and to
+    _at: function(o, p) {
+      // if a number, calculate pos
+      return typeof o.from == 'number' ?
+        o.from + (o.to - o.from) * p :
+      
+      // if animating to a color
+      o.to.r || /^#/.test(o.to) ?
+        this._color(o, p) :
+      
+      // for all other values wait until pos has reached 1 to return the final value
+      p < 1 ? o.from : o.to;
+    },
+    
+    // private: tween color
+    _color: function(o, p) {
+      // convert hex to rgb and store it for further reference
+      if (typeof o.from !== 'object')
+        o.from = this._h2r(o.from || '#000');
+      
+      // convert hex to rgb and store it for further reference
+      if (typeof o.to !== 'object')
+        o.to = this._h2r(o.to);
+      
+      // tween color and return hex
+      return this._r2h({
+        r: ~~(o.from.r + (o.to.r - o.from.r) * p),
+        g: ~~(o.from.g + (o.to.g - o.from.g) * p),
+        b: ~~(o.from.b + (o.to.b - o.from.b) * p)
+      });
+    },
+    
+    // private: convert hex to rgb object
+    _h2r: function(h) {
+      // parse full hex
+      var m = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(this._fh(h));
+      
+      // if the hex is successfully parsed, return it in rgb, otherwise return black
+      return m ? {
+        r: parseInt(m[1], 16),
+        g: parseInt(m[2], 16),
+        b: parseInt(m[3], 16)
+      } : { r: 0, g: 0, b: 0 };
+    },
+    
+    // private: convert rgb object to hex string
+    _r2h: function(r) {
+      return '#' + (r.r + 256 * r.g + 65536 * r.b).toString(16);
+    },
+    
+    // private: force potential 3-based hex to 6-based 
+    _fh: function(h) {
+      return h.length == 4 ?
+        [ '#',
+          h.substring(1, 2), h.substring(1, 2),
+          h.substring(2, 3), h.substring(2, 3),
+          h.substring(3, 4), h.substring(3, 4)
+        ].join('') : h;
+    }
+    
+  });
+  
+  
+  // delay: delay animation for a given amount of ms
+  // after: callback for when animation has finished
+  ['delay', 'after'].forEach(function(m) {
+    SVG.FX.prototype[m] = function(v) {
+      this['_' + m] = v;
+      
+      return this;
+    };
+  });
+  
+  
+  // make SVG.Element FX-aware
+  SVG.extend(SVG.Element, {
+    
+    // get fx module or create a new one, then animate with given ms and ease
+    animate: function(d, e) {
+      return (this._fx || (this._fx = new SVG.FX(this))).stop().animate(d, e);
+    },
+    
+    // stop current animation
+    stop: function() {
+      this._fx.stop();
+      
+      return this;
+    }
+    
+  });
+  
+
+
   [ 'click',
     'dblclick',
     'mousedown',
   SVG.extend(SVG.Element, {
     
     // rotation
-    rotate: function(d, x, y) {
-      var b = this.bbox();
-      
+    rotate: function(d) {
       return this.transform({
-        rotation: d || 0,
-        cx:       x == null ? b.cx : x,
-        cy:       y == null ? b.cx : y
+        rotation: d || 0
       });
     },
     
     
   });
   
+  // add methods to SVG.FX
+  if (SVG.FX) {
+    // add sugar for fill and stroke
+    ['fill', 'stroke'].forEach(function(m) {
+      SVG.FX.prototype[m] = function(o) {
+        var a, k;
+  
+        for (k in o) {
+          a = k == 'color' ? m : m + '-' + k;
+          this.attrs[a] = {
+            from: this.target.attrs[a],
+            to:   o[k]
+          };
+        };
+  
+        return this;
+      };
+    });
+    
+    SVG.extend(SVG.FX, {
+  
+      // rotation
+      rotate: function(d) {
+        return this.transform({
+          rotation: d || 0
+        });
+      },
+  
+      // skew
+      skew: function(x, y) {
+        return this.transform({
+          skewX: x || 0,
+          skewY: y || 0
+        });
+      }
+  
+    });
+  }
+  
   
   
 
index 9654c545cbdb2a5b1c72481e8097b4c7481cb183..e7b3442cdae8227add0a0da1a98191064bebf345 100644 (file)
@@ -1,2 +1,2 @@
-/* svg.js v0.1-54-g88987d6 - svg container element event group arrange defs mask gradient doc shape wrap rect ellipse poly path image text nested sugar - svgjs.com/license */
-function svg(e){return new SVG.Doc(e)}(function(){function e(e){this.constructor.call(this,SVG.create("g")),this.node.insertBefore(e.node,null),this.child=e}function n(){this.constructor.call(this,SVG.create("tspan"))}this.SVG={ns:"http://www.w3.org/2000/svg",xlink:"http://www.w3.org/1999/xlink",did:0,create:function(e){return document.createElementNS(this.ns,e)},extend:function(e,t){for(var n in t)e.prototype[n]=t[n]}},SVG.Container={add:function(e,t){return this.has(e)||(t=t==null?this.children().length:t,this.children().splice(t,0,e),this.node.insertBefore(e.node,this.node.childNodes[t]||null),e.parent=this),this},put:function(e,t){return this.add(e,t),e},has:function(e){return this.children().indexOf(e)>=0},children:function(){return this._children||(this._children=[])},each:function(e){var t,n=this.children();for(t=0,l=n.length;t<l;t++)n[t]instanceof SVG.Shape&&e.apply(n[t],[t,n]);return this},remove:function(e){return this.removeAt(this.children().indexOf(e))},removeAt:function(e){if(0<=e&&e<this.children().length){var t=this.children()[e];this.children().splice(e,1),this.node.removeChild(t.node),t.parent=null}return this},defs:function(){return this._defs||(this._defs=this.put(new SVG.Defs,0))},level:function(){return this.remove(d).put(this.defs(),0)},group:function(){return this.put(new SVG.G)},rect:function(e,t){return this.put((new SVG.Rect).size(e,t))},circle:function(e){return this.ellipse(e)},ellipse:function(e,t){return this.put((new SVG.Ellipse).size(e,t))},polyline:function(t){return this.put(new e(new SVG.Polyline)).plot(t)},polygon:function(t){return this.put(new e(new SVG.Polygon)).plot(t)},path:function(t){return this.put(new e(new SVG.Path)).plot(t)},image:function(e,t,n){return t=t!=null?t:100,this.put((new SVG.Image).load(e).size(t,n!=null?n:t))},text:function(e){return this.put((new SVG.Text).text(e))},nested:function(e){return this.put(new SVG.Nested)},gradient:function(e,t){return this.defs().gradient(e,t)},mask:function(){return this.defs().put(new SVG.Mask)},first:function(){return this.children()[1]},last:function(){return this.children()[this.children().length-1]},clear:function(){this._children=[];while(this.node.hasChildNodes())this.node.removeChild(this.node.lastChild);return this},stage:function(){var e,t=this;return e=function(){document.readyState==="complete"?(t.attr("style","position:absolute;"),setTimeout(function(){t.attr("style","position:relative;")},5)):setTimeout(e,10)},e(),this}},SVG.Element=function(t){this.node=t,this.attrs={},this.trans={x:0,y:0,scaleX:1,scaleY:1,rotation:0,skewX:0,skewY:0}},SVG.extend(SVG.Element,{move:function(e,t){return this.attr({x:e,y:t})},size:function(e,t){return this.attr({width:e,height:t})},center:function(e,t){var n=this.bbox();return this.move(e-n.width/2,t-n.height/2)},clone:function(){var t;if(this instanceof e)t=this.parent[this.child.node.nodeName](),t.child.trans=this.child.trans,t.child.attr(this.child.attrs).transform({});else{var n=this.node.nodeName;t=n=="rect"?this.parent[n](this.attrs.width,this.attrs.height):n=="ellipse"?this.parent[n](this.attrs.rx*2,this.attrs.ry*2):n=="image"?this.parent[n](this.src):n=="text"?this.parent[n](this.content):n=="g"?this.parent.group():this.parent[n]()}return t.trans=this.trans,t.attr(this.attrs).transform({})},remove:function(){return this.parent!=null?this.parent.remove(this):void 0},parentDoc:function(){return this._parent(SVG.Doc)},attr:function(e,t,n){if(arguments.length<2){if(typeof e!="object")return this._isStyle(e)?e=="text"?this.content:e=="leading"?this[e]:this.style[e]:this.attrs[e];for(t in e)this.attr(t,e[t])}else{this.attrs[e]=t;if(e=="x"&&this._isText())for(var r=this.lines.length-1;r>=0;r--)this.lines[r].attr(e,t);else n!=null?this.node.setAttributeNS(n,e,t):this.node.setAttribute(e,t);this._isStyle(e)&&(e=="text"?this.text(t):e=="leading"?this[e]=t:this.style[e]=t,this.text(this.content))}return this},transform:function(e){if(typeof e=="string")return this.trans[e];var t,n=[],r=this.bbox(),i=this.attr("transform")||"",s=i.match(/[a-z]+\([^\)]+\)/g)||[];for(t in e)e[t]!=null&&(this.trans[t]=e[t]);return e=this.trans,e.rotation!=0&&n.push("rotate("+e.rotation+","+(e.cx!=null?e.cx:r.cx)+","+(e.cy!=null?e.cy:r.cy)+")"),n.push("scale("+e.scaleX+","+e.scaleY+")"),e.skewX!=0&&n.push("skewX("+e.skewX+")"),e.skewY!=0&&n.push("skewY("+e.skewY+")"),n.push("translate("+e.x+","+e.y+")"),this.attr("transform",n.join(" "))},bbox:function(){var e=this.node.getBBox();return{x:e.x+this.trans.x,y:e.y+this.trans.y,cx:e.x+this.trans.x+e.width/2,cy:e.y+this.trans.y+e.height/2,width:e.width,height:e.height}},inside:function(e,t){var n=this.bbox();return e>n.x&&t>n.y&&e<n.x+n.width&&t<n.y+n.height},show:function(){return this.node.style.display="",this},hide:function(){return this.node.style.display="none",this},_parent:function(e){var t=this;while(t!=null&&!(t instanceof e))t=t.parent;return t},_isStyle:function(e){return typeof e=="string"&&this._isText()?/^font|text|leading/.test(e):!1},_isText:function(){return this instanceof SVG.Text}}),["click","dblclick","mousedown","mouseup","mouseover","mouseout","mousemove","touchstart","touchend","touchmove","touchcancel"].forEach(function(e){SVG.Element.prototype[e]=function(t){var n=this;return this.node["on"+e]=function(){return t.apply(n,arguments)},this}}),SVG.G=function(){this.constructor.call(this,SVG.create("g"))},SVG.G.prototype=new SVG.Element,SVG.extend(SVG.G,SVG.Container),SVG.extend(SVG.G,{defs:function(){return this.parentDoc().defs()}}),SVG.extend(SVG.Element,{siblings:function(){return this.parent.children()},position:function(){return this.siblings().indexOf(this)},next:function(){return this.siblings()[this.position()+1]},previous:function(){return this.siblings()[this.position()-1]},forward:function(){return this.parent.remove(this).put(this,this.position()+1)},backward:function(){var e,t=this.parent.level();return e=this.position(),e>1&&t.remove(this).add(this,e-1),this},front:function(){return this.parent.remove(this).put(this)},back:function(){var e=this.parent.level();return this.position()>1&&e.remove(this).add(this,0),this}}),SVG.Defs=function(){this.constructor.call(this,SVG.create("defs"))},SVG.Defs.prototype=new SVG.Element,SVG.extend(SVG.Defs,SVG.Container),SVG.Mask=function(){this.constructor.call(this,SVG.create("mask")),this.id="svgjs_"+SVG.did++,this.attr("id",this.id)},SVG.Mask.prototype=new SVG.Element,SVG.extend(SVG.Mask,SVG.Container),SVG.extend(SVG.Element,{maskWith:function(e){return this.attr("mask","url(#"+(e instanceof SVG.Mask?e:this.parent.mask().add(e)).id+")")}}),SVG.Gradient=function(t){this.constructor.call(this,SVG.create(t+"Gradient")),this.id="svgjs_"+SVG.did++,this.attr("id",this.id),this.type=t},SVG.Gradient.prototype=new SVG.Element,SVG.extend(SVG.Gradient,SVG.Container),SVG.extend(SVG.Gradient,{from:function(e,t){return this.type=="radial"?this.attr({fx:e+"%",fy:t+"%"}):this.attr({x1:e+"%",y1:t+"%"})},to:function(e,t){return this.type=="radial"?this.attr({cx:e+"%",cy:t+"%"}):this.attr({x2:e+"%",y2:t+"%"})},radius:function(e){return this.type=="radial"?this.attr({r:e+"%"}):this},at:function(e){return this.put(new SVG.Stop(e))},update:function(e){while(this.node.hasChildNodes())this.node.removeChild(this.node.lastChild);return e(this),this},fill:function(){return"url(#"+this.id+")"}}),SVG.extend(SVG.Defs,{gradient:function(e,t){var n=this.put(new SVG.Gradient(e));return t(n),n}}),SVG.Stop=function(t){this.constructor.call(this,SVG.create("stop")),this.update(t)},SVG.Stop.prototype=new SVG.Element,SVG.extend(SVG.Stop,{update:function(e){var t,n="",r=["opacity","color"];for(t=r.length-1;t>=0;t--)e[r[t]]!=null&&(n+="stop-"+r[t]+":"+e[r[t]]+";");return this.attr({offset:(e.offset!=null?e.offset:this.attrs.offset||0)+"%",style:n})}}),SVG.Doc=function(t){this.constructor.call(this,SVG.create("svg"));var n=document.createElement("div");n.style.cssText="position:relative;width:100%;height:100%;",typeof t=="string"&&(t=document.getElementById(t)),this.attr({xmlns:SVG.ns,version:"1.1",width:"100%",height:"100%"}).attr("xlink",SVG.xlink,SVG.ns).defs(),t.appendChild(n),n.appendChild(this.node),this.stage()},SVG.Doc.prototype=new SVG.Element,SVG.extend(SVG.Doc,SVG.Container),SVG.Shape=function(t){this.constructor.call(this,t)},SVG.Shape.prototype=new SVG.Element,e.prototype=new SVG.Shape,SVG.extend(e,{move:function(e,t){return this.center(e+this._b.width*this.child.trans.scaleX/2,t+this._b.height*this.child.trans.scaleY/2)},size:function(e,t){var n=e/this._b.width;return this.child.transform({scaleX:n,scaleY:t!=null?t/this._b.height:n}),this},center:function(e,t){return this.transform({x:e,y:t})},attr:function(e,t,n){if(typeof e=="object")for(t in e)this.attr(t,e[t]);else{if(arguments.length<2)return e=="transform"?this.attrs[e]:this.child.attrs[e];e=="transform"?(this.attrs[e]=t,n!=null?this.node.setAttributeNS(n,e,t):this.node.setAttribute(e,t)):this.child.attr(e,t,n)}return this},plot:function(e){return this.child.plot(e),this._b=this.child.bbox(),this.child.transform({x:-this._b.cx,y:-this._b.cy}),this}}),SVG.Rect=function(){this.constructor.call(this,SVG.create("rect"))},SVG.Rect.prototype=new SVG.Shape,SVG.Ellipse=function(){this.constructor.call(this,SVG.create("ellipse"))},SVG.Ellipse.prototype=new SVG.Shape,SVG.extend(SVG.Ellipse,{move:function(e,t){return this.attrs.x=e,this.attrs.y=t,this.center()},size:function(e,t){return this.attr({rx:e/2,ry:(t!=null?t:e)/2}).center()},center:function(e,t){return this.attr({cx:e||(this.attrs.x||0)+(this.attrs.rx||0),cy:t||(this.attrs.y||0)+(this.attrs.ry||0)})}}),SVG.Poly={plot:function(e){return this.attr("points",e||"0,0")},move:function(e,t){return this.transform({x:e,y:t})}},SVG.Polyline=function(){this.constructor.call(this,SVG.create("polyline"))},SVG.Polyline.prototype=new SVG.Shape,SVG.extend(SVG.Polyline,SVG.Poly),SVG.Polygon=function(){this.constructor.call(this,SVG.create("polygon"))},SVG.Polygon.prototype=new SVG.Shape,SVG.extend(SVG.Polygon,SVG.Poly),SVG.Path=function(){this.constructor.call(this,SVG.create("path"))},SVG.Path.prototype=new SVG.Shape,SVG.extend(SVG.Path,{move:function(e,t){this.transform({x:e,y:t})},plot:function(e){return this.attr("d",e||"M0,0")}}),SVG.Image=function(){this.constructor.call(this,SVG.create("image"))},SVG.Image.prototype=new SVG.Shape,SVG.extend(SVG.Image,{load:function(e){return this.src=e,e?this.attr("xlink:href",e,SVG.xlink):this}});var t=["size","family","weight","stretch","variant","style"];SVG.Text=function(){this.constructor.call(this,SVG.create("text")),this.style={"font-size":16,"font-family":"Helvetica","text-anchor":"start"},this.leading=1.2},SVG.Text.prototype=new SVG.Shape,SVG.extend(SVG.Text,{text:function(e){this.content=e=e||"text",this.lines=[];var t,r,i=this._style(),s=this.parentDoc(),o=e.split("\n"),u=this.style["font-size"];while(this.node.hasChildNodes())this.node.removeChild(this.node.lastChild);for(t=0,l=o.length;t<l;t++)r=(new n).text(o[t]).attr({dy:u*this.leading-(t==0?u*.3:0),x:this.attrs.x||0,style:i}),this.node.appendChild(r.node),this.lines.push(r);return this.attr("style",i)},_style:function(){var e,n="";for(e=t.length-1;e>=0;e--)this.style["font-"+t[e]]!=null&&(n+="font-"+t[e]+":"+this.style["font-"+t[e]]+";");return n+="text-anchor:"+this.style["text-anchor"]+";",n}}),n.prototype=new SVG.Shape,SVG.extend(n,{text:function(e){return this.node.appendChild(document.createTextNode(e)),this}}),SVG.Nested=function(){this.constructor.call(this,SVG.create("svg")),this.attr("overflow","visible")},SVG.Nested.prototype=new SVG.Element,SVG.extend(SVG.Nested,SVG.Container);var r=["width","opacity","linecap","linejoin","miterlimit","dasharray","dashoffset"],i=["opacity","rule"];SVG.extend(SVG.Shape,{fill:function(e){var t;e.color!=null&&this.attr("fill",e.color);for(t=i.length-1;t>=0;t--)e[i[t]]!=null&&this.attr("fill-"+i[t],e[i[t]]);return this},stroke:function(e){var t;e.color&&this.attr("stroke",e.color);for(t=r.length-1;t>=0;t--)e[r[t]]!=null&&this.attr("stroke-"+r[t],e[r[t]]);return this}}),SVG.extend(SVG.Element,{rotate:function(e,t,n){var r=this.bbox();return this.transform({rotation:e||0,cx:t==null?r.cx:t,cy:n==null?r.cx:n})},skew:function(e,t){return this.transform({skewX:e||0,skewY:t||0})}}),SVG.extend(SVG.G,{move:function(e,t){return this.transform({x:e,y:t})}}),SVG.extend(SVG.Text,{font:function(e){var n,r={};for(n in e)n=="leading"?r[n]=e[n]:n=="anchor"?r["text-anchor"]=e[n]:t.indexOf(n)>-1?r["font-"+n]=e[n]:void 0;return this.attr(r).text(this.content)}})}).call(this);
\ No newline at end of file
+/* svg.js v0.1-55-g0da8f1d - svg container element fx event group arrange defs mask gradient doc shape wrap rect ellipse poly path image text nested sugar - svgjs.com/license */
+function svg(e){return new SVG.Doc(e)}(function(){function e(e){this.constructor.call(this,SVG.create("g")),this.node.insertBefore(e.node,null),this.child=e}function n(){this.constructor.call(this,SVG.create("tspan"))}this.SVG={ns:"http://www.w3.org/2000/svg",xlink:"http://www.w3.org/1999/xlink",did:0,create:function(e){return document.createElementNS(this.ns,e)},extend:function(e,t){for(var n in t)e.prototype[n]=t[n]}},SVG.Container={add:function(e,t){return this.has(e)||(t=t==null?this.children().length:t,this.children().splice(t,0,e),this.node.insertBefore(e.node,this.node.childNodes[t]||null),e.parent=this),this},put:function(e,t){return this.add(e,t),e},has:function(e){return this.children().indexOf(e)>=0},children:function(){return this._children||(this._children=[])},each:function(e){var t,n=this.children();for(t=0,l=n.length;t<l;t++)n[t]instanceof SVG.Shape&&e.apply(n[t],[t,n]);return this},remove:function(e){return this.removeAt(this.children().indexOf(e))},removeAt:function(e){if(0<=e&&e<this.children().length){var t=this.children()[e];this.children().splice(e,1),this.node.removeChild(t.node),t.parent=null}return this},defs:function(){return this._defs||(this._defs=this.put(new SVG.Defs,0))},level:function(){return this.remove(d).put(this.defs(),0)},group:function(){return this.put(new SVG.G)},rect:function(e,t){return this.put((new SVG.Rect).size(e,t))},circle:function(e){return this.ellipse(e)},ellipse:function(e,t){return this.put((new SVG.Ellipse).size(e,t))},polyline:function(t){return this.put(new e(new SVG.Polyline)).plot(t)},polygon:function(t){return this.put(new e(new SVG.Polygon)).plot(t)},path:function(t){return this.put(new e(new SVG.Path)).plot(t)},image:function(e,t,n){return t=t!=null?t:100,this.put((new SVG.Image).load(e).size(t,n!=null?n:t))},text:function(e){return this.put((new SVG.Text).text(e))},nested:function(e){return this.put(new SVG.Nested)},gradient:function(e,t){return this.defs().gradient(e,t)},mask:function(){return this.defs().put(new SVG.Mask)},first:function(){return this.children()[1]},last:function(){return this.children()[this.children().length-1]},clear:function(){this._children=[];while(this.node.hasChildNodes())this.node.removeChild(this.node.lastChild);return this},stage:function(){var e,t=this;return e=function(){document.readyState==="complete"?(t.attr("style","position:absolute;"),setTimeout(function(){t.attr("style","position:relative;")},5)):setTimeout(e,10)},e(),this}},SVG.Element=function(t){this.node=t,this.attrs={"fill-opacity":1,"stroke-opacity":1,"stroke-width":0},this.trans={x:0,y:0,scaleX:1,scaleY:1,rotation:0,skewX:0,skewY:0}},SVG.extend(SVG.Element,{move:function(e,t){return this.attr({x:e,y:t})},size:function(e,t){return this.attr({width:e,height:t})},center:function(e,t){var n=this.bbox();return this.move(e-n.width/2,t-n.height/2)},clone:function(){var t;if(this instanceof e)t=this.parent[this.child.node.nodeName](),t.child.trans=this.child.trans,t.child.attr(this.child.attrs).transform({});else{var n=this.node.nodeName;t=n=="rect"?this.parent[n](this.attrs.width,this.attrs.height):n=="ellipse"?this.parent[n](this.attrs.rx*2,this.attrs.ry*2):n=="image"?this.parent[n](this.src):n=="text"?this.parent[n](this.content):n=="g"?this.parent.group():this.parent[n]()}return t.trans=this.trans,t.attr(this.attrs).transform({})},remove:function(){return this.parent!=null?this.parent.remove(this):void 0},parentDoc:function(){return this._parent(SVG.Doc)},attr:function(e,t,n){if(arguments.length<2){if(typeof e!="object")return this._isStyle(e)?e=="text"?this.content:e=="leading"?this[e]:this.style[e]:this.attrs[e];for(t in e)this.attr(t,e[t])}else{this.attrs[e]=t;if(e=="x"&&this._isText())for(var r=this.lines.length-1;r>=0;r--)this.lines[r].attr(e,t);else n!=null?this.node.setAttributeNS(n,e,t):this.node.setAttribute(e,t);this._isStyle(e)&&(e=="text"?this.text(t):e=="leading"?this[e]=t:this.style[e]=t,this.text(this.content))}return this},transform:function(e){if(typeof e=="string")return this.trans[e];var t,n=[],r=this.bbox(),i=this.attr("transform")||"",s=i.match(/[a-z]+\([^\)]+\)/g)||[];for(t in e)e[t]!=null&&(this.trans[t]=e[t]);return e=this.trans,e.rotation!=0&&n.push("rotate("+e.rotation+","+(e.cx!=null?e.cx:r.cx)+","+(e.cy!=null?e.cy:r.cy)+")"),n.push("scale("+e.scaleX+","+e.scaleY+")"),e.skewX!=0&&n.push("skewX("+e.skewX+")"),e.skewY!=0&&n.push("skewY("+e.skewY+")"),n.push("translate("+e.x+","+e.y+")"),this.attr("transform",n.join(" "))},bbox:function(){var e=this.node.getBBox();return{x:e.x+this.trans.x,y:e.y+this.trans.y,cx:e.x+this.trans.x+e.width/2,cy:e.y+this.trans.y+e.height/2,width:e.width,height:e.height}},inside:function(e,t){var n=this.bbox();return e>n.x&&t>n.y&&e<n.x+n.width&&t<n.y+n.height},show:function(){return this.node.style.display="",this},hide:function(){return this.node.style.display="none",this},_parent:function(e){var t=this;while(t!=null&&!(t instanceof e))t=t.parent;return t},_isStyle:function(e){return typeof e=="string"&&this._isText()?/^font|text|leading/.test(e):!1},_isText:function(){return this instanceof SVG.Text}}),SVG.FX=function(t){this.target=t},SVG.extend(SVG.FX,{animate:function(e,t){e=e||1e3,t=t||"<>";var n,r,i,s=this.target,o=this,u=(new Date).getTime(),a=u+e;return this.interval=setInterval(function(){var f,l=(new Date).getTime(),c=l>a?1:(l-u)/e;if(n==null){n=[];for(var h in o.attrs)n.push(h)}if(r==null){r=[];for(var h in o.trans)r.push(h);i={}}c=t=="<>"?-Math.cos(c*Math.PI)/2+.5:t==">"?Math.sin(c*Math.PI/2):t=="<"?-Math.cos(c*Math.PI/2)+1:t=="-"?c:typeof t=="function"?t(c):c,o._move?s.move(o._at(o._move.x,c),o._at(o._move.y,c)):o._center&&s.move(o._at(o._center.x,c),o._at(o._center.y,c)),o._size&&s.size(o._at(o._size.width,c),o._at(o._size.height,c));for(f=n.length-1;f>=0;f--)s.attr(n[f],o._at(o.attrs[n[f]],c));if(r.length>0){for(f=r.length-1;f>=0;f--)i[r[f]]=o._at(o.trans[r[f]],c);s.transform(i)}l>a&&(clearInterval(o.interval),o._after?o._after.apply(s):o.stop())},10),this},attr:function(e,t,n){if(typeof e=="object")for(var r in e)this.attr(r,e[r]);else this.attrs[e]={from:this.target.attr(e),to:t}},transform:function(e){for(var t in e)this.trans[t]={from:this.target.trans[t],to:e[t]};return this},move:function(e,t){var n=this.target.bbox();return this._move={x:{from:n.x,to:e},y:{from:n.y,to:t}},this},size:function(e,t){var n=this.target.bbox();return this._size={width:{from:n.width,to:e},height:{from:n.height,to:t}},this},center:function(e,t){var n=this.target.bbox();return this._move={x:{from:n.cx,to:e},y:{from:n.cy,to:t}},this},stop:function(){return clearInterval(this.interval),this.attrs={},this.trans={},this._move=null,this._size=null,this._after=null,this},_at:function(e,t){return typeof e.from=="number"?e.from+(e.to-e.from)*t:e.to.r||/^#/.test(e.to)?this._color(e,t):t<1?e.from:e.to},_color:function(e,t){return typeof e.from!="object"&&(e.from=this._h2r(e.from||"#000")),typeof e.to!="object"&&(e.to=this._h2r(e.to)),this._r2h({r:~~(e.from.r+(e.to.r-e.from.r)*t),g:~~(e.from.g+(e.to.g-e.from.g)*t),b:~~(e.from.b+(e.to.b-e.from.b)*t)})},_h2r:function(e){var t=/^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(this._fh(e));return t?{r:parseInt(t[1],16),g:parseInt(t[2],16),b:parseInt(t[3],16)}:{r:0,g:0,b:0}},_r2h:function(e){return"#"+(e.r+256*e.g+65536*e.b).toString(16)},_fh:function(e){return e.length==4?["#",e.substring(1,2),e.substring(1,2),e.substring(2,3),e.substring(2,3),e.substring(3,4),e.substring(3,4)].join(""):e}}),["delay","after"].forEach(function(e){SVG.FX.prototype[e]=function(t){return this["_"+e]=t,this}}),SVG.extend(SVG.Element,{animate:function(e,t){return(this._fx||(this._fx=new SVG.FX(this))).stop().animate(e,t)},stop:function(){return this._fx.stop(),this}}),["click","dblclick","mousedown","mouseup","mouseover","mouseout","mousemove","touchstart","touchend","touchmove","touchcancel"].forEach(function(e){SVG.Element.prototype[e]=function(t){var n=this;return this.node["on"+e]=function(){return t.apply(n,arguments)},this}}),SVG.G=function(){this.constructor.call(this,SVG.create("g"))},SVG.G.prototype=new SVG.Element,SVG.extend(SVG.G,SVG.Container),SVG.extend(SVG.G,{defs:function(){return this.parentDoc().defs()}}),SVG.extend(SVG.Element,{siblings:function(){return this.parent.children()},position:function(){return this.siblings().indexOf(this)},next:function(){return this.siblings()[this.position()+1]},previous:function(){return this.siblings()[this.position()-1]},forward:function(){return this.parent.remove(this).put(this,this.position()+1)},backward:function(){var e,t=this.parent.level();return e=this.position(),e>1&&t.remove(this).add(this,e-1),this},front:function(){return this.parent.remove(this).put(this)},back:function(){var e=this.parent.level();return this.position()>1&&e.remove(this).add(this,0),this}}),SVG.Defs=function(){this.constructor.call(this,SVG.create("defs"))},SVG.Defs.prototype=new SVG.Element,SVG.extend(SVG.Defs,SVG.Container),SVG.Mask=function(){this.constructor.call(this,SVG.create("mask")),this.id="svgjs_"+SVG.did++,this.attr("id",this.id)},SVG.Mask.prototype=new SVG.Element,SVG.extend(SVG.Mask,SVG.Container),SVG.extend(SVG.Element,{maskWith:function(e){return this.attr("mask","url(#"+(e instanceof SVG.Mask?e:this.parent.mask().add(e)).id+")")}}),SVG.Gradient=function(t){this.constructor.call(this,SVG.create(t+"Gradient")),this.id="svgjs_"+SVG.did++,this.attr("id",this.id),this.type=t},SVG.Gradient.prototype=new SVG.Element,SVG.extend(SVG.Gradient,SVG.Container),SVG.extend(SVG.Gradient,{from:function(e,t){return this.type=="radial"?this.attr({fx:e+"%",fy:t+"%"}):this.attr({x1:e+"%",y1:t+"%"})},to:function(e,t){return this.type=="radial"?this.attr({cx:e+"%",cy:t+"%"}):this.attr({x2:e+"%",y2:t+"%"})},radius:function(e){return this.type=="radial"?this.attr({r:e+"%"}):this},at:function(e){return this.put(new SVG.Stop(e))},update:function(e){while(this.node.hasChildNodes())this.node.removeChild(this.node.lastChild);return e(this),this},fill:function(){return"url(#"+this.id+")"}}),SVG.extend(SVG.Defs,{gradient:function(e,t){var n=this.put(new SVG.Gradient(e));return t(n),n}}),SVG.Stop=function(t){this.constructor.call(this,SVG.create("stop")),this.update(t)},SVG.Stop.prototype=new SVG.Element,SVG.extend(SVG.Stop,{update:function(e){var t,n="",r=["opacity","color"];for(t=r.length-1;t>=0;t--)e[r[t]]!=null&&(n+="stop-"+r[t]+":"+e[r[t]]+";");return this.attr({offset:(e.offset!=null?e.offset:this.attrs.offset||0)+"%",style:n})}}),SVG.Doc=function(t){this.constructor.call(this,SVG.create("svg"));var n=document.createElement("div");n.style.cssText="position:relative;width:100%;height:100%;",typeof t=="string"&&(t=document.getElementById(t)),this.attr({xmlns:SVG.ns,version:"1.1",width:"100%",height:"100%"}).attr("xlink",SVG.xlink,SVG.ns).defs(),t.appendChild(n),n.appendChild(this.node),this.stage()},SVG.Doc.prototype=new SVG.Element,SVG.extend(SVG.Doc,SVG.Container),SVG.Shape=function(t){this.constructor.call(this,t)},SVG.Shape.prototype=new SVG.Element,e.prototype=new SVG.Shape,SVG.extend(e,{move:function(e,t){return this.center(e+this._b.width*this.child.trans.scaleX/2,t+this._b.height*this.child.trans.scaleY/2)},size:function(e,t){var n=e/this._b.width;return this.child.transform({scaleX:n,scaleY:t!=null?t/this._b.height:n}),this},center:function(e,t){return this.transform({x:e,y:t})},attr:function(e,t,n){if(typeof e=="object")for(t in e)this.attr(t,e[t]);else{if(arguments.length<2)return e=="transform"?this.attrs[e]:this.child.attrs[e];e=="transform"?(this.attrs[e]=t,n!=null?this.node.setAttributeNS(n,e,t):this.node.setAttribute(e,t)):this.child.attr(e,t,n)}return this},plot:function(e){return this.child.plot(e),this._b=this.child.bbox(),this.child.transform({x:-this._b.cx,y:-this._b.cy}),this}}),SVG.Rect=function(){this.constructor.call(this,SVG.create("rect"))},SVG.Rect.prototype=new SVG.Shape,SVG.Ellipse=function(){this.constructor.call(this,SVG.create("ellipse"))},SVG.Ellipse.prototype=new SVG.Shape,SVG.extend(SVG.Ellipse,{move:function(e,t){return this.attrs.x=e,this.attrs.y=t,this.center()},size:function(e,t){return this.attr({rx:e/2,ry:(t!=null?t:e)/2}).center()},center:function(e,t){return this.attr({cx:e||(this.attrs.x||0)+(this.attrs.rx||0),cy:t||(this.attrs.y||0)+(this.attrs.ry||0)})}}),SVG.Poly={plot:function(e){return this.attr("points",e||"0,0")},move:function(e,t){return this.transform({x:e,y:t})}},SVG.Polyline=function(){this.constructor.call(this,SVG.create("polyline"))},SVG.Polyline.prototype=new SVG.Shape,SVG.extend(SVG.Polyline,SVG.Poly),SVG.Polygon=function(){this.constructor.call(this,SVG.create("polygon"))},SVG.Polygon.prototype=new SVG.Shape,SVG.extend(SVG.Polygon,SVG.Poly),SVG.Path=function(){this.constructor.call(this,SVG.create("path"))},SVG.Path.prototype=new SVG.Shape,SVG.extend(SVG.Path,{move:function(e,t){this.transform({x:e,y:t})},plot:function(e){return this.attr("d",e||"M0,0")}}),SVG.Image=function(){this.constructor.call(this,SVG.create("image"))},SVG.Image.prototype=new SVG.Shape,SVG.extend(SVG.Image,{load:function(e){return this.src=e,e?this.attr("xlink:href",e,SVG.xlink):this}});var t=["size","family","weight","stretch","variant","style"];SVG.Text=function(){this.constructor.call(this,SVG.create("text")),this.style={"font-size":16,"font-family":"Helvetica","text-anchor":"start"},this.leading=1.2},SVG.Text.prototype=new SVG.Shape,SVG.extend(SVG.Text,{text:function(e){this.content=e=e||"text",this.lines=[];var t,r,i=this._style(),s=this.parentDoc(),o=e.split("\n"),u=this.style["font-size"];while(this.node.hasChildNodes())this.node.removeChild(this.node.lastChild);for(t=0,l=o.length;t<l;t++)r=(new n).text(o[t]).attr({dy:u*this.leading-(t==0?u*.3:0),x:this.attrs.x||0,style:i}),this.node.appendChild(r.node),this.lines.push(r);return this.attr("style",i)},_style:function(){var e,n="";for(e=t.length-1;e>=0;e--)this.style["font-"+t[e]]!=null&&(n+="font-"+t[e]+":"+this.style["font-"+t[e]]+";");return n+="text-anchor:"+this.style["text-anchor"]+";",n}}),n.prototype=new SVG.Shape,SVG.extend(n,{text:function(e){return this.node.appendChild(document.createTextNode(e)),this}}),SVG.Nested=function(){this.constructor.call(this,SVG.create("svg")),this.attr("overflow","visible")},SVG.Nested.prototype=new SVG.Element,SVG.extend(SVG.Nested,SVG.Container);var r=["width","opacity","linecap","linejoin","miterlimit","dasharray","dashoffset"],i=["opacity","rule"];SVG.extend(SVG.Shape,{fill:function(e){var t;e.color!=null&&this.attr("fill",e.color);for(t=i.length-1;t>=0;t--)e[i[t]]!=null&&this.attr("fill-"+i[t],e[i[t]]);return this},stroke:function(e){var t;e.color&&this.attr("stroke",e.color);for(t=r.length-1;t>=0;t--)e[r[t]]!=null&&this.attr("stroke-"+r[t],e[r[t]]);return this}}),SVG.extend(SVG.Element,{rotate:function(e){return this.transform({rotation:e||0})},skew:function(e,t){return this.transform({skewX:e||0,skewY:t||0})}}),SVG.extend(SVG.G,{move:function(e,t){return this.transform({x:e,y:t})}}),SVG.extend(SVG.Text,{font:function(e){var n,r={};for(n in e)n=="leading"?r[n]=e[n]:n=="anchor"?r["text-anchor"]=e[n]:t.indexOf(n)>-1?r["font-"+n]=e[n]:void 0;return this.attr(r).text(this.content)}}),SVG.FX&&(["fill","stroke"].forEach(function(e){SVG.FX.prototype[e]=function(t){var n,r;for(r in t)n=r=="color"?e:e+"-"+r,this.attrs[n]={from:this.target.attrs[n],to:t[r]};return this}}),SVG.extend(SVG.FX,{rotate:function(e){return this.transform({rotation:e||0})},skew:function(e,t){return this.transform({skewX:e||0,skewY:t||0})}}))}).call(this);
\ No newline at end of file
index aaa601051f8ec2d73d52b7ba45a184d570324440..719c4719980252ee21089ae764f96b7903d19f9d 100644 (file)
@@ -4,9 +4,13 @@ SVG.Element = function Element(n) {
   this.node = n;
   
   // initialize attribute store
-  this.attrs = {};
+  this.attrs = {
+    'fill-opacity':   1,
+    'stroke-opacity': 1,
+    'stroke-width':   0
+  };
   
-  // initialize transformations store
+  // initialize transformation store
   this.trans = {
     x:        0,
     y:        0,
diff --git a/src/fx.js b/src/fx.js
new file mode 100644 (file)
index 0000000..c35478c
--- /dev/null
+++ b/src/fx.js
@@ -0,0 +1,249 @@
+
+SVG.FX = function FX(e) {
+  // store target element
+  this.target = e;
+};
+
+// add FX methods
+SVG.extend(SVG.FX, {
+  
+  // animation parameters and animate
+  animate: function(duration, ease) {
+    // ensure default duration adn easing
+    duration = duration || 1000;
+    ease = ease || '<>';
+    
+    var akeys, tkeys, tvalues,
+        element   = this.target,
+        fx        = this,
+        start     = (new Date).getTime(),
+        finish    = start + duration;
+    
+    // start animation
+    this.interval = setInterval(function(){
+      var i,
+          time = (new Date).getTime(),
+          pos = time > finish ? 1 : (time - start) / duration;
+      
+      // collect attribute keys
+      if (akeys == null) {
+        akeys = [];
+        for (var k in fx.attrs)
+          akeys.push(k);
+      };
+      
+      // collect transformation keys
+      if (tkeys == null) {
+        tkeys = [];
+        for (var k in fx.trans)
+          tkeys.push(k);
+        
+        tvalues = {};
+      };
+      
+      // apply easing
+      pos = ease == '<>' ?
+        (-Math.cos(pos * Math.PI) / 2) + 0.5 :
+      ease == '>' ?
+        Math.sin(pos * Math.PI / 2) :
+      ease == '<' ?
+        -Math.cos(pos * Math.PI / 2) + 1 :
+      ease == '-' ?
+        pos :
+      typeof ease == 'function' ?
+        ease(pos) :
+        pos;
+      
+      // run all position properties
+      if (fx._move)
+        element.move(fx._at(fx._move.x, pos), fx._at(fx._move.y, pos));
+      else if (fx._center)
+        element.move(fx._at(fx._center.x, pos), fx._at(fx._center.y, pos));
+      
+      // run all size properties
+      if (fx._size)
+        element.size(fx._at(fx._size.width, pos), fx._at(fx._size.height, pos));
+      
+      // animate attributes
+      for (i = akeys.length - 1; i >= 0; i--)
+        element.attr(akeys[i], fx._at(fx.attrs[akeys[i]], pos));
+      
+      // animate transformations
+      if (tkeys.length > 0) {
+        for (i = tkeys.length - 1; i >= 0; i--)
+          tvalues[tkeys[i]] = fx._at(fx.trans[tkeys[i]], pos);
+        
+        element.transform(tvalues);
+      }
+      
+      // finish off animation
+      if (time > finish) {
+        clearInterval(fx.interval);
+        fx._after ? fx._after.apply(element) : fx.stop();
+      }
+        
+    }, 10);
+    
+    return this;
+  },
+  
+  // animated attributes
+  attr: function(a, v, n) {
+    if (typeof a == 'object')
+      for (var k in a)
+        this.attr(k, a[k]);
+    
+    else
+      this.attrs[a] = { from: this.target.attr(a), to: v };
+  },
+  
+  // animated transformations
+  transform: function(o) {
+    for (var k in o)
+      this.trans[k] = { from: this.target.trans[k], to: o[k] };
+    
+    return this;
+  },
+  
+  // animated move
+  move: function(x, y) {
+    var b = this.target.bbox();
+    
+    this._move = {
+      x: { from: b.x, to: x },
+      y: { from: b.y, to: y }
+    };
+    
+    return this;
+  },
+  
+  // animated size
+  size: function(w, h) {
+    var b = this.target.bbox();
+    
+    this._size = {
+      width:  { from: b.width,  to: w },
+      height: { from: b.height, to: h }
+    };
+    
+    return this;
+  },
+  
+  // animated center
+  center: function(x, y) {
+    var b = this.target.bbox();
+    
+    this._move = {
+      x: { from: b.cx, to: x },
+      y: { from: b.cy, to: y }
+    };
+    
+    return this;
+  },
+  
+  // stop animation
+  stop: function() {
+    // stop current animation
+    clearInterval(this.interval);
+    
+    // create / reset storage for properties that need animation
+    this.attrs  = {};
+    this.trans  = {};
+    this._move  = null;
+    this._size  = null;
+    this._after = null;
+    
+    return this;
+  },
+  
+  // private: at position according to from and to
+  _at: function(o, p) {
+    // if a number, calculate pos
+    return typeof o.from == 'number' ?
+      o.from + (o.to - o.from) * p :
+    
+    // if animating to a color
+    o.to.r || /^#/.test(o.to) ?
+      this._color(o, p) :
+    
+    // for all other values wait until pos has reached 1 to return the final value
+    p < 1 ? o.from : o.to;
+  },
+  
+  // private: tween color
+  _color: function(o, p) {
+    // convert hex to rgb and store it for further reference
+    if (typeof o.from !== 'object')
+      o.from = this._h2r(o.from || '#000');
+    
+    // convert hex to rgb and store it for further reference
+    if (typeof o.to !== 'object')
+      o.to = this._h2r(o.to);
+    
+    // tween color and return hex
+    return this._r2h({
+      r: ~~(o.from.r + (o.to.r - o.from.r) * p),
+      g: ~~(o.from.g + (o.to.g - o.from.g) * p),
+      b: ~~(o.from.b + (o.to.b - o.from.b) * p)
+    });
+  },
+  
+  // private: convert hex to rgb object
+  _h2r: function(h) {
+    // parse full hex
+    var m = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(this._fh(h));
+    
+    // if the hex is successfully parsed, return it in rgb, otherwise return black
+    return m ? {
+      r: parseInt(m[1], 16),
+      g: parseInt(m[2], 16),
+      b: parseInt(m[3], 16)
+    } : { r: 0, g: 0, b: 0 };
+  },
+  
+  // private: convert rgb object to hex string
+  _r2h: function(r) {
+    return '#' + (r.r + 256 * r.g + 65536 * r.b).toString(16);
+  },
+  
+  // private: force potential 3-based hex to 6-based 
+  _fh: function(h) {
+    return h.length == 4 ?
+      [ '#',
+        h.substring(1, 2), h.substring(1, 2),
+        h.substring(2, 3), h.substring(2, 3),
+        h.substring(3, 4), h.substring(3, 4)
+      ].join('') : h;
+  }
+  
+});
+
+
+// delay: delay animation for a given amount of ms
+// after: callback for when animation has finished
+['delay', 'after'].forEach(function(m) {
+  SVG.FX.prototype[m] = function(v) {
+    this['_' + m] = v;
+    
+    return this;
+  };
+});
+
+
+// make SVG.Element FX-aware
+SVG.extend(SVG.Element, {
+  
+  // get fx module or create a new one, then animate with given ms and ease
+  animate: function(d, e) {
+    return (this._fx || (this._fx = new SVG.FX(this))).stop().animate(d, e);
+  },
+  
+  // stop current animation
+  stop: function() {
+    this._fx.stop();
+    
+    return this;
+  }
+  
+});
+
index 47ab35a5560f0c29c36e7b83ded2b860d38b34eb..c6b5c75f8f02f1ed019f7930d5c85dbb65112390 100644 (file)
@@ -45,13 +45,9 @@ SVG.extend(SVG.Shape, {
 SVG.extend(SVG.Element, {
   
   // rotation
-  rotate: function(d, x, y) {
-    var b = this.bbox();
-    
+  rotate: function(d) {
     return this.transform({
-      rotation: d || 0,
-      cx:       x == null ? b.cx : x,
-      cy:       y == null ? b.cx : y
+      rotation: d || 0
     });
   },
   
@@ -96,5 +92,44 @@ SVG.extend(SVG.Text, {
   
 });
 
+// add methods to SVG.FX
+if (SVG.FX) {
+  // add sugar for fill and stroke
+  ['fill', 'stroke'].forEach(function(m) {
+    SVG.FX.prototype[m] = function(o) {
+      var a, k;
+
+      for (k in o) {
+        a = k == 'color' ? m : m + '-' + k;
+        this.attrs[a] = {
+          from: this.target.attrs[a],
+          to:   o[k]
+        };
+      };
+
+      return this;
+    };
+  });
+  
+  SVG.extend(SVG.FX, {
+
+    // rotation
+    rotate: function(d) {
+      return this.transform({
+        rotation: d || 0
+      });
+    },
+
+    // skew
+    skew: function(x, y) {
+      return this.transform({
+        skewX: x || 0,
+        skewY: y || 0
+      });
+    }
+
+  });
+}
+