From 72f78387ce5fce50d1f73ae0ec1b72c6871461b5 Mon Sep 17 00:00:00 2001 From: Ola Rubaj <52197250+olayway@users.noreply.github.com> Date: Wed, 23 Oct 2024 17:55:50 +0200 Subject: [PATCH] [feat,LineChart][s]: support for multiple series --- .changeset/little-ways-refuse.md | 5 + .../components/src/components/LineChart.tsx | 164 ++++++++++-------- .../components/stories/LineChart.stories.ts | 51 +++++- 3 files changed, 147 insertions(+), 73 deletions(-) create mode 100644 .changeset/little-ways-refuse.md diff --git a/.changeset/little-ways-refuse.md b/.changeset/little-ways-refuse.md new file mode 100644 index 00000000..c366fbbf --- /dev/null +++ b/.changeset/little-ways-refuse.md @@ -0,0 +1,5 @@ +--- +'@portaljs/components': minor +--- + +Support for plotting multiple series in LineChart component. diff --git a/packages/components/src/components/LineChart.tsx b/packages/components/src/components/LineChart.tsx index f6ed3309..0db1f05a 100644 --- a/packages/components/src/components/LineChart.tsx +++ b/packages/components/src/components/LineChart.tsx @@ -8,86 +8,106 @@ type AxisType = 'quantitative' | 'temporal'; type TimeUnit = 'year' | undefined; // or ... export type LineChartProps = { - data: Omit; - title?: string; - xAxis: string; - xAxisType?: AxisType; - xAxisTimeUnit?: TimeUnit; - yAxis: string; - yAxisType?: AxisType; - fullWidth?: boolean; + data: Omit; + title?: string; + xAxis: string; + xAxisType?: AxisType; + xAxisTimeUnit?: TimeUnit; + yAxis: string | string[]; + yAxisType?: AxisType; + fullWidth?: boolean; + symbol?: string; }; export function LineChart({ - data, - title = '', - xAxis, - xAxisType = 'temporal', - xAxisTimeUnit = 'year', // TODO: defaults to undefined would probably work better... keeping it as it's for compatibility purposes - yAxis, - yAxisType = 'quantitative', + data, + title = '', + xAxis, + xAxisType = 'temporal', + xAxisTimeUnit = 'year', // TODO: defaults to undefined would probably work better... keeping it as it's for compatibility purposes + yAxis, + yAxisType = 'quantitative', + symbol, }: LineChartProps) { - const url = data.url; - const values = data.values; - const [isLoading, setIsLoading] = useState(false); + const url = data.url; + const values = data.values; + const [isLoading, setIsLoading] = useState(false); - // By default, assumes data is an Array... - const [specData, setSpecData] = useState({ name: 'table' }); + // By default, assumes data is an Array... + const [specData, setSpecData] = useState({ name: 'table' }); + const isMultiYAxis = Array.isArray(yAxis); - const spec = { - $schema: 'https://vega.github.io/schema/vega-lite/v5.json', - title, - width: 'container', - height: 300, - mark: { - type: 'line', - color: 'black', - strokeWidth: 1, - tooltip: true, - }, - data: specData, - selection: { - grid: { - type: 'interval', - bind: 'scales', - }, - }, - encoding: { - x: { - field: xAxis, - timeUnit: xAxisTimeUnit, - type: xAxisType, - }, - y: { - field: yAxis, - type: yAxisType, - }, - }, - } as any; + const spec = { + $schema: 'https://vega.github.io/schema/vega-lite/v5.json', + title, + width: 'container', + height: 300, + mark: { + type: 'line', + color: 'black', + strokeWidth: 1, + tooltip: true, + }, + data: specData, + ...(isMultiYAxis ? { + transform: [ + { fold: yAxis, as: ['key', 'value'] } + ] + } : {}), + selection: { + grid: { + type: 'interval', + bind: 'scales', + }, + }, + encoding: { + x: { + field: xAxis, + timeUnit: xAxisTimeUnit, + type: xAxisType, + }, + y: { + field: isMultiYAxis ? 'value' : yAxis, + type: yAxisType, + }, + ...(symbol ? { + color: { + field: symbol, + type: 'nominal' + } + } : {}), + ...(isMultiYAxis ? { + color: { + field: 'key', + type: 'nominal' + } + } : {}) + }, + } as any; - useEffect(() => { - if (url) { - setIsLoading(true); + useEffect(() => { + if (url) { + setIsLoading(true); - // Manualy loading the data allows us to do other kinds - // of stuff later e.g. load a file partially - loadData(url).then((res: any) => { - setSpecData({ values: res, format: { type: 'csv' } }); - setIsLoading(false); - }); + // Manualy loading the data allows us to do other kinds + // of stuff later e.g. load a file partially + loadData(url).then((res: any) => { + setSpecData({ values: res, format: { type: 'csv' } }); + setIsLoading(false); + }); + } + }, []); + + var vegaData = {}; + if (values) { + vegaData = { table: values }; } - }, []); - var vegaData = {}; - if (values) { - vegaData = { table: values }; - } - - return isLoading ? ( -
- -
- ) : ( - - ); + return isLoading ? ( +
+ +
+ ) : ( + + ); } diff --git a/packages/components/stories/LineChart.stories.ts b/packages/components/stories/LineChart.stories.ts index 27a8b3f8..70f6b63e 100644 --- a/packages/components/stories/LineChart.stories.ts +++ b/packages/components/stories/LineChart.stories.ts @@ -30,11 +30,14 @@ Must be an object with one of the following properties: `url` or `values` \n\n \ }, yAxis: { description: - 'Name of the column header or object property that represents the Y-axis on the data.', + 'Name of the column headers or object properties that represent the Y-axis on the data.', }, yAxisType: { description: 'Type of the Y-axis', }, + symbol: { + description: 'Name of the column header or object property that represents a series for multiple series.', + } }, }; @@ -60,6 +63,52 @@ export const FromDataPoints: Story = { }, }; +export const MultiSeries: Story = { + name: 'Line chart with multiple series (specifying symbol)', + args: { + data: { + values: [ + { year: '1850', value: -0.41765878, z: 'A' }, + { year: '1851', value: -0.2333498, z: 'A' }, + { year: '1852', value: -0.22939907, z: 'A' }, + { year: '1853', value: -0.27035445, z: 'A' }, + { year: '1854', value: -0.29163003, z: 'A' }, + { year: '1850', value: -0.42993882, z: 'B' }, + { year: '1851', value: -0.30365549, z: 'B' }, + { year: '1852', value: -0.27905189, z: 'B' }, + { year: '1853', value: -0.22939704, z: 'B' }, + { year: '1854', value: -0.25688013, z: 'B' }, + { year: '1850', value: -0.4757164, z: 'C' }, + { year: '1851', value: -0.41971018, z: 'C' }, + { year: '1852', value: -0.40724799, z: 'C' }, + { year: '1853', value: -0.45049156, z: 'C' }, + { year: '1854', value: -0.41896583, z: 'C' }, + ], + }, + xAxis: 'year', + yAxis: 'value', + symbol: 'z', + }, +}; + + +export const MultiColumns: Story = { + name: 'Line chart with multiple series (with multiple columns)', + args: { + data: { + values: [ + { year: '1850', A: -0.41765878, B: -0.42993882, C: -0.4757164 }, + { year: '1851', A: -0.2333498, B: -0.30365549, C: -0.41971018 }, + { year: '1852', A: -0.22939907, B: -0.27905189, C: -0.40724799 }, + { year: '1853', A: -0.27035445, B: -0.22939704, C: -0.45049156 }, + { year: '1854', A: -0.29163003, B: -0.25688013, C: -0.41896583 }, + ], + }, + xAxis: 'year', + yAxis: ['A', 'B', 'C'] + }, +}; + export const FromURL: Story = { name: 'Line chart from URL', args: {