UNPKG

@softvisio/ext

Version:
1,345 lines (1,337 loc) 1.39 MB
Ext.define("Ext.draw.ContainerBase", { extend: Ext.Container, constructor: function (config) { this.callParent([config]); this.initAnimator(); }, onResize: function (width, height, oldWidth, oldHeight) { this.handleResize( { width: width, height: height, }, true ); }, addElementListener: function () { var el = this.element; el.on.apply(el, arguments); }, removeElementListener: function () { var el = this.element; el.un.apply(el, arguments); }, preview: function (image) { var item; image = image || this.getImage(); if (image.type === "svg-markup") { item = { xtype: "container", html: image.data, }; } else { item = { xtype: "image", mode: "img", imageCls: "", cls: Ext.baseCSSPrefix + "chart-preview", src: image.data, }; } Ext.Viewport.add({ xtype: "panel", layout: "fit", modal: true, border: 1, shadow: true, width: "90%", height: "90%", hideOnMaskTap: true, centered: true, floated: true, scrollable: false, closable: true, // Use 'hide' so that hiding via close button/mask tap go through // the same code path closeAction: "hide", items: [item], listeners: { hide: function () { this.destroy(); }, }, }).show(); }, }); /** * @private * @class Ext.draw.SurfaceBase */ Ext.define("Ext.draw.SurfaceBase", { extend: Ext.Widget, getOwnerBody: function () { return this.getRefOwner().bodyElement; }, }); /** * @private * @class Ext.draw.sprite.AnimationParser * * Computes an intermidiate value between two values of the same type for use in animations. * Can have pre- and post- processor functions if the values need to be processed * before an intermidiate value can be computed (parseInitial), or the computed value * needs to be processed before it can be used as a valid attribute value (serve). */ Ext.define("Ext.draw.sprite.AnimationParser", function () { function compute(from, to, delta) { return from + (to - from) * delta; } return { singleton: true, attributeRe: /^url\(#([a-zA-Z-]+)\)$/, color: { parseInitial: function (color1, color2) { if (Ext.isString(color1)) { color1 = Ext.util.Color.create(color1); } if (Ext.isString(color2)) { color2 = Ext.util.Color.create(color2); } if (color1 && color1.isColor && color2 && color2.isColor) { return [ [color1.r, color1.g, color1.b, color1.a], [color2.r, color2.g, color2.b, color2.a], ]; } else { return [color1 || color2, color2 || color1]; } }, compute: function (from, to, delta) { if (!Ext.isArray(from) || !Ext.isArray(to)) { return to || from; } else { return [compute(from[0], to[0], delta), compute(from[1], to[1], delta), compute(from[2], to[2], delta), compute(from[3], to[3], delta)]; } }, serve: function (array) { var color = Ext.util.Color.fly(array[0], array[1], array[2], array[3]); return color.toString(); }, }, number: { parse: function (n) { return n === null ? null : +n; }, compute: function (from, to, delta) { if (!Ext.isNumber(from) || !Ext.isNumber(to)) { return to || from; } else { return compute(from, to, delta); } }, }, angle: { parseInitial: function (from, to) { if (to - from > Math.PI) { to -= Math.PI * 2; } else if (to - from < -Math.PI) { to += Math.PI * 2; } return [from, to]; }, compute: function (from, to, delta) { if (!Ext.isNumber(from) || !Ext.isNumber(to)) { return to || from; } else { return compute(from, to, delta); } }, }, path: { parseInitial: function (from, to) { var fromStripes = from.toStripes(), toStripes = to.toStripes(), i, j, fromLength = fromStripes.length, toLength = toStripes.length, fromStripe, toStripe, length, lastStripe = toStripes[toLength - 1], endPoint = [lastStripe[lastStripe.length - 2], lastStripe[lastStripe.length - 1]]; for (i = fromLength; i < toLength; i++) { fromStripes.push(fromStripes[fromLength - 1].slice(0)); } for (i = toLength; i < fromLength; i++) { toStripes.push(endPoint.slice(0)); } length = fromStripes.length; toStripes.path = to; toStripes.temp = new Ext.draw.Path(); for (i = 0; i < length; i++) { fromStripe = fromStripes[i]; toStripe = toStripes[i]; fromLength = fromStripe.length; toLength = toStripe.length; toStripes.temp.commands.push("M"); for (j = toLength; j < fromLength; j += 6) { toStripe.push(endPoint[0], endPoint[1], endPoint[0], endPoint[1], endPoint[0], endPoint[1]); } lastStripe = toStripes[toStripes.length - 1]; endPoint = [lastStripe[lastStripe.length - 2], lastStripe[lastStripe.length - 1]]; for (j = fromLength; j < toLength; j += 6) { fromStripe.push(endPoint[0], endPoint[1], endPoint[0], endPoint[1], endPoint[0], endPoint[1]); } for (i = 0; i < toStripe.length; i++) { toStripe[i] -= fromStripe[i]; } for (i = 2; i < toStripe.length; i += 6) { toStripes.temp.commands.push("C"); } } return [fromStripes, toStripes]; }, compute: function (fromStripes, toStripes, delta) { if (delta >= 1) { return toStripes.path; } // eslint-disable-next-line vars-on-top var i = 0, ln = fromStripes.length, j = 0, ln2, from, to, temp = toStripes.temp.params, pos = 0; for (; i < ln; i++) { from = fromStripes[i]; to = toStripes[i]; ln2 = from.length; for (j = 0; j < ln2; j++) { temp[pos++] = to[j] * delta + from[j]; } } return toStripes.temp; }, }, data: { compute: function (from, to, delta, target) { var iMaxFrom = from.length - 1, iMaxTo = to.length - 1, iMax = Math.max(iMaxFrom, iMaxTo), i, start, end; if (!target || target === from) { target = []; } target.length = iMax + 1; for (i = 0; i <= iMax; i++) { start = from[Math.min(i, iMaxFrom)]; end = to[Math.min(i, iMaxTo)]; if (Ext.isNumber(start)) { if (!Ext.isNumber(end)) { // This may not give the desired visual result during // animation (after all, we don't know what the target // value should be, if it wasn't given to us), but it's // better than spitting out a bunch of NaNs in the target // array, when transitioning from a non-empty to an empty // array. end = 0; } target[i] = start + (end - start) * delta; } else { target[i] = end; } } return target; }, }, text: { compute: function (from, to, delta) { return from.substr(0, Math.round(from.length * (1 - delta))) + to.substr(Math.round(to.length * (1 - delta))); }, }, limited: "number", limited01: "number", }; }); /* global Float32Array */ /* eslint-disable indent */ (function () { if (!Ext.global.Float32Array) { // Typed Array polyfill // eslint-disable-next-line vars-on-top var Float32Array = function (array) { var i, len; if (typeof array === "number") { this.length = array; } else if ("length" in array) { this.length = array.length; for (i = 0, len = array.length; i < len; i++) { this[i] = +array[i]; } } }; Float32Array.prototype = []; Ext.global.Float32Array = Float32Array; } })(); /* eslint-enable indent */ /** * Utility class providing mathematics functionalities through all the draw package. */ Ext.define("Ext.draw.Draw", { singleton: true, radian: Math.PI / 180, pi2: Math.PI * 2, /** * @deprecated 6.5.0 Please use the {@link Ext#identityFn} instead. * Function that returns its first element. * @param {Mixed} a * @return {Mixed} */ reflectFn: function (a) { return a; }, /** * Converting degrees to radians. * @param {Number} degrees * @return {Number} */ rad: function (degrees) { return (degrees % 360) * this.radian; }, /** * Converting radians to degrees. * @param {Number} radian * @return {Number} */ degrees: function (radian) { return (radian / this.radian) % 360; }, /** * * @param {Object} bbox1 * @param {Object} bbox2 * @param {Number} [padding] * @return {Boolean} */ isBBoxIntersect: function (bbox1, bbox2, padding) { padding = padding || 0; return Math.max(bbox1.x, bbox2.x) - padding > Math.min(bbox1.x + bbox1.width, bbox2.x + bbox2.width) || Math.max(bbox1.y, bbox2.y) - padding > Math.min(bbox1.y + bbox1.height, bbox2.y + bbox2.height); }, /** * Checks if a point is within a bounding box. * @param x * @param y * @param bbox * @return {Boolean} */ isPointInBBox: function (x, y, bbox) { return !!bbox && x >= bbox.x && x <= bbox.x + bbox.width && y >= bbox.y && y <= bbox.y + bbox.height; }, /** * Natural cubic spline interpolation. * This algorithm runs in linear time. * * @param {Array} points Array of numbers. */ naturalSpline: function (points) { var i, j, ln = points.length, nd, d, y, ny, r = 0, zs = new Float32Array(points.length), result = new Float32Array(points.length * 3 - 2); zs[0] = 0; zs[ln - 1] = 0; for (i = 1; i < ln - 1; i++) { zs[i] = points[i + 1] + points[i - 1] - 2 * points[i] - zs[i - 1]; r = 1 / (4 - r); zs[i] *= r; } for (i = ln - 2; i > 0; i--) { r = 3.732050807568877 + 48.248711305964385 / (-13.928203230275537 + Math.pow(0.07179676972449123, i)); zs[i] -= zs[i + 1] * r; } ny = points[0]; nd = ny - zs[0]; for (i = 0, j = 0; i < ln - 1; j += 3) { y = ny; d = nd; i++; ny = points[i]; nd = ny - zs[i]; result[j] = y; result[j + 1] = (nd + 2 * d) / 3; result[j + 2] = (nd * 2 + d) / 3; } result[j] = ny; return result; }, /** * Shorthand for {@link #naturalSpline} */ spline: function (points) { return this.naturalSpline(points); }, /** * @private * Cardinal spline interpolation. * Goes from cardinal control points to cubic Bezier control points. */ cardinalToBezier: function (P1, P2, P3, P4, tension) { return [P2, P2 + ((P3 - P1) / 6) * tension, P3 - ((P4 - P2) / 6) * tension, P3]; }, /** * @private * @param {Number[]} P An array of n x- or y-coordinates. * @param {Number} tension * @return {Float32Array} An array of 3n - 2 Bezier control points. */ cardinalSpline: function (P, tension) { var n = P.length, result = new Float32Array(n * 3 - 2), i, bezier; if (tension === undefined) { tension = 0.5; } bezier = this.cardinalToBezier(P[0], P[0], P[1], P[2], tension); result[0] = bezier[0]; result[1] = bezier[1]; result[2] = bezier[2]; result[3] = bezier[3]; for (i = 0; i < n - 3; i++) { bezier = this.cardinalToBezier(P[i], P[i + 1], P[i + 2], P[i + 3], tension); result[4 + i * 3] = bezier[1]; result[4 + i * 3 + 1] = bezier[2]; result[4 + i * 3 + 2] = bezier[3]; } bezier = this.cardinalToBezier(P[n - 3], P[n - 2], P[n - 1], P[n - 1], tension); result[4 + i * 3] = bezier[1]; result[4 + i * 3 + 1] = bezier[2]; result[4 + i * 3 + 2] = bezier[3]; return result; }, /** * @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) { var PI = Math.PI, halfPI = PI / 2, abs = Math.abs, sin = Math.sin, cos = Math.cos, atan = Math.atan, control1Length, control2Length, control1Angle, control2Angle, control1X, control1Y, control2X, control2Y, alpha; value = value || 4; // 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, }; }, /** * Given coordinates of the points, calculates coordinates of a Bezier curve * that goes through them. * @param dataX x-coordinates of the points. * @param dataY y-coordinates of the points. * @param value A value to control the smoothness of the curve. * @return {Object} Object holding two arrays, for x and y coordinates of the curve. */ smooth: function (dataX, dataY, value) { var ln = dataX.length, prevX, prevY, curX, curY, nextX, nextY, x, y, smoothX = [], smoothY = [], i, anchors; for (i = 0; i < ln - 1; i++) { prevX = dataX[i]; prevY = dataY[i]; if (i === 0) { x = prevX; y = prevY; smoothX.push(x); smoothY.push(y); if (ln === 1) { break; } } curX = dataX[i + 1]; curY = dataY[i + 1]; nextX = dataX[i + 2]; nextY = dataY[i + 2]; if (!(Ext.isNumber(nextX) && Ext.isNumber(nextY))) { smoothX.push(x, curX, curX); smoothY.push(y, curY, curY); break; } anchors = this.getAnchors(prevX, prevY, curX, curY, nextX, nextY, value); smoothX.push(x, anchors.x1, curX); smoothY.push(y, anchors.y1, curY); x = anchors.x2; y = anchors.y2; } return { smoothX: smoothX, smoothY: smoothY, }; }, /** * @method * @private * Work around for iOS. * Nested 3d-transforms seems to prevent the redraw inside it until some event is fired. */ beginUpdateIOS: Ext.os.is.iOS ? function () { this.iosUpdateEl = Ext.getBody().createChild({ "data-sticky": true, style: "position: absolute; top: 0px; bottom: 0px; left: 0px; right: 0px; " + "background: rgba(0,0,0,0.001); z-index: 100000", }); } : Ext.emptyFn, endUpdateIOS: function () { this.iosUpdateEl = Ext.destroy(this.iosUpdateEl); }, }); /** * @class Ext.draw.gradient.Gradient * * Creates a gradient. */ Ext.define("Ext.draw.gradient.Gradient", { isGradient: true, config: { /** * @cfg {Object[]} stops * Defines the stops of the gradient. */ stops: [], }, applyStops: function (newStops) { var stops = [], ln = newStops.length, i, stop, color; for (i = 0; i < ln; i++) { stop = newStops[i]; color = stop.color; if (!(color && color.isColor)) { color = Ext.util.Color.fly(color || Ext.util.Color.NONE); } stops.push({ // eslint-disable-next-line max-len offset: Math.min(1, Math.max(0, "offset" in stop ? stop.offset : stop.position || 0)), color: color.toString(), }); } stops.sort(function (a, b) { return a.offset - b.offset; }); return stops; }, onClassExtended: function (subClass, member) { if (!member.alias && member.type) { member.alias = "gradient." + member.type; } }, constructor: function (config) { this.initConfig(config); }, /** * @method * @protected * Generates the gradient for the given context. * @param {Ext.draw.engine.SvgContext} ctx The context. * @param {Object} bbox * @return {CanvasGradient/Ext.draw.engine.SvgContext.Gradient/Ext.util.Color.NONE} */ generateGradient: Ext.emptyFn, }); /** * @class Ext.draw.gradient.GradientDefinition * * A global map of all gradient configs. */ Ext.define("Ext.draw.gradient.GradientDefinition", { singleton: true, urlStringRe: /^url\(#([\w-]+)\)$/, gradients: {}, add: function (gradients) { var store = this.gradients, i, n, gradient; for (i = 0, n = gradients.length; i < n; i++) { gradient = gradients[i]; if (Ext.isString(gradient.id)) { store[gradient.id] = gradient; } } }, get: function (str) { var store = this.gradients, match = str.match(this.urlStringRe), gradient; if (match && match[1] && (gradient = store[match[1]])) { return gradient || str; } return str; }, }); /* global Float32Array */ /** * @private * @class Ext.draw.sprite.AttributeParser * * Parsers used for sprite attributes if they are * {@link Ext.draw.sprite.AttributeDefinition#normalize normalized} (default) when being * {@link Ext.draw.sprite.Sprite#setAttributes set}. * * Methods of the singleton correpond either to the processor functions themselves or processor * factories. */ Ext.define("Ext.draw.sprite.AttributeParser", { singleton: true, attributeRe: /^url\(#([a-zA-Z-]+)\)$/, default: Ext.identityFn, string: function (n) { return String(n); }, number: function (n) { // Numbers as strings will be converted to numbers, // null will be converted to 0. if (Ext.isNumber(+n)) { return n; } }, /** * Normalize angle to the [-180,180) interval. * @param n Angle in radians. * @return {Number/undefined} Normalized angle or undefined. */ angle: function (n) { if (Ext.isNumber(n)) { n %= Math.PI * 2; if (n < -Math.PI) { n += Math.PI * 2; } else if (n >= Math.PI) { n -= Math.PI * 2; } return n; } }, data: function (n) { if (Ext.isArray(n)) { return n.slice(); } else if (n instanceof Float32Array) { return new Float32Array(n); } }, bool: function (n) { return !!n; }, color: function (n) { if (n && n.isColor) { return n.toString(); } else if (n && n.isGradient) { return n; } else if (!n) { return Ext.util.Color.NONE; } else if (Ext.isString(n)) { if (n.substr(0, 3) === "url") { n = Ext.draw.gradient.GradientDefinition.get(n); if (Ext.isString(n)) { return n; } } else { return Ext.util.Color.fly(n).toString(); } } if (n.type === "linear") { return Ext.create("Ext.draw.gradient.Linear", n); } else if (n.type === "radial") { return Ext.create("Ext.draw.gradient.Radial", n); } else if (n.type === "pattern") { return Ext.create("Ext.draw.gradient.Pattern", n); } else { return Ext.util.Color.NONE; } }, limited: function (low, hi) { return function (n) { n = +n; return Ext.isNumber(n) ? Math.min(Math.max(n, low), hi) : undefined; }; }, limited01: function (n) { n = +n; return Ext.isNumber(n) ? Math.min(Math.max(n, 0), 1) : undefined; }, /** * Generates a function that checks if a value matches * one of the given attributes. * @return {Function} */ enums: function () { var enums = {}, args = Array.prototype.slice.call(arguments, 0), i, ln; for (i = 0, ln = args.length; i < ln; i++) { enums[args[i]] = true; } return function (n) { return n in enums ? n : undefined; }; }, }); /** * @private * Flyweight object to process the attributes of a sprite. * A single instance of the AttributeDefinition is created per sprite class. * See `onClassCreated` and `onClassExtended` callbacks * of the {@link Ext.draw.sprite.Sprite} for more info. */ Ext.define("Ext.draw.sprite.AttributeDefinition", { config: { /** * @cfg {Object} defaults Defines the default values of attributes. */ defaults: { $value: {}, lazy: true, }, /** * @cfg {Object} aliases Defines the alternative names for attributes. */ aliases: {}, /** * @cfg {Object} animationProcessors Defines the process used to animate between attributes. * One doesn't have to define animation processors for sprite attributes that use * predefined {@link #processors} from the {@link Ext.draw.sprite.AttributeParser} * singleton. * For such attributes matching animation processors from the * {@link Ext.draw.sprite.AnimationParser} singleton will be used automatically. * However, if you have a custom processor for an attribute that should support * animation, you must provide a corresponding animation processor for it here. * For more information on animation processors please see * {@link Ext.draw.sprite.AnimationParser} documentation. */ animationProcessors: {}, /** * @cfg {Object} processors Defines the preprocessing used on the attributes. * One can define a custom processor function here or use the name of a predefined * processor from the {@link Ext.draw.sprite.AttributeParser} singleton. */ processors: { // A plus side of lazy initialization is that the 'processors' and 'defaults' will // only be applied for those sprite classes that are actually instantiated. $value: {}, lazy: true, }, /** * @cfg {Object} dirtyTriggers * @deprecated 6.5.0 Use the {@link #triggers} config instead. */ dirtyTriggers: {}, /* eslint-disable max-len */ /** * @cfg {Object} triggers Defines which updaters have to be called when an attribute is changed. * For example, the config below indicates that the 'size' updater * of a {@link Ext.draw.sprite.Square square} sprite has to be called * when the 'size' attribute changes. * * triggers: { * size: 'size' // Use comma-separated values here if multiple updaters have to be called. * } // Note that the order is _not_ guaranteed. * * If any of the updaters to be called (triggered by the {@link Ext.draw.sprite.Sprite#setAttributes} call) * set attributes themselves and those attributes have triggers defined for them, * then their updaters will be called after all current updaters finish execution. * * The updater functions themselves are defined in the {@link #updaters} config, * aside from the 'canvas' updater, which doesn't have to be defined and acts as a flag, * indicating that this attribute should be applied to a Canvas context (or whatever emulates it). * @since 5.1.0 */ triggers: {}, /** * @cfg {Object} updaters Defines the postprocessing used by the attribute. * Inside the updater function 'this' refers to the sprite that the attributes belong to. * In case of an instancing sprite 'this' will refer to the instancing template. * The two parameters passed to the updater function are the attributes object * of the sprite or instance, and the names of attributes that triggered this updater call. * * The example below shows how the 'size' updater changes other attributes * of a {@link Ext.draw.sprite.Square square} sprite sprite when its 'size' attribute changes. * * updaters: { * size: function (attr) { * var size = attr.size; * this.setAttributes({ // Changes to these attributes will trigger the 'path' updater. * x: attr.x - size, * y: attr.y - size, * height: 2 * size, * width: 2 * size * }); * } * } */ updaters: {}, }, /* eslint-enable max-len */ inheritableStatics: { /** * @private * Processor declaration in the form of 'processorFactory(argument1,argument2,...)'. * E.g.: {@link Ext.draw.sprite.AttributeParser#enums enums}, * {@link Ext.draw.sprite.AttributeParser#limited limited}. */ processorFactoryRe: /^(\w+)\(([\w\-,]*)\)$/, }, // The sprite class for which AttributeDefinition instance is created. spriteClass: null, constructor: function (config) { var me = this; me.initConfig(config); }, applyDefaults: function (defaults, oldDefaults) { oldDefaults = Ext.apply(oldDefaults || {}, this.normalize(defaults)); return oldDefaults; }, applyAliases: function (aliases, oldAliases) { return Ext.apply(oldAliases || {}, aliases); }, applyProcessors: function (processors, oldProcessors) { this.getAnimationProcessors(); // Apply custom animation processors first. // eslint-disable-next-line vars-on-top var result = oldProcessors || {}, defaultProcessor = Ext.draw.sprite.AttributeParser, processorFactoryRe = this.self.processorFactoryRe, animationProcessors = {}, anyAnimationProcessors, name, match, fn; for (name in processors) { fn = processors[name]; if (typeof fn === "string") { match = fn.match(processorFactoryRe); if (match) { // enums(... , limited(... or something of that nature. fn = defaultProcessor[match[1]].apply(defaultProcessor, match[2].split(",")); } else if (defaultProcessor[fn]) { // Names of animation parsers match the names of attribute parsers. animationProcessors[name] = fn; anyAnimationProcessors = true; fn = defaultProcessor[fn]; } } if (!Ext.isFunction(fn)) { Ext.raise(this.spriteClass.$className + ": processor '" + name + "' has not been found."); } result[name] = fn; } if (anyAnimationProcessors) { this.setAnimationProcessors(animationProcessors); } return result; }, applyAnimationProcessors: function (animationProcessors, oldAnimationProcessors) { var parser = Ext.draw.sprite.AnimationParser, name, item; if (!oldAnimationProcessors) { oldAnimationProcessors = {}; } for (name in animationProcessors) { item = animationProcessors[name]; if (item === "none") { oldAnimationProcessors[name] = null; } else if (Ext.isString(item) && !(name in oldAnimationProcessors)) { if (item in parser) { // The while loop is used to resolve aliases, e.g. `num: 'number'`, // where `number` maps to a parser object or is an alias too. while (Ext.isString(parser[item])) { item = parser[item]; } oldAnimationProcessors[name] = parser[item]; } } else if (Ext.isObject(item)) { oldAnimationProcessors[name] = item; } } return oldAnimationProcessors; }, updateDirtyTriggers: function (dirtyTriggers) { this.setTriggers(dirtyTriggers); }, applyTriggers: function (triggers, oldTriggers) { var name; if (!oldTriggers) { oldTriggers = {}; } for (name in triggers) { oldTriggers[name] = triggers[name].split(","); } return oldTriggers; }, applyUpdaters: function (updaters, oldUpdaters) { return Ext.apply(oldUpdaters || {}, updaters); }, batchedNormalize: function (batchedChanges, keepUnrecognized) { if (!batchedChanges) { return {}; } // eslint-disable-next-line vars-on-top var processors = this.getProcessors(), aliases = this.getAliases(), translation = batchedChanges.translation || batchedChanges.translate, normalized = {}, i, ln, name, val, rotation, scaling, matrix, subVal, split; if ("rotation" in batchedChanges) { rotation = batchedChanges.rotation; } else { rotation = "rotate" in batchedChanges ? batchedChanges.rotate : undefined; } if ("scaling" in batchedChanges) { scaling = batchedChanges.scaling; } else { scaling = "scale" in batchedChanges ? batchedChanges.scale : undefined; } if (typeof scaling !== "undefined") { if (Ext.isNumber(scaling)) { normalized.scalingX = scaling; normalized.scalingY = scaling; } else { if ("x" in scaling) { normalized.scalingX = scaling.x; } if ("y" in scaling) { normalized.scalingY = scaling.y; } if ("centerX" in scaling) { normalized.scalingCenterX = scaling.centerX; } if ("centerY" in scaling) { normalized.scalingCenterY = scaling.centerY; } } } if (typeof rotation !== "undefined") { if (Ext.isNumber(rotation)) { rotation = Ext.draw.Draw.rad(rotation); normalized.rotationRads = rotation; } else { if ("rads" in rotation) { normalized.rotationRads = rotation.rads; } else if ("degrees" in rotation) { if (Ext.isArray(rotation.degrees)) { normalized.rotationRads = Ext.Array.map(rotation.degrees, function (deg) { return Ext.draw.Draw.rad(deg); }); } else { normalized.rotationRads = Ext.draw.Draw.rad(rotation.degrees); } } if ("centerX" in rotation) { normalized.rotationCenterX = rotation.centerX; } if ("centerY" in rotation) { normalized.rotationCenterY = rotation.centerY; } } } if (typeof translation !== "undefined") { if ("x" in translation) { normalized.translationX = translation.x; } if ("y" in translation) { normalized.translationY = translation.y; } } if ("matrix" in batchedChanges) { matrix = Ext.draw.Matrix.create(batchedChanges.matrix); split = matrix.split(); normalized.matrix = matrix; normalized.rotationRads = split.rotation; normalized.rotationCenterX = 0; normalized.rotationCenterY = 0; normalized.scalingX = split.scaleX; normalized.scalingY = split.scaleY; normalized.scalingCenterX = 0; normalized.scalingCenterY = 0; normalized.translationX = split.translateX; normalized.translationY = split.translateY; } for (name in batchedChanges) { val = batchedChanges[name]; if (typeof val === "undefined") { continue; } else if (Ext.isArray(val)) { if (name in aliases) { name = aliases[name]; } if (name in processors) { normalized[name] = []; for (i = 0, ln = val.length; i < ln; i++) { subVal = processors[name].call(this, val[i]); if (typeof subVal !== "undefined") { normalized[name][i] = subVal; } } } else if (keepUnrecognized) { normalized[name] = val; } } else { if (name in aliases) { name = aliases[name]; } if (name in processors) { val = processors[name].call(this, val); if (typeof val !== "undefined") { normalized[name] = val; } } else if (keepUnrecognized) { normalized[name] = val; } } } return normalized; }, /** * Normalizes the changes given via their processors before they are applied as attributes. * * @param {Object} changes The changes given. * @param {Boolean} keepUnrecognized If 'true', unknown attributes will be passed through * as normalized values. * @return {Object} The normalized values. */ normalize: function (changes, keepUnrecognized) { if (!changes) { return {}; } // eslint-disable-next-line vars-on-top var processors = this.getProcessors(), aliases = this.getAliases(), translation = changes.translation || changes.translate, normalized = {}, name, val, rotation, scaling, matrix, split; if ("rotation" in changes) { rotation = changes.rotation; } else { rotation = "rotate" in changes ? changes.rotate : undefined; } if ("scaling" in changes) { scaling = changes.scaling; } else { scaling = "scale" in changes ? changes.scale : undefined; } if (translation) { if ("x" in translation) { normalized.translationX = translation.x; } if ("y" in translation) { normalized.translationY = translation.y; } } if (typeof scaling !== "undefined") { if (Ext.isNumber(scaling)) { normalized.scalingX = scaling; normalized.scalingY = scaling; } else { if ("x" in scaling) { normalized.scalingX = scaling.x; } if ("y" in scaling) { normalized.scalingY = scaling.y; } if ("centerX" in scaling) { normalized.scalingCenterX = scaling.centerX; } if ("centerY" in scaling) { normalized.scalingCenterY = scaling.centerY; } } } if (typeof rotation !== "undefined") { if (Ext.isNumber(rotation)) { rotation = Ext.draw.Draw.rad(rotation); normalized.rotationRads = rotation; } else { if ("rads" in rotation) { normalized.rotationRads = rotation.rads; } else if ("degrees" in rotation) { normalized.rotationRads = Ext.draw.Draw.rad(rotation.degrees); } if ("centerX" in rotation) { normalized.rotationCenterX = rotation.centerX; } if ("centerY" in rotation) { normalized.rotationCenterY = rotation.centerY; } } } if ("matrix" in changes) { matrix = Ext.draw.Matrix.create(changes.matrix); split = matrix.split(); // This will NOT update the transformation matrix of a sprite // with the given elements. It will attempt to extract the // individual transformation attributes from the transformation matrix // elements provided. Then the extracted attributes will be used by // the sprite's 'applyTransformations' method to calculate // the transformation matrix of the sprite. // It's not possible to recover all the information from the given // transformation matrix elements. Shearing and centers of rotation // and scaling are not recovered. // Ideally, this should work like sprite.transform([elements], true), // i.e. update the transformation matrix of a sprite directly, // without attempting to update sprite's transformation attributes. // But we are not changing the behavior (just yet) for compatibility // reasons. normalized.matrix = matrix; normalized.rotationRads = split.rotation; normalized.rotationCenterX = 0; normalized.rotationCenterY = 0; normalized.scalingX = split.scaleX; normalized.scalingY = split.scaleY; normalized.scalingCenterX = 0; normalized.scalingCenterY = 0; normalized.translationX = split.translateX; normalized.translationY = split.translateY; } for (name in changes) { val = changes[name]; if (typeof val === "undefined") { continue; } if (name in aliases) { name = aliases[name]; } if (name in processors) { val = processors[name].call(this, val); if (typeof val !== "undefined") { normalized[name] = val; } } else if (keepUnrecognized) { normalized[name] = val; } } return normalized; }, setBypassingNormalization: function (attr, modifierStack, changes) { return modifierStack.pushDown(attr, changes); }, set: function (attr, modifierStack, changes) { changes = this.normalize(changes); return this.setBypassingNormalization(attr, modifierStack, changes); }, }); /** * Ext.draw.Matix is a utility class used to calculate * [affine transformation](http://en.wikipedia.org/wiki/Affine_transformation) matrix. * The matrix class is used to apply transformations to existing * {@link Ext.draw.sprite.Sprite sprites} using a number of convenience transform * methods. * * Transformations configured directly on a sprite are processed in the following order: * scaling, rotation, and translation. The matrix class offers additional flexibility. * Once a sprite is created, you can use the matrix class's transform methods as many * times as needed and in any order you choose. * * To demonstrate, we'll start with a simple {@link Ext.draw.sprite.Rect rect} sprite * with the intent of rotating it 180 degrees with the bottom right corner being the * center of rotation. To begin, let's look at the initial, untransformed sprite: * * @example * var drawContainer = new Ext.draw.Container({ * renderTo: Ext.getBody(), * width: 380, * height: 380, * sprites: [{ * type: 'rect', * width: 100, * height: 100, * fillStyle: 'red' * }] * }); * * Next, we'll use the {@link #rotate} and {@link #translate} methods from our matrix * class to position the rect sprite. * * @example * var drawContainer = new Ext.draw.Container({ * renderTo: Ext.getBody(), * width: 380, * height: 380, * sprites: [{ * type: 'rect', * width: 100, * height: 100, * fillStyle: 'red' * }] * }); * * var main = drawContainer.getSurface(); * var rect = main.getItems()[0]; * * var m = new Ext.draw.Matrix().translate(100, 100). * rotate(Math.PI). * translate(-100, - 100); * * rect.setTransform(m); * main.renderFrame(); * * In the previous example we perform the following steps in order to achieve our * desired rotated output: * * - translate the rect to the right and down by 100 * - rotate by 180 degrees * - translate the rect to the right and down by 100 * * **Note:** A couple of things to note at this stage; 1) the rotation center point is * the upper left corner of the sprite by default and 2) with transformations, the * sprite itself isn't transformed, but rather the entire coordinate plane of the sprite * is transformed. The coordinate plane itself is translated by 100 and then rotated * 180 degrees. And that is why in the third step we translate the sprite using * negative values. Translating by -100 in the third step results in the sprite * visually moving to the right and down within the draw container. * * Fortunately there is a shortcut we can apply using two optional params of the rotate * method allowing us to specify the center point of rotation: * * @example * var drawContainer = new Ext.draw.Container({ * renderTo: Ext.getBody(), * width: 380, * height: 380, * sprites: [{ * type: 'rect', * width: 100, * height: 100, * fillStyle: 'red' * }] * }); * * var main = drawContainer.getSurface(); * var rect = main.getItems()[0]; * * var m = new Ext.draw.Matrix().rotate(Math.PI, 100, 100); * * rect.setTransform(m); * main.renderFrame(); * * * This class is compatible with * [SVGMatrix](http://www.w3.org/TR/SVG11/coords.html#InterfaceSVGMatrix) except: * * 1. Ext.draw.Matrix is not read only * 2. Using Number as its values rather than floats * * Using this class helps to reduce the severe numeric * [problem with HTML Canvas and SVG transformation](http://stackoverflow.com/questions