1.0 RC2
[raphael] / raphael.js
1 /*
2  * Raphael 1.0 RC2 - JavaScript Vector Library
3  *
4  * Copyright (c) 2008 - 2009 Dmitry Baranovskiy (http://raphaeljs.com)
5  * Licensed under the MIT (http://www.opensource.org/licenses/mit-license.php) license.
6  */
7
8
9 window.Raphael = (function () {
10     var separator = /[, ]+/,
11         doc = document,
12         win = window,
13         oldRaphael = {
14             was: "Raphael" in window,
15             is: window.Raphael
16         },
17         R = function () {
18             if (R.is(arguments[0], "array")) {
19                 var res = [],
20                     a = arguments[0],
21                     cnv = create.apply(R, a.splice(0, 3 + R.is(a[0], "number")));
22                 for (var i = 0, ii = a.length; i < ii; i++) {
23                     var j = a[i] || {};
24                     j.type in {circle:1, rect:1, path:1, ellipse:1, text:1, image:1} && res.push(cnv[j.type]().attr(j));
25                 }
26                 return res;
27             }
28             return create.apply(R, arguments);
29         },
30         paper = {},
31         availableAttrs = {"clip-rect": "0 0 10e9 10e9", cx: 0, cy: 0, fill: "#fff", "fill-opacity": 1, font: '10px "Arial"', "font-family": '"Arial"', "font-size": "10", "font-style": "normal", "font-weight": 400, gradient: 0, height: 0, href: "http://raphaeljs.com/", opacity: 1, path: "M0,0", r: 0, rotation: 0, rx: 0, ry: 0, scale: "1 1", src: "", stroke: "#000", "stroke-dasharray": "", "stroke-linecap": "butt", "stroke-linejoin": "butt", "stroke-miterlimit": 0, "stroke-opacity": 1, "stroke-width": 1, target: "_blank", "text-anchor": "middle", title: "Raphael", translation: "0 0", width: 0, x: 0, y: 0},
32         availableAnimAttrs = {"clip-rect": "csv", cx: "number", cy: "number", fill: "colour", "fill-opacity": "number", "font-size": "number", height: "number", opacity: "number", path: "path", r: "number", rotation: "csv", rx: "number", ry: "number", scale: "csv", stroke: "colour", "stroke-opacity": "number", "stroke-width": "number", translation: "csv", width: "number", x: "number", y: "number"},
33         events = ["click", "dblclick", "mousedown", "mousemove", "mouseout", "mouseover", "mouseup"];
34     R.version = "1.0 RC2";
35     R.type = (window.SVGAngle || document.implementation.hasFeature("http://www.w3.org/TR/SVG11/feature#BasicStructure", "1.1") ? "SVG" : "VML");
36     R.svg = !(R.vml = R.type == "VML");
37     R.idGenerator = 0;
38     R.fn = {};
39     R.is = function (o, type) {
40         type = (type + "").toLowerCase();
41         if ((type == "object" || type == "undefined") && typeof o == type) {
42             return true;
43         }
44         if (o == null && type == "null") {
45             return true;
46         }
47         return Object.prototype.toString.call(o).replace(/^\[object\s+|\]$/gi, "").toLowerCase() == type;
48     };
49     R.setWindow = function (newwin) {
50         win = newwin;
51         doc = win.document;
52     };
53     // colour utilities
54     R.hsb2rgb = cacher(function (hue, saturation, brightness) {
55         if (R.is(hue, "object") && "h" in hue && "s" in hue && "b" in hue) {
56             brightness = hue.b;
57             saturation = hue.s;
58             hue = hue.h;
59         }
60         var red,
61             green,
62             blue;
63         if (brightness == 0) {
64             return {r: 0, g: 0, b: 0, hex: "#000"};
65         }
66         if (hue > 1 || saturation > 1 || brightness > 1) {
67             hue /= 255;
68             saturation /= 255;
69             brightness /= 255;
70         }
71         var i = Math.floor(hue * 6),
72             f = (hue * 6) - i,
73             p = brightness * (1 - saturation),
74             q = brightness * (1 - (saturation * f)),
75             t = brightness * (1 - (saturation * (1 - f)));
76         red = [brightness, q, p, p, t, brightness, brightness][i];
77         green = [t, brightness, brightness, q, p, p, t][i];
78         blue = [p, p, t, brightness, brightness, q, p][i];
79         red *= 255;
80         green *= 255;
81         blue *= 255;
82         var rgb = {r: red, g: green, b: blue},
83             r = Math.round(red).toString(16),
84             g = Math.round(green).toString(16),
85             b = Math.round(blue).toString(16);
86         if (r.length == 1) {
87             r = "0" + r;
88         }
89         if (g.length == 1) {
90             g = "0" + g;
91         }
92         if (b.length == 1) {
93             b = "0" + b;
94         }
95         rgb.hex = "#" + r + g + b;
96         return rgb;
97     }, R);
98     R.rgb2hsb = cacher(function (red, green, blue) {
99         if (R.is(red, "object") && "r" in red && "g" in red && "b" in red) {
100             blue = red.b;
101             green = red.g;
102             red = red.r;
103         }
104         if (R.is(red, "string")) {
105             var clr = R.getRGB(red);
106             red = clr.r;
107             green = clr.g;
108             blue = clr.b;
109         }
110         if (red > 1 || green > 1 || blue > 1) {
111             red /= 255;
112             green /= 255;
113             blue /= 255;
114         }
115         var max = Math.max(red, green, blue),
116             min = Math.min(red, green, blue),
117             hue,
118             saturation,
119             brightness = max;
120         if (min == max) {
121             return {h: 0, s: 0, b: max};
122         } else {
123             var delta = (max - min);
124             saturation = delta / max;
125             if (red == max) {
126                 hue = (green - blue) / delta;
127             } else if (green == max) {
128                 hue = 2 + ((blue - red) / delta);
129             } else {
130                 hue = 4 + ((red - green) / delta);
131             }
132             hue /= 6;
133             if (hue < 0) {
134                 hue += 1;
135             }
136             if (hue > 1) {
137                 hue -= 1;
138             }
139         }
140         return {h: hue, s: saturation, b: brightness};
141     }, R);
142     R._path2string = function () {
143         var res = "",
144             item;
145         for (var i = 0, ii = this.length; i < ii; i++) {
146             for (var j = 0, jj = this[i].length; j < jj; j++) {
147                 res += this[i][j];
148                 j && j != jj - 1 && (res += ",");
149             }
150             i != ii - 1 && (res += "\n");
151         }
152         return res.replace(/,(?=-)/g, "");
153     };
154     function cacher(f, scope, postprocessor) {
155         function newf() {
156             var arg = Array.prototype.splice.call(arguments, 0, arguments.length),
157                 args = arg.join("\u25ba");
158             newf.cache = newf.cache || {};
159             newf.count = newf.count || [];
160             if (args in newf.cache) {
161                 return postprocessor ? postprocessor(newf.cache[args]) : newf.cache[args];
162             }
163             if (newf.count.length > 1000) {
164                 delete newf.cache[newf.count.unshift()];
165             }
166             newf.count.push(args);
167             newf.cache[args] = f.apply(scope, arg);
168             return postprocessor ? postprocessor(newf.cache[args]) : newf.cache[args];
169         }
170         return newf;
171     }
172
173     R.getRGB = cacher(function (colour) {
174         var htmlcolors = {aliceblue: "#f0f8ff", amethyst: "#96c", antiquewhite: "#faebd7", aqua: "#0ff", aquamarine: "#7fffd4", azure: "#f0ffff", beige: "#f5f5dc", bisque: "#ffe4c4", black: "#000", blanchedalmond: "#ffebcd", blue: "#00f", blueviolet: "#8a2be2", brown: "#a52a2a", burlywood: "#deb887", cadetblue: "#5f9ea0", chartreuse: "#7fff00", chocolate: "#d2691e", coral: "#ff7f50", cornflowerblue: "#6495ed", cornsilk: "#fff8dc", crimson: "#dc143c", cyan: "#0ff", darkblue: "#00008b", darkcyan: "#008b8b", darkgoldenrod: "#b8860b", darkgray: "#a9a9a9", darkgreen: "#006400", darkkhaki: "#bdb76b", darkmagenta: "#8b008b", darkolivegreen: "#556b2f", darkorange: "#ff8c00", darkorchid: "#9932cc", darkred: "#8b0000", darksalmon: "#e9967a", darkseagreen: "#8fbc8f", darkslateblue: "#483d8b", darkslategray: "#2f4f4f", darkturquoise: "#00ced1", darkviolet: "#9400d3", deeppink: "#ff1493", deepskyblue: "#00bfff", dimgray: "#696969", dodgerblue: "#1e90ff", firebrick: "#b22222", floralwhite: "#fffaf0", forestgreen: "#228b22", fuchsia: "#f0f", gainsboro: "#dcdcdc", ghostwhite: "#f8f8ff", gold: "#ffd700", goldenrod: "#daa520", gray: "#808080", green: "#008000", greenyellow: "#adff2f", honeydew: "#f0fff0", hotpink: "#ff69b4", indianred: "#cd5c5c", indigo: "#4b0082", ivory: "#fffff0", khaki: "#f0e68c", lavender: "#e6e6fa", lavenderblush: "#fff0f5", lawngreen: "#7cfc00", lemonchiffon: "#fffacd", lightblue: "#add8e6", lightcoral: "#f08080", lightcyan: "#e0ffff", lightgoldenrodyellow: "#fafad2", lightgreen: "#90ee90", lightgrey: "#d3d3d3", lightpink: "#ffb6c1", lightsalmon: "#ffa07a", lightsalmon: "#ffa07a", lightseagreen: "#20b2aa", lightskyblue: "#87cefa", lightslategray: "#789", lightsteelblue: "#b0c4de", lightyellow: "#ffffe0", lime: "#0f0", limegreen: "#32cd32", linen: "#faf0e6", magenta: "#f0f", maroon: "#800000", mediumaquamarine: "#66cdaa", mediumblue: "#0000cd", mediumorchid: "#ba55d3", mediumpurple: "#9370db", mediumseagreen: "#3cb371", mediumslateblue: "#7b68ee", mediumslateblue: "#7b68ee", mediumspringgreen: "#00fa9a", mediumturquoise: "#48d1cc", mediumvioletred: "#c71585", midnightblue: "#191970", mintcream: "#f5fffa", mistyrose: "#ffe4e1", moccasin: "#ffe4b5", navajowhite: "#ffdead", navy: "#000080", oldlace: "#fdf5e6", olive: "#808000", olivedrab: "#6b8e23", orange: "#ffa500", orangered: "#ff4500", orchid: "#da70d6", palegoldenrod: "#eee8aa", palegreen: "#98fb98", paleturquoise: "#afeeee", palevioletred: "#db7093", papayawhip: "#ffefd5", peachpuff: "#ffdab9", peru: "#cd853f", pink: "#ffc0cb", plum: "#dda0dd", powderblue: "#b0e0e6", purple: "#800080", red: "#f00", rosybrown: "#bc8f8f", royalblue: "#4169e1", saddlebrown: "#8b4513", salmon: "#fa8072", sandybrown: "#f4a460", seagreen: "#2e8b57", seashell: "#fff5ee", sienna: "#a0522d", silver: "#c0c0c0", skyblue: "#87ceeb", slateblue: "#6a5acd", slategray: "#708090", snow: "#fffafa", springgreen: "#00ff7f", steelblue: "#4682b4", tan: "#d2b48c", teal: "#008080", thistle: "#d8bfd8", tomato: "#ff6347", turquoise: "#40e0d0", violet: "#ee82ee", wheat: "#f5deb3", white: "#fff", whitesmoke: "#f5f5f5", yellow: "#ff0", yellowgreen: "#9acd32"},
175         res;
176         if ((colour + "").toLowerCase() in htmlcolors) {
177             colour = htmlcolors[(colour + "").toLowerCase()];
178         }
179         if (!colour) {
180             return {r: 0, g: 0, b: 0, hex: "#000"};
181         }
182         if (colour == "none") {
183             return {r: -1, g: -1, b: -1, hex: "none"};
184         }
185         var red,
186             green,
187             blue,
188             rgb = (colour + "").match(/^\s*((#[a-f\d]{6})|(#[a-f\d]{3})|rgb\(\s*([\d\.]+\s*,\s*[\d\.]+\s*,\s*[\d\.]+)\s*\)|rgb\(\s*([\d\.]+%\s*,\s*[\d\.]+%\s*,\s*[\d\.]+%)\s*\)|hs[bl]\(\s*([\d\.]+\s*,\s*[\d\.]+\s*,\s*[\d\.]+)\s*\)|hs[bl]\(\s*([\d\.]+%\s*,\s*[\d\.]+%\s*,\s*[\d\.]+%)\s*\))\s*$/i);
189         if (rgb) {
190             if (rgb[2]) {
191                 blue = parseInt(rgb[2].substring(5), 16);
192                 green = parseInt(rgb[2].substring(3, 5), 16);
193                 red = parseInt(rgb[2].substring(1, 3), 16);
194             }
195             if (rgb[3]) {
196                 blue = parseInt(rgb[3].substring(3) + rgb[3].substring(3), 16);
197                 green = parseInt(rgb[3].substring(2, 3) + rgb[3].substring(2, 3), 16);
198                 red = parseInt(rgb[3].substring(1, 2) + rgb[3].substring(1, 2), 16);
199             }
200             if (rgb[4]) {
201                 rgb = rgb[4].split(/\s*,\s*/);
202                 red = parseFloat(rgb[0]);
203                 green = parseFloat(rgb[1]);
204                 blue = parseFloat(rgb[2]);
205             }
206             if (rgb[5]) {
207                 rgb = rgb[5].split(/\s*,\s*/);
208                 red = parseFloat(rgb[0]) * 2.55;
209                 green = parseFloat(rgb[1]) * 2.55;
210                 blue = parseFloat(rgb[2]) * 2.55;
211             }
212             if (rgb[6]) {
213                 rgb = rgb[6].split(/\s*,\s*/);
214                 red = parseFloat(rgb[0]);
215                 green = parseFloat(rgb[1]);
216                 blue = parseFloat(rgb[2]);
217                 return R.hsb2rgb(red, green, blue);
218             }
219             if (rgb[7]) {
220                 rgb = rgb[7].split(/\s*,\s*/);
221                 red = parseFloat(rgb[0]) * 2.55;
222                 green = parseFloat(rgb[1]) * 2.55;
223                 blue = parseFloat(rgb[2]) * 2.55;
224                 return R.hsb2rgb(red, green, blue);
225             }
226             rgb = {r: red, g: green, b: blue};
227             var r = Math.round(red).toString(16),
228                 g = Math.round(green).toString(16),
229                 b = Math.round(blue).toString(16);
230             (r.length == 1) && (r = "0" + r);
231             (g.length == 1) && (g = "0" + g);
232             (b.length == 1) && (b = "0" + b);
233             rgb.hex = "#" + r + g + b;
234             res = rgb;
235         } else {
236             res = {r: -1, g: -1, b: -1, hex: "none", error: 1};
237         }
238         return res;
239     }, R);
240     R.getColor = function (value) {
241         var start = this.getColor.start = this.getColor.start || {h: 0, s: 1, b: value || .75},
242             rgb = this.hsb2rgb(start.h, start.s, start.b);
243         start.h += .075;
244         if (start.h > 1) {
245             start.h = 0;
246             start.s -= .2;
247             if (start.s <= 0) {
248                 this.getColor.start = {h: 0, s: 1, b: start.b};
249             }
250         }
251         return rgb.hex;
252     };
253     R.getColor.reset = function () {
254         delete this.start;
255     };
256     // path utilities
257     R.parsePathString = cacher(function (pathString) {
258         if (!pathString) {
259             return null;
260         }
261         var paramCounts = {a: 7, c: 6, h: 1, l: 2, m: 2, q: 4, s: 4, t: 2, v: 1, z: 0},
262             data = [];
263         if (R.is(pathString, "array") && R.is(pathString[0], "array")) { // rough assumption
264             data = pathClone(pathString);
265         }
266         if (!data.length) {
267             (pathString + "").replace(/([achlmqstvz])[\s,]*((-?\d*\.?\d*(?:e[-+]?\d+)?\s*,?\s*)+)/ig, function (a, b, c) {
268                 var params = [],
269                     name = b.toLowerCase();
270                 c.replace(/(-?\d*\.?\d*(?:e[-+]?\d+)?)\s*,?\s*/ig, function (a, b) {
271                     b && params.push(+b);
272                 });
273                 while (params.length >= paramCounts[name]) {
274                     data.push([b].concat(params.splice(0, paramCounts[name])));
275                     if (!paramCounts[name]) {
276                         break;
277                     };
278                 }
279             });
280         }
281         data.toString = R._path2string;
282         return data;
283     });
284     var pathDimensions = cacher(function (path) {
285         if (!path) {
286             return {x: 0, y: 0, width: 0, height: 0};
287         }
288         path = path2curve(path);
289         var x = 0, 
290             y = 0,
291             X = [],
292             Y = [];
293         for (var i = 0, ii = path.length; i < ii; i++) {
294             if (path[i][0] == "M") {
295                 x = path[i][1];
296                 y = path[i][2];
297                 X.push(x);
298                 Y.push(y);
299             } else {
300                 var dim = curveDim(x, y, path[i][1], path[i][2], path[i][3], path[i][4], path[i][5], path[i][6]);
301                 X = X.concat(dim.min.x, dim.max.x);
302                 Y = Y.concat(dim.min.y, dim.max.y);
303             }
304         }
305         var xmin = Math.min.apply(0, X),
306             ymin = Math.min.apply(0, Y);
307         return {
308             x: xmin,
309             y: ymin,
310             width: Math.max.apply(0, X) - xmin,
311             height: Math.max.apply(0, Y) - ymin
312         };
313     }),
314         pathClone = function (pathArray) {
315             var res = [];
316             if (!R.is(pathArray, "array") || !R.is(pathArray && pathArray[0], "array")) { // rough assumption
317                 pathArray = R.parsePathString(pathArray);
318             }
319             for (var i = 0, ii = pathArray.length; i < ii; i++) {
320                 res[i] = [];
321                 for (var j = 0, jj = pathArray[i].length; j < jj; j++) {
322                     res[i][j] = pathArray[i][j];
323                 }
324             }
325             res.toString = R._path2string;
326             return res;
327         },
328         pathToRelative = cacher(function (pathArray) {
329             if (!R.is(pathArray, "array") || !R.is(pathArray && pathArray[0], "array")) { // rough assumption
330                 pathArray = R.parsePathString(pathArray);
331             }
332             var res = [],
333                 x = 0,
334                 y = 0,
335                 mx = 0,
336                 my = 0,
337                 start = 0;
338             if (pathArray[0][0] == "M") {
339                 x = pathArray[0][1];
340                 y = pathArray[0][2];
341                 mx = x;
342                 my = y;
343                 start++;
344                 res.push(["M", x, y]);
345             }
346             for (var i = start, ii = pathArray.length; i < ii; i++) {
347                 var r = res[i] = [],
348                     pa = pathArray[i];
349                 if (pa[0] != pa[0].toLowerCase()) {
350                     r[0] = pa[0].toLowerCase();
351                     switch (r[0]) {
352                         case "a":
353                             r[1] = pa[1];
354                             r[2] = pa[2];
355                             r[3] = pa[3];
356                             r[4] = pa[4];
357                             r[5] = pa[5];
358                             r[6] = +(pa[6] - x).toFixed(3);
359                             r[7] = +(pa[7] - y).toFixed(3);
360                             break;
361                         case "v":
362                             r[1] = +(pa[1] - y).toFixed(3);
363                             break;
364                         case "m":
365                             mx = pa[1];
366                             my = pa[2];
367                         default:
368                             for (var j = 1, jj = pa.length; j < jj; j++) {
369                                 r[j] = +(pa[j] - ((j % 2) ? x : y)).toFixed(3);
370                             }
371                     }
372                 } else {
373                     r = res[i] = [];
374                     if (pa[0] == "m") {
375                         mx = pa[1] + x;
376                         my = pa[2] + y;
377                     }
378                     for (var k = 0, kk = pa.length; k < kk; k++) {
379                         res[i][k] = pa[k];
380                     }
381                 }
382                 var len = res[i].length;
383                 switch (res[i][0]) {
384                     case "z":
385                         x = mx;
386                         y = my;
387                         break;
388                     case "h":
389                         x += +res[i][len - 1];
390                         break;
391                     case "v":
392                         y += +res[i][len - 1];
393                         break;
394                     default:
395                         x += +res[i][len - 2];
396                         y += +res[i][len - 1];
397                 }
398             }
399             res.toString = R._path2string;
400             return res;
401         }, 0, pathClone),
402         pathToAbsolute = cacher(function (pathArray) {
403             if (!R.is(pathArray, "array") || !R.is(pathArray && pathArray[0], "array")) { // rough assumption
404                 pathArray = R.parsePathString(pathArray);
405             }
406             var res = [],
407                 x = 0,
408                 y = 0,
409                 mx = 0,
410                 my = 0,
411                 start = 0;
412             if (pathArray[0][0] == "M") {
413                 x = +pathArray[0][1];
414                 y = +pathArray[0][2];
415                 mx = x;
416                 my = y;
417                 start++;
418                 res[0] = ["M", x, y];
419             }
420             for (var i = start, ii = pathArray.length; i < ii; i++) {
421                 var r = res[i] = [],
422                     pa = pathArray[i];
423                 if (pa[0] != (pa[0] + "").toUpperCase()) {
424                     r[0] = (pa[0] + "").toUpperCase();
425                     switch (r[0]) {
426                         case "A":
427                             r[1] = pa[1];
428                             r[2] = pa[2];
429                             r[3] = pa[3];
430                             r[4] = pa[4];
431                             r[5] = pa[5];
432                             r[6] = +(pa[6] + x);
433                             r[7] = +(pa[7] + y);
434                             break;
435                         case "V":
436                             r[1] = +pa[1] + y;
437                             break;
438                         case "H":
439                             r[1] = +pa[1] + x;
440                             break;
441                         case "M":
442                             mx = +pa[1] + x;
443                             my = +pa[2] + y;
444                         default:
445                             for (var j = 1, jj = pa.length; j < jj; j++) {
446                                 r[j] = +pa[j] + ((j % 2) ? x : y);
447                             }
448                     }
449                 } else {
450                     for (var k = 0, kk = pa.length; k < kk; k++) {
451                         res[i][k] = pa[k];
452                     }
453                 }
454                 switch (r[0]) {
455                     case "Z":
456                         x = mx;
457                         y = my;
458                         break;
459                     case "H":
460                         x = r[1];
461                         break;
462                     case "V":
463                         y = r[1];
464                         break;
465                     default:
466                         x = res[i][res[i].length - 2];
467                         y = res[i][res[i].length - 1];
468                 }
469             }
470             res.toString = R._path2string;
471             return res;
472         }, null, pathClone),
473         l2c = function (x1, y1, x2, y2) {
474             return [x1, y1, x2, y2, x2, y2];
475         },
476         q2c = function (x1, y1, ax, ay, x2, y2) {
477             var _13 = 1 / 3,
478                 _23 = 2 / 3;
479             return [
480                     _13 * x1 + _23 * ax,
481                     _13 * y1 + _23 * ay,
482                     _13 * x2 + _23 * ax,
483                     _13 * y2 + _23 * ay,
484                     x2,
485                     y2
486                 ];
487         },
488         a2c = function (x1, y1, rx, ry, angle, large_arc_flag, sweep_flag, x2, y2, recursive) {
489             // for more information of where this math came from visit:
490             // http://www.w3.org/TR/SVG11/implnote.html#ArcImplementationNotes
491             var _120 = Math.PI * 120 / 180,
492                 rad = Math.PI / 180 * (+angle || 0),
493                 res = [],
494                 xy,
495                 rotate = cacher(function (x, y, rad) {
496                     var X = x * Math.cos(rad) - y * Math.sin(rad),
497                         Y = x * Math.sin(rad) + y * Math.cos(rad);
498                     return {x: X, y: Y};
499                 });
500             if (!recursive) {
501                 xy = rotate(x1, y1, -rad);
502                 x1 = xy.x;
503                 y1 = xy.y;
504                 xy = rotate(x2, y2, -rad);
505                 x2 = xy.x;
506                 y2 = xy.y;
507                 var cos = Math.cos(Math.PI / 180 * angle),
508                     sin = Math.sin(Math.PI / 180 * angle),
509                     x = (x1 - x2) / 2,
510                     y = (y1 - y2) / 2;
511                 rx = Math.max(rx, Math.abs(x));
512                 ry = Math.max(ry, Math.abs(y));
513                 var rx2 = rx * rx,
514                     ry2 = ry * ry,
515                     k = (large_arc_flag == sweep_flag ? -1 : 1) *
516                         Math.sqrt(Math.abs((rx2 * ry2 - rx2 * y * y - ry2 * x * x) / (rx2 * y * y + ry2 * x * x))),
517                     cx = k * rx * y / ry + (x1 + x2) / 2,
518                     cy = k * -ry * x / rx + (y1 + y2) / 2,
519                     f1 = Math.asin((y1 - cy) / ry),
520                     f2 = Math.asin((y2 - cy) / ry);
521
522                 f1 = x1 < cx ? Math.PI - f1 : f1;
523                 f2 = x2 < cx ? Math.PI - f2 : f2;
524                 f1 < 0 && (f1 = Math.PI * 2 + f1);
525                 f2 < 0 && (f2 = Math.PI * 2 + f2);
526                 if (sweep_flag && f1 > f2) {
527                     f1 = f1 - Math.PI * 2;
528                 }
529                 if (!sweep_flag && f2 > f1) {
530                     f2 = f2 - Math.PI * 2;
531                 }
532             } else {
533                 f1 = recursive[0];
534                 f2 = recursive[1];
535                 cx = recursive[2];
536                 cy = recursive[3];
537             }
538             var df = f2 - f1;
539             if (Math.abs(df) > _120) {
540                 var f2old = f2,
541                     x2old = x2,
542                     y2old = y2;
543                 f2 = f1 + _120 * (sweep_flag && f2 > f1 ? 1 : -1);
544                 x2 = cx + rx * Math.cos(f2);
545                 y2 = cy + ry * Math.sin(f2);
546                 res = a2c(x2, y2, rx, ry, angle, 0, sweep_flag, x2old, y2old, [f2, f2old, cx, cy]);
547             }
548             df = f2 - f1;
549             var c1 = Math.cos(f1),
550                 s1 = Math.sin(f1),
551                 c2 = Math.cos(f2),
552                 s2 = Math.sin(f2),
553                 t = Math.tan(df / 4),
554                 hx = 4 / 3 * rx * t,
555                 hy = 4 / 3 * ry * t,
556                 m1 = [x1, y1],
557                 m2 = [x1 + hx * s1, y1 - hy * c1],
558                 m3 = [x2 + hx * s2, y2 - hy * c2],
559                 m4 = [x2, y2];
560             m2[0] = 2 * m1[0] - m2[0];
561             m2[1] = 2 * m1[1] - m2[1];
562             if (recursive) {
563                 return [m2, m3, m4].concat(res);
564             } else {
565                 res = [m2, m3, m4].concat(res).join(",").split(",");
566                 var newres = [];
567                 for (var i = 0, ii = res.length; i < ii; i++) {
568                     newres[i] = i % 2 ? rotate(res[i - 1], res[i], rad).y : rotate(res[i], res[i + 1], rad).x;
569                 }
570                 return newres;
571             }
572         },
573         findDotAtSegment = cacher(function (p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t) {
574             var x = Math.pow(1 - t, 3) * p1x + Math.pow(1 - t, 2) * 3 * t * c1x + (1 - t) * 3 * t * t * c2x + Math.pow(t, 3) * p2x,
575                 y = Math.pow(1 - t, 3) * p1y + Math.pow(1 - t, 2) * 3 * t * c1y + (1 - t) * 3 * t * t * c2y + Math.pow(t, 3) * p2y,
576                 mx = p1x + 2 * t * (c1x - p1x) + t * t * (c2x - 2 * c1x + p1x),
577                 my = p1y + 2 * t * (c1y - p1y) + t * t * (c2y - 2 * c1y + p1y),
578                 nx = c1x + 2 * t * (c2x - c1x) + t * t * (p2x - 2 * c2x + c1x),
579                 ny = c1y + 2 * t * (c2y - c1y) + t * t * (p2y - 2 * c2y + c1y),
580                 ax = (1 - t) * p1x + t * c1x,
581                 ay = (1 - t) * p1y + t * c1y,
582                 cx = (1 - t) * c2x + t * p2x,
583                 cy = (1 - t) * c2y + t * p2y;
584             return {x: x, y: y, m: {x: mx, y: my}, n: {x: nx, y: ny}, start: {x: ax, y: ay}, end: {x: cx, y: cy}};
585         }),
586         curveDim = cacher(function (p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y) {
587             var a = (c2x - 2 * c1x + p1x) - (p2x - 2 * c2x + c1x),
588                 b = 2 * (c1x - p1x) - 2 * (c2x - c1x),
589                 c = p1x - c1x,
590                 t1 = (-b + Math.sqrt(b * b - 4 * a * c)) / 2 / a,
591                 t2 = (-b - Math.sqrt(b * b - 4 * a * c)) / 2 / a,
592                 y = [p1y, p2y],
593                 x = [p1x, p2x],
594                 dot1 = findDotAtSegment(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t1 > 0 && t1 < 1 ? t1 : 0),
595                 dot2 = findDotAtSegment(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t2 > 0 && t2 < 1 ? t2 : 0);
596             x = x.concat(dot1.x, dot2.x);
597             y = y.concat(dot1.y, dot2.y);
598             a = (c2y - 2 * c1y + p1y) - (p2y - 2 * c2y + c1y);
599             b = 2 * (c1y - p1y) - 2 * (c2y - c1y);
600             c = p1y - c1y;
601             t1 = (-b + Math.sqrt(b * b - 4 * a * c)) / 2 / a;
602             t2 = (-b - Math.sqrt(b * b - 4 * a * c)) / 2 / a;
603             dot1 = findDotAtSegment(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t1 > 0 && t1 < 1 ? t1 : 0);
604             dot2 = findDotAtSegment(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t2 > 0 && t2 < 1 ? t2 : 0);
605             x = x.concat(dot1.x, dot2.x);
606             y = y.concat(dot1.y, dot2.y);
607             return {
608                 min: {x: Math.min.apply(Math, x), y: Math.min.apply(Math, y)},
609                 max: {x: Math.max.apply(Math, x), y: Math.max.apply(Math, y)}
610             };
611         }),
612         path2curve = cacher(function (path, path2) {
613             var p = pathToAbsolute(path),
614                 p2 = path2 && pathToAbsolute(path2),
615                 attrs = {x: 0, y: 0, bx: 0, by: 0, X: 0, Y: 0, qx: null, qy: null},
616                 attrs2 = {x: 0, y: 0, bx: 0, by: 0, X: 0, Y: 0, qx: null, qy: null},
617                 processPath = function (path, d) {
618                     var nx, ny;
619                     if (!path) {
620                         return ["C", d.x, d.y, d.x, d.y, d.x, d.y];
621                     }
622                     !(path[0] in {T:1, Q:1}) && (d.qx = d.qy = null);
623                     switch (path[0]) {
624                         case "M":
625                             d.X = path[1];
626                             d.Y = path[2];
627                             break;
628                         case "A":
629                             path = ["C"].concat(a2c.apply(0, [d.x, d.y].concat(path.slice(1))));
630                             break;
631                         case "S":
632                             nx = d.x + (d.x - (d.bx || d.x));
633                             ny = d.y + (d.y - (d.by || d.y));
634                             path = ["C", nx, ny].concat(path.slice(1));
635                             break;
636                         case "T":
637                             d.qx = d.x + (d.x - (d.qx || d.x));
638                             d.qy = d.y + (d.y - (d.qy || d.y));
639                             path = ["C"].concat(q2c(d.x, d.y, d.qx, d.qy, path[1], path[2]));
640                             break;
641                         case "Q":
642                             d.qx = path[1];
643                             d.qy = path[2];
644                             path = ["C"].concat(q2c(d.x, d.y, path[1], path[2], path[3], path[4]));
645                             break;
646                         case "L":
647                             path = ["C"].concat(l2c(d.x, d.y, path[1], path[2]));
648                             break;
649                         case "H":
650                             path = ["C"].concat(l2c(d.x, d.y, path[1], d.y));
651                             break;
652                         case "V":
653                             path = ["C"].concat(l2c(d.x, d.y, d.x, path[1]));
654                             break;
655                         case "Z":
656                             path = ["C"].concat(l2c(d.x, d.y, d.X, d.Y));
657                             break;
658                     }
659                     return path;
660                 },
661                 fixArc = function (pp, i) {
662                     if (pp[i].length > 7) {
663                         pp[i].shift();
664                         var pi = pp[i];
665                         while (pi.length) {
666                             pp.splice(i++, 0, ["C"].concat(pi.splice(0, 6)));
667                         }
668                         pp.splice(i, 1);
669                         ii = Math.max(p.length, p2 && p2.length || 0);
670                     }
671                 },
672                 fixM = function (path1, path2, a1, a2, i) {
673                     if (path1 && path2 && path1[i][0] == "M" && path2[i][0] != "M") {
674                         path2.splice(i, 0, ["M", a2.x, a2.y]);
675                         a1.bx = 0;
676                         a1.by = 0;
677                         a1.x = path1[i][1];
678                         a1.y = path1[i][2];
679                         ii = Math.max(p.length, p2 && p2.length || 0);
680                     }
681                 };
682             for (var i = 0, ii = Math.max(p.length, p2 && p2.length || 0); i < ii; i++) {
683                 p[i] = processPath(p[i], attrs);
684                 fixArc(p, i);
685                 p2 && (p2[i] = processPath(p2[i], attrs2));
686                 p2 && fixArc(p2, i);
687                 fixM(p, p2, attrs, attrs2, i);
688                 fixM(p2, p, attrs2, attrs, i);
689                 var seg = p[i],
690                     seg2 = p2 && p2[i],
691                     seglen = seg.length,
692                     seg2len = p2 && seg2.length;
693                 attrs.x = seg[seglen - 2];
694                 attrs.y = seg[seglen - 1];
695                 attrs.bx = parseFloat(seg[seglen - 4]) || attrs.x;
696                 attrs.by = parseFloat(seg[seglen - 3]) || attrs.y;
697                 attrs2.bx = p2 && (parseFloat(seg2[seg2len - 4]) || attrs2.x);
698                 attrs2.by = p2 && (parseFloat(seg2[seg2len - 3]) || attrs2.y);
699                 attrs2.x = p2 && seg2[seg2len - 2];
700                 attrs2.y = p2 && seg2[seg2len - 1];
701             }
702             return p2 ? [p, p2] : p;
703         }, null, pathClone),
704         parseDots = cacher(function (gradient) {
705             var dots = [];
706             for (var i = 0, ii = gradient.length; i < ii; i++) {
707                 var dot = {},
708                     par = gradient[i].match(/^([^:]*):?([\d\.]*)/);
709                 dot.color = R.getRGB(par[1]);
710                 if (dot.color.error) {
711                     return null;
712                 }
713                 dot.color = dot.color.hex;
714                 par[2] && (dot.offset = par[2] + "%");
715                 dots.push(dot);
716             }
717             for (var i = 1, ii = dots.length - 1; i < ii; i++) {
718                 if (!dots[i].offset) {
719                     var start = parseFloat(dots[i - 1].offset || 0),
720                         end = false;
721                     for (var j = i + 1; j < ii; j++) {
722                         if (dots[j].offset) {
723                             end = dots[j].offset;
724                             break;
725                         }
726                     }
727                     if (!end) {
728                         end = 100;
729                         j = ii;
730                     }
731                     end = parseFloat(end);
732                     var d = (end - start) / (j - i + 1);
733                     for (; i < j; i++) {
734                         start += d;
735                         dots[i].offset = start + "%";
736                     }
737                 }
738             }
739             return dots;
740         }),
741         getContainer = function () {
742             var container,
743                 x,
744                 y,
745                 width,
746                 height;
747             if (R.is(arguments[0], "string") || R.is(arguments[0], "object")) {
748                 if (R.is(arguments[0], "string")) {
749                     container = doc.getElementById(arguments[0]);
750                 } else {
751                     container = arguments[0];
752                 }
753                 if (container.tagName) {
754                     if (arguments[1] == null) {
755                         return {
756                             container: container,
757                             width: container.style.pixelWidth || container.offsetWidth,
758                             height: container.style.pixelHeight || container.offsetHeight
759                         };
760                     } else {
761                         return {container: container, width: arguments[1], height: arguments[2]};
762                     }
763                 }
764             } else if (R.is(arguments[0], "number") && arguments.length > 3) {
765                 return {container: 1, x: arguments[0], y: arguments[1], width: arguments[2], height: arguments[3]};
766             }
767         },
768         plugins = function (con, add) {
769             var that = this;
770             for (var prop in add) if (add.hasOwnProperty(prop) && !(prop in con)) {
771                 switch (typeof add[prop]) {
772                     case "function":
773                         (function (f) {
774                             con[prop] = con === that ? f : function () { return f.apply(that, arguments); };
775                         })(add[prop]);
776                     break;
777                     case "object":
778                         con[prop] = con[prop] || {};
779                         plugins.call(this, con[prop], add[prop]);
780                     break;
781                     default:
782                         con[prop] = add[prop];
783                     break;
784                 }
785             }
786         };
787
788     // SVG
789     if (R.svg) {
790         paper.svgns = "http://www.w3.org/2000/svg";
791         paper.xlink = "http://www.w3.org/1999/xlink";
792         var round = function (num) {
793             return +num + (Math.floor(num) == num) * .5;
794         };
795         var roundPath = function (path) {
796             for (var i = 0, ii = path.length; i < ii; i++) {
797                 if (path[i][0].toLowerCase() != "a") {
798                     for (var j = 1, jj = path[i].length; j < jj; j++) {
799                         path[i][j] = round(path[i][j]);
800                     }
801                 } else {
802                     path[i][6] = round(path[i][6]);
803                     path[i][7] = round(path[i][7]);
804                 }
805             }
806             return path;
807         };
808         var $ = function (el, attr) {
809             if (attr) {
810                 for (var key in attr) if (attr.hasOwnProperty(key)) {
811                     el.setAttribute(key, attr[key]);
812                 }
813             } else {
814                 return doc.createElementNS(paper.svgns, el);
815             }
816         };
817         R.toString = function () {
818             return  "Your browser supports SVG.\nYou are running Rapha\u00ebl " + this.version;
819         };
820         var thePath = function (pathString, SVG) {
821             var el = $("path");
822             SVG.canvas && SVG.canvas.appendChild(el);
823             var p = new Element(el, SVG);
824             p.type = "path";
825             setFillAndStroke(p, {fill: "none", stroke: "#000", path: pathString});
826             return p;
827         };
828         var addGradientFill = function (o, gradient, SVG) {
829             var type = "linear",
830                 fx = .5, fy = .5,
831                 s = o.style;
832             gradient = (gradient + "").replace(/^r(?:\(([^,]+?)\s*,\s*([^\)]+?)\))?/, function (all, _fx, _fy) {
833                 type = "radial";
834                 if (_fx && _fy) {
835                     fx = parseFloat(_fx);
836                     fy = parseFloat(_fy);
837                     if (Math.pow(fx - .5, 2) + Math.pow(fy - .5, 2) > .25) {
838                         fy = Math.sqrt(.25 - Math.pow(fx - .5, 2)) + .5;
839                     }
840                 }
841                 return "";
842             });
843             gradient = gradient.split(/\s*\-\s*/);
844             if (type == "linear") {
845                 var angle = gradient.shift();
846                 angle = -parseFloat(angle);
847                 if (isNaN(angle)) {
848                     return null;
849                 }
850                 var vector = [0, 0, Math.cos(angle * Math.PI / 180), Math.sin(angle * Math.PI / 180)],
851                     max = 1 / (Math.max(Math.abs(vector[2]), Math.abs(vector[3])) || 1);
852                 vector[2] *= max;
853                 vector[3] *= max;
854                 if (vector[2] < 0) {
855                     vector[0] = -vector[2];
856                     vector[2] = 0;
857                 }
858                 if (vector[3] < 0) {
859                     vector[1] = -vector[3];
860                     vector[3] = 0;
861                 }
862             }
863             var dots = parseDots(gradient);
864             if (!dots) {
865                 return null;
866             }
867             var el = $(type + "Gradient");
868             el.id = "r" + (R.idGenerator++).toString(36);
869             type == "radial" ? $(el, {fx: fx, fy: fy}) : $(el, {x1: vector[0], y1: vector[1], x2: vector[2], y2: vector[3]});
870             SVG.defs.appendChild(el);
871             for (var i = 0, ii = dots.length; i < ii; i++) {
872                 var stop = $("stop");
873                 $(stop, {
874                     offset: dots[i].offset ? dots[i].offset : !i ? "0%" : "100%",
875                     "stop-color": dots[i].color || "#fff"
876                 });
877                 el.appendChild(stop);
878             };
879             $(o, {
880                 fill: "url(#" + el.id + ")",
881                 opacity: 1,
882                 "fill-opacity": 1
883             });
884             s.fill = "";
885             s.opacity = 1;
886             s.fillOpacity = 1;
887             return 1;
888         };
889         var updatePosition = function (o) {
890             var bbox = o.getBBox();
891             $(o.pattern, {patternTransform: R.format("translate({0},{1})", bbox.x, bbox.y)});
892         };
893         var setFillAndStroke = function (o, params) {
894             var dasharray = {
895                     "": [0],
896                     "none": [0],
897                     "-": [3, 1],
898                     ".": [1, 1],
899                     "-.": [3, 1, 1, 1],
900                     "-..": [3, 1, 1, 1, 1, 1],
901                     ". ": [1, 3],
902                     "- ": [4, 3],
903                     "--": [8, 3],
904                     "- .": [4, 3, 1, 3],
905                     "--.": [8, 3, 1, 3],
906                     "--..": [8, 3, 1, 3, 1, 3]
907                 },
908                 node = o.node,
909                 attrs = o.attrs,
910                 rot = o.attr("rotation"),
911                 addDashes = function (o, value) {
912                     value = dasharray[(value + "").toLowerCase()];
913                     if (value) {
914                         var width = o.attrs["stroke-width"] || "1",
915                             butt = {round: width, square: width, butt: 0}[o.attrs["stroke-linecap"] || params["stroke-linecap"]] || 0,
916                             dashes = [];
917                         var i = value.length;
918                         while (i--) {
919                             dashes[i] = value[i] * width + ((i % 2) ? 1 : -1) * butt;
920                         }
921                         $(node, {"stroke-dasharray": dashes.join(",")});
922                     }
923                 };
924             parseFloat(rot) && o.rotate(0, true);
925             for (var att in params) if (params.hasOwnProperty(att)) {
926                 if (!(att in availableAttrs)) {
927                     continue;
928                 }
929                 var value = params[att];
930                 attrs[att] = value;
931                 switch (att) {
932                     // Hyperlink
933                     case "href":
934                     case "title":
935                     case "target":
936                         var pn = node.parentNode;
937                         if (pn.tagName.toLowerCase() != "a") {
938                             var hl = $("a");
939                             pn.insertBefore(hl, node);
940                             hl.appendChild(node);
941                             pn = hl;
942                         }
943                         pn.setAttributeNS(o.paper.xlink, att, value);
944                       break;
945                     case "clip-rect":
946                         var rect = (value + "").split(separator);
947                         if (rect.length == 4) {
948                             o.clip && o.clip.parentNode.parentNode.removeChild(o.clip.parentNode);
949                             var el = $("clipPath"),
950                                 rc = $("rect");
951                             el.id = "r" + (R.idGenerator++).toString(36);
952                             $(rc, {
953                                 x: rect[0],
954                                 y: rect[1],
955                                 width: rect[2],
956                                 height: rect[3]
957                             });
958                             el.appendChild(rc);
959                             o.paper.defs.appendChild(el);
960                             $(node, {"clip-path": "url(#" + el.id + ")"});
961                             o.clip = rc;
962                         }
963                         if (!value) {
964                             var clip = doc.getElementById(node.getAttribute("clip-path").replace(/(^url\(#|\)$)/g, ""));
965                             clip && clip.parentNode.removeChild(clip);
966                             $(node, {"clip-path": ""});
967                             delete o.clip;
968                         }
969                     break;
970                     case "path":
971                         if (value && o.type == "path") {
972                             attrs.path = roundPath(pathToAbsolute(value));
973                             $(node, {d: attrs.path});
974                         }
975                         break;
976                     case "width":
977                         node.setAttribute(att, value);
978                         if (attrs.fx) {
979                             att = "x";
980                             value = attrs.x;
981                         } else {
982                             break;
983                         }
984                     case "x":
985                         if (attrs.fx) {
986                             value = -attrs.x - (attrs.width || 0);
987                         }
988                     case "rx":
989                         if (att == "rx" && o.type == "rect") {
990                             break;
991                         }
992                     case "cx":
993                         node.setAttribute(att, value);
994                         o.pattern && updatePosition(o);
995                         break;
996                     case "height":
997                         node.setAttribute(att, value);
998                         if (attrs.fy) {
999                             att = "y";
1000                             value = attrs.y;
1001                         } else {
1002                             break;
1003                         }
1004                     case "y":
1005                         if (attrs.fy) {
1006                             value = -attrs.y - (attrs.height || 0);
1007                         }
1008                     case "ry":
1009                         if (att == "ry" && o.type == "rect") {
1010                             break;
1011                         }
1012                     case "cy":
1013                         node.setAttribute(att, value);
1014                         o.pattern && updatePosition(o);
1015                         break;
1016                     case "r":
1017                         if (o.type == "rect") {
1018                             $(node, {rx: value, ry: value});
1019                         } else {
1020                             node.setAttribute(att, value);
1021                         }
1022                         break;
1023                     case "src":
1024                         if (o.type == "image") {
1025                             node.setAttributeNS(o.paper.xlink, "href", value);
1026                         }
1027                         break;
1028                     case "stroke-width":
1029                         node.style.strokeWidth = value;
1030                         // Need following line for Firefox
1031                         node.setAttribute(att, value);
1032                         if (attrs["stroke-dasharray"]) {
1033                             addDashes(o, attrs["stroke-dasharray"]);
1034                         }
1035                         break;
1036                     case "stroke-dasharray":
1037                         addDashes(o, value);
1038                         break;
1039                     case "rotation":
1040                         rot = value;
1041                         o.rotate(value, true);
1042                         break;
1043                     case "translation":
1044                         var xy = (value + "").split(separator);
1045                         o.translate((+xy[0] + 1 || 2) - 1, (+xy[1] + 1 || 2) - 1);
1046                         break;
1047                     case "scale":
1048                         var xy = (value + "").split(separator);
1049                         o.scale(+xy[0] || 1, +xy[1] || +xy[0] || 1, +xy[2] || null, +xy[3] || null);
1050                         break;
1051                     case "fill":
1052                         var isURL = (value + "").match(/^url\(['"]?([^\)]+)['"]?\)$/i);
1053                         if (isURL) {
1054                             var el = $("pattern"),
1055                                 ig = $("image");
1056                             el.id = "r" + (R.idGenerator++).toString(36);
1057                             $(el, {x: 0, y: 0, patternUnits: "userSpaceOnUse"});
1058                             $(ig, {x: 0, y:0});
1059                             ig.setAttributeNS(o.paper.xlink, "href", isURL[1]);
1060                             el.appendChild(ig);
1061
1062                             var img = doc.createElement("img");
1063                             img.style.cssText = "position:absolute;left:-9999em;top-9999em";
1064                             img.onload = function () {
1065                                 $(el, {width: this.offsetWidth, height: this.offsetHeight});
1066                                 $(ig, {width: this.offsetWidth, height: this.offsetHeight});
1067                                 doc.body.removeChild(this);
1068                                 paper.safari();
1069                             };
1070                             doc.body.appendChild(img);
1071                             img.src = isURL[1];
1072                             o.paper.defs.appendChild(el);
1073                             node.style.fill = "url(#" + el.id + ")";
1074                             $(node, {fill: "url(#" + el.id + ")"});
1075                             o.pattern = el;
1076                             o.pattern && updatePosition(o);
1077                             break;
1078                         }
1079                         if (!R.getRGB(value).error) {
1080                             delete params.gradient;
1081                             delete attrs.gradient;
1082                             if (!R.is(attrs.opacity, "undefined") && R.is(params.opacity, "undefined") ) {
1083                                 node.style.opacity = attrs.opacity;
1084                                 // Need following line for Firefox
1085                                 $(node, {opacity: attrs.opacity});
1086                             }
1087                             if (!R.is(attrs["fill-opacity"], "undefined") && R.is(params["fill-opacity"], "undefined") ) {
1088                                 node.style.fillOpacity = attrs["fill-opacity"];
1089                                 // Need following line for Firefox
1090                                 $(node, {"fill-opacity": attrs["fill-opacity"]});
1091                             }
1092                         } else if ((o.type in {circle: 1, ellipse: 1} || (value + "").charAt(0) != "r") && addGradientFill(node, value, o.paper)) {
1093                             attrs.gradient = value;
1094                             attrs.fill = "none";
1095                             break;
1096                         }
1097                     case "stroke":
1098                         node.style[att] = R.getRGB(value).hex;
1099                         // Need following line for Firefox
1100                         node.setAttribute(att, R.getRGB(value).hex);
1101                         break;
1102                     case "gradient":
1103                         (o.type in {circle: 1, ellipse: 1} || (value + "").charAt(0) != "r") && addGradientFill(node, value, o.paper);
1104                         break;
1105                     case "opacity":
1106                     case "fill-opacity":
1107                         if (attrs.gradient) {
1108                             var gradient = doc.getElementById(node.getAttribute("fill").replace(/^url\(#|\)$/g, ""));
1109                             if (gradient) {
1110                                 var stops = gradient.getElementsByTagName("stop");
1111                                 stops[stops.length - 1].setAttribute("stop-opacity", value);
1112                             }
1113                             break;
1114                         }
1115                     default:
1116                         att == "font-size" && (value = parseInt(value, 10) + "px");
1117                         var cssrule = att.replace(/(\-.)/g, function (w) {
1118                             return w.substring(1).toUpperCase();
1119                         });
1120                         node.style[cssrule] = value;
1121                         // Need following line for Firefox
1122                         node.setAttribute(att, value);
1123                         break;
1124                 }
1125             }
1126             
1127             tuneText(o, params);
1128             parseInt(rot, 10) && o.rotate(rot, true);
1129         };
1130         var leading = 1.2;
1131         var tuneText = function (el, params) {
1132             if (el.type != "text" || !("text" in params || "font" in params || "font-size" in params || "x" in params || "y" in params)) {
1133                 return;
1134             }
1135             var a = el.attrs,
1136                 node = el.node,
1137                 fontSize = node.firstChild ? parseInt(doc.defaultView.getComputedStyle(node.firstChild, "").getPropertyValue("font-size"), 10) : 10;
1138
1139             if ("text" in params) {
1140                 while (node.firstChild) {
1141                     node.removeChild(node.firstChild);
1142                 }
1143                 var texts = (params.text + "").split("\n");
1144                 for (var i = 0, ii = texts.length; i < ii; i++) {
1145                     var tspan = $("tspan");
1146                     i && $(tspan, {dy: fontSize * leading, x: a.x});
1147                     tspan.appendChild(doc.createTextNode(texts[i]));
1148                     node.appendChild(tspan);
1149                 }
1150             } else {
1151                 var texts = node.getElementsByTagName("tspan");
1152                 for (var i = 0, ii = texts.length; i < ii; i++) {
1153                     i && $(texts[i], {dy: fontSize * leading, x: a.x});
1154                 }
1155             }
1156             $(node, {y: a.y});
1157             var bb = el.getBBox(),
1158                 dif = a.y - (bb.y + bb.height / 2);
1159             dif && $(node, {y: a.y + dif});
1160         };
1161         var Element = function (node, svg) {
1162             var X = 0,
1163                 Y = 0;
1164             this[0] = node;
1165             this.node = node;
1166             node.raphael = this;
1167             this.paper = svg;
1168             this.attrs = this.attrs || {};
1169             this.transformations = []; // rotate, translate, scale
1170             this._ = {
1171                 tx: 0,
1172                 ty: 0,
1173                 rt: {deg: 0, cx: 0, cy: 0},
1174                 sx: 1,
1175                 sy: 1
1176             };
1177         };
1178         Element.prototype.rotate = function (deg, cx, cy) {
1179             if (deg == null) {
1180                 if (this._.rt.cx) {
1181                     return [this._.rt.deg, this._.rt.cx, this._.rt.cy].join(" ");
1182                 }
1183                 return this._.rt.deg;
1184             }
1185             var bbox = this.getBBox();
1186             deg = (deg + "").split(separator);
1187             if (deg.length - 1) {
1188                 cx = parseFloat(deg[1]);
1189                 cy = parseFloat(deg[2]);
1190             }
1191             deg = parseFloat(deg[0]);
1192             if (cx != null) {
1193                 this._.rt.deg = deg;
1194             } else {
1195                 this._.rt.deg += deg;
1196             }
1197             (cy == null) && (cx = null);
1198             this._.rt.cx = cx;
1199             this._.rt.cy = cy;
1200             cx = cx == null ? bbox.x + bbox.width / 2 : cx;
1201             cy = cy == null ? bbox.y + bbox.height / 2 : cy;
1202             if (this._.rt.deg) {
1203                 this.transformations[0] = R.format("rotate({0} {1} {2})", this._.rt.deg, cx, cy);
1204                 this.clip && $(this.clip, {transform: R.format("rotate({0} {1} {2})", -this._.rt.deg, cx, cy)});
1205             } else {
1206                 this.transformations[0] = "";
1207                 this.clip && $(this.clip, {transform: ""});
1208             }
1209             $(this.node, {transform: this.transformations.join(" ")});
1210             return this;
1211         };
1212         Element.prototype.hide = function () {
1213             this.node.style.display = "none";
1214             return this;
1215         };
1216         Element.prototype.show = function () {
1217             this.node.style.display = "block";
1218             return this;
1219         };
1220         Element.prototype.remove = function () {
1221             this.node.parentNode.removeChild(this.node);
1222         };
1223         Element.prototype.getBBox = function () {
1224             if (this.type == "path") {
1225                 return pathDimensions(this.attrs.path);
1226             }
1227             if (this.node.style.display == "none") {
1228                 this.show();
1229                 var hide = true;
1230             }
1231             var bbox = {};
1232             try {
1233                 bbox = this.node.getBBox();
1234             } catch(e) {
1235                 // Firefox 3.0.x plays badly here
1236             } finally {
1237                 bbox = bbox || {};
1238             }
1239             if (this.type == "text") {
1240                 bbox = {x: bbox.x, y: Infinity, width: bbox.width, height: 0};
1241                 for (var i = 0, ii = this.node.getNumberOfChars(); i < ii; i++) {
1242                     var bb = this.node.getExtentOfChar(i);
1243                     (bb.y < bbox.y) && (bbox.y = bb.y);
1244                     (bb.y + bb.height - bbox.y > bbox.height) && (bbox.height = bb.y + bb.height - bbox.y);
1245                 }
1246             }
1247             hide && this.hide();
1248             return bbox;
1249         };
1250         Element.prototype.attr = function () {
1251             if (arguments.length == 1 && R.is(arguments[0], "string")) {
1252                 if (arguments[0] == "translation") {
1253                     return this.translate();
1254                 }
1255                 if (arguments[0] == "rotation") {
1256                     return this.rotate();
1257                 }
1258                 if (arguments[0] == "scale") {
1259                     return this.scale();
1260                 }
1261                 return this.attrs[arguments[0]];
1262             }
1263             if (arguments.length == 1 && R.is(arguments[0], "array")) {
1264                 var values = {};
1265                 for (var j in arguments[0]) if (arguments[0].hasOwnProperty(j)) {
1266                     values[arguments[0][j]] = this.attrs[arguments[0][j]];
1267                 }
1268                 return values;
1269             }
1270             if (arguments.length == 2) {
1271                 var params = {};
1272                 params[arguments[0]] = arguments[1];
1273                 setFillAndStroke(this, params);
1274             } else if (arguments.length == 1 && R.is(arguments[0], "object")) {
1275                 setFillAndStroke(this, arguments[0]);
1276             }
1277             return this;
1278         };
1279         Element.prototype.toFront = function () {
1280             this.node.parentNode.appendChild(this.node);
1281             return this;
1282         };
1283         Element.prototype.toBack = function () {
1284             if (this.node.parentNode.firstChild != this.node) {
1285                 this.node.parentNode.insertBefore(this.node, this.node.parentNode.firstChild);
1286             }
1287             return this;
1288         };
1289         Element.prototype.insertAfter = function (element) {
1290             if (element.node.nextSibling) {
1291                 element.node.parentNode.insertBefore(this.node, element.node.nextSibling);
1292             } else {
1293                 element.node.parentNode.appendChild(this.node);
1294             }
1295             return this;
1296         };
1297         Element.prototype.insertBefore = function (element) {
1298             var node = element.node;
1299             node.parentNode.insertBefore(this.node, node);
1300             return this;
1301         };
1302         
1303         var theCircle = function (svg, x, y, r) {
1304             x = round(x);
1305             y = round(y);
1306             var el = $("circle");
1307             svg.canvas && svg.canvas.appendChild(el);
1308             var res = new Element(el, svg);
1309             res.attrs = {cx: x, cy: y, r: r, fill: "none", stroke: "#000"};
1310             res.type = "circle";
1311             $(el, res.attrs);
1312             return res;
1313         };
1314         var theRect = function (svg, x, y, w, h, r) {
1315             x = round(x);
1316             y = round(y);
1317             var el = $("rect");
1318             svg.canvas && svg.canvas.appendChild(el);
1319             var res = new Element(el, svg);
1320             res.attrs = {x: x, y: y, width: w, height: h, r: r || 0, rx: r || 0, ry: r || 0, fill: "none", stroke: "#000"};
1321             res.type = "rect";
1322             $(el, res.attrs);
1323             return res;
1324         };
1325         var theEllipse = function (svg, x, y, rx, ry) {
1326             x = round(x);
1327             y = round(y);
1328             var el = $("ellipse");
1329             svg.canvas && svg.canvas.appendChild(el);
1330             var res = new Element(el, svg);
1331             res.attrs = {cx: x, cy: y, rx: rx, ry: ry, fill: "none", stroke: "#000"};
1332             res.type = "ellipse";
1333             $(el, res.attrs);
1334             return res;
1335         };
1336         var theImage = function (svg, src, x, y, w, h) {
1337             var el = $("image");
1338             $(el, {x: x, y: y, width: w, height: h, preserveAspectRatio: "none"});
1339             el.setAttributeNS(svg.xlink, "href", src);
1340             svg.canvas && svg.canvas.appendChild(el);
1341             var res = new Element(el, svg);
1342             res.attrs = {x: x, y: y, width: w, height: h, src: src};
1343             res.type = "image";
1344             return res;
1345         };
1346         var theText = function (svg, x, y, text) {
1347             var el = $("text");
1348             $(el, {x: x, y: y, "text-anchor": "middle"});
1349             svg.canvas && svg.canvas.appendChild(el);
1350             var res = new Element(el, svg);
1351             res.attrs = {x: x, y: y, "text-anchor": "middle", text: text, font: availableAttrs.font, stroke: "none", fill: "#000"};
1352             res.type = "text";
1353             setFillAndStroke(res, res.attrs);
1354             return res;
1355         };
1356         var setSize = function (width, height) {
1357             this.width = width || this.width;
1358             this.height = height || this.height;
1359             this.canvas.setAttribute("width", this.width);
1360             this.canvas.setAttribute("height", this.height);
1361             return this;
1362         };
1363         var create = function () {
1364             var con = getContainer.apply(null, arguments),
1365                 container = con && con.container,
1366                 x = con.x,
1367                 y = con.y,
1368                 width = con.width,
1369                 height = con.height;
1370             if (!container) {
1371                 throw new Error("SVG container not found.");
1372             }
1373             paper.canvas = $("svg");
1374             var cnvs = paper.canvas,
1375                 stl = cnvs.style;
1376             cnvs.setAttribute("width", width || 512);
1377             paper.width = width || 512;
1378             cnvs.setAttribute("height", height || 342);
1379             paper.height = height || 342;
1380             if (container == 1) {
1381                 doc.body.appendChild(cnvs);
1382                 stl.position = "absolute";
1383                 stl.left = x + "px";
1384                 stl.top = y + "px";
1385             } else {
1386                 if (container.firstChild) {
1387                     container.insertBefore(cnvs, container.firstChild);
1388                 } else {
1389                     container.appendChild(cnvs);
1390                 }
1391             }
1392             container = {
1393                 canvas: cnvs,
1394                 clear: function () {
1395                     while (this.canvas.firstChild) {
1396                         this.canvas.removeChild(this.canvas.firstChild);
1397                     }
1398                     this.desc = $("desc");
1399                     this.defs = $("defs");
1400                     this.desc.appendChild(doc.createTextNode("Created with Rapha\u00ebl"));
1401                     this.canvas.appendChild(this.desc);
1402                     this.canvas.appendChild(this.defs);
1403                 }
1404             };
1405             for (var prop in paper) if (paper.hasOwnProperty(prop)) {
1406                 if (prop != "create") {
1407                     container[prop] = paper[prop];
1408                 }
1409             }
1410             plugins.call(container, container, R.fn);
1411             container.clear();
1412             container.raphael = R;
1413             return container;
1414         };
1415         paper.remove = function () {
1416             this.canvas.parentNode && this.canvas.parentNode.removeChild(this.canvas);
1417         };
1418     }
1419
1420     // VML
1421     if (R.vml) {
1422         var path2vml = function (path) {
1423             var pa = path2curve(path);
1424             for (var i = 0, ii = pa.length; i < ii; i++) {
1425                 pa[i][0] = (pa[i][0] + "").toLowerCase();
1426                 pa[i][0] == "z" && (pa[i][0] = "x");
1427                 for (var j = 1, jj = pa[i].length; j < jj; j++) {
1428                     pa[i][j] = Math.round(pa[i][j]);
1429                 }
1430             }
1431             return (pa + "");
1432         };
1433         R.toString = function () {
1434             return  "Your browser doesn\u2019t support SVG. Assuming it is Internet Explorer and falling down to VML.\nYou are running Rapha\u00ebl " + this.version;
1435         };
1436         var thePath = function (pathString, VML) {
1437             var g = createNode("group"), gl = g.style;
1438             gl.position = "absolute";
1439             gl.left = 0;
1440             gl.top = 0;
1441             gl.width = VML.width + "px";
1442             gl.height = VML.height + "px";
1443             g.coordsize = VML.coordsize;
1444             g.coordorigin = VML.coordorigin;
1445             var el = createNode("shape"), ol = el.style;
1446             ol.width = VML.width + "px";
1447             ol.height = VML.height + "px";
1448             el.path = "";
1449             el.coordsize = this.coordsize;
1450             el.coordorigin = this.coordorigin;
1451             g.appendChild(el);
1452             var p = new Element(el, g, VML);
1453             p.isAbsolute = true;
1454             p.type = "path";
1455             p.path = [];
1456             p.Path = "";
1457             if (pathString) {
1458                 p.attrs.path = R.parsePathString(pathString);
1459                 p.node.path = path2vml(p.attrs.path);
1460             }
1461             setFillAndStroke(p, {fill: "none", stroke: "#000"});
1462             p.setBox();
1463             VML.canvas.appendChild(g);
1464             return p;
1465         };
1466         var setFillAndStroke = function (o, params) {
1467             o.attrs = o.attrs || {};
1468             var node = o.node,
1469                 a = o.attrs,
1470                 s = node.style,
1471                 xy,
1472                 res = o;
1473             for (var par in params) if (params.hasOwnProperty(par)) {
1474                 a[par] = params[par];
1475             }
1476             params.href && (node.href = params.href);
1477             params.title && (node.title = params.title);
1478             params.target && (node.target = params.target);
1479             if (params.path && o.type == "path") {
1480                 a.path = R.parsePathString(params.path);
1481                 node.path = path2vml(a.path);
1482             }
1483             if (params.rotation != null) {
1484                 o.rotate(params.rotation, true);
1485             }
1486             if (params.translation) {
1487                 xy = (params.translation + "").split(separator);
1488                 o.translate(xy[0], xy[1]);
1489             }
1490             if (params.scale) {
1491                 xy = (params.scale + "").split(separator);
1492                 o.scale(+xy[0] || 1, +xy[1] || +xy[0] || 1, +xy[2] || null, +xy[3] || null);
1493             }
1494             if ("clip-rect" in params) {
1495                 var rect = (params["clip-rect"] + "").split(separator);
1496                 if (rect.length == 4) {
1497                     rect[2] = +rect[2] + (+rect[0]);
1498                     rect[3] = +rect[3] + (+rect[1]);
1499                     var div = node.clipRect || doc.createElement("div"),
1500                         dstyle = div.style,
1501                         group = node.parentNode;
1502                     dstyle.clip = R.format("rect({1}px {2}px {3}px {0}px)", rect);
1503                     if (!node.clipRect) {
1504                         dstyle.position = "absolute";
1505                         dstyle.top = 0;
1506                         dstyle.left = 0;
1507                         dstyle.width = o.paper.width + "px";
1508                         dstyle.height = o.paper.height + "px";
1509                         group.parentNode.insertBefore(div, group);
1510                         div.appendChild(group);
1511                         node.clipRect = div;
1512                     }
1513                 }
1514                 if (!params["clip-rect"]) {
1515                     node.clipRect && (node.clipRect.style.clip = "");
1516                 }
1517             }
1518             if (o.type == "image" && params.src) {
1519                 node.src = params.src;
1520             }
1521             if (o.type == "image" && params.opacity) {
1522                 node.filterOpacity = " progid:DXImageTransform.Microsoft.Alpha(opacity=" + (params.opacity * 100) + ")";
1523                 s.filter = (node.filterMatrix || "") + (node.filterOpacity || "");
1524             }
1525             params.font && (s.font = params.font);
1526             params["font-family"] && (s.fontFamily = '"' + params["font-family"].split(",")[0].replace(/^['"]+|['"]+$/g, "") + '"');
1527             params["font-size"] && (s.fontSize = params["font-size"]);
1528             params["font-weight"] && (s.fontWeight = params["font-weight"]);
1529             params["font-style"] && (s.fontStyle = params["font-style"]);
1530             if (params.opacity != null || 
1531                 params["stroke-width"] != null ||
1532                 params.fill != null ||
1533                 params.stroke != null ||
1534                 params["stroke-width"] != null ||
1535                 params["stroke-opacity"] != null ||
1536                 params["fill-opacity"] != null ||
1537                 params["stroke-dasharray"] != null ||
1538                 params["stroke-miterlimit"] != null ||
1539                 params["stroke-linejoin"] != null ||
1540                 params["stroke-linecap"] != null) {
1541                 node = o.shape || node;
1542                 var fill = (node.getElementsByTagName("fill") && node.getElementsByTagName("fill")[0]),
1543                     newfill = false;
1544                 !fill && (newfill = fill = createNode("fill"));
1545                 if ("fill-opacity" in params || "opacity" in params) {
1546                     var opacity = ((+a["fill-opacity"] + 1 || 2) - 1) * ((+a.opacity + 1 || 2) - 1);
1547                     opacity < 0 && (opacity = 0);
1548                     opacity > 1 && (opacity = 1);
1549                     fill.opacity = opacity;
1550                 }
1551                 params.fill && (fill.on = true);
1552                 if (fill.on == null || params.fill == "none") {
1553                     fill.on = false;
1554                 }
1555                 if (fill.on && params.fill) {
1556                     var isURL = params.fill.match(/^url\(([^\)]+)\)$/i);
1557                     if (isURL) {
1558                         fill.src = isURL[1];
1559                         fill.type = "tile";
1560                     } else {
1561                         fill.color = R.getRGB(params.fill).hex;
1562                         fill.src = "";
1563                         fill.type = "solid";
1564                         if (R.getRGB(params.fill).error && (res.type in {circle: 1, ellipse: 1} || (params.fill + "").charAt(0) != "r") && addGradientFill(res, params.fill)) {
1565                             a.fill = "none";
1566                             a.gradient = params.fill;
1567                         }
1568                     }
1569                 }
1570                 newfill && node.appendChild(fill);
1571                 var stroke = (node.getElementsByTagName("stroke") && node.getElementsByTagName("stroke")[0]),
1572                 newstroke = false;
1573                 !stroke && (newstroke = stroke = createNode("stroke"));
1574                 if ((params.stroke && params.stroke != "none") ||
1575                     params["stroke-width"] ||
1576                     params["stroke-opacity"] != null ||
1577                     params["stroke-dasharray"] ||
1578                     params["stroke-miterlimit"] ||
1579                     params["stroke-linejoin"] ||
1580                     params["stroke-linecap"]) {
1581                     stroke.on = true;
1582                 }
1583                 (params.stroke == "none" || stroke.on == null || params.stroke == 0 || params["stroke-width"] == 0) && (stroke.on = false);
1584                 stroke.on && params.stroke && (stroke.color = R.getRGB(params.stroke).hex);
1585                 var opacity = ((+a["stroke-opacity"] + 1 || 2) - 1) * ((+a.opacity + 1 || 2) - 1);
1586                 opacity < 0 && (opacity = 0);
1587                 opacity > 1 && (opacity = 1);
1588                 stroke.opacity = opacity;
1589                 params["stroke-linejoin"] && (stroke.joinstyle = params["stroke-linejoin"] || "miter");
1590                 stroke.miterlimit = params["stroke-miterlimit"] || 8;
1591                 params["stroke-linecap"] && (stroke.endcap = {butt: "flat", square: "square", round: "round"}[params["stroke-linecap"]] || "miter");
1592                 params["stroke-width"] && (stroke.weight = (parseFloat(params["stroke-width"]) || 1) * 12 / 16);
1593                 if (params["stroke-dasharray"]) {
1594                     var dasharray = {
1595                         "-": "shortdash",
1596                         ".": "shortdot",
1597                         "-.": "shortdashdot",
1598                         "-..": "shortdashdotdot",
1599                         ". ": "dot",
1600                         "- ": "dash",
1601                         "--": "longdash",
1602                         "- .": "dashdot",
1603                         "--.": "longdashdot",
1604                         "--..": "longdashdotdot"
1605                     };
1606                     stroke.dashstyle = dasharray[params["stroke-dasharray"]] || "";
1607                 }
1608                 newstroke && node.appendChild(stroke);
1609             }
1610             if (res.type == "text") {
1611                 var s = res.paper.span.style;
1612                 a.font && (s.font = a.font);
1613                 a["font-family"] && (s.fontFamily = a["font-family"]);
1614                 a["font-size"] && (s.fontSize = a["font-size"]);
1615                 a["font-weight"] && (s.fontWeight = a["font-weight"]);
1616                 a["font-style"] && (s.fontStyle = a["font-style"]);
1617                 res.node.string && (res.paper.span.innerHTML = res.node.string.replace(/</g, "&#60;").replace(/&/g, "&#38;").replace(/\n/g, "<br>"));
1618                 res.W = a.w = res.paper.span.offsetWidth;
1619                 res.H = a.h = res.paper.span.offsetHeight;
1620                 res.X = a.x;
1621                 res.Y = a.y + Math.round(res.H / 2);
1622
1623                 // text-anchor emulation
1624                 switch (a["text-anchor"]) {
1625                     case "start":
1626                         res.node.style["v-text-align"] = "left";
1627                         res.bbx = Math.round(res.W / 2);
1628                     break;
1629                     case "end":
1630                         res.node.style["v-text-align"] = "right";
1631                         res.bbx = -Math.round(res.W / 2);
1632                     break;
1633                     default:
1634                         res.node.style["v-text-align"] = "center";
1635                     break;
1636                 }
1637             }
1638         };
1639         var addGradientFill = function (o, gradient) {
1640             o.attrs = o.attrs || {};
1641             var attrs = o.attrs,
1642                 fill = o.node.getElementsByTagName("fill"),
1643                 type = "linear",
1644                 fxfy = ".5 .5";
1645             o.attrs.gradient = gradient;
1646             gradient = (gradient + "").replace(/^r(?:\(([^,]+?)\s*,\s*([^\)]+?)\))?/, function (all, fx, fy) {
1647                 type = "radial";
1648                 if (fx && fy) {
1649                     fx = parseFloat(fx);
1650                     fy = parseFloat(fy);
1651                     if (Math.pow(fx - .5, 2) + Math.pow(fy - .5, 2) > .25) {
1652                         fy = Math.sqrt(.25 - Math.pow(fx - .5, 2)) + .5;
1653                     }
1654                     fxfy = fx + " " + fy;
1655                 }
1656                 return "";
1657             });
1658             gradient = gradient.split(/\s*\-\s*/);
1659             if (type == "linear") {
1660                 var angle = gradient.shift();
1661                 angle = -parseFloat(angle);
1662                 if (isNaN(angle)) {
1663                     return null;
1664                 }
1665             }
1666             var dots = parseDots(gradient);
1667             if (!dots) {
1668                 return null;
1669             }
1670             o = o.shape || o.node;
1671             fill = fill[0] || createNode("fill");
1672             if (dots.length) {
1673                 fill.on = true;
1674                 fill.method = "none";
1675                 fill.type = (type == "radial") ? "gradientradial" : "gradient";
1676                 fill.color = dots[0].color;
1677                 fill.color2 = dots[dots.length - 1].color;
1678                 var clrs = [];
1679                 for (var i = 0, ii = dots.length; i < ii; i++) {
1680                     dots[i].offset && clrs.push(dots[i].offset + " " + dots[i].color);
1681                 }
1682                 if (clrs.length) {
1683                     fill.colors.value = clrs.join(",");
1684                 } else {
1685                     fill.colors && (fill.colors.value = "0% " + fill.color);
1686                 }
1687                 if (type == "radial") {
1688                     fill.focus = "100%";
1689                     fill.focussize = fxfy;
1690                     fill.focusposition = fxfy;
1691                 } else {
1692                     fill.angle = (270 - angle) % 360;
1693                 }
1694             }
1695             return 1;
1696         };
1697         var Element = function (node, group, vml) {
1698             var Rotation = 0,
1699                 RotX = 0,
1700                 RotY = 0,
1701                 Scale = 1;
1702             this[0] = node;
1703             this.node = node;
1704             node.raphael = this;
1705             this.X = 0;
1706             this.Y = 0;
1707             this.attrs = {};
1708             this.Group = group;
1709             this.paper = vml;
1710             this._ = {
1711                 tx: 0,
1712                 ty: 0,
1713                 rt: {deg:0},
1714                 sx: 1,
1715                 sy: 1
1716             };
1717         };
1718         Element.prototype.rotate = function (deg, cx, cy) {
1719             if (deg == null) {
1720                 if (this._.rt.cx) {
1721                     return [this._.rt.deg, this._.rt.cx, this._.rt.cy].join(" ");
1722                 }
1723                 return this._.rt.deg;
1724             }
1725             deg = (deg + "").split(separator);
1726             if (deg.length - 1) {
1727                 cx = parseFloat(deg[1]);
1728                 cy = parseFloat(deg[2]);
1729             }
1730             deg = parseFloat(deg[0]);
1731             if (cx != null) {
1732                 this._.rt.deg = deg;
1733             } else {
1734                 this._.rt.deg += deg;
1735             }
1736             cy == null && (cx = null);
1737             this._.rt.cx = cx;
1738             this._.rt.cy = cy;
1739             this.setBox(this.attrs, cx, cy);
1740             this.Group.style.rotation = this._.rt.deg;
1741             // gradient fix for rotation. TODO
1742             // var fill = (this.shape || this.node).getElementsByTagName("fill");
1743             // fill = fill[0] || {};
1744             // var b = ((360 - this._.rt.deg) - 270) % 360;
1745             // !R.is(fill.angle, "undefined") && (fill.angle = b);
1746             return this;
1747         };
1748         Element.prototype.setBox = function (params, cx, cy) {
1749             var gs = this.Group.style,
1750                 os = (this.shape && this.shape.style) || this.node.style;
1751             params = params || {};
1752             for (var i in params) if (params.hasOwnProperty(i)) {
1753                 this.attrs[i] = params[i];
1754             }
1755             cx = cx || this._.rt.cx;
1756             cy = cy || this._.rt.cy;
1757             var attr = this.attrs,
1758                 x,
1759                 y,
1760                 w,
1761                 h;
1762             switch (this.type) {
1763                 case "circle":
1764                     x = attr.cx - attr.r;
1765                     y = attr.cy - attr.r;
1766                     w = h = attr.r * 2;
1767                     break;
1768                 case "ellipse":
1769                     x = attr.cx - attr.rx;
1770                     y = attr.cy - attr.ry;
1771                     w = attr.rx * 2;
1772                     h = attr.ry * 2;
1773                     break;
1774                 case "rect":
1775                 case "image":
1776                     x = attr.x;
1777                     y = attr.y;
1778                     w = attr.width || 0;
1779                     h = attr.height || 0;
1780                     break;
1781                 case "text":
1782                     this.textpath.v = ["m", Math.round(attr.x), ", ", Math.round(attr.y - 2), "l", Math.round(attr.x) + 1, ", ", Math.round(attr.y - 2)].join("");
1783                     x = attr.x - Math.round(this.W / 2);
1784                     y = attr.y - this.H / 2;
1785                     w = this.W;
1786                     h = this.H;
1787                     break;
1788                 case "path":
1789                     if (!this.attrs.path) {
1790                         x = 0;
1791                         y = 0;
1792                         w = this.paper.width;
1793                         h = this.paper.height;
1794                     } else {
1795                         var dim = pathDimensions(this.attrs.path);
1796                         x = dim.x;
1797                         y = dim.y;
1798                         w = dim.width;
1799                         h = dim.height;
1800                     }
1801                     break;
1802                 default:
1803                     x = 0;
1804                     y = 0;
1805                     w = this.paper.width;
1806                     h = this.paper.height;
1807                     break;
1808             }
1809             cx = (cx == null) ? x + w / 2 : cx;
1810             cy = (cy == null) ? y + h / 2 : cy;
1811             var left = cx - this.paper.width / 2,
1812                 top = cy - this.paper.height / 2;
1813             if (this.type == "path" || this.type == "text") {
1814                 (gs.left != left + "px") && (gs.left = left + "px");
1815                 (gs.top != top + "px") && (gs.top = top + "px");
1816                 this.X = this.type == "text" ? x : -left;
1817                 this.Y = this.type == "text" ? y : -top;
1818                 this.W = w;
1819                 this.H = h;
1820                 (os.left != -left + "px") && (os.left = -left + "px");
1821                 (os.top != -top + "px") && (os.top = -top + "px");
1822             } else {
1823                 (gs.left != left + "px") && (gs.left = left + "px");
1824                 (gs.top != top + "px") && (gs.top = top + "px");
1825                 this.X = x;
1826                 this.Y = y;
1827                 this.W = w;
1828                 this.H = h;
1829                 (gs.width != this.paper.width + "px") && (gs.width = this.paper.width + "px");
1830                 (gs.height != this.paper.height + "px") && (gs.height = this.paper.height + "px");
1831                 (os.left != x - left + "px") && (os.left = x - left + "px");
1832                 (os.top != y - top + "px") && (os.top = y - top + "px");
1833                 (os.width != w + "px") && (os.width = w + "px");
1834                 (os.height != h + "px") && (os.height = h + "px");
1835                 var arcsize = (+params.r || 0) / (Math.min(w, h));
1836                 if (this.type == "rect" && this.arcsize != arcsize && (arcsize || this.arcsize)) {
1837                     // We should replace element with the new one
1838                     var o = createNode(arcsize ? "roundrect" : "rect");
1839                     o.arcsize = arcsize;
1840                     this.Group.appendChild(o);
1841                     this.node.parentNode.removeChild(this.node);
1842                     this.node = o;
1843                     this.arcsize = arcsize;
1844                     setFillAndStroke(this, this.attrs);
1845                     this.setBox(this.attrs);
1846                 }
1847             }
1848         };
1849         Element.prototype.hide = function () {
1850             this.Group.style.display = "none";
1851             return this;
1852         };
1853         Element.prototype.show = function () {
1854             this.Group.style.display = "block";
1855             return this;
1856         };
1857         Element.prototype.getBBox = function () {
1858             if (this.type == "path") {
1859                 return pathDimensions(this.attrs.path);
1860             }
1861             return {
1862                 x: this.X + (this.bbx || 0),
1863                 y: this.Y,
1864                 width: this.W,
1865                 height: this.H
1866             };
1867         };
1868         Element.prototype.remove = function () {
1869             this[0].parentNode.removeChild(this[0]);
1870             this.Group.parentNode.removeChild(this.Group);
1871             this.shape && this.shape.parentNode.removeChild(this.shape);
1872         };
1873         Element.prototype.attr = function () {
1874             if (arguments.length == 1 && R.is(arguments[0], "string")) {
1875                 if (arguments[0] == "translation") {
1876                     return this.translate();
1877                 }
1878                 if (arguments[0] == "rotation") {
1879                     return this.rotate();
1880                 }
1881                 if (arguments[0] == "scale") {
1882                     return this.scale();
1883                 }
1884                 return this.attrs[arguments[0]];
1885             }
1886             if (this.attrs && arguments.length == 1 && R.is(arguments[0], "array")) {
1887                 var values = {};
1888                 for (var i = 0, ii = arguments[0].length; i < ii; i++) {
1889                     values[arguments[0][i]] = this.attrs[arguments[0][i]];
1890                 };
1891                 return values;
1892             }
1893             var params;
1894             if (arguments.length == 2) {
1895                 params = {};
1896                 params[arguments[0]] = arguments[1];
1897             }
1898             if (arguments.length == 1 && R.is(arguments[0], "object")) {
1899                 params = arguments[0];
1900             }
1901             if (params) {
1902                 if (params.gradient && (this.type in {circle: 1, ellipse: 1} || (params.gradient + "").charAt(0) != "r")) {
1903                     addGradientFill(this, params.gradient);
1904                 }
1905                 if (params.text && this.type == "text") {
1906                     this.node.string = params.text;
1907                 }
1908                 setFillAndStroke(this, params);
1909                 this.setBox(this.attrs);
1910             }
1911             return this;
1912         };
1913         Element.prototype.toFront = function () {
1914             this.Group.parentNode.appendChild(this.Group);
1915             return this;
1916         };
1917         Element.prototype.toBack = function () {
1918             if (this.Group.parentNode.firstChild != this.Group) {
1919                 this.Group.parentNode.insertBefore(this.Group, this.Group.parentNode.firstChild);
1920             }
1921             return this;
1922         };
1923         Element.prototype.insertAfter = function (element) {
1924             if (element.Group.nextSibling) {
1925                 element.Group.parentNode.insertBefore(this.Group, element.Group.nextSibling);
1926             } else {
1927                 element.Group.parentNode.appendChild(this.Group);
1928             }
1929             return this;
1930         };
1931         Element.prototype.insertBefore = function (element) {
1932             element.Group.parentNode.insertBefore(this.Group, element.Group);
1933             return this;
1934         };
1935
1936         var theCircle = function (vml, x, y, r) {
1937             var g = createNode("group"),
1938                 o = createNode("oval"),
1939                 ol = o.style;
1940             g.style.cssText = "position:absolute;left:0;top:0;width:" + vml.width + "px;height:" + vml.height + "px";
1941             g.coordsize = vml.coordsize;
1942             g.coordorigin = vml.coordorigin;
1943             g.appendChild(o);
1944             var res = new Element(o, g, vml);
1945             res.type = "circle";
1946             setFillAndStroke(res, {stroke: "#000", fill: "none"});
1947             res.attrs.cx = x;
1948             res.attrs.cy = y;
1949             res.attrs.r = r;
1950             res.setBox({x: x - r, y: y - r, width: r * 2, height: r * 2});
1951             vml.canvas.appendChild(g);
1952             return res;
1953         };
1954         var theRect = function (vml, x, y, w, h, r) {
1955             var g = createNode("group"),
1956                 o = createNode(r ? "roundrect" : "rect"),
1957                 arcsize = (+r || 0) / (Math.min(w, h));
1958             o.arcsize = arcsize;
1959             g.style.cssText = "position:absolute;left:0;top:0;width:" + vml.width + "px;height:" + vml.height + "px";
1960             g.coordsize = vml.coordsize;
1961             g.coordorigin = vml.coordorigin;
1962             g.appendChild(o);
1963             var res = new Element(o, g, vml);
1964             res.type = "rect";
1965             setFillAndStroke(res, {stroke: "#000"});
1966             res.arcsize = arcsize;
1967             res.setBox({x: x, y: y, width: w, height: h, r: +r});
1968             vml.canvas.appendChild(g);
1969             return res;
1970         };
1971         var theEllipse = function (vml, x, y, rx, ry) {
1972             var g = createNode("group"),
1973                 o = createNode("oval"),
1974                 ol = o.style;
1975             g.style.cssText = "position:absolute;left:0;top:0;width:" + vml.width + "px;height:" + vml.height + "px";
1976             g.coordsize = vml.coordsize;
1977             g.coordorigin = vml.coordorigin;
1978             g.appendChild(o);
1979             var res = new Element(o, g, vml);
1980             res.type = "ellipse";
1981             setFillAndStroke(res, {stroke: "#000"});
1982             res.attrs.cx = x;
1983             res.attrs.cy = y;
1984             res.attrs.rx = rx;
1985             res.attrs.ry = ry;
1986             res.setBox({x: x - rx, y: y - ry, width: rx * 2, height: ry * 2});
1987             vml.canvas.appendChild(g);
1988             return res;
1989         };
1990         var theImage = function (vml, src, x, y, w, h) {
1991             var g = createNode("group"),
1992                 o = createNode("image"),
1993                 ol = o.style;
1994             g.style.cssText = "position:absolute;left:0;top:0;width:" + vml.width + "px;height:" + vml.height + "px";
1995             g.coordsize = vml.coordsize;
1996             g.coordorigin = vml.coordorigin;
1997             o.src = src;
1998             g.appendChild(o);
1999             var res = new Element(o, g, vml);
2000             res.type = "image";
2001             res.attrs.src = src;
2002             res.attrs.x = x;
2003             res.attrs.y = y;
2004             res.attrs.w = w;
2005             res.attrs.h = h;
2006             res.setBox({x: x, y: y, width: w, height: h});
2007             vml.canvas.appendChild(g);
2008             return res;
2009         };
2010         var theText = function (vml, x, y, text) {
2011             var g = createNode("group"),
2012                 el = createNode("shape"),
2013                 ol = el.style,
2014                 path = createNode("path"),
2015                 ps = path.style,
2016                 o = createNode("textpath");
2017             g.style.cssText = "position:absolute;left:0;top:0;width:" + vml.width + "px;height:" + vml.height + "px";
2018             g.coordsize = vml.coordsize;
2019             g.coordorigin = vml.coordorigin;
2020             path.v = R.format("m{0},{1}l{2},{1}", Math.round(x), Math.round(y), Math.round(x) + 1);
2021             path.textpathok = true;
2022             ol.width = vml.width;
2023             ol.height = vml.height;
2024             o.string = text;
2025             o.on = true;
2026             el.appendChild(o);
2027             el.appendChild(path);
2028             g.appendChild(el);
2029             var res = new Element(o, g, vml);
2030             res.shape = el;
2031             res.textpath = path;
2032             res.type = "text";
2033             res.attrs.text = text;
2034             res.attrs.x = x;
2035             res.attrs.y = y;
2036             res.attrs.w = 1;
2037             res.attrs.h = 1;
2038             setFillAndStroke(res, {font: availableAttrs.font, stroke: "none", fill: "#000"});
2039             res.setBox();
2040             vml.canvas.appendChild(g);
2041             return res;
2042         };
2043         var setSize = function (width, height) {
2044             var cs = this.canvas.style;
2045             this.width = parseFloat(width || this.width);
2046             this.height = parseFloat(height || this.height);
2047             cs.width = this.width + "px";
2048             cs.height = this.height + "px";
2049             cs.clip = "rect(0 " + this.width + "px " + this.height + "px 0)";
2050             this.coordsize = this.width + " " + this.height;
2051             return this;
2052         };
2053         doc.createStyleSheet().addRule(".rvml", "behavior:url(#default#VML)");
2054         try {
2055             !doc.namespaces.rvml && doc.namespaces.add("rvml", "urn:schemas-microsoft-com:vml");
2056             var createNode = function (tagName) {
2057                 return doc.createElement('<rvml:' + tagName + ' class="rvml">');
2058             };
2059         } catch (e) {
2060             var createNode = function (tagName) {
2061                 return doc.createElement('<' + tagName + ' xmlns="urn:schemas-microsoft.com:vml" class="rvml">');
2062             };
2063         }
2064         var create = function () {
2065             var con = getContainer.apply(null, arguments),
2066                 container = con.container,
2067                 height = con.height,
2068                 s,
2069                 width = con.width,
2070                 x = con.x,
2071                 y = con.y;
2072             if (!container) {
2073                 throw new Error("VML container not found.");
2074             }
2075             var res = {},
2076                 c = res.canvas = doc.createElement("div"),
2077                 cs = c.style;
2078             width = parseFloat(width) || 512;
2079             height = parseFloat(height) || 342;
2080             res.width = width;
2081             res.height = height;
2082             res.coordsize = width + " " + height;
2083             res.coordorigin = "0 0";
2084             res.span = doc.createElement("span");
2085             res.span.style.cssText = "position:absolute;left:-9999px;top:-9999px;padding:0;margin:0;line-height:1;display:inline;";
2086             c.appendChild(res.span);
2087             cs.cssText = R.format("width:{0}px;height:{1}px;position:absolute;clip:rect(0 {0}px {1}px 0)", width, height);
2088             if (container == 1) {
2089                 doc.body.appendChild(c);
2090                 cs.left = x + "px";
2091                 cs.top = y + "px";
2092                 container = {
2093                     style: {
2094                         width: width,
2095                         height: height
2096                     }
2097                 };
2098             } else {
2099                 container.style.width = width;
2100                 container.style.height = height;
2101                 if (container.firstChild) {
2102                     container.insertBefore(c, container.firstChild);
2103                 } else {
2104                     container.appendChild(c);
2105                 }
2106             }
2107             for (var prop in paper) if (paper.hasOwnProperty(prop)) {
2108                 res[prop] = paper[prop];
2109             }
2110             plugins.call(res, res, R.fn);
2111             res.clear = function () {
2112                 while (c.firstChild) {
2113                     c.removeChild(c.firstChild);
2114                 }
2115             };
2116             res.raphael = R;
2117             return res;
2118         };
2119         paper.remove = function () {
2120             this.canvas.parentNode.removeChild(this.canvas);
2121         };
2122     }
2123
2124     // rest
2125     // Safari or Chrome (WebKit) rendering bug workaround method
2126     if ({"Apple Computer, Inc.": 1, "Google Inc.": 1}[navigator.vendor]) {
2127         paper.safari = function () {
2128             var rect = this.rect(-99, -99, this.width + 99, this.height + 99);
2129             setTimeout(function () {rect.remove();});
2130         };
2131     } else {
2132         paper.safari = function () {};
2133     }
2134
2135     // Events
2136     var addEvent = (function () {
2137         if (doc.addEventListener) {
2138             return function (obj, type, fn, element) {
2139                 var f = function (e) {
2140                     return fn.call(element, e);
2141                 };
2142                 obj.addEventListener(type, f, false);
2143                 return function () {
2144                     obj.removeEventListener(type, f, false);
2145                     return true;
2146                 };
2147             };
2148         } else if (doc.attachEvent) {
2149             return function (obj, type, fn, element) {
2150                 var f = function (e) {
2151                     return fn.call(element, e || win.event);
2152                 };
2153                 obj.attachEvent("on" + type, f);
2154                 var detacher = function () {
2155                     obj.detachEvent("on" + type, f);
2156                     return true;
2157                 };
2158                 if (type == "mouseover") {
2159                     obj.attachEvent("onmouseenter", f);
2160                     return function () {
2161                         obj.detachEvent("onmouseenter", f);
2162                         return detacher();
2163                     };
2164                 } else if (type == "mouseout") {
2165                     obj.attachEvent("onmouseleave", f);
2166                     return function () {
2167                         obj.detachEvent("onmouseleave", f);
2168                         return detacher();
2169                     };
2170                 }
2171                 return detacher;
2172             };
2173         }
2174     })();
2175     for (var i = events.length; i--;) {
2176         (function (eventName) {
2177             Element.prototype[eventName] = function (fn) {
2178                 if (R.is(fn, "function")) {
2179                     this.events = this.events || {};
2180                     this.events[eventName] = this.events[eventName] || {};
2181                     this.events[eventName][fn] = this.events[eventName][fn] || [];
2182                     this.events[eventName][fn].push(addEvent(this.shape || this.node, eventName, fn, this));
2183                 }
2184                 return this;
2185             };
2186             Element.prototype["un" + eventName] = function (fn) {
2187                 this.events &&
2188                 this.events[eventName] &&
2189                 this.events[eventName][fn] &&
2190                 this.events[eventName][fn].length &&
2191                 this.events[eventName][fn].shift()() &&
2192                 !this.events[eventName][fn].length &&
2193                 delete this.events[eventName][fn];
2194             };
2195
2196         })(events[i]);
2197     }
2198     paper.circle = function (x, y, r) {
2199         return theCircle(this, x || 0, y || 0, r || 0);
2200     };
2201     paper.rect = function (x, y, w, h, r) {
2202         return theRect(this, x || 0, y || 0, w || 0, h || 0, r || 0);
2203     };
2204     paper.ellipse = function (x, y, rx, ry) {
2205         return theEllipse(this, x || 0, y || 0, rx || 0, ry || 0);
2206     };
2207     paper.path = function (pathString) {
2208         pathString && !R.is(pathString, "string") && !R.is(pathString[0], "array") && (pathString += "");
2209         return thePath(R.format.apply(R, arguments), this);
2210     };
2211     paper.image = function (src, x, y, w, h) {
2212         return theImage(this, src || "about:blank", x || 0, y || 0, w || 0, h || 0);
2213     };
2214     paper.text = function (x, y, text) {
2215         return theText(this, x || 0, y || 0, text || "");
2216     };
2217     paper.set = function (itemsArray) {
2218         arguments.length > 1 && (itemsArray = Array.prototype.splice.call(arguments, 0, arguments.length));
2219         return new Set(itemsArray);
2220     };
2221     paper.setSize = setSize;
2222     Element.prototype.stop = function () {
2223         clearTimeout(this.animation_in_progress);
2224         return this;
2225     };
2226     Element.prototype.scale = function (x, y, cx, cy) {
2227         if (x == null && y == null) {
2228             return {x: this._.sx, y: this._.sy, toString: function () { return this.x + " " + this.y; }};
2229         }
2230         y = y || x;
2231         !+y && (y = x);
2232         var dx,
2233             dy,
2234             dcx,
2235             dcy,
2236             a = this.attrs;
2237         if (x != 0) {
2238             var bb = this.getBBox(),
2239                 rcx = bb.x + bb.width / 2,
2240                 rcy = bb.y + bb.height / 2,
2241                 kx = x / this._.sx,
2242                 ky = y / this._.sy;
2243             cx = (+cx || cx == 0) ? cx : rcx;
2244             cy = (+cy || cy == 0) ? cy : rcy;
2245             var dirx = Math.round(x / Math.abs(x)),
2246                 diry = Math.round(y / Math.abs(y)),
2247                 s = this.node.style,
2248                 ncx = cx + (rcx - cx) * dirx * kx,
2249                 ncy = cy + (rcy - cy) * diry * ky;
2250             switch (this.type) {
2251                 case "rect":
2252                 case "image":
2253                     var neww = a.width * dirx * kx,
2254                         newh = a.height * diry * ky,
2255                         newr = a.r * Math.min(kx, ky),
2256                         newx = ncx - neww / 2,
2257                         newy = ncy - newh / 2;
2258                     this.attr({
2259                         width: neww,
2260                         height: newh,
2261                         x: newx,
2262                         y: newy,
2263                         r: newr
2264                     });
2265                     break;
2266                 case "circle":
2267                 case "ellipse":
2268                     this.attr({
2269                         rx: a.rx * kx,
2270                         ry: a.ry * ky,
2271                         r: a.r * Math.min(kx, ky),
2272                         cx: ncx,
2273                         cy: ncy
2274                     });
2275                     break;
2276                 case "path":
2277                     var path = pathToRelative(a.path),
2278                         skip = true;
2279                     for (var i = 0, ii = path.length; i < ii; i++) {
2280                         var p = path[i];
2281                         if (p[0].toUpperCase() == "M" && skip) {
2282                             continue;
2283                         } else {
2284                             skip = false;
2285                         }
2286                         if (R.svg && p[0].toUpperCase() == "A") {
2287                             p[path[i].length - 2] *= kx;
2288                             p[path[i].length - 1] *= ky;
2289                             p[1] *= kx;
2290                             p[2] *= ky;
2291                             p[5] = +(dirx + diry ? !!+p[5] : !+p[5]);
2292                         } else {
2293                             for (var j = 1, jj = p.length; j < jj; j++) {
2294                                 p[j] *= (j % 2) ? kx : ky;
2295                             }
2296                         }
2297                     }
2298                     var dim2 = pathDimensions(path),
2299                         dx = ncx - dim2.x - dim2.width / 2,
2300                         dy = ncy - dim2.y - dim2.height / 2;
2301                     path[0][1] += dx;
2302                     path[0][2] += dy;
2303
2304                     this.attr({path: path});
2305                 break;
2306             }
2307             if (this.type in {text: 1, image:1} && (dirx != 1 || diry != 1)) {
2308                 if (this.transformations) {
2309                     this.transformations[2] = "scale(".concat(dirx, ",", diry, ")");
2310                     this.node.setAttribute("transform", this.transformations.join(" "));
2311                     dx = (dirx == -1) ? -a.x - (neww || 0) : a.x;
2312                     dy = (diry == -1) ? -a.y - (newh || 0) : a.y;
2313                     this.attr({x: dx, y: dy});
2314                     a.fx = dirx - 1;
2315                     a.fy = diry - 1;
2316                 } else {
2317                     this.node.filterMatrix = " progid:DXImageTransform.Microsoft.Matrix(M11=".concat(dirx,
2318                         ", M12=0, M21=0, M22=", diry,
2319                         ", Dx=0, Dy=0, sizingmethod='auto expand', filtertype='bilinear')");
2320                     s.filter = (this.node.filterMatrix || "") + (this.node.filterOpacity || "");
2321                 }
2322             } else {
2323                 if (this.transformations) {
2324                     this.transformations[2] = "";
2325                     this.node.setAttribute("transform", this.transformations.join(" "));
2326                     a.fx = 0;
2327                     a.fy = 0;
2328                 } else {
2329                     this.node.filterMatrix = "";
2330                     s.filter = (this.node.filterMatrix || "") + (this.node.filterOpacity || "");
2331                 }
2332             }
2333             a.scale = [x, y, cx, cy].join(" ");
2334             this._.sx = x;
2335             this._.sy = y;
2336         }
2337         return this;
2338     };
2339
2340     // animation easing formulas
2341     R.easing_formulas = {
2342         linear: function (n) {
2343             return n;
2344         },
2345         "<": function (n) {
2346             return Math.pow(n, 3);
2347         },
2348         ">": function (n) {
2349             return Math.pow(n - 1, 3) + 1;
2350         },
2351         "<>": function (n) {
2352             n = n * 2;
2353             if (n < 1) {
2354                 return Math.pow(n, 3) / 2;
2355             }
2356             n -= 2;
2357             return (Math.pow(n, 3) + 2) / 2;
2358         },
2359         backIn: function (n) {
2360             var s = 1.70158;
2361             return n * n * ((s + 1) * n - s);
2362         },
2363         backOut: function (n) {
2364             n = n - 1;
2365             var s = 1.70158;
2366             return n * n * ((s + 1) * n + s) + 1;
2367         },
2368         elastic: function (n) {
2369             if (n == 0 || n == 1) {
2370                 return n;
2371             }
2372             var p = .3,
2373                 s = p / 4;
2374             return Math.pow(2, -10 * n) * Math.sin((n - s) * (2 * Math.PI) / p) + 1;
2375         },
2376         bounce: function (n) {
2377             var s = 7.5625,
2378                 p = 2.75,
2379                 l;
2380             if (n < (1 / p)) {
2381                 l = s * n * n;
2382             } else {
2383                 if (n < (2 / p)) {
2384                     n -= (1.5 / p);
2385                     l = s * n * n + .75;
2386                 } else {
2387                     if (n < (2.5 / p)) {
2388                         n -= (2.25 / p);
2389                         l = s * n * n + .9375;
2390                     } else {
2391                         n -= (2.625 / p);
2392                         l = s * n * n + .984375;
2393                     }
2394                 }
2395             }
2396             return l;
2397         }
2398     };
2399
2400     Element.prototype.animate = function (params, ms, easing, callback) {
2401         clearTimeout(this.animation_in_progress);
2402         if (R.is(easing, "function") || !easing) {
2403             callback = easing || null;
2404         }
2405         var from = {},
2406             to = {},
2407             diff = {},
2408             t = {x: 0, y: 0};
2409         for (var attr in params) if (params.hasOwnProperty(attr)) {
2410             if (attr in availableAnimAttrs) {
2411                 from[attr] = this.attr(attr);
2412                 (from[attr] == null) && (from[attr] = availableAttrs[attr]);
2413                 to[attr] = params[attr];
2414                 switch (availableAnimAttrs[attr]) {
2415                     case "number":
2416                         diff[attr] = (to[attr] - from[attr]) / ms;
2417                         break;
2418                     case "colour":
2419                         from[attr] = R.getRGB(from[attr]);
2420                         var toColour = R.getRGB(to[attr]);
2421                         diff[attr] = {
2422                             r: (toColour.r - from[attr].r) / ms,
2423                             g: (toColour.g - from[attr].g) / ms,
2424                             b: (toColour.b - from[attr].b) / ms
2425                         };
2426                         break;
2427                     case "path":
2428                         var pathes = path2curve(from[attr], to[attr]);
2429                         from[attr] = pathes[0];
2430                         to[attr] = pathes[1];
2431                         diff[attr] = [];
2432                         for (var i = 0, ii = from[attr].length; i < ii; i++) {
2433                             diff[attr][i] = [0];
2434                             for (var j = 1, jj = from[attr][i].length; j < jj; j++) {
2435                                 diff[attr][i][j] = (to[attr][i][j] - from[attr][i][j]) / ms;
2436                             }
2437                         }
2438                         break;
2439                     case "csv":
2440                         var values = (params[attr] + "").split(separator),
2441                             from2 = (from[attr] + "").split(separator);
2442                         switch (attr) {
2443                             case "translation":
2444                                 from[attr] = [0, 0];
2445                                 diff[attr] = [values[0] / ms, values[1] / ms];
2446                             break;
2447                             case "rotation":
2448                                 from[attr] = (from2[1] == values[1] && from2[2] == values[2]) ? from2 : [0, values[1], values[2]];
2449                                 diff[attr] = [(values[0] - from[attr][0]) / ms, 0, 0];
2450                             break;
2451                             case "scale":
2452                                 params[attr] = values;
2453                                 from[attr] = (from[attr] + "").split(separator);
2454                                 diff[attr] = [(values[0] - from[attr][0]) / ms, (values[1] - from[attr][1]) / ms, 0, 0];
2455                             break;
2456                             case "clip-rect":
2457                                 from[attr] = (from[attr] + "").split(separator);
2458                                 diff[attr] = [];
2459                                 var i = 4;
2460                                 while (i--) {
2461                                     diff[attr][i] = (values[i] - from[attr][i]) / ms;
2462                                 }
2463                             break;
2464                         }
2465                         to[attr] = values;
2466                 }
2467             }
2468         }
2469         var start = +new Date,
2470             prev = 0,
2471             upto255 = function (color) {
2472                 return +color > 255 ? 255 : +color;
2473             },
2474             that = this;
2475         (function tick() {
2476             var time = new Date - start,
2477                 set = {},
2478                 now;
2479             if (time < ms) {
2480                 var pos = R.easing_formulas[easing] ? R.easing_formulas[easing](time / ms) : time / ms;
2481                 for (var attr in from) if (from.hasOwnProperty(attr)) {
2482                     switch (availableAnimAttrs[attr]) {
2483                         case "number":
2484                             now = +from[attr] + pos * ms * diff[attr];
2485                             break;
2486                         case "colour":
2487                             now = "rgb(" + [
2488                                 upto255(Math.round(from[attr].r + pos * ms * diff[attr].r)),
2489                                 upto255(Math.round(from[attr].g + pos * ms * diff[attr].g)),
2490                                 upto255(Math.round(from[attr].b + pos * ms * diff[attr].b))
2491                             ].join(",") + ")";
2492                             break;
2493                         case "path":
2494                             now = [];
2495                             for (var i = 0, ii = from[attr].length; i < ii; i++) {
2496                                 now[i] = [from[attr][i][0]];
2497                                 for (var j = 1, jj = from[attr][i].length; j < jj; j++) {
2498                                     now[i][j] = +from[attr][i][j] + pos * ms * diff[attr][i][j];
2499                                 }
2500                                 now[i] = now[i].join(" ");
2501                             }
2502                             now = now.join(" ");
2503                             break;
2504                         case "csv":
2505                             switch (attr) {
2506                                 case "translation":
2507                                     var x = diff[attr][0] * (time - prev),
2508                                         y = diff[attr][1] * (time - prev);
2509                                     t.x += x;
2510                                     t.y += y;
2511                                     now = [x, y].join(" ");
2512                                 break;
2513                                 case "rotation":
2514                                     now = +from[attr][0] + pos * ms * diff[attr][0];
2515                                     from[attr][1] && (now += "," + from[attr][1] + "," + from[attr][2]);
2516                                 break;
2517                                 case "scale":
2518                                     now = [+from[attr][0] + pos * ms * diff[attr][0], +from[attr][1] + pos * ms * diff[attr][1], (2 in params[attr] ? params[attr][2] : ""), (3 in params[attr] ? params[attr][3] : "")].join(" ");
2519                                 break;
2520                                 case "clip-rect":
2521                                     now = [];
2522                                     var i = 4;
2523                                     while (i--) {
2524                                         now[i] = +from[attr][i] + pos * ms * diff[attr][i];
2525                                     }
2526                                 break;
2527                             }
2528                             break;
2529                     }
2530                     set[attr] = now;
2531                 }
2532                 that.attr(set);
2533                 that.animation_in_progress = setTimeout(tick);
2534                 R.svg && paper.safari();
2535             } else {
2536                 (t.x || t.y) && that.translate(-t.x, -t.y);
2537                 that.attr(params);
2538                 clearTimeout(that.animation_in_progress);
2539                 R.svg && paper.safari();
2540                 (R.is(callback, "function")) && callback.call(that);
2541             }
2542             prev = time;
2543         })();
2544         return this;
2545     };
2546     Element.prototype.translate = function (x, y) {
2547         if (x == null) {
2548             return {x: this._.tx, y: this._.ty};
2549         }
2550         this._.tx += +x;
2551         this._.ty += +y;
2552         switch (this.type) {
2553             case "circle":
2554             case "ellipse":
2555                 this.attr({cx: +x + this.attrs.cx, cy: +y + this.attrs.cy});
2556                 break;
2557             case "rect":
2558             case "image":
2559             case "text":
2560                 this.attr({x: +x + this.attrs.x, y: +y + this.attrs.y});
2561                 break;
2562             case "path":
2563                 var path = pathToRelative(this.attrs.path);
2564                 path[0][1] += +x;
2565                 path[0][2] += +y;
2566                 this.attr({path: path});
2567             break;
2568         }
2569         return this;
2570     };
2571     Element.prototype.toString = function () {
2572         return "Rapha\u00ebl\u2019s object";
2573     };
2574     
2575
2576     // Set
2577     var Set = function (items) {
2578         this.items = [];
2579         this.length = 0;
2580         if (items) {
2581             for (var i = 0, ii = items.length; i < ii; i++) {
2582                 if (items[i] && (items[i].constructor == Element || items[i].constructor == Set)) {
2583                     this[this.items.length] = this.items[this.items.length] = items[i];
2584                     this.length++;
2585                 }
2586             }
2587         }
2588     };
2589     Set.prototype.push = function () {
2590         var item,
2591             len;
2592         for (var i = 0, ii = arguments.length; i < ii; i++) {
2593             item = arguments[i];
2594             if (item && (item.constructor == Element || item.constructor == Set)) {
2595                 len = this.items.length;
2596                 this[len] = this.items[len] = item;
2597                 this.length++;
2598             }
2599         }
2600         return this;
2601     };
2602     Set.prototype.pop = function () {
2603         delete this[this.length--];
2604         return this.items.pop();
2605     };
2606     for (var method in Element.prototype) if (Element.prototype.hasOwnProperty(method)) {
2607         Set.prototype[method] = (function (methodname) {
2608             return function () {
2609                 for (var i = 0, ii = this.items.length; i < ii; i++) {
2610                     this.items[i][methodname].apply(this.items[i], arguments);
2611                 }
2612                 return this;
2613             };
2614         })(method);
2615     }
2616     Set.prototype.attr = function (name, value) {
2617         if (name && R.is(name, "array") && R.is(name[0], "object")) {
2618             for (var j = 0, jj = name.length; j < jj; j++) {
2619                 this.items[j].attr(name[j]);
2620             }
2621         } else {
2622             for (var i = 0, ii = this.items.length; i < ii; i++) {
2623                 this.items[i].attr.apply(this.items[i], arguments);
2624             }
2625         }
2626         return this;
2627     };
2628     Set.prototype.animate = function (params, ms, easing, callback) {
2629         if (R.is(easing, "function") || !easing) {
2630             callback = easing || null;
2631         }
2632         var len = this.items.length,
2633             i = len,
2634             set = this;
2635         if (callback) {
2636             var collector = function () {
2637                 !--len && callback.call(set);
2638             };
2639             while (i--) {
2640                 this.items[i].animate(params, ms, easing || collector, collector);
2641             }
2642         } else {
2643             while (i--) {
2644                 this.items[i].animate(params, ms, easing);
2645             }
2646         }
2647         return this;
2648     };
2649     
2650     Set.prototype.getBBox = function () {
2651         var x = [],
2652             y = [],
2653             w = [],
2654             h = [];
2655         for (var i = this.items.length; i--;) {
2656             var box = this.items[i].getBBox();
2657             x.push(box.x);
2658             y.push(box.y);
2659             w.push(box.x + box.width);
2660             h.push(box.y + box.height);
2661         }
2662         x = Math.min.apply(Math, x);
2663         y = Math.min.apply(Math, y);
2664         return {
2665             x: x,
2666             y: y,
2667             width: Math.max.apply(Math, w) - x,
2668             height: Math.max.apply(Math, h) - y
2669         };
2670     };
2671
2672     R.registerFont = function (font) {
2673         if (!font.face) {
2674             return font;
2675         }
2676         this.fonts = this.fonts || {};
2677         var fontcopy = {
2678                 w: font.w,
2679                 face: {},
2680                 glyphs: {}
2681             },
2682             family = font.face["font-family"];
2683         for (var prop in font.face) if (font.face.hasOwnProperty(prop)) {
2684             fontcopy.face[prop] = font.face[prop];
2685         }
2686         if (this.fonts[family]) {
2687             this.fonts[family].push(fontcopy);
2688         } else {
2689             this.fonts[family] = [fontcopy];
2690         }
2691         if (!font.svg) {
2692             fontcopy.face["units-per-em"] = parseInt(font.face["units-per-em"], 10);
2693             for (var glyph in font.glyphs) if (font.glyphs.hasOwnProperty(glyph)) {
2694                 var path = font.glyphs[glyph];
2695                 fontcopy.glyphs[glyph] = {
2696                     w: path.w,
2697                     k: {},
2698                     d: path.d && "M" + path.d.replace(/[mlcxtrv]/g, function (command) {
2699                             return {l: "L", c: "C", x: "z", t: "m", r: "l", v: "c"}[command] || "M";
2700                         }) + "z"
2701                 };
2702                 if (path.k) {
2703                     for (var k in path.k) if (path.hasOwnProperty(k)) {
2704                         fontcopy.glyphs[glyph].k[k] = path.k[k];
2705                     }
2706                 }
2707             }
2708         }
2709         return font;
2710     };
2711     paper.getFont = function (family, weight, style, stretch) {
2712         stretch = stretch || "normal";
2713         style = style || "normal";
2714         weight = +weight || {normal: 400, bold: 700, lighter: 300, bolder: 800}[weight] || 400;
2715         var font = R.fonts[family];
2716         if (!font) {
2717             var name = new RegExp("(^|\\s)" + family.replace(/[^\w\d\s+!~.:_-]/g, "") + "(\\s|$)", "i");
2718             for (var fontName in R.fonts) if (R.fonts.hasOwnProperty(fontName)) {
2719                 if (name.test(fontName)) {
2720                     font = R.fonts[fontName];
2721                     break;
2722                 }
2723             }
2724         }
2725         var thefont;
2726         if (font) {
2727             for (var i = 0, ii = font.length; i < ii; i++) {
2728                 thefont = font[i];
2729                 if (thefont.face["font-weight"] == weight && (thefont.face["font-style"] == style || !thefont.face["font-style"]) && thefont.face["font-stretch"] == stretch) {
2730                     break;
2731                 }
2732             }
2733         }
2734         return thefont;
2735     };
2736     paper.print = function (x, y, string, font, size) {
2737         var out = this.set(),
2738             letters = (string + "").split(""),
2739             shift = 0,
2740             path = "",
2741             scale;
2742         R.is(font, "string") && (font = this.getFont(font));
2743         if (font) {
2744             scale = (size || 16) / font.face["units-per-em"];
2745             for (var i = 0, ii = letters.length; i < ii; i++) {
2746                 var prev = i && font.glyphs[letters[i - 1]] || {},
2747                     curr = font.glyphs[letters[i]];
2748                 shift += i ? (prev.w || font.w) + (prev.k && prev.k[letters[i]] || 0) : 0;
2749                 curr && curr.d && out.push(this.path(curr.d).attr({fill: "#000", stroke: "none", translation: [shift, 0]}));
2750             }
2751             out.scale(scale, scale, 0, y).translate(x, (size || 16) / 2);
2752         }
2753         return out;
2754     };
2755
2756     R.format = function (token) {
2757         var args = R.is(arguments[1], "array") ? [0].concat(arguments[1]) : arguments;
2758         token && R.is(token, "string") && args.length - 1 && (token = token.replace(/\{(\d+)\}/g, function (str, i) {
2759             return args[++i] == null ? "" : args[i];
2760         }));
2761         return token || "";
2762     };
2763     R.ninja = function () {
2764         var r = window.Raphael, u;
2765         if (oldRaphael.was) {
2766             window.Raphael = oldRaphael.is;
2767         } else {
2768             try {
2769                 delete window.Raphael;
2770             } catch (e) {
2771                 window.Raphael = u;
2772             }
2773         }
2774         return r;
2775     };
2776     R.el = Element.prototype;
2777     return R;
2778 })();