3 * Base Drawing class. Provides base drawing functions.
6 Ext.define('Ext.draw.Draw', {
7 /* Begin Definitions */
11 requires: ['Ext.draw.Color'],
15 pathToStringRE: /,?([achlmqrstvxz]),?/gi,
16 pathCommandRE: /([achlmqstvz])[\s,]*((-?\d*\.?\d*(?:e[-+]?\d+)?\s*,?\s*)+)/ig,
17 pathValuesRE: /(-?\d*\.?\d*(?:e[-+]?\d+)?)\s*,?\s*/ig,
19 radian: Math.PI / 180,
39 "stroke-opacity": null,
47 is: function(o, type) {
48 type = String(type).toLowerCase();
49 return (type == "object" && o === Object(o)) ||
50 (type == "undefined" && typeof o == type) ||
51 (type == "null" && o === null) ||
52 (type == "array" && Array.isArray && Array.isArray(o)) ||
53 (Object.prototype.toString.call(o).toLowerCase().slice(8, -1)) == type;
56 ellipsePath: function(sprite) {
57 var attr = sprite.attr;
58 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);
61 rectPath: function(sprite) {
62 var attr = sprite.attr;
64 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);
67 return Ext.String.format("M{0},{1}l{2},0,0,{3},{4},0z", attr.x, attr.y, attr.width, attr.height, -attr.width);
71 path2string: function () {
72 return this.join(",").replace(Ext.draw.Draw.pathToStringRE, "$1");
75 parsePathString: function (pathString) {
79 var paramCounts = {a: 7, c: 6, h: 1, l: 2, m: 2, q: 4, s: 4, t: 2, v: 1, z: 0},
82 if (me.is(pathString, "array") && me.is(pathString[0], "array")) { // rough assumption
83 data = me.pathClone(pathString);
86 String(pathString).replace(me.pathCommandRE, function (a, b, c) {
88 name = b.toLowerCase();
89 c.replace(me.pathValuesRE, function (a, b) {
92 if (name == "m" && params.length > 2) {
93 data.push([b].concat(params.splice(0, 2)));
95 b = (b == "m") ? "l" : "L";
97 while (params.length >= paramCounts[name]) {
98 data.push([b].concat(params.splice(0, paramCounts[name])));
99 if (!paramCounts[name]) {
105 data.toString = me.path2string;
109 mapPath: function (path, matrix) {
113 var x, y, i, ii, j, jj, pathi;
114 path = this.path2curve(path);
115 for (i = 0, ii = path.length; i < ii; i++) {
117 for (j = 1, jj = pathi.length; j < jj-1; j += 2) {
118 x = matrix.x(pathi[j], pathi[j + 1]);
119 y = matrix.y(pathi[j], pathi[j + 1]);
127 pathClone: function(pathArray) {
133 if (!this.is(pathArray, "array") || !this.is(pathArray && pathArray[0], "array")) { // rough assumption
134 pathArray = this.parsePathString(pathArray);
136 for (i = 0, ii = pathArray.length; i < ii; i++) {
138 for (j = 0, jj = pathArray[i].length; j < jj; j++) {
139 res[i][j] = pathArray[i][j];
142 res.toString = this.path2string;
146 pathToAbsolute: function (pathArray) {
147 if (!this.is(pathArray, "array") || !this.is(pathArray && pathArray[0], "array")) { // rough assumption
148 pathArray = this.parsePathString(pathArray);
164 if (pathArray[0][0] == "M") {
165 x = +pathArray[0][1];
166 y = +pathArray[0][2];
170 res[0] = ["M", x, y];
172 for (i = start, ii = pathArray.length; i < ii; i++) {
175 if (pa[0] != pa[0].toUpperCase()) {
176 r[0] = pa[0].toUpperCase();
197 for (j = 1, jj = pa.length; j < jj; j++) {
198 r[j] = +pa[j] + ((j % 2) ? x : y);
202 for (k = 0, kk = pa.length; k < kk; k++) {
218 mx = res[i][res[i].length - 2];
219 my = res[i][res[i].length - 1];
221 x = res[i][res[i].length - 2];
222 y = res[i][res[i].length - 1];
225 res.toString = this.path2string;
229 pathToRelative: function (pathArray) {
230 if (!this.is(pathArray, "array") || !this.is(pathArray && pathArray[0], "array")) {
231 pathArray = this.parsePathString(pathArray);
239 if (pathArray[0][0] == "M") {
245 res.push(["M", x, y]);
247 for (var i = start, ii = pathArray.length; i < ii; i++) {
250 if (pa[0] != pa[0].toLowerCase()) {
251 r[0] = pa[0].toLowerCase();
259 r[6] = +(pa[6] - x).toFixed(3);
260 r[7] = +(pa[7] - y).toFixed(3);
263 r[1] = +(pa[1] - y).toFixed(3);
269 for (var j = 1, jj = pa.length; j < jj; j++) {
270 r[j] = +(pa[j] - ((j % 2) ? x : y)).toFixed(3);
279 for (var k = 0, kk = pa.length; k < kk; k++) {
283 var len = res[i].length;
290 x += +res[i][len - 1];
293 y += +res[i][len - 1];
296 x += +res[i][len - 2];
297 y += +res[i][len - 1];
300 res.toString = this.path2string;
304 //Returns a path converted to a set of curveto commands
305 path2curve: function (path) {
307 points = me.pathToAbsolute(path),
309 attrs = {x: 0, y: 0, bx: 0, by: 0, X: 0, Y: 0, qx: null, qy: null},
310 i, seg, segLn, point;
312 for (i = 0; i < ln; i++) {
313 points[i] = me.command2curve(points[i], attrs);
314 if (points[i].length > 7) {
317 while (point.length) {
318 points.splice(i++, 0, ["C"].concat(point.splice(0, 6)));
325 attrs.x = seg[segLn - 2];
326 attrs.y = seg[segLn - 1];
327 attrs.bx = parseFloat(seg[segLn - 4]) || attrs.x;
328 attrs.by = parseFloat(seg[segLn - 3]) || attrs.y;
333 interpolatePaths: function (path, path2) {
335 p = me.pathToAbsolute(path),
336 p2 = me.pathToAbsolute(path2),
337 attrs = {x: 0, y: 0, bx: 0, by: 0, X: 0, Y: 0, qx: null, qy: null},
338 attrs2 = {x: 0, y: 0, bx: 0, by: 0, X: 0, Y: 0, qx: null, qy: null},
339 fixArc = function (pp, i) {
340 if (pp[i].length > 7) {
344 pp.splice(i++, 0, ["C"].concat(pi.splice(0, 6)));
347 ii = Math.max(p.length, p2.length || 0);
350 fixM = function (path1, path2, a1, a2, i) {
351 if (path1 && path2 && path1[i][0] == "M" && path2[i][0] != "M") {
352 path2.splice(i, 0, ["M", a2.x, a2.y]);
357 ii = Math.max(p.length, p2.length || 0);
360 for (var i = 0, ii = Math.max(p.length, p2.length || 0); i < ii; i++) {
361 p[i] = me.command2curve(p[i], attrs);
363 (p2[i] = me.command2curve(p2[i], attrs2));
365 fixM(p, p2, attrs, attrs2, i);
366 fixM(p2, p, attrs2, attrs, i);
370 seg2len = seg2.length;
371 attrs.x = seg[seglen - 2];
372 attrs.y = seg[seglen - 1];
373 attrs.bx = parseFloat(seg[seglen - 4]) || attrs.x;
374 attrs.by = parseFloat(seg[seglen - 3]) || attrs.y;
375 attrs2.bx = (parseFloat(seg2[seg2len - 4]) || attrs2.x);
376 attrs2.by = (parseFloat(seg2[seg2len - 3]) || attrs2.y);
377 attrs2.x = seg2[seg2len - 2];
378 attrs2.y = seg2[seg2len - 1];
383 //Returns any path command as a curveto command based on the attrs passed
384 command2curve: function (pathCommand, d) {
387 return ["C", d.x, d.y, d.x, d.y, d.x, d.y];
389 if (pathCommand[0] != "T" && pathCommand[0] != "Q") {
392 switch (pathCommand[0]) {
394 d.X = pathCommand[1];
395 d.Y = pathCommand[2];
398 pathCommand = ["C"].concat(me.arc2curve.apply(me, [d.x, d.y].concat(pathCommand.slice(1))));
401 pathCommand = ["C", d.x + (d.x - (d.bx || d.x)), d.y + (d.y - (d.by || d.y))].concat(pathCommand.slice(1));
404 d.qx = d.x + (d.x - (d.qx || d.x));
405 d.qy = d.y + (d.y - (d.qy || d.y));
406 pathCommand = ["C"].concat(me.quadratic2curve(d.x, d.y, d.qx, d.qy, pathCommand[1], pathCommand[2]));
409 d.qx = pathCommand[1];
410 d.qy = pathCommand[2];
411 pathCommand = ["C"].concat(me.quadratic2curve(d.x, d.y, pathCommand[1], pathCommand[2], pathCommand[3], pathCommand[4]));
414 pathCommand = ["C"].concat(d.x, d.y, pathCommand[1], pathCommand[2], pathCommand[1], pathCommand[2]);
417 pathCommand = ["C"].concat(d.x, d.y, pathCommand[1], d.y, pathCommand[1], d.y);
420 pathCommand = ["C"].concat(d.x, d.y, d.x, pathCommand[1], d.x, pathCommand[1]);
423 pathCommand = ["C"].concat(d.x, d.y, d.X, d.Y, d.X, d.Y);
429 quadratic2curve: function (x1, y1, ax, ay, x2, y2) {
442 rotate: function (x, y, rad) {
443 var cos = Math.cos(rad),
445 X = x * cos - y * sin,
446 Y = x * sin + y * cos;
450 arc2curve: function (x1, y1, rx, ry, angle, large_arc_flag, sweep_flag, x2, y2, recursive) {
451 // for more information of where this Math came from visit:
452 // http://www.w3.org/TR/SVG11/implnote.html#ArcImplementationNotes
456 _120 = PI * 120 / 180,
457 rad = radian * (+angle || 0),
465 xy, cos, sin, x, y, h, rx2, ry2, k, cx, cy, f1, f2, df, c1, s1, c2, s2,
466 t, hx, hy, m1, m2, m3, m4, newres, i, ln, f2old, x2old, y2old;
468 xy = me.rotate(x1, y1, -rad);
471 xy = me.rotate(x2, y2, -rad);
474 cos = mcos(radian * angle);
475 sin = msin(radian * angle);
478 h = (x * x) / (rx * rx) + (y * y) / (ry * ry);
486 k = (large_arc_flag == sweep_flag ? -1 : 1) *
487 msqrt(mabs((rx2 * ry2 - rx2 * y * y - ry2 * x * x) / (rx2 * y * y + ry2 * x * x)));
488 cx = k * rx * y / ry + (x1 + x2) / 2;
489 cy = k * -ry * x / rx + (y1 + y2) / 2;
490 f1 = masin(((y1 - cy) / ry).toFixed(7));
491 f2 = masin(((y2 - cy) / ry).toFixed(7));
493 f1 = x1 < cx ? PI - f1 : f1;
494 f2 = x2 < cx ? PI - f2 : f2;
501 if (sweep_flag && f1 > f2) {
504 if (!sweep_flag && f2 > f1) {
515 if (mabs(df) > _120) {
519 f2 = f1 + _120 * (sweep_flag && f2 > f1 ? 1 : -1);
520 x2 = cx + rx * mcos(f2);
521 y2 = cy + ry * msin(f2);
522 res = me.arc2curve(x2, y2, rx, ry, angle, 0, sweep_flag, x2old, y2old, [f2, f2old, cx, cy]);
529 t = math.tan(df / 4);
533 m2 = [x1 + hx * s1, y1 - hy * c1];
534 m3 = [x2 + hx * s2, y2 - hy * c2];
536 m2[0] = 2 * m1[0] - m2[0];
537 m2[1] = 2 * m1[1] - m2[1];
539 return [m2, m3, m4].concat(res);
542 res = [m2, m3, m4].concat(res).join().split(",");
545 for (i = 0; i < ln; i++) {
546 newres[i] = i % 2 ? me.rotate(res[i - 1], res[i], rad).y : me.rotate(res[i], res[i + 1], rad).x;
552 rotatePoint: function (x, y, alpha, cx, cy) {
563 alpha = alpha * this.radian;
564 var cos = Math.cos(alpha),
565 sin = Math.sin(alpha);
567 x: x * cos - y * sin + cx,
568 y: x * sin + y * cos + cy
572 rotateAndTranslatePath: function (sprite) {
573 var alpha = sprite.rotation.degrees,
574 cx = sprite.rotation.x,
575 cy = sprite.rotation.y,
576 dx = sprite.translation.x,
577 dy = sprite.translation.y,
584 if (!alpha && !dx && !dy) {
585 return this.pathToAbsolute(sprite.attr.path);
589 path = this.pathToAbsolute(sprite.attr.path);
590 for (i = path.length; i--;) {
591 p = res[i] = path[i].slice();
593 xy = this.rotatePoint(p[6], p[7], alpha, cx, cy);
598 while (p[j + 1] != null) {
599 xy = this.rotatePoint(p[j], p[j + 1], alpha, cx, cy);
601 p[j + 1] = xy.y + dy;
609 pathDimensions: function (path) {
610 if (!path || !(path + "")) {
611 return {x: 0, y: 0, width: 0, height: 0};
613 path = this.path2curve(path);
624 for (i = 0, ii = path.length; i < ii; i++) {
633 dim = this.curveDim(x, y, p[1], p[2], p[3], p[4], p[5], p[6]);
634 X = X.concat(dim.min.x, dim.max.x);
635 Y = Y.concat(dim.min.y, dim.max.y);
640 xmin = Math.min.apply(0, X);
641 ymin = Math.min.apply(0, Y);
646 width: Math.max.apply(0, X) - xmin,
647 height: Math.max.apply(0, Y) - ymin
651 intersect: function(subjectPolygon, clipPolygon) {
652 var cp1, cp2, s, e, point;
653 var inside = function(p) {
654 return (cp2[0]-cp1[0]) * (p[1]-cp1[1]) > (cp2[1]-cp1[1]) * (p[0]-cp1[0]);
656 var intersection = function() {
658 var dcx = cp1[0]-cp2[0],
662 n1 = cp1[0]*cp2[1] - cp1[1]*cp2[0],
663 n2 = s[0]*e[1] - s[1]*e[0],
664 n3 = 1 / (dcx*dpy - dcy*dpx);
666 p[0] = (n1*dpx - n2*dcx) * n3;
667 p[1] = (n1*dpy - n2*dcy) * n3;
670 var outputList = subjectPolygon;
671 cp1 = clipPolygon[clipPolygon.length -1];
672 for (var i = 0, l = clipPolygon.length; i < l; ++i) {
673 cp2 = clipPolygon[i];
674 var inputList = outputList;
676 s = inputList[inputList.length -1];
677 for (var j = 0, ln = inputList.length; j < ln; j++) {
681 outputList.push(intersection());
684 } else if (inside(s)) {
685 outputList.push(intersection());
694 curveDim: function (p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y) {
695 var a = (c2x - 2 * c1x + p1x) - (p2x - 2 * c2x + c1x),
696 b = 2 * (c1x - p1x) - 2 * (c2x - c1x),
698 t1 = (-b + Math.sqrt(b * b - 4 * a * c)) / 2 / a,
699 t2 = (-b - Math.sqrt(b * b - 4 * a * c)) / 2 / a,
703 if (Math.abs(t1) > 1e12) {
706 if (Math.abs(t2) > 1e12) {
709 if (t1 > 0 && t1 < 1) {
710 dot = this.findDotAtSegment(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t1);
714 if (t2 > 0 && t2 < 1) {
715 dot = this.findDotAtSegment(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t2);
719 a = (c2y - 2 * c1y + p1y) - (p2y - 2 * c2y + c1y);
720 b = 2 * (c1y - p1y) - 2 * (c2y - c1y);
722 t1 = (-b + Math.sqrt(b * b - 4 * a * c)) / 2 / a;
723 t2 = (-b - Math.sqrt(b * b - 4 * a * c)) / 2 / a;
724 if (Math.abs(t1) > 1e12) {
727 if (Math.abs(t2) > 1e12) {
730 if (t1 > 0 && t1 < 1) {
731 dot = this.findDotAtSegment(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t1);
735 if (t2 > 0 && t2 < 1) {
736 dot = this.findDotAtSegment(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t2);
741 min: {x: Math.min.apply(0, x), y: Math.min.apply(0, y)},
742 max: {x: Math.max.apply(0, x), y: Math.max.apply(0, y)}
746 getAnchors: function (p1x, p1y, p2x, p2y, p3x, p3y, value) {
748 var l = Math.min(Math.sqrt(Math.pow(p1x - p2x, 2) + Math.pow(p1y - p2y, 2)) / value, Math.sqrt(Math.pow(p3x - p2x, 2) + Math.pow(p3y - p2y, 2)) / value),
749 a = Math.atan((p2x - p1x) / Math.abs(p2y - p1y)),
750 b = Math.atan((p3x - p2x) / Math.abs(p2y - p3y)),
752 a = p1y < p2y ? pi - a : a;
753 b = p3y < p2y ? pi - b : b;
754 var alpha = pi / 2 - ((a + b) % (pi * 2)) / 2;
755 alpha > pi / 2 && (alpha -= pi);
756 var dx1 = l * Math.sin(alpha + a),
757 dy1 = l * Math.cos(alpha + a),
758 dx2 = l * Math.sin(alpha + b),
759 dy2 = l * Math.cos(alpha + b),
769 /* Smoothing function for a path. Converts a path into cubic beziers. Value defines the divider of the distance between points.
770 * Defaults to a value of 4.
772 smooth: function (originalPath, value) {
773 var path = this.path2curve(originalPath),
786 for (; i < ii; i++) {
788 pathil = pathi.length,
789 pathim = path[i - 1],
790 pathiml = pathim.length,
791 pathip = path[i + 1],
792 pathipl = pathip && pathip.length;
793 if (pathi[0] == "M") {
797 while (path[j][0] != "C") {
802 newp.push(["M", mx, my]);
808 if (pathi[pathil - 2] == mx && pathi[pathil - 1] == my && (!pathip || pathip[0] == "M")) {
809 var begl = newp[beg].length;
810 points = this.getAnchors(pathim[pathiml - 2], pathim[pathiml - 1], mx, my, newp[beg][begl - 2], newp[beg][begl - 1], value);
811 newp[beg][1] = points.x2;
812 newp[beg][2] = points.y2;
814 else if (!pathip || pathip[0] == "M") {
816 x1: pathi[pathil - 2],
817 y1: pathi[pathil - 1]
820 points = this.getAnchors(pathim[pathiml - 2], pathim[pathiml - 1], pathi[pathil - 2], pathi[pathil - 1], pathip[pathipl - 2], pathip[pathipl - 1], value);
822 newp.push(["C", x, y, points.x1, points.y1, pathi[pathil - 2], pathi[pathil - 1]]);
829 findDotAtSegment: function (p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t) {
832 x: Math.pow(t1, 3) * p1x + Math.pow(t1, 2) * 3 * t * c1x + t1 * 3 * t * t * c2x + Math.pow(t, 3) * p2x,
833 y: Math.pow(t1, 3) * p1y + Math.pow(t1, 2) * 3 * t * c1y + t1 * 3 * t * t * c2y + Math.pow(t, 3) * p2y
837 snapEnds: function (from, to, stepsMax) {
838 var step = (to - from) / stepsMax,
839 level = Math.floor(Math.log(step) / Math.LN10) + 1,
840 m = Math.pow(10, level),
842 modulo = Math.round((step % m) * Math.pow(10, 2 - level)),
843 interval = [[0, 15], [20, 4], [30, 2], [40, 4], [50, 9], [60, 4], [70, 2], [80, 4], [100, 15]],
850 ln = interval.length;
851 cur = from = Math.floor(from / m) * m;
852 for (i = 0; i < ln; i++) {
853 value = interval[i][0];
854 weight = (value - modulo) < 0 ? 1e6 : (value - modulo) / interval[i][1];
855 if (weight < topWeight) {
860 step = Math.floor(step * Math.pow(10, -level)) * Math.pow(10, level) + topValue * Math.pow(10, level - 2);
865 to = +cur.toFixed(10);
875 sorter: function (a, b) {
876 return a.offset - b.offset;
879 rad: function(degrees) {
880 return degrees % 360 * Math.PI / 180;
883 degrees: function(radian) {
884 return radian * 180 / Math.PI % 360;
887 withinBox: function(x, y, bbox) {
889 return (x >= bbox.x && x <= (bbox.x + bbox.width) && y >= bbox.y && y <= (bbox.y + bbox.height));
892 parseGradient: function(gradient) {
894 type = gradient.type || 'linear',
895 angle = gradient.angle || 0,
897 stops = gradient.stops,
904 if (type == 'linear') {
905 vector = [0, 0, Math.cos(angle * radian), Math.sin(angle * radian)];
906 max = 1 / (Math.max(Math.abs(vector[2]), Math.abs(vector[3])) || 1);
910 vector[0] = -vector[2];
914 vector[1] = -vector[3];
919 for (stop in stops) {
920 if (stops.hasOwnProperty(stop) && me.stopsRE.test(stop)) {
922 offset: parseInt(stop, 10),
923 color: Ext.draw.Color.toHex(stops[stop].color) || '#ffffff',
924 opacity: stops[stop].opacity || 1
926 stopsArr.push(stopObj);
929 // Sort by pct property
930 Ext.Array.sort(stopsArr, me.sorter);
931 if (type == 'linear') {
943 centerX: gradient.centerX,
944 centerY: gradient.centerY,
945 focalX: gradient.focalX,
946 focalY: gradient.focalY,
947 radius: gradient.radius,