Upgrade to ExtJS 4.0.7 - Released 10/19/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="../resources/prettify/prettify.css" type="text/css" rel="stylesheet" />
7   <script type="text/javascript" src="../resources/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"><span id='Ext-draw-Draw'>/**
19 </span> * @class Ext.draw.Draw
20  * Base Drawing class.  Provides base drawing functions.
21  * @private
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(Ext.Array.splice(params, 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(Ext.Array.splice(params, 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                         Ext.Array.splice(points, i++, 0, [&quot;C&quot;].concat(Ext.Array.splice(point, 0, 6)));
352                     }
353                     Ext.Array.erase(points, 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                         Ext.Array.splice(pp, i++, 0, [&quot;C&quot;].concat(Ext.Array.splice(pi, 0, 6)));
378                     }
379                     Ext.Array.erase(pp, 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                     Ext.Array.splice(path2, 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 <span id='Ext-draw-Draw-method-getAnchors'>    /**
787 </span>     * @private
788      *
789      * Calculates bezier curve control anchor points for a particular point in a path, with a
790      * smoothing curve applied. The smoothness of the curve is controlled by the 'value' parameter.
791      * Note that this algorithm assumes that the line being smoothed is normalized going from left
792      * to right; it makes special adjustments assuming this orientation.
793      *
794      * @param {Number} prevX X coordinate of the previous point in the path
795      * @param {Number} prevY Y coordinate of the previous point in the path
796      * @param {Number} curX X coordinate of the current point in the path
797      * @param {Number} curY Y coordinate of the current point in the path
798      * @param {Number} nextX X coordinate of the next point in the path
799      * @param {Number} nextY Y coordinate of the next point in the path
800      * @param {Number} value A value to control the smoothness of the curve; this is used to
801      * divide the distance between points, so a value of 2 corresponds to
802      * half the distance between points (a very smooth line) while higher values
803      * result in less smooth curves. Defaults to 4.
804      * @return {Object} Object containing x1, y1, x2, y2 bezier control anchor points; x1 and y1
805      * are the control point for the curve toward the previous path point, and
806      * x2 and y2 are the control point for the curve toward the next path point.
807      */
808     getAnchors: function (prevX, prevY, curX, curY, nextX, nextY, value) {
809         value = value || 4;
810         var M = Math,
811             PI = M.PI,
812             halfPI = PI / 2,
813             abs = M.abs,
814             sin = M.sin,
815             cos = M.cos,
816             atan = M.atan,
817             control1Length, control2Length, control1Angle, control2Angle,
818             control1X, control1Y, control2X, control2Y, alpha;
819
820         // Find the length of each control anchor line, by dividing the horizontal distance
821         // between points by the value parameter.
822         control1Length = (curX - prevX) / value;
823         control2Length = (nextX - curX) / value;
824
825         // Determine the angle of each control anchor line. If the middle point is a vertical
826         // turnaround then we force it to a flat horizontal angle to prevent the curve from
827         // dipping above or below the middle point. Otherwise we use an angle that points
828         // toward the previous/next target point.
829         if ((curY &gt;= prevY &amp;&amp; curY &gt;= nextY) || (curY &lt;= prevY &amp;&amp; curY &lt;= nextY)) {
830             control1Angle = control2Angle = halfPI;
831         } else {
832             control1Angle = atan((curX - prevX) / abs(curY - prevY));
833             if (prevY &lt; curY) {
834                 control1Angle = PI - control1Angle;
835             }
836             control2Angle = atan((nextX - curX) / abs(curY - nextY));
837             if (nextY &lt; curY) {
838                 control2Angle = PI - control2Angle;
839             }
840         }
841
842         // Adjust the calculated angles so they point away from each other on the same line
843         alpha = halfPI - ((control1Angle + control2Angle) % (PI * 2)) / 2;
844         if (alpha &gt; halfPI) {
845             alpha -= PI;
846         }
847         control1Angle += alpha;
848         control2Angle += alpha;
849
850         // Find the control anchor points from the angles and length
851         control1X = curX - control1Length * sin(control1Angle);
852         control1Y = curY + control1Length * cos(control1Angle);
853         control2X = curX + control2Length * sin(control2Angle);
854         control2Y = curY + control2Length * cos(control2Angle);
855
856         // One last adjustment, make sure that no control anchor point extends vertically past
857         // its target prev/next point, as that results in curves dipping above or below and
858         // bending back strangely. If we find this happening we keep the control angle but
859         // reduce the length of the control line so it stays within bounds.
860         if ((curY &gt; prevY &amp;&amp; control1Y &lt; prevY) || (curY &lt; prevY &amp;&amp; control1Y &gt; prevY)) {
861             control1X += abs(prevY - control1Y) * (control1X - curX) / (control1Y - curY);
862             control1Y = prevY;
863         }
864         if ((curY &gt; nextY &amp;&amp; control2Y &lt; nextY) || (curY &lt; nextY &amp;&amp; control2Y &gt; nextY)) {
865             control2X -= abs(nextY - control2Y) * (control2X - curX) / (control2Y - curY);
866             control2Y = nextY;
867         }
868         
869         return {
870             x1: control1X,
871             y1: control1Y,
872             x2: control2X,
873             y2: control2Y
874         };
875     },
876
877     /* Smoothing function for a path.  Converts a path into cubic beziers.  Value defines the divider of the distance between points.
878      * Defaults to a value of 4.
879      */
880     smooth: function (originalPath, value) {
881         var path = this.path2curve(originalPath),
882             newp = [path[0]],
883             x = path[0][1],
884             y = path[0][2],
885             j,
886             points,
887             i = 1,
888             ii = path.length,
889             beg = 1,
890             mx = x,
891             my = y,
892             cx = 0,
893             cy = 0;
894         for (; i &lt; ii; i++) {
895             var pathi = path[i],
896                 pathil = pathi.length,
897                 pathim = path[i - 1],
898                 pathiml = pathim.length,
899                 pathip = path[i + 1],
900                 pathipl = pathip &amp;&amp; pathip.length;
901             if (pathi[0] == &quot;M&quot;) {
902                 mx = pathi[1];
903                 my = pathi[2];
904                 j = i + 1;
905                 while (path[j][0] != &quot;C&quot;) {
906                     j++;
907                 }
908                 cx = path[j][5];
909                 cy = path[j][6];
910                 newp.push([&quot;M&quot;, mx, my]);
911                 beg = newp.length;
912                 x = mx;
913                 y = my;
914                 continue;
915             }
916             if (pathi[pathil - 2] == mx &amp;&amp; pathi[pathil - 1] == my &amp;&amp; (!pathip || pathip[0] == &quot;M&quot;)) {
917                 var begl = newp[beg].length;
918                 points = this.getAnchors(pathim[pathiml - 2], pathim[pathiml - 1], mx, my, newp[beg][begl - 2], newp[beg][begl - 1], value);
919                 newp[beg][1] = points.x2;
920                 newp[beg][2] = points.y2;
921             }
922             else if (!pathip || pathip[0] == &quot;M&quot;) {
923                 points = {
924                     x1: pathi[pathil - 2],
925                     y1: pathi[pathil - 1]
926                 };
927             } else {
928                 points = this.getAnchors(pathim[pathiml - 2], pathim[pathiml - 1], pathi[pathil - 2], pathi[pathil - 1], pathip[pathipl - 2], pathip[pathipl - 1], value);
929             }
930             newp.push([&quot;C&quot;, x, y, points.x1, points.y1, pathi[pathil - 2], pathi[pathil - 1]]);
931             x = points.x2;
932             y = points.y2;
933         }
934         return newp;
935     },
936
937     findDotAtSegment: function (p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t) {
938         var t1 = 1 - t;
939         return {
940             x: Math.pow(t1, 3) * p1x + Math.pow(t1, 2) * 3 * t * c1x + t1 * 3 * t * t * c2x + Math.pow(t, 3) * p2x,
941             y: Math.pow(t1, 3) * p1y + Math.pow(t1, 2) * 3 * t * c1y + t1 * 3 * t * t * c2y + Math.pow(t, 3) * p2y
942         };
943     },
944
945 <span id='Ext-draw-Draw-method-snapEnds'>    /**
946 </span>     * A utility method to deduce an appropriate tick configuration for the data set of given
947      * feature.
948      * 
949      * @param {Number/Date} from The minimum value in the data
950      * @param {Number/Date} to The maximum value in the data
951      * @param {Number} stepsMax The maximum number of ticks
952      * @return {Object} The calculated step and ends info; When `from` and `to` are Dates, refer to the
953      * return value of {@link #snapEndsByDate}. For numerical `from` and `to` the return value contains:
954      * @return {Number} return.from The result start value, which may be lower than the original start value
955      * @return {Number} return.to The result end value, which may be higher than the original end value
956      * @return {Number} return.power The calculate power.
957      * @return {Number} return.step The value size of each step
958      * @return {Number} return.steps The number of steps.
959      */
960     snapEnds: function (from, to, stepsMax) {
961         if (Ext.isDate(from)) {
962             return this.snapEndsByDate(from, to, stepsMax);
963         }
964         var step = (to - from) / stepsMax,
965             level = Math.floor(Math.log(step) / Math.LN10) + 1,
966             m = Math.pow(10, level),
967             cur,
968             modulo = Math.round((step % m) * Math.pow(10, 2 - level)),
969             interval = [[0, 15], [20, 4], [30, 2], [40, 4], [50, 9], [60, 4], [70, 2], [80, 4], [100, 15]],
970             stepCount = 0,
971             value,
972             weight,
973             i,
974             topValue,
975             topWeight = 1e9,
976             ln = interval.length;
977         cur = from = Math.floor(from / m) * m;
978         for (i = 0; i &lt; ln; i++) {
979             value = interval[i][0];
980             weight = (value - modulo) &lt; 0 ? 1e6 : (value - modulo) / interval[i][1];
981             if (weight &lt; topWeight) {
982                 topValue = value;
983                 topWeight = weight;
984             }
985         }
986         step = Math.floor(step * Math.pow(10, -level)) * Math.pow(10, level) + topValue * Math.pow(10, level - 2);
987         while (cur &lt; to) {
988             cur += step;
989             stepCount++;
990         }
991         to = +cur.toFixed(10);
992         return {
993             from: from,
994             to: to,
995             power: level,
996             step: step,
997             steps: stepCount
998         };
999     },
1000
1001 <span id='Ext-draw-Draw-method-snapEndsByDate'>    /**
1002 </span>     * A utility method to deduce an appropriate tick configuration for the data set of given
1003      * feature when data is Dates. Refer to {@link #snapEnds} for numeric data.
1004      *
1005      * @param {Date} from The minimum value in the data
1006      * @param {Date} to The maximum value in the data
1007      * @param {Number} stepsMax The maximum number of ticks
1008      * @param {Boolean} lockEnds If true, the 'from' and 'to' parameters will be used as fixed end values
1009      * and will not be adjusted
1010      * @return {Object} The calculated step and ends info; properties are:
1011      * @return {Date} return.from The result start value, which may be lower than the original start value
1012      * @return {Date} return.to The result end value, which may be higher than the original end value
1013      * @return {Number} return.step The value size of each step
1014      * @return {Number} return.steps The number of steps.
1015      * NOTE: the steps may not divide the from/to range perfectly evenly;
1016      * there may be a smaller distance between the last step and the end value than between prior
1017      * steps, particularly when the `endsLocked` param is true. Therefore it is best to not use
1018      * the `steps` result when finding the axis tick points, instead use the `step`, `to`, and
1019      * `from` to find the correct point for each tick.
1020      */
1021     snapEndsByDate: function (from, to, stepsMax, lockEnds) {
1022         var selectedStep = false, scales = [
1023                 [Ext.Date.MILLI, [1, 2, 3, 5, 10, 20, 30, 50, 100, 200, 300, 500]],
1024                 [Ext.Date.SECOND, [1, 2, 3, 5, 10, 15, 30]],
1025                 [Ext.Date.MINUTE, [1, 2, 3, 5, 10, 20, 30]],
1026                 [Ext.Date.HOUR, [1, 2, 3, 4, 6, 12]],
1027                 [Ext.Date.DAY, [1, 2, 3, 7, 14]],
1028                 [Ext.Date.MONTH, [1, 2, 3, 4, 6]]
1029             ], j, yearDiff;
1030
1031         // Find the most desirable scale
1032         Ext.each(scales, function(scale, i) {
1033             for (j = 0; j &lt; scale[1].length; j++) {
1034                 if (to &lt; Ext.Date.add(from, scale[0], scale[1][j] * stepsMax)) {
1035                     selectedStep = [scale[0], scale[1][j]];
1036                     return false;
1037                 }
1038             }
1039         });
1040         if (!selectedStep) {
1041             yearDiff = this.snapEnds(from.getFullYear(), to.getFullYear() + 1, stepsMax, lockEnds);
1042             selectedStep = [Date.YEAR, Math.round(yearDiff.step)];
1043         }
1044         return this.snapEndsByDateAndStep(from, to, selectedStep, lockEnds);
1045     },
1046
1047
1048 <span id='Ext-draw-Draw-method-snapEndsByDateAndStep'>    /**
1049 </span>     * A utility method to deduce an appropriate tick configuration for the data set of given
1050      * feature and specific step size.
1051      * @param {Date} from The minimum value in the data
1052      * @param {Date} to The maximum value in the data
1053      * @param {Array} step An array with two components: The first is the unit of the step (day, month, year, etc).
1054      * The second one is the number of units for the step (1, 2, etc.).
1055      * @param {Boolean} lockEnds If true, the 'from' and 'to' parameters will be used as fixed end values
1056      * and will not be adjusted
1057      * @return {Object} See the return value of {@link #snapEndsByDate}.
1058      */
1059     snapEndsByDateAndStep: function(from, to, step, lockEnds) {
1060         var fromStat = [from.getFullYear(), from.getMonth(), from.getDate(),
1061                 from.getHours(), from.getMinutes(), from.getSeconds(), from.getMilliseconds()],
1062             steps = 0, testFrom, testTo;
1063         if (lockEnds) {
1064             testFrom = from;
1065         } else {
1066             switch (step[0]) {
1067                 case Ext.Date.MILLI:
1068                     testFrom = new Date(fromStat[0], fromStat[1], fromStat[2], fromStat[3],
1069                             fromStat[4], fromStat[5], Math.floor(fromStat[6] / step[1]) * step[1]);
1070                     break;
1071                 case Ext.Date.SECOND:
1072                     testFrom = new Date(fromStat[0], fromStat[1], fromStat[2], fromStat[3],
1073                             fromStat[4], Math.floor(fromStat[5] / step[1]) * step[1], 0);
1074                     break;
1075                 case Ext.Date.MINUTE:
1076                     testFrom = new Date(fromStat[0], fromStat[1], fromStat[2], fromStat[3],
1077                             Math.floor(fromStat[4] / step[1]) * step[1], 0, 0);
1078                     break;
1079                 case Ext.Date.HOUR:
1080                     testFrom = new Date(fromStat[0], fromStat[1], fromStat[2],
1081                             Math.floor(fromStat[3] / step[1]) * step[1], 0, 0, 0);
1082                     break;
1083                 case Ext.Date.DAY:
1084                     testFrom = new Date(fromStat[0], fromStat[1],
1085                             Math.floor(fromStat[2] - 1 / step[1]) * step[1] + 1, 0, 0, 0, 0);
1086                     break;
1087                 case Ext.Date.MONTH:
1088                     testFrom = new Date(fromStat[0], Math.floor(fromStat[1] / step[1]) * step[1], 1, 0, 0, 0, 0);
1089                     break;
1090                 default: // Ext.Date.YEAR
1091                     testFrom = new Date(Math.floor(fromStat[0] / step[1]) * step[1], 0, 1, 0, 0, 0, 0);
1092                     break;
1093             }
1094         }
1095
1096         testTo = testFrom;
1097         // TODO(zhangbei) : We can do it better somehow...
1098         while (testTo &lt; to) {
1099             testTo = Ext.Date.add(testTo, step[0], step[1]);
1100             steps++;
1101         }
1102
1103         if (lockEnds) {
1104             testTo = to;
1105         }
1106         return {
1107             from : +testFrom,
1108             to : +testTo,
1109             step : (testTo - testFrom) / steps,
1110             steps : steps
1111         };
1112     },
1113
1114     sorter: function (a, b) {
1115         return a.offset - b.offset;
1116     },
1117
1118     rad: function(degrees) {
1119         return degrees % 360 * Math.PI / 180;
1120     },
1121
1122     degrees: function(radian) {
1123         return radian * 180 / Math.PI % 360;
1124     },
1125
1126     withinBox: function(x, y, bbox) {
1127         bbox = bbox || {};
1128         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));
1129     },
1130
1131     parseGradient: function(gradient) {
1132         var me = this,
1133             type = gradient.type || 'linear',
1134             angle = gradient.angle || 0,
1135             radian = me.radian,
1136             stops = gradient.stops,
1137             stopsArr = [],
1138             stop,
1139             vector,
1140             max,
1141             stopObj;
1142
1143         if (type == 'linear') {
1144             vector = [0, 0, Math.cos(angle * radian), Math.sin(angle * radian)];
1145             max = 1 / (Math.max(Math.abs(vector[2]), Math.abs(vector[3])) || 1);
1146             vector[2] *= max;
1147             vector[3] *= max;
1148             if (vector[2] &lt; 0) {
1149                 vector[0] = -vector[2];
1150                 vector[2] = 0;
1151             }
1152             if (vector[3] &lt; 0) {
1153                 vector[1] = -vector[3];
1154                 vector[3] = 0;
1155             }
1156         }
1157
1158         for (stop in stops) {
1159             if (stops.hasOwnProperty(stop) &amp;&amp; me.stopsRE.test(stop)) {
1160                 stopObj = {
1161                     offset: parseInt(stop, 10),
1162                     color: Ext.draw.Color.toHex(stops[stop].color) || '#ffffff',
1163                     opacity: stops[stop].opacity || 1
1164                 };
1165                 stopsArr.push(stopObj);
1166             }
1167         }
1168         // Sort by pct property
1169         Ext.Array.sort(stopsArr, me.sorter);
1170         if (type == 'linear') {
1171             return {
1172                 id: gradient.id,
1173                 type: type,
1174                 vector: vector,
1175                 stops: stopsArr
1176             };
1177         }
1178         else {
1179             return {
1180                 id: gradient.id,
1181                 type: type,
1182                 centerX: gradient.centerX,
1183                 centerY: gradient.centerY,
1184                 focalX: gradient.focalX,
1185                 focalY: gradient.focalY,
1186                 radius: gradient.radius,
1187                 vector: vector,
1188                 stops: stopsArr
1189             };
1190         }
1191     }
1192 });
1193
1194 </pre>
1195 </body>
1196 </html>