/* 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({
//<debug>
'data-sticky': true,
//</debug>
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);
}
});