UNPKG

@gooddata/react-components

Version:

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

1,314 lines (1,165 loc) • 156 kB
// (C) 2007-2020 GoodData Corporation import range = require("lodash/range"); import get = require("lodash/get"); import set = require("lodash/set"); import isNil = require("lodash/isNil"); import cloneDeep = require("lodash/cloneDeep"); import { Execution } from "@gooddata/typings"; import Highcharts from "../highcharts/highchartsEntryPoint"; import { findMeasureGroupInDimensions } from "../../../../helpers/executionResultHelper"; import { immutableSet } from "../../utils/common"; import { isNegativeValueIncluded, validateData, getSeriesItemData, getSeries, getDrillableSeries, customEscape, buildTooltipFactory, buildTooltipForTwoAttributesFactory, generateTooltipHeatmapFn, generateTooltipXYFn, buildTooltipTreemapFactory, getBubbleChartSeries, getHeatmapDataClasses, getTreemapAttributes, isDerivedMeasure, IValidationResult, getHeatmapSeries, } from "../chartOptionsBuilder"; import { DEFAULT_CATEGORIES_LIMIT } from "../highcharts/commonConfiguration"; import { generateChartOptions, getMVS, getMVSForViewByTwoAttributes } from "./helper"; import * as headerPredicateFactory from "../../../../factory/HeaderPredicateFactory"; import * as fixtures from "../../../../../stories/test_data/fixtures"; import { PIE_CHART_LIMIT, STACK_BY_DIMENSION_INDEX } from "../constants"; import { DEFAULT_COLOR_PALETTE, getLighterColor, getRgbString, GRAY, TRANSPARENT } from "../../utils/color"; import { IColorStrategy } from "../colorFactory"; import { IChartConfig, IColorPaletteItem, IPointData, IChartOptions, IMeasuresStackConfig, } from "../../../../interfaces/Config"; import { VisualizationTypes } from "../../../../constants/visualizationTypes"; import { NORMAL_STACK, PERCENT_STACK } from "../highcharts/getOptionalStackingConfiguration"; import HeatmapColorStrategy from "../colorStrategies/heatmap"; import TreemapColorStrategy from "../colorStrategies/treemap"; import BubbleChartColorStrategy from "../colorStrategies/bubbleChart"; import MeasureColorStrategy from "../colorStrategies/measure"; import AttributeColorStrategy from "../colorStrategies/attribute"; const FIRST_DEFAULT_COLOR_ITEM_AS_STRING = getRgbString(DEFAULT_COLOR_PALETTE[0]); const SECOND_DEFAULT_COLOR_ITEM_AS_STRING = getRgbString(DEFAULT_COLOR_PALETTE[1]); function getMVSTreemap(dataSet: any) { const { executionResponse: { dimensions }, executionResult: { headerItems }, mdObject, } = dataSet; const measureGroup = findMeasureGroupInDimensions(dimensions); const { viewByAttribute, stackByAttribute } = getTreemapAttributes(dimensions, headerItems, mdObject); return { measureGroup, viewByAttribute, stackByAttribute, }; } function getSeriesItemDataParameters(dataSet: any, seriesIndex: any) { const seriesItem = dataSet.executionResult.data[seriesIndex]; const { measureGroup, viewByAttribute, stackByAttribute } = getMVS(dataSet); return [seriesItem, seriesIndex, measureGroup, viewByAttribute, stackByAttribute]; } describe("chartOptionsBuilder", () => { const DEFAULT_TOOLTIP_CONTENT_WIDTH = 320; const SMALL_TOOLTIP_CONTENT_WIDTH = 200; const { COLUMN, LINE, COMBO } = VisualizationTypes; const barChartWithStackByAndViewByAttributesOptions = generateChartOptions(); const barChartWith3MetricsAndViewByAttributeOptions = generateChartOptions( fixtures.barChartWith3MetricsAndViewByAttribute, ); function getValues(str: string): string[] { const strWithoutHiddenSpan = str.replace(/<span[^><]+max-content[^<>]+>[^<]+<\/span>/g, ""); const test = />([^<]+)<\/span>/g; const result = strWithoutHiddenSpan.match(test).map((match: string) => match.slice(1, -7)); return (result && result.length) >= 2 ? result : null; } function getStyleMaxWidth(str: string): string[] { const strWithoutHiddenSpan = str.replace(/<span[^><]+max-content[^<>]+>[^<]+<\/span>/g, ""); const testRegex = /max-width: ([^;:]+)px;/g; return strWithoutHiddenSpan.match(testRegex).map((match: string): string => match.slice(11, -3)); } const pieAndTreemapDataSet = { ...fixtures.pieChartWithMetricsOnly, executionResult: { ...fixtures.pieChartWithMetricsOnly.executionResult, data: [["-1", "38310753.45", "9011389.956"]], }, }; const pieChartOptionsWithNegativeValue = generateChartOptions(pieAndTreemapDataSet, { type: "pie" }); const treemapOptionsWithNegativeValue = generateChartOptions(pieAndTreemapDataSet, { type: "treemap" }); const pieChartWithMetricsOnlyOptions: any = generateChartOptions( { ...fixtures.pieChartWithMetricsOnly, }, { type: "pie", }, ); const pointForSmallCharts = { node: { isLeaf: true, }, value: 300, x: 0, y: 0, series: { chart: { plotWidth: 200, }, name: "name", userOptions: { dataLabels: { formatGD: "abcd", }, }, }, }; describe("isNegativeValueIncluded", () => { it("should return true if there is at least one negative value in series", () => { expect(isNegativeValueIncluded(pieChartOptionsWithNegativeValue.data.series)).toBe(true); }); it("should return false if there are no negative values in series", () => { expect(isNegativeValueIncluded(pieChartWithMetricsOnlyOptions.data.series)).toBe(false); }); }); describe("validateData", () => { describe("user supplied limits", () => { it('should validate with "dataTooLarge: true" against series limit', () => { const validationResult = validateData( { series: 1, }, barChartWith3MetricsAndViewByAttributeOptions, ); expect(validationResult).toEqual({ dataTooLarge: true, hasNegativeValue: false, }); }); it('should validate with "dataTooLarge: true" against categories limit', () => { const validationResult = validateData( { categories: 1, }, barChartWith3MetricsAndViewByAttributeOptions, ); expect(validationResult).toEqual({ dataTooLarge: true, hasNegativeValue: false, }); }); }); describe("default limits", () => { it("should be able to validate successfully", () => { const chartOptions = barChartWithStackByAndViewByAttributesOptions; const validationResult = validateData(undefined, chartOptions); expect(validationResult).toEqual({ dataTooLarge: false, hasNegativeValue: false, }); }); it( 'should validate with "dataTooLarge: true" against default chart categories limit ' + `of ${DEFAULT_CATEGORIES_LIMIT}`, () => { const chartOptions = generateChartOptions( fixtures.barChartWith3MetricsAndViewByAttribute, ); chartOptions.data.categories = range(DEFAULT_CATEGORIES_LIMIT + 1); const validationResult = validateData(undefined, chartOptions); expect(validationResult).toEqual({ dataTooLarge: true, hasNegativeValue: false, }); }, ); it('should validate with "dataTooLarge: true" against default pie chart series limit of 1', () => { const chartOptions = generateChartOptions(fixtures.barChartWith3MetricsAndViewByAttribute, { type: "pie", }); const validationResult = validateData(undefined, chartOptions); expect(validationResult).toEqual({ dataTooLarge: true, hasNegativeValue: false, }); }); it( 'should validate with "dataTooLarge: true" against default' + `pie chart categories limit of ${PIE_CHART_LIMIT}`, () => { const chartOptions = generateChartOptions(fixtures.pieChartWithMetricsOnly, { type: "pie", }); chartOptions.data.categories = range(PIE_CHART_LIMIT + 1); const validationResult = validateData(undefined, chartOptions); expect(validationResult).toEqual({ dataTooLarge: true, hasNegativeValue: false, }); }, ); it('should validate with "hasNegativeValue: true" for pie chart if its series contains a negative value', () => { const chartOptions = pieChartOptionsWithNegativeValue; const validationResult = validateData(undefined, chartOptions); expect(validationResult).toEqual({ dataTooLarge: false, hasNegativeValue: true, }); }); it('should validate with "hasNegativeValue: true" for treemap if its series contains a negative value', () => { const validationResult = validateData(undefined, treemapOptionsWithNegativeValue); expect(validationResult).toEqual({ dataTooLarge: false, hasNegativeValue: true, }); }); }); describe("Treemap filters out root nodes for dataPoints limit", () => { it('should validate with "dataTooLarge: false" against data points limit', () => { // 2 roots + 4 leafs const treemapOptions = generateChartOptions( fixtures.treemapWithMetricViewByAndStackByAttribute, { type: "treemap", mdObject: fixtures.treemapWithMetricViewByAndStackByAttribute.mdObject, }, ); const validationResult = validateData( { dataPoints: 4, }, treemapOptions, ); expect(validationResult).toEqual({ dataTooLarge: false, hasNegativeValue: false, }); }); it('should validate with "dataTooLarge: true" against data points limit', () => { // 2 roots + 4 leafs const treemapOptions = generateChartOptions( fixtures.treemapWithMetricViewByAndStackByAttribute, { type: "treemap", mdObject: fixtures.treemapWithMetricViewByAndStackByAttribute.mdObject, }, ); const validationResult = validateData( { dataPoints: 3, }, treemapOptions, ); expect(validationResult).toEqual({ dataTooLarge: true, hasNegativeValue: false, }); }); }); describe("optional stacking with viewBy 2 attributes", () => { const chartOptions: IChartOptions = { isViewByTwoAttributes: true, type: "column", }; const testData = [ ["false", "less", 3, { dataTooLarge: false, hasNegativeValue: false }], ["true", "greater", 31, { dataTooLarge: true, hasNegativeValue: false }], ]; it.each(testData)( 'should validate with "dataTooLarge: %s" when category number is %s than default limit', (_result: string, _operator: string, categoriesNum: number, expected: IValidationResult) => { const data = { series: Array(1), categories: Array(categoriesNum).fill({ name: "Month", categories: Array(31), }), }; const validationResult = validateData(undefined, { ...chartOptions, data }); expect(validationResult).toEqual(expected); }, ); }); }); describe("isDerivedMeasure", () => { it("should return true if measureItem was defined as a popMeasure", () => { const measureItem = fixtures.barChartWithPopMeasureAndViewByAttribute.executionResponse.dimensions[ STACK_BY_DIMENSION_INDEX ].headers[0].measureGroupHeader.items[0]; const { afm } = fixtures.barChartWithPopMeasureAndViewByAttribute.executionRequest; expect(isDerivedMeasure(measureItem, afm)).toEqual(true); }); it("should return true if measureItem was defined as a previousPeriodMeasure", () => { const measureItem = fixtures.barChartWithPreviousPeriodMeasure.executionResponse.dimensions[ STACK_BY_DIMENSION_INDEX ].headers[0].measureGroupHeader.items[0]; const { afm } = fixtures.barChartWithPreviousPeriodMeasure.executionRequest; expect(isDerivedMeasure(measureItem, afm)).toEqual(true); }); it("should return false if measureItem was defined as a simple measure", () => { const measureItem = fixtures.barChartWithPopMeasureAndViewByAttribute.executionResponse.dimensions[ STACK_BY_DIMENSION_INDEX ].headers[0].measureGroupHeader.items[1]; const { afm } = fixtures.barChartWithPopMeasureAndViewByAttribute.executionRequest; expect(isDerivedMeasure(measureItem, afm)).toEqual(false); }); }); describe("getSeriesItemData", () => { describe("in usecase of bar chart with pop measure and view by attribute", () => { const parameters = getSeriesItemDataParameters( fixtures.barChartWithPopMeasureAndViewByAttribute, 0, ); const seriesItem = parameters[0]; const seriesIndex = parameters[1]; const measureGroup = parameters[2]; const viewByAttribute = parameters[3]; const stackByAttribute = parameters[4]; const attributeColorStrategy = new AttributeColorStrategy( DEFAULT_COLOR_PALETTE, undefined, measureGroup, viewByAttribute, stackByAttribute, fixtures.pieChartWithMetricsOnly.executionRequest.afm, ); const seriesItemData = getSeriesItemData( seriesItem, seriesIndex, measureGroup, viewByAttribute, stackByAttribute, "column", attributeColorStrategy, ); it("should fill correct pointData name", () => { expect(seriesItemData.map((pointData: any) => pointData.name)).toEqual([ "Amount previous year", "Amount previous year", "Amount previous year", "Amount previous year", "Amount previous year", "Amount previous year", ]); }); it("should parse all pointData values", () => { expect(seriesItemData.map((pointData: any) => pointData.y)).toEqual([ null, 2773426.95, 8656468.2, 29140409.09, 60270072.2, 15785080.1, ]); }); it("should enable markers for all non-null pointData values", () => { expect(seriesItemData.map((pointData: any) => pointData.marker.enabled)).toEqual([ false, true, true, true, true, true, ]); }); }); describe("in usecase of bar chart with previous period measure and view by attribute", () => { const parameters = getSeriesItemDataParameters(fixtures.barChartWithPreviousPeriodMeasure, 0); const seriesItem = parameters[0]; const seriesIndex = parameters[1]; const measureGroup = parameters[2]; const viewByAttribute = parameters[3]; const stackByAttribute = parameters[4]; const attributeColorStrategy = new AttributeColorStrategy( DEFAULT_COLOR_PALETTE, undefined, measureGroup, viewByAttribute, stackByAttribute, fixtures.pieChartWithMetricsOnly.executionRequest.afm, ); const seriesItemData = getSeriesItemData( seriesItem, seriesIndex, measureGroup, viewByAttribute, stackByAttribute, "column", attributeColorStrategy, ); it("should fill correct pointData name", () => { expect(seriesItemData.map((pointData: any) => pointData.name)).toEqual([ "Primary measure - period ago", "Primary measure - period ago", ]); }); it("should parse all pointData values", () => { expect(seriesItemData.map((pointData: any) => pointData.y)).toEqual([24000, null]); }); it("should enable markers for all non-null pointData values", () => { expect(seriesItemData.map((pointData: any) => pointData.marker.enabled)).toEqual([ true, false, ]); }); }); describe("in usecase of pie chart and treemap with metrics only", () => { const parameters = getSeriesItemDataParameters(fixtures.pieChartWithMetricsOnly, 0); const seriesItem = parameters[0]; const seriesIndex = parameters[1]; const measureGroup = parameters[2]; const viewByAttribute = parameters[3]; const stackByAttribute = parameters[4]; const metricColorStrategy = new MeasureColorStrategy( DEFAULT_COLOR_PALETTE, undefined, viewByAttribute, stackByAttribute, fixtures.pieChartWithMetricsOnly.executionResponse, fixtures.pieChartWithMetricsOnly.executionRequest.afm, ); const pieSeriesItemData = getSeriesItemData( seriesItem, seriesIndex, measureGroup, viewByAttribute, stackByAttribute, "pie", metricColorStrategy, ); const treeMapColorStrategy = new TreemapColorStrategy( DEFAULT_COLOR_PALETTE, undefined, viewByAttribute, stackByAttribute, fixtures.pieChartWithMetricsOnly.executionResponse, fixtures.pieChartWithMetricsOnly.executionRequest.afm, ); const treemapSeriesItemData = getSeriesItemData( seriesItem, seriesIndex, measureGroup, viewByAttribute, stackByAttribute, "treemap", treeMapColorStrategy, ); it("should fill correct pointData name", () => { expect(pieSeriesItemData.map(pointData => pointData.name)).toEqual([ "Lost", "Won", "Expected", ]); expect(treemapSeriesItemData.map(pointData => pointData.name)).toEqual([ "Lost", "Won", "Expected", ]); }); it("should fill correct pointData color", () => { expect(pieSeriesItemData.map(pointData => pointData.color)).toEqual([ FIRST_DEFAULT_COLOR_ITEM_AS_STRING, SECOND_DEFAULT_COLOR_ITEM_AS_STRING, getRgbString(DEFAULT_COLOR_PALETTE[2]), ]); expect(treemapSeriesItemData.map(pointData => pointData.color)).toEqual([ FIRST_DEFAULT_COLOR_ITEM_AS_STRING, SECOND_DEFAULT_COLOR_ITEM_AS_STRING, getRgbString(DEFAULT_COLOR_PALETTE[2]), ]); }); it("should fill correct pointData legendIndex", () => { expect(pieSeriesItemData.map(pointData => pointData.legendIndex)).toEqual([0, 1, 2]); expect(treemapSeriesItemData.map(pointData => pointData.legendIndex)).toEqual([0, 1, 2]); }); it("should fill correct pointData format", () => { expect(pieSeriesItemData.map(pointData => pointData.format)).toEqual([ "#,##0.00", "#,##0.00", "#,##0.00", ]); expect(treemapSeriesItemData.map(pointData => pointData.format)).toEqual([ "#,##0.00", "#,##0.00", "#,##0.00", ]); }); }); describe("in usecase of pie chart with an attribute", () => { const parameters = getSeriesItemDataParameters(fixtures.barChartWithViewByAttribute, 0); const seriesItem = parameters[0]; const seriesIndex = parameters[1]; const measureGroup = parameters[2]; const viewByAttribute = parameters[3]; const stackByAttribute = parameters[4]; const attributeColorStrategy = new AttributeColorStrategy( DEFAULT_COLOR_PALETTE, undefined, viewByAttribute, stackByAttribute, fixtures.pieChartWithMetricsOnly.executionResponse, fixtures.pieChartWithMetricsOnly.executionRequest.afm, ); const pieSeriesItemData = getSeriesItemData( seriesItem, seriesIndex, measureGroup, viewByAttribute, stackByAttribute, "pie", attributeColorStrategy, ); const treeMapColorStrategy = new TreemapColorStrategy( DEFAULT_COLOR_PALETTE, undefined, viewByAttribute, stackByAttribute, fixtures.barChartWithViewByAttribute.executionResponse, fixtures.barChartWithViewByAttribute.executionRequest.afm, ); const treemapSeriesItemData = getSeriesItemData( seriesItem, seriesIndex, measureGroup, viewByAttribute, stackByAttribute, "treemap", treeMapColorStrategy, ); it("should fill correct pointData name", () => { expect(pieSeriesItemData.map(pointData => pointData.name)).toEqual([ "Direct Sales", "Inside Sales", ]); expect(treemapSeriesItemData.map(pointData => pointData.name)).toEqual([ "Direct Sales", "Inside Sales", ]); }); it("should fill correct pointData color", () => { expect(pieSeriesItemData.map(pointData => pointData.color)).toEqual([ FIRST_DEFAULT_COLOR_ITEM_AS_STRING, SECOND_DEFAULT_COLOR_ITEM_AS_STRING, ]); expect(treemapSeriesItemData.map(pointData => pointData.color)).toEqual([ FIRST_DEFAULT_COLOR_ITEM_AS_STRING, SECOND_DEFAULT_COLOR_ITEM_AS_STRING, ]); }); it("should fill correct pointData legendIndex", () => { expect(pieSeriesItemData.map(pointData => pointData.legendIndex)).toEqual([0, 1]); expect(treemapSeriesItemData.map(pointData => pointData.legendIndex)).toEqual([0, 1]); }); it("should fill correct pointData format", () => { expect(pieSeriesItemData.map(pointData => pointData.format)).toEqual([ "#,##0.00", "#,##0.00", ]); expect(treemapSeriesItemData.map(pointData => pointData.format)).toEqual([ "#,##0.00", "#,##0.00", ]); }); }); }); describe("getSeries", () => { describe("in usecase of bar chart with 3 measures and view by attribute", () => { const dataSet = fixtures.barChartWith3MetricsAndViewByAttribute; const { measureGroup, viewByAttribute, stackByAttribute } = getMVS(dataSet); const attributeColorStrategy = new AttributeColorStrategy( DEFAULT_COLOR_PALETTE, undefined, viewByAttribute, stackByAttribute, fixtures.barChartWith3MetricsAndViewByAttribute.executionResponse, fixtures.barChartWith3MetricsAndViewByAttribute.executionRequest.afm, ); const type = "column"; const seriesData = getSeries( dataSet.executionResult.data, measureGroup, viewByAttribute, stackByAttribute, type, {} as any, attributeColorStrategy, ); it("should return number of series equal to the count of measures", () => { expect(seriesData.length).toBe(3); }); it("should fill correct series name", () => { expect(seriesData.map((seriesItem: any) => seriesItem.name)).toEqual([ "<button>Lost</button> ...", "Won", "Expected", ]); }); it("should fill correct series color", () => { expect(seriesData.map((seriesItem: any) => seriesItem.color)).toEqual([ FIRST_DEFAULT_COLOR_ITEM_AS_STRING, SECOND_DEFAULT_COLOR_ITEM_AS_STRING, getRgbString(DEFAULT_COLOR_PALETTE[2]), ]); }); it("should fill correct series legendIndex", () => { expect(seriesData.map((seriesItem: any) => seriesItem.legendIndex)).toEqual([0, 1, 2]); }); it("should fill correct series data", () => { const expectedData = [0, 1, 2].map((seriesIndex: any) => { const parameters = getSeriesItemDataParameters(dataSet, seriesIndex); const seriesItem = parameters[0]; const si = parameters[1]; const measureGroup = parameters[2]; const viewByAttribute = parameters[3]; const stackByAttribute = parameters[4]; return getSeriesItemData( seriesItem, si, measureGroup, viewByAttribute, stackByAttribute, type, attributeColorStrategy, ); }); expect(seriesData.map((seriesItem: any) => seriesItem.data)).toEqual(expectedData); }); }); describe("in usecase of bar chart with stack by and view by attributes", () => { const dataSet = fixtures.barChartWithStackByAndViewByAttributes; const { measureGroup, viewByAttribute, stackByAttribute } = getMVS(dataSet); const type = "column"; const attributeColorStrategy = new AttributeColorStrategy( DEFAULT_COLOR_PALETTE, undefined, viewByAttribute, stackByAttribute, fixtures.barChartWithStackByAndViewByAttributes.executionResponse, fixtures.barChartWithStackByAndViewByAttributes.executionRequest.afm, ); const seriesData = getSeries( dataSet.executionResult.data, measureGroup, viewByAttribute, stackByAttribute, type, {} as any, attributeColorStrategy, ); it("should return number of series equal to the count of stack by attribute values", () => { expect(seriesData.length).toBe(2); }); it("should fill correct series name equal to stack by attribute values", () => { expect(seriesData.map((seriesItem: any) => seriesItem.name)).toEqual([ "East Coast", "West Coast", ]); }); it("should fill correct series color", () => { expect(seriesData.map((seriesItem: any) => seriesItem.color)).toEqual([ FIRST_DEFAULT_COLOR_ITEM_AS_STRING, SECOND_DEFAULT_COLOR_ITEM_AS_STRING, ]); }); it("should fill correct series legendIndex", () => { expect(seriesData.map((seriesItem: any) => seriesItem.legendIndex)).toEqual([0, 1]); }); it("should fill correct series data", () => { const expectedData = [0, 1].map(seriesIndex => { const parameters = getSeriesItemDataParameters(dataSet, seriesIndex); const seriesItem = parameters[0]; const si = parameters[1]; const measureGroup = parameters[2]; const viewByAttribute = parameters[3]; const stackByAttribute = parameters[4]; const attributeColorStrategy = new AttributeColorStrategy( DEFAULT_COLOR_PALETTE, undefined, measureGroup, viewByAttribute, stackByAttribute, fixtures.barChartWith3MetricsAndViewByAttribute.executionRequest.afm, ); return getSeriesItemData( seriesItem, si, measureGroup, viewByAttribute, stackByAttribute, type, attributeColorStrategy, ); }); expect(seriesData.map((seriesItem: any) => seriesItem.data)).toEqual(expectedData); }); }); describe("in use case of bubble", () => { const dummyBucketItem = { visualizationAttribute: { localIdentifier: "abc", displayForm: { uri: "abc" }, }, }; const dummyMeasureGroup = { items: [ { measureHeaderItem: { localIdentifier: "m1", name: "dummyName", format: "#.##x", }, }, ], }; const dummyExecutionResponse: Execution.IExecutionResponse = { dimensions: [ { headers: [ { measureGroupHeader: dummyMeasureGroup, }, ], }, ], links: { executionResult: "foo" }, }; const stackByAttribute = { items: [ { attributeHeaderItem: { name: "abc", }, }, { attributeHeaderItem: { name: "def", }, }, ], }; const colorPalette = [ { guid: "3", fill: { r: 255, g: 0, b: 0, }, }, { guid: "2", fill: { r: 0, g: 255, b: 0, }, }, ]; it("should fill X, Y and Z with valid values when measure buckets are not empty", () => { const executionResultData = [[1, 2, 3], [4, 5, 6]]; const mdObject = { visualizationClass: { uri: "abc" }, buckets: [ { localIdentifier: "measures", items: [dummyBucketItem], }, { localIdentifier: "secondary_measures", items: [dummyBucketItem], }, { localIdentifier: "tertiary_measures", items: [dummyBucketItem], }, ], }; const expectedSeries = [ { name: "abc", color: "rgb(255,0,0)", legendIndex: 0, data: [{ x: 1, y: 2, z: 3, format: "#.##x" }], }, { name: "def", color: undefined, legendIndex: 1, data: [{ x: 4, y: 5, z: 6, format: "#.##x" }], }, ]; const colorStrategy = new BubbleChartColorStrategy( colorPalette, undefined, null, stackByAttribute, dummyExecutionResponse, {}, ); const series = getBubbleChartSeries( executionResultData, dummyMeasureGroup, stackByAttribute, mdObject, colorStrategy, ); expect(series).toEqual(expectedSeries); }); it("should fill X and Y with zeroes when X and Y measure buckets are empty", () => { const executionResultData = [[3], [6]]; const mdObject = { visualizationClass: { uri: "abc" }, buckets: [ { localIdentifier: "tertiary_measures", items: [dummyBucketItem], }, ], }; const expectedSeries = [ { name: "", color: "rgb(255,0,0)", legendIndex: 0, data: [{ x: 0, y: 0, z: 3, format: "#.##x" }], }, { name: "", color: undefined, legendIndex: 1, data: [{ x: 0, y: 0, z: 6, format: "#.##x" }], }, ]; const colorStrategy = new BubbleChartColorStrategy( colorPalette, undefined, null, stackByAttribute, dummyExecutionResponse, {}, ); const series = getBubbleChartSeries( executionResultData, dummyMeasureGroup, null, mdObject, colorStrategy, ); expect(series).toEqual(expectedSeries); }); it("should fill Y with x values when primary bucket is empty but secondary is not", () => { const executionResultData = [[1, 3], [4, 6]]; const mdObject = { visualizationClass: { uri: "abc" }, buckets: [ { localIdentifier: "secondary_measures", items: [dummyBucketItem], }, { localIdentifier: "tertiary_measures", items: [dummyBucketItem], }, ], }; const expectedSeries = [ { name: "abc", color: "rgb(255,0,0)", legendIndex: 0, data: [{ x: 0, y: 1, z: 3, format: "#.##x" }], }, { name: "def", color: undefined, legendIndex: 1, data: [{ x: 0, y: 4, z: 6, format: "#.##x" }], }, ]; const colorStrategy = new BubbleChartColorStrategy( colorPalette, undefined, stackByAttribute, stackByAttribute, dummyExecutionResponse, {}, ); const series = getBubbleChartSeries( executionResultData, dummyMeasureGroup, stackByAttribute, mdObject, colorStrategy, ); expect(series).toEqual(expectedSeries); }); it("should fill X with x and Z with z values when secondary bucket is empty", () => { const executionResultData = [[1, 3], [4, 6]]; const mdObject = { visualizationClass: { uri: "abc" }, buckets: [ { localIdentifier: "measures", items: [dummyBucketItem], }, { localIdentifier: "tertiary_measures", items: [dummyBucketItem], }, ], }; const expectedSeries = [ { name: "abc", color: "rgb(255,0,0)", legendIndex: 0, data: [{ x: 1, y: 0, z: 3, format: "#.##x" }], }, { name: "def", color: undefined, legendIndex: 1, data: [{ x: 4, y: 0, z: 6, format: "#.##x" }], }, ]; const colorStrategy = new BubbleChartColorStrategy( colorPalette, undefined, stackByAttribute, stackByAttribute, dummyExecutionResponse, null, ); const series = getBubbleChartSeries( executionResultData, dummyMeasureGroup, stackByAttribute, mdObject, colorStrategy, ); expect(series).toEqual(expectedSeries); }); it("should fill Z with NaNs when tertiary bucket is empty", () => { const executionResultData = [[1, 3], [4, 6]]; const mdObject = { visualizationClass: { uri: "abc" }, buckets: [ { localIdentifier: "measures", items: [dummyBucketItem], }, { localIdentifier: "secondary_measures", items: [dummyBucketItem], }, ], }; const expectedSeries = [ { name: "abc", color: "rgb(255,0,0)", legendIndex: 0, data: [{ x: 1, y: 3, z: NaN, format: "#.##x" }], }, { name: "def", color: undefined, legendIndex: 1, data: [{ x: 4, y: 6, z: NaN, format: "#.##x" }], }, ]; const colorStrategy = new BubbleChartColorStrategy( colorPalette, undefined, null, stackByAttribute, dummyExecutionResponse, null, ); const series = getBubbleChartSeries( executionResultData, dummyMeasureGroup, stackByAttribute, mdObject, colorStrategy, ); expect(series).toEqual(expectedSeries); }); it("should handle null in result", () => { const executionResultData = [[null, 2, 3], [4, null, 6], [7, 8, null]]; const stackByAttributeWithThreeElements = { items: [ { attributeHeaderItem: { name: "abc", }, }, { attributeHeaderItem: { name: "def", }, }, { attributeHeaderItem: { name: "ghi", }, }, ], }; const mdObject = { visualizationClass: { uri: "abc" }, buckets: [ { localIdentifier: "measures", items: [dummyBucketItem], }, { localIdentifier: "secondary_measures", items: [dummyBucketItem], }, { localIdentifier: "tertiary_measures", items: [dummyBucketItem], }, ], }; const colorPaletteWithBlue = [ ...colorPalette, { guid: "1", fill: { r: 0, g: 0, b: 255, }, }, ]; const expectedSeries = [ { name: "abc", color: "rgb(255,0,0)", legendIndex: 0, data: [] as any, }, { name: "def", color: undefined, legendIndex: 1, data: [], }, { name: "ghi", color: undefined, legendIndex: 2, data: [], }, ]; const colorStrategy = new BubbleChartColorStrategy( colorPaletteWithBlue, undefined, null, stackByAttributeWithThreeElements, dummyExecutionResponse, null, ); const series = getBubbleChartSeries( executionResultData, dummyMeasureGroup, stackByAttributeWithThreeElements, mdObject, colorStrategy, ); expect(series).toEqual(expectedSeries); }); }); describe("in use case of treemap", () => { describe("with only one measure", () => { const dataSet = fixtures.barChartWithSingleMeasureAndNoAttributes; const { measureGroup, viewByAttribute, stackByAttribute } = getMVSTreemap(dataSet); const type = "treemap"; const treeMapColorStrategy = new TreemapColorStrategy( DEFAULT_COLOR_PALETTE, undefined, measureGroup, viewByAttribute, stackByAttribute, fixtures.barChartWithSingleMeasureAndNoAttributes.executionRequest.afm, ); const seriesData = getSeries( dataSet.executionResult.data, measureGroup, viewByAttribute, stackByAttribute, type, {} as any, treeMapColorStrategy, ); it("should return only one serie", () => { expect(seriesData.length).toBe(1); }); it("should fill correct series name equal to measure name", () => { expect(seriesData[0].name).toEqual("Amount"); }); it("should fill correct series color", () => { expect(seriesData[0].color).toEqual(FIRST_DEFAULT_COLOR_ITEM_AS_STRING); }); it("should fill correct series legendIndex", () => { expect(seriesData[0].legendIndex).toEqual(0); }); it("should fill correct series data", () => { expect(seriesData[0].data.length).toBe(1); expect(seriesData[0].data[0]).toMatchObject({ value: 116625456.54, color: FIRST_DEFAULT_COLOR_ITEM_AS_STRING, format: "#,##0.00", legendIndex: 0, name: "Amount", marker: expect.any(Object), }); }); }); describe("with one measure and view by attribute", () => { const dataSet = fixtures.treemapWithMetricAndViewByAttribute; const { measureGroup, viewByAttribute, stackByAttribute } = getMVSTreemap(dataSet); const type = "treemap"; const treeMapColorStrategy = new TreemapColorStrategy( DEFAULT_COLOR_PALETTE, undefined, viewByAttribute, stackByAttribute, fixtures.treemapWithMetricAndViewByAttribute.executionResponse, fixtures.treemapWithMetricAndViewByAttribute.executionRequest.afm, ); const seriesData = getSeries( dataSet.executionResult.data, measureGroup, viewByAttribute, stackByAttribute, type, dataSet.mdObject, treeMapColorStrategy, ); it("should return only one serie", () => { expect(seriesData.length).toBe(1); }); it("should fill correct series name equal to measure name", () => { expect(seriesData[0].name).toEqual("Amount"); }); it("should fill correct series legendIndex", () => { expect(seriesData[0].legendIndex).toEqual(0); }); it("should fill correct series data", () => { expect(seriesData[