@gooddata/react-components
Version:
GoodData.UI - A powerful JavaScript library for building analytical applications
305 lines • 11.1 kB
JavaScript
;
// 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