@gooddata/react-components
Version:
GoodData.UI - A powerful JavaScript library for building analytical applications
389 lines • 13.2 kB
JavaScript
;
// tslint:disable-line
/**
* Calculate new min/max to make Y axes aligned, and insert them to Highcharts config
*
* Inspired by
* Author: Christos Koumenides
* Page: https://www.highcharts.com/products/plugin-registry/single/42/Zero-align%20y-axes
* Github: https://github.com/chriskmnds/highcharts-zero-align-y-axes
*
* Modified by binh.nguyen@gooddata.com to support min/max configuration
*/
var __assign = (this && this.__assign) || function () {
__assign = Object.assign || function(t) {
for (var s, i = 1, n = arguments.length; i < n; i++) {
s = arguments[i];
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
t[p] = s[p];
}
return t;
};
return __assign.apply(this, arguments);
};
Object.defineProperty(exports, "__esModule", { value: true });
var partial = require("lodash/partial");
var isNil = require("lodash/isNil");
var zip = require("lodash/zip");
var sum = require("lodash/sum");
var compact = require("lodash/compact");
var getOptionalStackingConfiguration_1 = require("./getOptionalStackingConfiguration");
var common_1 = require("../../utils/common");
/**
* Check if user sets min or max
* @param minmax
* @param index
*/
function isMinMaxConfig(minmax, index) {
return minmax[index].isSetMin || minmax[index].isSetMax;
}
function isMinMaxConfigOnAnyAxis(minmax) {
return isMinMaxConfig(minmax, 0) || isMinMaxConfig(minmax, 1);
}
function minmaxCanon(minmax) {
var canon = [];
var extremes = ["min", "max"];
minmax.forEach(function (item, i) {
canon[i] = {};
extremes.forEach(function (extreme) {
if (item[extreme] === 0) {
canon[i][extreme] = 0;
}
else if (item[extreme] > 0) {
canon[i][extreme] = 1;
}
else {
canon[i][extreme] = -1;
}
});
});
return canon;
}
function getMinMaxLookup(minmax) {
return minmax.reduce(function (result, item) {
result[item.id] = item;
return result;
}, {});
}
function calculateMin(idx, minmax, minmaxLookup, axisIndex) {
var fraction = !minmax[idx].max
? minmax[idx].min // handle divide zero case
: minmax[idx].min / minmax[idx].max;
return fraction * minmaxLookup[axisIndex].max;
}
function calculateMax(idx, minmax, minmaxLookup, axisIndex) {
var fraction = !minmax[idx].min
? minmax[idx].max // handle divide zero case
: minmax[idx].max / minmax[idx].min;
return fraction * minmaxLookup[axisIndex].min;
}
/**
* Calculate min or max and return it
*
* For min, the calculation is based on axis having smallest min in case having min/max setting
* Otherwise, it is calculated base on axis having smaller min
*
* For max, the calculation is base on axis having largest max in case having min/max setting
* Otherwise, it is calculated base on axis having larger max
*
* @param minmax
* @param minmaxLookup
* @param axisIndex
* @param getIndex
* @param calculateLimit
* @param findExtreme
*/
function getLimit(minmax, minmaxLookup, axisIndex, getIndex, // TODO: make the types more specific (FET-282)
calculateLimit, // TODO: make the types more specific (FET-282)
findExtreme) {
var isMinMaxConfig = isMinMaxConfigOnAnyAxis(minmax);
if (isMinMaxConfig) {
var idx = getIndex(minmax);
return calculateLimit(idx, minmax, minmaxLookup, axisIndex);
}
return findExtreme([0, 1].map(function (index) { return calculateLimit(index, minmax, minmaxLookup, axisIndex); }));
}
function getMinMax(axisIndex, min, max, minmax) {
var minmaxLookup = getMinMaxLookup(minmax);
var axesCanon = minmaxCanon(minmax);
var getLimitPartial = partial(getLimit, minmax, minmaxLookup, axisIndex);
var _a = minmaxLookup[axisIndex], newMin = _a.min, newMax = _a.max;
var _b = minmaxLookup[axisIndex], isSetMin = _b.isSetMin, isSetMax = _b.isSetMax;
if (axesCanon[0].min <= 0 && axesCanon[0].max <= 0 && axesCanon[1].min <= 0 && axesCanon[1].max <= 0) {
// set 0 at top of chart
// ['----', '-0--', '---0']
newMax = Math.min(0, max);
}
else if (axesCanon[0].min >= 0 &&
axesCanon[0].max >= 0 &&
axesCanon[1].min >= 0 &&
axesCanon[1].max >= 0) {
// set 0 at bottom of chart
// ['++++', '0+++', '++0+']
newMin = Math.max(0, min);
}
else if (axesCanon[0].max === axesCanon[1].max) {
newMin = getLimitPartial(function (minmax) { return (minmax[0].min <= minmax[1].min ? 0 : 1); }, calculateMin, function (minOnAxes) { return Math.min(minOnAxes[0], minOnAxes[1]); });
}
else if (axesCanon[0].min === axesCanon[1].min) {
newMax = getLimitPartial(function (minmax) { return (minmax[0].max > minmax[1].max ? 0 : 1); }, calculateMax, function (maxOnAxes) { return Math.max(maxOnAxes[0], maxOnAxes[1]); });
}
else {
// set 0 at center of chart
// ['--++', '-0++', '--0+', '-00+', '++--', '++-0', '0+--', '0+-0']
if (minmaxLookup[axisIndex].min < 0) {
newMax = Math.abs(newMin);
}
else {
newMin = 0 - newMax;
}
}
return {
min: isSetMin ? minmaxLookup[axisIndex].min : newMin,
max: isSetMax ? minmaxLookup[axisIndex].max : newMax,
};
}
exports.getMinMax = getMinMax;
function getMinMaxInfo(config, stacking, type) {
var series = config.series, yAxis = config.yAxis;
var isStackedChart = !isNil(stacking);
return yAxis.map(function (axis, axisIndex) {
var isLineChartOnAxis = isLineChartType(series, axisIndex, type);
var seriesOnAxis = getSeriesOnAxis(series, axisIndex);
var min = axis.min, max = axis.max, opposite = axis.opposite;
var _a = isStackedChart && !isLineChartOnAxis
? getDataMinMaxOnStackedChart(seriesOnAxis, stacking, opposite)
: getDataMinMax(seriesOnAxis, isLineChartOnAxis), dataMin = _a.min, dataMax = _a.max;
return {
id: axisIndex,
min: isNil(min) ? dataMin : min,
max: isNil(max) ? dataMax : max,
isSetMin: min !== undefined,
isSetMax: max !== undefined,
};
});
}
exports.getMinMaxInfo = getMinMaxInfo;
/**
* Get series on related axis
* @param axisIndex
* @param series
*/
function getSeriesOnAxis(series, axisIndex) {
return series.filter(function (item) { return item.yAxis === axisIndex; });
}
/**
* Get y value in series
* @param series
*/
function getYDataInSeries(series) {
return series.data.map(function (item) { return item.y; });
}
/**
* Convert table of y value from row-view
* [
* [1, 2, 3],
* [4, 5, 6]
* ]
* to column-view
* [
* [1, [2, [3,
* 4] 5] 6]
* ]
* @param yData
*/
function getStackedYData(yData) {
return zip.apply(void 0, yData);
}
/**
* Get extreme on columns
* [
* [1, [2, [3,
* 4] 5] 6]
* ]
* @param columns
* @return [min, max]
*/
function getColumnExtremes(columns) {
return columns.reduce(function (result, item) {
var extreme = item < 0 ? "min" : "max";
result[extreme] += item;
return result;
}, { min: 0, max: 0 });
}
function getStackedDataMinMax(yData) {
var isEmpty = yData.length === 0;
var min = isEmpty ? 0 : Number.POSITIVE_INFINITY;
var max = isEmpty ? 0 : Number.NEGATIVE_INFINITY;
yData.forEach(function (column) {
var _a = getColumnExtremes(column), columnDataMin = _a.min, columnDataMax = _a.max;
min = Math.min(min, columnDataMin);
max = Math.max(max, columnDataMax);
});
return { min: min, max: max };
}
/**
* Convert number to percent base on total of column
* From
* [
* [1, [3, [4, [null, [20,
* 4] 7] -6] null], null]
* ]
* to
* [
* [20, [30, [40, [ , [100
* 80] 70] -60] ] ]
* ]
* @param yData
*/
function convertNumberToPercent(yData) {
return yData.map(function (columns) {
var columnsWithoutNull = compact(columns); // remove null values
var total = sum(columnsWithoutNull.map(function (num) { return Math.abs(num); }));
return columnsWithoutNull.map(function (num) { return (num / total) * 100; });
});
}
exports.convertNumberToPercent = convertNumberToPercent;
/**
* Get data min/max in stacked chart
* By comparing total of positive value to get max and total of negative value to get min
* @param series
* @param stacking
* @param opposite
*/
function getDataMinMaxOnStackedChart(series, stacking, opposite) {
var yData = series.map(getYDataInSeries);
var stackedYData = getStackedYData(yData);
if (stacking === getOptionalStackingConfiguration_1.PERCENT_STACK && !opposite) {
var percentData = convertNumberToPercent(stackedYData);
return getStackedDataMinMax(percentData);
}
return getStackedDataMinMax(stackedYData);
}
/**
* Get data min/max in normal chart
* By comparing min max value in all series in axis
* @param series
*/
function getDataMinMax(series, isLineChart) {
var _a = series.reduce(function (result, item) {
var yData = getYDataInSeries(item);
return {
min: Math.min.apply(Math, [result.min].concat(yData)),
max: Math.max.apply(Math, [result.max].concat(yData)),
};
}, {
min: Number.POSITIVE_INFINITY,
max: Number.NEGATIVE_INFINITY,
}), min = _a.min, max = _a.max;
return {
min: isLineChart ? min : Math.min(0, min),
max: isLineChart ? max : Math.max(0, max),
};
}
function isLineChartType(series, axisIndex, type) {
if (common_1.isLineChart(type)) {
return true;
}
if (common_1.isComboChart(type)) {
return getSeriesOnAxis(series, axisIndex).every(function (item) { return common_1.isLineChart(item.type); });
}
return false;
}
function getExtremeByChartTypeOnAxis(extreme, series, axisIndex, type) {
var isLineChartOnAxis = isLineChartType(series, axisIndex, type);
if (isLineChartOnAxis) {
return extreme;
}
return Math.min(0, extreme);
}
/**
* Check whether axis has invalid min/max
* @param minmax
*/
function hasInvalidAxis(minmax) {
return minmax.reduce(function (result, item) {
var min = item.min, max = item.max;
if (min >= max) {
return true;
}
return result;
}, false);
}
/**
* Hide invalid axis by setting 'visible' to false
* @param config
* @param minmax
* @param type
*/
function hideInvalidAxis(config, minmax, type) {
var series = config.series.map(function (item) {
var yAxis = item.yAxis, type = item.type;
return type ? { yAxis: yAxis, type: type } : { yAxis: yAxis };
});
var yAxis = minmax.map(function (item, index) {
var isLineChartOnAxis = isLineChartType(series, index, type);
var min = item.min, max = item.max;
var shouldInvisible = isLineChartOnAxis ? min > max : min >= max;
if (shouldInvisible) {
return {
visible: false,
};
}
return {};
});
yAxis.forEach(function (axis, index) {
var visible = axis.visible;
if (visible === false) {
series.forEach(function (item) {
if (item.yAxis === index) {
item.visible = false;
}
});
}
});
return { yAxis: yAxis, series: series };
}
/**
* Calculate new min/max to make Y axes aligned
* @param chartOptions
* @param config
*/
function getZeroAlignConfiguration(chartOptions, config) {
var stacking = chartOptions.stacking, type = chartOptions.type;
var yAxis = config.yAxis;
var isDualAxis = (yAxis || []).length === 2;
if (!isDualAxis) {
return {};
}
var minmax = getMinMaxInfo(config, stacking, type);
if (hasInvalidAxis(minmax)) {
return hideInvalidAxis(config, minmax, type);
}
if (minmax[0].isSetMin && minmax[0].isSetMax && minmax[1].isSetMin && minmax[1].isSetMax) {
// take user-input min/max, no need to calculate
// this 'isUserMinMax' acts as a flag,
// so that 'adjustTickAmount' plugin knows this min/max is either user input or calculated
return {
yAxis: [
{
isUserMinMax: true,
},
{
isUserMinMax: true,
},
],
};
}
// calculate min/max on both Y axes and set it to HighCharts yAxis config
var yAxisWithMinMax = [0, 1].map(function (axisIndex) {
var _a = minmax[axisIndex], min = _a.min, max = _a.max;
var newMinMax = getMinMax(axisIndex, getExtremeByChartTypeOnAxis(min, config.series, axisIndex, type), getExtremeByChartTypeOnAxis(max, config.series, axisIndex, type), minmax);
return __assign({ isUserMinMax: minmax[axisIndex].isSetMin || minmax[axisIndex].isSetMax }, newMinMax);
});
return {
yAxis: yAxisWithMinMax,
};
}
exports.getZeroAlignConfiguration = getZeroAlignConfiguration;
//# sourceMappingURL=getZeroAlignConfiguration.js.map