| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435 |
- /* *
- *
- * (c) 2010-2020 Torstein Honsi
- *
- * License: www.highcharts.com/license
- *
- * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
- *
- * */
- 'use strict';
- import H from '../Core/Globals.js';
- import Math3D from '../Extensions/Math3D.js';
- var perspective = Math3D.perspective;
- import StackItem from '../Extensions/Stacking.js';
- import U from '../Core/Utilities.js';
- var addEvent = U.addEvent, pick = U.pick, wrap = U.wrap;
- import '../Core/Series/Series.js';
- var Series = H.Series, seriesTypes = H.seriesTypes, svg = H.svg;
- /**
- * Depth of the columns in a 3D column chart.
- *
- * @type {number}
- * @default 25
- * @since 4.0
- * @product highcharts
- * @requires highcharts-3d
- * @apioption plotOptions.column.depth
- */
- /**
- * 3D columns only. The color of the edges. Similar to `borderColor`, except it
- * defaults to the same color as the column.
- *
- * @type {Highcharts.ColorString}
- * @product highcharts
- * @requires highcharts-3d
- * @apioption plotOptions.column.edgeColor
- */
- /**
- * 3D columns only. The width of the colored edges.
- *
- * @type {number}
- * @default 1
- * @product highcharts
- * @requires highcharts-3d
- * @apioption plotOptions.column.edgeWidth
- */
- /**
- * The spacing between columns on the Z Axis in a 3D chart.
- *
- * @type {number}
- * @default 1
- * @since 4.0
- * @product highcharts
- * @requires highcharts-3d
- * @apioption plotOptions.column.groupZPadding
- */
- /* eslint-disable no-invalid-this */
- /**
- * @private
- * @param {Highcharts.Chart} chart
- * Chart with stacks
- * @param {string} stacking
- * Stacking option
- * @return {Highcharts.Stack3dDictionary}
- */
- function retrieveStacks(chart, stacking) {
- var series = chart.series, stacks = {};
- var stackNumber, i = 1;
- 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;
- }
- wrap(seriesTypes.column.prototype, 'translate', function (proceed) {
- proceed.apply(this, [].slice.call(arguments, 1));
- // Do not do this if the chart is not 3D
- if (this.chart.is3d()) {
- this.translate3dShapes();
- }
- });
- // Don't use justifyDataLabel when point is outsidePlot
- wrap(Series.prototype, 'justifyDataLabel', function (proceed) {
- return !(arguments[2].outside3dPlot) ?
- proceed.apply(this, [].slice.call(arguments, 1)) :
- false;
- });
- seriesTypes.column.prototype.translate3dPoints = function () { };
- seriesTypes.column.prototype.translate3dShapes = function () {
- var series = this, chart = series.chart, seriesOptions = series.options, depth = seriesOptions.depth, stack = seriesOptions.stacking ?
- (seriesOptions.stack || 0) :
- series.index, // #4743
- z = stack * (depth + (seriesOptions.groupZPadding || 1)), borderCrisp = series.borderWidth % 2 ? 0.5 : 0, point2dPos; // Position of point in 2D, used for 3D position calculation.
- if (chart.inverted && !series.yAxis.reversed) {
- borderCrisp *= -1;
- }
- if (seriesOptions.grouping !== false) {
- z = 0;
- }
- z += (seriesOptions.groupZPadding || 1);
- series.data.forEach(function (point) {
- // #7103 Reset outside3dPlot flag
- point.outside3dPlot = null;
- if (point.y !== null) {
- var shapeArgs = point.shapeArgs, tooltipPos = point.tooltipPos,
- // Array for final shapeArgs calculation.
- // We are checking two dimensions (x and y).
- dimensions = [['x', 'width'], ['y', 'height']], borderlessBase; // Crisped rects can have +/- 0.5 pixels offset.
- // #3131 We need to check if column is inside plotArea.
- dimensions.forEach(function (d) {
- borderlessBase = shapeArgs[d[0]] - borderCrisp;
- if (borderlessBase < 0) {
- // If borderLessBase is smaller than 0, it is needed to set
- // its value to 0 or 0.5 depending on borderWidth
- // borderWidth may be even or odd.
- shapeArgs[d[1]] +=
- shapeArgs[d[0]] + borderCrisp;
- shapeArgs[d[0]] = -borderCrisp;
- borderlessBase = 0;
- }
- if ((borderlessBase + shapeArgs[d[1]] >
- series[d[0] + 'Axis'].len) &&
- // Do not change height/width of column if 0 (#6708)
- shapeArgs[d[1]] !== 0) {
- shapeArgs[d[1]] =
- series[d[0] + 'Axis'].len -
- shapeArgs[d[0]];
- }
- if (
- // Do not remove columns with zero height/width.
- (shapeArgs[d[1]] !== 0) &&
- (shapeArgs[d[0]] >=
- series[d[0] + 'Axis'].len ||
- shapeArgs[d[0]] + shapeArgs[d[1]] <=
- borderCrisp)) {
- // Set args to 0 if column is outside the chart.
- for (var key in shapeArgs) { // eslint-disable-line guard-for-in
- shapeArgs[key] = 0;
- }
- // #7103 outside3dPlot flag is set on Points which are
- // currently outside of plot.
- point.outside3dPlot = true;
- }
- });
- // Change from 2d to 3d
- if (point.shapeType === 'rect') {
- point.shapeType = 'cuboid';
- }
- shapeArgs.z = z;
- shapeArgs.depth = depth;
- shapeArgs.insidePlotArea = true;
- // Point's position in 2D
- point2dPos = {
- x: shapeArgs.x + shapeArgs.width / 2,
- y: shapeArgs.y,
- z: z + depth / 2 // The center of column in Z dimension
- };
- // Recalculate point positions for inverted graphs
- if (chart.inverted) {
- point2dPos.x = shapeArgs.height;
- point2dPos.y = point.clientX;
- }
- // Calculate and store point's position in 3D,
- // using perspective method.
- point.plot3d = perspective([point2dPos], chart, true, false)[0];
- // Translate the tooltip position in 3d space
- tooltipPos = perspective([{
- x: tooltipPos[0],
- y: tooltipPos[1],
- z: z + depth / 2 // The center of column in Z dimension
- }], chart, true, false)[0];
- point.tooltipPos = [tooltipPos.x, tooltipPos.y];
- }
- });
- // store for later use #4067
- series.z = z;
- };
- wrap(seriesTypes.column.prototype, 'animate', function (proceed) {
- if (!this.chart.is3d()) {
- proceed.apply(this, [].slice.call(arguments, 1));
- }
- else {
- var args = arguments, init = args[1], yAxis = this.yAxis, series = this, reversed = this.yAxis.reversed;
- if (svg) { // VML is too slow anyway
- if (init) {
- series.data.forEach(function (point) {
- if (point.y !== null) {
- point.height = point.shapeArgs.height;
- point.shapey = point.shapeArgs.y; // #2968
- point.shapeArgs.height = 1;
- if (!reversed) {
- if (point.stackY) {
- point.shapeArgs.y =
- point.plotY +
- yAxis.translate(point.stackY);
- }
- else {
- point.shapeArgs.y =
- point.plotY +
- (point.negative ?
- -point.height :
- point.height);
- }
- }
- }
- });
- }
- else { // run the animation
- series.data.forEach(function (point) {
- if (point.y !== null) {
- point.shapeArgs.height = point.height;
- point.shapeArgs.y = point.shapey; // #2968
- // null value do not have a graphic
- if (point.graphic) {
- point.graphic.animate(point.shapeArgs, series.options.animation);
- }
- }
- });
- // redraw datalabels to the correct position
- this.drawDataLabels();
- }
- }
- }
- });
- // In case of 3d columns there is no sense to add this columns to a specific
- // series group - if series is added to a group all columns will have the same
- // zIndex in comparison with different series.
- wrap(seriesTypes.column.prototype, 'plotGroup', function (proceed, prop, name, visibility, zIndex, parent) {
- if (prop !== 'dataLabelsGroup') {
- if (this.chart.is3d()) {
- if (this[prop]) {
- delete this[prop];
- }
- if (parent) {
- if (!this.chart.columnGroup) {
- this.chart.columnGroup =
- this.chart.renderer.g('columnGroup').add(parent);
- }
- this[prop] = this.chart.columnGroup;
- this.chart.columnGroup.attr(this.getPlotBox());
- this[prop].survive = true;
- if (prop === 'group' || prop === 'markerGroup') {
- arguments[3] = 'visible';
- // For 3D column group and markerGroup should be visible
- }
- }
- }
- }
- return proceed.apply(this, Array.prototype.slice.call(arguments, 1));
- });
- // When series is not added to group it is needed to change setVisible method to
- // allow correct Legend funcionality. This wrap is basing on pie chart series.
- wrap(seriesTypes.column.prototype, 'setVisible', function (proceed, vis) {
- var series = this, pointVis;
- if (series.chart.is3d()) {
- series.data.forEach(function (point) {
- point.visible = point.options.visible = vis =
- typeof vis === 'undefined' ?
- !pick(series.visible, point.visible) : vis;
- pointVis = vis ? 'visible' : 'hidden';
- series.options.data[series.data.indexOf(point)] =
- point.options;
- if (point.graphic) {
- point.graphic.attr({
- visibility: pointVis
- });
- }
- });
- }
- proceed.apply(this, Array.prototype.slice.call(arguments, 1));
- });
- seriesTypes.column.prototype
- .handle3dGrouping = true;
- addEvent(Series, 'afterInit', function () {
- if (this.chart.is3d() &&
- this.handle3dGrouping) {
- var series = this, seriesOptions = this.options, grouping = seriesOptions.grouping, stacking = seriesOptions.stacking, reversedStacks = pick(this.yAxis.options.reversedStacks, true), z = 0;
- // @todo grouping === true ?
- if (!(typeof grouping !== 'undefined' && !grouping)) {
- var stacks = retrieveStacks(this.chart, stacking), stack = seriesOptions.stack || 0, i; // position within the stack
- for (i = 0; i < stacks[stack].series.length; i++) {
- if (stacks[stack].series[i] === this) {
- break;
- }
- }
- z = (10 * (stacks.totalStacks - stacks[stack].position)) +
- (reversedStacks ? i : -i); // #4369
- // In case when axis is reversed, columns are also reversed inside
- // the group (#3737)
- if (!this.xAxis.reversed) {
- z = (stacks.totalStacks * 10) - z;
- }
- }
- seriesOptions.depth = seriesOptions.depth || 25;
- series.z = series.z || 0;
- seriesOptions.zIndex = z;
- }
- });
- // eslint-disable-next-line valid-jsdoc
- /**
- * @private
- */
- function pointAttribs(proceed) {
- var attr = proceed.apply(this, [].slice.call(arguments, 1));
- if (this.chart.is3d && this.chart.is3d()) {
- // Set the fill color to the fill color to provide a smooth edge
- attr.stroke = this.options.edgeColor || attr.fill;
- attr['stroke-width'] = pick(this.options.edgeWidth, 1); // #4055
- }
- return attr;
- }
- // eslint-disable-next-line valid-jsdoc
- /**
- * In 3D mode, all column-series are rendered in one main group. Because of that
- * we need to apply inactive state on all points.
- * @private
- */
- function setState(proceed, state, inherit) {
- var is3d = this.chart.is3d && this.chart.is3d();
- if (is3d) {
- this.options.inactiveOtherPoints = true;
- }
- proceed.call(this, state, inherit);
- if (is3d) {
- this.options.inactiveOtherPoints = false;
- }
- }
- // eslint-disable-next-line valid-jsdoc
- /**
- * In 3D mode, simple checking for a new shape to animate is not enough.
- * Additionally check if graphic is a group of elements
- * @private
- */
- function hasNewShapeType(proceed) {
- var args = [];
- for (var _i = 1; _i < arguments.length; _i++) {
- args[_i - 1] = arguments[_i];
- }
- return this.series.chart.is3d() ?
- this.graphic && this.graphic.element.nodeName !== 'g' :
- proceed.apply(this, args);
- }
- wrap(seriesTypes.column.prototype, 'pointAttribs', pointAttribs);
- wrap(seriesTypes.column.prototype, 'setState', setState);
- wrap(seriesTypes.column.prototype.pointClass.prototype, 'hasNewShapeType', hasNewShapeType);
- if (seriesTypes.columnrange) {
- wrap(seriesTypes.columnrange.prototype, 'pointAttribs', pointAttribs);
- wrap(seriesTypes.columnrange.prototype, 'setState', setState);
- wrap(seriesTypes.columnrange.prototype.pointClass.prototype, 'hasNewShapeType', hasNewShapeType);
- seriesTypes.columnrange.prototype.plotGroup =
- seriesTypes.column.prototype.plotGroup;
- seriesTypes.columnrange.prototype.setVisible =
- seriesTypes.column.prototype.setVisible;
- }
- wrap(Series.prototype, 'alignDataLabel', function (proceed, point, dataLabel, options, alignTo) {
- var chart = this.chart;
- // In 3D we need to pass point.outsidePlot option to the justifyDataLabel
- // method for disabling justifying dataLabels in columns outside plot
- options.outside3dPlot = point.outside3dPlot;
- // Only do this for 3D columns and it's derived series
- if (chart.is3d() &&
- this.is('column')) {
- var series = this, seriesOptions = series.options, inside = pick(options.inside, !!series.options.stacking), options3d = chart.options.chart.options3d, xOffset = point.pointWidth / 2 || 0;
- var dLPosition = {
- x: alignTo.x + xOffset,
- y: alignTo.y,
- z: series.z + seriesOptions.depth / 2
- };
- if (chart.inverted) {
- // Inside dataLabels are positioned according to above
- // logic and there is no need to position them using
- // non-3D algorighm (that use alignTo.width)
- if (inside) {
- alignTo.width = 0;
- dLPosition.x += point.shapeArgs.height / 2;
- }
- // When chart is upside down
- // (alpha angle between 180 and 360 degrees)
- // it is needed to add column width to calculated value.
- if (options3d.alpha >= 90 && options3d.alpha <= 270) {
- dLPosition.y += point.shapeArgs.width;
- }
- }
- // dLPosition is recalculated for 3D graphs
- dLPosition = perspective([dLPosition], chart, true, false)[0];
- alignTo.x = dLPosition.x - xOffset;
- // #7103 If point is outside of plotArea, hide data label.
- alignTo.y = point.outside3dPlot ? -9e9 : dLPosition.y;
- }
- proceed.apply(this, [].slice.call(arguments, 1));
- });
- // Added stackLabels position calculation for 3D charts.
- wrap(StackItem.prototype, 'getStackBox', function (proceed, chart, stackItem, x, y, xWidth, h, axis) {
- var stackBox = proceed.apply(this, [].slice.call(arguments, 1));
- // Only do this for 3D graph
- if (chart.is3d() && stackItem.base) {
- // First element of stackItem.base is an index of base series.
- var baseSeriesInd = +(stackItem.base).split(',')[0];
- var columnSeries = chart.series[baseSeriesInd];
- var options3d = chart.options.chart.options3d;
- // Only do this if base series is a column or inherited type,
- // use its barW, z and depth parameters
- // for correct stackLabels position calculation
- if (columnSeries &&
- columnSeries instanceof seriesTypes.column) {
- var dLPosition = {
- x: stackBox.x + (chart.inverted ? h : xWidth / 2),
- y: stackBox.y,
- z: columnSeries.options.depth / 2
- };
- if (chart.inverted) {
- // Do not use default offset calculation logic
- // for 3D inverted stackLabels.
- stackBox.width = 0;
- // When chart is upside down
- // (alpha angle between 180 and 360 degrees)
- // it is needed to add column width to calculated value.
- if (options3d.alpha >= 90 && options3d.alpha <= 270) {
- dLPosition.y += xWidth;
- }
- }
- dLPosition = perspective([dLPosition], chart, true, false)[0];
- stackBox.x = dLPosition.x - xWidth / 2;
- stackBox.y = dLPosition.y;
- }
- }
- return stackBox;
- });
|