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 // To be deprecated, converts itself (an arrayPath) to a proper SVG path string
72 path2string: function () {
73 return this.join(",").replace(Ext.draw.Draw.pathToStringRE, "$1");
76 // Convert the passed arrayPath to a proper SVG path string (d attribute)
77 pathToString: function(arrayPath) {
78 return arrayPath.join(",").replace(Ext.draw.Draw.pathToStringRE, "$1");
81 parsePathString: function (pathString) {
85 var paramCounts = {a: 7, c: 6, h: 1, l: 2, m: 2, q: 4, s: 4, t: 2, v: 1, z: 0},
88 if (me.is(pathString, "array") && me.is(pathString[0], "array")) { // rough assumption
89 data = me.pathClone(pathString);
92 String(pathString).replace(me.pathCommandRE, function (a, b, c) {
94 name = b.toLowerCase();
95 c.replace(me.pathValuesRE, function (a, b) {
98 if (name == "m" && params.length > 2) {
99 data.push([b].concat(params.splice(0, 2)));
101 b = (b == "m") ? "l" : "L";
103 while (params.length >= paramCounts[name]) {
104 data.push([b].concat(params.splice(0, paramCounts[name])));
105 if (!paramCounts[name]) {
111 data.toString = me.path2string;
115 mapPath: function (path, matrix) {
119 var x, y, i, ii, j, jj, pathi;
120 path = this.path2curve(path);
121 for (i = 0, ii = path.length; i < ii; i++) {
123 for (j = 1, jj = pathi.length; j < jj-1; j += 2) {
124 x = matrix.x(pathi[j], pathi[j + 1]);
125 y = matrix.y(pathi[j], pathi[j + 1]);
133 pathClone: function(pathArray) {
136 if (!this.is(pathArray, "array") || !this.is(pathArray && pathArray[0], "array")) { // rough assumption
137 pathArray = this.parsePathString(pathArray);
139 for (i = 0, ii = pathArray.length; i < ii; i++) {
141 for (j = 0, jj = pathArray[i].length; j < jj; j++) {
142 res[i][j] = pathArray[i][j];
145 res.toString = this.path2string;
149 pathToAbsolute: function (pathArray) {
150 if (!this.is(pathArray, "array") || !this.is(pathArray && pathArray[0], "array")) { // rough assumption
151 pathArray = this.parsePathString(pathArray);
159 ln = pathArray.length,
160 r, pathSegment, j, ln2;
161 // MoveTo initial x/y position
162 if (ln && pathArray[0][0] == "M") {
163 x = +pathArray[0][1];
164 y = +pathArray[0][2];
168 res[0] = ["M", x, y];
170 for (; i < ln; i++) {
172 pathSegment = pathArray[i];
173 if (pathSegment[0] != pathSegment[0].toUpperCase()) {
174 r[0] = pathSegment[0].toUpperCase();
178 r[1] = pathSegment[1];
179 r[2] = pathSegment[2];
180 r[3] = pathSegment[3];
181 r[4] = pathSegment[4];
182 r[5] = pathSegment[5];
183 r[6] = +(pathSegment[6] + x);
184 r[7] = +(pathSegment[7] + y);
188 r[1] = +pathSegment[1] + y;
192 r[1] = +pathSegment[1] + x;
196 mx = +pathSegment[1] + x;
197 my = +pathSegment[2] + y;
200 ln2 = pathSegment.length;
201 for (; j < ln2; j++) {
202 r[j] = +pathSegment[j] + ((j % 2) ? x : y);
208 ln2 = pathSegment.length;
209 for (; j < ln2; j++) {
210 res[i][j] = pathSegment[j];
229 pathSegment = res[i];
230 ln2 = pathSegment.length;
231 mx = pathSegment[ln2 - 2];
232 my = pathSegment[ln2 - 1];
234 pathSegment = res[i];
235 ln2 = pathSegment.length;
236 x = pathSegment[ln2 - 2];
237 y = pathSegment[ln2 - 1];
240 res.toString = this.path2string;
245 pathToRelative: function (pathArray) {
246 if (!this.is(pathArray, "array") || !this.is(pathArray && pathArray[0], "array")) {
247 pathArray = this.parsePathString(pathArray);
255 if (pathArray[0][0] == "M") {
261 res.push(["M", x, y]);
263 for (var i = start, ii = pathArray.length; i < ii; i++) {
266 if (pa[0] != pa[0].toLowerCase()) {
267 r[0] = pa[0].toLowerCase();
275 r[6] = +(pa[6] - x).toFixed(3);
276 r[7] = +(pa[7] - y).toFixed(3);
279 r[1] = +(pa[1] - y).toFixed(3);
285 for (var j = 1, jj = pa.length; j < jj; j++) {
286 r[j] = +(pa[j] - ((j % 2) ? x : y)).toFixed(3);
295 for (var k = 0, kk = pa.length; k < kk; k++) {
299 var len = res[i].length;
306 x += +res[i][len - 1];
309 y += +res[i][len - 1];
312 x += +res[i][len - 2];
313 y += +res[i][len - 1];
316 res.toString = this.path2string;
320 // Returns a path converted to a set of curveto commands
321 path2curve: function (path) {
323 points = me.pathToAbsolute(path),
325 attrs = {x: 0, y: 0, bx: 0, by: 0, X: 0, Y: 0, qx: null, qy: null},
326 i, seg, segLn, point;
328 for (i = 0; i < ln; i++) {
329 points[i] = me.command2curve(points[i], attrs);
330 if (points[i].length > 7) {
333 while (point.length) {
334 points.splice(i++, 0, ["C"].concat(point.splice(0, 6)));
341 attrs.x = seg[segLn - 2];
342 attrs.y = seg[segLn - 1];
343 attrs.bx = parseFloat(seg[segLn - 4]) || attrs.x;
344 attrs.by = parseFloat(seg[segLn - 3]) || attrs.y;
349 interpolatePaths: function (path, path2) {
351 p = me.pathToAbsolute(path),
352 p2 = me.pathToAbsolute(path2),
353 attrs = {x: 0, y: 0, bx: 0, by: 0, X: 0, Y: 0, qx: null, qy: null},
354 attrs2 = {x: 0, y: 0, bx: 0, by: 0, X: 0, Y: 0, qx: null, qy: null},
355 fixArc = function (pp, i) {
356 if (pp[i].length > 7) {
360 pp.splice(i++, 0, ["C"].concat(pi.splice(0, 6)));
363 ii = Math.max(p.length, p2.length || 0);
366 fixM = function (path1, path2, a1, a2, i) {
367 if (path1 && path2 && path1[i][0] == "M" && path2[i][0] != "M") {
368 path2.splice(i, 0, ["M", a2.x, a2.y]);
373 ii = Math.max(p.length, p2.length || 0);
376 for (var i = 0, ii = Math.max(p.length, p2.length || 0); i < ii; i++) {
377 p[i] = me.command2curve(p[i], attrs);
379 (p2[i] = me.command2curve(p2[i], attrs2));
381 fixM(p, p2, attrs, attrs2, i);
382 fixM(p2, p, attrs2, attrs, i);
386 seg2len = seg2.length;
387 attrs.x = seg[seglen - 2];
388 attrs.y = seg[seglen - 1];
389 attrs.bx = parseFloat(seg[seglen - 4]) || attrs.x;
390 attrs.by = parseFloat(seg[seglen - 3]) || attrs.y;
391 attrs2.bx = (parseFloat(seg2[seg2len - 4]) || attrs2.x);
392 attrs2.by = (parseFloat(seg2[seg2len - 3]) || attrs2.y);
393 attrs2.x = seg2[seg2len - 2];
394 attrs2.y = seg2[seg2len - 1];
399 //Returns any path command as a curveto command based on the attrs passed
400 command2curve: function (pathCommand, d) {
403 return ["C", d.x, d.y, d.x, d.y, d.x, d.y];
405 if (pathCommand[0] != "T" && pathCommand[0] != "Q") {
408 switch (pathCommand[0]) {
410 d.X = pathCommand[1];
411 d.Y = pathCommand[2];
414 pathCommand = ["C"].concat(me.arc2curve.apply(me, [d.x, d.y].concat(pathCommand.slice(1))));
417 pathCommand = ["C", d.x + (d.x - (d.bx || d.x)), d.y + (d.y - (d.by || d.y))].concat(pathCommand.slice(1));
420 d.qx = d.x + (d.x - (d.qx || d.x));
421 d.qy = d.y + (d.y - (d.qy || d.y));
422 pathCommand = ["C"].concat(me.quadratic2curve(d.x, d.y, d.qx, d.qy, pathCommand[1], pathCommand[2]));
425 d.qx = pathCommand[1];
426 d.qy = pathCommand[2];
427 pathCommand = ["C"].concat(me.quadratic2curve(d.x, d.y, pathCommand[1], pathCommand[2], pathCommand[3], pathCommand[4]));
430 pathCommand = ["C"].concat(d.x, d.y, pathCommand[1], pathCommand[2], pathCommand[1], pathCommand[2]);
433 pathCommand = ["C"].concat(d.x, d.y, pathCommand[1], d.y, pathCommand[1], d.y);
436 pathCommand = ["C"].concat(d.x, d.y, d.x, pathCommand[1], d.x, pathCommand[1]);
439 pathCommand = ["C"].concat(d.x, d.y, d.X, d.Y, d.X, d.Y);
445 quadratic2curve: function (x1, y1, ax, ay, x2, y2) {
458 rotate: function (x, y, rad) {
459 var cos = Math.cos(rad),
461 X = x * cos - y * sin,
462 Y = x * sin + y * cos;
466 arc2curve: function (x1, y1, rx, ry, angle, large_arc_flag, sweep_flag, x2, y2, recursive) {
467 // for more information of where this Math came from visit:
468 // http://www.w3.org/TR/SVG11/implnote.html#ArcImplementationNotes
472 _120 = PI * 120 / 180,
473 rad = radian * (+angle || 0),
481 xy, cos, sin, x, y, h, rx2, ry2, k, cx, cy, f1, f2, df, c1, s1, c2, s2,
482 t, hx, hy, m1, m2, m3, m4, newres, i, ln, f2old, x2old, y2old;
484 xy = me.rotate(x1, y1, -rad);
487 xy = me.rotate(x2, y2, -rad);
490 cos = mcos(radian * angle);
491 sin = msin(radian * angle);
494 h = (x * x) / (rx * rx) + (y * y) / (ry * ry);
502 k = (large_arc_flag == sweep_flag ? -1 : 1) *
503 msqrt(mabs((rx2 * ry2 - rx2 * y * y - ry2 * x * x) / (rx2 * y * y + ry2 * x * x)));
504 cx = k * rx * y / ry + (x1 + x2) / 2;
505 cy = k * -ry * x / rx + (y1 + y2) / 2;
506 f1 = masin(((y1 - cy) / ry).toFixed(7));
507 f2 = masin(((y2 - cy) / ry).toFixed(7));
509 f1 = x1 < cx ? PI - f1 : f1;
510 f2 = x2 < cx ? PI - f2 : f2;
517 if (sweep_flag && f1 > f2) {
520 if (!sweep_flag && f2 > f1) {
531 if (mabs(df) > _120) {
535 f2 = f1 + _120 * (sweep_flag && f2 > f1 ? 1 : -1);
536 x2 = cx + rx * mcos(f2);
537 y2 = cy + ry * msin(f2);
538 res = me.arc2curve(x2, y2, rx, ry, angle, 0, sweep_flag, x2old, y2old, [f2, f2old, cx, cy]);
545 t = math.tan(df / 4);
549 m2 = [x1 + hx * s1, y1 - hy * c1];
550 m3 = [x2 + hx * s2, y2 - hy * c2];
552 m2[0] = 2 * m1[0] - m2[0];
553 m2[1] = 2 * m1[1] - m2[1];
555 return [m2, m3, m4].concat(res);
558 res = [m2, m3, m4].concat(res).join().split(",");
561 for (i = 0; i < ln; i++) {
562 newres[i] = i % 2 ? me.rotate(res[i - 1], res[i], rad).y : me.rotate(res[i], res[i + 1], rad).x;
569 rotateAndTranslatePath: function (sprite) {
570 var alpha = sprite.rotation.degrees,
571 cx = sprite.rotation.x,
572 cy = sprite.rotation.y,
573 dx = sprite.translation.x,
574 dy = sprite.translation.y,
581 if (!alpha && !dx && !dy) {
582 return this.pathToAbsolute(sprite.attr.path);
586 path = this.pathToAbsolute(sprite.attr.path);
587 for (i = path.length; i--;) {
588 p = res[i] = path[i].slice();
590 xy = this.rotatePoint(p[6], p[7], alpha, cx, cy);
595 while (p[j + 1] != null) {
596 xy = this.rotatePoint(p[j], p[j + 1], alpha, cx, cy);
598 p[j + 1] = xy.y + dy;
607 rotatePoint: function (x, y, alpha, cx, cy) {
618 alpha = alpha * this.radian;
619 var cos = Math.cos(alpha),
620 sin = Math.sin(alpha);
622 x: x * cos - y * sin + cx,
623 y: x * sin + y * cos + cy
627 pathDimensions: function (path) {
628 if (!path || !(path + "")) {
629 return {x: 0, y: 0, width: 0, height: 0};
631 path = this.path2curve(path);
639 for (; i < ln; i++) {
648 dim = this.curveDim(x, y, p[1], p[2], p[3], p[4], p[5], p[6]);
649 X = X.concat(dim.min.x, dim.max.x);
650 Y = Y.concat(dim.min.y, dim.max.y);
655 xmin = Math.min.apply(0, X);
656 ymin = Math.min.apply(0, Y);
661 width: Math.max.apply(0, X) - xmin,
662 height: Math.max.apply(0, Y) - ymin
666 intersectInside: function(path, cp1, cp2) {
667 return (cp2[0] - cp1[0]) * (path[1] - cp1[1]) > (cp2[1] - cp1[1]) * (path[0] - cp1[0]);
670 intersectIntersection: function(s, e, cp1, cp2) {
672 dcx = cp1[0] - cp2[0],
673 dcy = cp1[1] - cp2[1],
676 n1 = cp1[0] * cp2[1] - cp1[1] * cp2[0],
677 n2 = s[0] * e[1] - s[1] * e[0],
678 n3 = 1 / (dcx * dpy - dcy * dpx);
680 p[0] = (n1 * dpx - n2 * dcx) * n3;
681 p[1] = (n1 * dpy - n2 * dcy) * n3;
685 intersect: function(subjectPolygon, clipPolygon) {
688 ln = clipPolygon.length,
689 cp1 = clipPolygon[ln - 1],
690 outputList = subjectPolygon,
691 cp2, s, e, point, ln2, inputList, j;
692 for (; i < ln; ++i) {
693 cp2 = clipPolygon[i];
694 inputList = outputList;
696 s = inputList[inputList.length - 1];
698 ln2 = inputList.length;
699 for (; j < ln2; j++) {
701 if (me.intersectInside(e, cp1, cp2)) {
702 if (!me.intersectInside(s, cp1, cp2)) {
703 outputList.push(me.intersectIntersection(s, e, cp1, cp2));
707 else if (me.intersectInside(s, cp1, cp2)) {
708 outputList.push(me.intersectIntersection(s, e, cp1, cp2));
717 curveDim: function (p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y) {
718 var a = (c2x - 2 * c1x + p1x) - (p2x - 2 * c2x + c1x),
719 b = 2 * (c1x - p1x) - 2 * (c2x - c1x),
721 t1 = (-b + Math.sqrt(b * b - 4 * a * c)) / 2 / a,
722 t2 = (-b - Math.sqrt(b * b - 4 * a * c)) / 2 / a,
726 if (Math.abs(t1) > 1e12) {
729 if (Math.abs(t2) > 1e12) {
732 if (t1 > 0 && t1 < 1) {
733 dot = this.findDotAtSegment(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t1);
737 if (t2 > 0 && t2 < 1) {
738 dot = this.findDotAtSegment(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t2);
742 a = (c2y - 2 * c1y + p1y) - (p2y - 2 * c2y + c1y);
743 b = 2 * (c1y - p1y) - 2 * (c2y - c1y);
745 t1 = (-b + Math.sqrt(b * b - 4 * a * c)) / 2 / a;
746 t2 = (-b - Math.sqrt(b * b - 4 * a * c)) / 2 / a;
747 if (Math.abs(t1) > 1e12) {
750 if (Math.abs(t2) > 1e12) {
753 if (t1 > 0 && t1 < 1) {
754 dot = this.findDotAtSegment(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t1);
758 if (t2 > 0 && t2 < 1) {
759 dot = this.findDotAtSegment(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t2);
764 min: {x: Math.min.apply(0, x), y: Math.min.apply(0, y)},
765 max: {x: Math.max.apply(0, x), y: Math.max.apply(0, y)}
769 getAnchors: function (p1x, p1y, p2x, p2y, p3x, p3y, value) {
771 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),
772 a = Math.atan((p2x - p1x) / Math.abs(p2y - p1y)),
773 b = Math.atan((p3x - p2x) / Math.abs(p2y - p3y)),
775 a = p1y < p2y ? pi - a : a;
776 b = p3y < p2y ? pi - b : b;
777 var alpha = pi / 2 - ((a + b) % (pi * 2)) / 2;
778 alpha > pi / 2 && (alpha -= pi);
779 var dx1 = l * Math.sin(alpha + a),
780 dy1 = l * Math.cos(alpha + a),
781 dx2 = l * Math.sin(alpha + b),
782 dy2 = l * Math.cos(alpha + b),
792 /* Smoothing function for a path. Converts a path into cubic beziers. Value defines the divider of the distance between points.
793 * Defaults to a value of 4.
795 smooth: function (originalPath, value) {
796 var path = this.path2curve(originalPath),
809 for (; i < ii; i++) {
811 pathil = pathi.length,
812 pathim = path[i - 1],
813 pathiml = pathim.length,
814 pathip = path[i + 1],
815 pathipl = pathip && pathip.length;
816 if (pathi[0] == "M") {
820 while (path[j][0] != "C") {
825 newp.push(["M", mx, my]);
831 if (pathi[pathil - 2] == mx && pathi[pathil - 1] == my && (!pathip || pathip[0] == "M")) {
832 var begl = newp[beg].length;
833 points = this.getAnchors(pathim[pathiml - 2], pathim[pathiml - 1], mx, my, newp[beg][begl - 2], newp[beg][begl - 1], value);
834 newp[beg][1] = points.x2;
835 newp[beg][2] = points.y2;
837 else if (!pathip || pathip[0] == "M") {
839 x1: pathi[pathil - 2],
840 y1: pathi[pathil - 1]
843 points = this.getAnchors(pathim[pathiml - 2], pathim[pathiml - 1], pathi[pathil - 2], pathi[pathil - 1], pathip[pathipl - 2], pathip[pathipl - 1], value);
845 newp.push(["C", x, y, points.x1, points.y1, pathi[pathil - 2], pathi[pathil - 1]]);
852 findDotAtSegment: function (p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t) {
855 x: Math.pow(t1, 3) * p1x + Math.pow(t1, 2) * 3 * t * c1x + t1 * 3 * t * t * c2x + Math.pow(t, 3) * p2x,
856 y: Math.pow(t1, 3) * p1y + Math.pow(t1, 2) * 3 * t * c1y + t1 * 3 * t * t * c2y + Math.pow(t, 3) * p2y
860 snapEnds: function (from, to, stepsMax) {
861 var step = (to - from) / stepsMax,
862 level = Math.floor(Math.log(step) / Math.LN10) + 1,
863 m = Math.pow(10, level),
865 modulo = Math.round((step % m) * Math.pow(10, 2 - level)),
866 interval = [[0, 15], [20, 4], [30, 2], [40, 4], [50, 9], [60, 4], [70, 2], [80, 4], [100, 15]],
873 ln = interval.length;
874 cur = from = Math.floor(from / m) * m;
875 for (i = 0; i < ln; i++) {
876 value = interval[i][0];
877 weight = (value - modulo) < 0 ? 1e6 : (value - modulo) / interval[i][1];
878 if (weight < topWeight) {
883 step = Math.floor(step * Math.pow(10, -level)) * Math.pow(10, level) + topValue * Math.pow(10, level - 2);
888 to = +cur.toFixed(10);
898 sorter: function (a, b) {
899 return a.offset - b.offset;
902 rad: function(degrees) {
903 return degrees % 360 * Math.PI / 180;
906 degrees: function(radian) {
907 return radian * 180 / Math.PI % 360;
910 withinBox: function(x, y, bbox) {
912 return (x >= bbox.x && x <= (bbox.x + bbox.width) && y >= bbox.y && y <= (bbox.y + bbox.height));
915 parseGradient: function(gradient) {
917 type = gradient.type || 'linear',
918 angle = gradient.angle || 0,
920 stops = gradient.stops,
927 if (type == 'linear') {
928 vector = [0, 0, Math.cos(angle * radian), Math.sin(angle * radian)];
929 max = 1 / (Math.max(Math.abs(vector[2]), Math.abs(vector[3])) || 1);
933 vector[0] = -vector[2];
937 vector[1] = -vector[3];
942 for (stop in stops) {
943 if (stops.hasOwnProperty(stop) && me.stopsRE.test(stop)) {
945 offset: parseInt(stop, 10),
946 color: Ext.draw.Color.toHex(stops[stop].color) || '#ffffff',
947 opacity: stops[stop].opacity || 1
949 stopsArr.push(stopObj);
952 // Sort by pct property
953 Ext.Array.sort(stopsArr, me.sorter);
954 if (type == 'linear') {
966 centerX: gradient.centerX,
967 centerY: gradient.centerY,
968 focalX: gradient.focalX,
969 focalY: gradient.focalY,
970 radius: gradient.radius,