@syncfusion/ej2-charts
Version:
Feature-rich chart control with built-in support for over 25 chart types, technical indictors, trendline, zooming, tooltip, selection, crosshair and trackball.
1,244 lines • 90 kB
JavaScript
var __assign = (this && this.__assign) || function () {
__assign = Object.assign || function(t) {
for (var s, i = 1, n = arguments.length; i < n; i++) {
s = arguments[i];
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
t[p] = s[p];
}
return t;
};
return __assign.apply(this, arguments);
};
import { measureText } from '@syncfusion/ej2-svg-base';
import { appendChildElement, colorNameToHex } from '../../common/utils/helper';
import { isNullOrUndefined } from '@syncfusion/ej2-base';
/**
* Represents a 3D rendering configuration for the EJ3D rendering engine.
*
*/
var Chart3DRender = /** @class */ (function () {
function Chart3DRender() {
this.transform = null; // Chart3DBasicTransform
}
return Chart3DRender;
}());
export { Chart3DRender };
var chart3DRender = new Chart3DRender();
/**
* Represents a three-dimensional vector in space.
*/
var Vector3D = /** @class */ (function () {
/**
* Constructs a new Vector3D instance.
*
* @constructor
* @param {number | { x: number, y: number }} pointX - Either an object with x and y properties or the x-coordinate.
* @param {number} [vy] - The y-coordinate (if the first parameter is a number).
* @param {number} [vz] - The z-coordinate (if the first parameter is a number).
*/
function Vector3D(pointX, vy, vz) {
/** The x-coordinate of the vector. */
this.x = 0;
/** The y-coordinate of the vector. */
this.y = 0;
/** The z-coordinate of the vector. */
this.z = 0;
/** A small value used for epsilon comparisons to handle floating-point inaccuracies.*/
this.epsilon = 0.00001;
this.x = pointX;
this.y = vy || 0;
this.z = vz || 0;
}
/**
* Checks if a vector is valid (not NaN for any component).
*
* @param {Chart3DVector} point - The vector to check.
* @returns {boolean} - True if the vector is valid, false otherwise.
*/
Vector3D.prototype.isValid = function (point) {
return !isNaN(point.x) && !isNaN(point.y) && !isNaN(point.z);
};
/**
* Creates a new Vector3D instance from provided coordinates.
*
* @param {number | { x: number, y: number }} vx - Either an object with x and y properties or the x-coordinate.
* @param {number} vy - The y-coordinate.
* @param {number} vz - The z-coordinate.
* @returns {Chart3DVector} - The new Vector3D instance.
*/
Vector3D.prototype.vector3D = function (vx, vy, vz) {
this.x = vx;
this.y = vy;
this.z = vz;
return { x: this.x, y: this.y, z: this.z };
};
/**
* Subtracts one vector from another and returns the result.
*
* @param {Chart3DVector} v1 - The first vector.
* @param {Chart3DVector} v2 - The second vector to subtract from the first.
* @returns {Chart3DVector} - The resulting vector.
*/
Vector3D.prototype.vector3DMinus = function (v1, v2) {
return this.vector3D(v1.x - v2.x, v1.y - v2.y, v1.z - v2.z);
};
/**
* Adds two vectors and returns the result.
*
* @param {Chart3DVector} v1 - The first vector.
* @param {Chart3DVector} v2 - The second vector to add to the first.
* @returns {Chart3DVector} - The resulting vector.
*/
Vector3D.prototype.vector3DPlus = function (v1, v2) {
return this.vector3D(v1.x + v2.x, v1.y + v2.y, v1.z + v2.z);
};
/**
* Multiplies two vectors using the cross product and returns the result.
*
* @param {Chart3DVector} v1 - The first vector.
* @param {Chart3DVector} v2 - The second vector.
* @returns {Chart3DVector} - The resulting vector.
*/
Vector3D.prototype.vector3DMultiply = function (v1, v2) {
var x = v1.y * v2.z - v2.y * v1.z;
var y = v1.z * v2.x - v2.z * v1.x;
var z = v1.x * v2.y - v2.x * v1.y;
return this.vector3D(x, y, z);
};
/**
* Calculates the dot product of two vectors.
*
* @param {Chart3DVector} v1 - The first vector.
* @param {Chart3DVector} v2 - The second vector.
* @returns {number} - The dot product.
*/
Vector3D.prototype.vector3DAdd = function (v1, v2) {
return v1.x * v2.x + v1.y * v2.y + v1.z * v2.z;
};
/**
* Multiplies a vector by a scalar value.
*
* @param {Chart3DVector} v1 - The vector to multiply.
* @param {number} value - The scalar value.
* @returns {Chart3DVector} - The resulting vector.
*/
Vector3D.prototype.vector3DStarMultiply = function (v1, value) {
var x = v1.x * value;
var y = v1.y * value;
var z = v1.z * value;
return this.vector3D(x, y, z);
};
/**
* Calculates the length of a vector.
*
* @param {Chart3DVector} vector - The vector to calculate the length of.
* @returns {number} - The length of the vector.
*/
Vector3D.prototype.getLength = function (vector) {
var sqt = this.vector3DAdd(vector, vector);
return Math.sqrt(sqt);
};
/**
* Normalizes the vector to have a length of 1.
*
* @returns {void}
*/
Vector3D.prototype.normalize = function () {
var length = this.getLength(this);
this.x /= length;
this.y /= length;
this.z /= length;
};
/**
* Calculates the normal vector of a triangle defined by three vectors.
*
* @param {Chart3DVector} v1 - The first vertex of the triangle.
* @param {Chart3DVector} v2 - The second vertex of the triangle.
* @param {Chart3DVector} v3 - The third vertex of the triangle.
* @returns {Chart3DVector} - The normal vector of the triangle.
*/
Vector3D.prototype.getNormal = function (v1, v2, v3) {
var vector4 = this.vector3DMinus(v1, v2);
var vector5 = this.vector3DMinus(v3, v2);
var n = this.vector3DMultiply(vector4, vector5);
var length = this.getLength(n);
if (length < this.epsilon) {
return this.vector3D(0, 0, 0);
}
return this.vector3D(n.x / length, n.y / length, n.z / length);
};
return Vector3D;
}());
export { Vector3D };
/**
* Represents a 3x3 or 4x4 matrix in 3D space and provides various matrix operations.
*
*/
var Matrix3D = /** @class */ (function () {
function Matrix3D() {
/** The size of the matrix, which is set to 4 by default. */
this.matrixSize = 4;
}
/**
* Creates a 3D matrix with the specified size.
*
* @param {number} size - The size of the matrix.
* @returns {number[][]} - The created 3D matrix.
*/
Matrix3D.prototype.matrix3D = function (size) {
var matrixData = [];
for (var i = 0; i < size; i++) {
matrixData[i] = this.createArray(size);
}
return matrixData;
};
/**
* Checks if a matrix is an affine matrix.
*
* @param {number[][]} matrixData - The matrix to check.
* @returns {boolean} - True if the matrix is an affine matrix, false otherwise.
*/
Matrix3D.prototype.isAffine = function (matrixData) {
return matrixData[0][3] === 0 && matrixData[1][3] === 0 && matrixData[2][3] === 0 && matrixData[3][3] === 1;
};
/**
* Creates a new array with zeros.
*
* @param {number} initialSize - The size of the array.
* @returns {number[]} - The created array.
*/
Matrix3D.prototype.createArray = function (initialSize) {
var matrixData = [];
for (var index = 0; index < initialSize; ++index) {
matrixData[index] = 0;
}
return matrixData;
};
/**
* Gets the identity matrix.
*
* @returns {number[][]} -The identity matrix.
*/
Matrix3D.prototype.getIdentity = function () {
var matrixData = this.matrix3D(this.matrixSize);
for (var i = 0; i < this.matrixSize; i++) {
matrixData[i][i] = 1.0;
}
return matrixData;
};
/**
* Gets the interval of a matrix.
*
* @param {number[][]} matrix - The matrix to get the interval for.
* @returns {number[][]} - The interval matrix.
*/
Matrix3D.prototype.getInterval = function (matrix) {
var matrixData = this.getIdentity();
for (var i = 0; i < this.matrixSize; i++) {
for (var j = 0; j < this.matrixSize; j++) {
matrixData[i][j] = this.getMinor(matrix, i, j);
}
}
matrixData = this.transposed(matrixData);
matrixData = this.getMatrixMultiple(1 / this.getDeterminant(matrix), matrixData);
return matrixData;
};
/**
* Multiplies all elements of a matrix by a factor.
*
* @param {number} factor - The factor to multiply with.
* @param {number[][]} matrix - The matrix to multiply.
* @returns {number[][]} - The resulting matrix.
*/
Matrix3D.prototype.getMatrixMultiple = function (factor, matrix) {
for (var i = 0; i < matrix.length; i++) {
for (var j = 0; j < matrix[i].length; j++) {
matrix[i][j] = matrix[i][j] * factor;
}
}
return matrix;
};
/**
* Multiplies a matrix by a vector.
*
* @param {number[][]} matrix - The matrix.
* @param {Chart3DVector} point - The vector to multiply with.
* @returns {Chart3DVector} - The resulting vector.
*/
Matrix3D.prototype.getMatrixVectorMultiple = function (matrix, point) {
var x = matrix[0][0] * point.x +
matrix[1][0] * point.y +
matrix[2][0] * point.z +
matrix[3][0];
var y = matrix[0][1] * point.x +
matrix[1][1] * point.y +
matrix[2][1] * point.z +
matrix[3][1];
var z = matrix[0][2] * point.x +
matrix[1][2] * point.y +
matrix[2][2] * point.z +
matrix[3][2];
if (!this.isAffine(matrix)) {
var c = 1 / (matrix[0][3] * point.x + matrix[1][3] * point.y + matrix[2][3] * point.z + matrix[3][3]);
x *= c;
y *= c;
z *= c;
}
return { x: x, y: y, z: z };
};
/**
* Multiplies a matrix by a vector and applies translation.
*
* @param {number[][]} matrix - The matrix.
* @param {Chart3DVector} vector - The vector to multiply with.
* @returns {Vector3D} - The resulting vector.
*/
Matrix3D.prototype.getMatrixVectorAnd = function (matrix, vector) {
var x = matrix[0][0] * vector.x +
matrix[1][0] * vector.y +
matrix[2][0] * vector.z;
var y = matrix[0][1] * vector.x +
matrix[1][1] * vector.y +
matrix[2][1] * vector.z;
var z = matrix[0][2] * vector.x +
matrix[1][2] * vector.y +
matrix[2][2] * vector.z;
return new Vector3D(x, y, z);
};
/**
* Multiplies two matrices.
*
* @param {number[][]} matrix1 - The first matrix.
* @param {number[][]} matrix2 - The second matrix.
* @returns {number[][]} - The resulting matrix.
*/
Matrix3D.prototype.getMatrixMultiplication = function (matrix1, matrix2) {
var result = this.getIdentity();
for (var i = 0; i < this.matrixSize; i++) {
for (var j = 0; j < this.matrixSize; j++) {
var value = 0;
for (var k = 0; k < this.matrixSize; k++) {
value += matrix1[k][j] * matrix2[i][k];
}
result[i][j] = value;
}
}
return result;
};
/**
* Gets the minor of a matrix.
*
* @param {number[][]} matrix - The matrix.
* @param {number} columnIndex - The column index.
* @param {number} rowIndex - The row index.
* @returns {number} - The minor of the matrix.
* @private
*/
Matrix3D.prototype.getMinor = function (matrix, columnIndex, rowIndex) {
return ((columnIndex + rowIndex) % 2 === 0 ? 1 : -1) * this.getDeterminant(this.getMatrix(matrix, columnIndex, rowIndex));
};
/**
* Gets a submatrix of a matrix.
*
* @param {number[][]} matrix - The matrix.
* @param {number} columnIndex - The column index.
* @param {number} rowIndex - The row index.
* @returns {number[][]} - The submatrix.
*/
Matrix3D.prototype.getMatrix = function (matrix, columnIndex, rowIndex) {
var count = matrix.length - 1;
var subMatrix = this.createArray(count);
for (var i = 0; i < count; i++) {
var matrixColumn = i >= columnIndex ? i + 1 : i;
subMatrix[i] = this.createArray(count);
for (var j = 0; j < count; j++) {
var matrixRow = j >= rowIndex ? j + 1 : j;
subMatrix[i][j] = matrix[matrixColumn][matrixRow];
}
}
return subMatrix;
};
/**
* Gets the determinant of a matrix.
*
* @param {number[][]} matrix - The matrix.
* @returns {number} - The determinant of the matrix.
*/
Matrix3D.prototype.getDeterminant = function (matrix) {
var count = matrix.length;
var determinant = 0;
if (count < 2) {
determinant = matrix[0][0];
}
else {
var k = 1;
for (var i = 0; i < count; i++) {
var submatrix = this.getMatrix(matrix, i, 0);
determinant += k * matrix[i][0] * this.getDeterminant(submatrix);
k = k > 0 ? -1 : 1;
}
}
return determinant;
};
/**
* Transforms a matrix by translation.
*
* @param {number} x - The x-coordinate of the translation.
* @param {number} y - The y-coordinate of the translation.
* @param {number} z - The z-coordinate of the translation.
* @returns {number[][]} - The transformed matrix.
*/
Matrix3D.prototype.transform = function (x, y, z) {
var transformedMatrix = this.getIdentity();
transformedMatrix[3][0] = x;
transformedMatrix[3][1] = y;
transformedMatrix[3][2] = z;
return transformedMatrix;
};
/**
* Creates a matrix for rotation around the y-axis.
*
* @param {number} angle - The angle of rotation.
* @returns {number[][]} - The rotation matrix.
*/
Matrix3D.prototype.turn = function (angle) {
var rotatedMatrix = this.getIdentity();
rotatedMatrix[0][0] = Math.cos(angle);
rotatedMatrix[2][0] = -Math.sin(angle);
rotatedMatrix[0][2] = Math.sin(angle);
rotatedMatrix[2][2] = Math.cos(angle);
return rotatedMatrix;
};
/**
* Creates a matrix for rotation around the x-axis.
*
* @param {number} angle - The angle of rotation.
* @returns {number[][]} - The rotation matrix.
*/
Matrix3D.prototype.tilt = function (angle) {
var rotatedMatrix = this.getIdentity();
rotatedMatrix[1][1] = Math.cos(angle);
rotatedMatrix[2][1] = Math.sin(angle);
rotatedMatrix[1][2] = -Math.sin(angle);
rotatedMatrix[2][2] = Math.cos(angle);
return rotatedMatrix;
};
/**
* Transposes a matrix.
*
* @param {number[][]} matrix3D - The matrix to transpose.
* @returns {number[][]} - The transposed matrix.
*/
Matrix3D.prototype.transposed = function (matrix3D) {
var transposedMatrix = this.getIdentity();
for (var i = 0; i < this.matrixSize; i++) {
for (var j = 0; j < this.matrixSize; j++) {
transposedMatrix[i][j] = matrix3D[j][i];
}
}
return transposedMatrix;
};
return Matrix3D;
}());
export { Matrix3D };
/**
* Represents a 3D chart transformation utility that provides methods for transforming
* and projecting 3D coordinates onto a 2D screen.
*
*/
var ChartTransform3D = /** @class */ (function () {
/**
* Initializes a new instance of the `ChartTransform3D` class.
*/
function ChartTransform3D() {
/** Represents the angle conversion factor from degrees to radians. */
this.toRadial = Math.PI / 180;
this.vector = new Vector3D(0, 0, 0);
this.matrixObj = new Matrix3D();
}
/**
* Creates a 3D transformation based on the specified size.
*
* @param {Size} size - The size of the viewing area.
* @returns {Chart3DBasicTransform} - The 3D transformation.
*/
ChartTransform3D.prototype.transform3D = function (size) {
return {
viewingArea: size,
rotation: 0,
tilt: 0,
depth: 0,
perspectiveAngle: 0,
needUpdate: true,
centeredMatrix: this.matrixObj.getIdentity(),
perspective: this.matrixObj.getIdentity(),
resultMatrix: this.matrixObj.getIdentity(),
viewMatrix: this.matrixObj.getIdentity()
};
};
/**
* Applies the specified 3D transformation to the current state.
*
* @param {Chart3DBasicTransform} transform - The 3D transformation to apply.
* @returns {void} - The 3D transformation.
*/
ChartTransform3D.prototype.transform = function (transform) {
this.setCenter(this.vector.vector3D(transform.viewingArea.width / 2, transform.viewingArea.height / 2, transform.depth / 2), transform);
this.setViewMatrix(this.matrixObj.transform(0, 0, transform.depth), transform);
this.setViewMatrix(this.matrixObj.getMatrixMultiplication(transform.viewMatrix, this.matrixObj.turn(-this.toRadial * transform.rotation)), transform);
this.setViewMatrix(this.matrixObj.getMatrixMultiplication(transform.viewMatrix, this.matrixObj.tilt(-this.toRadial * transform.tilt)), transform);
this.updatePerspective(transform.perspectiveAngle, transform);
transform.needUpdate = true;
};
/**
* Updates the perspective matrix based on the specified angle.
*
* @param {number} angle - The perspective angle.
* @param {Chart3DBasicTransform} transform - The 3D transformation.
* @returns {void}
*/
ChartTransform3D.prototype.updatePerspective = function (angle, transform) {
var width = (((transform.viewingArea.width + transform.viewingArea.height) *
Math.tan(this.degreeToRadianConverter((180 - Math.abs(angle % 181)) / 2.0))) + (transform.depth * 2) / 2);
transform.perspective[0][0] = width;
transform.perspective[1][1] = width;
transform.perspective[2][3] = 1;
transform.perspective[3][3] = width;
};
/**
* Converts degrees to radians.
*
* @param {number} angle - The angle in degrees.
* @returns {number} - The angle in radians.
* @private
*/
ChartTransform3D.prototype.degreeToRadianConverter = function (angle) {
return angle * Math.PI / 180;
};
/**
* Transforms a 3D vector to screen coordinates based on the current state.
*
* @param {Chart3DVector} vector3D - The 3D vector to transform.
* @param {Chart3DBasicTransform} transform - The 3D transformation.
* @param {Matrix3D} chartObj - Optional custom matrix object for transformation.
* @returns {Chart3DLocation} - The screen coordinates.
*/
ChartTransform3D.prototype.toScreen = function (vector3D, transform, chartObj) {
if (!chartObj) {
transform.chartObj = this.matrixObj;
vector3D = this.matrixObj.getMatrixVectorMultiple(this.result(transform), vector3D);
}
else {
this.matrixObj = chartObj;
vector3D = chartObj.getMatrixVectorMultiple(this.result(transform, chartObj), vector3D);
}
return { x: vector3D.x, y: vector3D.y };
};
/**
* Sets the view matrix in the transformation state.
*
* @param {number[][]} matrix - The new view matrix.
* @param {Chart3DBasicTransform} transform - The 3D transformation.
* @returns {void}
*/
ChartTransform3D.prototype.setViewMatrix = function (matrix, transform) {
if (transform.viewMatrix === matrix) {
return;
}
transform.viewMatrix = matrix;
transform.needUpdate = true;
};
/**
* Calculates the final result matrix based on the current state.
*
* @param {Chart3DBasicTransform} transform - The 3D transformation.
* @param {Matrix3D} matrixobj - Optional custom matrix object for transformation.
* @returns {number[][]} - The final result matrix.
*/
ChartTransform3D.prototype.result = function (transform, matrixobj) {
var chartObj = transform.chartObj ? transform.chartObj : this.matrixObj;
if (!chartObj) {
chartObj = matrixobj;
}
if (!transform.needUpdate) {
return transform.resultMatrix;
}
var matrixObj = this.matrixObj ? this.matrixObj : matrixobj;
transform.resultMatrix = chartObj.getMatrixMultiplication(matrixObj.getInterval(transform.centeredMatrix), transform.perspective);
transform.resultMatrix = chartObj.getMatrixMultiplication(transform.resultMatrix, transform.viewMatrix);
transform.resultMatrix = chartObj.getMatrixMultiplication(transform.resultMatrix, transform.centeredMatrix);
transform.needUpdate = false;
return transform.resultMatrix;
};
/**
* Sets the center in the transformation state.
*
* @param {Chart3DVector} center - The new center vector.
* @param {Chart3DBasicTransform} transform - The 3D transformation.
* @returns {void}
*/
ChartTransform3D.prototype.setCenter = function (center, transform) {
transform.centeredMatrix = this.matrixObj.transform(-center.x, -center.y, -center.z);
transform.needUpdate = true;
};
return ChartTransform3D;
}());
export { ChartTransform3D };
/**
* Represents a 3D graphics rendering utility for drawing and managing 3D elements in a chart.
*
*/
var Graphics3D = /** @class */ (function () {
function Graphics3D() {
/** The vector class. */
this.vector = new Vector3D(0, 0, 0);
}
/**
* Adds a visual polygon to the 3D chart and returns its identifier.
*
* @param {Chart3DPolygon} polygon - The polygon to add.
* @param {Chart3D} chart - The 3D chart.
* @returns {number} - The identifier of the added polygon.
*/
Graphics3D.prototype.addVisual = function (polygon, chart) {
if (polygon == null || polygonObj.test()) {
return -1;
}
return bspTreeObj.add(polygon, chart);
};
/**
* Prepares the view for rendering based on specified parameters.
*
* @param {number} perspectiveAngle - The perspective angle.
* @param {number} depth - The depth of the view.
* @param {number} rotation - The rotation angle.
* @param {number} tilt - The tilt angle.
* @param {Size} size - The size of the viewing area.
* @param {Chart3D} chart - The 3D chart.
* @returns {void}
*/
Graphics3D.prototype.prepareView = function (perspectiveAngle, depth, rotation, tilt, size, chart) {
if (chart3DRender.transform == null) {
chart3DRender.transform = chart.transform3D.transform3D(size);
}
else {
chart3DRender.transform.viewingArea = size;
}
if (!chart3DRender.tree) {
chart3DRender.tree = [];
}
chart3DRender.transform.rotation = rotation;
chart3DRender.transform.tilt = tilt;
chart3DRender.transform.depth = depth;
chart3DRender.transform.perspectiveAngle = perspectiveAngle;
chart.transform3D.transform(chart3DRender.transform);
chart3DRender.tree[chart.chart3D.id] = bspTreeObj.build();
};
/**
* Renders the 3D view on the specified panel element.
*
* @param {Element} panel - The panel element to render the view on.
* @param {Chart3D} chart - The 3D chart.
* @param {number} rotation - The rotation angle.
* @param {number} tilt - The tilt angle.
* @param {Size} size - The size of the viewing area.
* @param {number} perspectiveAngle - The perspective angle.
* @param {number} depth - The depth of the view.
* @returns {void}
*/
Graphics3D.prototype.view = function (panel, chart, rotation, tilt, size, perspectiveAngle, depth) {
var MaxValue = 32767;
if (arguments.length === 2) {
if (panel == null) {
return;
}
var eyeVector = this.vector.vector3D(0, 0, MaxValue);
this.drawNode3D(chart3DRender.tree[chart.chart3D.id], eyeVector, panel, chart);
}
else {
if (panel == null) {
return;
}
if (chart3DRender.transform == null) {
chart3DRender.transform = chart.transform3D.transform3D(size);
}
else {
chart3DRender.transform.viewingArea = size;
}
chart3DRender.transform.rotation = rotation;
chart3DRender.transform.tilt = tilt;
chart3DRender.transform.depth = depth;
chart3DRender.transform.perspectiveAngle = perspectiveAngle;
chart.transform3D.transform(chart3DRender.transform);
var eye = this.vector.vector3D(0, 0, MaxValue);
this.drawNode3D(chart3DRender.tree[chart.chart3D.id], eye, panel, chart);
}
};
/**
* Draws a 3D element based on the specified Binary Space Partitioning Node.
*
* @param {Chart3DBspNode} bspElement - The Binary Space Partitioning Node representing the 3D element.
* @param {Chart3D} chart - The 3D chart.
* @returns {void}
*/
Graphics3D.prototype.draw3DElement = function (bspElement, chart) {
if (bspElement.plane.element) {
if (bspElement.plane.element.tag === 'text' || bspElement.plane.element.tag === 'dataLabel') {
polygonObj.drawText(bspElement.plane, chart);
}
else if (bspElement.plane.element.tag === 'template') {
polygonObj.drawTemplate(bspElement.plane, chart);
}
else {
polygonObj.drawLine(bspElement.plane, chart);
}
}
else {
polygonObj.draw(bspElement.plane, chart);
}
};
/**
* Draws the 3D nodes starting from the root based on the eye vector.
*
* @param {Chart3DBspNode} bspElement - The root Binary Space Partitioning Node.
* @param {Chart3DVector} eyeVector - The eye vector.
* @param {Element} panel - The panel element to render the view on.
* @param {Chart3D} chart - The 3D chart.
* @returns {void}
*/
Graphics3D.prototype.drawNode3D = function (bspElement, eyeVector, panel, chart) {
if (bspElement === null || chart3DRender.transform == null) {
return;
}
var isVector = true;
while (isVector) {
var r = vector.vector3DAdd(polygonObj.getNormal(chart.transform3D.result(chart3DRender.transform), bspElement.plane.vectorPoints), eyeVector);
if (r > bspElement.plane.d) {
if (bspElement.front != null) {
this.drawNode3D(bspElement.front, eyeVector, panel, chart);
}
this.draw3DElement(bspElement, chart);
if (bspElement.back != null) {
bspElement = bspElement.back;
continue;
}
}
else {
if (bspElement.back != null) {
this.drawNode3D(bspElement.back, eyeVector, panel, chart);
}
this.draw3DElement(bspElement, chart);
if (bspElement.front != null) {
bspElement = bspElement.front;
continue;
}
}
break;
}
};
return Graphics3D;
}());
export { Graphics3D };
/**
* Represents a binary tree builder for 3D polygons in a chart.
*
*/
var BinaryTreeBuilder = /** @class */ (function () {
function BinaryTreeBuilder(chart) {
/** A small value used for epsilon comparisons to handle floating-point inaccuracies.*/
this.epsilon = 0.0005;
this.chart = chart;
}
/**
* Adds a polygon to the binary tree and returns its index.
*
* @param {Chart3DPolygon} polygon - The polygon to add.
* @param {Chart3D} chart - The 3D chart.
* @returns {number} - The index of the added polygon.
*/
BinaryTreeBuilder.prototype.add = function (polygon, chart) {
this.chart = chart;
chart.polygons.push(polygon);
return chart.polygons.length - 1;
};
/**
* Gets the next index considering the array length and the current index.
*
* @param {number} index - The current index.
* @param {number} count - The length of the array.
* @returns {number} - The next index.
*/
BinaryTreeBuilder.prototype.getNext = function (index, count) {
if (index >= count) {
return index - count;
}
if (index < 0) {
return index + count;
}
return index;
};
/**
* Creates a PolyAttributes object based on the vector, index, and result.
*
* @param {Chart3DVector} point - The vector representing the point.
* @param {number} index - The index of the point.
* @param {string} result - The result classification.
* @returns {Chart3DPolyAttributes} - The created PolyAttributes object.
*/
BinaryTreeBuilder.prototype.vector3DIndexClassification = function (point, index, result) {
return {
index: index,
result: result,
vector: point,
isCuttingBackPoint: false,
cuttingBackPairIndex: null,
alreadyCutBack: false,
isCuttingFrontPoint: false,
cuttingFrontPairIndex: null,
alreadyCutFront: false
};
};
/**
* Classifies a point relative to a polygon.
*
* @param {Chart3DVector} point - The point to classify.
* @param {Chart3DPolygon} polygon - The polygon for classification.
* @returns {string} - The classification result ('OnPlane', 'OnBack', 'OnFront').
*/
BinaryTreeBuilder.prototype.classifyPoint = function (point, polygon) {
var result = 'OnPlane';
var signedDistance = -polygon.d - vector.vector3DAdd(point, polygon.normal);
if (signedDistance > this.epsilon) {
result = 'OnBack';
}
else if (signedDistance < -this.epsilon) {
result = 'OnFront';
}
return result;
};
/**
* Classifies a polygon relative to another polygon.
*
* @param {Chart3DPolygon} refPolygon - The reference polygon.
* @param {Chart3DPolygon} classPolygon - The polygon to classify.
* @returns {string} - The classification result ('OnPlane', 'ToRight', 'ToLeft', 'Unknown').
*/
BinaryTreeBuilder.prototype.classifyPolygon = function (refPolygon, classPolygon) {
var result = 'Unknown';
var points = classPolygon.points;
if (points == null) {
return result;
}
var onBack = 0;
var onFront = 0;
var onPlane = 0;
var normal = refPolygon.normal;
var polygonValue = refPolygon.d;
for (var i = 0, len = points.length; i < len; i++) {
var value = -polygonValue - vector.vector3DAdd(points[i], normal);
if (value > this.epsilon) {
onBack++;
}
else if (value < -this.epsilon) {
onFront++;
}
else {
onPlane++;
}
if (onBack > 0 && onFront > 0) {
break;
}
}
if (onPlane === points.length) {
result = 'OnPlane';
}
else if (onFront + onPlane === points.length) {
result = 'ToRight';
}
else if (onBack + onPlane === points.length) {
result = 'ToLeft';
}
else {
result = 'Unknown';
}
return result;
};
/**
* Splits a polygon into two parts based on another polygon.
*
* @param {Chart3DPolygon} splitPolygon - The polygon to split.
* @param {Chart3DPolygon} refPolygon - The reference polygon for splitting.
* @returns {Chart3DPolyCollections} - The resulting back and front parts.
* @private
*/
BinaryTreeBuilder.prototype.splitPolygon = function (splitPolygon, refPolygon) {
var backPoint = [];
var frontPoint = [];
if (splitPolygon.points != null) {
var polyPoints = [];
var backPartPoints = [];
var frontPartPoints = [];
var outputs = void 0;
var inputs = void 0;
var count = splitPolygon.points.length;
for (var i = 0; i < count; i++) {
var pointB = splitPolygon.points[i];
var pointC = splitPolygon.points[this.getNext(i + 1, count)];
var sideB = this.classifyPoint(pointB, refPolygon);
var sideC = this.classifyPoint(pointC, refPolygon);
var attributeB = this.vector3DIndexClassification(pointB, polyPoints.length, sideB);
polyPoints.push(attributeB);
if (sideB !== sideC && sideB !== 'OnPlane' && sideC !== 'OnPlane') {
var vectorValue = vector.vector3DMinus(pointB, pointC);
var direction = vector.vector3DMinus(vector.vector3DStarMultiply(refPolygon.normal, -refPolygon.d), pointC);
var signedDistance = vector.vector3DAdd(direction, refPolygon.normal);
var intersectionParameter = signedDistance / vector.vector3DAdd(refPolygon.normal, vectorValue);
var intersectionPoint = vector.vector3DPlus(pointC, vector.vector3DStarMultiply(vectorValue, intersectionParameter));
var attributeIntersection = this.vector3DIndexClassification(intersectionPoint, polyPoints.length, 'OnPlane');
polyPoints.push(attributeIntersection);
backPartPoints.push(attributeIntersection);
frontPartPoints.push(attributeIntersection);
}
else if (sideB === 'OnPlane') {
var pointA = splitPolygon.points[this.getNext(i - 1, count)];
var sideA = this.classifyPoint(pointA, refPolygon);
if (sideA === sideC) {
continue;
}
if (sideA !== 'OnPlane' && sideC !== 'OnPlane') {
backPartPoints.push(attributeB);
frontPartPoints.push(attributeB);
}
else if (sideA === 'OnPlane') {
switch (sideC) {
case 'OnBack':
backPartPoints.push(attributeB);
break;
case 'OnFront':
frontPartPoints.push(attributeB);
break;
}
}
else if (sideC === 'OnPlane') {
switch (sideA) {
case 'OnBack':
backPartPoints.push(attributeB);
break;
case 'OnFront':
frontPartPoints.push(attributeB);
break;
}
}
}
}
if (frontPartPoints.length !== 0 || backPartPoints.length !== 0) {
for (var i = 0; i < backPartPoints.length - 1; i += 2) {
var backAttribute1 = backPartPoints[i];
var backAttribute2 = backPartPoints[i + 1];
backAttribute1.cuttingBackPoint = true;
backAttribute2.cuttingBackPoint = true;
backAttribute1.alterCuttingBackPairIndex = backAttribute2.index;
backAttribute2.alterCuttingBackPairIndex = backAttribute1.index;
}
for (var i = 0; i < frontPartPoints.length - 1; i += 2) {
var frontAttribute1 = frontPartPoints[i];
var frontAttribute2 = frontPartPoints[i + 1];
frontAttribute1.cuttingFrontPoint = true;
frontAttribute2.cuttingFrontPoint = true;
frontAttribute1.alterCuttingFrontPairIndex = frontAttribute2.index;
frontAttribute2.alterCuttingFrontPairIndex = frontAttribute1.index;
}
for (var i = 0; i < backPartPoints.length - 1; i++) {
var backAttribute1 = backPartPoints[i];
if (backAttribute1.alreadyCutBack) {
continue;
}
outputs = this.cutOutBackPolygon(polyPoints, backAttribute1);
if (outputs.length > 2) {
var polygon1 = polygonObj.polygon3D(outputs, splitPolygon);
backPoint.push(__assign({}, polygon1));
}
}
for (var i = 0; i < frontPartPoints.length - 1; i++) {
var backAttribute2 = frontPartPoints[i];
if (backAttribute2.alreadyCutFront) {
continue;
}
inputs = this.cutOutFrontPolygon(polyPoints, backAttribute2);
if (inputs.length > 2) {
var polygon2 = polygonObj.polygon3D(inputs, splitPolygon);
frontPoint.push(__assign({}, polygon2));
}
}
}
}
else {
backPoint.push(splitPolygon);
frontPoint.push(splitPolygon);
}
return { backPolygon: backPoint, frontPolygon: frontPoint };
};
/**
* Cuts out the front part of a polygon based on the PolyAttributes.
*
* @param {Chart3DPolyAttributes[]} polyPoints - The PolyAttributes array of the polygon.
* @param {Chart3DPolyAttributes} initialVertex - The PolyAttributes representing the cutting point.
* @returns {Chart3DVector[]} - The resulting points of the front part.
*/
BinaryTreeBuilder.prototype.cutOutFrontPolygon = function (polyPoints, initialVertex) {
var points = [];
var currentVertex = initialVertex;
var isVector = true;
while (isVector) {
currentVertex.alreadyCutFront = true;
points.push(currentVertex.vector);
var currentVertexPair = polyPoints[currentVertex.alterCuttingFrontPairIndex];
if (currentVertex.cuttingFrontPoint) {
if (!currentVertexPair.alreadyCutFront) {
currentVertex = currentVertexPair;
}
else {
var previousVertexOnBack = polyPoints[this.getNext(currentVertex.index - 1, polyPoints.length)];
var nextVertexOnBack = polyPoints[this.getNext(currentVertex.index + 1, polyPoints.length)];
if (previousVertexOnBack.result === 'OnFront' && !previousVertexOnBack.alreadyCutFront) {
currentVertex = previousVertexOnBack;
}
else if (nextVertexOnBack.result === 'OnFront' && !nextVertexOnBack.alreadyCutFront) {
currentVertex = nextVertexOnBack;
}
else {
return points;
}
}
}
else {
var previousVertexOnBack = polyPoints[this.getNext(currentVertex.index - 1, polyPoints.length)];
var nextVertexOnBack = polyPoints[this.getNext(currentVertex.index + 1, polyPoints.length)];
if (previousVertexOnBack.result !== 'OnBack' && !previousVertexOnBack.alreadyCutFront) {
currentVertex = previousVertexOnBack;
}
else if (nextVertexOnBack.result !== 'OnBack' && !nextVertexOnBack.alreadyCutFront) {
currentVertex = nextVertexOnBack;
}
else {
return points;
}
}
}
return null;
};
/**
* Cuts out the back part of a polygon based on the PolyAttributes.
*
* @param {Chart3DPolyAttributes[]} polyPoints - The PolyAttributes array of the polygon.
* @param {Chart3DPolyAttributes} initialVertex - The PolyAttributes representing the cutting point.
* @returns {Chart3DVector[]} - The resulting points of the back part.
*/
BinaryTreeBuilder.prototype.cutOutBackPolygon = function (polyPoints, initialVertex) {
var points = [];
var currentVertex = initialVertex;
var isVector = true;
while (isVector) {
currentVertex.alreadyCutBack = true;
points.push(currentVertex.vector);
var currentVertexPair = polyPoints[currentVertex.alterCuttingBackPairIndex];
if (currentVertex.cuttingBackPoint) {
if (!currentVertexPair.alreadyCutBack) {
currentVertex = currentVertexPair;
}
else {
var previousVertexOnBack = polyPoints[this.getNext(currentVertex.index - 1, polyPoints.length)];
var nextVertexOnBack = polyPoints[this.getNext(currentVertex.index + 1, polyPoints.length)];
if (previousVertexOnBack.result === 'OnBack' && !previousVertexOnBack.alreadyCutBack) {
currentVertex = previousVertexOnBack;
}
else if (nextVertexOnBack.result === 'OnBack' && !nextVertexOnBack.alreadyCutBack) {
currentVertex = nextVertexOnBack;
}
else {
return points;
}
}
}
else {
var previousVertexOnBack = polyPoints[this.getNext(currentVertex.index - 1, polyPoints.length)];
var nextVertexOnBack = polyPoints[this.getNext(currentVertex.index + 1, polyPoints.length)];
if (previousVertexOnBack.result !== 'OnFront' && !previousVertexOnBack.alreadyCutBack) {
currentVertex = previousVertexOnBack;
}
else if (nextVertexOnBack.result !== 'OnFront' && !nextVertexOnBack.alreadyCutBack) {
currentVertex = nextVertexOnBack;
}
else {
return points;
}
}
}
return null;
};
/**
* Builds a Binary Space Partitioning from a list of polygons.
*
* @param {Chart3DPolygon[]} [points] - The list of polygons to build the tree from.
* @returns {Chart3DBspNode} - The root node of the Binary Space Partitioning tree.
*/
BinaryTreeBuilder.prototype.build = function (points) {
if (!points) {
return this.build(this.chart.polygons);
}
else {
var inputPolygons = points;
if (inputPolygons.length < 1) {
return null;
}
var bspNode = { back: null, front: null, plane: null };
var plane = inputPolygons[0];
bspNode.plane = plane;
var polygonsToLeft = [];
var polygonsToRight = [];
for (var i = 1, len = inputPolygons.length; i < len; i++) {
var currentPolygon = inputPolygons[i];
if (currentPolygon === plane) {
continue;
}
var classificationResult = this.classifyPolygon(plane, currentPolygon);
switch (classificationResult) {
case 'OnPlane':
case 'ToRight':
polygonsToRight.push(currentPolygon);
break;
case 'ToLeft':
polygonsToLeft.push(currentPolygon);
break;
case 'Unknown':
if (currentPolygon.element && (currentPolygon.element.tag === 'line' || currentPolygon.element.tag === 'text')) {
polygonsToLeft.push(currentPolygon);
}
else {
var result = this.splitPolygon(currentPolygon, plane);
for (var k = 0; k < result.backPolygon.length; k++) {
result.backPolygon[k].name = result.backPolygon[k].name + '-back';
polygonsToLeft.push(result.backPolygon[k]);
}
for (var j = 0; j < result.frontPolygon.length; j++) {
result.frontPolygon[j].name = result.frontPolygon[j].name + '-front';
polygonsToRight.push(result.frontPolygon[j]);
}
}
break;
}
}
if (polygonsToLeft.length > 0) {
bspNode.back = this.build(polygonsToLeft);
}
if (polygonsToRight.length > 0) {
bspNode.front = this.build(polygonsToRight);
}
return bspNode;
}
};
return BinaryTreeBuilder;
}());
export { BinaryTreeBuilder };
/**
* The Svg3DRenderer class provides methods for rendering SVG graphics in a 3D context.
*/
var Svg3DRenderer = /** @class */ (function () {
function Svg3DRenderer() {
}
/**
* Gets a Chart3DStringBuilder instance for constructing strings.
*
* @returns {Chart3DStringBuilder} - The StringBuilder instance.
*/
Svg3DRenderer.prototype.getStringBuilder = function () {
var data = [];
var counter = 0;
return {
append: function (text) {
data[counter++] = text;
return this;
},
remove: function (i, j) {
data.splice(i, j || 1);
return this;
},
insert: function (i, text) {
data.splice(i, 0, text);
return this;
},
toString: function (text) {
return data.join(text || '');
}
};
};
/**
* Parses a hex color code and returns its Red green Blue values.
*
* @param {string} hexColorCode - The hex color code.
* @returns {Chart3DColorFormat | null} - The parsed color format (Red green Blue) or null if parsing fails.
*/
Svg3DRenderer.prototype.hexToValue = function (hexColorCode) {
var result;
var values;
if (hexColorCode.indexOf('rgba(') === 0) {
values = hexColorCode.slice(5, -1).split(',');
return values ? {
red: parseInt(values[0], 10),
green: parseInt(values[1], 10),
blue: parseInt(values[2], 10),
alpha: parseFloat(values[3])
} : null;
}
else if (hexColorCode.indexOf('rgb(') === 0) {
values = hexColorCode.slice(4, -1).split(',');
return values ? {
red: parseInt(values[0], 10),
green: parseInt(values[1], 10),
blue: parseInt(values[2], 10)
} : null;
}
else {
result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hexColorCode);
return result
? {
red: parseInt(result[1], 16),
green: parseInt(result[2], 16),
blue: parseInt(result[3], 16)
}
: null;
}
};
/**
* Converts a Chart3DColorFormat object to its corresponding color string.
*
* @param {Chart3DColorFormat} color - The color in Chart3DColorFormat.
* @returns {string} - The color string representation.
*/
Svg3DRenderer.prototype.hexColor = function (color) {
var redValue = color.red;
var greenValue = color.green;
var blueValue = color.blue;
if (color.alpha) {
var returnColor = "rgba(" + redValue.toString() + "," + greenValue.toString() + "," + blueValue.toString() + "," + color.alpha + ")";
return returnColor;
}
else {
var hex_1 = [redValue.toString(16), greenValue.toString(16), blueValue.toString(16)];
hex_1.forEach(function (val, nr) {
if (val.length === 1) {
hex_1[nr] = '0' + val;
}
});
return '#' + hex_1.join('').toUpperCase();
}
};
/**
* Checks if a given color string is in a valid format (hex or rgba).
*
* @param {string} color - The color string to check.
* @returns {boolean} - True if the color string is valid, otherwise false.
*/
Svg3DRenderer.prototype.checkColorFormat = function (color) {
if (color.indexOf('rgba(') === 0 || color.indexOf('rgb(') === 0) {
var rgbaValues = color.substring(color.indexOf('(') + 1, color.lastIndexOf(')')).spli