Skip to content

Commit

Permalink
feat: add various stackModes to bar and area charts
Browse files Browse the repository at this point in the history
the `stackMode` was introduced to apply percentage, wiggle and silhouette offset for stacking
charts.

BREAKING CHANGE: `stackAsPercentage` prop was replaced by `stackMode` that accept one `StackModes`.

fix elastic#715
  • Loading branch information
markov00 committed Jul 23, 2020
1 parent cbb1ccd commit bb2191f
Show file tree
Hide file tree
Showing 16 changed files with 287 additions and 162 deletions.
38 changes: 24 additions & 14 deletions .playground/playground.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand All @@ -36,23 +35,34 @@ export class Playground extends React.Component {
<Axis
id="x"
position={Position.Bottom}
tickFormat={timeFormatter(niceTimeFormatByDay(365 * 10))}
// tickFormat={timeFormatter(niceTimeFormatByDay(365 * 10))}
/>
<Settings />

<AreaSeries
id="spec1"
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')}
// 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' },
]}
/>

{/* <AreaSeries
Expand Down
6 changes: 3 additions & 3 deletions src/chart_types/xy_chart/domains/y_domain.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import { SpecTypes } from '../../../specs/constants';
import { Position } from '../../../utils/commons';
import { BARCHART_1Y0G } from '../../../utils/data_samples/test_dataset';
import { computeSeriesDomainsSelector } from '../state/selectors/compute_series_domains';
import { BasicSeriesSpec, SeriesTypes, DEFAULT_GLOBAL_ID } from '../utils/specs';
import { BasicSeriesSpec, SeriesTypes, DEFAULT_GLOBAL_ID, StackModes } from '../utils/specs';
import {
coerceYScaleTypes,
splitSpecsByGroupId,
Expand Down Expand Up @@ -461,7 +461,7 @@ describe('Y Domain', () => {
MockStore.addSpecs([
MockSeriesSpec.area({
...DEMO_AREA_SPEC_1,
stackAsPercentage: true,
stackMode: StackModes.Percentage,
}),
MockSeriesSpec.area({
...DEMO_AREA_SPEC_2,
Expand Down Expand Up @@ -492,7 +492,7 @@ describe('Y Domain', () => {
}),
MockSeriesSpec.area({
...DEMO_AREA_SPEC_1,
stackAsPercentage: true,
stackMode: StackModes.Percentage,
}),
], store);
const { yDomain } = computeSeriesDomainsSelector(store.getState());
Expand Down
30 changes: 19 additions & 11 deletions src/chart_types/xy_chart/domains/y_domain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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[];
}
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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: [],
};
Expand All @@ -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);
});
Expand Down
6 changes: 3 additions & 3 deletions src/chart_types/xy_chart/rendering/rendering.areas.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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',
Expand All @@ -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([
Expand Down
2 changes: 0 additions & 2 deletions src/chart_types/xy_chart/specs/bar_series.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@ const defaultProps = {
yScaleToDataExtent: false,
hideInLegend: false,
enableHistogramMode: false,
stackAsPercentage: false,
};

type SpecRequiredProps = Pick<BarSeriesSpec, 'id' | 'data'>;
Expand All @@ -55,6 +54,5 @@ export const BarSeries: React.FunctionComponent<SpecRequiredProps & SpecOptional
| 'yScaleToDataExtent'
| 'hideInLegend'
| 'enableHistogramMode'
| 'stackAsPercentage'
>(defaultProps),
);
Loading

0 comments on commit bb2191f

Please sign in to comment.