You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@superset.apache.org by el...@apache.org on 2023/05/25 22:26:12 UTC
[superset] 07/07: fix(plugin-chart-echarts): reorder totals and support multimetric sort (#23675)
This is an automated email from the ASF dual-hosted git repository.
elizabeth pushed a commit to branch 2.1
in repository https://gitbox.apache.org/repos/asf/superset.git
commit 661a559cb2fe4cad1c384203224e2622e2e309c8
Author: Ville Brofeldt <33...@users.noreply.github.com>
AuthorDate: Fri Apr 14 20:43:15 2023 +0300
fix(plugin-chart-echarts): reorder totals and support multimetric sort (#23675)
---
.../src/shared-controls/customControls.tsx | 29 +-
.../src/MixedTimeseries/transformProps.ts | 4 +-
.../src/Timeseries/transformProps.ts | 13 +-
.../plugin-chart-echarts/src/Timeseries/types.ts | 2 +
.../plugins/plugin-chart-echarts/src/constants.ts | 6 +
.../plugin-chart-echarts/src/utils/series.ts | 134 ++++++++-
.../plugin-chart-echarts/test/utils/series.test.ts | 300 +++++++++++++++++----
7 files changed, 416 insertions(+), 72 deletions(-)
diff --git a/superset-frontend/packages/superset-ui-chart-controls/src/shared-controls/customControls.tsx b/superset-frontend/packages/superset-ui-chart-controls/src/shared-controls/customControls.tsx
index 8e8f4d8400..0564745eae 100644
--- a/superset-frontend/packages/superset-ui-chart-controls/src/shared-controls/customControls.tsx
+++ b/superset-frontend/packages/superset-ui-chart-controls/src/shared-controls/customControls.tsx
@@ -50,14 +50,29 @@ export const contributionModeControl = {
},
};
+function isTemporal(controls: ControlStateMapping): boolean {
+ return !(
+ isDefined(controls?.x_axis?.value) &&
+ !isTemporalColumn(
+ getColumnLabel(controls?.x_axis?.value as QueryFormColumn),
+ controls?.datasource?.datasource,
+ )
+ );
+}
+
const xAxisSortVisibility = ({ controls }: { controls: ControlStateMapping }) =>
- isDefined(controls?.x_axis?.value) &&
- !isTemporalColumn(
- getColumnLabel(controls?.x_axis?.value as QueryFormColumn),
- controls?.datasource?.datasource,
- ) &&
- Array.isArray(controls?.groupby?.value) &&
- controls.groupby.value.length === 0;
+ !isTemporal(controls) &&
+ ensureIsArray(controls?.groupby?.value).length === 0 &&
+ ensureIsArray(controls?.metrics?.value).length === 1;
+
+const xAxisMultiSortVisibility = ({
+ controls,
+}: {
+ controls: ControlStateMapping;
+}) =>
+ !isTemporal(controls) &&
+ (!!ensureIsArray(controls?.groupby?.value).length ||
+ ensureIsArray(controls?.metrics?.value).length > 1);
export const xAxisSortControl = {
name: 'x_axis_sort',
diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/MixedTimeseries/transformProps.ts b/superset-frontend/plugins/plugin-chart-echarts/src/MixedTimeseries/transformProps.ts
index 98367daf9a..da8ac01f4e 100644
--- a/superset-frontend/plugins/plugin-chart-echarts/src/MixedTimeseries/transformProps.ts
+++ b/superset-frontend/plugins/plugin-chart-echarts/src/MixedTimeseries/transformProps.ts
@@ -170,12 +170,12 @@ export default function transformProps(
}
const rebasedDataA = rebaseForecastDatum(data1, verboseMap);
- const rawSeriesA = extractSeries(rebasedDataA, {
+ const [rawSeriesA] = extractSeries(rebasedDataA, {
fillNeighborValue: stack ? 0 : undefined,
xAxis: xAxisLabel,
});
const rebasedDataB = rebaseForecastDatum(data2, verboseMap);
- const rawSeriesB = extractSeries(rebasedDataB, {
+ const [rawSeriesB] = extractSeries(rebasedDataB, {
fillNeighborValue: stackB ? 0 : undefined,
xAxis: xAxisLabel,
});
diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/transformProps.ts b/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/transformProps.ts
index 4398f7e555..afd54e552c 100644
--- a/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/transformProps.ts
+++ b/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/transformProps.ts
@@ -125,6 +125,7 @@ export default function transformProps(
logAxis,
markerEnabled,
markerSize,
+ metrics,
minorSplitLine,
onlyTotal,
opacity,
@@ -144,6 +145,8 @@ export default function transformProps(
truncateYAxis,
xAxis: xAxisOrig,
xAxisLabelRotation,
+ xAxisSortSeries,
+ xAxisSortSeriesAscending,
xAxisTimeFormat,
xAxisTitle,
xAxisTitleMargin,
@@ -178,7 +181,9 @@ export default function transformProps(
getMetricLabel,
);
- const rawSeries = extractSeries(rebasedData, {
+ const isMultiSeries = groupby.length || metrics.length > 1;
+
+ const [rawSeries, sortedTotalValues] = extractSeries(rebasedData, {
fillNeighborValue: stack && !forecastEnabled ? 0 : undefined,
xAxis: xAxisLabel,
extraMetricLabels,
@@ -188,6 +193,10 @@ export default function transformProps(
isHorizontal,
sortSeriesType,
sortSeriesAscending,
+ xAxisSortSeries: isMultiSeries ? xAxisSortSeries : undefined,
+ xAxisSortSeriesAscending: isMultiSeries
+ ? xAxisSortSeriesAscending
+ : undefined,
});
const showValueIndexes = extractShowValueIndexes(rawSeries, {
stack,
@@ -222,7 +231,7 @@ export default function transformProps(
formatter,
showValue,
onlyTotal,
- totalStackedValues,
+ totalStackedValues: sortedTotalValues,
showValueIndexes,
thresholdValues,
richTooltip,
diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/types.ts b/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/types.ts
index 56527ebd63..b5a67d977d 100644
--- a/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/types.ts
+++ b/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/types.ts
@@ -23,6 +23,7 @@ import {
ContributionType,
QueryFormColumn,
QueryFormData,
+ QueryFormMetric,
TimeFormatter,
TimeGranularity,
} from '@superset-ui/core';
@@ -65,6 +66,7 @@ export type EchartsTimeseriesFormData = QueryFormData & {
logAxis: boolean;
markerEnabled: boolean;
markerSize: number;
+ metrics: QueryFormMetric[];
minorSplitLine: boolean;
opacity: number;
orderDesc: boolean;
diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/constants.ts b/superset-frontend/plugins/plugin-chart-echarts/src/constants.ts
index 3fc3fc999b..6be6706886 100644
--- a/superset-frontend/plugins/plugin-chart-echarts/src/constants.ts
+++ b/superset-frontend/plugins/plugin-chart-echarts/src/constants.ts
@@ -49,6 +49,12 @@ export const TIMESERIES_CONSTANTS = {
extraControlsOffset: 22,
};
+export enum StackControlsValue {
+ Stack = 'Stack',
+ Stream = 'Stream',
+ Expand = 'Expand',
+}
+
export const LABEL_POSITION: [LabelPositionEnum, string][] = [
[LabelPositionEnum.Top, 'Top'],
[LabelPositionEnum.Left, 'Left'],
diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/utils/series.ts b/superset-frontend/plugins/plugin-chart-echarts/src/utils/series.ts
index 6d1396afc2..5bb026dc77 100644
--- a/superset-frontend/plugins/plugin-chart-echarts/src/utils/series.ts
+++ b/superset-frontend/plugins/plugin-chart-echarts/src/utils/series.ts
@@ -35,6 +35,7 @@ import {
AreaChartExtraControlsValue,
NULL_STRING,
TIMESERIES_CONSTANTS,
+ StackControlsValue
} from '../constants';
import {
LegendOrientation,
@@ -154,6 +155,86 @@ export function sortAndFilterSeries(
).map(({ name }) => name);
}
+export function sortRows(
+ rows: DataRecord[],
+ totalStackedValues: number[],
+ xAxis: string,
+ xAxisSortSeries: SortSeriesType,
+ xAxisSortSeriesAscending: boolean,
+) {
+ const sortedRows = rows.map((row, idx) => {
+ let sortKey: DataRecordValue = '';
+ let aggregate: number | undefined;
+ let entries = 0;
+ Object.entries(row).forEach(([key, value]) => {
+ const isValueDefined = isDefined(value);
+ if (key === xAxis) {
+ sortKey = value;
+ }
+ if (
+ xAxisSortSeries === SortSeriesType.Name ||
+ typeof value !== 'number'
+ ) {
+ return;
+ }
+
+ if (!(xAxisSortSeries === SortSeriesType.Avg && !isValueDefined)) {
+ entries += 1;
+ }
+
+ switch (xAxisSortSeries) {
+ case SortSeriesType.Avg:
+ case SortSeriesType.Sum:
+ if (aggregate === undefined) {
+ aggregate = value;
+ } else {
+ aggregate += value;
+ }
+ break;
+ case SortSeriesType.Min:
+ aggregate =
+ aggregate === undefined || (isValueDefined && value < aggregate)
+ ? value
+ : aggregate;
+ break;
+ case SortSeriesType.Max:
+ aggregate =
+ aggregate === undefined || (isValueDefined && value > aggregate)
+ ? value
+ : aggregate;
+ break;
+ default:
+ break;
+ }
+ });
+ if (
+ xAxisSortSeries === SortSeriesType.Avg &&
+ entries > 0 &&
+ aggregate !== undefined
+ ) {
+ aggregate /= entries;
+ }
+
+ const value =
+ xAxisSortSeries === SortSeriesType.Name && typeof sortKey === 'string'
+ ? sortKey.toLowerCase()
+ : aggregate;
+
+ return {
+ key: sortKey,
+ value,
+ row,
+ totalStackedValue: totalStackedValues[idx],
+ };
+ });
+
+ return orderBy(
+ sortedRows,
+ ['value'],
+ [xAxisSortSeriesAscending ? 'asc' : 'desc'],
+ ).map(({ row, totalStackedValue }) => ({ row, totalStackedValue }));
+}
+
export function extractSeries(
data: DataRecord[],
opts: {
@@ -166,8 +247,10 @@ export function extractSeries(
isHorizontal?: boolean;
sortSeriesType?: SortSeriesType;
sortSeriesAscending?: boolean;
+ xAxisSortSeries?: SortSeriesType;
+ xAxisSortSeriesAscending?: boolean;
} = {},
-): SeriesOption[] {
+): [SeriesOption[], number[], number | undefined] {
const {
fillNeighborValue,
xAxis = DTTM_ALIAS,
@@ -178,45 +261,74 @@ export function extractSeries(
isHorizontal = false,
sortSeriesType,
sortSeriesAscending,
+ xAxisSortSeries,
+ xAxisSortSeriesAscending,
} = opts;
- if (data.length === 0) return [];
+ if (data.length === 0) return [[], [], undefined];
const rows: DataRecord[] = data.map(datum => ({
...datum,
[xAxis]: datum[xAxis],
}));
- const series = sortAndFilterSeries(
+ const sortedSeries = sortAndFilterSeries(
rows,
xAxis,
extraMetricLabels,
sortSeriesType,
sortSeriesAscending,
);
+ const sortedRows =
+ isDefined(xAxisSortSeries) && isDefined(xAxisSortSeriesAscending)
+ ? sortRows(
+ rows,
+ totalStackedValues,
+ xAxis,
+ xAxisSortSeries!,
+ xAxisSortSeriesAscending!,
+ )
+ : rows.map((row, idx) => ({
+ row,
+ totalStackedValue: totalStackedValues[idx],
+ }));
- return series.map(name => ({
+ let minPositiveValue: number | undefined;
+ const finalSeries = sortedSeries.map(name => ({
id: name,
name,
- data: rows
- .map((row, idx) => {
+ data: sortedRows
+ .map(({ row, totalStackedValue }, idx) => {
+ const currentValue = row[name];
+ if (
+ typeof currentValue === 'number' &&
+ currentValue > 0 &&
+ (minPositiveValue === undefined || minPositiveValue > currentValue)
+ ) {
+ minPositiveValue = currentValue;
+ }
const isNextToDefinedValue =
isDefined(rows[idx - 1]?.[name]) || isDefined(rows[idx + 1]?.[name]);
const isFillNeighborValue =
- !isDefined(row[name]) &&
+ !isDefined(currentValue) &&
isNextToDefinedValue &&
fillNeighborValue !== undefined;
- let value: DataRecordValue | undefined = row[name];
+ let value: DataRecordValue | undefined = currentValue;
if (isFillNeighborValue) {
value = fillNeighborValue;
} else if (
- stack === AreaChartExtraControlsValue.Expand &&
- totalStackedValues.length > 0
+ stack === StackControlsValue.Expand &&
+ totalStackedValue !== undefined
) {
- value = ((value || 0) as number) / totalStackedValues[idx];
+ value = ((value || 0) as number) / totalStackedValue;
}
return [row[xAxis], value];
})
.filter(obs => !removeNulls || (obs[0] !== null && obs[1] !== null))
.map(obs => (isHorizontal ? [obs[1], obs[0]] : obs)),
}));
+ return [
+ finalSeries,
+ sortedRows.map(({ totalStackedValue }) => totalStackedValue),
+ minPositiveValue,
+ ];
}
export function formatSeriesName(
diff --git a/superset-frontend/plugins/plugin-chart-echarts/test/utils/series.test.ts b/superset-frontend/plugins/plugin-chart-echarts/test/utils/series.test.ts
index 51b8b72f06..89e37b0856 100644
--- a/superset-frontend/plugins/plugin-chart-echarts/test/utils/series.test.ts
+++ b/superset-frontend/plugins/plugin-chart-echarts/test/utils/series.test.ts
@@ -32,11 +32,30 @@ import {
getOverMaxHiddenFormatter,
sanitizeHtml,
sortAndFilterSeries,
+ sortRows,
} from '../../src/utils/series';
import { LegendOrientation, LegendType, SortSeriesType } from '../../src/types';
import { defaultLegendPadding } from '../../src/defaults';
import { NULL_STRING } from '../../src/constants';
+const expectedThemeProps = {
+ selector: ['all', 'inverse'],
+ selectorLabel: {
+ fontFamily: theme.typography.families.sansSerif,
+ fontSize: theme.typography.sizes.s,
+ color: theme.colors.grayscale.base,
+ borderColor: theme.colors.grayscale.base,
+ },
+};
+
+const sortData: DataRecord[] = [
+ { my_x_axis: 'abc', x: 1, y: 0, z: 2 },
+ { my_x_axis: 'foo', x: null, y: 10, z: 5 },
+ { my_x_axis: null, x: 4, y: 3, z: 7 },
+];
+
+const totalStackedValues = [3, 15, 14];
+
test('sortAndFilterSeries', () => {
const data: DataRecord[] = [
{ my_x_axis: 'abc', x: 1, y: 0, z: 2 },
@@ -44,6 +63,167 @@ test('sortAndFilterSeries', () => {
{ my_x_axis: null, x: 4, y: 3, z: 7 },
];
+
+test('sortRows by name ascending', () => {
+ expect(
+ sortRows(
+ sortData,
+ totalStackedValues,
+ 'my_x_axis',
+ SortSeriesType.Name,
+ true,
+ ),
+ ).toEqual([
+ { row: { my_x_axis: 'abc', x: 1, y: 0, z: 2 }, totalStackedValue: 3 },
+ { row: { my_x_axis: 'foo', x: null, y: 10, z: 5 }, totalStackedValue: 15 },
+ { row: { my_x_axis: null, x: 4, y: 3, z: 7 }, totalStackedValue: 14 },
+ ]);
+});
+
+test('sortRows by name descending', () => {
+ expect(
+ sortRows(
+ sortData,
+ totalStackedValues,
+ 'my_x_axis',
+ SortSeriesType.Name,
+ false,
+ ),
+ ).toEqual([
+ { row: { my_x_axis: null, x: 4, y: 3, z: 7 }, totalStackedValue: 14 },
+ { row: { my_x_axis: 'foo', x: null, y: 10, z: 5 }, totalStackedValue: 15 },
+ { row: { my_x_axis: 'abc', x: 1, y: 0, z: 2 }, totalStackedValue: 3 },
+ ]);
+});
+
+test('sortRows by sum ascending', () => {
+ expect(
+ sortRows(
+ sortData,
+ totalStackedValues,
+ 'my_x_axis',
+ SortSeriesType.Sum,
+ true,
+ ),
+ ).toEqual([
+ { row: { my_x_axis: 'abc', x: 1, y: 0, z: 2 }, totalStackedValue: 3 },
+ { row: { my_x_axis: null, x: 4, y: 3, z: 7 }, totalStackedValue: 14 },
+ { row: { my_x_axis: 'foo', x: null, y: 10, z: 5 }, totalStackedValue: 15 },
+ ]);
+});
+
+test('sortRows by sum descending', () => {
+ expect(
+ sortRows(
+ sortData,
+ totalStackedValues,
+ 'my_x_axis',
+ SortSeriesType.Sum,
+ false,
+ ),
+ ).toEqual([
+ { row: { my_x_axis: 'foo', x: null, y: 10, z: 5 }, totalStackedValue: 15 },
+ { row: { my_x_axis: null, x: 4, y: 3, z: 7 }, totalStackedValue: 14 },
+ { row: { my_x_axis: 'abc', x: 1, y: 0, z: 2 }, totalStackedValue: 3 },
+ ]);
+});
+
+test('sortRows by avg ascending', () => {
+ expect(
+ sortRows(
+ sortData,
+ totalStackedValues,
+ 'my_x_axis',
+ SortSeriesType.Avg,
+ true,
+ ),
+ ).toEqual([
+ { row: { my_x_axis: 'abc', x: 1, y: 0, z: 2 }, totalStackedValue: 3 },
+ { row: { my_x_axis: null, x: 4, y: 3, z: 7 }, totalStackedValue: 14 },
+ { row: { my_x_axis: 'foo', x: null, y: 10, z: 5 }, totalStackedValue: 15 },
+ ]);
+});
+
+test('sortRows by avg descending', () => {
+ expect(
+ sortRows(
+ sortData,
+ totalStackedValues,
+ 'my_x_axis',
+ SortSeriesType.Avg,
+ false,
+ ),
+ ).toEqual([
+ { row: { my_x_axis: 'foo', x: null, y: 10, z: 5 }, totalStackedValue: 15 },
+ { row: { my_x_axis: null, x: 4, y: 3, z: 7 }, totalStackedValue: 14 },
+ { row: { my_x_axis: 'abc', x: 1, y: 0, z: 2 }, totalStackedValue: 3 },
+ ]);
+});
+
+test('sortRows by min ascending', () => {
+ expect(
+ sortRows(
+ sortData,
+ totalStackedValues,
+ 'my_x_axis',
+ SortSeriesType.Min,
+ true,
+ ),
+ ).toEqual([
+ { row: { my_x_axis: 'abc', x: 1, y: 0, z: 2 }, totalStackedValue: 3 },
+ { row: { my_x_axis: null, x: 4, y: 3, z: 7 }, totalStackedValue: 14 },
+ { row: { my_x_axis: 'foo', x: null, y: 10, z: 5 }, totalStackedValue: 15 },
+ ]);
+});
+
+test('sortRows by min descending', () => {
+ expect(
+ sortRows(
+ sortData,
+ totalStackedValues,
+ 'my_x_axis',
+ SortSeriesType.Min,
+ false,
+ ),
+ ).toEqual([
+ { row: { my_x_axis: 'foo', x: null, y: 10, z: 5 }, totalStackedValue: 15 },
+ { row: { my_x_axis: null, x: 4, y: 3, z: 7 }, totalStackedValue: 14 },
+ { row: { my_x_axis: 'abc', x: 1, y: 0, z: 2 }, totalStackedValue: 3 },
+ ]);
+});
+
+test('sortRows by max ascending', () => {
+ expect(
+ sortRows(
+ sortData,
+ totalStackedValues,
+ 'my_x_axis',
+ SortSeriesType.Min,
+ true,
+ ),
+ ).toEqual([
+ { row: { my_x_axis: 'abc', x: 1, y: 0, z: 2 }, totalStackedValue: 3 },
+ { row: { my_x_axis: null, x: 4, y: 3, z: 7 }, totalStackedValue: 14 },
+ { row: { my_x_axis: 'foo', x: null, y: 10, z: 5 }, totalStackedValue: 15 },
+ ]);
+});
+
+test('sortRows by max descending', () => {
+ expect(
+ sortRows(
+ sortData,
+ totalStackedValues,
+ 'my_x_axis',
+ SortSeriesType.Min,
+ false,
+ ),
+ ).toEqual([
+ { row: { my_x_axis: 'foo', x: null, y: 10, z: 5 }, totalStackedValue: 15 },
+ { row: { my_x_axis: null, x: 4, y: 3, z: 7 }, totalStackedValue: 14 },
+ { row: { my_x_axis: 'abc', x: 1, y: 0, z: 2 }, totalStackedValue: 3 },
+ ]);
+});
+
expect(
sortAndFilterSeries(data, 'my_x_axis', [], SortSeriesType.Min, true),
).toEqual(['y', 'x', 'z']);
@@ -95,25 +275,29 @@ describe('extractSeries', () => {
abc: 5,
},
];
- expect(extractSeries(data)).toEqual([
- {
- id: 'Hulk',
- name: 'Hulk',
- data: [
- ['2000-01-01', null],
- ['2000-02-01', 2],
- ['2000-03-01', 1],
- ],
- },
- {
- id: 'abc',
- name: 'abc',
- data: [
- ['2000-01-01', 2],
- ['2000-02-01', 10],
- ['2000-03-01', 5],
- ],
- },
+ const totalStackedValues = [2, 12, 6];
+ expect(extractSeries(data, { totalStackedValues })).toEqual([
+ [
+ {
+ id: 'Hulk',
+ name: 'Hulk',
+ data: [
+ ['2000-01-01', null],
+ ['2000-02-01', 2],
+ ['2000-03-01', 1],
+ ],
+ },
+ {
+ id: 'abc',
+ name: 'abc',
+ data: [
+ ['2000-01-01', 2],
+ ['2000-02-01', 10],
+ ['2000-03-01', 5],
+ ],
+ },
+ ],
+ totalStackedValues,
]);
});
@@ -135,20 +319,30 @@ describe('extractSeries', () => {
abc: 5,
},
];
- expect(extractSeries(data, { xAxis: 'x', removeNulls: true })).toEqual([
- {
- id: 'Hulk',
- name: 'Hulk',
- data: [[2, 1]],
- },
- {
- id: 'abc',
- name: 'abc',
- data: [
- [1, 2],
- [2, 5],
- ],
- },
+ const totalStackedValues = [3, 12, 8];
+ expect(
+ extractSeries(data, {
+ totalStackedValues,
+ xAxis: 'x',
+ removeNulls: true,
+ }),
+ ).toEqual([
+ [
+ {
+ id: 'Hulk',
+ name: 'Hulk',
+ data: [[2, 1]],
+ },
+ {
+ id: 'abc',
+ name: 'abc',
+ data: [
+ [1, 2],
+ [2, 5],
+ ],
+ },
+ ],
+ totalStackedValues,
]);
});
@@ -195,23 +389,29 @@ describe('extractSeries', () => {
abc: null,
},
];
- expect(extractSeries(data, { fillNeighborValue: 0 })).toEqual([
- {
- id: 'abc',
- name: 'abc',
- data: [
- ['2000-01-01', null],
- ['2000-02-01', 0],
- ['2000-03-01', 1],
- ['2000-04-01', 0],
- ['2000-05-01', null],
- ['2000-06-01', 0],
- ['2000-07-01', 2],
- ['2000-08-01', 3],
- ['2000-09-01', 0],
- ['2000-10-01', null],
- ],
- },
+ const totalStackedValues = [0, 0, 1, 0, 0, 0, 2, 3, 0, 0];
+ expect(
+ extractSeries(data, { totalStackedValues, fillNeighborValue: 0 }),
+ ).toEqual([
+ [
+ {
+ id: 'abc',
+ name: 'abc',
+ data: [
+ ['2000-01-01', null],
+ ['2000-02-01', 0],
+ ['2000-03-01', 1],
+ ['2000-04-01', 0],
+ ['2000-05-01', null],
+ ['2000-06-01', 0],
+ ['2000-07-01', 2],
+ ['2000-08-01', 3],
+ ['2000-09-01', 0],
+ ['2000-10-01', null],
+ ],
+ },
+ ],
+ totalStackedValues,
]);
});
});