233 lines
7.0 KiB
JavaScript
233 lines
7.0 KiB
JavaScript
/* *
|
|
*
|
|
* (c) 2010-2020 Torstein Honsi
|
|
*
|
|
* License: www.highcharts.com/license
|
|
*
|
|
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
|
|
*
|
|
* */
|
|
'use strict';
|
|
import H from '../parts/Globals.js';
|
|
import U from '../parts/Utilities.js';
|
|
var pick = U.pick;
|
|
// Mathematical Functionility
|
|
var deg2rad = H.deg2rad;
|
|
/* eslint-disable max-len */
|
|
/**
|
|
* Apply 3-D rotation
|
|
* Euler Angles (XYZ):
|
|
* cosA = cos(Alfa|Roll)
|
|
* cosB = cos(Beta|Pitch)
|
|
* cosG = cos(Gamma|Yaw)
|
|
*
|
|
* Composite rotation:
|
|
* | cosB * cosG | cosB * sinG | -sinB |
|
|
* | sinA * sinB * cosG - cosA * sinG | sinA * sinB * sinG + cosA * cosG | sinA * cosB |
|
|
* | cosA * sinB * cosG + sinA * sinG | cosA * sinB * sinG - sinA * cosG | cosA * cosB |
|
|
*
|
|
* Now, Gamma/Yaw is not used (angle=0), so we assume cosG = 1 and sinG = 0, so
|
|
* we get:
|
|
* | cosB | 0 | - sinB |
|
|
* | sinA * sinB | cosA | sinA * cosB |
|
|
* | cosA * sinB | - sinA | cosA * cosB |
|
|
*
|
|
* But in browsers, y is reversed, so we get sinA => -sinA. The general result
|
|
* is:
|
|
* | cosB | 0 | - sinB | | x | | px |
|
|
* | - sinA * sinB | cosA | - sinA * cosB | x | y | = | py |
|
|
* | cosA * sinB | sinA | cosA * cosB | | z | | pz |
|
|
*
|
|
* @private
|
|
* @function rotate3D
|
|
*/
|
|
/* eslint-enable max-len */
|
|
/**
|
|
* @private
|
|
* @param {number} x
|
|
* X coordinate
|
|
* @param {number} y
|
|
* Y coordinate
|
|
* @param {number} z
|
|
* Z coordinate
|
|
* @param {Highcharts.Rotation3dObject} angles
|
|
* Rotation angles
|
|
* @return {Highcharts.Rotation3dObject}
|
|
* Rotated position
|
|
*/
|
|
function rotate3D(x, y, z, angles) {
|
|
return {
|
|
x: angles.cosB * x - angles.sinB * z,
|
|
y: -angles.sinA * angles.sinB * x + angles.cosA * y -
|
|
angles.cosB * angles.sinA * z,
|
|
z: angles.cosA * angles.sinB * x + angles.sinA * y +
|
|
angles.cosA * angles.cosB * z
|
|
};
|
|
}
|
|
/**
|
|
* Perspective3D function is available in global Highcharts scope because is
|
|
* needed also outside of perspective() function (#8042).
|
|
* @private
|
|
* @function Highcharts.perspective3D
|
|
*
|
|
* @param {Highcharts.Position3dObject} coordinate
|
|
* 3D position
|
|
*
|
|
* @param {Highcharts.Position3dObject} origin
|
|
* 3D root position
|
|
*
|
|
* @param {number} distance
|
|
* Perspective distance
|
|
*
|
|
* @return {Highcharts.PositionObject}
|
|
* Perspective 3D Position
|
|
*
|
|
* @requires highcharts-3d
|
|
*/
|
|
H.perspective3D = function (coordinate, origin, distance) {
|
|
var projection = ((distance > 0) && (distance < Number.POSITIVE_INFINITY)) ?
|
|
distance / (coordinate.z + origin.z + distance) :
|
|
1;
|
|
return {
|
|
x: coordinate.x * projection,
|
|
y: coordinate.y * projection
|
|
};
|
|
};
|
|
/**
|
|
* Transforms a given array of points according to the angles in chart.options.
|
|
*
|
|
* @private
|
|
* @function Highcharts.perspective
|
|
*
|
|
* @param {Array<Highcharts.Position3dObject>} points
|
|
* The array of points
|
|
*
|
|
* @param {Highcharts.Chart} chart
|
|
* The chart
|
|
*
|
|
* @param {boolean} [insidePlotArea]
|
|
* Whether to verifiy that the points are inside the plotArea
|
|
*
|
|
* @param {boolean} [useInvertedPersp]
|
|
* Whether to use inverted perspective in calculations
|
|
*
|
|
* @return {Array<Highcharts.Position3dObject>}
|
|
* An array of transformed points
|
|
*
|
|
* @requires highcharts-3d
|
|
*/
|
|
H.perspective = function (points, chart, insidePlotArea, useInvertedPersp) {
|
|
var options3d = chart.options.chart.options3d,
|
|
/* The useInvertedPersp argument is used for
|
|
* inverted charts with already inverted elements,
|
|
* such as dataLabels or tooltip positions.
|
|
*/
|
|
inverted = pick(useInvertedPersp, insidePlotArea ? chart.inverted : false), origin = {
|
|
x: chart.plotWidth / 2,
|
|
y: chart.plotHeight / 2,
|
|
z: options3d.depth / 2,
|
|
vd: pick(options3d.depth, 1) * pick(options3d.viewDistance, 0)
|
|
}, scale = chart.scale3d || 1, beta = deg2rad * options3d.beta * (inverted ? -1 : 1), alpha = deg2rad * options3d.alpha * (inverted ? -1 : 1), angles = {
|
|
cosA: Math.cos(alpha),
|
|
cosB: Math.cos(-beta),
|
|
sinA: Math.sin(alpha),
|
|
sinB: Math.sin(-beta)
|
|
};
|
|
if (!insidePlotArea) {
|
|
origin.x += chart.plotLeft;
|
|
origin.y += chart.plotTop;
|
|
}
|
|
// Transform each point
|
|
return points.map(function (point) {
|
|
var rotated = rotate3D((inverted ? point.y : point.x) - origin.x, (inverted ? point.x : point.y) - origin.y, (point.z || 0) - origin.z, angles),
|
|
// Apply perspective
|
|
coordinate = H.perspective3D(rotated, origin, origin.vd);
|
|
// Apply translation
|
|
coordinate.x = coordinate.x * scale + origin.x;
|
|
coordinate.y = coordinate.y * scale + origin.y;
|
|
coordinate.z = rotated.z * scale + origin.z;
|
|
return {
|
|
x: (inverted ? coordinate.y : coordinate.x),
|
|
y: (inverted ? coordinate.x : coordinate.y),
|
|
z: coordinate.z
|
|
};
|
|
});
|
|
};
|
|
/**
|
|
* Calculate a distance from camera to points - made for calculating zIndex of
|
|
* scatter points.
|
|
*
|
|
* @private
|
|
* @function Highcharts.pointCameraDistance
|
|
*
|
|
* @param {Highcharts.Dictionary<number>} coordinates
|
|
* Coordinates of the specific point
|
|
*
|
|
* @param {Highcharts.Chart} chart
|
|
* Related chart
|
|
*
|
|
* @return {number}
|
|
* Distance from camera to point
|
|
*
|
|
* @requires highcharts-3d
|
|
*/
|
|
H.pointCameraDistance = function (coordinates, chart) {
|
|
var options3d = chart.options.chart.options3d, cameraPosition = {
|
|
x: chart.plotWidth / 2,
|
|
y: chart.plotHeight / 2,
|
|
z: pick(options3d.depth, 1) * pick(options3d.viewDistance, 0) +
|
|
options3d.depth
|
|
},
|
|
// Added support for objects with plotX or x coordinates.
|
|
distance = Math.sqrt(Math.pow(cameraPosition.x - pick(coordinates.plotX, coordinates.x), 2) +
|
|
Math.pow(cameraPosition.y - pick(coordinates.plotY, coordinates.y), 2) +
|
|
Math.pow(cameraPosition.z - pick(coordinates.plotZ, coordinates.z), 2));
|
|
return distance;
|
|
};
|
|
/**
|
|
* Calculate area of a 2D polygon using Shoelace algorithm
|
|
* https://en.wikipedia.org/wiki/Shoelace_formula
|
|
*
|
|
* @private
|
|
* @function Highcharts.shapeArea
|
|
*
|
|
* @param {Array<Highcharts.PositionObject>} vertexes
|
|
* 2D Polygon
|
|
*
|
|
* @return {number}
|
|
* Calculated area
|
|
*
|
|
* @requires highcharts-3d
|
|
*/
|
|
H.shapeArea = function (vertexes) {
|
|
var area = 0, i, j;
|
|
for (i = 0; i < vertexes.length; i++) {
|
|
j = (i + 1) % vertexes.length;
|
|
area += vertexes[i].x * vertexes[j].y - vertexes[j].x * vertexes[i].y;
|
|
}
|
|
return area / 2;
|
|
};
|
|
/**
|
|
* Calculate area of a 3D polygon after perspective projection
|
|
*
|
|
* @private
|
|
* @function Highcharts.shapeArea3d
|
|
*
|
|
* @param {Array<Highcharts.Position3dObject>} vertexes
|
|
* 3D Polygon
|
|
*
|
|
* @param {Highcharts.Chart} chart
|
|
* Related chart
|
|
*
|
|
* @param {boolean} [insidePlotArea]
|
|
* Whether to verifiy that the points are inside the plotArea
|
|
*
|
|
* @return {number}
|
|
* Calculated area
|
|
*
|
|
* @requires highcharts-3d
|
|
*/
|
|
H.shapeArea3d = function (vertexes, chart, insidePlotArea) {
|
|
return H.shapeArea(H.perspective(vertexes, chart, insidePlotArea));
|
|
};
|