From 042b637e9b6324bb89b5d1c0f3b34c325d2d78db Mon Sep 17 00:00:00 2001 From: IlyesBENAMARA Date: Tue, 11 Oct 2022 17:27:31 +0100 Subject: [PATCH 1/7] feat(partial): multi area chart --- packages/ez-react/src/components/Areas.tsx | 84 ++++++++++ .../ez-react/src/lib/useToggableDomainKey.ts | 34 +++- .../src/recipes/area/AreaChart.stories.tsx | 24 ++- .../ez-react/src/recipes/area/AreaChart.tsx | 51 +----- .../src/recipes/area/MultiAreaChart.tsx | 151 ++++++++++++++++++ 5 files changed, 293 insertions(+), 51 deletions(-) create mode 100644 packages/ez-react/src/components/Areas.tsx create mode 100644 packages/ez-react/src/recipes/area/MultiAreaChart.tsx diff --git a/packages/ez-react/src/components/Areas.tsx b/packages/ez-react/src/components/Areas.tsx new file mode 100644 index 0000000..44fdfd9 --- /dev/null +++ b/packages/ez-react/src/components/Areas.tsx @@ -0,0 +1,84 @@ +import React, { FC, SVGAttributes, useMemo } from 'react'; +import { AreaConfig, AreaData, MarkerConfig } from 'eazychart-core/src/types'; +import { Point } from '@/components/shapes/Point'; +import { Points } from '@/components/Points'; +import { useColorScale } from '@/components/scales/ColorScale'; +import { Area } from './shapes/Area'; +import { LinePath } from './shapes/LinePath'; + +export interface AreasProps extends SVGAttributes { + xDomainKey: string; + yDomainKey: string; + area?: AreaConfig; + marker?: MarkerConfig; +} + +export const Areas: FC = ({ + xDomainKey, + yDomainKey, + area = { + stroke: '#339999', + strokeWidth: 2, + curve: 'curveLinear', + }, + marker = { + hidden: true, + radius: 5, + color: '#FFF', + }, +}) => { + const { colorScale } = useColorScale(); + + const color = useMemo( + () => (colorScale.isDefined() ? colorScale.scale(yDomainKey) : area.stroke), + [area.stroke, colorScale, yDomainKey] + ); + + return ( + { + const lineAreaData: AreaData = shapeData.map((d) => { + return { + x: d.x, + y0: chartDimensions.height, + y1: d.y, + }; + }); + return ( + + + + {!marker.hidden && + shapeData.map((shapeDatum) => { + return ( + + ); + })} + + ); + }, + }} + /> + ); +}; diff --git a/packages/ez-react/src/lib/useToggableDomainKey.ts b/packages/ez-react/src/lib/useToggableDomainKey.ts index 57e41dc..9e7991c 100644 --- a/packages/ez-react/src/lib/useToggableDomainKey.ts +++ b/packages/ez-react/src/lib/useToggableDomainKey.ts @@ -1,24 +1,52 @@ -import { useCallback, useMemo, useState } from 'react'; +import { useCallback, useEffect, useMemo, useState } from 'react'; import { RawData } from 'eazychart-core/src/types'; -import { getDomainByKeys } from 'eazychart-core/src'; +import { getDomainByKeys } from 'eazychart-core'; export const useToggableDomainKey = (data: RawData, domainKeys: string[]) => { // Setup a state for the domain keys to make them toggable const [activeDomainKeys, setActiveDomainKeys] = useState(domainKeys); + + const sortDomainKeys = useCallback( + (domainkeys: string[]) => { + const maxValues = domainkeys.reduce((acc, curr: string, index) => { + const values = data.map((datum) => datum[curr] as number); + acc.push([Math.max(...values), index] as never); + return acc; + }, []); + const sortedMaxValues = maxValues.sort((a, b) => { + return a[0] > b[0] ? -1 : 1; + }); + const sortedDomains = sortedMaxValues.reduce((acc, curr) => { + acc.push(domainkeys[curr[1]] as never); + return acc; + }, []); + // console.log('sortedDomains', sortedDomains); + setActiveDomainKeys(sortedDomains); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, + [data, setActiveDomainKeys] + ); + // Toggle Y axis domain keys whenever a legend key is clicked const toggleDomainKey = useCallback( (key: string, isActive: boolean, _color: string) => { if (isActive) { setActiveDomainKeys([...activeDomainKeys, key]); + sortDomainKeys(activeDomainKeys); } else { setActiveDomainKeys( activeDomainKeys.filter((domainKey) => domainKey !== key) ); } }, - [activeDomainKeys, setActiveDomainKeys] + [activeDomainKeys, setActiveDomainKeys, sortDomainKeys] ); + useEffect(() => { + sortDomainKeys(activeDomainKeys); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [sortDomainKeys]); + // Re-scale the Y axis const activeDomain = useMemo( () => getDomainByKeys(activeDomainKeys, data), diff --git a/packages/ez-react/src/recipes/area/AreaChart.stories.tsx b/packages/ez-react/src/recipes/area/AreaChart.stories.tsx index 7ac0dd4..a898d6d 100644 --- a/packages/ez-react/src/recipes/area/AreaChart.stories.tsx +++ b/packages/ez-react/src/recipes/area/AreaChart.stories.tsx @@ -3,6 +3,7 @@ import { Meta, Story } from '@storybook/react'; import { AreaChart, AreaChartProps } from '@/recipes/area/AreaChart'; import { baseChartArgTypes, ChartWrapper } from '../../lib/storybook-utils'; import { colors, evolutionData } from 'eazychart-dev/storybook/data'; +import { MultiAreaChart, MultiAreaChartProps } from './MultiAreaChart'; const meta: Meta = { id: '2', @@ -24,11 +25,19 @@ const Template: Story = (args) => { ); }; +const MultiAreaTemplate: Story = (args) => { + return ( + + + + ); +}; + // By passing using the Args format for exported stories, you can control the props for a component for reuse in a test // https://storybook.js.org/docs/react/workflows/unit-testing export const Default = Template.bind({}); -Default.args = { +const defaultArguments = { area: { stroke: colors[0], strokeWidth: 2, @@ -52,3 +61,16 @@ Default.args = { }, data: evolutionData, }; + +Default.args = defaultArguments; + +export const MultiArea = MultiAreaTemplate.bind({}); + +MultiArea.args = { + ...defaultArguments, + yAxis: { + domainKeys: ['yValue', 'yValue1', 'yValue2'], + title: 'Temperature', + tickFormat: (d: number) => `${d}°`, + }, +}; diff --git a/packages/ez-react/src/recipes/area/AreaChart.tsx b/packages/ez-react/src/recipes/area/AreaChart.tsx index f0b7416..69505e2 100644 --- a/packages/ez-react/src/recipes/area/AreaChart.tsx +++ b/packages/ez-react/src/recipes/area/AreaChart.tsx @@ -11,17 +11,13 @@ import { Dimensions, AreaConfig, MarkerConfig, - AreaData, } from 'eazychart-core/src/types'; import { TooltipProps, Tooltip } from '@/components/addons/tooltip/Tooltip'; import { Chart } from '@/components/Chart'; -import { Points } from '@/components/Points'; import { Axis } from '@/components/scales/Axis'; import { Grid } from '@/components/scales/grid/Grid'; -import { LinePath } from '@/components/shapes/LinePath'; -import { Point } from '@/components/shapes/Point'; -import { Area } from '@/components/shapes/Area'; import { CartesianScale } from '@/components/scales/CartesianScale'; +import { Areas } from '@/components/Areas'; export interface AreaChartProps extends SVGAttributes { data: RawData; @@ -107,50 +103,11 @@ export const AreaChart: FC = ({ }} > - { - const lineAreaData: AreaData = shapeData.map((d) => { - return { - x: d.x, - y0: chartDimensions.height, - y1: d.y, - }; - }); - return ( - - - - {!marker.hidden && - shapeData.map((shapeDatum) => { - return ( - - ); - })} - - ); - }, - }} + area={area} + marker={marker} /> { + data: RawData; + colors?: string[]; + area?: AreaConfig; + marker?: MarkerConfig; + animationOptions?: AnimationOptions; + padding?: ChartPadding; + grid?: GridConfig; + isRTL?: boolean; + xAxis?: AxisConfig; + yAxis?: AxisConfigMulti; + dimensions?: Partial; + scopedSlots?: { + TooltipComponent: FC; + LegendComponent: FC; + }; + onResize?: (dimensions: Dimensions) => void; +} + +export const MultiAreaChart: FC = ({ + data, + colors = ['#339999', '#993399', '#333399'], + area = { + stroke: '#339999', + strokeWidth: 2, + curve: 'curveLinear', + }, + marker = { + hidden: true, + radius: 5, + color: '#FFF', + }, + animationOptions = { + easing: 'easeBack', + duration: 400, + delay: 0, + }, + padding = { + left: 100, + bottom: 100, + right: 100, + top: 100, + }, + grid = { + directions: [Direction.HORIZONTAL, Direction.VERTICAL], + color: '#a8a8a8', + }, + xAxis = { + domainKey: 'xValue', + }, + yAxis = { + domainKeys: ['yValue', 'yValue1', 'yValue2'], + }, + isRTL = false, + dimensions = {}, + scopedSlots = { + TooltipComponent: Tooltip, + LegendComponent: Legend, + }, +}) => { + const { activeDomainKeys, activeDomain, toggleDomainKey } = + useToggableDomainKey(data, yAxis.domainKeys); + return ( + + + + + {activeDomainKeys.map((yDomainKey) => { + return ( + + ); + })} + + + + + + ); +}; From 3960b5383aaa7fb1c776cfc9a64ec50e834e0b64 Mon Sep 17 00:00:00 2001 From: IlyesBENAMARA Date: Thu, 13 Oct 2022 09:00:08 +0100 Subject: [PATCH 2/7] feat: multi area chart --- packages/ez-react/src/components/Areas.tsx | 3 +-- packages/ez-react/src/lib/useToggableDomainKey.ts | 14 +++++++------- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/packages/ez-react/src/components/Areas.tsx b/packages/ez-react/src/components/Areas.tsx index 44fdfd9..0243805 100644 --- a/packages/ez-react/src/components/Areas.tsx +++ b/packages/ez-react/src/components/Areas.tsx @@ -33,7 +33,6 @@ export const Areas: FC = ({ () => (colorScale.isDefined() ? colorScale.scale(yDomainKey) : area.stroke), [area.stroke, colorScale, yDomainKey] ); - return ( = ({ }; }); return ( - + { useState(domainKeys); const sortDomainKeys = useCallback( - (domainkeys: string[]) => { + (keys?: string[]) => { + const domainkeys = keys ? [...keys] : [...activeDomainKeys]; const maxValues = domainkeys.reduce((acc, curr: string, index) => { const values = data.map((datum) => datum[curr] as number); acc.push([Math.max(...values), index] as never); @@ -21,19 +22,18 @@ export const useToggableDomainKey = (data: RawData, domainKeys: string[]) => { acc.push(domainkeys[curr[1]] as never); return acc; }, []); - // console.log('sortedDomains', sortedDomains); setActiveDomainKeys(sortedDomains); - // eslint-disable-next-line react-hooks/exhaustive-deps }, - [data, setActiveDomainKeys] + // eslint-disable-next-line react-hooks/exhaustive-deps + [data] ); // Toggle Y axis domain keys whenever a legend key is clicked const toggleDomainKey = useCallback( (key: string, isActive: boolean, _color: string) => { if (isActive) { - setActiveDomainKeys([...activeDomainKeys, key]); - sortDomainKeys(activeDomainKeys); + const newActiveDomains = [...activeDomainKeys, key]; + sortDomainKeys(newActiveDomains); } else { setActiveDomainKeys( activeDomainKeys.filter((domainKey) => domainKey !== key) @@ -43,7 +43,7 @@ export const useToggableDomainKey = (data: RawData, domainKeys: string[]) => { [activeDomainKeys, setActiveDomainKeys, sortDomainKeys] ); useEffect(() => { - sortDomainKeys(activeDomainKeys); + sortDomainKeys(); // eslint-disable-next-line react-hooks/exhaustive-deps }, [sortDomainKeys]); From 12ee004d9d96ab6d6ad5efb34a111f7931e7be54 Mon Sep 17 00:00:00 2001 From: IlyesBENAMARA Date: Thu, 13 Oct 2022 11:42:17 +0100 Subject: [PATCH 3/7] fix: areas test not going through --- packages/ez-react/src/components/Areas.tsx | 4 ++-- packages/ez-react/src/lib/useToggableDomainKey.ts | 3 +-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/packages/ez-react/src/components/Areas.tsx b/packages/ez-react/src/components/Areas.tsx index 0243805..fa85458 100644 --- a/packages/ez-react/src/components/Areas.tsx +++ b/packages/ez-react/src/components/Areas.tsx @@ -30,8 +30,8 @@ export const Areas: FC = ({ const { colorScale } = useColorScale(); const color = useMemo( - () => (colorScale.isDefined() ? colorScale.scale(yDomainKey) : area.stroke), - [area.stroke, colorScale, yDomainKey] + () => (colorScale.isDefined() ? colorScale.scale(yDomainKey) : area.fill), + [area.fill, colorScale, yDomainKey] ); return ( { const toggleDomainKey = useCallback( (key: string, isActive: boolean, _color: string) => { if (isActive) { - const newActiveDomains = [...activeDomainKeys, key]; - sortDomainKeys(newActiveDomains); + sortDomainKeys([...activeDomainKeys, key]); } else { setActiveDomainKeys( activeDomainKeys.filter((domainKey) => domainKey !== key) From 49ec03adc2fa5cd48a97e25469f306d6585b2241 Mon Sep 17 00:00:00 2001 From: IlyesBENAMARA Date: Thu, 13 Oct 2022 11:45:50 +0100 Subject: [PATCH 4/7] feat: unit tests for multi area chart on react --- .../recipes/area/MultiAreaChart.spec.tsx.snap | 655 ++++++++++++++++++ .../unit/recipes/area/MultiAreaChart.spec.tsx | 41 ++ 2 files changed, 696 insertions(+) create mode 100644 packages/ez-dev/jest/snapshots/recipes/area/MultiAreaChart.spec.tsx.snap create mode 100644 packages/ez-react/tests/unit/recipes/area/MultiAreaChart.spec.tsx diff --git a/packages/ez-dev/jest/snapshots/recipes/area/MultiAreaChart.spec.tsx.snap b/packages/ez-dev/jest/snapshots/recipes/area/MultiAreaChart.spec.tsx.snap new file mode 100644 index 0000000..2f5e8de --- /dev/null +++ b/packages/ez-dev/jest/snapshots/recipes/area/MultiAreaChart.spec.tsx.snap @@ -0,0 +1,655 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`MultiAreaChart renders a multiarea chart 1`] = ` +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0 + + + + + + + 2 + + + + + + + 4 + + + + + + + 6 + + + + + + + 8 + + + + + + + 10 + + + + + + + 12 + + + + + + + 14 + + + + + + + + + + + 20 + + + + + + + 40 + + + + + + + 60 + + + + + + + 80 + + + + + + + 100 + + + + + + + 120 + + + + + + + 140 + + + + + + + 160 + + + + + + + 180 + + + + + + + 200 + + + + + + + 220 + + + + + + + 240 + + + + + + + 260 + + + + + + + 280 + + + + + + + 300 + + + + + + +
+
+
+
+
+ + yValue + +
+
+
+
+ + zValue + +
+
+
+
+
+
+
+`; diff --git a/packages/ez-react/tests/unit/recipes/area/MultiAreaChart.spec.tsx b/packages/ez-react/tests/unit/recipes/area/MultiAreaChart.spec.tsx new file mode 100644 index 0000000..8061bb4 --- /dev/null +++ b/packages/ez-react/tests/unit/recipes/area/MultiAreaChart.spec.tsx @@ -0,0 +1,41 @@ +import React from 'react'; +import { dimensions, pointsRawData } from 'eazychart-core/src/sample-data'; +import { act, render, RenderResult, waitFor } from '@testing-library/react'; +import 'tests/mocks/ResizeObserver'; +import { MultiAreaChart } from '@/recipes/area/MultiAreaChart'; + +describe('MultiAreaChart', () => { + it('renders a multiarea chart', async () => { + let wrapper: RenderResult; + act(() => { + wrapper = render( + + ); + }); + + await waitFor(() => { + expect(wrapper.container.innerHTML).toMatchSnapshot(); + }); + }); +}); From 45720d96e73707346b184dbdbd5fca18796ead84 Mon Sep 17 00:00:00 2001 From: IlyesBENAMARA Date: Tue, 18 Oct 2022 16:39:56 +0100 Subject: [PATCH 5/7] fix: apply requested changes --- packages/ez-core/src/utils/types.ts | 1 + .../src/components/{Areas.tsx => Area.tsx} | 11 ++++--- .../ez-react/src/components/shapes/Area.tsx | 4 +-- .../ez-react/src/lib/useToggableDomainKey.ts | 31 ++----------------- .../ez-react/src/recipes/area/AreaChart.tsx | 4 +-- .../src/recipes/area/MultiAreaChart.tsx | 9 ++++-- .../src/recipes/line/LineErrorMarginChart.tsx | 4 +-- 7 files changed, 22 insertions(+), 42 deletions(-) rename packages/ez-react/src/components/{Areas.tsx => Area.tsx} (89%) diff --git a/packages/ez-core/src/utils/types.ts b/packages/ez-core/src/utils/types.ts index b2c76da..5e8f9d2 100644 --- a/packages/ez-core/src/utils/types.ts +++ b/packages/ez-core/src/utils/types.ts @@ -120,6 +120,7 @@ type CurveConfig = { export type AreaConfig = CurveConfig & { curve?: AreaCurve; fill?: string; + opacity?: number; }; export type LineConfig = CurveConfig & { diff --git a/packages/ez-react/src/components/Areas.tsx b/packages/ez-react/src/components/Area.tsx similarity index 89% rename from packages/ez-react/src/components/Areas.tsx rename to packages/ez-react/src/components/Area.tsx index fa85458..7d942b6 100644 --- a/packages/ez-react/src/components/Areas.tsx +++ b/packages/ez-react/src/components/Area.tsx @@ -3,17 +3,17 @@ import { AreaConfig, AreaData, MarkerConfig } from 'eazychart-core/src/types'; import { Point } from '@/components/shapes/Point'; import { Points } from '@/components/Points'; import { useColorScale } from '@/components/scales/ColorScale'; -import { Area } from './shapes/Area'; +import { AreaPath } from './shapes/Area'; import { LinePath } from './shapes/LinePath'; -export interface AreasProps extends SVGAttributes { +export interface AreaProps extends SVGAttributes { xDomainKey: string; yDomainKey: string; area?: AreaConfig; marker?: MarkerConfig; } -export const Areas: FC = ({ +export const Area: FC = ({ xDomainKey, yDomainKey, area = { @@ -48,17 +48,18 @@ export const Areas: FC = ({ }); return ( - {!marker.hidden && diff --git a/packages/ez-react/src/components/shapes/Area.tsx b/packages/ez-react/src/components/shapes/Area.tsx index b92ada2..d92faa9 100644 --- a/packages/ez-react/src/components/shapes/Area.tsx +++ b/packages/ez-react/src/components/shapes/Area.tsx @@ -4,13 +4,13 @@ import { defaultColor, generateAreaPath } from 'eazychart-core/src'; import { useAnimation } from '../../lib/use-animation'; import { useChart } from '@/lib/use-chart'; -export interface AreaProps extends SVGAttributes { +export interface AreaPathProps extends SVGAttributes { shapeData?: AreaData; curve?: AreaCurve; beta?: number; } -export const Area: FC = ({ +export const AreaPath: FC = ({ shapeData = [], curve = 'curveLinear', beta, diff --git a/packages/ez-react/src/lib/useToggableDomainKey.ts b/packages/ez-react/src/lib/useToggableDomainKey.ts index 30d5357..851e67b 100644 --- a/packages/ez-react/src/lib/useToggableDomainKey.ts +++ b/packages/ez-react/src/lib/useToggableDomainKey.ts @@ -1,4 +1,4 @@ -import { useCallback, useEffect, useMemo, useState } from 'react'; +import { useCallback, useMemo, useState } from 'react'; import { RawData } from 'eazychart-core/src/types'; import { getDomainByKeys } from 'eazychart-core'; @@ -7,44 +7,19 @@ export const useToggableDomainKey = (data: RawData, domainKeys: string[]) => { const [activeDomainKeys, setActiveDomainKeys] = useState(domainKeys); - const sortDomainKeys = useCallback( - (keys?: string[]) => { - const domainkeys = keys ? [...keys] : [...activeDomainKeys]; - const maxValues = domainkeys.reduce((acc, curr: string, index) => { - const values = data.map((datum) => datum[curr] as number); - acc.push([Math.max(...values), index] as never); - return acc; - }, []); - const sortedMaxValues = maxValues.sort((a, b) => { - return a[0] > b[0] ? -1 : 1; - }); - const sortedDomains = sortedMaxValues.reduce((acc, curr) => { - acc.push(domainkeys[curr[1]] as never); - return acc; - }, []); - setActiveDomainKeys(sortedDomains); - }, - // eslint-disable-next-line react-hooks/exhaustive-deps - [data] - ); - // Toggle Y axis domain keys whenever a legend key is clicked const toggleDomainKey = useCallback( (key: string, isActive: boolean, _color: string) => { if (isActive) { - sortDomainKeys([...activeDomainKeys, key]); + setActiveDomainKeys([...activeDomainKeys, key]); } else { setActiveDomainKeys( activeDomainKeys.filter((domainKey) => domainKey !== key) ); } }, - [activeDomainKeys, setActiveDomainKeys, sortDomainKeys] + [activeDomainKeys, setActiveDomainKeys] ); - useEffect(() => { - sortDomainKeys(); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [sortDomainKeys]); // Re-scale the Y axis const activeDomain = useMemo( diff --git a/packages/ez-react/src/recipes/area/AreaChart.tsx b/packages/ez-react/src/recipes/area/AreaChart.tsx index 69505e2..e0bba73 100644 --- a/packages/ez-react/src/recipes/area/AreaChart.tsx +++ b/packages/ez-react/src/recipes/area/AreaChart.tsx @@ -17,7 +17,7 @@ import { Chart } from '@/components/Chart'; import { Axis } from '@/components/scales/Axis'; import { Grid } from '@/components/scales/grid/Grid'; import { CartesianScale } from '@/components/scales/CartesianScale'; -import { Areas } from '@/components/Areas'; +import { Area } from '@/components/Area'; export interface AreaChartProps extends SVGAttributes { data: RawData; @@ -103,7 +103,7 @@ export const AreaChart: FC = ({ }} > - = ({ {activeDomainKeys.map((yDomainKey) => { return ( - ); diff --git a/packages/ez-react/src/recipes/line/LineErrorMarginChart.tsx b/packages/ez-react/src/recipes/line/LineErrorMarginChart.tsx index c940b71..9dd6f13 100644 --- a/packages/ez-react/src/recipes/line/LineErrorMarginChart.tsx +++ b/packages/ez-react/src/recipes/line/LineErrorMarginChart.tsx @@ -23,7 +23,7 @@ import { Axis } from '@/components/scales/Axis'; import { Grid } from '@/components/scales/grid/Grid'; import { LinePath } from '@/components/shapes/LinePath'; import { Point } from '@/components/shapes/Point'; -import { Area } from '@/components/shapes/Area'; +import { AreaPath } from '@/components/shapes/Area'; import { CartesianScale } from '@/components/scales/CartesianScale'; export interface LineErrorMarginChartProps extends SVGAttributes { @@ -153,7 +153,7 @@ export const LineErrorMarginChart: FC = ({ }); return ( - Date: Tue, 18 Oct 2022 14:14:19 +0100 Subject: [PATCH 6/7] feat(partial): stacked column chart --- packages/ez-react/src/components/Bars.tsx | 33 ++++- .../ez-react/src/components/StackedBars.tsx | 53 ++++++++ .../recipes/column/ColumnChart.stories.tsx | 29 ++++- .../src/recipes/column/StackedColumnChart.tsx | 122 ++++++++++++++++++ 4 files changed, 234 insertions(+), 3 deletions(-) create mode 100644 packages/ez-react/src/components/StackedBars.tsx create mode 100644 packages/ez-react/src/recipes/column/StackedColumnChart.tsx diff --git a/packages/ez-react/src/components/Bars.tsx b/packages/ez-react/src/components/Bars.tsx index 72abebf..3b2d6e2 100644 --- a/packages/ez-react/src/components/Bars.tsx +++ b/packages/ez-react/src/components/Bars.tsx @@ -4,13 +4,34 @@ import { Bar } from '@/components/shapes/Bar'; import { useChart } from '@/lib/use-chart'; import { useCartesianScales } from '@/components/scales/CartesianScale'; import { useColorScale } from './scales/ColorScale'; +import { Dimensions, ScaleLinearOrBand } from 'eazychart-core/src/types'; +import { RectangleDatum } from 'eazychart-core/dist/types'; export interface BarsProps extends SVGAttributes { xDomainKey: string; yDomainKey: string; + scopedSlots?: { + default: ({ + shapeData, + scales, + dimensions, + }: { + shapeData: RectangleDatum[]; + scales: { + xScale: ScaleLinearOrBand; + yScale: ScaleLinearOrBand; + }; + dimensions: Dimensions; + }) => React.ReactChild; + }; } -export const Bars: FC = ({ xDomainKey, yDomainKey, ...rest }) => { +export const Bars: FC = ({ + xDomainKey, + yDomainKey, + scopedSlots, + ...rest +}) => { const { data, dimensions, isRTL } = useChart(); const { xScale, yScale } = useCartesianScales(); const { colorScale } = useColorScale(); @@ -37,7 +58,15 @@ export const Bars: FC = ({ xDomainKey, yDomainKey, ...rest }) => { isRTL, ]); - return ( + return scopedSlots && scopedSlots.default ? ( + + {scopedSlots.default({ + shapeData: scaledData, + scales: { xScale, yScale }, + dimensions, + })} + + ) : ( {scaledData.map((rectDatum) => { return ; diff --git a/packages/ez-react/src/components/StackedBars.tsx b/packages/ez-react/src/components/StackedBars.tsx new file mode 100644 index 0000000..232fe74 --- /dev/null +++ b/packages/ez-react/src/components/StackedBars.tsx @@ -0,0 +1,53 @@ +import React, { FC, SVGAttributes, useMemo } from 'react'; +import { scaleRectangleData } from 'eazychart-core/src'; +import { Bar } from '@/components/shapes/Bar'; +import { useChart } from '@/lib/use-chart'; +import { useCartesianScales } from '@/components/scales/CartesianScale'; +import { useColorScale } from './scales/ColorScale'; + +export interface StackedBarsProps extends SVGAttributes { + xDomainKey: string; + yDomainKeys: string[]; +} + +export const StackedBars: FC = ({ + xDomainKey, + yDomainKeys, + ...rest +}) => { + const { data, dimensions, isRTL } = useChart(); + const { xScale, yScale } = useCartesianScales(); + const { colorScale } = useColorScale(); + + const scaledData = useMemo(() => { + return yDomainKeys.reduce((acc, yDomainKey) => { + // @ts-ignore + acc[yDomainKey] = scaleRectangleData( + data, + 'id', + yDomainKey, + xScale, + yScale, + colorScale, + dimensions, + isRTL + ); + return acc; + }, {}); + }, [yDomainKeys, data, xScale, yScale, colorScale, dimensions, isRTL]); + + return ( + + {data.forEach((_datum, index) => { + return yDomainKeys.map((yDomainKey) => { + return ( + + ); + }); + })} + + ); +}; diff --git a/packages/ez-react/src/recipes/column/ColumnChart.stories.tsx b/packages/ez-react/src/recipes/column/ColumnChart.stories.tsx index 3cc8395..0c8113a 100644 --- a/packages/ez-react/src/recipes/column/ColumnChart.stories.tsx +++ b/packages/ez-react/src/recipes/column/ColumnChart.stories.tsx @@ -6,7 +6,11 @@ import { LineColumnChartProps, } from '@/recipes/column/LineColumnChart'; import { baseChartArgTypes, ChartWrapper } from '@/lib/storybook-utils'; -import { colors, rawData } from 'eazychart-dev/storybook/data'; +import { colors, evolutionData, rawData } from 'eazychart-dev/storybook/data'; +import { + StackedColumnChart, + StackedColumnChartProps, +} from './StackedColumnChart'; const meta: Meta = { id: '4', @@ -36,6 +40,14 @@ const LineColumnTemplate: Story = (args) => { ); }; +const StackedColumnTemplate: Story = (args) => { + return ( + + + + ); +}; + // By passing using the Args format for exported stories, you can control the props for a component for reuse in a test // https://storybook.js.org/docs/react/workflows/unit-testing export const Default = DefaultTemplate.bind({}); @@ -78,3 +90,18 @@ const lineColumnArguments = { }; LineColumn.args = lineColumnArguments; + +export const StackedColumn = StackedColumnTemplate.bind({}); + +const StackedColumnArguments = { + ...defaultArguments, + colors: ['#339999', '#993399', '#333399'], + data: evolutionData, + yAxis: { + domainKeys: ['yValue', 'yValue1', 'yValue2'], + title: 'Temperature', + tickFormat: (d: number) => `${d}°`, + }, +}; + +StackedColumn.args = StackedColumnArguments; diff --git a/packages/ez-react/src/recipes/column/StackedColumnChart.tsx b/packages/ez-react/src/recipes/column/StackedColumnChart.tsx new file mode 100644 index 0000000..9917a74 --- /dev/null +++ b/packages/ez-react/src/recipes/column/StackedColumnChart.tsx @@ -0,0 +1,122 @@ +import { Legend, LegendProps } from '@/components/addons/legend/Legend'; +import { Tooltip, TooltipProps } from '@/components/addons/tooltip/Tooltip'; +import { Chart } from '@/components/Chart'; +import { Axis } from '@/components/scales/Axis'; +import { CartesianScale } from '@/components/scales/CartesianScale'; +import { ColorScale } from '@/components/scales/ColorScale'; +import { StackedBars } from '@/components/StackedBars'; +import { useToggableDomainKey } from '@/lib/useToggableDomainKey'; +import { ScaleBand, ScaleLinear } from 'eazychart-core/src/scales'; +import { + RawData, + AnimationOptions, + ChartPadding, + AxisConfig, + Position, + Dimensions, + Direction, + AxisConfigMulti, +} from 'eazychart-core/src/types'; +import React, { FC, SVGAttributes } from 'react'; + +export interface StackedColumnChartProps extends SVGAttributes { + data: RawData; + colors?: string[]; + animationOptions?: AnimationOptions; + padding?: ChartPadding; + isRTL?: boolean; + xAxis?: AxisConfig; + yAxis?: AxisConfigMulti; + dimensions?: Partial; + scopedSlots?: { + LegendComponent: React.FC; + TooltipComponent: React.FC; + }; +} + +export const StackedColumnChart: FC = ({ + data, + colors = ['#339999', '#993399', '#333399'], + animationOptions = { + easing: 'easeBack', + duration: 400, + delay: 0, + }, + padding = { + left: 150, + bottom: 100, + right: 150, + top: 100, + }, + xAxis = { + domainKey: 'xValue', + position: Position.BOTTOM, + }, + yAxis = { + domainKeys: ['yValue', 'yValue1', 'yValue2'], + position: Position.LEFT, + }, + isRTL = false, + dimensions = { height: 800, width: 1200 }, + scopedSlots = { + LegendComponent: Legend, + TooltipComponent: Tooltip, + }, +}) => { + const { activeDomainKeys, activeDomain, toggleDomainKey } = + useToggableDomainKey(data, yAxis.domainKeys); + + return ( + + + + + + + + + + ); +}; From efd1898d4a6d69497c96a2e4ca33a38284d9f99e Mon Sep 17 00:00:00 2001 From: IlyesBENAMARA Date: Wed, 19 Oct 2022 11:51:54 +0100 Subject: [PATCH 7/7] feat: cstacked column chart --- packages/ez-dev/storybook/data.ts | 5 ++ packages/ez-react/src/components/Bars.tsx | 33 +-------- .../ez-react/src/components/StackedBars.tsx | 68 ++++++++++++++----- .../recipes/column/ColumnChart.stories.tsx | 6 +- .../src/recipes/column/StackedColumnChart.tsx | 8 +-- 5 files changed, 64 insertions(+), 56 deletions(-) diff --git a/packages/ez-dev/storybook/data.ts b/packages/ez-dev/storybook/data.ts index a51f0f3..4b07c4d 100644 --- a/packages/ez-dev/storybook/data.ts +++ b/packages/ez-dev/storybook/data.ts @@ -9,30 +9,35 @@ export const dimensions = { export const rawData: RawData = [ { value: 9, + value1: 20, name: 'Alpha', id: '1', v: 2, }, { value: 45, + value1: 20, name: 'Beta', id: '2', v: 5, }, { value: 29, + value1: 20, name: 'Gamma', id: '3', v: 10, }, { value: 30, + value1: 20, name: 'Delta', id: '4', v: 4, }, { value: 50, + value1: 20, name: 'Epsilon', id: '5', v: 8, diff --git a/packages/ez-react/src/components/Bars.tsx b/packages/ez-react/src/components/Bars.tsx index 3b2d6e2..72abebf 100644 --- a/packages/ez-react/src/components/Bars.tsx +++ b/packages/ez-react/src/components/Bars.tsx @@ -4,34 +4,13 @@ import { Bar } from '@/components/shapes/Bar'; import { useChart } from '@/lib/use-chart'; import { useCartesianScales } from '@/components/scales/CartesianScale'; import { useColorScale } from './scales/ColorScale'; -import { Dimensions, ScaleLinearOrBand } from 'eazychart-core/src/types'; -import { RectangleDatum } from 'eazychart-core/dist/types'; export interface BarsProps extends SVGAttributes { xDomainKey: string; yDomainKey: string; - scopedSlots?: { - default: ({ - shapeData, - scales, - dimensions, - }: { - shapeData: RectangleDatum[]; - scales: { - xScale: ScaleLinearOrBand; - yScale: ScaleLinearOrBand; - }; - dimensions: Dimensions; - }) => React.ReactChild; - }; } -export const Bars: FC = ({ - xDomainKey, - yDomainKey, - scopedSlots, - ...rest -}) => { +export const Bars: FC = ({ xDomainKey, yDomainKey, ...rest }) => { const { data, dimensions, isRTL } = useChart(); const { xScale, yScale } = useCartesianScales(); const { colorScale } = useColorScale(); @@ -58,15 +37,7 @@ export const Bars: FC = ({ isRTL, ]); - return scopedSlots && scopedSlots.default ? ( - - {scopedSlots.default({ - shapeData: scaledData, - scales: { xScale, yScale }, - dimensions, - })} - - ) : ( + return ( {scaledData.map((rectDatum) => { return ; diff --git a/packages/ez-react/src/components/StackedBars.tsx b/packages/ez-react/src/components/StackedBars.tsx index 232fe74..e693a84 100644 --- a/packages/ez-react/src/components/StackedBars.tsx +++ b/packages/ez-react/src/components/StackedBars.tsx @@ -4,6 +4,7 @@ import { Bar } from '@/components/shapes/Bar'; import { useChart } from '@/lib/use-chart'; import { useCartesianScales } from '@/components/scales/CartesianScale'; import { useColorScale } from './scales/ColorScale'; +import { RectangleDatum } from 'eazychart-core/src/types'; export interface StackedBarsProps extends SVGAttributes { xDomainKey: string; @@ -20,30 +21,63 @@ export const StackedBars: FC = ({ const { colorScale } = useColorScale(); const scaledData = useMemo(() => { - return yDomainKeys.reduce((acc, yDomainKey) => { - // @ts-ignore - acc[yDomainKey] = scaleRectangleData( - data, - 'id', - yDomainKey, - xScale, - yScale, - colorScale, - dimensions, - isRTL - ); - return acc; + return yDomainKeys.reduce((acc, yDomainKey, index) => { + if (index === 0) { + // @ts-ignore + acc[yDomainKey] = scaleRectangleData( + data, + xDomainKey, + yDomainKey, + xScale, + yScale, + colorScale, + dimensions, + isRTL + ); + return acc; + } else { + // @ts-ignore + acc[yDomainKey] = scaleRectangleData( + data, + xDomainKey, + yDomainKey, + xScale, + yScale, + colorScale, + dimensions, + isRTL + ).map((datum, id) => { + // @ts-ignore + const height0 = acc[yDomainKeys[index - 1]][id].height; + return datum.height > height0 + ? { ...datum, height: datum.height - height0 } + : datum; + }); + return acc; + } }, {}); - }, [yDomainKeys, data, xScale, yScale, colorScale, dimensions, isRTL]); + }, [ + data, + yDomainKeys, + xDomainKey, + xScale, + yScale, + colorScale, + dimensions, + isRTL, + ]); return ( - {data.forEach((_datum, index) => { - return yDomainKeys.map((yDomainKey) => { + {yDomainKeys.map((yDomainKey) => { + // @ts-ignore + const shapeData = scaledData[yDomainKey] as RectangleDatum[]; + const color = colorScale.scale(yDomainKey); + return shapeData.map((shapeDatum: RectangleDatum, index: number) => { return ( ); }); diff --git a/packages/ez-react/src/recipes/column/ColumnChart.stories.tsx b/packages/ez-react/src/recipes/column/ColumnChart.stories.tsx index 0c8113a..8da296e 100644 --- a/packages/ez-react/src/recipes/column/ColumnChart.stories.tsx +++ b/packages/ez-react/src/recipes/column/ColumnChart.stories.tsx @@ -6,7 +6,7 @@ import { LineColumnChartProps, } from '@/recipes/column/LineColumnChart'; import { baseChartArgTypes, ChartWrapper } from '@/lib/storybook-utils'; -import { colors, evolutionData, rawData } from 'eazychart-dev/storybook/data'; +import { colors, rawData } from 'eazychart-dev/storybook/data'; import { StackedColumnChart, StackedColumnChartProps, @@ -95,10 +95,8 @@ export const StackedColumn = StackedColumnTemplate.bind({}); const StackedColumnArguments = { ...defaultArguments, - colors: ['#339999', '#993399', '#333399'], - data: evolutionData, yAxis: { - domainKeys: ['yValue', 'yValue1', 'yValue2'], + domainKeys: ['value', 'value1'], title: 'Temperature', tickFormat: (d: number) => `${d}°`, }, diff --git a/packages/ez-react/src/recipes/column/StackedColumnChart.tsx b/packages/ez-react/src/recipes/column/StackedColumnChart.tsx index 9917a74..8967f48 100644 --- a/packages/ez-react/src/recipes/column/StackedColumnChart.tsx +++ b/packages/ez-react/src/recipes/column/StackedColumnChart.tsx @@ -49,15 +49,15 @@ export const StackedColumnChart: FC = ({ top: 100, }, xAxis = { - domainKey: 'xValue', + domainKey: 'name', position: Position.BOTTOM, }, yAxis = { - domainKeys: ['yValue', 'yValue1', 'yValue2'], + domainKeys: ['value', 'value1'], position: Position.LEFT, }, isRTL = false, - dimensions = { height: 800, width: 1200 }, + dimensions = {}, scopedSlots = { LegendComponent: Legend, TooltipComponent: Tooltip, @@ -79,7 +79,7 @@ export const StackedColumnChart: FC = ({ xScaleConfig={{ ScaleClass: ScaleBand, definition: { - domainKey: 'id', + domainKey: xAxis.domainKey, direction: Direction.HORIZONTAL, }, }}