3 This file is part of Ext JS 4
5 Copyright (c) 2011 Sencha Inc
7 Contact: http://www.sencha.com/contact
9 GNU General Public License Usage
10 This file may be used under the terms of the GNU General Public License version 3.0 as published by the Free Software Foundation and appearing in the file LICENSE included in the packaging of this file. Please review the following information to ensure the GNU General Public License version 3.0 requirements will be met: http://www.gnu.org/copyleft/gpl.html.
12 If you are unsure which license is appropriate for your use, please contact the sales department at http://www.sencha.com/contact.
16 * @class Ext.draw.Draw
17 * Base Drawing class. Provides base drawing functions.
20 Ext.define('Ext.draw.Draw', {
21 /* Begin Definitions */
25 requires: ['Ext.draw.Color'],
29 pathToStringRE: /,?([achlmqrstvxz]),?/gi,
30 pathCommandRE: /([achlmqstvz])[\s,]*((-?\d*\.?\d*(?:e[-+]?\d+)?\s*,?\s*)+)/ig,
31 pathValuesRE: /(-?\d*\.?\d*(?:e[-+]?\d+)?)\s*,?\s*/ig,
33 radian: Math.PI / 180,
53 "stroke-opacity": null,
61 is: function(o, type) {
62 type = String(type).toLowerCase();
63 return (type == "object" && o === Object(o)) ||
64 (type == "undefined" && typeof o == type) ||
65 (type == "null" && o === null) ||
66 (type == "array" && Array.isArray && Array.isArray(o)) ||
67 (Object.prototype.toString.call(o).toLowerCase().slice(8, -1)) == type;
70 ellipsePath: function(sprite) {
71 var attr = sprite.attr;
72 return Ext.String.format("M{0},{1}A{2},{3},0,1,1,{0},{4}A{2},{3},0,1,1,{0},{1}z", attr.x, attr.y - attr.ry, attr.rx, attr.ry, attr.y + attr.ry);
75 rectPath: function(sprite) {
76 var attr = sprite.attr;
78 return Ext.String.format("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", 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);
81 return Ext.String.format("M{0},{1}l{2},0,0,{3},{4},0z", attr.x, attr.y, attr.width, attr.height, -attr.width);
85 // To be deprecated, converts itself (an arrayPath) to a proper SVG path string
86 path2string: function () {
87 return this.join(",").replace(Ext.draw.Draw.pathToStringRE, "$1");
90 // Convert the passed arrayPath to a proper SVG path string (d attribute)
91 pathToString: function(arrayPath) {
92 return arrayPath.join(",").replace(Ext.draw.Draw.pathToStringRE, "$1");
95 parsePathString: function (pathString) {
99 var paramCounts = {a: 7, c: 6, h: 1, l: 2, m: 2, q: 4, s: 4, t: 2, v: 1, z: 0},
102 if (me.is(pathString, "array") && me.is(pathString[0], "array")) { // rough assumption
103 data = me.pathClone(pathString);
106 String(pathString).replace(me.pathCommandRE, function (a, b, c) {
108 name = b.toLowerCase();
109 c.replace(me.pathValuesRE, function (a, b) {
110 b && params.push(+b);
112 if (name == "m" && params.length > 2) {
113 data.push([b].concat(Ext.Array.splice(params, 0, 2)));
115 b = (b == "m") ? "l" : "L";
117 while (params.length >= paramCounts[name]) {
118 data.push([b].concat(Ext.Array.splice(params, 0, paramCounts[name])));
119 if (!paramCounts[name]) {
125 data.toString = me.path2string;
129 mapPath: function (path, matrix) {
133 var x, y, i, ii, j, jj, pathi;
134 path = this.path2curve(path);
135 for (i = 0, ii = path.length; i < ii; i++) {
137 for (j = 1, jj = pathi.length; j < jj-1; j += 2) {
138 x = matrix.x(pathi[j], pathi[j + 1]);
139 y = matrix.y(pathi[j], pathi[j + 1]);
147 pathClone: function(pathArray) {
150 if (!this.is(pathArray, "array") || !this.is(pathArray && pathArray[0], "array")) { // rough assumption
151 pathArray = this.parsePathString(pathArray);
153 for (i = 0, ii = pathArray.length; i < ii; i++) {
155 for (j = 0, jj = pathArray[i].length; j < jj; j++) {
156 res[i][j] = pathArray[i][j];
159 res.toString = this.path2string;
163 pathToAbsolute: function (pathArray) {
164 if (!this.is(pathArray, "array") || !this.is(pathArray && pathArray[0], "array")) { // rough assumption
165 pathArray = this.parsePathString(pathArray);
173 ln = pathArray.length,
174 r, pathSegment, j, ln2;
175 // MoveTo initial x/y position
176 if (ln && pathArray[0][0] == "M") {
177 x = +pathArray[0][1];
178 y = +pathArray[0][2];
182 res[0] = ["M", x, y];
184 for (; i < ln; i++) {
186 pathSegment = pathArray[i];
187 if (pathSegment[0] != pathSegment[0].toUpperCase()) {
188 r[0] = pathSegment[0].toUpperCase();
192 r[1] = pathSegment[1];
193 r[2] = pathSegment[2];
194 r[3] = pathSegment[3];
195 r[4] = pathSegment[4];
196 r[5] = pathSegment[5];
197 r[6] = +(pathSegment[6] + x);
198 r[7] = +(pathSegment[7] + y);
202 r[1] = +pathSegment[1] + y;
206 r[1] = +pathSegment[1] + x;
210 mx = +pathSegment[1] + x;
211 my = +pathSegment[2] + y;
214 ln2 = pathSegment.length;
215 for (; j < ln2; j++) {
216 r[j] = +pathSegment[j] + ((j % 2) ? x : y);
222 ln2 = pathSegment.length;
223 for (; j < ln2; j++) {
224 res[i][j] = pathSegment[j];
243 pathSegment = res[i];
244 ln2 = pathSegment.length;
245 mx = pathSegment[ln2 - 2];
246 my = pathSegment[ln2 - 1];
248 pathSegment = res[i];
249 ln2 = pathSegment.length;
250 x = pathSegment[ln2 - 2];
251 y = pathSegment[ln2 - 1];
254 res.toString = this.path2string;
259 pathToRelative: function (pathArray) {
260 if (!this.is(pathArray, "array") || !this.is(pathArray && pathArray[0], "array")) {
261 pathArray = this.parsePathString(pathArray);
269 if (pathArray[0][0] == "M") {
275 res.push(["M", x, y]);
277 for (var i = start, ii = pathArray.length; i < ii; i++) {
280 if (pa[0] != pa[0].toLowerCase()) {
281 r[0] = pa[0].toLowerCase();
289 r[6] = +(pa[6] - x).toFixed(3);
290 r[7] = +(pa[7] - y).toFixed(3);
293 r[1] = +(pa[1] - y).toFixed(3);
299 for (var j = 1, jj = pa.length; j < jj; j++) {
300 r[j] = +(pa[j] - ((j % 2) ? x : y)).toFixed(3);
309 for (var k = 0, kk = pa.length; k < kk; k++) {
313 var len = res[i].length;
320 x += +res[i][len - 1];
323 y += +res[i][len - 1];
326 x += +res[i][len - 2];
327 y += +res[i][len - 1];
330 res.toString = this.path2string;
334 // Returns a path converted to a set of curveto commands
335 path2curve: function (path) {
337 points = me.pathToAbsolute(path),
339 attrs = {x: 0, y: 0, bx: 0, by: 0, X: 0, Y: 0, qx: null, qy: null},
340 i, seg, segLn, point;
342 for (i = 0; i < ln; i++) {
343 points[i] = me.command2curve(points[i], attrs);
344 if (points[i].length > 7) {
347 while (point.length) {
348 Ext.Array.splice(points, i++, 0, ["C"].concat(Ext.Array.splice(point, 0, 6)));
350 Ext.Array.erase(points, i, 1);
355 attrs.x = seg[segLn - 2];
356 attrs.y = seg[segLn - 1];
357 attrs.bx = parseFloat(seg[segLn - 4]) || attrs.x;
358 attrs.by = parseFloat(seg[segLn - 3]) || attrs.y;
363 interpolatePaths: function (path, path2) {
365 p = me.pathToAbsolute(path),
366 p2 = me.pathToAbsolute(path2),
367 attrs = {x: 0, y: 0, bx: 0, by: 0, X: 0, Y: 0, qx: null, qy: null},
368 attrs2 = {x: 0, y: 0, bx: 0, by: 0, X: 0, Y: 0, qx: null, qy: null},
369 fixArc = function (pp, i) {
370 if (pp[i].length > 7) {
374 Ext.Array.splice(pp, i++, 0, ["C"].concat(Ext.Array.splice(pi, 0, 6)));
376 Ext.Array.erase(pp, i, 1);
377 ii = Math.max(p.length, p2.length || 0);
380 fixM = function (path1, path2, a1, a2, i) {
381 if (path1 && path2 && path1[i][0] == "M" && path2[i][0] != "M") {
382 Ext.Array.splice(path2, i, 0, ["M", a2.x, a2.y]);
387 ii = Math.max(p.length, p2.length || 0);
390 for (var i = 0, ii = Math.max(p.length, p2.length || 0); i < ii; i++) {
391 p[i] = me.command2curve(p[i], attrs);
393 (p2[i] = me.command2curve(p2[i], attrs2));
395 fixM(p, p2, attrs, attrs2, i);
396 fixM(p2, p, attrs2, attrs, i);
400 seg2len = seg2.length;
401 attrs.x = seg[seglen - 2];
402 attrs.y = seg[seglen - 1];
403 attrs.bx = parseFloat(seg[seglen - 4]) || attrs.x;
404 attrs.by = parseFloat(seg[seglen - 3]) || attrs.y;
405 attrs2.bx = (parseFloat(seg2[seg2len - 4]) || attrs2.x);
406 attrs2.by = (parseFloat(seg2[seg2len - 3]) || attrs2.y);
407 attrs2.x = seg2[seg2len - 2];
408 attrs2.y = seg2[seg2len - 1];
413 //Returns any path command as a curveto command based on the attrs passed
414 command2curve: function (pathCommand, d) {
417 return ["C", d.x, d.y, d.x, d.y, d.x, d.y];
419 if (pathCommand[0] != "T" && pathCommand[0] != "Q") {
422 switch (pathCommand[0]) {
424 d.X = pathCommand[1];
425 d.Y = pathCommand[2];
428 pathCommand = ["C"].concat(me.arc2curve.apply(me, [d.x, d.y].concat(pathCommand.slice(1))));
431 pathCommand = ["C", d.x + (d.x - (d.bx || d.x)), d.y + (d.y - (d.by || d.y))].concat(pathCommand.slice(1));
434 d.qx = d.x + (d.x - (d.qx || d.x));
435 d.qy = d.y + (d.y - (d.qy || d.y));
436 pathCommand = ["C"].concat(me.quadratic2curve(d.x, d.y, d.qx, d.qy, pathCommand[1], pathCommand[2]));
439 d.qx = pathCommand[1];
440 d.qy = pathCommand[2];
441 pathCommand = ["C"].concat(me.quadratic2curve(d.x, d.y, pathCommand[1], pathCommand[2], pathCommand[3], pathCommand[4]));
444 pathCommand = ["C"].concat(d.x, d.y, pathCommand[1], pathCommand[2], pathCommand[1], pathCommand[2]);
447 pathCommand = ["C"].concat(d.x, d.y, pathCommand[1], d.y, pathCommand[1], d.y);
450 pathCommand = ["C"].concat(d.x, d.y, d.x, pathCommand[1], d.x, pathCommand[1]);
453 pathCommand = ["C"].concat(d.x, d.y, d.X, d.Y, d.X, d.Y);
459 quadratic2curve: function (x1, y1, ax, ay, x2, y2) {
472 rotate: function (x, y, rad) {
473 var cos = Math.cos(rad),
475 X = x * cos - y * sin,
476 Y = x * sin + y * cos;
480 arc2curve: function (x1, y1, rx, ry, angle, large_arc_flag, sweep_flag, x2, y2, recursive) {
481 // for more information of where this Math came from visit:
482 // http://www.w3.org/TR/SVG11/implnote.html#ArcImplementationNotes
486 _120 = PI * 120 / 180,
487 rad = radian * (+angle || 0),
495 xy, cos, sin, x, y, h, rx2, ry2, k, cx, cy, f1, f2, df, c1, s1, c2, s2,
496 t, hx, hy, m1, m2, m3, m4, newres, i, ln, f2old, x2old, y2old;
498 xy = me.rotate(x1, y1, -rad);
501 xy = me.rotate(x2, y2, -rad);
504 cos = mcos(radian * angle);
505 sin = msin(radian * angle);
508 h = (x * x) / (rx * rx) + (y * y) / (ry * ry);
516 k = (large_arc_flag == sweep_flag ? -1 : 1) *
517 msqrt(mabs((rx2 * ry2 - rx2 * y * y - ry2 * x * x) / (rx2 * y * y + ry2 * x * x)));
518 cx = k * rx * y / ry + (x1 + x2) / 2;
519 cy = k * -ry * x / rx + (y1 + y2) / 2;
520 f1 = masin(((y1 - cy) / ry).toFixed(7));
521 f2 = masin(((y2 - cy) / ry).toFixed(7));
523 f1 = x1 < cx ? PI - f1 : f1;
524 f2 = x2 < cx ? PI - f2 : f2;
531 if (sweep_flag && f1 > f2) {
534 if (!sweep_flag && f2 > f1) {
545 if (mabs(df) > _120) {
549 f2 = f1 + _120 * (sweep_flag && f2 > f1 ? 1 : -1);
550 x2 = cx + rx * mcos(f2);
551 y2 = cy + ry * msin(f2);
552 res = me.arc2curve(x2, y2, rx, ry, angle, 0, sweep_flag, x2old, y2old, [f2, f2old, cx, cy]);
559 t = math.tan(df / 4);
563 m2 = [x1 + hx * s1, y1 - hy * c1];
564 m3 = [x2 + hx * s2, y2 - hy * c2];
566 m2[0] = 2 * m1[0] - m2[0];
567 m2[1] = 2 * m1[1] - m2[1];
569 return [m2, m3, m4].concat(res);
572 res = [m2, m3, m4].concat(res).join().split(",");
575 for (i = 0; i < ln; i++) {
576 newres[i] = i % 2 ? me.rotate(res[i - 1], res[i], rad).y : me.rotate(res[i], res[i + 1], rad).x;
583 rotateAndTranslatePath: function (sprite) {
584 var alpha = sprite.rotation.degrees,
585 cx = sprite.rotation.x,
586 cy = sprite.rotation.y,
587 dx = sprite.translation.x,
588 dy = sprite.translation.y,
595 if (!alpha && !dx && !dy) {
596 return this.pathToAbsolute(sprite.attr.path);
600 path = this.pathToAbsolute(sprite.attr.path);
601 for (i = path.length; i--;) {
602 p = res[i] = path[i].slice();
604 xy = this.rotatePoint(p[6], p[7], alpha, cx, cy);
609 while (p[j + 1] != null) {
610 xy = this.rotatePoint(p[j], p[j + 1], alpha, cx, cy);
612 p[j + 1] = xy.y + dy;
621 rotatePoint: function (x, y, alpha, cx, cy) {
632 alpha = alpha * this.radian;
633 var cos = Math.cos(alpha),
634 sin = Math.sin(alpha);
636 x: x * cos - y * sin + cx,
637 y: x * sin + y * cos + cy
641 pathDimensions: function (path) {
642 if (!path || !(path + "")) {
643 return {x: 0, y: 0, width: 0, height: 0};
645 path = this.path2curve(path);
653 for (; i < ln; i++) {
662 dim = this.curveDim(x, y, p[1], p[2], p[3], p[4], p[5], p[6]);
663 X = X.concat(dim.min.x, dim.max.x);
664 Y = Y.concat(dim.min.y, dim.max.y);
669 xmin = Math.min.apply(0, X);
670 ymin = Math.min.apply(0, Y);
675 width: Math.max.apply(0, X) - xmin,
676 height: Math.max.apply(0, Y) - ymin
680 intersectInside: function(path, cp1, cp2) {
681 return (cp2[0] - cp1[0]) * (path[1] - cp1[1]) > (cp2[1] - cp1[1]) * (path[0] - cp1[0]);
684 intersectIntersection: function(s, e, cp1, cp2) {
686 dcx = cp1[0] - cp2[0],
687 dcy = cp1[1] - cp2[1],
690 n1 = cp1[0] * cp2[1] - cp1[1] * cp2[0],
691 n2 = s[0] * e[1] - s[1] * e[0],
692 n3 = 1 / (dcx * dpy - dcy * dpx);
694 p[0] = (n1 * dpx - n2 * dcx) * n3;
695 p[1] = (n1 * dpy - n2 * dcy) * n3;
699 intersect: function(subjectPolygon, clipPolygon) {
702 ln = clipPolygon.length,
703 cp1 = clipPolygon[ln - 1],
704 outputList = subjectPolygon,
705 cp2, s, e, point, ln2, inputList, j;
706 for (; i < ln; ++i) {
707 cp2 = clipPolygon[i];
708 inputList = outputList;
710 s = inputList[inputList.length - 1];
712 ln2 = inputList.length;
713 for (; j < ln2; j++) {
715 if (me.intersectInside(e, cp1, cp2)) {
716 if (!me.intersectInside(s, cp1, cp2)) {
717 outputList.push(me.intersectIntersection(s, e, cp1, cp2));
721 else if (me.intersectInside(s, cp1, cp2)) {
722 outputList.push(me.intersectIntersection(s, e, cp1, cp2));
731 curveDim: function (p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y) {
732 var a = (c2x - 2 * c1x + p1x) - (p2x - 2 * c2x + c1x),
733 b = 2 * (c1x - p1x) - 2 * (c2x - c1x),
735 t1 = (-b + Math.sqrt(b * b - 4 * a * c)) / 2 / a,
736 t2 = (-b - Math.sqrt(b * b - 4 * a * c)) / 2 / a,
740 if (Math.abs(t1) > 1e12) {
743 if (Math.abs(t2) > 1e12) {
746 if (t1 > 0 && t1 < 1) {
747 dot = this.findDotAtSegment(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t1);
751 if (t2 > 0 && t2 < 1) {
752 dot = this.findDotAtSegment(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t2);
756 a = (c2y - 2 * c1y + p1y) - (p2y - 2 * c2y + c1y);
757 b = 2 * (c1y - p1y) - 2 * (c2y - c1y);
759 t1 = (-b + Math.sqrt(b * b - 4 * a * c)) / 2 / a;
760 t2 = (-b - Math.sqrt(b * b - 4 * a * c)) / 2 / a;
761 if (Math.abs(t1) > 1e12) {
764 if (Math.abs(t2) > 1e12) {
767 if (t1 > 0 && t1 < 1) {
768 dot = this.findDotAtSegment(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t1);
772 if (t2 > 0 && t2 < 1) {
773 dot = this.findDotAtSegment(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t2);
778 min: {x: Math.min.apply(0, x), y: Math.min.apply(0, y)},
779 max: {x: Math.max.apply(0, x), y: Math.max.apply(0, y)}
786 * Calculates bezier curve control anchor points for a particular point in a path, with a
787 * smoothing curve applied. The smoothness of the curve is controlled by the 'value' parameter.
788 * Note that this algorithm assumes that the line being smoothed is normalized going from left
789 * to right; it makes special adjustments assuming this orientation.
791 * @param {Number} prevX X coordinate of the previous point in the path
792 * @param {Number} prevY Y coordinate of the previous point in the path
793 * @param {Number} curX X coordinate of the current point in the path
794 * @param {Number} curY Y coordinate of the current point in the path
795 * @param {Number} nextX X coordinate of the next point in the path
796 * @param {Number} nextY Y coordinate of the next point in the path
797 * @param {Number} value A value to control the smoothness of the curve; this is used to
798 * divide the distance between points, so a value of 2 corresponds to
799 * half the distance between points (a very smooth line) while higher values
800 * result in less smooth curves. Defaults to 4.
801 * @return {Object} Object containing x1, y1, x2, y2 bezier control anchor points; x1 and y1
802 * are the control point for the curve toward the previous path point, and
803 * x2 and y2 are the control point for the curve toward the next path point.
805 getAnchors: function (prevX, prevY, curX, curY, nextX, nextY, value) {
814 control1Length, control2Length, control1Angle, control2Angle,
815 control1X, control1Y, control2X, control2Y, alpha;
817 // Find the length of each control anchor line, by dividing the horizontal distance
818 // between points by the value parameter.
819 control1Length = (curX - prevX) / value;
820 control2Length = (nextX - curX) / value;
822 // Determine the angle of each control anchor line. If the middle point is a vertical
823 // turnaround then we force it to a flat horizontal angle to prevent the curve from
824 // dipping above or below the middle point. Otherwise we use an angle that points
825 // toward the previous/next target point.
826 if ((curY >= prevY && curY >= nextY) || (curY <= prevY && curY <= nextY)) {
827 control1Angle = control2Angle = halfPI;
829 control1Angle = atan((curX - prevX) / abs(curY - prevY));
831 control1Angle = PI - control1Angle;
833 control2Angle = atan((nextX - curX) / abs(curY - nextY));
835 control2Angle = PI - control2Angle;
839 // Adjust the calculated angles so they point away from each other on the same line
840 alpha = halfPI - ((control1Angle + control2Angle) % (PI * 2)) / 2;
841 if (alpha > halfPI) {
844 control1Angle += alpha;
845 control2Angle += alpha;
847 // Find the control anchor points from the angles and length
848 control1X = curX - control1Length * sin(control1Angle);
849 control1Y = curY + control1Length * cos(control1Angle);
850 control2X = curX + control2Length * sin(control2Angle);
851 control2Y = curY + control2Length * cos(control2Angle);
853 // One last adjustment, make sure that no control anchor point extends vertically past
854 // its target prev/next point, as that results in curves dipping above or below and
855 // bending back strangely. If we find this happening we keep the control angle but
856 // reduce the length of the control line so it stays within bounds.
857 if ((curY > prevY && control1Y < prevY) || (curY < prevY && control1Y > prevY)) {
858 control1X += abs(prevY - control1Y) * (control1X - curX) / (control1Y - curY);
861 if ((curY > nextY && control2Y < nextY) || (curY < nextY && control2Y > nextY)) {
862 control2X -= abs(nextY - control2Y) * (control2X - curX) / (control2Y - curY);
874 /* Smoothing function for a path. Converts a path into cubic beziers. Value defines the divider of the distance between points.
875 * Defaults to a value of 4.
877 smooth: function (originalPath, value) {
878 var path = this.path2curve(originalPath),
891 for (; i < ii; i++) {
893 pathil = pathi.length,
894 pathim = path[i - 1],
895 pathiml = pathim.length,
896 pathip = path[i + 1],
897 pathipl = pathip && pathip.length;
898 if (pathi[0] == "M") {
902 while (path[j][0] != "C") {
907 newp.push(["M", mx, my]);
913 if (pathi[pathil - 2] == mx && pathi[pathil - 1] == my && (!pathip || pathip[0] == "M")) {
914 var begl = newp[beg].length;
915 points = this.getAnchors(pathim[pathiml - 2], pathim[pathiml - 1], mx, my, newp[beg][begl - 2], newp[beg][begl - 1], value);
916 newp[beg][1] = points.x2;
917 newp[beg][2] = points.y2;
919 else if (!pathip || pathip[0] == "M") {
921 x1: pathi[pathil - 2],
922 y1: pathi[pathil - 1]
925 points = this.getAnchors(pathim[pathiml - 2], pathim[pathiml - 1], pathi[pathil - 2], pathi[pathil - 1], pathip[pathipl - 2], pathip[pathipl - 1], value);
927 newp.push(["C", x, y, points.x1, points.y1, pathi[pathil - 2], pathi[pathil - 1]]);
934 findDotAtSegment: function (p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t) {
937 x: Math.pow(t1, 3) * p1x + Math.pow(t1, 2) * 3 * t * c1x + t1 * 3 * t * t * c2x + Math.pow(t, 3) * p2x,
938 y: Math.pow(t1, 3) * p1y + Math.pow(t1, 2) * 3 * t * c1y + t1 * 3 * t * t * c2y + Math.pow(t, 3) * p2y
943 * A utility method to deduce an appropriate tick configuration for the data set of given
946 * @param {Number/Date} from The minimum value in the data
947 * @param {Number/Date} to The maximum value in the data
948 * @param {Number} stepsMax The maximum number of ticks
949 * @return {Object} The calculated step and ends info; When `from` and `to` are Dates, refer to the
950 * return value of {@link #snapEndsByDate}. For numerical `from` and `to` the return value contains:
951 * @return {Number} return.from The result start value, which may be lower than the original start value
952 * @return {Number} return.to The result end value, which may be higher than the original end value
953 * @return {Number} return.power The calculate power.
954 * @return {Number} return.step The value size of each step
955 * @return {Number} return.steps The number of steps.
957 snapEnds: function (from, to, stepsMax) {
958 if (Ext.isDate(from)) {
959 return this.snapEndsByDate(from, to, stepsMax);
961 var step = (to - from) / stepsMax,
962 level = Math.floor(Math.log(step) / Math.LN10) + 1,
963 m = Math.pow(10, level),
965 modulo = Math.round((step % m) * Math.pow(10, 2 - level)),
966 interval = [[0, 15], [20, 4], [30, 2], [40, 4], [50, 9], [60, 4], [70, 2], [80, 4], [100, 15]],
973 ln = interval.length;
974 cur = from = Math.floor(from / m) * m;
975 for (i = 0; i < ln; i++) {
976 value = interval[i][0];
977 weight = (value - modulo) < 0 ? 1e6 : (value - modulo) / interval[i][1];
978 if (weight < topWeight) {
983 step = Math.floor(step * Math.pow(10, -level)) * Math.pow(10, level) + topValue * Math.pow(10, level - 2);
988 to = +cur.toFixed(10);
999 * A utility method to deduce an appropriate tick configuration for the data set of given
1000 * feature when data is Dates. Refer to {@link #snapEnds} for numeric data.
1002 * @param {Date} from The minimum value in the data
1003 * @param {Date} to The maximum value in the data
1004 * @param {Number} stepsMax The maximum number of ticks
1005 * @param {Boolean} lockEnds If true, the 'from' and 'to' parameters will be used as fixed end values
1006 * and will not be adjusted
1007 * @return {Object} The calculated step and ends info; properties are:
1008 * @return {Date} return.from The result start value, which may be lower than the original start value
1009 * @return {Date} return.to The result end value, which may be higher than the original end value
1010 * @return {Number} return.step The value size of each step
1011 * @return {Number} return.steps The number of steps.
1012 * NOTE: the steps may not divide the from/to range perfectly evenly;
1013 * there may be a smaller distance between the last step and the end value than between prior
1014 * steps, particularly when the `endsLocked` param is true. Therefore it is best to not use
1015 * the `steps` result when finding the axis tick points, instead use the `step`, `to`, and
1016 * `from` to find the correct point for each tick.
1018 snapEndsByDate: function (from, to, stepsMax, lockEnds) {
1019 var selectedStep = false, scales = [
1020 [Ext.Date.MILLI, [1, 2, 3, 5, 10, 20, 30, 50, 100, 200, 300, 500]],
1021 [Ext.Date.SECOND, [1, 2, 3, 5, 10, 15, 30]],
1022 [Ext.Date.MINUTE, [1, 2, 3, 5, 10, 20, 30]],
1023 [Ext.Date.HOUR, [1, 2, 3, 4, 6, 12]],
1024 [Ext.Date.DAY, [1, 2, 3, 7, 14]],
1025 [Ext.Date.MONTH, [1, 2, 3, 4, 6]]
1028 // Find the most desirable scale
1029 Ext.each(scales, function(scale, i) {
1030 for (j = 0; j < scale[1].length; j++) {
1031 if (to < Ext.Date.add(from, scale[0], scale[1][j] * stepsMax)) {
1032 selectedStep = [scale[0], scale[1][j]];
1037 if (!selectedStep) {
1038 yearDiff = this.snapEnds(from.getFullYear(), to.getFullYear() + 1, stepsMax, lockEnds);
1039 selectedStep = [Date.YEAR, Math.round(yearDiff.step)];
1041 return this.snapEndsByDateAndStep(from, to, selectedStep, lockEnds);
1046 * A utility method to deduce an appropriate tick configuration for the data set of given
1047 * feature and specific step size.
1048 * @param {Date} from The minimum value in the data
1049 * @param {Date} to The maximum value in the data
1050 * @param {Array} step An array with two components: The first is the unit of the step (day, month, year, etc).
1051 * The second one is the number of units for the step (1, 2, etc.).
1052 * @param {Boolean} lockEnds If true, the 'from' and 'to' parameters will be used as fixed end values
1053 * and will not be adjusted
1054 * @return {Object} See the return value of {@link #snapEndsByDate}.
1056 snapEndsByDateAndStep: function(from, to, step, lockEnds) {
1057 var fromStat = [from.getFullYear(), from.getMonth(), from.getDate(),
1058 from.getHours(), from.getMinutes(), from.getSeconds(), from.getMilliseconds()],
1059 steps = 0, testFrom, testTo;
1064 case Ext.Date.MILLI:
1065 testFrom = new Date(fromStat[0], fromStat[1], fromStat[2], fromStat[3],
1066 fromStat[4], fromStat[5], Math.floor(fromStat[6] / step[1]) * step[1]);
1068 case Ext.Date.SECOND:
1069 testFrom = new Date(fromStat[0], fromStat[1], fromStat[2], fromStat[3],
1070 fromStat[4], Math.floor(fromStat[5] / step[1]) * step[1], 0);
1072 case Ext.Date.MINUTE:
1073 testFrom = new Date(fromStat[0], fromStat[1], fromStat[2], fromStat[3],
1074 Math.floor(fromStat[4] / step[1]) * step[1], 0, 0);
1077 testFrom = new Date(fromStat[0], fromStat[1], fromStat[2],
1078 Math.floor(fromStat[3] / step[1]) * step[1], 0, 0, 0);
1081 testFrom = new Date(fromStat[0], fromStat[1],
1082 Math.floor(fromStat[2] - 1 / step[1]) * step[1] + 1, 0, 0, 0, 0);
1084 case Ext.Date.MONTH:
1085 testFrom = new Date(fromStat[0], Math.floor(fromStat[1] / step[1]) * step[1], 1, 0, 0, 0, 0);
1087 default: // Ext.Date.YEAR
1088 testFrom = new Date(Math.floor(fromStat[0] / step[1]) * step[1], 0, 1, 0, 0, 0, 0);
1094 // TODO(zhangbei) : We can do it better somehow...
1095 while (testTo < to) {
1096 testTo = Ext.Date.add(testTo, step[0], step[1]);
1106 step : (testTo - testFrom) / steps,
1111 sorter: function (a, b) {
1112 return a.offset - b.offset;
1115 rad: function(degrees) {
1116 return degrees % 360 * Math.PI / 180;
1119 degrees: function(radian) {
1120 return radian * 180 / Math.PI % 360;
1123 withinBox: function(x, y, bbox) {
1125 return (x >= bbox.x && x <= (bbox.x + bbox.width) && y >= bbox.y && y <= (bbox.y + bbox.height));
1128 parseGradient: function(gradient) {
1130 type = gradient.type || 'linear',
1131 angle = gradient.angle || 0,
1133 stops = gradient.stops,
1140 if (type == 'linear') {
1141 vector = [0, 0, Math.cos(angle * radian), Math.sin(angle * radian)];
1142 max = 1 / (Math.max(Math.abs(vector[2]), Math.abs(vector[3])) || 1);
1145 if (vector[2] < 0) {
1146 vector[0] = -vector[2];
1149 if (vector[3] < 0) {
1150 vector[1] = -vector[3];
1155 for (stop in stops) {
1156 if (stops.hasOwnProperty(stop) && me.stopsRE.test(stop)) {
1158 offset: parseInt(stop, 10),
1159 color: Ext.draw.Color.toHex(stops[stop].color) || '#ffffff',
1160 opacity: stops[stop].opacity || 1
1162 stopsArr.push(stopObj);
1165 // Sort by pct property
1166 Ext.Array.sort(stopsArr, me.sorter);
1167 if (type == 'linear') {
1179 centerX: gradient.centerX,
1180 centerY: gradient.centerY,
1181 focalX: gradient.focalX,
1182 focalY: gradient.focalY,
1183 radius: gradient.radius,