Skip to content

Commit b8868e3

Browse files
refactor: use better typing and interface for Series
1 parent 103af51 commit b8868e3

File tree

6 files changed

+124
-58
lines changed

6 files changed

+124
-58
lines changed

charts/core/src/Echart/Echart.types.ts

Lines changed: 34 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,45 @@ import type {
88
TooltipComponentOption,
99
} from 'echarts/components';
1010
import type { ComposeOption, EChartsType } from 'echarts/core';
11-
import type { LineSeriesOption, SeriesOption } from 'echarts/types/dist/shared';
11+
import {
12+
BarSeriesOption,
13+
LineSeriesOption,
14+
SeriesOption,
15+
} from 'echarts/types/dist/shared';
1216

13-
import { Theme } from '@leafygreen-ui/lib';
17+
import { Theme, ValuesOf } from '@leafygreen-ui/lib';
1418

1519
// Type not exported by echarts.
1620
// reference: https://github.com/apache/echarts/blob/master/src/coord/axisCommonTypes.ts#L193
1721
export type AxisLabelValueFormatter = (value: number, index?: number) => string;
1822

19-
export type EChartLineSeriesOption = LineSeriesOption;
20-
export type EChartSeriesOption = { name: string } & SeriesOption;
23+
export interface StylingContext {
24+
seriesColor?: string;
25+
}
26+
27+
// to convert an SeriesOption type of echarts into a more structured form aligned with LeafyGreen design standards,
28+
// where the 'type', 'name', and 'data' fields are explicitly required and typed,
29+
// and all additional properties related to series styling are encapsulated within the 'stylingOptions' object
30+
interface DisciplinedSeriesOption<EChartType extends SeriesOption> {
31+
type: NonNullable<EChartType['type']>;
32+
name: string;
33+
data: NonNullable<EChartType['data']>;
34+
stylingOptions: Omit<EChartType, 'type' | 'name' | 'data'>;
35+
}
36+
37+
// all supported series options types disciplined and grouped into a single interface
38+
export interface EChartSeriesOptions {
39+
line: DisciplinedSeriesOption<LineSeriesOption>;
40+
// TODO: to be leveraged in a follow-up PR that adds Bar chart support
41+
bar: DisciplinedSeriesOption<BarSeriesOption>;
42+
}
43+
44+
// a disciplined substitute for SeriesOption type of echarts limited to the ones supported here
45+
export type EChartSeriesOption = Omit<
46+
ValuesOf<EChartSeriesOptions>,
47+
'stylingOptions'
48+
> &
49+
ValuesOf<EChartSeriesOptions>['stylingOptions'];
2150

