From ebe88dc52887ae18658e558490b5368c60e961d3 Mon Sep 17 00:00:00 2001 From: Matt Vickers Date: Wed, 10 Jan 2024 13:05:11 -0600 Subject: [PATCH] Rework LineChartPredictive to use in chart LineSeries --- .../src/components/LineSeries/LineSeries.tsx | 4 +- .../LineChartPredictive.tsx | 47 +- .../components/CustomLegend/CustomLegend.scss | 8 - .../components/CustomLegend/CustomLegend.tsx | 58 +-- .../PredictiveLinePoints.tsx} | 41 +- .../components/PredictiveLinePoints/index.ts | 1 + .../components/PredictiveLineSeries/index.ts | 1 - .../components/SeriesIcon/SeriesIcon.scss | 7 + .../components/SeriesIcon/SeriesIcon.tsx | 44 ++ .../components/SeriesIcon/index.ts | 1 + .../LineChartPredictive/components/index.ts | 3 +- .../playground/DynamicData.stories.tsx | 442 ++++++++++++++++++ .../LineChartPredictive/utilities/Styles.scss | 6 + .../renderLinearPredictiveTooltipContent.tsx | 90 ++++ .../components/TooltipRow/TooltipRow.tsx | 2 +- 15 files changed, 652 insertions(+), 103 deletions(-) rename packages/polaris-viz/src/components/LineChartPredictive/components/{PredictiveLineSeries/PredictiveLineSeries.tsx => PredictiveLinePoints/PredictiveLinePoints.tsx} (66%) create mode 100644 packages/polaris-viz/src/components/LineChartPredictive/components/PredictiveLinePoints/index.ts delete mode 100644 packages/polaris-viz/src/components/LineChartPredictive/components/PredictiveLineSeries/index.ts create mode 100644 packages/polaris-viz/src/components/LineChartPredictive/components/SeriesIcon/SeriesIcon.scss create mode 100644 packages/polaris-viz/src/components/LineChartPredictive/components/SeriesIcon/SeriesIcon.tsx create mode 100644 packages/polaris-viz/src/components/LineChartPredictive/components/SeriesIcon/index.ts create mode 100644 packages/polaris-viz/src/components/LineChartPredictive/stories/playground/DynamicData.stories.tsx create mode 100644 packages/polaris-viz/src/components/LineChartPredictive/utilities/Styles.scss create mode 100644 packages/polaris-viz/src/components/LineChartPredictive/utilities/renderLinearPredictiveTooltipContent.tsx diff --git a/packages/polaris-viz-core/src/components/LineSeries/LineSeries.tsx b/packages/polaris-viz-core/src/components/LineSeries/LineSeries.tsx index 850d89ec2..76921a2e9 100644 --- a/packages/polaris-viz-core/src/components/LineSeries/LineSeries.tsx +++ b/packages/polaris-viz-core/src/components/LineSeries/LineSeries.tsx @@ -177,7 +177,7 @@ export function LineSeries({ style={{ ...getColorVisionStylesForActiveIndex({ activeIndex: activeLineIndex, - index, + index: data?.metadata?.relatedIndex ?? index, }), strokeDasharray, strokeLinecap: 'round', @@ -228,7 +228,7 @@ export function LineSeries({ fill="none" {...getColorVisionEventAttrs({ type: COLOR_VISION_SINGLE_ITEM, - index, + index: data?.metadata?.relatedIndex ?? index, })} /> diff --git a/packages/polaris-viz/src/components/LineChartPredictive/LineChartPredictive.tsx b/packages/polaris-viz/src/components/LineChartPredictive/LineChartPredictive.tsx index 57fc7a04c..371ca58de 100644 --- a/packages/polaris-viz/src/components/LineChartPredictive/LineChartPredictive.tsx +++ b/packages/polaris-viz/src/components/LineChartPredictive/LineChartPredictive.tsx @@ -1,14 +1,18 @@ +import type {DataSeries} from '@shopify/polaris-viz-core'; import { DEFAULT_CHART_PROPS, DEFAULT_THEME_NAME, useTheme, useThemeSeriesColors, } from '@shopify/polaris-viz-core'; +import {useMemo} from 'react'; +import type {RenderTooltipContentData} from 'types'; import {LineChart} from '../LineChart'; import type {LineChartPredictiveProps} from './types'; -import {CustomLegend, PredictiveLineSeries} from './components'; +import {CustomLegend, PredictiveLinePoints} from './components'; +import {renderLinearPredictiveTooltipContent} from './utilities/renderLinearPredictiveTooltipContent'; export function LineChartPredictive(props: LineChartPredictiveProps) { const { @@ -22,7 +26,7 @@ export function LineChartPredictive(props: LineChartPredictiveProps) { skipLinkText, state, theme, - tooltipOptions, + tooltipOptions: initialTooltipOptions, xAxisOptions, yAxisOptions, } = { @@ -41,19 +45,44 @@ export function LineChartPredictive(props: LineChartPredictiveProps) { } } + const selectedTheme = useTheme(theme); + const seriesColors = useThemeSeriesColors(nonPredictiveData, selectedTheme); + const predictiveSeriesNames = predictiveData .map(({metadata}) => { return data[metadata?.relatedIndex ?? -1].name; }) .filter((value) => value != null) as string[]; - const selectedTheme = useTheme(theme); - const seriesColors = useThemeSeriesColors(nonPredictiveData, selectedTheme); + const dataWithColors: DataSeries[] = []; + let index = -1; + + for (const series of data) { + if (series.metadata?.relatedIndex == null) { + index += 1; + } + + dataWithColors.push({ + ...series, + color: seriesColors[index], + }); + } + + const tooltipOptions = useMemo(() => { + function renderTooltipContent(tooltipData: RenderTooltipContentData) { + return renderLinearPredictiveTooltipContent(tooltipData); + } + + return { + ...initialTooltipOptions, + renderTooltipContent, + }; + }, [initialTooltipOptions]); return ( { return ( - ); diff --git a/packages/polaris-viz/src/components/LineChartPredictive/components/CustomLegend/CustomLegend.scss b/packages/polaris-viz/src/components/LineChartPredictive/components/CustomLegend/CustomLegend.scss index d1e05d3d4..7900c3649 100644 --- a/packages/polaris-viz/src/components/LineChartPredictive/components/CustomLegend/CustomLegend.scss +++ b/packages/polaris-viz/src/components/LineChartPredictive/components/CustomLegend/CustomLegend.scss @@ -4,11 +4,3 @@ flex-wrap: wrap; list-style: none; } - -.IconContainer { - display: flex; - align-items: center; - justify-items: center; - height: 12px; - width: 20px; -} diff --git a/packages/polaris-viz/src/components/LineChartPredictive/components/CustomLegend/CustomLegend.tsx b/packages/polaris-viz/src/components/LineChartPredictive/components/CustomLegend/CustomLegend.tsx index f577a7b09..6c4b67672 100644 --- a/packages/polaris-viz/src/components/LineChartPredictive/components/CustomLegend/CustomLegend.tsx +++ b/packages/polaris-viz/src/components/LineChartPredictive/components/CustomLegend/CustomLegend.tsx @@ -1,25 +1,16 @@ -import { - LinearGradientWithStops, - changeGradientOpacity, - isGradientType, - uniqueId, -} from '@shopify/polaris-viz-core'; -import type {Color} from '@shopify/polaris-viz-core'; +import {uniqueId} from '@shopify/polaris-viz-core'; import {useMemo} from 'react'; import type {LineChartPredictiveDataSeries} from '../../../../components/LineChartPredictive/types'; import type {ColorVisionInteractionMethods} from '../../../../types'; -import {getLineChartDataWithDefaults} from '../../../../utilities/getLineChartDataWithDefaults'; import {LegendItem} from '../../../../components/Legend'; +import {SeriesIcon} from '../SeriesIcon'; import styles from './CustomLegend.scss'; interface Props extends ColorVisionInteractionMethods { data: LineChartPredictiveDataSeries[]; predictiveSeriesNames: string[]; - getColorVisionEventAttrs: any; - getColorVisionStyles: any; - seriesColors: Color[]; theme: string; } @@ -28,20 +19,17 @@ export function CustomLegend({ predictiveSeriesNames, getColorVisionEventAttrs, getColorVisionStyles, - seriesColors, theme, }: Props) { - const id = useMemo(() => uniqueId('CustomLegen'), []); - - const dataWithDefaults = getLineChartDataWithDefaults(data, seriesColors); - return (
    - {dataWithDefaults.map(({color, name, isComparison}, index) => { - const gradientId = `${id}-${index}`; + {data.map(({color, name, isComparison, metadata}, index) => { + if (metadata?.isPredictive) { + return null; + } function renderSeriesIcon() { - return ; + return ; } return ( @@ -71,35 +59,3 @@ export function CustomLegend({
); } - -function SeriesIcon({color, gradientId}: {color: Color; gradientId: string}) { - return ( -
- - - {isGradientType(color) ? ( - - - - ) : null} - -
- ); -} diff --git a/packages/polaris-viz/src/components/LineChartPredictive/components/PredictiveLineSeries/PredictiveLineSeries.tsx b/packages/polaris-viz/src/components/LineChartPredictive/components/PredictiveLinePoints/PredictiveLinePoints.tsx similarity index 66% rename from packages/polaris-viz/src/components/LineChartPredictive/components/PredictiveLineSeries/PredictiveLineSeries.tsx rename to packages/polaris-viz/src/components/LineChartPredictive/components/PredictiveLinePoints/PredictiveLinePoints.tsx index b4eec68f8..2571c777c 100644 --- a/packages/polaris-viz/src/components/LineChartPredictive/components/PredictiveLineSeries/PredictiveLineSeries.tsx +++ b/packages/polaris-viz/src/components/LineChartPredictive/components/PredictiveLinePoints/PredictiveLinePoints.tsx @@ -1,7 +1,6 @@ import type {Color} from '@shopify/polaris-viz-core'; import { COLOR_VISION_SINGLE_ITEM, - LineSeries, LinearGradientWithStops, changeColorOpacity, changeGradientOpacity, @@ -11,46 +10,42 @@ import { import {Fragment, useMemo, useState} from 'react'; import type {LineChartSlotProps} from 'types'; -import {Point} from '../../../../components/Point'; +import {Point} from '../../../Point'; import {useWatchColorVisionEvents} from '../../../../hooks'; -import {getLineChartDataWithDefaults} from '../../../../utilities/getLineChartDataWithDefaults'; import type {LineChartPredictiveProps} from '../../types'; -interface PredictiveLinesProps extends LineChartSlotProps { +interface PredictiveLinePointsProps extends LineChartSlotProps { data: LineChartPredictiveProps['data']; - seriesColors: Color[]; - theme: string; } -export function PredictiveLineSeries({ +export function PredictiveLinePoints({ data, - drawableHeight, - drawableWidth, - seriesColors, - theme, xScale, yScale, -}: PredictiveLinesProps) { +}: PredictiveLinePointsProps) { const [activeLineIndex, setActiveLineIndex] = useState(-1); - const id = useMemo(() => uniqueId('PredictiveLines'), []); + const id = useMemo(() => uniqueId('PredictiveLinePoints'), []); useWatchColorVisionEvents({ type: COLOR_VISION_SINGLE_ITEM, onIndexChange: ({detail}) => setActiveLineIndex(detail.index), }); - const dataWithDefaults = getLineChartDataWithDefaults(data, seriesColors); - return ( - {dataWithDefaults.map((series, index) => { + {data.map((series, seriesIndex) => { + if (series.metadata?.isPredictive == null) { + return false; + } + + const index = series.metadata?.relatedIndex ?? seriesIndex; const pointGradientId = `${id}-point-${index}`; const predictiveStartIndex = series.data.findIndex( ({key}) => key === series.metadata?.startKey, ); - const color = series.color; + const color = series.color!; const pointColor = isGradientType(color) ? `url(#${pointGradientId})` @@ -58,18 +53,6 @@ export function PredictiveLineSeries({ return ( - {isGradientType(color) ? ( uniqueId('SeriesIcon'), []); + + return ( +
+ + + {isGradientType(color) ? ( + + + + ) : null} + +
+ ); +} diff --git a/packages/polaris-viz/src/components/LineChartPredictive/components/SeriesIcon/index.ts b/packages/polaris-viz/src/components/LineChartPredictive/components/SeriesIcon/index.ts new file mode 100644 index 000000000..67ffefcd7 --- /dev/null +++ b/packages/polaris-viz/src/components/LineChartPredictive/components/SeriesIcon/index.ts @@ -0,0 +1 @@ +export {SeriesIcon} from './SeriesIcon'; diff --git a/packages/polaris-viz/src/components/LineChartPredictive/components/index.ts b/packages/polaris-viz/src/components/LineChartPredictive/components/index.ts index 62b896d03..fe18bba2b 100644 --- a/packages/polaris-viz/src/components/LineChartPredictive/components/index.ts +++ b/packages/polaris-viz/src/components/LineChartPredictive/components/index.ts @@ -1,2 +1,3 @@ -export {PredictiveLineSeries} from './PredictiveLineSeries'; +export {PredictiveLinePoints} from './PredictiveLinePoints'; export {CustomLegend} from './CustomLegend'; +export {SeriesIcon} from './SeriesIcon'; diff --git a/packages/polaris-viz/src/components/LineChartPredictive/stories/playground/DynamicData.stories.tsx b/packages/polaris-viz/src/components/LineChartPredictive/stories/playground/DynamicData.stories.tsx new file mode 100644 index 000000000..e2967a704 --- /dev/null +++ b/packages/polaris-viz/src/components/LineChartPredictive/stories/playground/DynamicData.stories.tsx @@ -0,0 +1,442 @@ +import {useState} from 'react'; + +import {LineChartPredictive} from '../../LineChartPredictive'; +import type {DataSeries} from '@shopify/polaris-viz-core'; + +export {META as default} from '../meta'; + +// const DATA: DataSeries[] = [ +// { +// name: 'January 2023-December 2023', +// data: [ +// { +// key: 0, +// value: 70, +// }, +// { +// key: 1, +// value: 80, +// }, +// { +// key: 2, +// value: 87, +// }, +// { +// value: null, +// key: 3, +// }, +// { +// value: null, +// key: 4, +// }, +// { +// value: null, +// key: 5, +// }, +// { +// value: null, +// key: 6, +// }, +// { +// value: null, +// key: 7, +// }, +// { +// value: null, +// key: 8, +// }, +// { +// value: null, +// key: 9, +// }, +// { +// value: null, +// key: 10, +// }, +// { +// value: null, +// key: 11, +// }, +// ], +// styleOverride: { +// line: { +// hasArea: false, +// }, +// }, +// }, +// { +// name: 'January 2023-December 2023', +// data: [ +// { +// value: null, +// key: 0, +// }, +// { +// value: null, +// key: 1, +// }, +// { +// key: 2, +// value: 87, +// }, +// { +// key: 3, +// value: 93, +// }, +// { +// key: 4, +// value: 96, +// }, +// { +// key: 5, +// value: 102, +// }, +// { +// key: 6, +// value: 104, +// }, +// { +// key: 7, +// value: 105, +// }, +// { +// key: 8, +// value: 106, +// }, +// { +// key: 9, +// value: 107, +// }, +// { +// key: 10, +// value: 108, +// }, +// { +// key: 11, +// value: 109, +// }, +// ], +// styleOverride: { +// line: { +// strokeDasharray: '1 10 1', +// hasArea: false, +// }, +// }, +// metadata: { +// relatedIndex: 0, +// isPredictive: true, +// startKey: 2, +// }, +// }, +// { +// data: [ +// { +// key: 0, +// value: 70, +// }, +// { +// key: 1, +// value: 57, +// }, +// { +// key: 2, +// value: 62, +// }, +// { +// key: 3, +// value: 67, +// }, +// { +// key: 4, +// value: 69, +// }, +// { +// key: 5, +// value: 72, +// }, +// { +// key: 6, +// value: 73, +// }, +// { +// key: 7, +// value: 75, +// }, +// { +// key: 8, +// value: 77, +// }, +// { +// key: 9, +// value: 78, +// }, +// { +// key: 10, +// value: 79, +// }, +// { +// key: 11, +// value: 81, +// }, +// ], +// isComparison: true, +// name: 'January 2022-December 2022', +// }, +// ]; + +const DATA: DataSeries[] = [ + { + name: 'January 2023-December 2023', + data: [ + { + key: 0, + value: 70, + }, + { + key: 1, + value: 80, + }, + { + key: 2, + value: 87, + }, + { + value: null, + key: 3, + }, + { + value: null, + key: 4, + }, + { + value: null, + key: 5, + }, + ], + styleOverride: { + line: { + hasArea: false, + }, + }, + }, + { + name: 'January 2023-December 2023', + data: [ + { + value: null, + key: 0, + }, + { + value: null, + key: 1, + }, + { + key: 2, + value: 87, + }, + { + key: 3, + value: 93, + }, + { + key: 4, + value: 96, + }, + { + key: 5, + value: 102, + }, + ], + styleOverride: { + line: { + strokeDasharray: '1 10 1', + hasArea: false, + }, + }, + metadata: { + relatedIndex: 0, + isPredictive: true, + startKey: 2, + }, + }, + // + { + name: 'Aug 2023-December 2023', + data: [ + { + key: 0, + value: 170, + }, + { + key: 1, + value: 180, + }, + { + key: 2, + value: 187, + }, + { + value: null, + key: 3, + }, + { + value: null, + key: 4, + }, + { + value: null, + key: 5, + }, + ], + styleOverride: { + line: { + hasArea: false, + }, + }, + }, + { + name: 'Aug 2023-December 2023', + data: [ + { + value: null, + key: 0, + }, + { + value: null, + key: 1, + }, + { + key: 2, + value: 187, + }, + { + key: 3, + value: 193, + }, + { + key: 4, + value: 196, + }, + { + key: 5, + value: 1102, + }, + ], + styleOverride: { + line: { + strokeDasharray: '1 10 1', + hasArea: false, + }, + }, + metadata: { + relatedIndex: 2, + isPredictive: true, + startKey: 2, + }, + }, + // + { + data: [ + { + key: 0, + value: 70, + }, + { + key: 1, + value: 57, + }, + { + key: 2, + value: 62, + }, + { + key: 3, + value: 67, + }, + { + key: 4, + value: 69, + }, + { + key: 5, + value: 72, + }, + ], + isComparison: true, + name: 'January 2022-December 2022', + }, +]; + +export const DynamicData = () => { + // const [data, setData] = useState([ + // { + // name: 'BCFM 2019', + // data: [ + // { + // key: 'Womens Leggings', + // value: 3, + // }, + // { + // key: 'Mens Bottoms', + // value: 7, + // }, + // { + // key: 'Mens Shorts', + // value: 4, + // }, + // ], + // }, + // { + // name: 'BCFM 2020', + // data: [ + // { + // key: 'Womens Leggings', + // value: 1, + // }, + // { + // key: 'Mens Bottoms', + // value: 2, + // }, + // { + // key: 'Mens Shorts', + // value: 5, + // }, + // ], + // }, + // ]); + + // const onClick = () => { + // const newData = data.map((series) => { + // return { + // ...series, + // data: series.data.map(({key}) => { + // const newValue = Math.floor(Math.random() * 200); + // return { + // key, + // value: newValue, + // }; + // }), + // }; + // }); + + // setData(newData); + // }; + + return ( + <> +
+ +
+ {/* */} + + ); +}; diff --git a/packages/polaris-viz/src/components/LineChartPredictive/utilities/Styles.scss b/packages/polaris-viz/src/components/LineChartPredictive/utilities/Styles.scss new file mode 100644 index 000000000..a72dfb67e --- /dev/null +++ b/packages/polaris-viz/src/components/LineChartPredictive/utilities/Styles.scss @@ -0,0 +1,6 @@ +.Icon { + display: flex; + align-items: center; + justify-content: center; + margin-right: 4px; +} diff --git a/packages/polaris-viz/src/components/LineChartPredictive/utilities/renderLinearPredictiveTooltipContent.tsx b/packages/polaris-viz/src/components/LineChartPredictive/utilities/renderLinearPredictiveTooltipContent.tsx new file mode 100644 index 000000000..4432b9b75 --- /dev/null +++ b/packages/polaris-viz/src/components/LineChartPredictive/utilities/renderLinearPredictiveTooltipContent.tsx @@ -0,0 +1,90 @@ +import type {ReactNode} from 'react'; +import {Fragment} from 'react'; + +import {PREVIEW_ICON_SIZE} from '../../../constants'; +import { + TooltipContentContainer, + TooltipTitle, + TooltipRow, + LinePreview, +} from '../../'; +import type {RenderTooltipContentData} from '../../../types'; +import {SeriesIcon} from '../components'; + +import styles from './Styles.scss'; + +export function renderLinearPredictiveTooltipContent( + tooltipData: RenderTooltipContentData, +): ReactNode { + const {theme} = tooltipData; + + const formatters = { + keyFormatter: (key) => `${key}`, + valueFormatter: (value) => `${value}`, + titleFormatter: (title) => `${title}`, + ...tooltipData.formatters, + }; + + function renderSeriesIcon(color, isComparison): ReactNode { + return ( +
+ {isComparison ? ( + + ) : ( + + )} +
+ ); + } + + function renderContent({ + activeColorVisionIndex, + }: { + activeColorVisionIndex: number; + }) { + const item = tooltipData.data[0]; + + return item.data.map(({color, key, value, isComparison}, seriesIndex) => { + const metadata = tooltipData.dataSeries[seriesIndex].metadata; + const activeKey = + tooltipData.dataSeries[seriesIndex].data[tooltipData.activeIndex].key; + const index = metadata?.relatedIndex ?? seriesIndex; + + const isNull = value == null; + const isPredictiveStartKey = metadata?.startKey === activeKey; + const isHidden = isNull || isPredictiveStartKey; + + return ( + renderSeriesIcon(color, isComparison)} + shape="Line" + value={formatters.valueFormatter(value ?? 0)} + /> + ); + }); + } + + return ( + + {({activeColorVisionIndex}) => ( + + {tooltipData.title != null && ( + + {formatters.titleFormatter(tooltipData.title)} + + )} + {renderContent({activeColorVisionIndex})} + + )} + + ); +} diff --git a/packages/polaris-viz/src/components/TooltipContent/components/TooltipRow/TooltipRow.tsx b/packages/polaris-viz/src/components/TooltipContent/components/TooltipRow/TooltipRow.tsx index 4d4e2ba0e..d335cbfb4 100644 --- a/packages/polaris-viz/src/components/TooltipContent/components/TooltipRow/TooltipRow.tsx +++ b/packages/polaris-viz/src/components/TooltipContent/components/TooltipRow/TooltipRow.tsx @@ -48,7 +48,7 @@ export function TooltipRow({ })} > {color != null && ( -
+
{renderSeriesIcon?.() ?? (