UNPKG

highcharts

Version:
1,126 lines (1,123 loc) 217 kB
/** * @license Highcharts JS v11.3.0 (2024-01-10) * * 3D features for Highcharts JS * * License: www.highcharts.com/license */ (function (factory) { if (typeof module === 'object' && module.exports) { factory['default'] = factory; module.exports = factory; } else if (typeof define === 'function' && define.amd) { define('highcharts/highcharts-3d', ['highcharts'], function (Highcharts) { factory(Highcharts); factory.Highcharts = Highcharts; return factory; }); } else { factory(typeof Highcharts !== 'undefined' ? Highcharts : undefined); } }(function (Highcharts) { 'use strict'; var _modules = Highcharts ? Highcharts._modules : {}; function _registerModule(obj, path, args, fn) { if (!obj.hasOwnProperty(path)) { obj[path] = fn.apply(null, args); if (typeof CustomEvent === 'function') { window.dispatchEvent(new CustomEvent( 'HighchartsModuleLoaded', { detail: { path: path, module: obj[path] } } )); } } } _registerModule(_modules, 'Core/Math3D.js', [_modules['Core/Globals.js'], _modules['Core/Utilities.js']], function (H, U) { /* * * * (c) 2010-2024 Torstein Honsi * * License: www.highcharts.com/license * * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! * * */ const { deg2rad } = H; const { pick } = U; /* * * * Functions * * */ /* 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 */ /** * Rotates the position as defined in angles. * @private * @param {number} x * X coordinate * @param {number} y * Y coordinate * @param {number} z * Z coordinate * @param {Highcharts.Rotation3DObject} angles * Rotation angles * @return {Highcharts.Position3DObject} * 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 }; } /** * 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 verify 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 */ function perspective(points, chart, insidePlotArea, useInvertedPersp) { const 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) { const 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 = 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 }; }); } /** * 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 */ function perspective3D(coordinate, origin, distance) { const projection = ((distance > 0) && (distance < Number.POSITIVE_INFINITY)) ? distance / (coordinate.z + origin.z + distance) : 1; return { x: coordinate.x * projection, y: coordinate.y * projection }; } /** * 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 */ function pointCameraDistance(coordinates, chart) { const 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 */ function shapeArea(vertexes) { let 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 verify that the points are inside the plotArea * * @return {number} * Calculated area * * @requires highcharts-3d */ function shapeArea3D(vertexes, chart, insidePlotArea) { return shapeArea(perspective(vertexes, chart, insidePlotArea)); } /* * * * Default Export * * */ const Math3D = { perspective, perspective3D, pointCameraDistance, shapeArea, shapeArea3D }; return Math3D; }); _registerModule(_modules, 'Core/Chart/Chart3D.js', [_modules['Core/Color/Color.js'], _modules['Core/Defaults.js'], _modules['Core/Math3D.js'], _modules['Core/Utilities.js']], function (Color, D, Math3D, U) { /* * * * (c) 2010-2024 Torstein Honsi * * Extension for 3D charts * * License: www.highcharts.com/license * * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! * * */ const { parse: color } = Color; const { defaultOptions: genericDefaultOptions } = D; const { perspective, shapeArea3D } = Math3D; const { addEvent, isArray, merge, pick, wrap } = U; /* * * * Composition * * */ var Chart3D; (function (Chart3D) { /* * * * Declarations * * */ /* * * * Constants * * */ /** * @optionparent * @private */ Chart3D.defaultOptions = { chart: { /** * Options to render charts in 3 dimensions. This feature requires * `highcharts-3d.js`, found in the download package or online at * [code.highcharts.com/highcharts-3d.js](https://code.highcharts.com/highcharts-3d.js). * * @since 4.0 * @product highcharts * @requires highcharts-3d */ options3d: { /** * Whether to render the chart using the 3D functionality. * * @since 4.0 * @product highcharts */ enabled: false, /** * One of the two rotation angles for the chart. * * @since 4.0 * @product highcharts */ alpha: 0, /** * One of the two rotation angles for the chart. * * @since 4.0 * @product highcharts */ beta: 0, /** * The total depth of the chart. * * @since 4.0 * @product highcharts */ depth: 100, /** * Whether the 3d box should automatically adjust to the chart * plot area. * * @since 4.2.4 * @product highcharts */ fitToPlot: true, /** * Defines the distance the viewer is standing in front of the * chart, this setting is important to calculate the perspective * effect in column and scatter charts. It is not used for 3D * pie charts. * * @since 4.0 * @product highcharts */ viewDistance: 25, /** * Set it to `"auto"` to automatically move the labels to the * best edge. * * @type {"auto"|null} * @since 5.0.12 * @product highcharts */ axisLabelPosition: null, /** * Provides the option to draw a frame around the charts by * defining a bottom, front and back panel. * * @since 4.0 * @product highcharts * @requires highcharts-3d */ frame: { /** * Whether the frames are visible. */ visible: 'default', /** * General pixel thickness for the frame faces. */ size: 1, /** * The bottom of the frame around a 3D chart. * * @since 4.0 * @product highcharts * @requires highcharts-3d */ /** * The color of the panel. * * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject} * @default transparent * @since 4.0 * @product highcharts * @apioption chart.options3d.frame.bottom.color */ /** * The thickness of the panel. * * @type {number} * @default 1 * @since 4.0 * @product highcharts * @apioption chart.options3d.frame.bottom.size */ /** * Whether to display the frame. Possible values are `true`, * `false`, `"auto"` to display only the frames behind the * data, and `"default"` to display faces behind the data * based on the axis layout, ignoring the point of view. * * @sample {highcharts} highcharts/3d/scatter-frame/ * Auto frames * * @type {boolean|"default"|"auto"} * @default default * @since 5.0.12 * @product highcharts * @apioption chart.options3d.frame.bottom.visible */ /** * The bottom of the frame around a 3D chart. */ bottom: {}, /** * The top of the frame around a 3D chart. * * @extends chart.options3d.frame.bottom */ top: {}, /** * The left side of the frame around a 3D chart. * * @extends chart.options3d.frame.bottom */ left: {}, /** * The right of the frame around a 3D chart. * * @extends chart.options3d.frame.bottom */ right: {}, /** * The back side of the frame around a 3D chart. * * @extends chart.options3d.frame.bottom */ back: {}, /** * The front of the frame around a 3D chart. * * @extends chart.options3d.frame.bottom */ front: {} } } } }; /* * * * Functions * * */ /** * @private */ function compose(ChartClass, FxClass) { const chartProto = ChartClass.prototype; const fxProto = FxClass.prototype; /** * Shorthand to check the is3d flag. * @private * @return {boolean} * Whether it is a 3D chart. */ chartProto.is3d = function () { return !!this.options.chart.options3d?.enabled; }; chartProto.propsRequireDirtyBox.push('chart.options3d'); chartProto.propsRequireUpdateSeries.push('chart.options3d'); /** * Animation setter for matrix property. * @private */ fxProto.matrixSetter = function () { let interpolated; if (this.pos < 1 && (isArray(this.start) || isArray(this.end))) { const start = (this.start || [1, 0, 0, 1, 0, 0]), end = this.end || [1, 0, 0, 1, 0, 0]; interpolated = []; for (let i = 0; i < 6; i++) { interpolated.push(this.pos * end[i] + (1 - this.pos) * start[i]); } } else { interpolated = this.end; } this.elem.attr(this.prop, interpolated, null, true); }; merge(true, genericDefaultOptions, Chart3D.defaultOptions); addEvent(ChartClass, 'init', onInit); addEvent(ChartClass, 'addSeries', onAddSeries); addEvent(ChartClass, 'afterDrawChartBox', onAfterDrawChartBox); addEvent(ChartClass, 'afterGetContainer', onAfterGetContainer); addEvent(ChartClass, 'afterInit', onAfterInit); addEvent(ChartClass, 'afterSetChartSize', onAfterSetChartSize); addEvent(ChartClass, 'beforeRedraw', onBeforeRedraw); addEvent(ChartClass, 'beforeRender', onBeforeRender); wrap(chartProto, 'isInsidePlot', wrapIsInsidePlot); wrap(chartProto, 'renderSeries', wrapRenderSeries); wrap(chartProto, 'setClassName', wrapSetClassName); } Chart3D.compose = compose; /** * Legacy support for HC < 6 to make 'scatter' series in a 3D chart route to * the real 'scatter3d' series type. (#8407) * @private */ function onAddSeries(e) { if (this.is3d()) { if (e.options.type === 'scatter') { e.options.type = 'scatter3d'; } } } /** * @private */ function onAfterDrawChartBox() { if (this.chart3d && this.is3d()) { const chart = this, renderer = chart.renderer, options3d = chart.options.chart.options3d, frame = chart.chart3d.get3dFrame(), xm = chart.plotLeft, xp = chart.plotLeft + chart.plotWidth, ym = chart.plotTop, yp = chart.plotTop + chart.plotHeight, zm = 0, zp = options3d.depth, xmm = xm - (frame.left.visible ? frame.left.size : 0), xpp = xp + (frame.right.visible ? frame.right.size : 0), ymm = ym - (frame.top.visible ? frame.top.size : 0), ypp = yp + (frame.bottom.visible ? frame.bottom.size : 0), zmm = zm - (frame.front.visible ? frame.front.size : 0), zpp = zp + (frame.back.visible ? frame.back.size : 0), verb = chart.hasRendered ? 'animate' : 'attr'; chart.chart3d.frame3d = frame; if (!chart.frameShapes) { chart.frameShapes = { bottom: renderer.polyhedron().add(), top: renderer.polyhedron().add(), left: renderer.polyhedron().add(), right: renderer.polyhedron().add(), back: renderer.polyhedron().add(), front: renderer.polyhedron().add() }; } chart.frameShapes.bottom[verb]({ 'class': 'highcharts-3d-frame highcharts-3d-frame-bottom', zIndex: frame.bottom.frontFacing ? -1000 : 1000, faces: [{ fill: color(frame.bottom.color).brighten(0.1).get(), vertexes: [{ x: xmm, y: ypp, z: zmm }, { x: xpp, y: ypp, z: zmm }, { x: xpp, y: ypp, z: zpp }, { x: xmm, y: ypp, z: zpp }], enabled: frame.bottom.visible }, { fill: color(frame.bottom.color).brighten(0.1).get(), vertexes: [{ x: xm, y: yp, z: zp }, { x: xp, y: yp, z: zp }, { x: xp, y: yp, z: zm }, { x: xm, y: yp, z: zm }], enabled: frame.bottom.visible }, { fill: color(frame.bottom.color).brighten(-0.1).get(), vertexes: [{ x: xmm, y: ypp, z: zmm }, { x: xmm, y: ypp, z: zpp }, { x: xm, y: yp, z: zp }, { x: xm, y: yp, z: zm }], enabled: frame.bottom.visible && !frame.left.visible }, { fill: color(frame.bottom.color).brighten(-0.1).get(), vertexes: [{ x: xpp, y: ypp, z: zpp }, { x: xpp, y: ypp, z: zmm }, { x: xp, y: yp, z: zm }, { x: xp, y: yp, z: zp }], enabled: frame.bottom.visible && !frame.right.visible }, { fill: color(frame.bottom.color).get(), vertexes: [{ x: xpp, y: ypp, z: zmm }, { x: xmm, y: ypp, z: zmm }, { x: xm, y: yp, z: zm }, { x: xp, y: yp, z: zm }], enabled: frame.bottom.visible && !frame.front.visible }, { fill: color(frame.bottom.color).get(), vertexes: [{ x: xmm, y: ypp, z: zpp }, { x: xpp, y: ypp, z: zpp }, { x: xp, y: yp, z: zp }, { x: xm, y: yp, z: zp }], enabled: frame.bottom.visible && !frame.back.visible }] }); chart.frameShapes.top[verb]({ 'class': 'highcharts-3d-frame highcharts-3d-frame-top', zIndex: frame.top.frontFacing ? -1000 : 1000, faces: [{ fill: color(frame.top.color).brighten(0.1).get(), vertexes: [{ x: xmm, y: ymm, z: zpp }, { x: xpp, y: ymm, z: zpp }, { x: xpp, y: ymm, z: zmm }, { x: xmm, y: ymm, z: zmm }], enabled: frame.top.visible }, { fill: color(frame.top.color).brighten(0.1).get(), vertexes: [{ x: xm, y: ym, z: zm }, { x: xp, y: ym, z: zm }, { x: xp, y: ym, z: zp }, { x: xm, y: ym, z: zp }], enabled: frame.top.visible }, { fill: color(frame.top.color).brighten(-0.1).get(), vertexes: [{ x: xmm, y: ymm, z: zpp }, { x: xmm, y: ymm, z: zmm }, { x: xm, y: ym, z: zm }, { x: xm, y: ym, z: zp }], enabled: frame.top.visible && !frame.left.visible }, { fill: color(frame.top.color).brighten(-0.1).get(), vertexes: [{ x: xpp, y: ymm, z: zmm }, { x: xpp, y: ymm, z: zpp }, { x: xp, y: ym, z: zp }, { x: xp, y: ym, z: zm }], enabled: frame.top.visible && !frame.right.visible }, { fill: color(frame.top.color).get(), vertexes: [{ x: xmm, y: ymm, z: zmm }, { x: xpp, y: ymm, z: zmm }, { x: xp, y: ym, z: zm }, { x: xm, y: ym, z: zm }], enabled: frame.top.visible && !frame.front.visible }, { fill: color(frame.top.color).get(), vertexes: [{ x: xpp, y: ymm, z: zpp }, { x: xmm, y: ymm, z: zpp }, { x: xm, y: ym, z: zp }, { x: xp, y: ym, z: zp }], enabled: frame.top.visible && !frame.back.visible }] }); chart.frameShapes.left[verb]({ 'class': 'highcharts-3d-frame highcharts-3d-frame-left', zIndex: frame.left.frontFacing ? -1000 : 1000, faces: [{ fill: color(frame.left.color).brighten(0.1).get(), vertexes: [{ x: xmm, y: ypp, z: zmm }, { x: xm, y: yp, z: zm }, { x: xm, y: yp, z: zp }, { x: xmm, y: ypp, z: zpp }], enabled: frame.left.visible && !frame.bottom.visible }, { fill: color(frame.left.color).brighten(0.1).get(), vertexes: [{ x: xmm, y: ymm, z: zpp }, { x: xm, y: ym, z: zp }, { x: xm, y: ym, z: zm }, { x: xmm, y: ymm, z: zmm }], enabled: frame.left.visible && !frame.top.visible }, { fill: color(frame.left.color).brighten(-0.1).get(), vertexes: [{ x: xmm, y: ypp, z: zpp }, { x: xmm, y: ymm, z: zpp }, { x: xmm, y: ymm, z: zmm }, { x: xmm, y: ypp, z: zmm }], enabled: frame.left.visible }, { fill: color(frame.left.color).brighten(-0.1).get(), vertexes: [{ x: xm, y: ym, z: zp }, { x: xm, y: yp, z: zp }, { x: xm, y: yp, z: zm }, { x: xm, y: ym, z: zm }], enabled: frame.left.visible }, { fill: color(frame.left.color).get(), vertexes: [{ x: xmm, y: ypp, z: zmm }, { x: xmm, y: ymm, z: zmm }, { x: xm, y: ym, z: zm }, { x: xm, y: yp, z: zm }], enabled: frame.left.visible && !frame.front.visible }, { fill: color(frame.left.color).get(), vertexes: [{ x: xmm, y: ymm, z: zpp }, { x: xmm, y: ypp, z: zpp }, { x: xm, y: yp, z: zp }, { x: xm, y: ym, z: zp }], enabled: frame.left.visible && !frame.back.visible }] }); chart.frameShapes.right[verb]({ 'class': 'highcharts-3d-frame highcharts-3d-frame-right', zIndex: frame.right.frontFacing ? -1000 : 1000, faces: [{ fill: color(frame.right.color).brighten(0.1).get(), vertexes: [{ x: xpp, y: ypp, z: zpp }, { x: xp, y: yp, z: zp }, { x: xp, y: yp, z: zm }, { x: xpp, y: ypp, z: zmm }], enabled: frame.right.visible && !frame.bottom.visible }, { fill: color(frame.right.color).brighten(0.1).get(), vertexes: [{ x: xpp, y: ymm, z: zmm }, { x: xp, y: ym, z: zm }, { x: xp, y: ym, z: zp }, { x: xpp, y: ymm, z: zpp }], enabled: frame.right.visible && !frame.top.visible }, { fill: color(frame.right.color).brighten(-0.1).get(), vertexes: [{ x: xp, y: ym, z: zm }, { x: xp, y: yp, z: zm }, { x: xp, y: yp, z: zp }, { x: xp, y: ym, z: zp }], enabled: frame.right.visible }, { fill: color(frame.right.color).brighten(-0.1).get(), vertexes: [{ x: xpp, y: ypp, z: zmm }, { x: xpp, y: ymm, z: zmm }, { x: xpp, y: ymm, z: zpp }, { x: xpp, y: ypp, z: zpp }], enabled: frame.right.visible }, { fill: color(frame.right.color).get(), vertexes: [{ x: xpp, y: ymm, z: zmm }, { x: xpp, y: ypp, z: zmm }, { x: xp, y: yp, z: zm }, { x: xp, y: ym, z: zm }], enabled: frame.right.visible && !frame.front.visible }, { fill: color(frame.right.color).get(), vertexes: [{ x: xpp, y: ypp, z: zpp }, { x: xpp, y: ymm, z: zpp }, { x: xp, y: ym, z: zp }, { x: xp, y: yp, z: zp }], enabled: frame.right.visible && !frame.back.visible }] }); chart.frameShapes.back[verb]({ 'class': 'highcharts-3d-frame highcharts-3d-frame-back', zIndex: frame.back.frontFacing ? -1000 : 1000, faces: [{ fill: color(frame.back.color).brighten(0.1).get(), vertexes: [{ x: xpp, y: ypp, z: zpp }, { x: xmm, y: ypp, z: zpp }, { x: xm, y: yp, z: zp }, { x: xp, y: yp, z: zp }], enabled: frame.back.visible && !frame.bottom.visible }, { fill: color(frame.back.color).brighten(0.1).get(), vertexes: [{ x: xmm, y: ymm, z: zpp }, { x: xpp, y: ymm, z: zpp }, {