/* * * * (c) 2010-2020 Torstein Honsi * * Extension for 3D charts * * License: www.highcharts.com/license * * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! * * */ 'use strict'; import Axis from '../parts/Axis.js'; import Axis3D from './Axis3D.js'; import H from '../parts/Globals.js'; import U from '../parts/Utilities.js'; var addEvent = U.addEvent, Fx = U.Fx, isArray = U.isArray, merge = U.merge, pick = U.pick, wrap = U.wrap; import ZAxis from './ZAxis.js'; import '../parts/Chart.js'; var Chart = H.Chart, perspective = H.perspective; /** * Shorthand to check the is3d flag. * @private * @return {boolean} * Whether it is a 3D chart. */ Chart.prototype.is3d = function () { return (this.options.chart.options3d && this.options.chart.options3d.enabled); // #4280 }; Chart.prototype.propsRequireDirtyBox.push('chart.options3d'); Chart.prototype.propsRequireUpdateSeries.push('chart.options3d'); /* eslint-disable no-invalid-this */ // Legacy support for HC < 6 to make 'scatter' series in a 3D chart route to the // real 'scatter3d' series type. addEvent(Chart, 'afterInit', function () { var options = this.options; if (this.is3d()) { (options.series || []).forEach(function (s) { var type = s.type || options.chart.type || options.chart.defaultSeriesType; if (type === 'scatter') { s.type = 'scatter3d'; } }); } }); // And do it on dynamic add (#8407) addEvent(Chart, 'addSeries', function (e) { if (this.is3d()) { if (e.options.type === 'scatter') { e.options.type = 'scatter3d'; } } }); /** * Calculate scale of the 3D view. That is required to * fit chart's 3D projection into the actual plotting area. Reported as #4933. * @notice This function should ideally take the plot values instead of a chart * object, but since the chart object is needed for perspective it is * not practical. Possible to make both getScale and perspective more * logical and also immutable. * * @private * @function getScale * * @param {Highcharts.Chart} chart * Chart object * * @param {number} depth * The depth of the chart * * @return {number} * The scale to fit the 3D chart into the plotting area. * * @requires highcharts-3d */ function getScale(chart, depth) { var plotLeft = chart.plotLeft, plotRight = chart.plotWidth + plotLeft, plotTop = chart.plotTop, plotBottom = chart.plotHeight + plotTop, originX = plotLeft + chart.plotWidth / 2, originY = plotTop + chart.plotHeight / 2, bbox3d = { minX: Number.MAX_VALUE, maxX: -Number.MAX_VALUE, minY: Number.MAX_VALUE, maxY: -Number.MAX_VALUE }, corners, scale = 1; // Top left corners: corners = [{ x: plotLeft, y: plotTop, z: 0 }, { x: plotLeft, y: plotTop, z: depth }]; // Top right corners: [0, 1].forEach(function (i) { corners.push({ x: plotRight, y: corners[i].y, z: corners[i].z }); }); // All bottom corners: [0, 1, 2, 3].forEach(function (i) { corners.push({ x: corners[i].x, y: plotBottom, z: corners[i].z }); }); // Calculate 3D corners: corners = perspective(corners, chart, false); // Get bounding box of 3D element: corners.forEach(function (corner) { bbox3d.minX = Math.min(bbox3d.minX, corner.x); bbox3d.maxX = Math.max(bbox3d.maxX, corner.x); bbox3d.minY = Math.min(bbox3d.minY, corner.y); bbox3d.maxY = Math.max(bbox3d.maxY, corner.y); }); // Left edge: if (plotLeft > bbox3d.minX) { scale = Math.min(scale, 1 - Math.abs((plotLeft + originX) / (bbox3d.minX + originX)) % 1); } // Right edge: if (plotRight < bbox3d.maxX) { scale = Math.min(scale, (plotRight - originX) / (bbox3d.maxX - originX)); } // Top edge: if (plotTop > bbox3d.minY) { if (bbox3d.minY < 0) { scale = Math.min(scale, (plotTop + originY) / (-bbox3d.minY + plotTop + originY)); } else { scale = Math.min(scale, 1 - (plotTop + originY) / (bbox3d.minY + originY) % 1); } } // Bottom edge: if (plotBottom < bbox3d.maxY) { scale = Math.min(scale, Math.abs((plotBottom - originY) / (bbox3d.maxY - originY))); } return scale; } wrap(H.Chart.prototype, 'isInsidePlot', function (proceed) { return this.is3d() || proceed.apply(this, [].slice.call(arguments, 1)); }); var defaultOptions = H.getOptions(); /** * @optionparent */ var extendedOptions = { 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: { /** * Wether 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: {} } } } }; merge(true, defaultOptions, extendedOptions); // Add the required CSS classes for column sides (#6018) addEvent(Chart, 'afterGetContainer', function () { if (this.styledMode) { this.renderer.definition({ tagName: 'style', textContent: '.highcharts-3d-top{' + 'filter: url(#highcharts-brighter)' + '}\n' + '.highcharts-3d-side{' + 'filter: url(#highcharts-darker)' + '}\n' }); // Add add definitions used by brighter and darker faces of the cuboids. [{ name: 'darker', slope: 0.6 }, { name: 'brighter', slope: 1.4 }].forEach(function (cfg) { this.renderer.definition({ tagName: 'filter', id: 'highcharts-' + cfg.name, children: [{ tagName: 'feComponentTransfer', children: [{ tagName: 'feFuncR', type: 'linear', slope: cfg.slope }, { tagName: 'feFuncG', type: 'linear', slope: cfg.slope }, { tagName: 'feFuncB', type: 'linear', slope: cfg.slope }] }] }); }, this); } }); wrap(Chart.prototype, 'setClassName', function (proceed) { proceed.apply(this, [].slice.call(arguments, 1)); if (this.is3d()) { this.container.className += ' highcharts-3d-chart'; } }); addEvent(H.Chart, 'afterSetChartSize', function () { var chart = this, options3d = chart.options.chart.options3d; if (chart.is3d()) { // Add a 0-360 normalisation for alfa and beta angles in 3d graph if (options3d) { options3d.alpha = options3d.alpha % 360 + (options3d.alpha >= 0 ? 0 : 360); options3d.beta = options3d.beta % 360 + (options3d.beta >= 0 ? 0 : 360); } var inverted = chart.inverted, clipBox = chart.clipBox, margin = chart.margin, x = inverted ? 'y' : 'x', y = inverted ? 'x' : 'y', w = inverted ? 'height' : 'width', h = inverted ? 'width' : 'height'; clipBox[x] = -(margin[3] || 0); clipBox[y] = -(margin[0] || 0); clipBox[w] = chart.chartWidth + (margin[3] || 0) + (margin[1] || 0); clipBox[h] = chart.chartHeight + (margin[0] || 0) + (margin[2] || 0); // Set scale, used later in perspective method(): // getScale uses perspective, so scale3d has to be reset. chart.scale3d = 1; if (options3d.fitToPlot === true) { chart.scale3d = getScale(chart, options3d.depth); } // Recalculate the 3d frame with every call of setChartSize, // instead of doing it after every redraw(). It avoids ticks // and axis title outside of chart. chart.frame3d = this.get3dFrame(); // #7942 } }); addEvent(Chart, 'beforeRedraw', function () { if (this.is3d()) { // Set to force a redraw of all elements this.isDirtyBox = true; } }); addEvent(Chart, 'beforeRender', function () { if (this.is3d()) { this.frame3d = this.get3dFrame(); } }); // Draw the series in the reverse order (#3803, #3917) wrap(Chart.prototype, 'renderSeries', function (proceed) { var series, i = this.series.length; if (this.is3d()) { while (i--) { series = this.series[i]; series.translate(); series.render(); } } else { proceed.call(this); } }); addEvent(Chart, 'afterDrawChartBox', function () { if (this.is3d()) { var chart = this, renderer = chart.renderer, options3d = this.options.chart.options3d, frame = chart.get3dFrame(), xm = this.plotLeft, xp = this.plotLeft + this.plotWidth, ym = this.plotTop, yp = this.plotTop + this.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'; this.frame3d = frame; if (!this.frameShapes) { this.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() }; } this.frameShapes.bottom[verb]({ 'class': 'highcharts-3d-frame highcharts-3d-frame-bottom', zIndex: frame.bottom.frontFacing ? -1000 : 1000, faces: [{ fill: H.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: H.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: H.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: H.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: H.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: H.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 }] }); this.frameShapes.top[verb]({ 'class': 'highcharts-3d-frame highcharts-3d-frame-top', zIndex: frame.top.frontFacing ? -1000 : 1000, faces: [{ fill: H.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: H.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: H.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: H.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: H.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: H.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 }] }); this.frameShapes.left[verb]({ 'class': 'highcharts-3d-frame highcharts-3d-frame-left', zIndex: frame.left.frontFacing ? -1000 : 1000, faces: [{ fill: H.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: H.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: H.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: H.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: H.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: H.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 }] }); this.frameShapes.right[verb]({ 'class': 'highcharts-3d-frame highcharts-3d-frame-right', zIndex: frame.right.frontFacing ? -1000 : 1000, faces: [{ fill: H.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: H.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: H.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: H.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: H.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: H.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 }] }); this.frameShapes.back[verb]({ 'class': 'highcharts-3d-frame highcharts-3d-frame-back', zIndex: frame.back.frontFacing ? -1000 : 1000, faces: [{ fill: H.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: H.color(frame.back.color).brighten(0.1).get(), vertexes: [{ x: xmm, y: ymm, z: zpp }, { x: xpp, y: ymm, z: zpp }, { x: xp, y: ym, z: zp }, { x: xm, y: ym, z: zp }], enabled: frame.back.visible && !frame.top.visible }, { fill: H.color(frame.back.color).brighten(-0.1).get(), vertexes: [{ x: xmm, y: ypp, z: zpp }, { x: xmm, y: ymm, z: zpp }, { x: xm, y: ym, z: zp }, { x: xm, y: yp, z: zp }], enabled: frame.back.visible && !frame.left.visible }, { fill: H.color(frame.back.color).brighten(-0.1).get(), vertexes: [{ x: xpp, y: ymm, z: zpp }, { x: xpp, y: ypp, z: zpp }, { x: xp, y: yp, z: zp }, { x: xp, y: ym, z: zp }], enabled: frame.back.visible && !frame.right.visible }, { fill: H.color(frame.back.color).get(), vertexes: [{ x: xm, y: ym, z: zp }, { x: xp, y: ym, z: zp }, { x: xp, y: yp, z: zp }, { x: xm, y: yp, z: zp }], enabled: frame.back.visible }, { fill: H.color(frame.back.color).get(), vertexes: [{ x: xmm, y: ypp, z: zpp }, { x: xpp, y: ypp, z: zpp }, { x: xpp, y: ymm, z: zpp }, { x: xmm, y: ymm, z: zpp }], enabled: frame.back.visible }] }); this.frameShapes.front[verb]({ 'class': 'highcharts-3d-frame highcharts-3d-frame-front', zIndex: frame.front.frontFacing ? -1000 : 1000, faces: [{ fill: H.color(frame.front.color).brighten(0.1).get(), vertexes: [{ x: xmm, y: ypp, z: zmm }, { x: xpp, y: ypp, z: zmm }, { x: xp, y: yp, z: zm }, { x: xm, y: yp, z: zm }], enabled: frame.front.visible && !frame.bottom.visible }, { fill: H.color(frame.front.color).brighten(0.1).get(), vertexes: [{ x: xpp, y: ymm, z: zmm }, { x: xmm, y: ymm, z: zmm }, { x: xm, y: ym, z: zm }, { x: xp, y: ym, z: zm }], enabled: frame.front.visible && !frame.top.visible }, { fill: H.color(frame.front.color).brighten(-0.1).get(), vertexes: [{ x: xmm, y: ymm, z: zmm }, { x: xmm, y: ypp, z: zmm }, { x: xm, y: yp, z: zm }, { x: xm, y: ym, z: zm }], enabled: frame.front.visible && !frame.left.visible }, { fill: H.color(frame.front.color).brighten(-0.1).get(), vertexes: [{ x: xpp, y: ypp, z: zmm }, { x: xpp, y: ymm, z: zmm }, { x: xp, y: ym, z: zm }, { x: xp, y: yp, z: zm }], enabled: frame.front.visible && !frame.right.visible }, { fill: H.color(frame.front.color).get(), vertexes: [{ x: xp, y: ym, z: zm }, { x: xm, y: ym, z: zm }, { x: xm, y: yp, z: zm }, { x: xp, y: yp, z: zm }], enabled: frame.front.visible }, { fill: H.color(frame.front.color).get(), vertexes: [{ x: xpp, y: ypp, z: zmm }, { x: xmm, y: ypp, z: zmm }, { x: xmm, y: ymm, z: zmm }, { x: xpp, y: ymm, z: zmm }], enabled: frame.front.visible }] }); } }); Chart.prototype.retrieveStacks = function (stacking) { var series = this.series, stacks = {}, stackNumber, i = 1; this.series.forEach(function (s) { stackNumber = pick(s.options.stack, (stacking ? 0 : series.length - 1 - s.index)); // #3841, #4532 if (!stacks[stackNumber]) { stacks[stackNumber] = { series: [s], position: i }; i++; } else { stacks[stackNumber].series.push(s); } }); stacks.totalStacks = i + 1; return stacks; }; Chart.prototype.get3dFrame = function () { var chart = this, options3d = chart.options.chart.options3d, frameOptions = options3d.frame, xm = chart.plotLeft, xp = chart.plotLeft + chart.plotWidth, ym = chart.plotTop, yp = chart.plotTop + chart.plotHeight, zm = 0, zp = options3d.depth, faceOrientation = function (vertexes) { var area = H.shapeArea3d(vertexes, chart); // Give it 0.5 squared-pixel as a margin for rounding errors. if (area > 0.5) { return 1; } if (area < -0.5) { return -1; } return 0; }, bottomOrientation = faceOrientation([ { x: xm, y: yp, z: zp }, { x: xp, y: yp, z: zp }, { x: xp, y: yp, z: zm }, { x: xm, y: yp, z: zm } ]), topOrientation = faceOrientation([ { x: xm, y: ym, z: zm }, { x: xp, y: ym, z: zm }, { x: xp, y: ym, z: zp }, { x: xm, y: ym, z: zp } ]), leftOrientation = faceOrientation([ { x: xm, y: ym, z: zm }, { x: xm, y: ym, z: zp }, { x: xm, y: yp, z: zp }, { x: xm, y: yp, z: zm } ]), rightOrientation = faceOrientation([ { x: xp, y: ym, z: zp }, { x: xp, y: ym, z: zm }, { x: xp, y: yp, z: zm }, { x: xp, y: yp, z: zp } ]), frontOrientation = faceOrientation([ { x: xm, y: yp, z: zm }, { x: xp, y: yp, z: zm }, { x: xp, y: ym, z: zm }, { x: xm, y: ym, z: zm } ]), backOrientation = faceOrientation([ { x: xm, y: ym, z: zp }, { x: xp, y: ym, z: zp }, { x: xp, y: yp, z: zp }, { x: xm, y: yp, z: zp } ]), defaultShowBottom = false, defaultShowTop = false, defaultShowLeft = false, defaultShowRight = false, defaultShowFront = false, defaultShowBack = true; // The 'default' criteria to visible faces of the frame is looking up every // axis to decide whenever the left/right//top/bottom sides of the frame // will be shown [] .concat(chart.xAxis, chart.yAxis, chart.zAxis) .forEach(function (axis) { if (axis) { if (axis.horiz) { if (axis.opposite) { defaultShowTop = true; } else { defaultShowBottom = true; } } else { if (axis.opposite) { defaultShowRight = true; } else { defaultShowLeft = true; } } } }); var getFaceOptions = function (sources, faceOrientation, defaultVisible) { var faceAttrs = ['size', 'color', 'visible']; var options = {}; for (var i = 0; i < faceAttrs.length; i++) { var attr = faceAttrs[i]; for (var j = 0; j < sources.length; j++) { if (typeof sources[j] === 'object') { var val = sources[j][attr]; if (typeof val !== 'undefined' && val !== null) { options[attr] = val; break; } } } } var isVisible = defaultVisible; if (options.visible === true || options.visible === false) { isVisible = options.visible; } else if (options.visible === 'auto') { isVisible = faceOrientation > 0; } return { size: pick(options.size, 1), color: pick(options.color, 'none'), frontFacing: faceOrientation > 0, visible: isVisible }; }; // docs @TODO: Add all frame options (left, right, top, bottom, front, back) // to apioptions JSDoc once the new system is up. var ret = { axes: {}, // FIXME: Previously, left/right, top/bottom and front/back pairs shared // size and color. // For compatibility and consistency sake, when one face have // size/color/visibility set, the opposite face will default to the same // values. Also, left/right used to be called 'side', so that's also // added as a fallback bottom: getFaceOptions([frameOptions.bottom, frameOptions.top, frameOptions], bottomOrientation, defaultShowBottom), top: getFaceOptions([frameOptions.top, frameOptions.bottom, frameOptions], topOrientation, defaultShowTop), left: getFaceOptions([ frameOptions.left, frameOptions.right, frameOptions.side, frameOptions ], leftOrientation, defaultShowLeft), right: getFaceOptions([ frameOptions.right, frameOptions.left, frameOptions.side, frameOptions ], rightOrientation, defaultShowRight), back: getFaceOptions([frameOptions.back, frameOptions.front, frameOptions], backOrientation, defaultShowBack), front: getFaceOptions([frameOptions.front, frameOptions.back, frameOptions], frontOrientation, defaultShowFront) }; // Decide the bast place to put axis title/labels based on the visible // faces. Ideally, The labels can only be on the edge between a visible face // and an invisble one. Also, the Y label should be one the left-most edge // (right-most if opposite), if (options3d.axisLabelPosition === 'auto') { var isValidEdge = function (face1, face2) { return ((face1.visible !== face2.visible) || (face1.visible && face2.visible && (face1.frontFacing !== face2.frontFacing))); }; var yEdges = []; if (isValidEdge(ret.left, ret.front)) { yEdges.push({ y: (ym + yp) / 2, x: xm, z: zm, xDir: { x: 1, y: 0, z: 0 } }); } if (isValidEdge(ret.left, ret.back)) { yEdges.push({ y: (ym + yp) / 2, x: xm, z: zp, xDir: { x: 0, y: 0, z: -1 } }); } if (isValidEdge(ret.right, ret.front)) { yEdges.push({ y: (ym + yp) / 2, x: xp, z: zm, xDir: { x: 0, y: 0, z: 1 } }); } if (isValidEdge(ret.right, ret.back)) { yEdges.push({ y: (ym + yp) / 2, x: xp, z: zp, xDir: { x: -1, y: 0, z: 0 } }); } var xBottomEdges = []; if (isValidEdge(ret.bottom, ret.front)) { xBottomEdges.push({ x: (xm + xp) / 2, y: yp, z: zm, xDir: { x: 1, y: 0, z: 0 } }); } if (isValidEdge(ret.bottom, ret.back)) { xBottomEdges.push({ x: (xm + xp) / 2, y: yp, z: zp, xDir: { x: -1, y: 0, z: 0 } }); } var xTopEdges = []; if (isValidEdge(ret.top, ret.front)) { xTopEdges.push({ x: (xm + xp) / 2, y: ym, z: zm, xDir: { x: 1, y: 0, z: 0 } }); } if (isValidEdge(ret.top, ret.back)) { xTopEdges.push({ x: (xm + xp) / 2, y: ym, z: zp, xDir: { x: -1, y: 0, z: 0 } }); } var zBottomEdges = []; if (isValidEdge(ret.bottom, ret.left)) { zBottomEdges.push({ z: (zm + zp) / 2, y: yp, x: xm, xDir: { x: 0, y: 0, z: -1 } }); } if (isValidEdge(ret.bottom, ret.right)) { zBottomEdges.push({ z: (zm + zp) / 2, y: yp, x: xp, xDir: { x: 0, y: 0, z: 1 } }); } var zTopEdges = []; if (isValidEdge(ret.top, ret.left)) { zTopEdges.push({ z: (zm + zp) / 2, y: ym, x: xm, xDir: { x: 0, y: 0, z: -1 } }); } if (isValidEdge(ret.top, ret.right)) { zTopEdges.push({ z: (zm + zp) / 2, y: ym, x: xp, xDir: { x: 0, y: 0, z: 1 } }); } var pickEdge = function (edges, axis, mult) { if (edges.length === 0) { return null; } if (edges.length === 1) { return edges[0]; } var best = 0, projections = perspective(edges, chart, false); for (var i = 1; i < projections.length; i++) { if (mult * projections[i][axis] > mult * projections[best][axis]) { best = i; } else if ((mult * projections[i][axis] === mult * projections[best][axis]) && (projections[i].z < projections[best].z)) { best = i; } } return edges[best]; }; ret.axes = { y: { 'left': pickEdge(yEdges, 'x', -1), 'right': pickEdge(yEdges, 'x', +1) }, x: { 'top': pickEdge(xTopEdges, 'y', -1), 'bottom': pickEdge(xBottomEdges, 'y', +1) }, z: { 'top': pickEdge(zTopEdges, 'y', -1), 'bottom': pickEdge(zBottomEdges, 'y', +1) } }; } else { ret.axes = { y: { 'left': { x: xm, z: zm, xDir: { x: 1, y: 0, z: 0 } }, 'right': { x: xp, z: zm, xDir: { x: 0, y: 0, z: 1 } } }, x: { 'top': { y: ym, z: zm, xDir: { x: 1, y: 0, z: 0 } }, 'bottom': { y: yp, z: zm, xDir: { x: 1, y: 0, z: 0 } } }, z: { 'top': { x: defaultShowLeft ? xp : xm, y: ym, xDir: defaultShowLeft ? { x: 0, y: 0, z: 1 } : { x: 0, y: 0, z: -1 } }, 'bottom': { x: defaultShowLeft ? xp : xm, y: yp, xDir: defaultShowLeft ? { x: 0, y: 0, z: 1 } : { x: 0, y: 0, z: -1 } } } }; } return ret; }; // Animation setter for matrix property. Fx.prototype.matrixSetter = function () { var interpolated; if (this.pos < 1 && (isArray(this.start) || isArray(this.end))) { var start = this.start || [1, 0, 0, 1, 0, 0]; var end = this.end || [1, 0, 0, 1, 0, 0]; interpolated = []; for (var 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); }; ZAxis.ZChartComposition.compose(Chart); Axis3D.compose(Axis); /** * Note: As of v5.0.12, `frame.left` or `frame.right` should be used instead. * * The side for the frame around a 3D chart. * * @deprecated * @since 4.0 * @product highcharts * @requires highcharts-3d * @apioption chart.options3d.frame.side */ /** * The color of the panel. * * @deprecated * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject} * @default transparent * @since 4.0 * @product highcharts * @apioption chart.options3d.frame.side.color */ /** * The thickness of the panel. * * @deprecated * @type {number} * @default 1 * @since 4.0 * @product highcharts * @apioption chart.options3d.frame.side.size */ ''; // adds doclets above to transpiled file