2251
/**
2352
* TODO: This might need to be improved. `ComposeOption` appears to make most base option
@@ -107,6 +136,7 @@ interface EChartsEventHandlerType {
107136
callback: (params: any) => void,
108137
options?: Partial<{ useCanvasAsTrigger: boolean }>,
109138
): void;
139+
110140
(
111141
event: 'zoomselect',
112142
callback: (params: EChartZoomSelectionEvent) => void,

charts/core/src/Echart/utils/updateUtils.spec.ts

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,44 @@
1-
import { EChartOptions } from '../Echart.types';
1+
import { EChartOptions, EChartSeriesOption } from '../Echart.types';
22

33
import { addSeries, removeSeries, updateOptions } from './updateUtils';
44

55
describe('@lg-charts/core/Chart/hooks/updateUtils', () => {
66
test('addSeries should add a series to the chart options', () => {
77
const currentOptions: Partial<EChartOptions> = {
8-
series: [{ name: 'series1' }],
8+
series: [{ type: 'line', name: 'series1', data: [] }],
99
};
1010
const newSeriesName = 'series2';
11-
const data = { name: newSeriesName };
11+
const data: EChartSeriesOption = {
12+
type: 'line',
13+
name: newSeriesName,
14+
data: [],
15+
};
1216
const updatedOptions = addSeries(currentOptions, data);
1317
expect(updatedOptions.series).toHaveLength(2);
1418
expect(updatedOptions.series?.[1].name).toBe(newSeriesName);
1519
});
1620

1721
test('addSeries should not add a series if a chart with the same name exists', () => {
1822
const currentOptions: Partial<EChartOptions> = {
19-
series: [{ name: 'series1' }],
23+
series: [{ type: 'line', name: 'series1', data: [] }],
2024
};
2125
const newSeriesName = 'series1';
22-
const data = { name: newSeriesName };
26+
const data: EChartSeriesOption = {
27+
type: 'line',
28+
name: newSeriesName,
29+
data: [],
30+
};
2331
const updatedOptions = addSeries(currentOptions, data);
2432
expect(updatedOptions.series).toHaveLength(1);
2533
expect(updatedOptions.series?.[0].name).toBe(newSeriesName);
2634
});
2735

2836
test('removeSeries should remove a series from the chart options', () => {
2937
const currentOptions: Partial<EChartOptions> = {
30-
series: [{ name: 'series1' }, { name: 'series2' }],
38+
series: [
39+
{ type: 'line', name: 'series1', data: [] },
40+
{ type: 'line', name: 'series2', data: [] },
41+
],
3142
};
3243
const seriesName1 = 'series1';
3344
const seriesName2 = 'series2';

charts/core/src/EventMarkers/BaseEventMarker/utils.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@ export function getMarkConfig({
109109
symbolSize: [16, 16],
110110
symbolRotate: 360, // Icon shows upside down without this
111111
},
112+
data: [],
112113
} as SeriesOption;
113114
} else {
114115
return {
@@ -130,6 +131,7 @@ export function getMarkConfig({
130131
symbolSize: [16, 16],
131132
symbol: generateSymbolDataUri(level, theme),
132133
},
134+
data: [],
133135
} as SeriesOption;
134136
}
135137
}
Lines changed: 31 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,52 +1,44 @@
11
import React from 'react';
2-
import { useSeriesContext } from '@lg-charts/series-provider';
3-
import _ from 'lodash';
42

5-
import { useDarkMode } from '@leafygreen-ui/leafygreen-provider';
6-
7-
import { EChartSeriesOption } from '../../Echart';
8-
import { EChartLineSeriesOption } from '../../Echart/Echart.types';
3+
import { EChartSeriesOptions, StylingContext } from '../../Echart/Echart.types';
94
import { Series } from '../Series';
105
import { SeriesProps } from '../Series.types';
116

127
export type LineProps = SeriesProps;
138

14-
const defaultLineOptions: EChartLineSeriesOption = {
15-
type: 'line',
16-
showSymbol: false,
17-
symbol: 'circle',
18-
clip: false,
19-
symbolSize: 7,
20-
emphasis: {
21-
focus: 'series',
22-
},
23-
blur: {
9+
function getDefaultLineOptions(
10+
stylingContext: StylingContext,
11+
): EChartSeriesOptions['line']['stylingOptions'] {
12+
return {
13+
showSymbol: false,
14+
symbol: 'circle',
15+
clip: false,
16+
symbolSize: 7,
17+
emphasis: {
18+
focus: 'series',
19+
},
20+
blur: {
21+
lineStyle: {
22+
opacity: 0.5,
23+
},
24+
},
25+
itemStyle: {
26+
color: stylingContext.seriesColor,
27+
},
2428
lineStyle: {
25-
opacity: 0.5,
29+
color: stylingContext.seriesColor,
30+
width: 1,
2631
},
27-
},
28-
lineStyle: {
29-
width: 1,
30-
},
31-
};
32-
33-
export function Line(props: LineProps) {
34-
const { getColor } = useSeriesContext();
35-
const { theme } = useDarkMode();
36-
const color = getColor(props.name, theme);
37-
const colorOverrides: EChartLineSeriesOption = {
38-
lineStyle: { color: color || undefined },
39-
itemStyle: { color: color || undefined },
4032
};
41-
42-
const options: EChartSeriesOption = _.merge(
43-
{},
44-
defaultLineOptions,
45-
props,
46-
colorOverrides,
47-
);
48-
49-
return <Series {...options} />;
5033
}
5134

35+
export const Line = (props: LineProps) => (
36+
<Series
37+
type={'line'}
38+
name={props.name}
39+
data={props.data}
40+
stylingOptions={getDefaultLineOptions}
41+
/>
42+
);
43+
5244
Line.displayName = 'Line';

charts/core/src/Series/Series.tsx

Lines changed: 39 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,64 @@
11
import { useEffect } from 'react';
22
import { useSeriesContext } from '@lg-charts/series-provider';
33

4+
import { useDarkMode } from '@leafygreen-ui/leafygreen-provider';
5+
import { ValuesOf } from '@leafygreen-ui/lib';
6+
47
import { useChartContext } from '../ChartContext';
5-
import { EChartSeriesOption } from '../Echart';
8+
import { EChartSeriesOptions, StylingContext } from '../Echart/Echart.types';
69

7-
export function Series(options: EChartSeriesOption) {
10+
export function Series<T extends ValuesOf<EChartSeriesOptions>>({
11+
type,
12+
name,
13+
data,
14+
stylingOptions,
15+
}: {
16+
type: T['type'];
17+
name: T['name'];
18+
data: T['data'];
19+
stylingOptions: (ctx: StylingContext) => T['stylingOptions'];
20+
}) {
821
const {
922
chart: { addSeries, ready, removeSeries },
1023
} = useChartContext();
11-
const { isChecked } = useSeriesContext();
12-
13-
const isVisible = isChecked(options.name);
24+
const { theme } = useDarkMode();
25+
const { isChecked, getColor } = useSeriesContext();
26+
const seriesColor = getColor(name, theme) || undefined;
27+
const isVisible = isChecked(name);
1428

1529
useEffect(() => {
1630
if (!ready) return;
1731

1832
if (isVisible) {
19-
addSeries(options);
33+
const context = { seriesColor };
34+
addSeries({
35+
type,
36+
name,
37+
data,
38+
...stylingOptions(context),
39+
});
2040
} else {
21-
removeSeries(options.name);
41+
removeSeries(name);
2242
}
2343

2444
return () => {
2545
/**
2646
* Remove the series when the component unmounts to make sure the series
2747
* is removed when a `Series` is hidden.
2848
*/
29-
removeSeries(options.name);
49+
removeSeries(name);
3050
};
31-
}, [addSeries, isVisible, ready, removeSeries]);
51+
}, [
52+
addSeries,
53+
isVisible,
54+
seriesColor,
55+
ready,
56+
removeSeries,
57+
type,
58+
name,
59+
data,
60+
stylingOptions,
61+
]);
3262

3363
return null;
3464
}

charts/core/src/ThresholdLine/ThresholdLine.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ function getThresholdLineConfig({
8888
symbolSize: [7, 10],
8989
symbolRotate: 360,
9090
},
91+
data: [],
9192
};
9293
}
9394

0 commit comments

Comments
 (0)