UNPKG

@gooddata/react-components

Version:

GoodData.UI - A powerful JavaScript library for building analytical applications

305 lines • 11.1 kB
"use strict"; // tslint:disable-line /** * Highcharts extension that overwrites 'axis.adjustTickAmount' of Highcharts * Original code snippet * https://github.com/highcharts/highcharts/blob/b54fe33d91c0d1fd7da009aaa84af694f15cffad/js/parts/Axis.js#L4214 * * Modified by binh.nguyen@gooddata.com to support zero alignment */ Object.defineProperty(exports, "__esModule", { value: true }); var isNil = require("lodash/isNil"); var get = require("lodash/get"); var highchartsEntryPoint_1 = require("../highchartsEntryPoint"); var common_1 = require("../../../utils/common"); exports.ALIGNED = 0; exports.MOVE_ZERO_LEFT = -1; exports.MOVE_ZERO_RIGHT = 1; exports.Y_AXIS_SCORE = { NO_DATA: 0, ONLY_NEGATIVE_OR_POSITIVE_DATA: 1, NEGATIVE_AND_POSITIVE_DATA: 2, }; function getYAxes(chart) { return chart.axes.filter(isYAxis); } function isYAxis(axis) { return axis.coll === "yAxis"; } /** * Check if user sets min/max on any axis * @param chart * @return true if any axis is set min/max to. Otherwise false */ function isUserSetExtremesOnAnyAxis(chart) { var yAxes = chart.userOptions.yAxis; return yAxes[0].isUserMinMax || yAxes[1].isUserMinMax; } /** * Get direction to make secondary axis align to primary axis * @param primaryAxis * @param secondaryAxis * @return * -1: move zero index to left * 0: it aligns * 1: move zero index to right */ function getDirection(primaryAxis, secondaryAxis) { if (isNil(primaryAxis) || isNil(secondaryAxis)) { return exports.ALIGNED; } var _a = primaryAxis.tickPositions, primaryTickPositions = _a === void 0 ? [] : _a; var _b = secondaryAxis.tickPositions, secondaryTickPositions = _b === void 0 ? [] : _b; var primaryZeroIndex = primaryTickPositions.indexOf(0); var secondaryZeroIndex = secondaryTickPositions.indexOf(0); // no need to align zero on axes without zero if (primaryZeroIndex < 0 || secondaryZeroIndex < 0) { return exports.ALIGNED; } if (primaryZeroIndex > secondaryZeroIndex) { return exports.MOVE_ZERO_RIGHT; } if (primaryZeroIndex < secondaryZeroIndex) { return exports.MOVE_ZERO_LEFT; } return exports.ALIGNED; } exports.getDirection = getDirection; /** * Add new tick to first or last position * @param tickPositions * @param tickInterval * @param isAddFirst: if true, add to first. Otherwise, add to last */ function addTick(tickPositions, tickInterval, isAddFirst) { var tick = isAddFirst ? highchartsEntryPoint_1.default.correctFloat(tickPositions[0] - tickInterval) : highchartsEntryPoint_1.default.correctFloat(tickPositions[tickPositions.length - 1] + tickInterval); return isAddFirst ? [tick].concat(tickPositions) : tickPositions.concat([tick]); } /** * Add or reduce ticks * @param axis */ function adjustTicks(axis) { var tickPositions = (axis.tickPositions || []).slice(); var tickAmount = axis.tickAmount; var currentTickAmount = tickPositions.length; if (currentTickAmount === tickAmount) { return; } // add ticks to either start or end if (currentTickAmount < tickAmount) { var min = axis.min; var tickInterval = axis.tickInterval; while (tickPositions.length < tickAmount) { var isAddFirst = axis.dataMax <= 0 || // negative dataSet axis.max <= 0 || !(axis.dataMin >= 0 || // positive dataSet axis.min >= 0 || min === 0 || // default HC behavior tickPositions.length % 2 !== 0); tickPositions = addTick(tickPositions, tickInterval, isAddFirst); } } else { // reduce ticks var _a = getSelectionRange(axis), start = _a[0], end = _a[1]; tickPositions = tickPositions.slice(start, end); } axis.tickPositions = tickPositions.slice(); } exports.adjustTicks = adjustTicks; function getSelectionRange(axis) { var tickAmount = axis.tickAmount, tickPositions = axis.tickPositions, dataMin = axis.dataMin, dataMax = axis.dataMax; var currentTickAmount = tickPositions.length; if (dataMin >= 0) { return [currentTickAmount - tickAmount, currentTickAmount]; } if (dataMax <= 0) { return [0, tickAmount]; } var zeroIndex = tickPositions.indexOf(0); var firstTickToZero = Math.abs(0 - zeroIndex); var lastTickToZero = currentTickAmount - 1 - zeroIndex; // get range from furthest tick to zero if (firstTickToZero <= lastTickToZero) { return [0, tickAmount]; } return [currentTickAmount - tickAmount, currentTickAmount]; } exports.getSelectionRange = getSelectionRange; /** * Get axis score that increase 1 for data having positive and negative values * @param Y axis * @return Y axis score */ function getYAxisScore(axis) { var dataMin = axis.dataMin, dataMax = axis.dataMax; var yAxisMin = Math.min(0, dataMin); var yAxisMax = Math.max(0, dataMax); if (yAxisMin < 0 && yAxisMax > 0) { return exports.Y_AXIS_SCORE.NEGATIVE_AND_POSITIVE_DATA; } if (yAxisMin < 0 || yAxisMax > 0) { return exports.Y_AXIS_SCORE.ONLY_NEGATIVE_OR_POSITIVE_DATA; } return exports.Y_AXIS_SCORE.NO_DATA; } exports.getYAxisScore = getYAxisScore; /** * Base on axis score which is bigger than another, will become base axis * The other axis will be aligned to base axis * @param yAxes * @return base Y axis and aligned Y axis */ function getBaseYAxis(yAxes) { var _a = yAxes.map(getYAxisScore), firstAxisScore = _a[0], secondAxisScore = _a[1]; if (firstAxisScore >= secondAxisScore) { return { baseYAxis: yAxes[0], alignedYAxis: yAxes[1], }; } return { baseYAxis: yAxes[1], alignedYAxis: yAxes[0], }; } function alignToBaseAxis(yAxis, baseYAxis) { var tickInterval = yAxis.tickInterval; for (var direction = getDirection(baseYAxis, yAxis); direction !== exports.ALIGNED; direction = getDirection(baseYAxis, yAxis)) { var tickPositions = yAxis.tickPositions.slice(); if (direction === exports.MOVE_ZERO_RIGHT) { // add new tick to the start tickPositions = addTick(tickPositions, tickInterval, true); // remove last tick tickPositions = tickPositions.slice(0, tickPositions.length - 1); } else if (direction === exports.MOVE_ZERO_LEFT) { // add new tick to the end tickPositions = addTick(tickPositions, tickInterval, false); // remove first tick tickPositions = tickPositions.slice(1, tickPositions.length); } yAxis.tickPositions = tickPositions; } } exports.alignToBaseAxis = alignToBaseAxis; function updateAxis(axis, currentTickAmount) { var options = axis.options, tickPositions = axis.tickPositions; axis.transA *= (currentTickAmount - 1) / (Math.max(axis.tickAmount, 2) - 1); // avoid N/0 case axis.min = options.startOnTick ? tickPositions[0] : Math.min(axis.min, tickPositions[0]); axis.max = options.endOnTick ? tickPositions[tickPositions.length - 1] : Math.max(axis.max, tickPositions[tickPositions.length - 1]); } /** * Prevent data is cut off by increasing tick interval to zoom out axis * Only apply to chart without user-input min/max * @param axis */ function preventDataCutOff(axis) { var chart = axis.chart; var min = axis.min, max = axis.max, dataMin = axis.dataMin, dataMax = axis.dataMax; var isCutOff = !isUserSetExtremesOnAnyAxis(chart) && (min > dataMin || max < dataMax); if (!isCutOff) { return; } axis.tickInterval *= 2; axis.tickPositions = axis.tickPositions.map(function (value) { return value * 2; }); updateAxis(axis, axis.tickAmount); } exports.preventDataCutOff = preventDataCutOff; /** * Align axes once secondary axis is ready * Cause at the time HC finishes adjust primary axis, secondary axis has not been done yet * @param axis */ function alignYAxes(axis) { var chart = axis.chart; var yAxes = getYAxes(chart); var _a = getBaseYAxis(yAxes), baseYAxis = _a.baseYAxis, alignedYAxis = _a.alignedYAxis; var direction = getDirection(baseYAxis, alignedYAxis); var isReadyToAlign = axis.opposite && direction !== exports.ALIGNED; var hasLineChart = isAxisWithLineChartType(baseYAxis) || isAxisWithLineChartType(alignedYAxis); if (baseYAxis && alignedYAxis && isReadyToAlign && !hasLineChart) { alignToBaseAxis(alignedYAxis, baseYAxis); updateAxis(alignedYAxis, alignedYAxis.tickAmount); preventDataCutOff(alignedYAxis); } } /** * Copy and modify Highcharts behavior */ function customAdjustTickAmount() { var axis = this; if (!axis.hasData()) { return; } if (isYAxis(axis)) { // persist tick amount value to calculate transA in 'updateAxis' var currentTickAmount = (axis.tickPositions || []).length; adjustTicks(axis); updateAxis(axis, currentTickAmount); preventDataCutOff(axis); } // The finalTickAmt property is set in getTickAmount var finalTickAmt = axis.finalTickAmt; if (!isNil(finalTickAmt)) { var len = axis.tickPositions.length; var i = len; while (i--) { if ( // Remove every other tick (finalTickAmt === 3 && i % 2 === 1) || // Remove all but first and last (finalTickAmt <= 2 && i > 0 && i < len - 1)) { axis.tickPositions.splice(i, 1); } } axis.finalTickAmt = undefined; } } exports.customAdjustTickAmount = customAdjustTickAmount; function isAxisWithLineChartType(axis) { if (common_1.isLineChart(get(axis, "chart.userOptions.chart.type"))) { return true; } var series = axis.series; return series.reduce(function (result, item) { return common_1.isLineChart(item.type) ? true : result; }, false); } function isSingleAxisChart(axis) { var yAxes = getYAxes(axis.chart); return yAxes.length < 2; } /** * Decide whether run default or custom behavior * @param axis * @return true as leaving to HC, otherwise false as running custom behavior */ function shouldBeHandledByHighcharts(axis) { if (!isYAxis(axis) || isSingleAxisChart(axis) || isAxisWithLineChartType(axis)) { return true; } var yAxes = getYAxes(axis.chart); return yAxes.some(function (axis) { return axis.visible === false; }); } exports.shouldBeHandledByHighcharts = shouldBeHandledByHighcharts; exports.adjustTickAmount = function (HighchartsInstance) { highchartsEntryPoint_1.default.wrap(HighchartsInstance.Axis.prototype, "adjustTickAmount", function (proceed) { var axis = this; if (shouldBeHandledByHighcharts(axis)) { proceed.call(axis); } else { customAdjustTickAmount.call(axis); } if (!isSingleAxisChart(axis)) { alignYAxes(axis); } }); }; //# sourceMappingURL=adjustTickAmount.js.map