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
942 snapEnds: function (from, to, stepsMax) {
943 var step = (to - from) / stepsMax,
944 level = Math.floor(Math.log(step) / Math.LN10) + 1,
945 m = Math.pow(10, level),
947 modulo = Math.round((step % m) * Math.pow(10, 2 - level)),
948 interval = [[0, 15], [20, 4], [30, 2], [40, 4], [50, 9], [60, 4], [70, 2], [80, 4], [100, 15]],
955 ln = interval.length;
956 cur = from = Math.floor(from / m) * m;
957 for (i = 0; i < ln; i++) {
958 value = interval[i][0];
959 weight = (value - modulo) < 0 ? 1e6 : (value - modulo) / interval[i][1];
960 if (weight < topWeight) {
965 step = Math.floor(step * Math.pow(10, -level)) * Math.pow(10, level) + topValue * Math.pow(10, level - 2);
970 to = +cur.toFixed(10);
980 sorter: function (a, b) {
981 return a.offset - b.offset;
984 rad: function(degrees) {
985 return degrees % 360 * Math.PI / 180;
988 degrees: function(radian) {
989 return radian * 180 / Math.PI % 360;
992 withinBox: function(x, y, bbox) {
994 return (x >= bbox.x && x <= (bbox.x + bbox.width) && y >= bbox.y && y <= (bbox.y + bbox.height));
997 parseGradient: function(gradient) {
999 type = gradient.type || 'linear',
1000 angle = gradient.angle || 0,
1002 stops = gradient.stops,
1009 if (type == 'linear') {
1010 vector = [0, 0, Math.cos(angle * radian), Math.sin(angle * radian)];
1011 max = 1 / (Math.max(Math.abs(vector[2]), Math.abs(vector[3])) || 1);
1014 if (vector[2] < 0) {
1015 vector[0] = -vector[2];
1018 if (vector[3] < 0) {
1019 vector[1] = -vector[3];
1024 for (stop in stops) {
1025 if (stops.hasOwnProperty(stop) && me.stopsRE.test(stop)) {
1027 offset: parseInt(stop, 10),
1028 color: Ext.draw.Color.toHex(stops[stop].color) || '#ffffff',
1029 opacity: stops[stop].opacity || 1
1031 stopsArr.push(stopObj);
1034 // Sort by pct property
1035 Ext.Array.sort(stopsArr, me.sorter);
1036 if (type == 'linear') {
1048 centerX: gradient.centerX,
1049 centerY: gradient.centerY,
1050 focalX: gradient.focalX,
1051 focalY: gradient.focalY,
1052 radius: gradient.radius,