+ parsePathString: function (pathString) {
+ if (!pathString) {
+ return null;
+ }
+ var paramCounts = {a: 7, c: 6, h: 1, l: 2, m: 2, q: 4, s: 4, t: 2, v: 1, z: 0},
+ data = [],
+ me = this;
+ if (me.is(pathString, "array") && me.is(pathString[0], "array")) { // rough assumption
+ data = me.pathClone(pathString);
+ }
+ if (!data.length) {
+ String(pathString).replace(me.pathCommandRE, function (a, b, c) {
+ var params = [],
+ name = b.toLowerCase();
+ c.replace(me.pathValuesRE, function (a, b) {
+ b && params.push(+b);
+ });
+ if (name == "m" && params.length > 2) {
+ data.push([b].concat(Ext.Array.splice(params, 0, 2)));
+ name = "l";
+ b = (b == "m") ? "l" : "L";
+ }
+ while (params.length >= paramCounts[name]) {
+ data.push([b].concat(Ext.Array.splice(params, 0, paramCounts[name])));
+ if (!paramCounts[name]) {
+ break;
+ }
+ }
+ });
+ }
+ data.toString = me.path2string;
+ return data;
+ },
+
+ mapPath: function (path, matrix) {
+ if (!matrix) {
+ return path;
+ }
+ var x, y, i, ii, j, jj, pathi;
+ path = this.path2curve(path);
+ for (i = 0, ii = path.length; i < ii; i++) {
+ pathi = path[i];
+ for (j = 1, jj = pathi.length; j < jj-1; j += 2) {
+ x = matrix.x(pathi[j], pathi[j + 1]);
+ y = matrix.y(pathi[j], pathi[j + 1]);
+ pathi[j] = x;
+ pathi[j + 1] = y;
+ }
+ }
+ return path;
+ },
+
+ pathClone: function(pathArray) {
+ var res = [],
+ j, jj, i, ii;
+ if (!this.is(pathArray, "array") || !this.is(pathArray && pathArray[0], "array")) { // rough assumption
+ pathArray = this.parsePathString(pathArray);
+ }
+ for (i = 0, ii = pathArray.length; i < ii; i++) {
+ res[i] = [];
+ for (j = 0, jj = pathArray[i].length; j < jj; j++) {
+ res[i][j] = pathArray[i][j];
+ }
+ }
+ res.toString = this.path2string;
+ return res;
+ },
+
+ pathToAbsolute: function (pathArray) {
+ if (!this.is(pathArray, "array") || !this.is(pathArray && pathArray[0], "array")) { // rough assumption
+ pathArray = this.parsePathString(pathArray);
+ }
+ var res = [],
+ x = 0,
+ y = 0,
+ mx = 0,
+ my = 0,
+ i = 0,
+ ln = pathArray.length,
+ r, pathSegment, j, ln2;
+ // MoveTo initial x/y position
+ if (ln && pathArray[0][0] == "M") {
+ x = +pathArray[0][1];
+ y = +pathArray[0][2];
+ mx = x;
+ my = y;
+ i++;
+ res[0] = ["M", x, y];
+ }
+ for (; i < ln; i++) {
+ r = res[i] = [];
+ pathSegment = pathArray[i];
+ if (pathSegment[0] != pathSegment[0].toUpperCase()) {
+ r[0] = pathSegment[0].toUpperCase();
+ switch (r[0]) {
+ // Elliptical Arc
+ case "A":
+ r[1] = pathSegment[1];
+ r[2] = pathSegment[2];
+ r[3] = pathSegment[3];
+ r[4] = pathSegment[4];
+ r[5] = pathSegment[5];
+ r[6] = +(pathSegment[6] + x);
+ r[7] = +(pathSegment[7] + y);
+ break;
+ // Vertical LineTo
+ case "V":
+ r[1] = +pathSegment[1] + y;
+ break;
+ // Horizontal LineTo
+ case "H":
+ r[1] = +pathSegment[1] + x;
+ break;
+ case "M":
+ // MoveTo
+ mx = +pathSegment[1] + x;
+ my = +pathSegment[2] + y;
+ default:
+ j = 1;
+ ln2 = pathSegment.length;
+ for (; j < ln2; j++) {
+ r[j] = +pathSegment[j] + ((j % 2) ? x : y);
+ }
+ }
+ }
+ else {
+ j = 0;
+ ln2 = pathSegment.length;
+ for (; j < ln2; j++) {
+ res[i][j] = pathSegment[j];
+ }
+ }
+ switch (r[0]) {
+ // ClosePath
+ case "Z":
+ x = mx;
+ y = my;
+ break;
+ // Horizontal LineTo
+ case "H":
+ x = r[1];
+ break;
+ // Vertical LineTo
+ case "V":
+ y = r[1];
+ break;
+ // MoveTo
+ case "M":
+ pathSegment = res[i];
+ ln2 = pathSegment.length;
+ mx = pathSegment[ln2 - 2];
+ my = pathSegment[ln2 - 1];
+ default:
+ pathSegment = res[i];
+ ln2 = pathSegment.length;
+ x = pathSegment[ln2 - 2];
+ y = pathSegment[ln2 - 1];
+ }
+ }
+ res.toString = this.path2string;
+ return res;
+ },
+
+ // TO BE DEPRECATED
+ pathToRelative: function (pathArray) {
+ if (!this.is(pathArray, "array") || !this.is(pathArray && pathArray[0], "array")) {
+ pathArray = this.parsePathString(pathArray);
+ }
+ var res = [],
+ x = 0,
+ y = 0,
+ mx = 0,
+ my = 0,
+ start = 0;
+ if (pathArray[0][0] == "M") {
+ x = pathArray[0][1];
+ y = pathArray[0][2];
+ mx = x;
+ my = y;
+ start++;
+ res.push(["M", x, y]);
+ }
+ for (var i = start, ii = pathArray.length; i < ii; i++) {
+ var r = res[i] = [],
+ pa = pathArray[i];
+ if (pa[0] != pa[0].toLowerCase()) {
+ r[0] = pa[0].toLowerCase();
+ switch (r[0]) {
+ case "a":
+ r[1] = pa[1];
+ r[2] = pa[2];
+ r[3] = pa[3];
+ r[4] = pa[4];
+ r[5] = pa[5];
+ r[6] = +(pa[6] - x).toFixed(3);
+ r[7] = +(pa[7] - y).toFixed(3);
+ break;
+ case "v":
+ r[1] = +(pa[1] - y).toFixed(3);
+ break;
+ case "m":
+ mx = pa[1];
+ my = pa[2];
+ default:
+ for (var j = 1, jj = pa.length; j < jj; j++) {
+ r[j] = +(pa[j] - ((j % 2) ? x : y)).toFixed(3);
+ }
+ }
+ } else {
+ r = res[i] = [];
+ if (pa[0] == "m") {
+ mx = pa[1] + x;
+ my = pa[2] + y;
+ }
+ for (var k = 0, kk = pa.length; k < kk; k++) {
+ res[i][k] = pa[k];
+ }
+ }
+ var len = res[i].length;
+ switch (res[i][0]) {
+ case "z":
+ x = mx;
+ y = my;
+ break;
+ case "h":
+ x += +res[i][len - 1];
+ break;
+ case "v":
+ y += +res[i][len - 1];
+ break;
+ default:
+ x += +res[i][len - 2];
+ y += +res[i][len - 1];
+ }
+ }
+ res.toString = this.path2string;
+ return res;
+ },
+
+ // Returns a path converted to a set of curveto commands
+ path2curve: function (path) {
+ var me = this,
+ points = me.pathToAbsolute(path),
+ ln = points.length,
+ attrs = {x: 0, y: 0, bx: 0, by: 0, X: 0, Y: 0, qx: null, qy: null},
+ i, seg, segLn, point;
+
+ for (i = 0; i < ln; i++) {
+ points[i] = me.command2curve(points[i], attrs);
+ if (points[i].length > 7) {
+ points[i].shift();
+ point = points[i];
+ while (point.length) {
+ Ext.Array.splice(points, i++, 0, ["C"].concat(Ext.Array.splice(point, 0, 6)));
+ }
+ Ext.Array.erase(points, i, 1);
+ ln = points.length;
+ }
+ seg = points[i];
+ segLn = seg.length;
+ attrs.x = seg[segLn - 2];
+ attrs.y = seg[segLn - 1];
+ attrs.bx = parseFloat(seg[segLn - 4]) || attrs.x;
+ attrs.by = parseFloat(seg[segLn - 3]) || attrs.y;
+ }
+ return points;
+ },
+
+ interpolatePaths: function (path, path2) {
+ var me = this,
+ p = me.pathToAbsolute(path),
+ p2 = me.pathToAbsolute(path2),
+ attrs = {x: 0, y: 0, bx: 0, by: 0, X: 0, Y: 0, qx: null, qy: null},
+ attrs2 = {x: 0, y: 0, bx: 0, by: 0, X: 0, Y: 0, qx: null, qy: null},
+ fixArc = function (pp, i) {
+ if (pp[i].length > 7) {
+ pp[i].shift();
+ var pi = pp[i];
+ while (pi.length) {
+ Ext.Array.splice(pp, i++, 0, ["C"].concat(Ext.Array.splice(pi, 0, 6)));
+ }
+ Ext.Array.erase(pp, i, 1);
+ ii = Math.max(p.length, p2.length || 0);
+ }
+ },
+ fixM = function (path1, path2, a1, a2, i) {
+ if (path1 && path2 && path1[i][0] == "M" && path2[i][0] != "M") {
+ Ext.Array.splice(path2, i, 0, ["M", a2.x, a2.y]);
+ a1.bx = 0;
+ a1.by = 0;
+ a1.x = path1[i][1];
+ a1.y = path1[i][2];
+ ii = Math.max(p.length, p2.length || 0);
+ }
+ };
+ for (var i = 0, ii = Math.max(p.length, p2.length || 0); i < ii; i++) {
+ p[i] = me.command2curve(p[i], attrs);
+ fixArc(p, i);
+ (p2[i] = me.command2curve(p2[i], attrs2));
+ fixArc(p2, i);
+ fixM(p, p2, attrs, attrs2, i);
+ fixM(p2, p, attrs2, attrs, i);
+ var seg = p[i],
+ seg2 = p2[i],
+ seglen = seg.length,
+ seg2len = seg2.length;
+ attrs.x = seg[seglen - 2];
+ attrs.y = seg[seglen - 1];
+ attrs.bx = parseFloat(seg[seglen - 4]) || attrs.x;
+ attrs.by = parseFloat(seg[seglen - 3]) || attrs.y;
+ attrs2.bx = (parseFloat(seg2[seg2len - 4]) || attrs2.x);
+ attrs2.by = (parseFloat(seg2[seg2len - 3]) || attrs2.y);
+ attrs2.x = seg2[seg2len - 2];
+ attrs2.y = seg2[seg2len - 1];
+ }
+ return [p, p2];
+ },
+
+ //Returns any path command as a curveto command based on the attrs passed
+ command2curve: function (pathCommand, d) {
+ var me = this;
+ if (!pathCommand) {
+ return ["C", d.x, d.y, d.x, d.y, d.x, d.y];
+ }
+ if (pathCommand[0] != "T" && pathCommand[0] != "Q") {
+ d.qx = d.qy = null;
+ }
+ switch (pathCommand[0]) {
+ case "M":
+ d.X = pathCommand[1];
+ d.Y = pathCommand[2];
+ break;
+ case "A":
+ pathCommand = ["C"].concat(me.arc2curve.apply(me, [d.x, d.y].concat(pathCommand.slice(1))));
+ break;
+ case "S":
+ pathCommand = ["C", d.x + (d.x - (d.bx || d.x)), d.y + (d.y - (d.by || d.y))].concat(pathCommand.slice(1));
+ break;
+ case "T":
+ d.qx = d.x + (d.x - (d.qx || d.x));
+ d.qy = d.y + (d.y - (d.qy || d.y));
+ pathCommand = ["C"].concat(me.quadratic2curve(d.x, d.y, d.qx, d.qy, pathCommand[1], pathCommand[2]));
+ break;
+ case "Q":
+ d.qx = pathCommand[1];
+ d.qy = pathCommand[2];
+ pathCommand = ["C"].concat(me.quadratic2curve(d.x, d.y, pathCommand[1], pathCommand[2], pathCommand[3], pathCommand[4]));
+ break;
+ case "L":
+ pathCommand = ["C"].concat(d.x, d.y, pathCommand[1], pathCommand[2], pathCommand[1], pathCommand[2]);
+ break;
+ case "H":
+ pathCommand = ["C"].concat(d.x, d.y, pathCommand[1], d.y, pathCommand[1], d.y);
+ break;
+ case "V":
+ pathCommand = ["C"].concat(d.x, d.y, d.x, pathCommand[1], d.x, pathCommand[1]);
+ break;
+ case "Z":
+ pathCommand = ["C"].concat(d.x, d.y, d.X, d.Y, d.X, d.Y);
+ break;
+ }
+ return pathCommand;
+ },
+
+ quadratic2curve: function (x1, y1, ax, ay, x2, y2) {
+ var _13 = 1 / 3,
+ _23 = 2 / 3;
+ return [
+ _13 * x1 + _23 * ax,
+ _13 * y1 + _23 * ay,
+ _13 * x2 + _23 * ax,
+ _13 * y2 + _23 * ay,
+ x2,
+ y2
+ ];
+ },
+
+ rotate: function (x, y, rad) {
+ var cos = Math.cos(rad),
+ sin = Math.sin(rad),
+ X = x * cos - y * sin,
+ Y = x * sin + y * cos;
+ return {x: X, y: Y};
+ },
+
+ arc2curve: function (x1, y1, rx, ry, angle, large_arc_flag, sweep_flag, x2, y2, recursive) {
+ // for more information of where this Math came from visit:
+ // http://www.w3.org/TR/SVG11/implnote.html#ArcImplementationNotes
+ var me = this,
+ PI = Math.PI,
+ radian = me.radian,
+ _120 = PI * 120 / 180,
+ rad = radian * (+angle || 0),
+ res = [],
+ math = Math,
+ mcos = math.cos,
+ msin = math.sin,
+ msqrt = math.sqrt,
+ mabs = math.abs,
+ masin = math.asin,
+ xy, cos, sin, x, y, h, rx2, ry2, k, cx, cy, f1, f2, df, c1, s1, c2, s2,
+ t, hx, hy, m1, m2, m3, m4, newres, i, ln, f2old, x2old, y2old;
+ if (!recursive) {
+ xy = me.rotate(x1, y1, -rad);
+ x1 = xy.x;
+ y1 = xy.y;
+ xy = me.rotate(x2, y2, -rad);
+ x2 = xy.x;
+ y2 = xy.y;
+ cos = mcos(radian * angle);
+ sin = msin(radian * angle);
+ x = (x1 - x2) / 2;
+ y = (y1 - y2) / 2;
+ h = (x * x) / (rx * rx) + (y * y) / (ry * ry);
+ if (h > 1) {
+ h = msqrt(h);
+ rx = h * rx;
+ ry = h * ry;
+ }
+ rx2 = rx * rx;
+ ry2 = ry * ry;
+ k = (large_arc_flag == sweep_flag ? -1 : 1) *
+ msqrt(mabs((rx2 * ry2 - rx2 * y * y - ry2 * x * x) / (rx2 * y * y + ry2 * x * x)));
+ cx = k * rx * y / ry + (x1 + x2) / 2;
+ cy = k * -ry * x / rx + (y1 + y2) / 2;
+ f1 = masin(((y1 - cy) / ry).toFixed(7));
+ f2 = masin(((y2 - cy) / ry).toFixed(7));
+
+ f1 = x1 < cx ? PI - f1 : f1;
+ f2 = x2 < cx ? PI - f2 : f2;
+ if (f1 < 0) {
+ f1 = PI * 2 + f1;
+ }
+ if (f2 < 0) {
+ f2 = PI * 2 + f2;
+ }
+ if (sweep_flag && f1 > f2) {
+ f1 = f1 - PI * 2;
+ }
+ if (!sweep_flag && f2 > f1) {
+ f2 = f2 - PI * 2;
+ }
+ }
+ else {
+ f1 = recursive[0];
+ f2 = recursive[1];
+ cx = recursive[2];
+ cy = recursive[3];
+ }
+ df = f2 - f1;
+ if (mabs(df) > _120) {
+ f2old = f2;
+ x2old = x2;
+ y2old = y2;
+ f2 = f1 + _120 * (sweep_flag && f2 > f1 ? 1 : -1);
+ x2 = cx + rx * mcos(f2);
+ y2 = cy + ry * msin(f2);
+ res = me.arc2curve(x2, y2, rx, ry, angle, 0, sweep_flag, x2old, y2old, [f2, f2old, cx, cy]);
+ }
+ df = f2 - f1;
+ c1 = mcos(f1);
+ s1 = msin(f1);
+ c2 = mcos(f2);
+ s2 = msin(f2);
+ t = math.tan(df / 4);
+ hx = 4 / 3 * rx * t;
+ hy = 4 / 3 * ry * t;
+ m1 = [x1, y1];
+ m2 = [x1 + hx * s1, y1 - hy * c1];
+ m3 = [x2 + hx * s2, y2 - hy * c2];
+ m4 = [x2, y2];
+ m2[0] = 2 * m1[0] - m2[0];
+ m2[1] = 2 * m1[1] - m2[1];
+ if (recursive) {
+ return [m2, m3, m4].concat(res);
+ }
+ else {
+ res = [m2, m3, m4].concat(res).join().split(",");
+ newres = [];
+ ln = res.length;
+ for (i = 0; i < ln; i++) {
+ newres[i] = i % 2 ? me.rotate(res[i - 1], res[i], rad).y : me.rotate(res[i], res[i + 1], rad).x;
+ }
+ return newres;
+ }
+ },
+
+ // TO BE DEPRECATED
+ rotateAndTranslatePath: function (sprite) {
+ var alpha = sprite.rotation.degrees,
+ cx = sprite.rotation.x,
+ cy = sprite.rotation.y,
+ dx = sprite.translation.x,
+ dy = sprite.translation.y,
+ path,
+ i,
+ p,
+ xy,
+ j,
+ res = [];
+ if (!alpha && !dx && !dy) {
+ return this.pathToAbsolute(sprite.attr.path);
+ }
+ dx = dx || 0;
+ dy = dy || 0;
+ path = this.pathToAbsolute(sprite.attr.path);
+ for (i = path.length; i--;) {
+ p = res[i] = path[i].slice();
+ if (p[0] == "A") {
+ xy = this.rotatePoint(p[6], p[7], alpha, cx, cy);
+ p[6] = xy.x + dx;
+ p[7] = xy.y + dy;
+ } else {
+ j = 1;
+ while (p[j + 1] != null) {
+ xy = this.rotatePoint(p[j], p[j + 1], alpha, cx, cy);
+ p[j] = xy.x + dx;
+ p[j + 1] = xy.y + dy;
+ j += 2;
+ }
+ }
+ }
+ return res;
+ },
+
+ // TO BE DEPRECATED
+ rotatePoint: function (x, y, alpha, cx, cy) {
+ if (!alpha) {
+ return {
+ x: x,
+ y: y
+ };
+ }
+ cx = cx || 0;
+ cy = cy || 0;
+ x = x - cx;
+ y = y - cy;
+ alpha = alpha * this.radian;
+ var cos = Math.cos(alpha),
+ sin = Math.sin(alpha);
+ return {
+ x: x * cos - y * sin + cx,
+ y: x * sin + y * cos + cy
+ };
+ },
+
+ pathDimensions: function (path) {
+ if (!path || !(path + "")) {
+ return {x: 0, y: 0, width: 0, height: 0};
+ }
+ path = this.path2curve(path);
+ var x = 0,
+ y = 0,
+ X = [],
+ Y = [],
+ i = 0,
+ ln = path.length,
+ p, xmin, ymin, dim;
+ for (; i < ln; i++) {
+ p = path[i];
+ if (p[0] == "M") {
+ x = p[1];
+ y = p[2];
+ X.push(x);
+ Y.push(y);
+ }
+ else {
+ dim = this.curveDim(x, y, p[1], p[2], p[3], p[4], p[5], p[6]);
+ X = X.concat(dim.min.x, dim.max.x);
+ Y = Y.concat(dim.min.y, dim.max.y);
+ x = p[5];
+ y = p[6];
+ }
+ }
+ xmin = Math.min.apply(0, X);
+ ymin = Math.min.apply(0, Y);
+ return {
+ x: xmin,
+ y: ymin,
+ path: path,
+ width: Math.max.apply(0, X) - xmin,
+ height: Math.max.apply(0, Y) - ymin
+ };
+ },
+
+ intersectInside: function(path, cp1, cp2) {
+ return (cp2[0] - cp1[0]) * (path[1] - cp1[1]) > (cp2[1] - cp1[1]) * (path[0] - cp1[0]);
+ },
+
+ intersectIntersection: function(s, e, cp1, cp2) {
+ var p = [],
+ dcx = cp1[0] - cp2[0],
+ dcy = cp1[1] - cp2[1],
+ dpx = s[0] - e[0],
+ dpy = s[1] - e[1],
+ n1 = cp1[0] * cp2[1] - cp1[1] * cp2[0],
+ n2 = s[0] * e[1] - s[1] * e[0],
+ n3 = 1 / (dcx * dpy - dcy * dpx);
+
+ p[0] = (n1 * dpx - n2 * dcx) * n3;
+ p[1] = (n1 * dpy - n2 * dcy) * n3;
+ return p;
+ },
+
+ intersect: function(subjectPolygon, clipPolygon) {
+ var me = this,
+ i = 0,
+ ln = clipPolygon.length,
+ cp1 = clipPolygon[ln - 1],
+ outputList = subjectPolygon,
+ cp2, s, e, point, ln2, inputList, j;
+ for (; i < ln; ++i) {
+ cp2 = clipPolygon[i];
+ inputList = outputList;
+ outputList = [];
+ s = inputList[inputList.length - 1];
+ j = 0;
+ ln2 = inputList.length;
+ for (; j < ln2; j++) {
+ e = inputList[j];
+ if (me.intersectInside(e, cp1, cp2)) {
+ if (!me.intersectInside(s, cp1, cp2)) {
+ outputList.push(me.intersectIntersection(s, e, cp1, cp2));
+ }
+ outputList.push(e);
+ }
+ else if (me.intersectInside(s, cp1, cp2)) {
+ outputList.push(me.intersectIntersection(s, e, cp1, cp2));
+ }
+ s = e;
+ }
+ cp1 = cp2;
+ }
+ return outputList;
+ },
+
+ curveDim: function (p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y) {
+ var a = (c2x - 2 * c1x + p1x) - (p2x - 2 * c2x + c1x),
+ b = 2 * (c1x - p1x) - 2 * (c2x - c1x),
+ c = p1x - c1x,
+ t1 = (-b + Math.sqrt(b * b - 4 * a * c)) / 2 / a,
+ t2 = (-b - Math.sqrt(b * b - 4 * a * c)) / 2 / a,
+ y = [p1y, p2y],
+ x = [p1x, p2x],
+ dot;
+ if (Math.abs(t1) > 1e12) {
+ t1 = 0.5;
+ }
+ if (Math.abs(t2) > 1e12) {
+ t2 = 0.5;
+ }
+ if (t1 > 0 && t1 < 1) {
+ dot = this.findDotAtSegment(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t1);
+ x.push(dot.x);
+ y.push(dot.y);
+ }
+ if (t2 > 0 && t2 < 1) {
+ dot = this.findDotAtSegment(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t2);
+ x.push(dot.x);
+ y.push(dot.y);
+ }
+ a = (c2y - 2 * c1y + p1y) - (p2y - 2 * c2y + c1y);
+ b = 2 * (c1y - p1y) - 2 * (c2y - c1y);
+ c = p1y - c1y;
+ t1 = (-b + Math.sqrt(b * b - 4 * a * c)) / 2 / a;
+ t2 = (-b - Math.sqrt(b * b - 4 * a * c)) / 2 / a;
+ if (Math.abs(t1) > 1e12) {
+ t1 = 0.5;
+ }
+ if (Math.abs(t2) > 1e12) {
+ t2 = 0.5;
+ }
+ if (t1 > 0 && t1 < 1) {
+ dot = this.findDotAtSegment(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t1);
+ x.push(dot.x);
+ y.push(dot.y);
+ }
+ if (t2 > 0 && t2 < 1) {
+ dot = this.findDotAtSegment(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t2);
+ x.push(dot.x);
+ y.push(dot.y);
+ }
+ return {
+ min: {x: Math.min.apply(0, x), y: Math.min.apply(0, y)},
+ max: {x: Math.max.apply(0, x), y: Math.max.apply(0, y)}
+ };
+ },
+
+ /**
+ * @private
+ *
+ * Calculates bezier curve control anchor points for a particular point in a path, with a
+ * smoothing curve applied. The smoothness of the curve is controlled by the 'value' parameter.
+ * Note that this algorithm assumes that the line being smoothed is normalized going from left
+ * to right; it makes special adjustments assuming this orientation.
+ *
+ * @param {Number} prevX X coordinate of the previous point in the path
+ * @param {Number} prevY Y coordinate of the previous point in the path
+ * @param {Number} curX X coordinate of the current point in the path
+ * @param {Number} curY Y coordinate of the current point in the path
+ * @param {Number} nextX X coordinate of the next point in the path
+ * @param {Number} nextY Y coordinate of the next point in the path
+ * @param {Number} value A value to control the smoothness of the curve; this is used to
+ * divide the distance between points, so a value of 2 corresponds to
+ * half the distance between points (a very smooth line) while higher values
+ * result in less smooth curves. Defaults to 4.
+ * @return {Object} Object containing x1, y1, x2, y2 bezier control anchor points; x1 and y1
+ * are the control point for the curve toward the previous path point, and
+ * x2 and y2 are the control point for the curve toward the next path point.
+ */
+ getAnchors: function (prevX, prevY, curX, curY, nextX, nextY, value) {
+ value = value || 4;
+ var M = Math,
+ PI = M.PI,
+ halfPI = PI / 2,
+ abs = M.abs,
+ sin = M.sin,
+ cos = M.cos,
+ atan = M.atan,
+ control1Length, control2Length, control1Angle, control2Angle,
+ control1X, control1Y, control2X, control2Y, alpha;
+
+ // Find the length of each control anchor line, by dividing the horizontal distance
+ // between points by the value parameter.
+ control1Length = (curX - prevX) / value;
+ control2Length = (nextX - curX) / value;
+
+ // Determine the angle of each control anchor line. If the middle point is a vertical
+ // turnaround then we force it to a flat horizontal angle to prevent the curve from
+ // dipping above or below the middle point. Otherwise we use an angle that points
+ // toward the previous/next target point.
+ if ((curY >= prevY && curY >= nextY) || (curY <= prevY && curY <= nextY)) {
+ control1Angle = control2Angle = halfPI;
+ } else {
+ control1Angle = atan((curX - prevX) / abs(curY - prevY));
+ if (prevY < curY) {
+ control1Angle = PI - control1Angle;
+ }
+ control2Angle = atan((nextX - curX) / abs(curY - nextY));
+ if (nextY < curY) {
+ control2Angle = PI - control2Angle;
+ }
+ }
+
+ // Adjust the calculated angles so they point away from each other on the same line
+ alpha = halfPI - ((control1Angle + control2Angle) % (PI * 2)) / 2;
+ if (alpha > halfPI) {
+ alpha -= PI;
+ }
+ control1Angle += alpha;
+ control2Angle += alpha;
+
+ // Find the control anchor points from the angles and length
+ control1X = curX - control1Length * sin(control1Angle);
+ control1Y = curY + control1Length * cos(control1Angle);
+ control2X = curX + control2Length * sin(control2Angle);
+ control2Y = curY + control2Length * cos(control2Angle);
+
+ // One last adjustment, make sure that no control anchor point extends vertically past
+ // its target prev/next point, as that results in curves dipping above or below and
+ // bending back strangely. If we find this happening we keep the control angle but
+ // reduce the length of the control line so it stays within bounds.
+ if ((curY > prevY && control1Y < prevY) || (curY < prevY && control1Y > prevY)) {
+ control1X += abs(prevY - control1Y) * (control1X - curX) / (control1Y - curY);
+ control1Y = prevY;
+ }
+ if ((curY > nextY && control2Y < nextY) || (curY < nextY && control2Y > nextY)) {
+ control2X -= abs(nextY - control2Y) * (control2X - curX) / (control2Y - curY);
+ control2Y = nextY;
+ }
+
+ return {
+ x1: control1X,
+ y1: control1Y,
+ x2: control2X,
+ y2: control2Y
+ };
+ },
+
+ /* Smoothing function for a path. Converts a path into cubic beziers. Value defines the divider of the distance between points.
+ * Defaults to a value of 4.
+ */
+ smooth: function (originalPath, value) {
+ var path = this.path2curve(originalPath),
+ newp = [path[0]],
+ x = path[0][1],
+ y = path[0][2],
+ j,
+ points,
+ i = 1,
+ ii = path.length,
+ beg = 1,
+ mx = x,
+ my = y,
+ cx = 0,
+ cy = 0;
+ for (; i < ii; i++) {
+ var pathi = path[i],
+ pathil = pathi.length,
+ pathim = path[i - 1],
+ pathiml = pathim.length,
+ pathip = path[i + 1],
+ pathipl = pathip && pathip.length;
+ if (pathi[0] == "M") {
+ mx = pathi[1];
+ my = pathi[2];
+ j = i + 1;
+ while (path[j][0] != "C") {
+ j++;
+ }
+ cx = path[j][5];
+ cy = path[j][6];
+ newp.push(["M", mx, my]);
+ beg = newp.length;
+ x = mx;
+ y = my;
+ continue;
+ }
+ if (pathi[pathil - 2] == mx && pathi[pathil - 1] == my && (!pathip || pathip[0] == "M")) {
+ var begl = newp[beg].length;
+ points = this.getAnchors(pathim[pathiml - 2], pathim[pathiml - 1], mx, my, newp[beg][begl - 2], newp[beg][begl - 1], value);
+ newp[beg][1] = points.x2;
+ newp[beg][2] = points.y2;
+ }
+ else if (!pathip || pathip[0] == "M") {
+ points = {
+ x1: pathi[pathil - 2],
+ y1: pathi[pathil - 1]
+ };
+ } else {
+ points = this.getAnchors(pathim[pathiml - 2], pathim[pathiml - 1], pathi[pathil - 2], pathi[pathil - 1], pathip[pathipl - 2], pathip[pathipl - 1], value);
+ }
+ newp.push(["C", x, y, points.x1, points.y1, pathi[pathil - 2], pathi[pathil - 1]]);
+ x = points.x2;
+ y = points.y2;
+ }
+ return newp;
+ },
+
+ findDotAtSegment: function (p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t) {
+ var t1 = 1 - t;
+ return {
+ x: Math.pow(t1, 3) * p1x + Math.pow(t1, 2) * 3 * t * c1x + t1 * 3 * t * t * c2x + Math.pow(t, 3) * p2x,
+ y: Math.pow(t1, 3) * p1y + Math.pow(t1, 2) * 3 * t * c1y + t1 * 3 * t * t * c2y + Math.pow(t, 3) * p2y
+ };
+ },
+
+ /**
+ * A utility method to deduce an appropriate tick configuration for the data set of given
+ * feature.
+ *
+ * @param {Number/Date} from The minimum value in the data
+ * @param {Number/Date} to The maximum value in the data
+ * @param {Number} stepsMax The maximum number of ticks
+ * @return {Object} The calculated step and ends info; When `from` and `to` are Dates, refer to the
+ * return value of {@link #snapEndsByDate}. For numerical `from` and `to` the return value contains:
+ * @return {Number} return.from The result start value, which may be lower than the original start value
+ * @return {Number} return.to The result end value, which may be higher than the original end value
+ * @return {Number} return.power The calculate power.
+ * @return {Number} return.step The value size of each step
+ * @return {Number} return.steps The number of steps.
+ */
+ snapEnds: function (from, to, stepsMax) {
+ if (Ext.isDate(from)) {
+ return this.snapEndsByDate(from, to, stepsMax);
+ }
+ var step = (to - from) / stepsMax,
+ level = Math.floor(Math.log(step) / Math.LN10) + 1,
+ m = Math.pow(10, level),
+ cur,
+ modulo = Math.round((step % m) * Math.pow(10, 2 - level)),
+ interval = [[0, 15], [20, 4], [30, 2], [40, 4], [50, 9], [60, 4], [70, 2], [80, 4], [100, 15]],
+ stepCount = 0,
+ value,
+ weight,
+ i,
+ topValue,
+ topWeight = 1e9,
+ ln = interval.length;
+ cur = from = Math.floor(from / m) * m;
+ for (i = 0; i < ln; i++) {
+ value = interval[i][0];
+ weight = (value - modulo) < 0 ? 1e6 : (value - modulo) / interval[i][1];
+ if (weight < topWeight) {
+ topValue = value;
+ topWeight = weight;
+ }
+ }
+ step = Math.floor(step * Math.pow(10, -level)) * Math.pow(10, level) + topValue * Math.pow(10, level - 2);
+ while (cur < to) {
+ cur += step;
+ stepCount++;
+ }
+ to = +cur.toFixed(10);
+ return {
+ from: from,
+ to: to,
+ power: level,
+ step: step,
+ steps: stepCount
+ };
+ },
+
+ /**
+ * A utility method to deduce an appropriate tick configuration for the data set of given
+ * feature when data is Dates. Refer to {@link #snapEnds} for numeric data.
+ *
+ * @param {Date} from The minimum value in the data
+ * @param {Date} to The maximum value in the data
+ * @param {Number} stepsMax The maximum number of ticks
+ * @param {Boolean} lockEnds If true, the 'from' and 'to' parameters will be used as fixed end values
+ * and will not be adjusted
+ * @return {Object} The calculated step and ends info; properties are:
+ * @return {Date} return.from The result start value, which may be lower than the original start value
+ * @return {Date} return.to The result end value, which may be higher than the original end value
+ * @return {Number} return.step The value size of each step
+ * @return {Number} return.steps The number of steps.
+ * NOTE: the steps may not divide the from/to range perfectly evenly;
+ * there may be a smaller distance between the last step and the end value than between prior
+ * steps, particularly when the `endsLocked` param is true. Therefore it is best to not use
+ * the `steps` result when finding the axis tick points, instead use the `step`, `to`, and
+ * `from` to find the correct point for each tick.
+ */
+ snapEndsByDate: function (from, to, stepsMax, lockEnds) {
+ var selectedStep = false, scales = [
+ [Ext.Date.MILLI, [1, 2, 3, 5, 10, 20, 30, 50, 100, 200, 300, 500]],
+ [Ext.Date.SECOND, [1, 2, 3, 5, 10, 15, 30]],
+ [Ext.Date.MINUTE, [1, 2, 3, 5, 10, 20, 30]],
+ [Ext.Date.HOUR, [1, 2, 3, 4, 6, 12]],
+ [Ext.Date.DAY, [1, 2, 3, 7, 14]],
+ [Ext.Date.MONTH, [1, 2, 3, 4, 6]]
+ ], j, yearDiff;
+
+ // Find the most desirable scale
+ Ext.each(scales, function(scale, i) {
+ for (j = 0; j < scale[1].length; j++) {
+ if (to < Ext.Date.add(from, scale[0], scale[1][j] * stepsMax)) {
+ selectedStep = [scale[0], scale[1][j]];
+ return false;
+ }
+ }
+ });
+ if (!selectedStep) {
+ yearDiff = this.snapEnds(from.getFullYear(), to.getFullYear() + 1, stepsMax, lockEnds);
+ selectedStep = [Date.YEAR, Math.round(yearDiff.step)];
+ }
+ return this.snapEndsByDateAndStep(from, to, selectedStep, lockEnds);
+ },
+
+
+ /**
+ * A utility method to deduce an appropriate tick configuration for the data set of given
+ * feature and specific step size.
+ * @param {Date} from The minimum value in the data
+ * @param {Date} to The maximum value in the data
+ * @param {Array} step An array with two components: The first is the unit of the step (day, month, year, etc).
+ * The second one is the number of units for the step (1, 2, etc.).
+ * @param {Boolean} lockEnds If true, the 'from' and 'to' parameters will be used as fixed end values
+ * and will not be adjusted
+ * @return {Object} See the return value of {@link #snapEndsByDate}.
+ */
+ snapEndsByDateAndStep: function(from, to, step, lockEnds) {
+ var fromStat = [from.getFullYear(), from.getMonth(), from.getDate(),
+ from.getHours(), from.getMinutes(), from.getSeconds(), from.getMilliseconds()],
+ steps = 0, testFrom, testTo;
+ if (lockEnds) {
+ testFrom = from;
+ } else {
+ switch (step[0]) {
+ case Ext.Date.MILLI:
+ testFrom = new Date(fromStat[0], fromStat[1], fromStat[2], fromStat[3],
+ fromStat[4], fromStat[5], Math.floor(fromStat[6] / step[1]) * step[1]);
+ break;
+ case Ext.Date.SECOND:
+ testFrom = new Date(fromStat[0], fromStat[1], fromStat[2], fromStat[3],
+ fromStat[4], Math.floor(fromStat[5] / step[1]) * step[1], 0);
+ break;
+ case Ext.Date.MINUTE:
+ testFrom = new Date(fromStat[0], fromStat[1], fromStat[2], fromStat[3],
+ Math.floor(fromStat[4] / step[1]) * step[1], 0, 0);
+ break;
+ case Ext.Date.HOUR:
+ testFrom = new Date(fromStat[0], fromStat[1], fromStat[2],
+ Math.floor(fromStat[3] / step[1]) * step[1], 0, 0, 0);
+ break;
+ case Ext.Date.DAY:
+ testFrom = new Date(fromStat[0], fromStat[1],
+ Math.floor(fromStat[2] - 1 / step[1]) * step[1] + 1, 0, 0, 0, 0);
+ break;
+ case Ext.Date.MONTH:
+ testFrom = new Date(fromStat[0], Math.floor(fromStat[1] / step[1]) * step[1], 1, 0, 0, 0, 0);
+ break;
+ default: // Ext.Date.YEAR
+ testFrom = new Date(Math.floor(fromStat[0] / step[1]) * step[1], 0, 1, 0, 0, 0, 0);
+ break;
+ }
+ }
+
+ testTo = testFrom;
+ // TODO(zhangbei) : We can do it better somehow...
+ while (testTo < to) {
+ testTo = Ext.Date.add(testTo, step[0], step[1]);
+ steps++;
+ }
+
+ if (lockEnds) {
+ testTo = to;
+ }
+ return {
+ from : +testFrom,
+ to : +testTo,
+ step : (testTo - testFrom) / steps,
+ steps : steps
+ };
+ },
+
+ sorter: function (a, b) {
+ return a.offset - b.offset;
+ },
+
+ rad: function(degrees) {
+ return degrees % 360 * Math.PI / 180;
+ },
+
+ degrees: function(radian) {
+ return radian * 180 / Math.PI % 360;
+ },
+
+ withinBox: function(x, y, bbox) {
+ bbox = bbox || {};
+ return (x >= bbox.x && x <= (bbox.x + bbox.width) && y >= bbox.y && y <= (bbox.y + bbox.height));
+ },
+
+ parseGradient: function(gradient) {
+ var me = this,
+ type = gradient.type || 'linear',
+ angle = gradient.angle || 0,
+ radian = me.radian,
+ stops = gradient.stops,
+ stopsArr = [],
+ stop,
+ vector,
+ max,
+ stopObj;
+
+ if (type == 'linear') {
+ vector = [0, 0, Math.cos(angle * radian), Math.sin(angle * radian)];
+ max = 1 / (Math.max(Math.abs(vector[2]), Math.abs(vector[3])) || 1);
+ vector[2] *= max;
+ vector[3] *= max;
+ if (vector[2] < 0) {
+ vector[0] = -vector[2];
+ vector[2] = 0;
+ }
+ if (vector[3] < 0) {
+ vector[1] = -vector[3];
+ vector[3] = 0;
+ }
+ }
+
+ for (stop in stops) {
+ if (stops.hasOwnProperty(stop) && me.stopsRE.test(stop)) {
+ stopObj = {
+ offset: parseInt(stop, 10),
+ color: Ext.draw.Color.toHex(stops[stop].color) || '#ffffff',
+ opacity: stops[stop].opacity || 1
+ };
+ stopsArr.push(stopObj);
+ }
+ }
+ // Sort by pct property
+ Ext.Array.sort(stopsArr, me.sorter);
+ if (type == 'linear') {
+ return {
+ id: gradient.id,
+ type: type,
+ vector: vector,
+ stops: stopsArr
+ };
+ }
+ else {
+ return {
+ id: gradient.id,
+ type: type,
+ centerX: gradient.centerX,
+ centerY: gradient.centerY,
+ focalX: gradient.focalX,
+ focalY: gradient.focalY,
+ radius: gradient.radius,
+ vector: vector,
+ stops: stopsArr
+ };
+ }
+ }
+});
+
+
+/**
+ * @class Ext.fx.PropertyHandler
+ * @ignore
+ */
+Ext.define('Ext.fx.PropertyHandler', {
+
+ /* Begin Definitions */
+
+ requires: ['Ext.draw.Draw'],
+
+ statics: {
+ defaultHandler: {
+ pixelDefaultsRE: /width|height|top$|bottom$|left$|right$/i,
+ unitRE: /^(-?\d*\.?\d*){1}(em|ex|px|in|cm|mm|pt|pc|%)*$/,
+ scrollRE: /^scroll/i,
+
+ computeDelta: function(from, end, damper, initial, attr) {
+ damper = (typeof damper == 'number') ? damper : 1;
+ var unitRE = this.unitRE,
+ match = unitRE.exec(from),
+ start, units;
+ if (match) {
+ from = match[1];
+ units = match[2];
+ if (!this.scrollRE.test(attr) && !units && this.pixelDefaultsRE.test(attr)) {
+ units = 'px';
+ }
+ }
+ from = +from || 0;
+
+ match = unitRE.exec(end);
+ if (match) {
+ end = match[1];
+ units = match[2] || units;
+ }
+ end = +end || 0;
+ start = (initial != null) ? initial : from;
+ return {
+ from: from,
+ delta: (end - start) * damper,
+ units: units
+ };
+ },
+
+ get: function(from, end, damper, initialFrom, attr) {
+ var ln = from.length,
+ out = [],
+ i, initial, res, j, len;
+ for (i = 0; i < ln; i++) {
+ if (initialFrom) {
+ initial = initialFrom[i][1].from;
+ }
+ if (Ext.isArray(from[i][1]) && Ext.isArray(end)) {
+ res = [];
+ j = 0;
+ len = from[i][1].length;
+ for (; j < len; j++) {
+ res.push(this.computeDelta(from[i][1][j], end[j], damper, initial, attr));
+ }
+ out.push([from[i][0], res]);
+ }
+ else {
+ out.push([from[i][0], this.computeDelta(from[i][1], end, damper, initial, attr)]);
+ }
+ }
+ return out;
+ },
+
+ set: function(values, easing) {
+ var ln = values.length,
+ out = [],
+ i, val, res, len, j;
+ for (i = 0; i < ln; i++) {
+ val = values[i][1];
+ if (Ext.isArray(val)) {
+ res = [];
+ j = 0;
+ len = val.length;
+ for (; j < len; j++) {
+ res.push(val[j].from + (val[j].delta * easing) + (val[j].units || 0));
+ }
+ out.push([values[i][0], res]);
+ } else {
+ out.push([values[i][0], val.from + (val.delta * easing) + (val.units || 0)]);
+ }
+ }
+ return out;
+ }
+ },
+ color: {
+ rgbRE: /^rgb\(([0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-9]+)\)$/i,
+ hexRE: /^#?([0-9A-F]{2})([0-9A-F]{2})([0-9A-F]{2})$/i,
+ hex3RE: /^#?([0-9A-F]{1})([0-9A-F]{1})([0-9A-F]{1})$/i,
+
+ parseColor : function(color, damper) {
+ damper = (typeof damper == 'number') ? damper : 1;
+ var base,
+ out = false,
+ match;
+
+ Ext.each([this.hexRE, this.rgbRE, this.hex3RE], function(re, idx) {
+ base = (idx % 2 == 0) ? 16 : 10;
+ match = re.exec(color);
+ if (match && match.length == 4) {
+ if (idx == 2) {
+ match[1] += match[1];
+ match[2] += match[2];
+ match[3] += match[3];
+ }
+ out = {
+ red: parseInt(match[1], base),
+ green: parseInt(match[2], base),
+ blue: parseInt(match[3], base)
+ };
+ return false;
+ }
+ });
+ return out || color;
+ },
+
+ computeDelta: function(from, end, damper, initial) {
+ from = this.parseColor(from);
+ end = this.parseColor(end, damper);
+ var start = initial ? initial : from,
+ tfrom = typeof start,
+ tend = typeof end;
+ //Extra check for when the color string is not recognized.
+ if (tfrom == 'string' || tfrom == 'undefined'
+ || tend == 'string' || tend == 'undefined') {
+ return end || start;
+ }
+ return {
+ from: from,
+ delta: {
+ red: Math.round((end.red - start.red) * damper),
+ green: Math.round((end.green - start.green) * damper),
+ blue: Math.round((end.blue - start.blue) * damper)
+ }
+ };
+ },
+
+ get: function(start, end, damper, initialFrom) {
+ var ln = start.length,
+ out = [],
+ i, initial;
+ for (i = 0; i < ln; i++) {
+ if (initialFrom) {
+ initial = initialFrom[i][1].from;
+ }
+ out.push([start[i][0], this.computeDelta(start[i][1], end, damper, initial)]);
+ }
+ return out;
+ },
+
+ set: function(values, easing) {
+ var ln = values.length,
+ out = [],
+ i, val, parsedString, from, delta;
+ for (i = 0; i < ln; i++) {
+ val = values[i][1];
+ if (val) {
+ from = val.from;
+ delta = val.delta;
+ //multiple checks to reformat the color if it can't recognized by computeDelta.
+ val = (typeof val == 'object' && 'red' in val)?
+ 'rgb(' + val.red + ', ' + val.green + ', ' + val.blue + ')' : val;
+ val = (typeof val == 'object' && val.length)? val[0] : val;
+ if (typeof val == 'undefined') {
+ return [];
+ }
+ parsedString = typeof val == 'string'? val :
+ 'rgb(' + [
+ (from.red + Math.round(delta.red * easing)) % 256,
+ (from.green + Math.round(delta.green * easing)) % 256,
+ (from.blue + Math.round(delta.blue * easing)) % 256
+ ].join(',') + ')';
+ out.push([
+ values[i][0],
+ parsedString
+ ]);
+ }
+ }
+ return out;
+ }
+ },
+ object: {
+ interpolate: function(prop, damper) {
+ damper = (typeof damper == 'number') ? damper : 1;
+ var out = {},
+ p;
+ for(p in prop) {
+ out[p] = parseInt(prop[p], 10) * damper;
+ }
+ return out;
+ },
+
+ computeDelta: function(from, end, damper, initial) {
+ from = this.interpolate(from);
+ end = this.interpolate(end, damper);
+ var start = initial ? initial : from,
+ delta = {},
+ p;
+
+ for(p in end) {
+ delta[p] = end[p] - start[p];
+ }
+ return {
+ from: from,
+ delta: delta
+ };
+ },
+
+ get: function(start, end, damper, initialFrom) {
+ var ln = start.length,
+ out = [],
+ i, initial;
+ for (i = 0; i < ln; i++) {
+ if (initialFrom) {
+ initial = initialFrom[i][1].from;
+ }
+ out.push([start[i][0], this.computeDelta(start[i][1], end, damper, initial)]);
+ }
+ return out;
+ },
+
+ set: function(values, easing) {
+ var ln = values.length,
+ out = [],
+ outObject = {},
+ i, from, delta, val, p;
+ for (i = 0; i < ln; i++) {
+ val = values[i][1];
+ from = val.from;
+ delta = val.delta;
+ for (p in from) {
+ outObject[p] = Math.round(from[p] + delta[p] * easing);
+ }
+ out.push([
+ values[i][0],
+ outObject
+ ]);
+ }
+ return out;
+ }
+ },
+
+ path: {
+ computeDelta: function(from, end, damper, initial) {
+ damper = (typeof damper == 'number') ? damper : 1;
+ var start;
+ from = +from || 0;
+ end = +end || 0;
+ start = (initial != null) ? initial : from;
+ return {
+ from: from,
+ delta: (end - start) * damper
+ };
+ },
+
+ forcePath: function(path) {
+ if (!Ext.isArray(path) && !Ext.isArray(path[0])) {
+ path = Ext.draw.Draw.parsePathString(path);
+ }
+ return path;
+ },
+
+ get: function(start, end, damper, initialFrom) {
+ var endPath = this.forcePath(end),
+ out = [],
+ startLn = start.length,
+ startPathLn, pointsLn, i, deltaPath, initial, j, k, path, startPath;
+ for (i = 0; i < startLn; i++) {
+ startPath = this.forcePath(start[i][1]);
+
+ deltaPath = Ext.draw.Draw.interpolatePaths(startPath, endPath);
+ startPath = deltaPath[0];
+ endPath = deltaPath[1];
+
+ startPathLn = startPath.length;
+ path = [];
+ for (j = 0; j < startPathLn; j++) {
+ deltaPath = [startPath[j][0]];
+ pointsLn = startPath[j].length;
+ for (k = 1; k < pointsLn; k++) {
+ initial = initialFrom && initialFrom[0][1][j][k].from;
+ deltaPath.push(this.computeDelta(startPath[j][k], endPath[j][k], damper, initial));
+ }
+ path.push(deltaPath);
+ }
+ out.push([start[i][0], path]);
+ }
+ return out;
+ },
+
+ set: function(values, easing) {
+ var ln = values.length,
+ out = [],
+ i, j, k, newPath, calcPath, deltaPath, deltaPathLn, pointsLn;
+ for (i = 0; i < ln; i++) {
+ deltaPath = values[i][1];
+ newPath = [];
+ deltaPathLn = deltaPath.length;
+ for (j = 0; j < deltaPathLn; j++) {
+ calcPath = [deltaPath[j][0]];
+ pointsLn = deltaPath[j].length;
+ for (k = 1; k < pointsLn; k++) {
+ calcPath.push(deltaPath[j][k].from + deltaPath[j][k].delta * easing);
+ }
+ newPath.push(calcPath.join(','));
+ }
+ out.push([values[i][0], newPath.join(',')]);
+ }
+ return out;
+ }
+ }
+ /* End Definitions */
+ }
+}, function() {
+ Ext.each([
+ 'outlineColor',
+ 'backgroundColor',
+ 'borderColor',
+ 'borderTopColor',
+ 'borderRightColor',
+ 'borderBottomColor',
+ 'borderLeftColor',
+ 'fill',
+ 'stroke'
+ ], function(prop) {
+ this[prop] = this.color;
+ }, this);
+});
+/**
+ * @class Ext.fx.Anim
+ *
+ * This class manages animation for a specific {@link #target}. The animation allows
+ * animation of various properties on the target, such as size, position, color and others.
+ *
+ * ## Starting Conditions
+ * The starting conditions for the animation are provided by the {@link #from} configuration.
+ * Any/all of the properties in the {@link #from} configuration can be specified. If a particular
+ * property is not defined, the starting value for that property will be read directly from the target.
+ *
+ * ## End Conditions
+ * The ending conditions for the animation are provided by the {@link #to} configuration. These mark
+ * the final values once the animations has finished. The values in the {@link #from} can mirror
+ * those in the {@link #to} configuration to provide a starting point.
+ *
+ * ## Other Options
+ * - {@link #duration}: Specifies the time period of the animation.
+ * - {@link #easing}: Specifies the easing of the animation.
+ * - {@link #iterations}: Allows the animation to repeat a number of times.
+ * - {@link #alternate}: Used in conjunction with {@link #iterations}, reverses the direction every second iteration.
+ *
+ * ## Example Code
+ *
+ * @example
+ * var myComponent = Ext.create('Ext.Component', {
+ * renderTo: document.body,
+ * width: 200,
+ * height: 200,
+ * style: 'border: 1px solid red;'
+ * });
+ *
+ * Ext.create('Ext.fx.Anim', {
+ * target: myComponent,
+ * duration: 1000,
+ * from: {
+ * width: 400 //starting width 400
+ * },
+ * to: {
+ * width: 300, //end width 300
+ * height: 300 // end width 300
+ * }
+ * });
+ */
+Ext.define('Ext.fx.Anim', {
+
+ /* Begin Definitions */
+
+ mixins: {
+ observable: 'Ext.util.Observable'
+ },
+
+ requires: ['Ext.fx.Manager', 'Ext.fx.Animator', 'Ext.fx.Easing', 'Ext.fx.CubicBezier', 'Ext.fx.PropertyHandler'],
+
+ /* End Definitions */
+
+ isAnimation: true,
+
+ /**
+ * @cfg {Function} callback
+ * A function to be run after the animation has completed.
+ */
+
+ /**
+ * @cfg {Function} scope
+ * The scope that the {@link #callback} function will be called with
+ */
+
+ /**
+ * @cfg {Number} duration
+ * Time in milliseconds for a single animation to last. Defaults to 250. If the {@link #iterations} property is
+ * specified, then each animate will take the same duration for each iteration.
+ */
+ duration: 250,
+
+ /**
+ * @cfg {Number} delay
+ * Time to delay before starting the animation. Defaults to 0.
+ */
+ delay: 0,
+
+ /* private used to track a delayed starting time */
+ delayStart: 0,
+
+ /**
+ * @cfg {Boolean} dynamic
+ * Currently only for Component Animation: Only set a component's outer element size bypassing layouts. Set to true to do full layouts for every frame of the animation. Defaults to false.
+ */
+ dynamic: false,
+
+ /**
+ * @cfg {String} easing
+This describes how the intermediate values used during a transition will be calculated. It allows for a transition to change
+speed over its duration.
+
+ -backIn
+ -backOut
+ -bounceIn
+ -bounceOut
+ -ease
+ -easeIn
+ -easeOut
+ -easeInOut
+ -elasticIn
+ -elasticOut
+ -cubic-bezier(x1, y1, x2, y2)
+
+Note that cubic-bezier will create a custom easing curve following the CSS3 [transition-timing-function][0]
+specification. The four values specify points P1 and P2 of the curve as (x1, y1, x2, y2). All values must
+be in the range [0, 1] or the definition is invalid.
+
+[0]: http://www.w3.org/TR/css3-transitions/#transition-timing-function_tag
+
+ * @markdown
+ */
+ easing: 'ease',
+
+ /**
+ * @cfg {Object} keyframes
+ * Animation keyframes follow the CSS3 Animation configuration pattern. 'from' is always considered '0%' and 'to'
+ * is considered '100%'.<b>Every keyframe declaration must have a keyframe rule for 0% and 100%, possibly defined using
+ * "from" or "to"</b>. A keyframe declaration without these keyframe selectors is invalid and will not be available for
+ * animation. The keyframe declaration for a keyframe rule consists of properties and values. Properties that are unable to
+ * be animated are ignored in these rules, with the exception of 'easing' which can be changed at each keyframe. For example:
+ <pre><code>
+keyframes : {
+ '0%': {
+ left: 100
+ },
+ '40%': {
+ left: 150
+ },
+ '60%': {
+ left: 75
+ },
+ '100%': {
+ left: 100
+ }
+}
+ </code></pre>
+ */
+
+ /**
+ * @private
+ */
+ damper: 1,
+
+ /**
+ * @private
+ */
+ bezierRE: /^(?:cubic-)?bezier\(([^,]+),([^,]+),([^,]+),([^\)]+)\)/,
+
+ /**
+ * Run the animation from the end to the beginning
+ * Defaults to false.
+ * @cfg {Boolean} reverse
+ */
+ reverse: false,
+
+ /**
+ * Flag to determine if the animation has started
+ * @property running
+ * @type Boolean
+ */
+ running: false,
+
+ /**
+ * Flag to determine if the animation is paused. Only set this to true if you need to
+ * keep the Anim instance around to be unpaused later; otherwise call {@link #end}.
+ * @property paused
+ * @type Boolean
+ */
+ paused: false,
+
+ /**
+ * Number of times to execute the animation. Defaults to 1.
+ * @cfg {Number} iterations
+ */
+ iterations: 1,
+
+ /**
+ * Used in conjunction with iterations to reverse the animation each time an iteration completes.
+ * @cfg {Boolean} alternate
+ * Defaults to false.
+ */
+ alternate: false,
+
+ /**
+ * Current iteration the animation is running.
+ * @property currentIteration
+ * @type Number
+ */
+ currentIteration: 0,
+
+ /**
+ * Starting time of the animation.
+ * @property startTime
+ * @type Date
+ */
+ startTime: 0,
+
+ /**
+ * Contains a cache of the interpolators to be used.
+ * @private
+ * @property propHandlers
+ * @type Object
+ */
+
+ /**
+ * @cfg {String/Object} target
+ * The {@link Ext.fx.target.Target} to apply the animation to. This should only be specified when creating an Ext.fx.Anim directly.
+ * The target does not need to be a {@link Ext.fx.target.Target} instance, it can be the underlying object. For example, you can
+ * pass a Component, Element or Sprite as the target and the Anim will create the appropriate {@link Ext.fx.target.Target} object
+ * automatically.
+ */
+
+ /**
+ * @cfg {Object} from
+ * An object containing property/value pairs for the beginning of the animation. If not specified, the current state of the
+ * Ext.fx.target will be used. For example:
+<pre><code>
+from : {
+ opacity: 0, // Transparent
+ color: '#ffffff', // White
+ left: 0
+}
+</code></pre>
+ */
+
+ /**
+ * @cfg {Object} to
+ * An object containing property/value pairs for the end of the animation. For example:
+ <pre><code>
+ to : {
+ opacity: 1, // Opaque
+ color: '#00ff00', // Green
+ left: 500
+ }
+ </code></pre>
+ */
+
+ // @private
+ constructor: function(config) {
+ var me = this,
+ curve;
+
+ config = config || {};
+ // If keyframes are passed, they really want an Animator instead.
+ if (config.keyframes) {
+ return Ext.create('Ext.fx.Animator', config);
+ }
+ config = Ext.apply(me, config);
+ if (me.from === undefined) {
+ me.from = {};
+ }
+ me.propHandlers = {};
+ me.config = config;
+ me.target = Ext.fx.Manager.createTarget(me.target);
+ me.easingFn = Ext.fx.Easing[me.easing];
+ me.target.dynamic = me.dynamic;
+
+ // If not a pre-defined curve, try a cubic-bezier
+ if (!me.easingFn) {
+ me.easingFn = String(me.easing).match(me.bezierRE);
+ if (me.easingFn && me.easingFn.length == 5) {
+ curve = me.easingFn;
+ me.easingFn = Ext.fx.CubicBezier.cubicBezier(+curve[1], +curve[2], +curve[3], +curve[4]);
+ }
+ }
+ me.id = Ext.id(null, 'ext-anim-');
+ Ext.fx.Manager.addAnim(me);
+ me.addEvents(
+ /**
+ * @event beforeanimate
+ * Fires before the animation starts. A handler can return false to cancel the animation.
+ * @param {Ext.fx.Anim} this
+ */
+ 'beforeanimate',
+ /**
+ * @event afteranimate
+ * Fires when the animation is complete.
+ * @param {Ext.fx.Anim} this
+ * @param {Date} startTime
+ */
+ 'afteranimate',
+ /**
+ * @event lastframe
+ * Fires when the animation's last frame has been set.
+ * @param {Ext.fx.Anim} this
+ * @param {Date} startTime
+ */
+ 'lastframe'
+ );
+ me.mixins.observable.constructor.call(me, config);
+ if (config.callback) {
+ me.on('afteranimate', config.callback, config.scope);
+ }
+ return me;
+ },
+
+ /**
+ * @private
+ * Helper to the target
+ */
+ setAttr: function(attr, value) {
+ return Ext.fx.Manager.items.get(this.id).setAttr(this.target, attr, value);
+ },
+
+ /**
+ * @private
+ * Set up the initial currentAttrs hash.
+ */
+ initAttrs: function() {
+ var me = this,
+ from = me.from,
+ to = me.to,
+ initialFrom = me.initialFrom || {},
+ out = {},
+ start, end, propHandler, attr;
+
+ for (attr in to) {
+ if (to.hasOwnProperty(attr)) {
+ start = me.target.getAttr(attr, from[attr]);
+ end = to[attr];
+ // Use default (numeric) property handler
+ if (!Ext.fx.PropertyHandler[attr]) {
+ if (Ext.isObject(end)) {
+ propHandler = me.propHandlers[attr] = Ext.fx.PropertyHandler.object;
+ } else {
+ propHandler = me.propHandlers[attr] = Ext.fx.PropertyHandler.defaultHandler;
+ }
+ }
+ // Use custom handler
+ else {
+ propHandler = me.propHandlers[attr] = Ext.fx.PropertyHandler[attr];
+ }
+ out[attr] = propHandler.get(start, end, me.damper, initialFrom[attr], attr);
+ }
+ }
+ me.currentAttrs = out;
+ },
+
+ /**
+ * @private
+ * Fires beforeanimate and sets the running flag.
+ */
+ start: function(startTime) {
+ var me = this,
+ delay = me.delay,
+ delayStart = me.delayStart,
+ delayDelta;
+ if (delay) {
+ if (!delayStart) {
+ me.delayStart = startTime;
+ return;
+ }
+ else {
+ delayDelta = startTime - delayStart;
+ if (delayDelta < delay) {
+ return;
+ }
+ else {
+ // Compensate for frame delay;
+ startTime = new Date(delayStart.getTime() + delay);
+ }
+ }
+ }
+ if (me.fireEvent('beforeanimate', me) !== false) {
+ me.startTime = startTime;
+ if (!me.paused && !me.currentAttrs) {
+ me.initAttrs();
+ }
+ me.running = true;
+ }
+ },
+
+ /**
+ * @private
+ * Calculate attribute value at the passed timestamp.
+ * @returns a hash of the new attributes.
+ */
+ runAnim: function(elapsedTime) {
+ var me = this,
+ attrs = me.currentAttrs,
+ duration = me.duration,
+ easingFn = me.easingFn,
+ propHandlers = me.propHandlers,
+ ret = {},
+ easing, values, attr, lastFrame;
+
+ if (elapsedTime >= duration) {
+ elapsedTime = duration;
+ lastFrame = true;
+ }
+ if (me.reverse) {
+ elapsedTime = duration - elapsedTime;
+ }
+
+ for (attr in attrs) {
+ if (attrs.hasOwnProperty(attr)) {
+ values = attrs[attr];
+ easing = lastFrame ? 1 : easingFn(elapsedTime / duration);
+ ret[attr] = propHandlers[attr].set(values, easing);
+ }
+ }
+ return ret;
+ },
+
+ /**
+ * @private
+ * Perform lastFrame cleanup and handle iterations
+ * @returns a hash of the new attributes.
+ */
+ lastFrame: function() {
+ var me = this,
+ iter = me.iterations,
+ iterCount = me.currentIteration;
+
+ iterCount++;
+ if (iterCount < iter) {
+ if (me.alternate) {
+ me.reverse = !me.reverse;
+ }
+ me.startTime = new Date();
+ me.currentIteration = iterCount;
+ // Turn off paused for CSS3 Transitions
+ me.paused = false;
+ }
+ else {
+ me.currentIteration = 0;
+ me.end();
+ me.fireEvent('lastframe', me, me.startTime);
+ }
+ },
+
+ /**
+ * Fire afteranimate event and end the animation. Usually called automatically when the
+ * animation reaches its final frame, but can also be called manually to pre-emptively
+ * stop and destroy the running animation.
+ */
+ end: function() {
+ var me = this;
+ me.startTime = 0;
+ me.paused = false;
+ me.running = false;
+ Ext.fx.Manager.removeAnim(me);
+ me.fireEvent('afteranimate', me, me.startTime);
+ }
+});
+// Set flag to indicate that Fx is available. Class might not be available immediately.
+Ext.enableFx = true;
+
+/*
+ * This is a derivative of the similarly named class in the YUI Library.
+ * The original license:
+ * Copyright (c) 2006, Yahoo! Inc. All rights reserved.
+ * Code licensed under the BSD License:
+ * http://developer.yahoo.net/yui/license.txt
+ */
+
+
+/**
+ * Defines the interface and base operation of items that that can be
+ * dragged or can be drop targets. It was designed to be extended, overriding
+ * the event handlers for startDrag, onDrag, onDragOver and onDragOut.
+ * Up to three html elements can be associated with a DragDrop instance:
+ *
+ * - linked element: the element that is passed into the constructor.
+ * This is the element which defines the boundaries for interaction with
+ * other DragDrop objects.
+ *
+ * - handle element(s): The drag operation only occurs if the element that
+ * was clicked matches a handle element. By default this is the linked
+ * element, but there are times that you will want only a portion of the
+ * linked element to initiate the drag operation, and the setHandleElId()
+ * method provides a way to define this.
+ *
+ * - drag element: this represents the element that would be moved along
+ * with the cursor during a drag operation. By default, this is the linked
+ * element itself as in {@link Ext.dd.DD}. setDragElId() lets you define
+ * a separate element that would be moved, as in {@link Ext.dd.DDProxy}.
+ *
+ * This class should not be instantiated until the onload event to ensure that
+ * the associated elements are available.
+ * The following would define a DragDrop obj that would interact with any
+ * other DragDrop obj in the "group1" group:
+ *
+ * dd = new Ext.dd.DragDrop("div1", "group1");
+ *
+ * Since none of the event handlers have been implemented, nothing would
+ * actually happen if you were to run the code above. Normally you would
+ * override this class or one of the default implementations, but you can
+ * also override the methods you want on an instance of the class...
+ *
+ * dd.onDragDrop = function(e, id) {
+ * alert("dd was dropped on " + id);
+ * }
+ *
+ */
+Ext.define('Ext.dd.DragDrop', {
+ requires: ['Ext.dd.DragDropManager'],
+
+ /**
+ * Creates new DragDrop.
+ * @param {String} id of the element that is linked to this instance
+ * @param {String} sGroup the group of related DragDrop objects
+ * @param {Object} config an object containing configurable attributes.
+ * Valid properties for DragDrop:
+ *
+ * - padding
+ * - isTarget
+ * - maintainOffset
+ * - primaryButtonOnly
+ */
+ constructor: function(id, sGroup, config) {
+ if(id) {
+ this.init(id, sGroup, config);
+ }
+ },
+
+ /**
+ * Set to false to enable a DragDrop object to fire drag events while dragging
+ * over its own Element. Defaults to true - DragDrop objects do not by default
+ * fire drag events to themselves.
+ * @property ignoreSelf
+ * @type Boolean
+ */
+
+ /**
+ * The id of the element associated with this object. This is what we
+ * refer to as the "linked element" because the size and position of
+ * this element is used to determine when the drag and drop objects have
+ * interacted.
+ * @property id
+ * @type String
+ */
+ id: null,
+
+ /**
+ * Configuration attributes passed into the constructor
+ * @property config
+ * @type Object
+ */
+ config: null,
+
+ /**
+ * The id of the element that will be dragged. By default this is same
+ * as the linked element, but could be changed to another element. Ex:
+ * Ext.dd.DDProxy
+ * @property dragElId
+ * @type String
+ * @private
+ */
+ dragElId: null,
+
+ /**
+ * The ID of the element that initiates the drag operation. By default
+ * this is the linked element, but could be changed to be a child of this
+ * element. This lets us do things like only starting the drag when the
+ * header element within the linked html element is clicked.
+ * @property handleElId
+ * @type String
+ * @private
+ */
+ handleElId: null,
+
+ /**
+ * An object who's property names identify HTML tags to be considered invalid as drag handles.
+ * A non-null property value identifies the tag as invalid. Defaults to the
+ * following value which prevents drag operations from being initiated by <a> elements:<pre><code>
+{
+ A: "A"
+}</code></pre>
+ * @property invalidHandleTypes
+ * @type Object
+ */
+ invalidHandleTypes: null,
+
+ /**
+ * An object who's property names identify the IDs of elements to be considered invalid as drag handles.
+ * A non-null property value identifies the ID as invalid. For example, to prevent
+ * dragging from being initiated on element ID "foo", use:<pre><code>
+{
+ foo: true
+}</code></pre>
+ * @property invalidHandleIds
+ * @type Object
+ */
+ invalidHandleIds: null,
+
+ /**
+ * An Array of CSS class names for elements to be considered in valid as drag handles.
+ * @property {String[]} invalidHandleClasses
+ */
+ invalidHandleClasses: null,
+
+ /**
+ * The linked element's absolute X position at the time the drag was
+ * started
+ * @property startPageX
+ * @type Number
+ * @private
+ */
+ startPageX: 0,
+
+ /**
+ * The linked element's absolute X position at the time the drag was
+ * started
+ * @property startPageY
+ * @type Number
+ * @private
+ */
+ startPageY: 0,
+
+ /**
+ * The group defines a logical collection of DragDrop objects that are
+ * related. Instances only get events when interacting with other
+ * DragDrop object in the same group. This lets us define multiple
+ * groups using a single DragDrop subclass if we want.
+ * @property groups
+ * @type Object An object in the format {'group1':true, 'group2':true}
+ */
+ groups: null,
+
+ /**
+ * Individual drag/drop instances can be locked. This will prevent
+ * onmousedown start drag.
+ * @property locked
+ * @type Boolean
+ * @private
+ */
+ locked: false,
+
+ /**
+ * Locks this instance
+ */
+ lock: function() {
+ this.locked = true;
+ },
+
+ /**
+ * When set to true, other DD objects in cooperating DDGroups do not receive
+ * notification events when this DD object is dragged over them. Defaults to false.
+ * @property moveOnly
+ * @type Boolean
+ */
+ moveOnly: false,
+
+ /**
+ * Unlocks this instace
+ */
+ unlock: function() {
+ this.locked = false;
+ },
+
+ /**
+ * By default, all instances can be a drop target. This can be disabled by
+ * setting isTarget to false.
+ * @property isTarget
+ * @type Boolean
+ */
+ isTarget: true,
+
+ /**
+ * The padding configured for this drag and drop object for calculating
+ * the drop zone intersection with this object.
+ * An array containing the 4 padding values: [top, right, bottom, left]
+ * @property {Number[]} padding
+ */
+ padding: null,
+
+ /**
+ * Cached reference to the linked element
+ * @property _domRef
+ * @private
+ */
+ _domRef: null,
+
+ /**
+ * Internal typeof flag
+ * @property __ygDragDrop
+ * @private
+ */
+ __ygDragDrop: true,
+
+ /**
+ * Set to true when horizontal contraints are applied
+ * @property constrainX
+ * @type Boolean
+ * @private
+ */
+ constrainX: false,
+
+ /**
+ * Set to true when vertical contraints are applied
+ * @property constrainY
+ * @type Boolean
+ * @private
+ */
+ constrainY: false,
+
+ /**
+ * The left constraint
+ * @property minX
+ * @type Number
+ * @private
+ */
+ minX: 0,
+
+ /**
+ * The right constraint
+ * @property maxX
+ * @type Number
+ * @private
+ */
+ maxX: 0,
+
+ /**
+ * The up constraint
+ * @property minY
+ * @type Number
+ * @private
+ */
+ minY: 0,
+
+ /**
+ * The down constraint
+ * @property maxY
+ * @type Number
+ * @private
+ */
+ maxY: 0,
+
+ /**
+ * Maintain offsets when we resetconstraints. Set to true when you want
+ * the position of the element relative to its parent to stay the same
+ * when the page changes
+ *
+ * @property maintainOffset
+ * @type Boolean
+ */
+ maintainOffset: false,
+
+ /**
+ * Array of pixel locations the element will snap to if we specified a
+ * horizontal graduation/interval. This array is generated automatically
+ * when you define a tick interval.
+ * @property {Number[]} xTicks
+ */
+ xTicks: null,
+
+ /**
+ * Array of pixel locations the element will snap to if we specified a
+ * vertical graduation/interval. This array is generated automatically
+ * when you define a tick interval.
+ * @property {Number[]} yTicks
+ */
+ yTicks: null,
+
+ /**
+ * By default the drag and drop instance will only respond to the primary
+ * button click (left button for a right-handed mouse). Set to true to
+ * allow drag and drop to start with any mouse click that is propogated
+ * by the browser
+ * @property primaryButtonOnly
+ * @type Boolean
+ */
+ primaryButtonOnly: true,
+
+ /**
+ * The available property is false until the linked dom element is accessible.
+ * @property available
+ * @type Boolean
+ */
+ available: false,
+
+ /**
+ * By default, drags can only be initiated if the mousedown occurs in the
+ * region the linked element is. This is done in part to work around a
+ * bug in some browsers that mis-report the mousedown if the previous
+ * mouseup happened outside of the window. This property is set to true
+ * if outer handles are defined. Defaults to false.
+ *
+ * @property hasOuterHandles
+ * @type Boolean
+ */
+ hasOuterHandles: false,
+
+ /**
+ * Code that executes immediately before the startDrag event
+ * @private
+ */
+ b4StartDrag: function(x, y) { },
+
+ /**
+ * Abstract method called after a drag/drop object is clicked
+ * and the drag or mousedown time thresholds have beeen met.
+ * @param {Number} X click location
+ * @param {Number} Y click location
+ */
+ startDrag: function(x, y) { /* override this */ },
+
+ /**
+ * Code that executes immediately before the onDrag event
+ * @private
+ */
+ b4Drag: function(e) { },
+
+ /**
+ * Abstract method called during the onMouseMove event while dragging an
+ * object.
+ * @param {Event} e the mousemove event
+ */
+ onDrag: function(e) { /* override this */ },
+
+ /**
+ * Abstract method called when this element fist begins hovering over
+ * another DragDrop obj
+ * @param {Event} e the mousemove event
+ * @param {String/Ext.dd.DragDrop[]} id In POINT mode, the element
+ * id this is hovering over. In INTERSECT mode, an array of one or more
+ * dragdrop items being hovered over.
+ */
+ onDragEnter: function(e, id) { /* override this */ },
+
+ /**
+ * Code that executes immediately before the onDragOver event
+ * @private
+ */
+ b4DragOver: function(e) { },
+
+ /**
+ * Abstract method called when this element is hovering over another
+ * DragDrop obj
+ * @param {Event} e the mousemove event
+ * @param {String/Ext.dd.DragDrop[]} id In POINT mode, the element
+ * id this is hovering over. In INTERSECT mode, an array of dd items
+ * being hovered over.
+ */
+ onDragOver: function(e, id) { /* override this */ },
+
+ /**
+ * Code that executes immediately before the onDragOut event
+ * @private
+ */
+ b4DragOut: function(e) { },
+
+ /**
+ * Abstract method called when we are no longer hovering over an element
+ * @param {Event} e the mousemove event
+ * @param {String/Ext.dd.DragDrop[]} id In POINT mode, the element
+ * id this was hovering over. In INTERSECT mode, an array of dd items
+ * that the mouse is no longer over.
+ */
+ onDragOut: function(e, id) { /* override this */ },
+
+ /**
+ * Code that executes immediately before the onDragDrop event
+ * @private
+ */
+ b4DragDrop: function(e) { },
+
+ /**
+ * Abstract method called when this item is dropped on another DragDrop
+ * obj
+ * @param {Event} e the mouseup event
+ * @param {String/Ext.dd.DragDrop[]} id In POINT mode, the element
+ * id this was dropped on. In INTERSECT mode, an array of dd items this
+ * was dropped on.
+ */
+ onDragDrop: function(e, id) { /* override this */ },
+
+ /**
+ * Abstract method called when this item is dropped on an area with no
+ * drop target
+ * @param {Event} e the mouseup event
+ */
+ onInvalidDrop: function(e) { /* override this */ },
+
+ /**
+ * Code that executes immediately before the endDrag event
+ * @private
+ */
+ b4EndDrag: function(e) { },
+
+ /**
+ * Called when we are done dragging the object
+ * @param {Event} e the mouseup event
+ */
+ endDrag: function(e) { /* override this */ },
+
+ /**
+ * Code executed immediately before the onMouseDown event
+ * @param {Event} e the mousedown event
+ * @private
+ */
+ b4MouseDown: function(e) { },
+
+ /**
+ * Called when a drag/drop obj gets a mousedown
+ * @param {Event} e the mousedown event
+ */
+ onMouseDown: function(e) { /* override this */ },
+
+ /**
+ * Called when a drag/drop obj gets a mouseup
+ * @param {Event} e the mouseup event
+ */
+ onMouseUp: function(e) { /* override this */ },
+
+ /**
+ * Override the onAvailable method to do what is needed after the initial
+ * position was determined.
+ */
+ onAvailable: function () {
+ },
+
+ /**
+ * @property {Object} defaultPadding
+ * Provides default constraint padding to "constrainTo" elements.
+ */
+ defaultPadding: {
+ left: 0,
+ right: 0,
+ top: 0,
+ bottom: 0
+ },
+
+ /**
+ * Initializes the drag drop object's constraints to restrict movement to a certain element.
+ *
+ * Usage:
+ *
+ * var dd = new Ext.dd.DDProxy("dragDiv1", "proxytest",
+ * { dragElId: "existingProxyDiv" });
+ * dd.startDrag = function(){
+ * this.constrainTo("parent-id");
+ * };
+ *
+ * Or you can initalize it using the {@link Ext.Element} object:
+ *
+ * Ext.get("dragDiv1").initDDProxy("proxytest", {dragElId: "existingProxyDiv"}, {
+ * startDrag : function(){
+ * this.constrainTo("parent-id");
+ * }
+ * });
+ *
+ * @param {String/HTMLElement/Ext.Element} constrainTo The element or element ID to constrain to.
+ * @param {Object/Number} pad (optional) Pad provides a way to specify "padding" of the constraints,
+ * and can be either a number for symmetrical padding (4 would be equal to `{left:4, right:4, top:4, bottom:4}`) or
+ * an object containing the sides to pad. For example: `{right:10, bottom:10}`
+ * @param {Boolean} inContent (optional) Constrain the draggable in the content box of the element (inside padding and borders)
+ */
+ constrainTo : function(constrainTo, pad, inContent){
+ if(Ext.isNumber(pad)){
+ pad = {left: pad, right:pad, top:pad, bottom:pad};
+ }
+ pad = pad || this.defaultPadding;
+ var b = Ext.get(this.getEl()).getBox(),
+ ce = Ext.get(constrainTo),
+ s = ce.getScroll(),
+ c,
+ cd = ce.dom;
+ if(cd == document.body){
+ c = { x: s.left, y: s.top, width: Ext.Element.getViewWidth(), height: Ext.Element.getViewHeight()};
+ }else{
+ var xy = ce.getXY();
+ c = {x : xy[0], y: xy[1], width: cd.clientWidth, height: cd.clientHeight};
+ }
+
+
+ var topSpace = b.y - c.y,
+ leftSpace = b.x - c.x;
+
+ this.resetConstraints();
+ this.setXConstraint(leftSpace - (pad.left||0), // left
+ c.width - leftSpace - b.width - (pad.right||0), //right
+ this.xTickSize
+ );
+ this.setYConstraint(topSpace - (pad.top||0), //top
+ c.height - topSpace - b.height - (pad.bottom||0), //bottom
+ this.yTickSize
+ );
+ },
+
+ /**
+ * Returns a reference to the linked element
+ * @return {HTMLElement} the html element
+ */
+ getEl: function() {
+ if (!this._domRef) {
+ this._domRef = Ext.getDom(this.id);
+ }
+
+ return this._domRef;
+ },
+
+ /**
+ * Returns a reference to the actual element to drag. By default this is
+ * the same as the html element, but it can be assigned to another
+ * element. An example of this can be found in Ext.dd.DDProxy
+ * @return {HTMLElement} the html element
+ */
+ getDragEl: function() {
+ return Ext.getDom(this.dragElId);
+ },
+
+ /**
+ * Sets up the DragDrop object. Must be called in the constructor of any
+ * Ext.dd.DragDrop subclass
+ * @param {String} id the id of the linked element
+ * @param {String} sGroup the group of related items
+ * @param {Object} config configuration attributes
+ */
+ init: function(id, sGroup, config) {
+ this.initTarget(id, sGroup, config);
+ Ext.EventManager.on(this.id, "mousedown", this.handleMouseDown, this);
+ // Ext.EventManager.on(this.id, "selectstart", Event.preventDefault);
+ },
+
+ /**
+ * Initializes Targeting functionality only... the object does not
+ * get a mousedown handler.
+ * @param {String} id the id of the linked element
+ * @param {String} sGroup the group of related items
+ * @param {Object} config configuration attributes
+ */
+ initTarget: function(id, sGroup, config) {
+ // configuration attributes
+ this.config = config || {};
+
+ // create a local reference to the drag and drop manager
+ this.DDMInstance = Ext.dd.DragDropManager;
+ // initialize the groups array
+ this.groups = {};
+
+ // assume that we have an element reference instead of an id if the
+ // parameter is not a string
+ if (typeof id !== "string") {
+ id = Ext.id(id);
+ }
+
+ // set the id
+ this.id = id;
+
+ // add to an interaction group
+ this.addToGroup((sGroup) ? sGroup : "default");
+
+ // We don't want to register this as the handle with the manager
+ // so we just set the id rather than calling the setter.
+ this.handleElId = id;
+
+ // the linked element is the element that gets dragged by default
+ this.setDragElId(id);
+
+ // by default, clicked anchors will not start drag operations.
+ this.invalidHandleTypes = { A: "A" };
+ this.invalidHandleIds = {};
+ this.invalidHandleClasses = [];
+
+ this.applyConfig();
+
+ this.handleOnAvailable();
+ },
+
+ /**
+ * Applies the configuration parameters that were passed into the constructor.
+ * This is supposed to happen at each level through the inheritance chain. So
+ * a DDProxy implentation will execute apply config on DDProxy, DD, and
+ * DragDrop in order to get all of the parameters that are available in
+ * each object.