diff --git a/.playground/playground.tsx b/.playground/playground.tsx
index 489232967a..939747c428 100644
--- a/.playground/playground.tsx
+++ b/.playground/playground.tsx
@@ -20,8 +20,7 @@
import React from 'react';
-import { Chart, AreaSeries, Axis, Position, ScaleType, Fit, Settings, CurveType, timeFormatter, niceTimeFormatByDay } from '../src';
-import { data } from './data';
+import { Chart, AreaSeries, Axis, Position, Settings } from '../src';
export class Playground extends React.Component {
render() {
@@ -36,23 +35,34 @@ export class Playground extends React.Component {
d.year !== 2006 || d.series !== 'Manufacturing')}
+ // xAccessor="date"
+ // yAccessors={['count']}
+ // // y0Accessors={['metric0']}
+ // splitSeriesAccessors={['series']}
+ // stackAccessors={['date']}
+ // xScaleType={ScaleType.Time}
+ // fit={Fit.Lookahead}
+ // curve={CurveType.CURVE_MONOTONE_X}
+ // // areaSeriesStyle={{ point: { visible: true } }}
+ // // stackAsPercentage
+ // data={data.filter((d) => d.year !== 2006 || d.series !== 'Manufacturing')}
+
+ splitSeriesAccessors={['g']}
+ yAccessors={['y1']}
+ stackAccessors={['x']}
+ data={[
+ { x: 1, y1: 1, g: 'a' },
+ { x: 4, y1: 4, g: 'a' },
+ { x: 2, y1: 2, g: 'a' },
+ { x: 3, y1: 23, g: 'b' },
+ { x: 1, y1: 21, g: 'b' },
+ ]}
/>
{/* {
MockStore.addSpecs([
MockSeriesSpec.area({
...DEMO_AREA_SPEC_1,
- stackAsPercentage: true,
+ stackMode: StackModes.Percentage,
}),
MockSeriesSpec.area({
...DEMO_AREA_SPEC_2,
@@ -492,7 +492,7 @@ describe('Y Domain', () => {
}),
MockSeriesSpec.area({
...DEMO_AREA_SPEC_1,
- stackAsPercentage: true,
+ stackMode: StackModes.Percentage,
}),
], store);
const { yDomain } = computeSeriesDomainsSelector(store.getState());
diff --git a/src/chart_types/xy_chart/domains/y_domain.ts b/src/chart_types/xy_chart/domains/y_domain.ts
index f7e80fbe92..a0a8b1e67c 100644
--- a/src/chart_types/xy_chart/domains/y_domain.ts
+++ b/src/chart_types/xy_chart/domains/y_domain.ts
@@ -22,18 +22,19 @@ import { ScaleType } from '../../../scales/constants';
import { identity } from '../../../utils/commons';
import { computeContinuousDataDomain } from '../../../utils/domain';
import { GroupId } from '../../../utils/ids';
+import { Logger } from '../../../utils/logger';
import { isCompleteBound, isLowerBound, isUpperBound } from '../utils/axis_type_utils';
import { DataSeries, FormattedDataSeries } from '../utils/series';
-import { BasicSeriesSpec, YDomainRange, DEFAULT_GLOBAL_ID, SeriesTypes } from '../utils/specs';
+import { BasicSeriesSpec, YDomainRange, DEFAULT_GLOBAL_ID, SeriesTypes, StackModes } from '../utils/specs';
import { YDomain } from './types';
export type YBasicSeriesSpec = Pick<
BasicSeriesSpec,
'id' | 'seriesType' | 'yScaleType' | 'groupId' | 'stackAccessors' | 'yScaleToDataExtent' | 'useDefaultGroupDomain'
-> & { stackAsPercentage?: boolean; enableHistogramMode?: boolean };
+> & { stackMode?: StackModes; enableHistogramMode?: boolean };
interface GroupSpecs {
- isPercentageStack: boolean;
+ stackMode?: StackModes;
stacked: YBasicSeriesSpec[];
nonStacked: YBasicSeriesSpec[];
}
@@ -91,10 +92,10 @@ function mergeYDomainForGroup(
customDomain?: YDomainRange,
): YDomain {
const groupYScaleType = coerceYScaleTypes([...groupSpecs.stacked, ...groupSpecs.nonStacked]);
- const { isPercentageStack } = groupSpecs;
+ const { stackMode } = groupSpecs;
let domain: number[];
- if (isPercentageStack) {
+ if (stackMode === StackModes.Percentage) {
domain = computeContinuousDataDomain([0, 1], identity, customDomain);
} else {
// TODO remove when removing yScaleToDataExtent
@@ -166,16 +167,19 @@ function computeYDomain(dataseries: DataSeries[], isStacked = false) {
export function splitSpecsByGroupId(specs: YBasicSeriesSpec[]) {
const specsByGroupIds = new Map<
GroupId,
- { isPercentageStack: boolean; stacked: YBasicSeriesSpec[]; nonStacked: YBasicSeriesSpec[] }
+ { stackMode: StackModes | undefined; stacked: YBasicSeriesSpec[]; nonStacked: YBasicSeriesSpec[] }
>();
// After mobx->redux https://github.com/elastic/elastic-charts/pull/281 we keep the specs untouched on mount
// in MobX version, the stackAccessors was programmatically added to every histogram specs
// in ReduX version, we left untouched the specs, so we have to manually check that
- const isHistogramEnabled = specs.some(({ seriesType, enableHistogramMode }) => seriesType === SeriesTypes.Bar && enableHistogramMode);
- // split each specs by groupId and by stacked or not
+ const isHistogramEnabled = specs.some(
+ ({ seriesType, enableHistogramMode }) =>
+ seriesType === SeriesTypes.Bar && enableHistogramMode
+ );
+ // split each specs by groupId and by stacked or not
specs.forEach((spec) => {
const group = specsByGroupIds.get(spec.groupId) || {
- isPercentageStack: false,
+ stackMode: undefined,
stacked: [],
nonStacked: [],
};
@@ -189,8 +193,12 @@ export function splitSpecsByGroupId(specs: YBasicSeriesSpec[]) {
} else {
group.nonStacked.push(spec);
}
- if (spec.stackAsPercentage === true) {
- group.isPercentageStack = true;
+ if (group.stackMode === undefined && spec.stackMode !== undefined) {
+ group.stackMode = spec.stackMode;
+ }
+ if (spec.stackMode !== undefined && group.stackMode !== undefined && group.stackMode !== spec.stackMode) {
+ Logger.warn(`Is not possible to mix different stackModes, please align all stackMode on the same GroupId
+ to the same mode. The default behaviour will be to use the first encountered stackMode on the series`);
}
specsByGroupIds.set(spec.groupId, group);
});
diff --git a/src/chart_types/xy_chart/rendering/rendering.areas.test.ts b/src/chart_types/xy_chart/rendering/rendering.areas.test.ts
index 7d3112c5ee..2f2c98b862 100644
--- a/src/chart_types/xy_chart/rendering/rendering.areas.test.ts
+++ b/src/chart_types/xy_chart/rendering/rendering.areas.test.ts
@@ -27,7 +27,7 @@ import { LIGHT_THEME } from '../../../utils/themes/light_theme';
import { computeSeriesDomains } from '../state/utils/utils';
import { IndexedGeometryMap } from '../utils/indexed_geometry_map';
import { computeXScale, computeYScales } from '../utils/scales';
-import { AreaSeriesSpec, SeriesTypes } from '../utils/specs';
+import { AreaSeriesSpec, SeriesTypes, StackModes } from '../utils/specs';
import { renderArea } from './rendering';
const SPEC_ID = 'spec_1';
@@ -1136,7 +1136,7 @@ describe('Rendering points - areas', () => {
xScaleType: ScaleType.Time,
yScaleType: ScaleType.Linear,
stackAccessors: [0],
- stackAsPercentage: true,
+ stackMode: StackModes.Percentage,
});
const pointSeriesSpec2: AreaSeriesSpec = MockSeriesSpec.area({
id: 'spec_2',
@@ -1149,7 +1149,7 @@ describe('Rendering points - areas', () => {
xScaleType: ScaleType.Time,
yScaleType: ScaleType.Linear,
stackAccessors: [0],
- stackAsPercentage: true,
+ stackMode: StackModes.Percentage,
});
const pointSeriesDomains = computeSeriesDomains([pointSeriesSpec1, pointSeriesSpec2]);
expect(pointSeriesDomains.formattedDataSeries.stacked[0].dataSeries[0].data).toMatchObject([
diff --git a/src/chart_types/xy_chart/specs/bar_series.tsx b/src/chart_types/xy_chart/specs/bar_series.tsx
index ab6933e99e..d8d4a1e5bd 100644
--- a/src/chart_types/xy_chart/specs/bar_series.tsx
+++ b/src/chart_types/xy_chart/specs/bar_series.tsx
@@ -37,7 +37,6 @@ const defaultProps = {
yScaleToDataExtent: false,
hideInLegend: false,
enableHistogramMode: false,
- stackAsPercentage: false,
};
type SpecRequiredProps = Pick;
@@ -55,6 +54,5 @@ export const BarSeries: React.FunctionComponent(defaultProps),
);
diff --git a/src/chart_types/xy_chart/utils/__snapshots__/series.test.ts.snap b/src/chart_types/xy_chart/utils/__snapshots__/series.test.ts.snap
index 543fa7eb75..1ed26fce79 100644
--- a/src/chart_types/xy_chart/utils/__snapshots__/series.test.ts.snap
+++ b/src/chart_types/xy_chart/utils/__snapshots__/series.test.ts.snap
@@ -22095,7 +22095,11 @@ Array [
Object {
"data": Array [
Object {
- "datum": undefined,
+ "datum": Object {
+ "g": "a",
+ "x": 1,
+ "y1": 1,
+ },
"filled": undefined,
"initialY0": null,
"initialY1": 1,
@@ -22105,7 +22109,11 @@ Array [
"y1": 1,
},
Object {
- "datum": undefined,
+ "datum": Object {
+ "g": "a",
+ "x": 2,
+ "y1": 2,
+ },
"filled": undefined,
"initialY0": null,
"initialY1": 2,
@@ -22115,17 +22123,23 @@ Array [
"y1": 2,
},
Object {
- "datum": null,
- "filled": undefined,
+ "datum": undefined,
+ "filled": Object {
+ "x": 3,
+ },
"initialY0": null,
"initialY1": null,
"mark": null,
"x": 3,
- "y0": NaN,
- "y1": NaN,
+ "y0": 0,
+ "y1": 0,
},
Object {
- "datum": undefined,
+ "datum": Object {
+ "g": "a",
+ "x": 4,
+ "y1": 4,
+ },
"filled": undefined,
"initialY0": null,
"initialY1": 4,
@@ -22135,18 +22149,25 @@ Array [
"y1": 4,
},
],
- "key": "a",
+ "key": "spec{spec1}yAccessor{y1}splitAccessors{g-a}",
"seriesKeys": Array [
"a",
+ "y1",
],
"specId": "spec1",
- "splitAccessors": Map {},
+ "splitAccessors": Map {
+ "g" => "a",
+ },
"yAccessor": "y1",
},
Object {
"data": Array [
Object {
- "datum": undefined,
+ "datum": Object {
+ "g": "b",
+ "x": 1,
+ "y1": 21,
+ },
"filled": undefined,
"initialY0": null,
"initialY1": 21,
@@ -22156,17 +22177,23 @@ Array [
"y1": 22,
},
Object {
- "datum": null,
- "filled": undefined,
+ "datum": undefined,
+ "filled": Object {
+ "x": 2,
+ },
"initialY0": null,
"initialY1": null,
"mark": null,
"x": 2,
- "y0": NaN,
- "y1": NaN,
+ "y0": 2,
+ "y1": 2,
},
Object {
- "datum": undefined,
+ "datum": Object {
+ "g": "b",
+ "x": 3,
+ "y1": 23,
+ },
"filled": undefined,
"initialY0": null,
"initialY1": 23,
@@ -22176,22 +22203,27 @@ Array [
"y1": 23,
},
Object {
- "datum": null,
- "filled": undefined,
+ "datum": undefined,
+ "filled": Object {
+ "x": 4,
+ },
"initialY0": null,
"initialY1": null,
"mark": null,
"x": 4,
- "y0": NaN,
- "y1": NaN,
+ "y0": 4,
+ "y1": 4,
},
],
- "key": "b",
+ "key": "spec{spec1}yAccessor{y1}splitAccessors{g-b}",
"seriesKeys": Array [
"b",
+ "y1",
],
"specId": "spec1",
- "splitAccessors": Map {},
+ "splitAccessors": Map {
+ "g" => "b",
+ },
"yAccessor": "y1",
},
]
@@ -22641,7 +22673,11 @@ Array [
Object {
"data": Array [
Object {
- "datum": undefined,
+ "datum": Object {
+ "g": "a",
+ "x": 1,
+ "y1": 1,
+ },
"filled": undefined,
"initialY0": null,
"initialY1": 1,
@@ -22651,7 +22687,11 @@ Array [
"y1": 1,
},
Object {
- "datum": undefined,
+ "datum": Object {
+ "g": "a",
+ "x": 2,
+ "y1": 2,
+ },
"filled": undefined,
"initialY0": null,
"initialY1": 2,
@@ -22661,17 +22701,23 @@ Array [
"y1": 2,
},
Object {
- "datum": null,
- "filled": undefined,
+ "datum": undefined,
+ "filled": Object {
+ "x": 3,
+ },
"initialY0": null,
"initialY1": null,
"mark": null,
"x": 3,
- "y0": NaN,
- "y1": NaN,
+ "y0": 0,
+ "y1": 0,
},
Object {
- "datum": undefined,
+ "datum": Object {
+ "g": "a",
+ "x": 4,
+ "y1": 4,
+ },
"filled": undefined,
"initialY0": null,
"initialY1": 4,
@@ -22681,18 +22727,25 @@ Array [
"y1": 4,
},
],
- "key": "a",
+ "key": "spec{spec1}yAccessor{y1}splitAccessors{g-a}",
"seriesKeys": Array [
"a",
+ "y1",
],
"specId": "spec1",
- "splitAccessors": Map {},
+ "splitAccessors": Map {
+ "g" => "a",
+ },
"yAccessor": "y1",
},
Object {
"data": Array [
Object {
- "datum": undefined,
+ "datum": Object {
+ "g": "b",
+ "x": 1,
+ "y1": 21,
+ },
"filled": undefined,
"initialY0": null,
"initialY1": 21,
@@ -22702,17 +22755,23 @@ Array [
"y1": 22,
},
Object {
- "datum": null,
- "filled": undefined,
+ "datum": undefined,
+ "filled": Object {
+ "x": 2,
+ },
"initialY0": null,
"initialY1": null,
"mark": null,
"x": 2,
- "y0": NaN,
- "y1": NaN,
+ "y0": 2,
+ "y1": 2,
},
Object {
- "datum": undefined,
+ "datum": Object {
+ "g": "b",
+ "x": 3,
+ "y1": 23,
+ },
"filled": undefined,
"initialY0": null,
"initialY1": 23,
@@ -22722,22 +22781,27 @@ Array [
"y1": 23,
},
Object {
- "datum": null,
- "filled": undefined,
+ "datum": undefined,
+ "filled": Object {
+ "x": 4,
+ },
"initialY0": null,
"initialY1": null,
"mark": null,
"x": 4,
- "y0": NaN,
- "y1": NaN,
+ "y0": 4,
+ "y1": 4,
},
],
- "key": "b",
+ "key": "spec{spec1}yAccessor{y1}splitAccessors{g-b}",
"seriesKeys": Array [
"b",
+ "y1",
],
"specId": "spec1",
- "splitAccessors": Map {},
+ "splitAccessors": Map {
+ "g" => "b",
+ },
"yAccessor": "y1",
},
]
diff --git a/src/chart_types/xy_chart/utils/series.test.ts b/src/chart_types/xy_chart/utils/series.test.ts
index 721f9e3fff..7e71bab96f 100644
--- a/src/chart_types/xy_chart/utils/series.test.ts
+++ b/src/chart_types/xy_chart/utils/series.test.ts
@@ -107,34 +107,25 @@ describe('Series', () => {
expect([...splittedSeries.dataSeries.values()]).toMatchSnapshot();
});
test('Can stack simple dataseries', () => {
- const dataSeries: DataSeries[] = [
- {
- specId: 'spec1',
- yAccessor: 'y1',
- splitAccessors: new Map(),
- seriesKeys: ['a'],
- key: 'a',
- data: [
- { x: 1, y1: 1, mark: null, y0: null, initialY1: 1, initialY0: null, datum: undefined },
- { x: 2, y1: 2, mark: null, y0: null, initialY1: 2, initialY0: null, datum: undefined },
- { x: 4, y1: 4, mark: null, y0: null, initialY1: 4, initialY0: null, datum: undefined },
- ],
- },
- {
- specId: 'spec1',
- yAccessor: 'y1',
- splitAccessors: new Map(),
- seriesKeys: ['b'],
- key: 'b',
- data: [
- { x: 1, y1: 21, mark: null, y0: null, initialY1: 21, initialY0: null, datum: undefined },
- { x: 3, y1: 23, mark: null, y0: null, initialY1: 23, initialY0: null, datum: undefined },
- ],
- },
- ];
- const xValues = new Set([1, 2, 3, 4]);
- const stackedValues = formatStackedDataSeriesValues(dataSeries, false, xValues);
- expect(stackedValues).toMatchSnapshot();
+ const store = MockStore.default();
+ MockStore.addSpecs(MockSeriesSpec.area({
+ id: 'spec1',
+ splitSeriesAccessors: ['g'],
+ yAccessors: ['y1'],
+ stackAccessors: ['x'],
+ xScaleType: ScaleType.Linear,
+ data: [
+ { x: 1, y1: 1, g: 'a' },
+ { x: 2, y1: 2, g: 'a' },
+ { x: 4, y1: 4, g: 'a' },
+ { x: 1, y1: 21, g: 'b' },
+ { x: 3, y1: 23, g: 'b' },
+ ],
+ }), store);
+
+ const { formattedDataSeries: { stacked } } = computeSeriesDomainsSelector(store.getState());
+
+ expect(stacked[0].dataSeries).toMatchSnapshot();
});
test('Can stack multiple dataseries', () => {
const dataSeries: DataSeries[] = [
@@ -192,38 +183,55 @@ describe('Series', () => {
},
];
const xValues = new Set([1, 2, 3, 4]);
- const stackedValues = formatStackedDataSeriesValues(dataSeries, false, xValues);
+ const stackedValues = formatStackedDataSeriesValues(dataSeries, xValues);
expect(stackedValues).toMatchSnapshot();
});
test('Can stack unsorted dataseries', () => {
- const dataSeries: DataSeries[] = [
- {
- specId: 'spec1',
- yAccessor: 'y1',
- splitAccessors: new Map(),
- seriesKeys: ['a'],
- key: 'a',
- data: [
- { x: 1, y1: 1, mark: null, y0: null, initialY1: 1, initialY0: null, datum: undefined },
- { x: 4, y1: 4, mark: null, y0: null, initialY1: 4, initialY0: null, datum: undefined },
- { x: 2, y1: 2, mark: null, y0: null, initialY1: 2, initialY0: null, datum: undefined },
- ],
- },
- {
- specId: 'spec1',
- yAccessor: 'y1',
- splitAccessors: new Map(),
- seriesKeys: ['b'],
- key: 'b',
- data: [
- { x: 3, y1: 23, mark: null, y0: null, initialY1: 23, initialY0: null, datum: undefined },
- { x: 1, y1: 21, mark: null, y0: null, initialY1: 21, initialY0: null, datum: undefined },
- ],
- },
- ];
- const xValues = new Set([1, 2, 3, 4]);
- const stackedValues = formatStackedDataSeriesValues(dataSeries, false, xValues);
- expect(stackedValues).toMatchSnapshot();
+ const store = MockStore.default();
+ MockStore.addSpecs(MockSeriesSpec.area({
+ id: 'spec1',
+ splitSeriesAccessors: ['g'],
+ yAccessors: ['y1'],
+ stackAccessors: ['x'],
+ xScaleType: ScaleType.Linear,
+ data: [
+ { x: 1, y1: 1, g: 'a' },
+ { x: 4, y1: 4, g: 'a' },
+ { x: 2, y1: 2, g: 'a' },
+ { x: 3, y1: 23, g: 'b' },
+ { x: 1, y1: 21, g: 'b' },
+ ],
+ }), store);
+ const { formattedDataSeries: { stacked } } = computeSeriesDomainsSelector(store.getState());
+ // const dataSeries: DataSeries[] = [
+ // {
+ // specId: 'spec1',
+ // yAccessor: 'y1',
+ // splitAccessors: new Map(),
+ // seriesKeys: ['a'],
+ // key: 'a',
+ // data: [
+ // { x: 1, y1: 1, mark: null, y0: null, initialY1: 1, initialY0: null, datum: undefined },
+ // { x: 4, y1: 4, mark: null, y0: null, initialY1: 4, initialY0: null, datum: undefined },
+ // { x: 2, y1: 2, mark: null, y0: null, initialY1: 2, initialY0: null, datum: undefined },
+ // ],
+ // },
+ // {
+ // specId: 'spec1',
+ // yAccessor: 'y1',
+ // splitAccessors: new Map(),
+ // seriesKeys: ['b'],
+ // key: 'b',
+ // data: [
+ // { x: 3, y1: 23, mark: null, y0: null, initialY1: 23, initialY0: null, datum: undefined },
+ // { x: 1, y1: 21, mark: null, y0: null, initialY1: 21, initialY0: null, datum: undefined },
+ // ],
+ // },
+ // ];
+ // const xValues = new Set([1, 2, 3, 4]);
+ // const stackedValues = formatStackedDataSeriesValues(dataSeries, xValues);
+
+ expect(stacked[0].dataSeries).toMatchSnapshot();
});
test('Can stack high volume of dataseries', () => {
const maxArrayItems = 1000;
@@ -246,7 +254,7 @@ describe('Series', () => {
},
];
const xValues = new Set(new Array(maxArrayItems).fill(0).map((d, i) => i));
- const stackedValues = formatStackedDataSeriesValues(dataSeries, false, xValues);
+ const stackedValues = formatStackedDataSeriesValues(dataSeries, xValues);
expect(stackedValues).toMatchSnapshot();
});
test('Can stack simple dataseries with scale to extent', () => {
diff --git a/src/chart_types/xy_chart/utils/series.ts b/src/chart_types/xy_chart/utils/series.ts
index 77aa3bb65a..666c76e431 100644
--- a/src/chart_types/xy_chart/utils/series.ts
+++ b/src/chart_types/xy_chart/utils/series.ts
@@ -287,14 +287,14 @@ export function getFormattedDataseries(
}[] = [];
specsByGroupIdsEntries.forEach(([groupId, groupSpecs]) => {
- const { isPercentageStack } = groupSpecs;
+ const { stackMode } = groupSpecs;
// format stacked data series
const stackedDataSeries = getDataSeriesBySpecGroup(groupSpecs.stacked, availableDataSeries);
const fittedDataSeries = applyFitFunctionToDataSeries(stackedDataSeries.dataSeries, seriesSpecs, xScaleType);
const fittedAndStackedDataSeries = formatStackedDataSeriesValues(
fittedDataSeries,
- isPercentageStack,
xValues,
+ stackMode,
);
stackedFormattedDataSeries.push({
diff --git a/src/chart_types/xy_chart/utils/specs.ts b/src/chart_types/xy_chart/utils/specs.ts
index 889309646e..16a3628105 100644
--- a/src/chart_types/xy_chart/utils/specs.ts
+++ b/src/chart_types/xy_chart/utils/specs.ts
@@ -49,15 +49,28 @@ export type BarStyleOverride = RecursivePartial | Color | null;
/** @public */
export type PointStyleOverride = RecursivePartial | Color | null;
+/** @public */
export const SeriesTypes = Object.freeze({
Area: 'area' as const,
Bar: 'bar' as const,
Line: 'line' as const,
Bubble: 'bubble' as const,
});
+
/** @public */
export type SeriesTypes = $Values;
+
+/** @public */
+export const StackModes = Object.freeze({
+ Percentage: 'percentage' as const,
+ Wiggle: 'wiggle' as const,
+ Silhouette: 'silhouette' as const,
+});
+
+/** @public */
+export type StackModes = $Values;
+
/**
* Override for bar styles per datum
*
@@ -446,9 +459,10 @@ export type BarSeriesSpec = BasicSeriesSpec &
enableHistogramMode?: boolean;
barSeriesStyle?: RecursivePartial;
/**
- * Stack each series in percentage for each point.
+ * Stack each series using a specific mode: Percentage, Wiggle, Silhouette.
+ * The last two modes are generally used for stream graphs
*/
- stackAsPercentage?: boolean;
+ stackMode?: StackModes;
/**
* Functional accessor to return custom color or style for bar datum
*/
@@ -541,9 +555,10 @@ export type AreaSeriesSpec = BasicSeriesSpec &
curve?: CurveType;
areaSeriesStyle?: RecursivePartial;
/**
- * Stack each series in percentage for each point.
+ * Stack each series using a specific mode: Percentage, Wiggle, Silhouette.
+ * The last two modes are generally used for stream graphs
*/
- stackAsPercentage?: boolean;
+ stackMode?: StackModes;
/**
* An optional functional accessor to return custom color or style for point datum
*/
diff --git a/src/chart_types/xy_chart/utils/stacked_percent_series_utils.test.ts b/src/chart_types/xy_chart/utils/stacked_percent_series_utils.test.ts
index d8bd5a9539..b88f6da40f 100644
--- a/src/chart_types/xy_chart/utils/stacked_percent_series_utils.test.ts
+++ b/src/chart_types/xy_chart/utils/stacked_percent_series_utils.test.ts
@@ -21,6 +21,7 @@ import { MockSeriesSpec } from '../../../mocks/specs';
import { MockStore } from '../../../mocks/store';
import { ScaleType } from '../../../scales/constants';
import { computeSeriesDomainsSelector } from '../state/selectors/compute_series_domains';
+import { StackModes } from './specs';
describe('Stacked Series Utils', () => {
const STANDARD_DATA_SET = [{ x: 0, y1: 10, g: 'a' }, { x: 0, y1: 20, g: 'b' }, { x: 0, y1: 70, g: 'c' }];
@@ -43,14 +44,17 @@ describe('Stacked Series Utils', () => {
const store = MockStore.default();
MockStore.addSpecs([
MockSeriesSpec.area({
+ xAccessor: 'x',
yAccessors: ['y1'],
splitSeriesAccessors: ['g'],
stackAccessors: ['x'],
- stackAsPercentage: true,
+ stackMode: StackModes.Percentage,
data: STANDARD_DATA_SET,
}),
], store);
- const { formattedDataSeries: { stacked } } = computeSeriesDomainsSelector(store.getState());
+ const { formattedDataSeries } = computeSeriesDomainsSelector(store.getState());
+ const { stacked } = formattedDataSeries;
+ // console.log(JSON.stringify(formattedDataSeries.stacked, null, 2));
const [data0] = stacked[0].dataSeries[0].data;
expect(data0.initialY1).toBe(10);
expect(data0.y0).toBe(0);
@@ -73,7 +77,7 @@ describe('Stacked Series Utils', () => {
yAccessors: ['y1'],
splitSeriesAccessors: ['g'],
stackAccessors: ['x'],
- stackAsPercentage: true,
+ stackMode: StackModes.Percentage,
data: WITH_NULL_DATASET,
}),
], store);
@@ -106,7 +110,7 @@ describe('Stacked Series Utils', () => {
y0Accessors: ['y0'],
splitSeriesAccessors: ['g'],
stackAccessors: ['x'],
- stackAsPercentage: true,
+ stackMode: StackModes.Percentage,
data: STANDARD_DATA_SET_WY0,
}),
], store);
@@ -138,7 +142,7 @@ describe('Stacked Series Utils', () => {
y0Accessors: ['y0'],
splitSeriesAccessors: ['g'],
stackAccessors: ['x'],
- stackAsPercentage: true,
+ stackMode: StackModes.Percentage,
data: WITH_NULL_DATASET_WY0,
}),
], store);
@@ -172,7 +176,7 @@ describe('Stacked Series Utils', () => {
y0Accessors: ['y0'],
splitSeriesAccessors: ['g'],
stackAccessors: ['x'],
- stackAsPercentage: true,
+ stackMode: StackModes.Percentage,
data: DATA_SET_WITH_NULL_2,
}),
], store);
diff --git a/src/chart_types/xy_chart/utils/stacked_series_utils.test.ts b/src/chart_types/xy_chart/utils/stacked_series_utils.test.ts
index 11290679ed..da046811e4 100644
--- a/src/chart_types/xy_chart/utils/stacked_series_utils.test.ts
+++ b/src/chart_types/xy_chart/utils/stacked_series_utils.test.ts
@@ -21,6 +21,7 @@ import { MockSeriesSpec } from '../../../mocks/specs';
import { MockStore } from '../../../mocks/store';
import { ScaleType } from '../../../scales/constants';
import { computeSeriesDomainsSelector } from '../state/selectors/compute_series_domains';
+import { StackModes } from './specs';
describe('Stacked Series Utils', () => {
const EMPTY_DATA_SET = MockSeriesSpec.area({
@@ -118,7 +119,7 @@ describe('Stacked Series Utils', () => {
MockStore.addSpecs(
MockSeriesSpec.area({
...STANDARD_DATA_SET,
- stackAsPercentage: true,
+ stackMode: StackModes.Percentage,
}),
store);
const { formattedDataSeries: { stacked: [{ dataSeries }] } } = computeSeriesDomainsSelector(store.getState());
@@ -148,7 +149,7 @@ describe('Stacked Series Utils', () => {
const store = MockStore.default();
MockStore.addSpecs(MockSeriesSpec.area({
...WITH_NULL_DATASET,
- stackAsPercentage: true,
+ stackMode: StackModes.Percentage,
}), store);
const { formattedDataSeries: { stacked: [{ dataSeries }] } } = computeSeriesDomainsSelector(store.getState());
diff --git a/src/chart_types/xy_chart/utils/stacked_series_utils.ts b/src/chart_types/xy_chart/utils/stacked_series_utils.ts
index 8e29333236..0205484f26 100644
--- a/src/chart_types/xy_chart/utils/stacked_series_utils.ts
+++ b/src/chart_types/xy_chart/utils/stacked_series_utils.ts
@@ -21,6 +21,8 @@ import {
stack as D3Stack,
stackOffsetExpand as D3StackOffsetExpand,
stackOffsetNone as D3StackOffsetNone,
+ stackOffsetSilhouette as D3StackOffsetSilhouette,
+ stackOffsetWiggle as D3StackOffsetWiggle,
stackOrderNone,
SeriesPoint,
} from 'd3-shape';
@@ -28,6 +30,7 @@ import {
import { SeriesKey } from '../../../commons/series_id';
import { ScaleType } from '../../../scales/constants';
import { DataSeries, DataSeriesDatum } from './series';
+import { StackModes } from './specs';
/** @internal */
export interface StackedValues {
@@ -66,8 +69,8 @@ type D3UnionStack = Record<
/** @internal */
export function formatStackedDataSeriesValues(
dataSeries: DataSeries[],
- isPercentageMode: boolean,
xValues: Set,
+ stackMode?: StackModes,
): DataSeries[] {
const dataSeriesKeys = dataSeries.reduce>((acc, curr) => {
acc[curr.key] = curr;
@@ -97,10 +100,12 @@ export function formatStackedDataSeriesValues(
xValueMap.set(key, dsMap);
});
- const stackOffset = isPercentageMode ? D3StackOffsetExpand : D3StackOffsetNone;
+ const stackOffset = getOffsetBasedOnStackMode(stackMode);
+
+ const keys = Object.keys(dataSeriesKeys).reduce((acc, key) => ([...acc, `${key}-y0`, `${key}-y1`]), []);
const stack = D3Stack()
- .keys(Object.keys(dataSeriesKeys).reduce((acc, key) => ([...acc, `${key}-y0`, `${key}-y1`]), []))
+ .keys(keys)
.order(stackOrderNone)
.offset(stackOffset)(reorderedArray);
@@ -120,14 +125,14 @@ export function formatStackedDataSeriesValues(
return acc;
}, {});
+
return Object.keys(unionedYStacks).map((stackedDataSeriesKey) => {
const dataSeriesProps = dataSeriesKeys[stackedDataSeriesKey];
const dsMap = xValueMap.get(stackedDataSeriesKey);
const { y0: y0StackArray, y1: y1StackArray } = unionedYStacks[stackedDataSeriesKey];
-
const data = y1StackArray.map((y1Stack, index) => {
const { x } = y1Stack.data;
- if (!x) {
+ if (x === undefined || x === null) {
return null;
}
const originalData = dsMap?.get(x);
@@ -148,10 +153,23 @@ export function formatStackedDataSeriesValues(
filled,
};
}).filter((d) => d !== null) as DataSeriesDatum[];
-
return {
...dataSeriesProps,
data,
};
});
}
+
+
+function getOffsetBasedOnStackMode(stackMode?: StackModes) {
+ switch (stackMode) {
+ case StackModes.Percentage:
+ return D3StackOffsetExpand;
+ case StackModes.Silhouette:
+ return D3StackOffsetSilhouette;
+ case StackModes.Wiggle:
+ return D3StackOffsetWiggle;
+ default:
+ return D3StackOffsetNone;
+ }
+}
diff --git a/src/mocks/specs/specs.ts b/src/mocks/specs/specs.ts
index 69ce8abc66..f30ebe6641 100644
--- a/src/mocks/specs/specs.ts
+++ b/src/mocks/specs/specs.ts
@@ -59,7 +59,6 @@ export class MockSeriesSpec {
yAccessors: ['y'],
hideInLegend: false,
enableHistogramMode: false,
- stackAsPercentage: false,
data: [] as any[],
};
diff --git a/stories/area/8_stacked_percentage.tsx b/stories/area/8_stacked_percentage.tsx
index 6c072875ac..9a91634502 100644
--- a/stories/area/8_stacked_percentage.tsx
+++ b/stories/area/8_stacked_percentage.tsx
@@ -20,7 +20,7 @@
import { boolean } from '@storybook/addon-knobs';
import React from 'react';
-import { AreaSeries, Axis, Chart, Position, ScaleType, Settings } from '../../src';
+import { AreaSeries, Axis, Chart, Position, ScaleType, Settings, StackModes } from '../../src';
export const Example = () => {
const stackedAsPercentage = boolean('stacked as percentage', true);
@@ -42,7 +42,7 @@ export const Example = () => {
xAccessor="x"
yAccessors={['y']}
stackAccessors={['x']}
- stackAsPercentage={stackedAsPercentage}
+ stackMode={stackedAsPercentage ? StackModes.Percentage : undefined}
splitSeriesAccessors={['g']}
data={[
{ x: 0, y: 2, g: 'a' },
diff --git a/stories/area/8_stacked_percentage_zeros.tsx b/stories/area/8_stacked_percentage_zeros.tsx
index 68bfe60239..949ef0fd7f 100644
--- a/stories/area/8_stacked_percentage_zeros.tsx
+++ b/stories/area/8_stacked_percentage_zeros.tsx
@@ -19,7 +19,7 @@
import React from 'react';
-import { AreaSeries, Axis, Chart, Position, ScaleType, Settings, niceTimeFormatter } from '../../src';
+import { AreaSeries, Axis, Chart, Position, ScaleType, Settings, niceTimeFormatter, StackModes } from '../../src';
export const Example = () => (
@@ -46,7 +46,7 @@ export const Example = () => (
yAccessors={[1]}
data={DATA[0].data}
stackAccessors={[0]}
- stackAsPercentage
+ stackMode={StackModes.Percentage}
/>
(
yAccessors={[1]}
data={DATA[1].data}
stackAccessors={[0]}
- stackAsPercentage
+ stackMode={StackModes.Percentage}
/>
(
xAccessor={0}
yAccessors={[1]}
data={DATA[2].data}
- stackAsPercentage
+ stackMode={StackModes.Percentage}
stackAccessors={[0]}
/>
diff --git a/stories/bar/12_stacked_as_percentage.tsx b/stories/bar/12_stacked_as_percentage.tsx
index ff95d6031f..e9519049af 100644
--- a/stories/bar/12_stacked_as_percentage.tsx
+++ b/stories/bar/12_stacked_as_percentage.tsx
@@ -20,7 +20,7 @@
import { boolean } from '@storybook/addon-knobs';
import React from 'react';
-import { Axis, BarSeries, Chart, Position, ScaleType, Settings } from '../../src';
+import { Axis, BarSeries, Chart, Position, ScaleType, Settings, StackModes } from '../../src';
import { SB_SOURCE_PANEL } from '../utils/storybook';
export const Example = () => {
@@ -44,7 +44,7 @@ export const Example = () => {
xAccessor="x"
yAccessors={['y']}
stackAccessors={clusterBars ? [] : ['x']}
- stackAsPercentage={clusterBars ? false : stackedAsPercentage}
+ stackMode={clusterBars ? undefined : StackModes.Percentage}
splitSeriesAccessors={['g']}
data={[
{ x: 0, y: 2, g: 'a' },