Skip to content

Commit ef84fd4

Browse files
authored
feat(data): fill datasets with zeros with missing points when stacked (#409)
When displaying a stacked area/line chart with multiple data sets we need to fill the dataset with zeros on missing data points. The rendering is affected also: it will hide the filled missing point but will continue to show the area beneath it. fix #388
1 parent 3b37328 commit ef84fd4

File tree

15 files changed

+480
-192
lines changed

15 files changed

+480
-192
lines changed

.playground/index.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
background: white;
2626
position: relative;
2727
width: 800px;
28-
height: 150px;
28+
height: 450px;
2929
margin: 10px;
3030
}
3131
</style>

.playground/playgroud.tsx

Lines changed: 43 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,75 +1,69 @@
11
import React, { Fragment } from 'react';
2-
import {
3-
Axis,
4-
Chart,
5-
getAxisId,
6-
getSpecId,
7-
Position,
8-
ScaleType,
9-
Settings,
10-
BarSeries,
11-
LineSeries,
12-
AreaSeries,
13-
} from '../src';
2+
import { Axis, Chart, getAxisId, getSpecId, Position, ScaleType, Settings, AreaSeries } from '../src';
143

154
export class Playground extends React.Component {
165
render() {
176
return (
187
<Fragment>
198
<div className="chart">
20-
<Chart className="story-chart">
21-
<Settings
22-
theme={{
23-
areaSeriesStyle: {
24-
point: {
25-
visible: true,
26-
},
27-
},
28-
}}
29-
xDomain={{
30-
max: 3.8,
31-
}}
32-
/>
9+
<Chart>
10+
<Settings showLegend theme={{ areaSeriesStyle: { point: { visible: true } } }} />
3311
<Axis
3412
id={getAxisId('bottom')}
3513
position={Position.Bottom}
3614
title={'Bottom axis'}
3715
showOverlappingTicks={true}
3816
/>
3917
<Axis
40-
id={getAxisId('left')}
18+
id={getAxisId('left2')}
4119
title={'Left axis'}
4220
position={Position.Left}
43-
domain={{
44-
max: 5,
45-
}}
46-
/>
47-
48-
<BarSeries
49-
id={getSpecId('bar')}
50-
xScaleType={ScaleType.Linear}
51-
yScaleType={ScaleType.Linear}
52-
xAccessor={0}
53-
yAccessors={[1]}
54-
data={[[0, 1], [1, 2], [2, 10], [3, 4], [4, 5]]}
21+
tickFormat={(d: any) => Number(d).toFixed(2)}
5522
/>
56-
57-
<LineSeries
58-
id={getSpecId('line')}
23+
<AreaSeries
24+
id={getSpecId('bars1')}
5925
xScaleType={ScaleType.Linear}
6026
yScaleType={ScaleType.Linear}
61-
xAccessor={0}
62-
yAccessors={[1]}
63-
data={[[0, 1], [1, 2], [2, 10], [3, 4], [4, 5]]}
27+
xAccessor="x"
28+
yAccessors={['y']}
29+
stackAccessors={['x']}
30+
splitSeriesAccessors={['g']}
31+
// curve={CurveType.CURVE_MONOTONE_X}
32+
data={[
33+
{ x: 0, y: 2, g: 'a' },
34+
{ x: 1, y: 7, g: 'a' },
35+
{ x: 2, y: 3, g: 'a' },
36+
{ x: 3, y: 6, g: 'a' },
37+
{ x: 0, y: 4, g: 'b' },
38+
{ x: 1, y: 5, g: 'b' },
39+
{ x: 2, y: 8, g: 'b' },
40+
{ x: 3, y: 2, g: 'b' },
41+
{ x: 4, y: 6, g: 'b' },
42+
{ x: 5, y: 7, g: 'a' },
43+
{ x: 5, y: 7, g: 'b' },
44+
{ x: 6, y: 7, g: 'a' },
45+
{ x: 6, y: 7, g: 'b' },
46+
]}
6447
/>
65-
6648
<AreaSeries
67-
id={getSpecId('area')}
49+
id={getSpecId('area2')}
6850
xScaleType={ScaleType.Linear}
6951
yScaleType={ScaleType.Linear}
70-
xAccessor={0}
71-
yAccessors={[1]}
72-
data={[[0, 1], [1, 2], [2, 10], [3, 4], [4, 5]]}
52+
xAccessor="x"
53+
yAccessors={['y']}
54+
stackAccessors={['x']}
55+
splitSeriesAccessors={['g']}
56+
// curve={CurveType.CURVE_MONOTONE_X}
57+
data={[
58+
{ x: 1, y: 7, g: 'a' },
59+
{ x: 2, y: 3, g: 'a' },
60+
{ x: 3, y: 6, g: 'a' },
61+
{ x: 0, y: 4, g: 'b' },
62+
{ x: 1, y: 5, g: 'b' },
63+
{ x: 2, y: 8, g: 'b' },
64+
{ x: 3, y: 2, g: 'b' },
65+
{ x: 4, y: 6, g: 'b' },
66+
]}
7367
/>
7468
</Chart>
7569
</div>

src/chart_types/xy_chart/domains/x_domain.ts

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { isCompleteBound, isLowerBound, isUpperBound } from '../utils/axis_utils';
2-
import { compareByValueAsc, identity } from '../../../utils/commons';
2+
import { compareByValueAsc, identity, isNumberArray } from '../../../utils/commons';
33
import { computeContinuousDataDomain, computeOrdinalDataDomain, Domain } from '../../../utils/domain';
44
import { ScaleType } from '../../../utils/scales/scales';
55
import { BasicSeriesSpec, DomainRange } from '../utils/specs';
@@ -22,7 +22,7 @@ export type XDomain = BaseDomain & {
2222
*/
2323
export function mergeXDomain(
2424
specs: Pick<BasicSeriesSpec, 'seriesType' | 'xScaleType'>[],
25-
xValues: Set<any>,
25+
xValues: Set<string | number>,
2626
customXDomain?: DomainRange | Domain,
2727
): XDomain {
2828
const mainXScaleType = convertXScaleTypes(specs);
@@ -46,7 +46,11 @@ export function mergeXDomain(
4646
} else {
4747
seriesXComputedDomains = computeContinuousDataDomain(values, identity, true);
4848
let customMinInterval: undefined | number;
49-
49+
if (!isNumberArray(values)) {
50+
throw new Error(
51+
`Each X value in a ${mainXScaleType.scaleType} x scale needs be be a number. String or objects are not allowed`,
52+
);
53+
}
5054
if (customXDomain) {
5155
if (Array.isArray(customXDomain)) {
5256
throw new Error('xDomain for continuous scale should be a DomainRange object, not an array');
@@ -78,7 +82,6 @@ export function mergeXDomain(
7882
}
7983
}
8084
}
81-
8285
const computedMinInterval = findMinInterval(values);
8386
if (customMinInterval != null) {
8487
// Allow greater custom min iff xValues has 1 member.

src/chart_types/xy_chart/rendering/rendering.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -197,9 +197,9 @@ export function renderPoints(
197197
const isLogScale = isLogarithmicScale(yScale);
198198
const pointGeometries = dataset.reduce(
199199
(acc, datum) => {
200-
const { x: xValue, y0, y1, initialY0, initialY1 } = datum;
201-
// don't create the point if not within the xScale domain
202-
if (!xScale.isValueInDomain(xValue)) {
200+
const { x: xValue, y0, y1, initialY0, initialY1, filled } = datum;
201+
// don't create the point if not within the xScale domain or it that point was filled
202+
if (!xScale.isValueInDomain(xValue) || (filled && filled.y1 !== undefined)) {
203203
return acc;
204204
}
205205
const x = xScale.scale(xValue);
@@ -286,9 +286,9 @@ export function renderBars(
286286
const fontFamily = sharedSeriesStyle.displayValue.fontFamily;
287287

288288
dataset.forEach((datum) => {
289-
const { y0, y1, initialY1 } = datum;
289+
const { y0, y1, initialY1, filled } = datum;
290290
// don't create a bar if the initialY1 value is null.
291-
if (initialY1 === null) {
291+
if (initialY1 === null || (filled && filled.y1 !== undefined)) {
292292
return;
293293
}
294294
// don't create a bar if not within the xScale domain

src/chart_types/xy_chart/store/utils.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ export function getLastValues(formattedDataSeries: {
118118
if (last !== null) {
119119
const { initialY1: y1, initialY0: y0 } = last;
120120

121-
if (y1 !== null || y0 !== null) {
121+
if (!last.filled && (y1 !== null || y0 !== null)) {
122122
lastValues.set(series.seriesColorKey, { y0, y1 });
123123
}
124124
}
@@ -165,7 +165,7 @@ export function computeSeriesDomains(
165165
const xDomain = mergeXDomain(specsArray, xValues, customXDomain);
166166
const yDomain = mergeYDomain(splittedSeries, specsArray, customYDomainsByGroupId);
167167

168-
const formattedDataSeries = getFormattedDataseries(specsArray, splittedSeries);
168+
const formattedDataSeries = getFormattedDataseries(specsArray, splittedSeries, xValues, xDomain.scaleType);
169169

170170
// we need to get the last values from the formatted dataseries
171171
// because we change the format if we are on percentage mode

0 commit comments

Comments
 (0)