Upgrade to ExtJS 4.0.1 - Released 05/18/2011
[extjs.git] / docs / source / Draw.html
1 <!DOCTYPE html>
2 <html>
3 <head>
4   <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
5   <title>The source code</title>
6   <link href="../prettify/prettify.css" type="text/css" rel="stylesheet" />
7   <script type="text/javascript" src="../prettify/prettify.js"></script>
8   <style type="text/css">
9     .highlight { display: block; background-color: #ddd; }
10   </style>
11   <script type="text/javascript">
12     function highlight() {
13       document.getElementById(location.hash.replace(/#/, "")).className = "highlight";
14     }
15   </script>
16 </head>
17 <body onload="prettyPrint(); highlight();">
18   <pre class="prettyprint lang-js">/*
19  * @class Ext.draw.Draw
20  * Base Drawing class.  Provides base drawing functions.
21  */
22
23 Ext.define('Ext.draw.Draw', {
24     /* Begin Definitions */
25
26     singleton: true,
27
28     requires: ['Ext.draw.Color'],
29
30     /* End Definitions */
31
32     pathToStringRE: /,?([achlmqrstvxz]),?/gi,
33     pathCommandRE: /([achlmqstvz])[\s,]*((-?\d*\.?\d*(?:e[-+]?\d+)?\s*,?\s*)+)/ig,
34     pathValuesRE: /(-?\d*\.?\d*(?:e[-+]?\d+)?)\s*,?\s*/ig,
35     stopsRE: /^(\d+%?)$/,
36     radian: Math.PI / 180,
37
38     availableAnimAttrs: {
39         along: &quot;along&quot;,
40         blur: null,
41         &quot;clip-rect&quot;: &quot;csv&quot;,
42         cx: null,
43         cy: null,
44         fill: &quot;color&quot;,
45         &quot;fill-opacity&quot;: null,
46         &quot;font-size&quot;: null,
47         height: null,
48         opacity: null,
49         path: &quot;path&quot;,
50         r: null,
51         rotation: &quot;csv&quot;,
52         rx: null,
53         ry: null,
54         scale: &quot;csv&quot;,
55         stroke: &quot;color&quot;,
56         &quot;stroke-opacity&quot;: null,
57         &quot;stroke-width&quot;: null,
58         translation: &quot;csv&quot;,
59         width: null,
60         x: null,
61         y: null
62     },
63
64     is: function(o, type) {
65         type = String(type).toLowerCase();
66         return (type == &quot;object&quot; &amp;&amp; o === Object(o)) ||
67             (type == &quot;undefined&quot; &amp;&amp; typeof o == type) ||
68             (type == &quot;null&quot; &amp;&amp; o === null) ||
69             (type == &quot;array&quot; &amp;&amp; Array.isArray &amp;&amp; Array.isArray(o)) ||
70             (Object.prototype.toString.call(o).toLowerCase().slice(8, -1)) == type;
71     },
72
73     ellipsePath: function(sprite) {
74         var attr = sprite.attr;
75         return Ext.String.format(&quot;M{0},{1}A{2},{3},0,1,1,{0},{4}A{2},{3},0,1,1,{0},{1}z&quot;, attr.x, attr.y - attr.ry, attr.rx, attr.ry, attr.y + attr.ry);
76     },
77
78     rectPath: function(sprite) {
79         var attr = sprite.attr;
80         if (attr.radius) {
81             return Ext.String.format(&quot;M{0},{1}l{2},0a{3},{3},0,0,1,{3},{3}l0,{5}a{3},{3},0,0,1,{4},{3}l{6},0a{3},{3},0,0,1,{4},{4}l0,{7}a{3},{3},0,0,1,{3},{4}z&quot;, attr.x + attr.radius, attr.y, attr.width - attr.radius * 2, attr.radius, -attr.radius, attr.height - attr.radius * 2, attr.radius * 2 - attr.width, attr.radius * 2 - attr.height);
82         }
83         else {
84             return Ext.String.format(&quot;M{0},{1}l{2},0,0,{3},{4},0z&quot;, attr.x, attr.y, attr.width, attr.height, -attr.width);
85         }
86     },
87
88     // To be deprecated, converts itself (an arrayPath) to a proper SVG path string
89     path2string: function () {
90         return this.join(&quot;,&quot;).replace(Ext.draw.Draw.pathToStringRE, &quot;$1&quot;);
91     },
92
93     // Convert the passed arrayPath to a proper SVG path string (d attribute)
94     pathToString: function(arrayPath) {
95         return arrayPath.join(&quot;,&quot;).replace(Ext.draw.Draw.pathToStringRE, &quot;$1&quot;);
96     },
97
98     parsePathString: function (pathString) {
99         if (!pathString) {
100             return null;
101         }
102         var paramCounts = {a: 7, c: 6, h: 1, l: 2, m: 2, q: 4, s: 4, t: 2, v: 1, z: 0},
103             data = [],
104             me = this;
105         if (me.is(pathString, &quot;array&quot;) &amp;&amp; me.is(pathString[0], &quot;array&quot;)) { // rough assumption
106             data = me.pathClone(pathString);
107         }
108         if (!data.length) {
109             String(pathString).replace(me.pathCommandRE, function (a, b, c) {
110                 var params = [],
111                     name = b.toLowerCase();
112                 c.replace(me.pathValuesRE, function (a, b) {
113                     b &amp;&amp; params.push(+b);
114                 });
115                 if (name == &quot;m&quot; &amp;&amp; params.length &gt; 2) {
116                     data.push([b].concat(params.splice(0, 2)));
117                     name = &quot;l&quot;;
118                     b = (b == &quot;m&quot;) ? &quot;l&quot; : &quot;L&quot;;
119                 }
120                 while (params.length &gt;= paramCounts[name]) {
121                     data.push([b].concat(params.splice(0, paramCounts[name])));
122                     if (!paramCounts[name]) {
123                         break;
124                     }
125                 }
126             });
127         }
128         data.toString = me.path2string;
129         return data;
130     },
131
132     mapPath: function (path, matrix) {
133         if (!matrix) {
134             return path;
135         }
136         var x, y, i, ii, j, jj, pathi;
137         path = this.path2curve(path);
138         for (i = 0, ii = path.length; i &lt; ii; i++) {
139             pathi = path[i];
140             for (j = 1, jj = pathi.length; j &lt; jj-1; j += 2) {
141                 x = matrix.x(pathi[j], pathi[j + 1]);
142                 y = matrix.y(pathi[j], pathi[j + 1]);
143                 pathi[j] = x;
144                 pathi[j + 1] = y;
145             }
146         }
147         return path;
148     },
149
150     pathClone: function(pathArray) {
151         var res = [],
152             j, jj, i, ii;
153         if (!this.is(pathArray, &quot;array&quot;) || !this.is(pathArray &amp;&amp; pathArray[0], &quot;array&quot;)) { // rough assumption
154             pathArray = this.parsePathString(pathArray);
155         }
156         for (i = 0, ii = pathArray.length; i &lt; ii; i++) {
157             res[i] = [];
158             for (j = 0, jj = pathArray[i].length; j &lt; jj; j++) {
159                 res[i][j] = pathArray[i][j];
160             }
161         }
162         res.toString = this.path2string;
163         return res;
164     },
165
166     pathToAbsolute: function (pathArray) {
167         if (!this.is(pathArray, &quot;array&quot;) || !this.is(pathArray &amp;&amp; pathArray[0], &quot;array&quot;)) { // rough assumption
168             pathArray = this.parsePathString(pathArray);
169         }
170         var res = [],
171             x = 0,
172             y = 0,
173             mx = 0,
174             my = 0,
175             i = 0,
176             ln = pathArray.length,
177             r, pathSegment, j, ln2;
178         // MoveTo initial x/y position
179         if (ln &amp;&amp; pathArray[0][0] == &quot;M&quot;) {
180             x = +pathArray[0][1];
181             y = +pathArray[0][2];
182             mx = x;
183             my = y;
184             i++;
185             res[0] = [&quot;M&quot;, x, y];
186         }
187         for (; i &lt; ln; i++) {
188             r = res[i] = [];
189             pathSegment = pathArray[i];
190             if (pathSegment[0] != pathSegment[0].toUpperCase()) {
191                 r[0] = pathSegment[0].toUpperCase();
192                 switch (r[0]) {
193                     // Elliptical Arc
194                     case &quot;A&quot;:
195                         r[1] = pathSegment[1];
196                         r[2] = pathSegment[2];
197                         r[3] = pathSegment[3];
198                         r[4] = pathSegment[4];
199                         r[5] = pathSegment[5];
200                         r[6] = +(pathSegment[6] + x);
201                         r[7] = +(pathSegment[7] + y);
202                         break;
203                     // Vertical LineTo
204                     case &quot;V&quot;:
205                         r[1] = +pathSegment[1] + y;
206                         break;
207                     // Horizontal LineTo
208                     case &quot;H&quot;:
209                         r[1] = +pathSegment[1] + x;
210                         break;
211                     case &quot;M&quot;:
212                     // MoveTo
213                         mx = +pathSegment[1] + x;
214                         my = +pathSegment[2] + y;
215                     default:
216                         j = 1;
217                         ln2 = pathSegment.length;
218                         for (; j &lt; ln2; j++) {
219                             r[j] = +pathSegment[j] + ((j % 2) ? x : y);
220                         }
221                 }
222             }
223             else {
224                 j = 0;
225                 ln2 = pathSegment.length;
226                 for (; j &lt; ln2; j++) {
227                     res[i][j] = pathSegment[j];
228                 }
229             }
230             switch (r[0]) {
231                 // ClosePath
232                 case &quot;Z&quot;:
233                     x = mx;
234                     y = my;
235                     break;
236                 // Horizontal LineTo
237                 case &quot;H&quot;:
238                     x = r[1];
239                     break;
240                 // Vertical LineTo
241                 case &quot;V&quot;:
242                     y = r[1];
243                     break;
244                 // MoveTo
245                 case &quot;M&quot;:
246                     pathSegment = res[i];
247                     ln2 = pathSegment.length;
248                     mx = pathSegment[ln2 - 2];
249                     my = pathSegment[ln2 - 1];
250                 default:
251                     pathSegment = res[i];
252                     ln2 = pathSegment.length;
253                     x = pathSegment[ln2 - 2];
254                     y = pathSegment[ln2 - 1];
255             }
256         }
257         res.toString = this.path2string;
258         return res;
259     },
260
261     // TO BE DEPRECATED
262     pathToRelative: function (pathArray) {
263         if (!this.is(pathArray, &quot;array&quot;) || !this.is(pathArray &amp;&amp; pathArray[0], &quot;array&quot;)) {
264             pathArray = this.parsePathString(pathArray);
265         }
266         var res = [],
267             x = 0,
268             y = 0,
269             mx = 0,
270             my = 0,
271             start = 0;
272         if (pathArray[0][0] == &quot;M&quot;) {
273             x = pathArray[0][1];
274             y = pathArray[0][2];
275             mx = x;
276             my = y;
277             start++;
278             res.push([&quot;M&quot;, x, y]);
279         }
280         for (var i = start, ii = pathArray.length; i &lt; ii; i++) {
281             var r = res[i] = [],
282                 pa = pathArray[i];
283             if (pa[0] != pa[0].toLowerCase()) {
284                 r[0] = pa[0].toLowerCase();
285                 switch (r[0]) {
286                     case &quot;a&quot;:
287                         r[1] = pa[1];
288                         r[2] = pa[2];
289                         r[3] = pa[3];
290                         r[4] = pa[4];
291                         r[5] = pa[5];
292                         r[6] = +(pa[6] - x).toFixed(3);
293                         r[7] = +(pa[7] - y).toFixed(3);
294                         break;
295                     case &quot;v&quot;:
296                         r[1] = +(pa[1] - y).toFixed(3);
297                         break;
298                     case &quot;m&quot;:
299                         mx = pa[1];
300                         my = pa[2];
301                     default:
302                         for (var j = 1, jj = pa.length; j &lt; jj; j++) {
303                             r[j] = +(pa[j] - ((j % 2) ? x : y)).toFixed(3);
304                         }
305                 }
306             } else {
307                 r = res[i] = [];
308                 if (pa[0] == &quot;m&quot;) {
309                     mx = pa[1] + x;
310                     my = pa[2] + y;
311                 }
312                 for (var k = 0, kk = pa.length; k &lt; kk; k++) {
313                     res[i][k] = pa[k];
314                 }
315             }
316             var len = res[i].length;
317             switch (res[i][0]) {
318                 case &quot;z&quot;:
319                     x = mx;
320                     y = my;
321                     break;
322                 case &quot;h&quot;:
323                     x += +res[i][len - 1];
324                     break;
325                 case &quot;v&quot;:
326                     y += +res[i][len - 1];
327                     break;
328                 default:
329                     x += +res[i][len - 2];
330                     y += +res[i][len - 1];
331             }
332         }
333         res.toString = this.path2string;
334         return res;
335     },
336
337     // Returns a path converted to a set of curveto commands
338     path2curve: function (path) {
339         var me = this,
340             points = me.pathToAbsolute(path),
341             ln = points.length,
342             attrs = {x: 0, y: 0, bx: 0, by: 0, X: 0, Y: 0, qx: null, qy: null},
343             i, seg, segLn, point;
344             
345         for (i = 0; i &lt; ln; i++) {
346             points[i] = me.command2curve(points[i], attrs);
347             if (points[i].length &gt; 7) {
348                     points[i].shift();
349                     point = points[i];
350                     while (point.length) {
351                         points.splice(i++, 0, [&quot;C&quot;].concat(point.splice(0, 6)));
352                     }
353                     points.splice(i, 1);
354                     ln = points.length;
355                 }
356             seg = points[i];
357             segLn = seg.length;
358             attrs.x = seg[segLn - 2];
359             attrs.y = seg[segLn - 1];
360             attrs.bx = parseFloat(seg[segLn - 4]) || attrs.x;
361             attrs.by = parseFloat(seg[segLn - 3]) || attrs.y;
362         }
363         return points;
364     },
365     
366     interpolatePaths: function (path, path2) {
367         var me = this,
368             p = me.pathToAbsolute(path),
369             p2 = me.pathToAbsolute(path2),
370             attrs = {x: 0, y: 0, bx: 0, by: 0, X: 0, Y: 0, qx: null, qy: null},
371             attrs2 = {x: 0, y: 0, bx: 0, by: 0, X: 0, Y: 0, qx: null, qy: null},
372             fixArc = function (pp, i) {
373                 if (pp[i].length &gt; 7) {
374                     pp[i].shift();
375                     var pi = pp[i];
376                     while (pi.length) {
377                         pp.splice(i++, 0, [&quot;C&quot;].concat(pi.splice(0, 6)));
378                     }
379                     pp.splice(i, 1);
380                     ii = Math.max(p.length, p2.length || 0);
381                 }
382             },
383             fixM = function (path1, path2, a1, a2, i) {
384                 if (path1 &amp;&amp; path2 &amp;&amp; path1[i][0] == &quot;M&quot; &amp;&amp; path2[i][0] != &quot;M&quot;) {
385                     path2.splice(i, 0, [&quot;M&quot;, a2.x, a2.y]);
386                     a1.bx = 0;
387                     a1.by = 0;
388                     a1.x = path1[i][1];
389                     a1.y = path1[i][2];
390                     ii = Math.max(p.length, p2.length || 0);
391                 }
392             };
393         for (var i = 0, ii = Math.max(p.length, p2.length || 0); i &lt; ii; i++) {
394             p[i] = me.command2curve(p[i], attrs);
395             fixArc(p, i);
396             (p2[i] = me.command2curve(p2[i], attrs2));
397             fixArc(p2, i);
398             fixM(p, p2, attrs, attrs2, i);
399             fixM(p2, p, attrs2, attrs, i);
400             var seg = p[i],
401                 seg2 = p2[i],
402                 seglen = seg.length,
403                 seg2len = seg2.length;
404             attrs.x = seg[seglen - 2];
405             attrs.y = seg[seglen - 1];
406             attrs.bx = parseFloat(seg[seglen - 4]) || attrs.x;
407             attrs.by = parseFloat(seg[seglen - 3]) || attrs.y;
408             attrs2.bx = (parseFloat(seg2[seg2len - 4]) || attrs2.x);
409             attrs2.by = (parseFloat(seg2[seg2len - 3]) || attrs2.y);
410             attrs2.x = seg2[seg2len - 2];
411             attrs2.y = seg2[seg2len - 1];
412         }
413         return [p, p2];
414     },
415     
416     //Returns any path command as a curveto command based on the attrs passed
417     command2curve: function (pathCommand, d) {
418         var me = this;
419         if (!pathCommand) {
420             return [&quot;C&quot;, d.x, d.y, d.x, d.y, d.x, d.y];
421         }
422         if (pathCommand[0] != &quot;T&quot; &amp;&amp; pathCommand[0] != &quot;Q&quot;) {
423             d.qx = d.qy = null;
424         }
425         switch (pathCommand[0]) {
426             case &quot;M&quot;:
427                 d.X = pathCommand[1];
428                 d.Y = pathCommand[2];
429                 break;
430             case &quot;A&quot;:
431                 pathCommand = [&quot;C&quot;].concat(me.arc2curve.apply(me, [d.x, d.y].concat(pathCommand.slice(1))));
432                 break;
433             case &quot;S&quot;:
434                 pathCommand = [&quot;C&quot;, d.x + (d.x - (d.bx || d.x)), d.y + (d.y - (d.by || d.y))].concat(pathCommand.slice(1));
435                 break;
436             case &quot;T&quot;:
437                 d.qx = d.x + (d.x - (d.qx || d.x));
438                 d.qy = d.y + (d.y - (d.qy || d.y));
439                 pathCommand = [&quot;C&quot;].concat(me.quadratic2curve(d.x, d.y, d.qx, d.qy, pathCommand[1], pathCommand[2]));
440                 break;
441             case &quot;Q&quot;:
442                 d.qx = pathCommand[1];
443                 d.qy = pathCommand[2];
444                 pathCommand = [&quot;C&quot;].concat(me.quadratic2curve(d.x, d.y, pathCommand[1], pathCommand[2], pathCommand[3], pathCommand[4]));
445                 break;
446             case &quot;L&quot;:
447                 pathCommand = [&quot;C&quot;].concat(d.x, d.y, pathCommand[1], pathCommand[2], pathCommand[1], pathCommand[2]);
448                 break;
449             case &quot;H&quot;:
450                 pathCommand = [&quot;C&quot;].concat(d.x, d.y, pathCommand[1], d.y, pathCommand[1], d.y);
451                 break;
452             case &quot;V&quot;:
453                 pathCommand = [&quot;C&quot;].concat(d.x, d.y, d.x, pathCommand[1], d.x, pathCommand[1]);
454                 break;
455             case &quot;Z&quot;:
456                 pathCommand = [&quot;C&quot;].concat(d.x, d.y, d.X, d.Y, d.X, d.Y);
457                 break;
458         }
459         return pathCommand;
460     },
461
462     quadratic2curve: function (x1, y1, ax, ay, x2, y2) {
463         var _13 = 1 / 3,
464             _23 = 2 / 3;
465         return [
466                 _13 * x1 + _23 * ax,
467                 _13 * y1 + _23 * ay,
468                 _13 * x2 + _23 * ax,
469                 _13 * y2 + _23 * ay,
470                 x2,
471                 y2
472             ];
473     },
474     
475     rotate: function (x, y, rad) {
476         var cos = Math.cos(rad),
477             sin = Math.sin(rad),
478             X = x * cos - y * sin,
479             Y = x * sin + y * cos;
480         return {x: X, y: Y};
481     },
482
483     arc2curve: function (x1, y1, rx, ry, angle, large_arc_flag, sweep_flag, x2, y2, recursive) {
484         // for more information of where this Math came from visit:
485         // http://www.w3.org/TR/SVG11/implnote.html#ArcImplementationNotes
486         var me = this,
487             PI = Math.PI,
488             radian = me.radian,
489             _120 = PI * 120 / 180,
490             rad = radian * (+angle || 0),
491             res = [],
492             math = Math,
493             mcos = math.cos,
494             msin = math.sin,
495             msqrt = math.sqrt,
496             mabs = math.abs,
497             masin = math.asin,
498             xy, cos, sin, x, y, h, rx2, ry2, k, cx, cy, f1, f2, df, c1, s1, c2, s2,
499             t, hx, hy, m1, m2, m3, m4, newres, i, ln, f2old, x2old, y2old;
500         if (!recursive) {
501             xy = me.rotate(x1, y1, -rad);
502             x1 = xy.x;
503             y1 = xy.y;
504             xy = me.rotate(x2, y2, -rad);
505             x2 = xy.x;
506             y2 = xy.y;
507             cos = mcos(radian * angle);
508             sin = msin(radian * angle);
509             x = (x1 - x2) / 2;
510             y = (y1 - y2) / 2;
511             h = (x * x) / (rx * rx) + (y * y) / (ry * ry);
512             if (h &gt; 1) {
513                 h = msqrt(h);
514                 rx = h * rx;
515                 ry = h * ry;
516             }
517             rx2 = rx * rx;
518             ry2 = ry * ry;
519             k = (large_arc_flag == sweep_flag ? -1 : 1) *
520                     msqrt(mabs((rx2 * ry2 - rx2 * y * y - ry2 * x * x) / (rx2 * y * y + ry2 * x * x)));
521             cx = k * rx * y / ry + (x1 + x2) / 2;
522             cy = k * -ry * x / rx + (y1 + y2) / 2;
523             f1 = masin(((y1 - cy) / ry).toFixed(7));
524             f2 = masin(((y2 - cy) / ry).toFixed(7));
525
526             f1 = x1 &lt; cx ? PI - f1 : f1;
527             f2 = x2 &lt; cx ? PI - f2 : f2;
528             if (f1 &lt; 0) {
529                 f1 = PI * 2 + f1;
530             }
531             if (f2 &lt; 0) {
532                 f2 = PI * 2 + f2;
533             }
534             if (sweep_flag &amp;&amp; f1 &gt; f2) {
535                 f1 = f1 - PI * 2;
536             }
537             if (!sweep_flag &amp;&amp; f2 &gt; f1) {
538                 f2 = f2 - PI * 2;
539             }
540         }
541         else {
542             f1 = recursive[0];
543             f2 = recursive[1];
544             cx = recursive[2];
545             cy = recursive[3];
546         }
547         df = f2 - f1;
548         if (mabs(df) &gt; _120) {
549             f2old = f2;
550             x2old = x2;
551             y2old = y2;
552             f2 = f1 + _120 * (sweep_flag &amp;&amp; f2 &gt; f1 ? 1 : -1);
553             x2 = cx + rx * mcos(f2);
554             y2 = cy + ry * msin(f2);
555             res = me.arc2curve(x2, y2, rx, ry, angle, 0, sweep_flag, x2old, y2old, [f2, f2old, cx, cy]);
556         }
557         df = f2 - f1;
558         c1 = mcos(f1);
559         s1 = msin(f1);
560         c2 = mcos(f2);
561         s2 = msin(f2);
562         t = math.tan(df / 4);
563         hx = 4 / 3 * rx * t;
564         hy = 4 / 3 * ry * t;
565         m1 = [x1, y1];
566         m2 = [x1 + hx * s1, y1 - hy * c1];
567         m3 = [x2 + hx * s2, y2 - hy * c2];
568         m4 = [x2, y2];
569         m2[0] = 2 * m1[0] - m2[0];
570         m2[1] = 2 * m1[1] - m2[1];
571         if (recursive) {
572             return [m2, m3, m4].concat(res);
573         }
574         else {
575             res = [m2, m3, m4].concat(res).join().split(&quot;,&quot;);
576             newres = [];
577             ln = res.length;
578             for (i = 0;  i &lt; ln; i++) {
579                 newres[i] = i % 2 ? me.rotate(res[i - 1], res[i], rad).y : me.rotate(res[i], res[i + 1], rad).x;
580             }
581             return newres;
582         }
583     },
584
585     // TO BE DEPRECATED
586     rotateAndTranslatePath: function (sprite) {
587         var alpha = sprite.rotation.degrees,
588             cx = sprite.rotation.x,
589             cy = sprite.rotation.y,
590             dx = sprite.translation.x,
591             dy = sprite.translation.y,
592             path,
593             i,
594             p,
595             xy,
596             j,
597             res = [];
598         if (!alpha &amp;&amp; !dx &amp;&amp; !dy) {
599             return this.pathToAbsolute(sprite.attr.path);
600         }
601         dx = dx || 0;
602         dy = dy || 0;
603         path = this.pathToAbsolute(sprite.attr.path);
604         for (i = path.length; i--;) {
605             p = res[i] = path[i].slice();
606             if (p[0] == &quot;A&quot;) {
607                 xy = this.rotatePoint(p[6], p[7], alpha, cx, cy);
608                 p[6] = xy.x + dx;
609                 p[7] = xy.y + dy;
610             } else {
611                 j = 1;
612                 while (p[j + 1] != null) {
613                     xy = this.rotatePoint(p[j], p[j + 1], alpha, cx, cy);
614                     p[j] = xy.x + dx;
615                     p[j + 1] = xy.y + dy;
616                     j += 2;
617                 }
618             }
619         }
620         return res;
621     },
622
623     // TO BE DEPRECATED
624     rotatePoint: function (x, y, alpha, cx, cy) {
625         if (!alpha) {
626             return {
627                 x: x,
628                 y: y
629             };
630         }
631         cx = cx || 0;
632         cy = cy || 0;
633         x = x - cx;
634         y = y - cy;
635         alpha = alpha * this.radian;
636         var cos = Math.cos(alpha),
637             sin = Math.sin(alpha);
638         return {
639             x: x * cos - y * sin + cx,
640             y: x * sin + y * cos + cy
641         };
642     },
643
644     pathDimensions: function (path) {
645         if (!path || !(path + &quot;&quot;)) {
646             return {x: 0, y: 0, width: 0, height: 0};
647         }
648         path = this.path2curve(path);
649         var x = 0, 
650             y = 0,
651             X = [],
652             Y = [],
653             i = 0,
654             ln = path.length,
655             p, xmin, ymin, dim;
656         for (; i &lt; ln; i++) {
657             p = path[i];
658             if (p[0] == &quot;M&quot;) {
659                 x = p[1];
660                 y = p[2];
661                 X.push(x);
662                 Y.push(y);
663             }
664             else {
665                 dim = this.curveDim(x, y, p[1], p[2], p[3], p[4], p[5], p[6]);
666                 X = X.concat(dim.min.x, dim.max.x);
667                 Y = Y.concat(dim.min.y, dim.max.y);
668                 x = p[5];
669                 y = p[6];
670             }
671         }
672         xmin = Math.min.apply(0, X);
673         ymin = Math.min.apply(0, Y);
674         return {
675             x: xmin,
676             y: ymin,
677             path: path,
678             width: Math.max.apply(0, X) - xmin,
679             height: Math.max.apply(0, Y) - ymin
680         };
681     },
682
683     intersectInside: function(path, cp1, cp2) {
684         return (cp2[0] - cp1[0]) * (path[1] - cp1[1]) &gt; (cp2[1] - cp1[1]) * (path[0] - cp1[0]);
685     },
686
687     intersectIntersection: function(s, e, cp1, cp2) {
688         var p = [],
689             dcx = cp1[0] - cp2[0],
690             dcy = cp1[1] - cp2[1],
691             dpx = s[0] - e[0],
692             dpy = s[1] - e[1],
693             n1 = cp1[0] * cp2[1] - cp1[1] * cp2[0],
694             n2 = s[0] * e[1] - s[1] * e[0],
695             n3 = 1 / (dcx * dpy - dcy * dpx);
696
697         p[0] = (n1 * dpx - n2 * dcx) * n3;
698         p[1] = (n1 * dpy - n2 * dcy) * n3;
699         return p;
700     },
701
702     intersect: function(subjectPolygon, clipPolygon) {
703         var me = this,
704             i = 0,
705             ln = clipPolygon.length,
706             cp1 = clipPolygon[ln - 1],
707             outputList = subjectPolygon,
708             cp2, s, e, point, ln2, inputList, j;
709         for (; i &lt; ln; ++i) {
710             cp2 = clipPolygon[i];
711             inputList = outputList;
712             outputList = [];
713             s = inputList[inputList.length - 1];
714             j = 0;
715             ln2 = inputList.length;
716             for (; j &lt; ln2; j++) {
717                 e = inputList[j];
718                 if (me.intersectInside(e, cp1, cp2)) {
719                     if (!me.intersectInside(s, cp1, cp2)) {
720                         outputList.push(me.intersectIntersection(s, e, cp1, cp2));
721                     }
722                     outputList.push(e);
723                 }
724                 else if (me.intersectInside(s, cp1, cp2)) {
725                     outputList.push(me.intersectIntersection(s, e, cp1, cp2));
726                 }
727                 s = e;
728             }
729             cp1 = cp2;
730         }
731         return outputList;
732     },
733
734     curveDim: function (p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y) {
735         var a = (c2x - 2 * c1x + p1x) - (p2x - 2 * c2x + c1x),
736             b = 2 * (c1x - p1x) - 2 * (c2x - c1x),
737             c = p1x - c1x,
738             t1 = (-b + Math.sqrt(b * b - 4 * a * c)) / 2 / a,
739             t2 = (-b - Math.sqrt(b * b - 4 * a * c)) / 2 / a,
740             y = [p1y, p2y],
741             x = [p1x, p2x],
742             dot;
743         if (Math.abs(t1) &gt; 1e12) {
744             t1 = 0.5;
745         }
746         if (Math.abs(t2) &gt; 1e12) {
747             t2 = 0.5;
748         }
749         if (t1 &gt; 0 &amp;&amp; t1 &lt; 1) {
750             dot = this.findDotAtSegment(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t1);
751             x.push(dot.x);
752             y.push(dot.y);
753         }
754         if (t2 &gt; 0 &amp;&amp; t2 &lt; 1) {
755             dot = this.findDotAtSegment(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t2);
756             x.push(dot.x);
757             y.push(dot.y);
758         }
759         a = (c2y - 2 * c1y + p1y) - (p2y - 2 * c2y + c1y);
760         b = 2 * (c1y - p1y) - 2 * (c2y - c1y);
761         c = p1y - c1y;
762         t1 = (-b + Math.sqrt(b * b - 4 * a * c)) / 2 / a;
763         t2 = (-b - Math.sqrt(b * b - 4 * a * c)) / 2 / a;
764         if (Math.abs(t1) &gt; 1e12) {
765             t1 = 0.5;
766         }
767         if (Math.abs(t2) &gt; 1e12) {
768             t2 = 0.5;
769         }
770         if (t1 &gt; 0 &amp;&amp; t1 &lt; 1) {
771             dot = this.findDotAtSegment(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t1);
772             x.push(dot.x);
773             y.push(dot.y);
774         }
775         if (t2 &gt; 0 &amp;&amp; t2 &lt; 1) {
776             dot = this.findDotAtSegment(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t2);
777             x.push(dot.x);
778             y.push(dot.y);
779         }
780         return {
781             min: {x: Math.min.apply(0, x), y: Math.min.apply(0, y)},
782             max: {x: Math.max.apply(0, x), y: Math.max.apply(0, y)}
783         };
784     },
785
786     getAnchors: function (p1x, p1y, p2x, p2y, p3x, p3y, value) {
787         value = value || 4;
788         var l = Math.min(Math.sqrt(Math.pow(p1x - p2x, 2) + Math.pow(p1y - p2y, 2)) / value, Math.sqrt(Math.pow(p3x - p2x, 2) + Math.pow(p3y - p2y, 2)) / value),
789             a = Math.atan((p2x - p1x) / Math.abs(p2y - p1y)),
790             b = Math.atan((p3x - p2x) / Math.abs(p2y - p3y)),
791             pi = Math.PI;
792         a = p1y &lt; p2y ? pi - a : a;
793         b = p3y &lt; p2y ? pi - b : b;
794         var alpha = pi / 2 - ((a + b) % (pi * 2)) / 2;
795         alpha &gt; pi / 2 &amp;&amp; (alpha -= pi);
796         var dx1 = l * Math.sin(alpha + a),
797             dy1 = l * Math.cos(alpha + a),
798             dx2 = l * Math.sin(alpha + b),
799             dy2 = l * Math.cos(alpha + b),
800             out = {
801                 x1: p2x - dx1,
802                 y1: p2y + dy1,
803                 x2: p2x + dx2,
804                 y2: p2y + dy2
805             };
806         return out;
807     },
808
809     /* Smoothing function for a path.  Converts a path into cubic beziers.  Value defines the divider of the distance between points.
810      * Defaults to a value of 4.
811      */
812     smooth: function (originalPath, value) {
813         var path = this.path2curve(originalPath),
814             newp = [path[0]],
815             x = path[0][1],
816             y = path[0][2],
817             j,
818             points,
819             i = 1,
820             ii = path.length,
821             beg = 1,
822             mx = x,
823             my = y,
824             cx = 0,
825             cy = 0;
826         for (; i &lt; ii; i++) {
827             var pathi = path[i],
828                 pathil = pathi.length,
829                 pathim = path[i - 1],
830                 pathiml = pathim.length,
831                 pathip = path[i + 1],
832                 pathipl = pathip &amp;&amp; pathip.length;
833             if (pathi[0] == &quot;M&quot;) {
834                 mx = pathi[1];
835                 my = pathi[2];
836                 j = i + 1;
837                 while (path[j][0] != &quot;C&quot;) {
838                     j++;
839                 }
840                 cx = path[j][5];
841                 cy = path[j][6];
842                 newp.push([&quot;M&quot;, mx, my]);
843                 beg = newp.length;
844                 x = mx;
845                 y = my;
846                 continue;
847             }
848             if (pathi[pathil - 2] == mx &amp;&amp; pathi[pathil - 1] == my &amp;&amp; (!pathip || pathip[0] == &quot;M&quot;)) {
849                 var begl = newp[beg].length;
850                 points = this.getAnchors(pathim[pathiml - 2], pathim[pathiml - 1], mx, my, newp[beg][begl - 2], newp[beg][begl - 1], value);
851                 newp[beg][1] = points.x2;
852                 newp[beg][2] = points.y2;
853             }
854             else if (!pathip || pathip[0] == &quot;M&quot;) {
855                 points = {
856                     x1: pathi[pathil - 2],
857                     y1: pathi[pathil - 1]
858                 };
859             } else {
860                 points = this.getAnchors(pathim[pathiml - 2], pathim[pathiml - 1], pathi[pathil - 2], pathi[pathil - 1], pathip[pathipl - 2], pathip[pathipl - 1], value);
861             }
862             newp.push([&quot;C&quot;, x, y, points.x1, points.y1, pathi[pathil - 2], pathi[pathil - 1]]);
863             x = points.x2;
864             y = points.y2;
865         }
866         return newp;
867     },
868
869     findDotAtSegment: function (p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t) {
870         var t1 = 1 - t;
871         return {
872             x: Math.pow(t1, 3) * p1x + Math.pow(t1, 2) * 3 * t * c1x + t1 * 3 * t * t * c2x + Math.pow(t, 3) * p2x,
873             y: Math.pow(t1, 3) * p1y + Math.pow(t1, 2) * 3 * t * c1y + t1 * 3 * t * t * c2y + Math.pow(t, 3) * p2y
874         };
875     },
876
877     snapEnds: function (from, to, stepsMax) {
878         var step = (to - from) / stepsMax,
879             level = Math.floor(Math.log(step) / Math.LN10) + 1,
880             m = Math.pow(10, level),
881             cur,
882             modulo = Math.round((step % m) * Math.pow(10, 2 - level)),
883             interval = [[0, 15], [20, 4], [30, 2], [40, 4], [50, 9], [60, 4], [70, 2], [80, 4], [100, 15]],
884             stepCount = 0,
885             value,
886             weight,
887             i,
888             topValue,
889             topWeight = 1e9,
890             ln = interval.length;
891         cur = from = Math.floor(from / m) * m;
892         for (i = 0; i &lt; ln; i++) {
893             value = interval[i][0];
894             weight = (value - modulo) &lt; 0 ? 1e6 : (value - modulo) / interval[i][1];
895             if (weight &lt; topWeight) {
896                 topValue = value;
897                 topWeight = weight;
898             }
899         }
900         step = Math.floor(step * Math.pow(10, -level)) * Math.pow(10, level) + topValue * Math.pow(10, level - 2);
901         while (cur &lt; to) {
902             cur += step;
903             stepCount++;
904         }
905         to = +cur.toFixed(10);
906         return {
907             from: from,
908             to: to,
909             power: level,
910             step: step,
911             steps: stepCount
912         };
913     },
914
915     sorter: function (a, b) {
916         return a.offset - b.offset;
917     },
918
919     rad: function(degrees) {
920         return degrees % 360 * Math.PI / 180;
921     },
922
923     degrees: function(radian) {
924         return radian * 180 / Math.PI % 360;
925     },
926
927     withinBox: function(x, y, bbox) {
928         bbox = bbox || {};
929         return (x &gt;= bbox.x &amp;&amp; x &lt;= (bbox.x + bbox.width) &amp;&amp; y &gt;= bbox.y &amp;&amp; y &lt;= (bbox.y + bbox.height));
930     },
931
932     parseGradient: function(gradient) {
933         var me = this,
934             type = gradient.type || 'linear',
935             angle = gradient.angle || 0,
936             radian = me.radian,
937             stops = gradient.stops,
938             stopsArr = [],
939             stop,
940             vector,
941             max,
942             stopObj;
943
944         if (type == 'linear') {
945             vector = [0, 0, Math.cos(angle * radian), Math.sin(angle * radian)];
946             max = 1 / (Math.max(Math.abs(vector[2]), Math.abs(vector[3])) || 1);
947             vector[2] *= max;
948             vector[3] *= max;
949             if (vector[2] &lt; 0) {
950                 vector[0] = -vector[2];
951                 vector[2] = 0;
952             }
953             if (vector[3] &lt; 0) {
954                 vector[1] = -vector[3];
955                 vector[3] = 0;
956             }
957         }
958
959         for (stop in stops) {
960             if (stops.hasOwnProperty(stop) &amp;&amp; me.stopsRE.test(stop)) {
961                 stopObj = {
962                     offset: parseInt(stop, 10),
963                     color: Ext.draw.Color.toHex(stops[stop].color) || '#ffffff',
964                     opacity: stops[stop].opacity || 1
965                 };
966                 stopsArr.push(stopObj);
967             }
968         }
969         // Sort by pct property
970         Ext.Array.sort(stopsArr, me.sorter);
971         if (type == 'linear') {
972             return {
973                 id: gradient.id,
974                 type: type,
975                 vector: vector,
976                 stops: stopsArr
977             };
978         }
979         else {
980             return {
981                 id: gradient.id,
982                 type: type,
983                 centerX: gradient.centerX,
984                 centerY: gradient.centerY,
985                 focalX: gradient.focalX,
986                 focalY: gradient.focalY,
987                 radius: gradient.radius,
988                 vector: vector,
989                 stops: stopsArr
990             };
991         }
992     }
993 });
994 </pre>
995 </body>
996 </html>