UNPKG

@gooddata/react-components

Version:

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

1,053 lines • 65.2 kB
"use strict"; 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 }); // (C) 2007-2020 GoodData Corporation var numberjs_1 = require("@gooddata/numberjs"); var typings_1 = require("@gooddata/typings"); var invariant = require("invariant"); var cloneDeep = require("lodash/cloneDeep"); var compact = require("lodash/compact"); var escape = require("lodash/escape"); var get = require("lodash/get"); var includes = require("lodash/includes"); var isEmpty = require("lodash/isEmpty"); var isEqual = require("lodash/isEqual"); var isUndefined = require("lodash/isUndefined"); var last = require("lodash/last"); var range = require("lodash/range"); var unescape = require("lodash/unescape"); var without = require("lodash/without"); var isNil = require("lodash/isNil"); var cx = require("classnames"); var bucketNames_1 = require("../../../constants/bucketNames"); var visualizationTypes_1 = require("../../../constants/visualizationTypes"); var executionResultHelper_1 = require("../../../helpers/executionResultHelper"); var utils_1 = require("../../../helpers/utils"); var mdObjBucketHelper_1 = require("../../../helpers/mdObjBucketHelper"); var headerPredicate_1 = require("../../../helpers/headerPredicate"); var color_1 = require("../utils/color"); var common_1 = require("../utils/common"); var drilldownEventing_1 = require("../utils/drilldownEventing"); var comboChartOptions_1 = require("./chartOptions/comboChartOptions"); var bulletChartOptions_1 = require("./chartOptions/bulletChartOptions"); var colorFactory_1 = require("./colorFactory"); var constants_1 = require("./constants"); var tooltip_1 = require("./tooltip"); var commonConfiguration_1 = require("./highcharts/commonConfiguration"); var helpers_1 = require("./highcharts/helpers"); var highChartsCreators_1 = require("./highChartsCreators"); var getOptionalStackingConfiguration_1 = require("./highcharts/getOptionalStackingConfiguration"); var extendedStackingChartOptions_1 = require("./chartOptions/extendedStackingChartOptions"); var dualAxis_1 = require("../../../helpers/dualAxis"); var domUtils_1 = require("../../../helpers/domUtils"); var omit = require("lodash/omit"); var bucketHelper_1 = require("../../../internal/utils/bucketHelper"); var TOOLTIP_PADDING = 10; var isAreaChartStackingEnabled = function (options) { var type = options.type, stacking = options.stacking, stackMeasures = options.stackMeasures; if (!common_1.isAreaChart(type)) { return false; } if (isUndefined(stackMeasures)) { return stacking || isUndefined(stacking); } return stackMeasures; }; // types with only many measures or one measure and one attribute var multiMeasuresAlternatingTypes = [ visualizationTypes_1.VisualizationTypes.PIE, visualizationTypes_1.VisualizationTypes.DONUT, visualizationTypes_1.VisualizationTypes.FUNNEL, visualizationTypes_1.VisualizationTypes.TREEMAP, ]; var unsupportedNegativeValuesTypes = [ visualizationTypes_1.VisualizationTypes.PIE, visualizationTypes_1.VisualizationTypes.DONUT, visualizationTypes_1.VisualizationTypes.FUNNEL, visualizationTypes_1.VisualizationTypes.TREEMAP, ]; // charts sorted by default by measure value var sortedByMeasureTypes = [visualizationTypes_1.VisualizationTypes.PIE, visualizationTypes_1.VisualizationTypes.DONUT, visualizationTypes_1.VisualizationTypes.FUNNEL]; var unsupportedStackingTypes = [ visualizationTypes_1.VisualizationTypes.LINE, visualizationTypes_1.VisualizationTypes.AREA, visualizationTypes_1.VisualizationTypes.SCATTER, visualizationTypes_1.VisualizationTypes.BUBBLE, ]; var nullColor = { pattern: { path: { d: "M 10 0 L 0 10 M 9 11 L 11 9 M 4 11 L 11 4 M -1 1 L 1 -1 M -1 6 L 6 -1", stroke: color_1.GRAY, strokeWidth: 1, fill: color_1.WHITE, }, width: 10, height: 10, }, }; exports.supportedDualAxesChartTypes = [ visualizationTypes_1.VisualizationTypes.COLUMN, visualizationTypes_1.VisualizationTypes.BAR, visualizationTypes_1.VisualizationTypes.BULLET, visualizationTypes_1.VisualizationTypes.LINE, visualizationTypes_1.VisualizationTypes.AREA, visualizationTypes_1.VisualizationTypes.COMBO, visualizationTypes_1.VisualizationTypes.COMBO2, ]; exports.supportedTooltipFollowPointerChartTypes = [ visualizationTypes_1.VisualizationTypes.COLUMN, visualizationTypes_1.VisualizationTypes.BAR, visualizationTypes_1.VisualizationTypes.BULLET, visualizationTypes_1.VisualizationTypes.COMBO, visualizationTypes_1.VisualizationTypes.COMBO2, ]; exports.supportedStackingAttributesChartTypes = [ visualizationTypes_1.VisualizationTypes.COLUMN, visualizationTypes_1.VisualizationTypes.BAR, visualizationTypes_1.VisualizationTypes.BULLET, visualizationTypes_1.VisualizationTypes.AREA, visualizationTypes_1.VisualizationTypes.COMBO, visualizationTypes_1.VisualizationTypes.COMBO2, ]; function isNegativeValueIncluded(series) { return series.some(function (seriesItem) { return (seriesItem.data || []).some(function (_a) { var y = _a.y, value = _a.value; return y < 0 || value < 0; }); }); } exports.isNegativeValueIncluded = isNegativeValueIncluded; function getChartLimits(type) { switch (type) { case visualizationTypes_1.VisualizationTypes.SCATTER: return { series: commonConfiguration_1.DEFAULT_SERIES_LIMIT, categories: commonConfiguration_1.DEFAULT_SERIES_LIMIT, }; case visualizationTypes_1.VisualizationTypes.PIE: case visualizationTypes_1.VisualizationTypes.DONUT: case visualizationTypes_1.VisualizationTypes.FUNNEL: return { series: 1, categories: constants_1.PIE_CHART_LIMIT, }; case visualizationTypes_1.VisualizationTypes.TREEMAP: return { series: commonConfiguration_1.DEFAULT_SERIES_LIMIT, categories: commonConfiguration_1.DEFAULT_DATA_POINTS_LIMIT, dataPoints: commonConfiguration_1.DEFAULT_DATA_POINTS_LIMIT, }; case visualizationTypes_1.VisualizationTypes.HEATMAP: return { series: commonConfiguration_1.DEFAULT_SERIES_LIMIT, categories: commonConfiguration_1.DEFAULT_CATEGORIES_LIMIT, dataPoints: constants_1.HEATMAP_DATA_POINTS_LIMIT, }; default: return { series: commonConfiguration_1.DEFAULT_SERIES_LIMIT, categories: commonConfiguration_1.DEFAULT_CATEGORIES_LIMIT, }; } } function cannotShowNegativeValues(type) { return common_1.isOneOfTypes(type, unsupportedNegativeValuesTypes); } exports.cannotShowNegativeValues = cannotShowNegativeValues; function getTreemapDataForValidation(data) { // filter out root nodes return __assign({}, data, { series: data.series.map(function (serie) { return (__assign({}, serie, { data: serie.data.filter(function (dataItem) { return dataItem.id === undefined; }) })); }) }); } function validateData(limits, chartOptions) { var type = chartOptions.type, isViewByTwoAttributes = chartOptions.isViewByTwoAttributes; var finalLimits = limits || getChartLimits(type); var dataToValidate = chartOptions.data; if (common_1.isTreemap(type)) { dataToValidate = getTreemapDataForValidation(chartOptions.data); } return { dataTooLarge: !highChartsCreators_1.isDataOfReasonableSize(dataToValidate, finalLimits, isViewByTwoAttributes), hasNegativeValue: cannotShowNegativeValues(type) && isNegativeValueIncluded(chartOptions.data.series), }; } exports.validateData = validateData; function isDerivedMeasure(measureItem, afm) { return afm.measures.some(function (measure) { var measureDefinition = get(measure, "definition.popMeasure") || get(measure, "definition.previousPeriodMeasure"); var derivedMeasureIdentifier = measureDefinition ? measure.localIdentifier : null; return (derivedMeasureIdentifier && derivedMeasureIdentifier === measureItem.measureHeaderItem.localIdentifier); }); } exports.isDerivedMeasure = isDerivedMeasure; function findMeasureIndex(afm, measureIdentifier) { return afm.measures.findIndex(function (measure) { return measure.localIdentifier === measureIdentifier; }); } function findParentMeasureIndex(afm, measureItemIndex) { var measureDefinition = afm.measures[measureItemIndex].definition; if (typings_1.AFM.isPopMeasureDefinition(measureDefinition)) { var sourceMeasureIdentifier = measureDefinition.popMeasure.measureIdentifier; return findMeasureIndex(afm, sourceMeasureIdentifier); } if (typings_1.AFM.isPreviousPeriodMeasureDefinition(measureDefinition)) { var sourceMeasureIdentifier = measureDefinition.previousPeriodMeasure.measureIdentifier; return findMeasureIndex(afm, sourceMeasureIdentifier); } return -1; } exports.findParentMeasureIndex = findParentMeasureIndex; function getSeriesItemData(seriesItem, seriesIndex, measureGroup, viewByAttribute, stackByAttribute, type, colorStrategy) { return seriesItem.map(function (pointValue, pointIndex) { // by default seriesIndex corresponds to measureGroup label index var measureIndex = seriesIndex; // by default pointIndex corresponds to viewBy label index var viewByIndex = pointIndex; // drillContext can have 1 to 3 items // viewBy attribute label, stackby label if available // last drillContextItem is always current serie measure if (stackByAttribute) { // pointIndex corresponds to viewBy attribute label (if available) viewByIndex = pointIndex; // stack bar chart has always just one measure measureIndex = 0; } else if (common_1.isOneOfTypes(type, multiMeasuresAlternatingTypes) && !viewByAttribute) { measureIndex = pointIndex; } var valueProp = { y: common_1.parseValue(pointValue), }; if (common_1.isTreemap(type)) { valueProp = { value: common_1.parseValue(pointValue), }; } var pointData = __assign({}, valueProp, { format: utils_1.unwrap(measureGroup.items[measureIndex]).format, marker: { enabled: pointValue !== null, } }); if (stackByAttribute) { // if there is a stackBy attribute, then seriesIndex corresponds to stackBy label index pointData.name = utils_1.unwrap(stackByAttribute.items[seriesIndex]).name; } else if (common_1.isOneOfTypes(type, multiMeasuresAlternatingTypes) && viewByAttribute) { pointData.name = utils_1.unwrap(viewByAttribute.items[viewByIndex]).name; } else { pointData.name = utils_1.unwrap(measureGroup.items[measureIndex]).name; } if (common_1.isOneOfTypes(type, multiMeasuresAlternatingTypes)) { pointData.color = colorStrategy.getColorByIndex(pointIndex); // Pie and Treemap charts use pointData viewByIndex as legendIndex if available // instead of seriesItem legendIndex pointData.legendIndex = viewByAttribute ? viewByIndex : pointIndex; } return pointData; }); } exports.getSeriesItemData = getSeriesItemData; function getHeatmapSeries(executionResultData, measureGroup) { var data = []; executionResultData.forEach(function (rowItem, rowItemIndex) { rowItem.forEach(function (columnItem, columnItemIndex) { var value = common_1.parseValue(String(columnItem)); var pointData = { x: columnItemIndex, y: rowItemIndex, value: value }; if (isNil(value)) { data.push(__assign({}, pointData, { borderWidth: 1, borderColor: color_1.GRAY, color: color_1.TRANSPARENT })); data.push(__assign({}, pointData, { borderWidth: 0, pointPadding: 2, color: nullColor, // ignoredInDrillEventContext flag is used internally, not related to Highchart // to check and remove this null-value point in drill message ignoredInDrillEventContext: true })); } else { data.push(pointData); } }); }); return [ { name: measureGroup.items[0].measureHeaderItem.name, data: data, turboThreshold: 0, yAxis: 0, dataLabels: { formatGD: utils_1.unwrap(measureGroup.items[0]).format, }, legendIndex: 0, }, ]; } exports.getHeatmapSeries = getHeatmapSeries; function getScatterPlotSeries(executionResultData, stackByAttribute, mdObject, colorStrategy) { var buckets = get(mdObject, "buckets", []); var primaryMeasuresBucketEmpty = mdObjBucketHelper_1.isBucketEmpty(buckets, bucketNames_1.MEASURES); var secondaryMeasuresBucketEmpty = mdObjBucketHelper_1.isBucketEmpty(buckets, bucketNames_1.SECONDARY_MEASURES); var data = executionResultData.map(function (seriesItem, seriesIndex) { var values = seriesItem.map(function (value) { return common_1.parseValue(value); }); return { x: !primaryMeasuresBucketEmpty ? values[0] : 0, y: !secondaryMeasuresBucketEmpty ? (primaryMeasuresBucketEmpty ? values[0] : values[1]) : 0, name: stackByAttribute ? stackByAttribute.items[seriesIndex].attributeHeaderItem.name : "", }; }); return [ { turboThreshold: 0, color: colorStrategy.getColorByIndex(0), legendIndex: 0, data: data, }, ]; } exports.getScatterPlotSeries = getScatterPlotSeries; function getCountOfEmptyBuckets(bucketEmptyFlags) { if (bucketEmptyFlags === void 0) { bucketEmptyFlags = []; } return bucketEmptyFlags.filter(function (bucketEmpyFlag) { return bucketEmpyFlag; }).length; } function getBubbleChartSeries(executionResultData, measureGroup, stackByAttribute, mdObject, colorStrategy) { var primaryMeasuresBucket = get(mdObject, ["buckets"], []).find(function (bucket) { return bucket.localIdentifier === bucketNames_1.MEASURES; }); var secondaryMeasuresBucket = get(mdObject, ["buckets"], []).find(function (bucket) { return bucket.localIdentifier === bucketNames_1.SECONDARY_MEASURES; }); var primaryMeasuresBucketEmpty = isEmpty(get(primaryMeasuresBucket, "items", [])); var secondaryMeasuresBucketEmpty = isEmpty(get(secondaryMeasuresBucket, "items", [])); return executionResultData.map(function (resData, index) { var data = []; if (resData[0] !== null && resData[1] !== null && resData[2] !== null) { var emptyBucketsCount = getCountOfEmptyBuckets([ primaryMeasuresBucketEmpty, secondaryMeasuresBucketEmpty, ]); data = [ { x: !primaryMeasuresBucketEmpty ? common_1.parseValue(resData[0]) : 0, y: !secondaryMeasuresBucketEmpty ? common_1.parseValue(resData[1 - emptyBucketsCount]) : 0, // we want to allow NaN on z to be able show bubble of default size when Size bucket is empty z: parseFloat(resData[2 - emptyBucketsCount]), format: utils_1.unwrap(last(measureGroup.items)).format, }, ]; } return { name: stackByAttribute ? stackByAttribute.items[index].attributeHeaderItem.name : "", color: colorStrategy.getColorByIndex(index), legendIndex: index, data: data, }; }); } exports.getBubbleChartSeries = getBubbleChartSeries; function getColorStep(valuesCount) { var MAX_COLOR_BRIGHTNESS = 0.8; return MAX_COLOR_BRIGHTNESS / valuesCount; } function gradientPreviousGroup(solidColorLeafs) { var colorChange = getColorStep(solidColorLeafs.length); return solidColorLeafs.map(function (leaf, index) { return (__assign({}, leaf, { color: color_1.getLighterColor(leaf.color, colorChange * index) })); }); } function getRootPoint(rootName, index, format, colorStrategy) { return { id: "" + index, name: rootName, color: colorStrategy.getColorByIndex(index), showInLegend: true, legendIndex: index, format: format, }; } function getLeafPoint(stackByAttribute, parentIndex, seriesIndex, data, format, colorStrategy) { return { name: stackByAttribute.items[seriesIndex].attributeHeaderItem.name, parent: "" + parentIndex, value: common_1.parseValue(data), x: seriesIndex, y: seriesIndex, showInLegend: false, color: colorStrategy.getColorByIndex(parentIndex), format: format, }; } function isLastSerie(seriesIndex, dataLength) { return seriesIndex === dataLength - 1; } function getTreemapStackedSeriesDataWithViewBy(executionResultData, measureGroup, viewByAttribute, stackByAttribute, colorStrategy) { var roots = []; var leafs = []; var rootId = -1; var uncoloredLeafs = []; var lastRoot = null; var dataLength = executionResultData.length; var format = utils_1.unwrap(measureGroup.items[0]).format; // this configuration has only one measure executionResultData.forEach(function (seriesItems, seriesIndex) { var currentRoot = viewByAttribute.items[seriesIndex].attributeHeaderItem; if (!isEqual(currentRoot, lastRoot)) { // store previous group leafs leafs.push.apply(leafs, gradientPreviousGroup(uncoloredLeafs)); rootId++; lastRoot = currentRoot; uncoloredLeafs = []; // create parent for pasted leafs var lastRootName = get(lastRoot, "name"); roots.push(getRootPoint(lastRootName, rootId, format, colorStrategy)); } // create leafs which will be colored at the end of group uncoloredLeafs.push(getLeafPoint(stackByAttribute, rootId, seriesIndex, seriesItems[0], format, colorStrategy)); if (isLastSerie(seriesIndex, dataLength)) { // store last group leafs leafs.push.apply(leafs, gradientPreviousGroup(uncoloredLeafs)); } }); return roots.concat(leafs); // roots need to be first items in data to keep legend working } exports.getTreemapStackedSeriesDataWithViewBy = getTreemapStackedSeriesDataWithViewBy; function getTreemapStackedSeriesDataWithMeasures(executionResultData, measureGroup, stackByAttribute, colorStrategy) { var data = []; measureGroup.items.reduce(function (data, measureGroupItem, index) { data.push({ id: "" + index, name: measureGroupItem.measureHeaderItem.name, format: measureGroupItem.measureHeaderItem.format, color: colorStrategy.getColorByIndex(index), showInLegend: true, legendIndex: index, }); return data; }, data); executionResultData.forEach(function (seriesItems, seriesIndex) { var colorChange = getColorStep(seriesItems.length); var unsortedLeafs = []; seriesItems.forEach(function (seriesItem, seriesItemIndex) { unsortedLeafs.push({ name: stackByAttribute.items[seriesItemIndex].attributeHeaderItem.name, parent: "" + seriesIndex, format: utils_1.unwrap(measureGroup.items[seriesIndex]).format, value: common_1.parseValue(seriesItem), x: seriesIndex, y: seriesItemIndex, showInLegend: false, }); }); var sortedLeafs = unsortedLeafs.sort(function (a, b) { return b.value - a.value; }); data = data.concat(sortedLeafs.map(function (leaf, seriesItemIndex) { return (__assign({}, leaf, { color: color_1.getLighterColor(colorStrategy.getColorByIndex(seriesIndex), colorChange * seriesItemIndex) })); })); }); return data; } exports.getTreemapStackedSeriesDataWithMeasures = getTreemapStackedSeriesDataWithMeasures; function getTreemapStackedSeries(executionResultData, measureGroup, viewByAttribute, stackByAttribute, colorStrategy) { var data = []; if (viewByAttribute) { data = getTreemapStackedSeriesDataWithViewBy(executionResultData, measureGroup, viewByAttribute, stackByAttribute, colorStrategy); } else { data = getTreemapStackedSeriesDataWithMeasures(executionResultData, measureGroup, stackByAttribute, colorStrategy); } var seriesName = measureGroup.items .map(function (wrappedMeasure) { return utils_1.unwrap(wrappedMeasure).name; }) .join(", "); return [ { name: seriesName, legendType: "point", showInLegend: true, data: data, turboThreshold: 0, }, ]; } exports.getTreemapStackedSeries = getTreemapStackedSeries; function getSeries(executionResultData, measureGroup, viewByAttribute, stackByAttribute, type, mdObject, colorStrategy, occupiedMeasureBucketsLocalIdentifiers) { if (common_1.isHeatmap(type)) { return getHeatmapSeries(executionResultData, measureGroup); } else if (common_1.isScatterPlot(type)) { return getScatterPlotSeries(executionResultData, stackByAttribute, mdObject, colorStrategy); } else if (common_1.isBubbleChart(type)) { return getBubbleChartSeries(executionResultData, measureGroup, stackByAttribute, mdObject, colorStrategy); } else if (common_1.isTreemap(type) && stackByAttribute) { return getTreemapStackedSeries(executionResultData, measureGroup, viewByAttribute, stackByAttribute, colorStrategy); } else if (common_1.isBulletChart(type)) { return bulletChartOptions_1.getBulletChartSeries(executionResultData, measureGroup, colorStrategy, occupiedMeasureBucketsLocalIdentifiers); } return executionResultData.map(function (seriesItem, seriesIndex) { var seriesItemData = getSeriesItemData(seriesItem, seriesIndex, measureGroup, viewByAttribute, stackByAttribute, type, colorStrategy); var seriesItemConfig = { color: colorStrategy.getColorByIndex(seriesIndex), legendIndex: seriesIndex, data: seriesItemData, }; if (stackByAttribute) { // if stackBy attribute is available, seriesName is a stackBy attribute value of index seriesIndex // this is a limitiation of highcharts and a reason why you can not have multi-measure stacked charts seriesItemConfig.name = stackByAttribute.items[seriesIndex].attributeHeaderItem.name; } else if (common_1.isOneOfTypes(type, multiMeasuresAlternatingTypes) && !viewByAttribute) { // Pie charts with measures only have a single series which name would is ambiguous seriesItemConfig.name = measureGroup.items .map(function (wrappedMeasure) { return utils_1.unwrap(wrappedMeasure).name; }) .join(", "); } else { // otherwise seriesName is a measure name of index seriesIndex seriesItemConfig.name = measureGroup.items[seriesIndex].measureHeaderItem.name; } var turboThresholdProp = common_1.isTreemap(type) ? { turboThreshold: 0 } : {}; return __assign({}, seriesItemConfig, turboThresholdProp); }); } exports.getSeries = getSeries; exports.customEscape = function (str) { return str && escape(unescape(str)); }; var renderTooltipHTML = function (textData, maxTooltipContentWidth) { var maxItemWidth = maxTooltipContentWidth - TOOLTIP_PADDING * 2; var titleMaxWidth = maxItemWidth; var multiLineTruncationSupported = domUtils_1.isCssMultiLineTruncationSupported(); var threeDotsWidth = 16; var valueMaxWidth = multiLineTruncationSupported ? maxItemWidth : maxItemWidth - threeDotsWidth; var titleStyle = "style=\"max-width: " + titleMaxWidth + "px;\""; var valueStyle = "style=\"max-width: " + valueMaxWidth + "px;\""; var itemClass = cx("gd-viz-tooltip-item", { "multiline-supported": multiLineTruncationSupported, }); var valueClass = cx("gd-viz-tooltip-value", { "clamp-two-line": multiLineTruncationSupported, }); return textData .map(function (item) { // the third span is hidden, that help to have tooltip work with max-width return "<div class=\"" + itemClass + "\">\n <span class=\"gd-viz-tooltip-title\" " + titleStyle + ">" + item[0] + "</span>\n <div class=\"gd-viz-tooltip-value-wraper\" " + titleStyle + ">\n <span class=\"" + valueClass + "\" " + valueStyle + ">" + item[1] + "</span>\n </div>\n <div class=\"gd-viz-tooltip-value-wraper\" " + titleStyle + ">\n <span class=\"gd-viz-tooltip-value-max-content\" " + valueStyle + ">" + item[1] + "</span>\n </div>\n </div>"; }) .join("\n"); }; function isPointOnOppositeAxis(point) { return get(point, ["series", "yAxis", "opposite"], false); } function buildTooltipFactory(viewByAttribute, type, config, isDualAxis) { if (config === void 0) { config = {}; } if (isDualAxis === void 0) { isDualAxis = false; } var separators = config.separators, _a = config.stackMeasuresToPercent, stackMeasuresToPercent = _a === void 0 ? false : _a; return function (point, maxTooltipContentWidth, percentageValue) { var isDualChartWithRightAxis = isDualAxis && isPointOnOppositeAxis(point); var formattedValue = tooltip_1.getFormattedValueForTooltip(isDualChartWithRightAxis, stackMeasuresToPercent, point, separators, percentageValue); var textData = [[exports.customEscape(point.series.name), formattedValue]]; if (viewByAttribute) { // For some reason, highcharts ommit categories for pie charts with attribute. Use point.name instead. // use attribute name instead of attribute display form name textData.unshift([ exports.customEscape(viewByAttribute.formOf.name), // since applying 'grouped-categories' plugin, // 'category' type is replaced from string to object in highchart exports.customEscape((point.category && point.category.name) || point.name), ]); } else if (common_1.isOneOfTypes(type, multiMeasuresAlternatingTypes)) { // Pie charts with measure only have to use point.name instead of series.name to get the measure name textData[0][0] = exports.customEscape(point.name); } return renderTooltipHTML(textData, maxTooltipContentWidth); }; } exports.buildTooltipFactory = buildTooltipFactory; function buildTooltipForTwoAttributesFactory(viewByAttribute, viewByParentAttribute, config, isDualAxis) { if (config === void 0) { config = {}; } if (isDualAxis === void 0) { isDualAxis = false; } var separators = config.separators, _a = config.stackMeasuresToPercent, stackMeasuresToPercent = _a === void 0 ? false : _a; return function (point, maxTooltipContentWidth, percentageValue) { var category = point.category; var isDualChartWithRightAxis = isDualAxis && isPointOnOppositeAxis(point); var formattedValue = tooltip_1.getFormattedValueForTooltip(isDualChartWithRightAxis, stackMeasuresToPercent, point, separators, percentageValue); var textData = [[exports.customEscape(point.series.name), formattedValue]]; if (category) { if (viewByAttribute) { textData.unshift([exports.customEscape(viewByAttribute.formOf.name), exports.customEscape(category.name)]); } if (viewByParentAttribute && category.parent) { textData.unshift([ exports.customEscape(viewByParentAttribute.formOf.name), exports.customEscape(category.parent.name), ]); } } return renderTooltipHTML(textData, maxTooltipContentWidth); }; } exports.buildTooltipForTwoAttributesFactory = buildTooltipForTwoAttributesFactory; function generateTooltipXYFn(measures, stackByAttribute, config) { if (config === void 0) { config = {}; } var separators = config.separators; return function (point, maxTooltipContentWidth) { var textData = []; var name = point.name ? point.name : point.series.name; if (stackByAttribute) { textData.unshift([exports.customEscape(stackByAttribute.formOf.name), exports.customEscape(name)]); } if (measures[0]) { textData.push([ exports.customEscape(measures[0].measureHeaderItem.name), tooltip_1.formatValueForTooltip(point.x, measures[0].measureHeaderItem.format, separators), ]); } if (measures[1]) { textData.push([ exports.customEscape(measures[1].measureHeaderItem.name), tooltip_1.formatValueForTooltip(point.y, measures[1].measureHeaderItem.format, separators), ]); } if (measures[2]) { textData.push([ exports.customEscape(measures[2].measureHeaderItem.name), tooltip_1.formatValueForTooltip(point.z, measures[2].measureHeaderItem.format, separators), ]); } return renderTooltipHTML(textData, maxTooltipContentWidth); }; } exports.generateTooltipXYFn = generateTooltipXYFn; function generateTooltipHeatmapFn(viewByAttribute, stackByAttribute, config) { if (config === void 0) { config = {}; } var separators = config.separators; var formatValue = function (val, format) { return numberjs_1.colors2Object(val === null ? "-" : numberjs_1.numberFormat(val, format, undefined, separators)); }; return function (point, maxTooltipContentWidth) { var formattedValue = exports.customEscape(formatValue(point.value, point.series.userOptions.dataLabels.formatGD).label); var textData = []; textData.unshift([exports.customEscape(point.series.name), formattedValue]); if (viewByAttribute) { textData.unshift([ exports.customEscape(viewByAttribute.formOf.name), exports.customEscape(viewByAttribute.items[point.x].attributeHeaderItem.name), ]); } if (stackByAttribute) { textData.unshift([ exports.customEscape(stackByAttribute.formOf.name), exports.customEscape(stackByAttribute.items[point.y].attributeHeaderItem.name), ]); } return renderTooltipHTML(textData, maxTooltipContentWidth); }; } exports.generateTooltipHeatmapFn = generateTooltipHeatmapFn; function buildTooltipTreemapFactory(viewByAttribute, stackByAttribute, config) { if (config === void 0) { config = {}; } var separators = config.separators; return function (point, maxTooltipContentWidth) { // show tooltip for leaf node only if (!point.node || point.node.isLeaf === false) { return null; } var formattedValue = tooltip_1.formatValueForTooltip(point.value, point.format, separators); var textData = []; if (stackByAttribute) { textData.push([ exports.customEscape(stackByAttribute.formOf.name), exports.customEscape(stackByAttribute.items[point.y].attributeHeaderItem.name), ]); } if (viewByAttribute) { textData.unshift([ exports.customEscape(viewByAttribute.formOf.name), exports.customEscape(viewByAttribute.items[point.x].attributeHeaderItem.name), ]); textData.push([exports.customEscape(point.series.name), formattedValue]); } else { textData.push([exports.customEscape(point.category && point.category.name), formattedValue]); } return renderTooltipHTML(textData, maxTooltipContentWidth); }; } exports.buildTooltipTreemapFactory = buildTooltipTreemapFactory; function isLegacyAttributeHeader(header) { return header.attribute !== undefined; } exports.isLegacyAttributeHeader = isLegacyAttributeHeader; function getViewBy(viewByAttribute, viewByIndex) { var viewByItemHeader = null; var viewByAttributeHeader = null; if (viewByAttribute) { viewByItemHeader = viewByAttribute.items[viewByIndex]; viewByAttributeHeader = { attributeHeader: omit(viewByAttribute, "items") }; } return { viewByItemHeader: viewByItemHeader, viewByAttributeHeader: viewByAttributeHeader, }; } function getStackBy(stackByAttribute, stackByIndex) { var stackByItemHeader = null; var stackByAttributeHeader = null; if (stackByAttribute) { // stackBy item index is always equal to seriesIndex stackByItemHeader = stackByAttribute.items[stackByIndex]; stackByAttributeHeader = { attributeHeader: omit(stackByAttribute, "items") }; } return { stackByItemHeader: stackByItemHeader, stackByAttributeHeader: stackByAttributeHeader, }; } function getDrillableSeries(series, drillableItems, viewByAttributes, stackByAttribute, executionResponse, afm, type) { var viewByChildAttribute = viewByAttributes[0], viewByParentAttribute = viewByAttributes[1]; var isMultiMeasureWithOnlyMeasures = common_1.isOneOfTypes(type, multiMeasuresAlternatingTypes) && !viewByChildAttribute; var measureGroup = executionResultHelper_1.findMeasureGroupInDimensions(executionResponse.dimensions); return series.map(function (seriesItem, seriesIndex) { var isSeriesDrillable = false; var data = seriesItem.data && seriesItem.data.map(function (pointData, pointIndex) { var measureHeaders = []; var isStackedTreemap = common_1.isTreemap(type) && !!stackByAttribute; if (common_1.isScatterPlot(type)) { measureHeaders = get(measureGroup, "items", []).slice(0, 2); } else if (common_1.isBubbleChart(type)) { measureHeaders = get(measureGroup, "items", []).slice(0, 3); } else if (isStackedTreemap) { if (pointData.id !== undefined) { // not leaf -> can't be drillable return pointData; } var measureIndex = viewByChildAttribute ? 0 : parseInt(pointData.parent, 10); measureHeaders = [measureGroup.items[measureIndex]]; } else { // measureIndex is usually seriesIndex, // except for stack by attribute and metricOnly pie or donut chart // it is looped-around pointIndex instead // Looping around the end of items array only works when // measureGroup is the last header on it's dimension // We do not support setups with measureGroup before attributeHeaders var measureIndex = !stackByAttribute && !isMultiMeasureWithOnlyMeasures ? seriesIndex : pointIndex % measureGroup.items.length; measureHeaders = [measureGroup.items[measureIndex]]; } var viewByIndex = common_1.isHeatmap(type) || isStackedTreemap ? pointData.x : pointIndex; var stackByIndex = common_1.isHeatmap(type) || isStackedTreemap ? pointData.y : seriesIndex; if (common_1.isScatterPlot(type)) { stackByIndex = viewByIndex; // scatter plot uses stack by attribute but has only one serie } var _a = getStackBy(stackByAttribute, stackByIndex), stackByItemHeader = _a.stackByItemHeader, stackByAttributeHeader = _a.stackByAttributeHeader; var _b = getViewBy(viewByChildAttribute, viewByIndex), viewByChildItemHeader = _b.viewByItemHeader, viewByChildAttributeHeader = _b.viewByAttributeHeader; var _c = getViewBy(viewByParentAttribute, viewByIndex), viewByParentItemHeader = _c.viewByItemHeader, viewByParentAttributeHeader = _c.viewByAttributeHeader; // point is drillable if a drillableItem matches: // point's measure, // point's viewBy attribute, // point's viewBy attribute item, // point's stackBy attribute, // point's stackBy attribute item, var drillableHooks = without(measureHeaders.concat([ viewByChildAttributeHeader, viewByChildItemHeader, viewByParentAttributeHeader, viewByParentItemHeader, stackByAttributeHeader, stackByItemHeader, ]), null); var drilldown = drillableHooks.some(function (drillableHook) { return headerPredicate_1.isSomeHeaderPredicateMatched(drillableItems, drillableHook, afm, executionResponse); }); var drillableProps = { drilldown: drilldown, }; if (drilldown) { var headers = measureHeaders.concat([ viewByChildItemHeader, viewByChildAttributeHeader, viewByParentItemHeader, viewByParentAttributeHeader, stackByItemHeader, stackByAttributeHeader, ]); var sanitizedHeaders = without(headers.slice(), null); drillableProps.drillIntersection = drilldownEventing_1.getDrillIntersection(sanitizedHeaders); isSeriesDrillable = true; } return __assign({}, pointData, drillableProps); }); if (common_1.isScatterPlot(type)) { data = data.filter(function (dataItem) { return dataItem.x !== null && dataItem.y !== null; }); } return __assign({}, seriesItem, { data: data, isDrillable: isSeriesDrillable }); }); } exports.getDrillableSeries = getDrillableSeries; function getCategories(type, measureGroup, viewByAttribute, stackByAttribute) { if (common_1.isHeatmap(type)) { return [ viewByAttribute ? viewByAttribute.items.map(function (item) { return item.attributeHeaderItem.name; }) : [""], stackByAttribute ? stackByAttribute.items.map(function (item) { return item.attributeHeaderItem.name; }) : [""], ]; } if (common_1.isScatterPlot(type)) { return stackByAttribute ? stackByAttribute.items.map(function (item) { return item.attributeHeaderItem.name; }) : [""]; } // Categories make up bar/slice labels in charts. These usually match view by attribute values. // Measure only pie or treemap charts get categories from measure names if (viewByAttribute) { return viewByAttribute.items.map(function (_a) { var attributeHeaderItem = _a.attributeHeaderItem; return attributeHeaderItem.name; }); } if (common_1.isOneOfTypes(type, multiMeasuresAlternatingTypes)) { // Pie or Treemap chart with measures only (no viewByAttribute) needs to list return measureGroup.items.map(function (wrappedMeasure) { return utils_1.unwrap(wrappedMeasure).name; }); // Pie chart categories are later sorted by seriesItem pointValue } return []; } function getStackingConfig(stackByAttribute, options) { var type = options.type, stackMeasures = options.stackMeasures, stackMeasuresToPercent = options.stackMeasuresToPercent; var stackingValue = stackMeasuresToPercent ? getOptionalStackingConfiguration_1.PERCENT_STACK : getOptionalStackingConfiguration_1.NORMAL_STACK; var supportsStacking = !common_1.isOneOfTypes(type, unsupportedStackingTypes); /** * we should enable stacking for one of the following cases : * 1) If stackby attribute have been set and chart supports stacking * 2) If chart is an area chart and stacking is enabled (stackBy attribute doesn't matter) * 3) If chart is column/bar chart and 'Stack Measures' is enabled */ var isStackByChart = stackByAttribute && supportsStacking; var isAreaChartWithEnabledStacking = isAreaChartStackingEnabled(options); if (isStackByChart || isAreaChartWithEnabledStacking || stackMeasures || stackMeasuresToPercent) { return stackingValue; } return null; // no stacking } function preprocessMeasureGroupItems(measureGroup, defaultValues) { return measureGroup.items.map(function (item, index) { var unwrapped = utils_1.unwrap(item); return index ? { label: unwrapped.name, format: unwrapped.format, } : { label: defaultValues.label || unwrapped.name, format: defaultValues.format || unwrapped.format, }; }); } function getXAxes(config, measureGroup, viewByAttribute) { var type = config.type, mdObject = config.mdObject; var buckets = get(mdObject, "buckets", []); var measureGroupItems = preprocessMeasureGroupItems(measureGroup, { label: config.xLabel, format: config.xFormat, }); var firstMeasureGroupItem = measureGroupItems[0]; if (common_1.isScatterPlot(type) || common_1.isBubbleChart(type)) { var noPrimaryMeasures = mdObjBucketHelper_1.isBucketEmpty(buckets, bucketNames_1.MEASURES); if (noPrimaryMeasures) { return [ { label: "", }, ]; } else { return [ { label: firstMeasureGroupItem.label || "", format: firstMeasureGroupItem.format || "", }, ]; } } var xLabel = config.xLabel || (viewByAttribute ? viewByAttribute.formOf.name : ""); return [ { label: xLabel, }, ]; } function getMeasureFormatKey(measureGroupItems) { var percentageFormat = getMeasureFormat(measureGroupItems.find(function (measure) { return isPercentage(getMeasureFormat(measure)); })); return percentageFormat !== "" ? { format: percentageFormat, } : {}; } function getMeasureFormat(measure) { return get(measure, "format", ""); } function isPercentage(format) { return format.includes("%"); } function getYAxes(config, measureGroup, stackByAttribute) { var type = config.type, mdObject = config.mdObject; var buckets = get(mdObject, "buckets", []); var measureGroupItems = preprocessMeasureGroupItems(measureGroup, { label: config.yLabel, format: config.yFormat, }); var firstMeasureGroupItem = measureGroupItems[0]; var secondMeasureGroupItem = measureGroupItems[1]; var hasMoreThanOneMeasure = measureGroupItems.length > 1; var noPrimaryMeasures = mdObjBucketHelper_1.isBucketEmpty(buckets, bucketNames_1.MEASURES); var _a = ((common_1.isBarChart(type) ? config.secondary_xaxis : config.secondary_yaxis) || {}).measures, secondaryAxisMeasures = _a === void 0 ? [] : _a; var yAxes = []; if (common_1.isScatterPlot(type) || common_1.isBubbleChart(type)) { var hasSecondaryMeasure = get(mdObject, "buckets", []).find(function (m) { return m.localIdentifier === bucketNames_1.SECONDARY_MEASURES && m.items.length > 0; }); if (hasSecondaryMeasure) { if (noPrimaryMeasures) { yAxes = [ __assign({}, firstMeasureGroupItem), ]; } else { yAxes = [ __assign({}, secondMeasureGroupItem), ]; } } else { yAxes = [{ label: "" }]; } } else if (common_1.isHeatmap(type)) { yAxes = [ { label: stackByAttribute ? stackByAttribute.formOf.name : "", }, ]; } else if (common_1.isOneOfTypes(type, exports.supportedDualAxesChartTypes) && !isEmpty(measureGroupItems) && !isEmpty(secondaryAxisMeasures)) { var _b = assignMeasuresToAxes(secondaryAxisMeasures, measureGroup), measuresInFirstAxis = _b.measuresInFirstAxis, measuresInSecondAxis = _b.measuresInSecondAxis; var firstAxis = createYAxisItem(measuresInFirstAxis, false); var secondAxis = createYAxisItem(measuresInSecondAxis, true); if (firstAxis) { firstAxis = __assign({}, firstAxis, getMeasureFormatKey(measuresInFirstAxis), { seriesIndices: measuresInFirstAxis.map(function (_a) { var index = _a.index; return index; }) }); } if (secondAxis) { secondAxis = __assign({}, secondAxis, getMeasureFormatKey(measuresInSecondAxis), { seriesIndices: measuresInSecondAxis.map(function (_a) { var index = _a.index; return index; }) }); } yAxes = compact([firstAxis, secondAxis]); } else { // if more than one measure and NOT dual, then have empty item name var nonDualMeasureAxis = hasMoreThanOneMeasure ? { label: "", } : {}; yAxes = [ __assign({}, firstMeasureGroupItem, nonDualMeasureAxis, { seriesIndices: range(measureGroupItems.length) }, getMeasureFormatKey(measureGroupItems)), ]; } return yAxes; } function assignMeasuresToAxes(secondMeasures, measureGroup) { return measureGroup.items.reduce(function (result, _a, index) { var _b = _a.measureHeaderItem, name = _b.name, format = _b.format, localIdentifier = _b.localIdentifier; if (includes(secondMeasures, localIdentifier)) { result.measuresInSecondAxis.push({ name: name, format: format, index: index }); } else { result.measuresInFirstAxis.push({ name: name, format: format, index: index }); } return result; }, { measuresInFirstAxis: [], measuresInSecondAxis: [], }); } function createYAxisItem(measuresInAxis, opposite) { if (opposite === void 0) { opposite = false; } var length = measuresInAxis.length; if (length) { var _a = measuresInAxis[0], name_1 = _a.name, format = _a.format; return { label: length === 1 ? name_1 : "", format: format, opposite: opposite, }; } return null; } function assignYAxes(series, yAxes) { return series.reduce(function (result, item, index) { var yAxisIndex = yAxes.findIndex(function (axis) { return includes(get(axis, "seriesIndices", []), index); }); // for case viewBy and stackBy have one attribute, and one measure is sliced to multiple series // then 'yAxis' in other series should follow the first one var firstYAxisIndex = result.lenght > 0 ? result[0].yAxis : 0; var seriesItem = __assign({}, item, { yAxis: yAxisIndex !== -1 ? yAxisIndex : firstYAxisIndex }); result.push(seriesItem); return result; }, []); } exports.HEAT_MAP_CATEGORIES_COUNT = 7; exports.HIGHCHARTS_PRECISION = 15; exports.DEFAULT_HEATMAP_COLOR_INDEX = 1; function getHeatmapDataClasses(series, colorStrategy) { if (series === void 0) { series = []; } var values = without(get(series, "0.data", []).map(function (item) { return item.value; }), null, undefined, NaN); if (isEmpty(values)) { return []; } var min = Math.min.apply(Math, values); var max = Math.max.apply(Math, values); var safeMin = parseFloat(Number(min).toPrecision(exports.HIGHCHARTS_PRECISION)); var safeMax = parseFloat(Number(max).toPrecision(exports.HIGHCHARTS_PRECISION)); var dataClasses = []; if (min === max) { dataClasses.push({ from: min, to: max, color: colorStrategy.getColorByIndex(exports.DEFAULT_HEATMAP_COLOR_INDEX), }); } else { var step = (safeMax - safeMin) / exports.HEAT_MAP_CATEGORIES_COUNT; var currentSum = safeMin; for (var i = 0; i < exports.HEAT_MAP_CATEGORIES_COUNT; i += 1) { dataClasses.push({ from: currentSum, to: i === exports.HEAT_MAP_CATEGORIES_COUNT - 1 ? safeMax : currentSum + step, color: colorStrategy.getColorByIndex(i), }); currentSum += step; } } return dataClasses; } exports.getHeatmapDataClasses = getHeatmapDataClasses; function getDefaultTreemapAttributes(dimensions, attributeHeaderItems) { var viewByAttribute = executionRe