@softvisio/ext
Version:
ExtJS patches
1,345 lines (1,337 loc) • 1.39 MB
JavaScript
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