summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorwout <wout@impinc.co.uk>2013-01-03 21:15:32 +0100
committerwout <wout@impinc.co.uk>2013-01-03 21:15:32 +0100
commit6f49a9d4c5ada0b7335c08fbcef623cf32898938 (patch)
tree560c92b904ae232089874507bde65adaaf2f4d80
parent0da8f1d9c9794155c29b8e8f9a0b5ca940061033 (diff)
downloadsvg.js-6f49a9d4c5ada0b7335c08fbcef623cf32898938.tar.gz
svg.js-6f49a9d4c5ada0b7335c08fbcef623cf32898938.zip
Added the SVG.FX module for animations
-rw-r--r--README.md103
-rw-r--r--Rakefile2
-rw-r--r--dist/svg.js307
-rw-r--r--dist/svg.min.js4
-rw-r--r--src/element.js8
-rw-r--r--src/fx.js249
-rw-r--r--src/sugar.js47
7 files changed, 686 insertions, 34 deletions
diff --git a/README.md b/README.md
index f5b9294..6cf54ac 100644
--- 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)_
diff --git a/Rakefile b/Rakefile
index 880e97e..a237b8c 100644
--- 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
diff --git a/dist/svg.js b/dist/svg.js
index c7943c2..016a568 100644
--- a/dist/svg.js
+++ b/dist/svg.js
@@ -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 = {
@@ -200,9 +200,13 @@
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,
@@ -440,6 +444,256 @@
});
+ 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',
@@ -1079,13 +1333,9 @@
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
});
},
@@ -1130,6 +1380,45 @@
});
+ // 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
+ });
+ }
+
+ });
+ }
+
diff --git a/dist/svg.min.js b/dist/svg.min.js
index 9654c54..e7b3442 100644
--- a/dist/svg.min.js
+++ b/dist/svg.min.js
@@ -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
diff --git a/src/element.js b/src/element.js
index aaa6010..719c471 100644
--- a/src/element.js
+++ b/src/element.js
@@ -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
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;
+ }
+
+});
+
diff --git a/src/sugar.js b/src/sugar.js
index 47ab35a..c6b5c75 100644
--- a/src/sugar.js
+++ b/src/sugar.js
@@ -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
+ });
+ }
+
+ });
+}
+