UNPKG

@gooddata/react-components

Version:

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

389 lines • 13.2 kB
"use strict"; // 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