From 82dc4b417fed0d875e433fcc6593dc777135f681 Mon Sep 17 00:00:00 2001 From: Michael Nesen Date: Tue, 17 Dec 2024 22:04:46 +0000 Subject: [PATCH] Refactor funnel chart props interface --- .../src/components/FunnelChartNext/Chart.tsx | 62 +++++++++---------- .../FunnelChartNext/FunnelChartNext.tsx | 34 ++++------ .../components/Tooltip/Tooltip.tsx | 12 ++-- .../stories/Default.stories.tsx | 14 ++--- .../FunnelChartNext.chromatic.stories.tsx | 14 ++--- .../stories/Playground.stories.tsx | 14 ++--- .../FunnelChartNext/stories/meta.ts | 10 +-- .../FunnelChartNext/tests/Chart.test.tsx | 16 ++--- .../tests/FunnelChartNext.test.tsx | 12 ++-- .../src/components/SparkFunnelChart/Chart.tsx | 19 +++--- .../SparkFunnelChart/SparkFunnelChart.tsx | 27 +------- .../polaris-viz/src/storybook/constants.ts | 13 ++++ 12 files changed, 103 insertions(+), 144 deletions(-) diff --git a/packages/polaris-viz/src/components/FunnelChartNext/Chart.tsx b/packages/polaris-viz/src/components/FunnelChartNext/Chart.tsx index 9df261dbe..257cb7f8d 100644 --- a/packages/polaris-viz/src/components/FunnelChartNext/Chart.tsx +++ b/packages/polaris-viz/src/components/FunnelChartNext/Chart.tsx @@ -1,11 +1,7 @@ import type {ReactNode} from 'react'; import {Fragment, useMemo, useState} from 'react'; import {scaleBand, scaleLinear} from 'd3-scale'; -import type { - DataSeries, - XAxisOptions, - YAxisOptions, -} from '@shopify/polaris-viz-core'; +import type {DataSeries, LabelFormatter} from '@shopify/polaris-viz-core'; import { uniqueId, LinearGradientWithStops, @@ -46,22 +42,22 @@ import { export interface ChartProps { data: DataSeries[]; tooltipLabels: FunnelChartNextProps['tooltipLabels']; - xAxisOptions: Required; - yAxisOptions: Required; - renderScaleIconTooltipContent?: () => ReactNode; + seriesNameFormatter: LabelFormatter; + labelFormatter: LabelFormatter; percentageFormatter?: (value: number) => string; + renderScaleIconTooltipContent?: () => ReactNode; } export function Chart({ data, tooltipLabels, - xAxisOptions, - yAxisOptions, - renderScaleIconTooltipContent, + seriesNameFormatter, + labelFormatter, percentageFormatter = (value: number) => { - const formattedValue = yAxisOptions.labelFormatter(value); + const formattedValue = labelFormatter(value); return `${formattedValue}%`; }, + renderScaleIconTooltipContent, }: ChartProps) { const [tooltipIndex, setTooltipIndex] = useState(null); const {containerBounds} = useChartContext(); @@ -93,12 +89,12 @@ export function Chart({ }); const labels = useMemo( - () => dataSeries.map(({key}) => xAxisOptions.labelFormatter(key)), - [dataSeries, xAxisOptions], + () => dataSeries.map(({key}) => seriesNameFormatter(key)), + [dataSeries, seriesNameFormatter], ); - const segmentWidth = drawableWidth / xValues.length; - const connectorWidth = segmentWidth * (1 - SEGMENT_WIDTH_RATIO); + const totalStepWidth = drawableWidth / xValues.length; + const connectorWidth = totalStepWidth * (1 - SEGMENT_WIDTH_RATIO); const drawableWidthWithLastConnector = drawableWidth + connectorWidth; const xScale = scaleBand() @@ -128,7 +124,7 @@ export function Chart({ }); const formattedValues = dataSeries.map((dataPoint) => { - return yAxisOptions.labelFormatter(dataPoint.value); + return labelFormatter(dataPoint.value); }); const mainPercentage = percentageFormatter( @@ -167,20 +163,18 @@ export function Chart({ textAnchor="start" /> - {xAxisOptions.hide === false && ( - - - - )} + + + {dataSeries.map((dataPoint, index: number) => { const nextPoint = dataSeries[index + 1]; @@ -194,9 +188,9 @@ export function Chart({ diff --git a/packages/polaris-viz/src/components/FunnelChartNext/FunnelChartNext.tsx b/packages/polaris-viz/src/components/FunnelChartNext/FunnelChartNext.tsx index 41a741958..8d37ad657 100644 --- a/packages/polaris-viz/src/components/FunnelChartNext/FunnelChartNext.tsx +++ b/packages/polaris-viz/src/components/FunnelChartNext/FunnelChartNext.tsx @@ -1,8 +1,4 @@ -import type { - XAxisOptions, - YAxisOptions, - ChartProps, -} from '@shopify/polaris-viz-core'; +import type {ChartProps, LabelFormatter} from '@shopify/polaris-viz-core'; import { DEFAULT_CHART_PROPS, ChartState, @@ -11,10 +7,6 @@ import { import type {ReactNode} from 'react'; import {ChartContainer} from '../../components/ChartContainer'; -import { - getYAxisOptionsWithDefaults, - getXAxisOptionsWithDefaults, -} from '../../utilities'; import {ChartSkeleton} from '../'; import {Chart} from './Chart'; @@ -24,39 +16,35 @@ export type FunnelChartNextProps = { reached: string; dropped: string; }; - xAxisOptions?: Pick; - yAxisOptions?: Pick; + seriesNameFormatter?: LabelFormatter; + labelFormatter?: LabelFormatter; renderScaleIconTooltipContent?: () => ReactNode; percentageFormatter?: (value: number) => string; } & ChartProps; +const DEFAULT_LABEL_FORMATTER: LabelFormatter = (value) => `${value}`; + export function FunnelChartNext(props: FunnelChartNextProps) { const {theme: defaultTheme} = useChartContext(); const { data, theme = defaultTheme, - xAxisOptions, - yAxisOptions, id, isAnimated, state, errorText, tooltipLabels, + seriesNameFormatter = DEFAULT_LABEL_FORMATTER, + labelFormatter = DEFAULT_LABEL_FORMATTER, + percentageFormatter, onError, renderScaleIconTooltipContent, - percentageFormatter, } = { ...DEFAULT_CHART_PROPS, ...props, }; - const xAxisOptionsForChart: Required = - getXAxisOptionsWithDefaults(xAxisOptions); - - const yAxisOptionsForChart: Required = - getYAxisOptionsWithDefaults(yAxisOptions); - return ( )} diff --git a/packages/polaris-viz/src/components/FunnelChartNext/components/Tooltip/Tooltip.tsx b/packages/polaris-viz/src/components/FunnelChartNext/components/Tooltip/Tooltip.tsx index d3ead0522..b274217db 100644 --- a/packages/polaris-viz/src/components/FunnelChartNext/components/Tooltip/Tooltip.tsx +++ b/packages/polaris-viz/src/components/FunnelChartNext/components/Tooltip/Tooltip.tsx @@ -1,5 +1,5 @@ import {Fragment} from 'react'; -import type {Color, DataPoint, YAxisOptions} from '@shopify/polaris-viz-core'; +import type {Color, DataPoint, LabelFormatter} from '@shopify/polaris-viz-core'; import {DEFAULT_THEME_NAME} from '@shopify/polaris-viz-core'; import {TOOLTIP_WIDTH} from '../../constants'; @@ -17,7 +17,7 @@ export interface TooltipContentProps { dataSeries: DataPoint[]; isLast: boolean; tooltipLabels: FunnelChartNextProps['tooltipLabels']; - yAxisOptions: Required; + labelFormatter: LabelFormatter; percentageFormatter: (value: number) => string; } @@ -32,8 +32,8 @@ export function Tooltip({ activeIndex, dataSeries, isLast, - yAxisOptions, tooltipLabels, + labelFormatter, percentageFormatter, }: TooltipContentProps) { const point = dataSeries[activeIndex]; @@ -46,7 +46,7 @@ export function Tooltip({ const data: Data[] = [ { key: tooltipLabels.reached, - value: yAxisOptions.labelFormatter(point.value), + value: labelFormatter(point.value), color: FUNNEL_CHART_SEGMENT_FILL, percent: 100 - dropOffPercentage, }, @@ -55,9 +55,7 @@ export function Tooltip({ if (!isLast) { data.push({ key: tooltipLabels.dropped, - value: yAxisOptions.labelFormatter( - nextPoint?.value ?? 0 * dropOffPercentage, - ), + value: labelFormatter(nextPoint?.value ?? 0 * dropOffPercentage), percent: dropOffPercentage, color: FUNNEL_CHART_CONNECTOR_GRADIENT, }); diff --git a/packages/polaris-viz/src/components/FunnelChartNext/stories/Default.stories.tsx b/packages/polaris-viz/src/components/FunnelChartNext/stories/Default.stories.tsx index ca68fe7f2..8696d7c5b 100644 --- a/packages/polaris-viz/src/components/FunnelChartNext/stories/Default.stories.tsx +++ b/packages/polaris-viz/src/components/FunnelChartNext/stories/Default.stories.tsx @@ -9,18 +9,16 @@ import {Fragment} from 'react'; export const Default: Story = Template.bind({}); -const yAxisOptions = { - labelFormatter: (value) => { - return new Intl.NumberFormat('en', { - style: 'decimal', - maximumFractionDigits: 2, - }).format(Number(value)); - }, +const labelFormatter = (value) => { + return new Intl.NumberFormat('en', { + style: 'decimal', + maximumFractionDigits: 2, + }).format(Number(value)); }; Default.args = { data: DEFAULT_DATA, - yAxisOptions: yAxisOptions, + labelFormatter, tooltipLabels: { reached: 'Reached this step', dropped: 'Dropped off', diff --git a/packages/polaris-viz/src/components/FunnelChartNext/stories/FunnelChartNext.chromatic.stories.tsx b/packages/polaris-viz/src/components/FunnelChartNext/stories/FunnelChartNext.chromatic.stories.tsx index 2d2c27864..cddd91139 100644 --- a/packages/polaris-viz/src/components/FunnelChartNext/stories/FunnelChartNext.chromatic.stories.tsx +++ b/packages/polaris-viz/src/components/FunnelChartNext/stories/FunnelChartNext.chromatic.stories.tsx @@ -15,18 +15,16 @@ import type {FunnelChartNextProps} from '../FunnelChartNext'; export const Default: Story = Template.bind({}); -const yAxisOptions = { - labelFormatter: (value) => { - return new Intl.NumberFormat('en', { - style: 'decimal', - maximumFractionDigits: 2, - }).format(Number(value)); - }, +const labelFormatter = (value) => { + return new Intl.NumberFormat('en', { + style: 'decimal', + maximumFractionDigits: 2, + }).format(Number(value)); }; Default.args = { data: DEFAULT_DATA, - yAxisOptions: yAxisOptions, + labelFormatter, tooltipLabels: { reached: 'Reached this step', dropped: 'Dropped off', diff --git a/packages/polaris-viz/src/components/FunnelChartNext/stories/Playground.stories.tsx b/packages/polaris-viz/src/components/FunnelChartNext/stories/Playground.stories.tsx index cfd5d5096..644743155 100644 --- a/packages/polaris-viz/src/components/FunnelChartNext/stories/Playground.stories.tsx +++ b/packages/polaris-viz/src/components/FunnelChartNext/stories/Playground.stories.tsx @@ -12,13 +12,11 @@ import {META} from './meta'; export const ZeroValues: Story = Template.bind({}); -const yAxisOptions = { - labelFormatter: (value) => { - return new Intl.NumberFormat('en', { - style: 'decimal', - maximumFractionDigits: 2, - }).format(Number(value)); - }, +const labelFormatter = (value) => { + return new Intl.NumberFormat('en', { + style: 'decimal', + maximumFractionDigits: 2, + }).format(Number(value)); }; ZeroValues.args = { @@ -45,7 +43,7 @@ ZeroValues.args = { name: 'Conversion rates', }, ], - yAxisOptions: yAxisOptions, + labelFormatter, tooltipLabels: { reached: 'Reached this step', dropped: 'Dropped off', diff --git a/packages/polaris-viz/src/components/FunnelChartNext/stories/meta.ts b/packages/polaris-viz/src/components/FunnelChartNext/stories/meta.ts index b08bc538a..ca3801d88 100644 --- a/packages/polaris-viz/src/components/FunnelChartNext/stories/meta.ts +++ b/packages/polaris-viz/src/components/FunnelChartNext/stories/meta.ts @@ -3,9 +3,10 @@ import type {Meta} from '@storybook/react'; import { CHART_STATE_CONTROL_ARGS, CONTROLS_ARGS, + LABEL_FORMATTER_ARGS, + PERCENTAGE_FORMATTER_ARGS, + SERIES_NAME_FORMATTER_ARGS, THEME_CONTROL_ARGS, - X_AXIS_OPTIONS_ARGS, - Y_AXIS_OPTIONS_ARGS, } from '../../../storybook/constants'; import {PageWithSizingInfo} from '../../Docs/stories'; import {FunnelChartNext} from '../FunnelChartNext'; @@ -23,8 +24,9 @@ export const META: Meta = { }, }, argTypes: { - xAxisOptions: X_AXIS_OPTIONS_ARGS, - yAxisOptions: Y_AXIS_OPTIONS_ARGS, + seriesNameFormatter: SERIES_NAME_FORMATTER_ARGS, + labelFormatter: LABEL_FORMATTER_ARGS, + percentageFormatter: PERCENTAGE_FORMATTER_ARGS, theme: THEME_CONTROL_ARGS, state: CHART_STATE_CONTROL_ARGS, }, diff --git a/packages/polaris-viz/src/components/FunnelChartNext/tests/Chart.test.tsx b/packages/polaris-viz/src/components/FunnelChartNext/tests/Chart.test.tsx index 2131c0b0a..8c8fc3e84 100644 --- a/packages/polaris-viz/src/components/FunnelChartNext/tests/Chart.test.tsx +++ b/packages/polaris-viz/src/components/FunnelChartNext/tests/Chart.test.tsx @@ -30,18 +30,12 @@ const mockContext = { const defaultProps = { data: mockData, - showConnectionPercentage: false, tooltipLabels: { dropoff: 'Dropoff', total: 'Total', }, - xAxisOptions: { - hide: false, - labelFormatter: (value: string) => value, - }, - yAxisOptions: { - labelFormatter: (value: number) => value.toString(), - }, + seriesNameFormatter: (value: string) => `$${value}`, + labelFormatter: (value: string) => `$${value}`, }; describe('', () => { @@ -71,10 +65,8 @@ describe('', () => { , ); diff --git a/packages/polaris-viz/src/components/FunnelChartNext/tests/FunnelChartNext.test.tsx b/packages/polaris-viz/src/components/FunnelChartNext/tests/FunnelChartNext.test.tsx index 4f1f4c3d8..45cd8cc9d 100644 --- a/packages/polaris-viz/src/components/FunnelChartNext/tests/FunnelChartNext.test.tsx +++ b/packages/polaris-viz/src/components/FunnelChartNext/tests/FunnelChartNext.test.tsx @@ -92,19 +92,19 @@ describe('', () => { expect(funnel).toContainReactComponent(Chart); }); - it('passes xAxisOptions to Chart', () => { - const xAxisOptions = {hide: true}; + it('passes seriesNameFormatter to Chart', () => { + const seriesNameFormatter = (value) => `$${value}`; const funnel = mount( , ); expect(funnel).toContainReactComponent(Chart, { - xAxisOptions: expect.objectContaining({hide: true}), + seriesNameFormatter, }); }); @@ -114,13 +114,13 @@ describe('', () => { , ); expect(funnel).toContainReactComponent(Chart, { - yAxisOptions: expect.objectContaining({labelFormatter}), + labelFormatter, }); }); }); diff --git a/packages/polaris-viz/src/components/SparkFunnelChart/Chart.tsx b/packages/polaris-viz/src/components/SparkFunnelChart/Chart.tsx index cf275c379..a99577bf0 100644 --- a/packages/polaris-viz/src/components/SparkFunnelChart/Chart.tsx +++ b/packages/polaris-viz/src/components/SparkFunnelChart/Chart.tsx @@ -1,10 +1,6 @@ import {Fragment} from 'react'; import {scaleBand, scaleLinear} from 'd3-scale'; -import type { - DataSeries, - XAxisOptions, - YAxisOptions, -} from '@shopify/polaris-viz-core'; +import type {DataSeries} from '@shopify/polaris-viz-core'; import {useChartContext} from '@shopify/polaris-viz-core'; import {useFunnelBarScaling} from '../../hooks'; @@ -17,12 +13,11 @@ import type {SparkFunnelChartProps} from './SparkFunnelChart'; export interface ChartProps { data: DataSeries[]; tooltipLabels: SparkFunnelChartProps['tooltipLabels']; - xAxisOptions: Required; - yAxisOptions: Required; } const LINE_OFFSET = 1; const GAP = 1; +const SEGMENT_WIDTH_RATIO = 0.75; export function Chart({data}: ChartProps) { const {containerBounds} = useChartContext(); @@ -36,7 +31,13 @@ export function Chart({data}: ChartProps) { height: 0, }; - const xScale = scaleBand().domain(xValues).range([0, drawableWidth]); + const totalStepWidth = drawableWidth / xValues.length; + const connectorWidth = totalStepWidth * (1 - SEGMENT_WIDTH_RATIO); + const drawableWidthWithLastConnector = drawableWidth + connectorWidth; + + const xScale = scaleBand() + .domain(xValues) + .range([0, drawableWidthWithLastConnector]); const yScale = scaleLinear() .range([0, drawableHeight]) @@ -48,7 +49,7 @@ export function Chart({data}: ChartProps) { }); const sectionWidth = xScale.bandwidth(); - const barWidth = sectionWidth * 0.75; + const barWidth = sectionWidth * SEGMENT_WIDTH_RATIO; return ( diff --git a/packages/polaris-viz/src/components/SparkFunnelChart/SparkFunnelChart.tsx b/packages/polaris-viz/src/components/SparkFunnelChart/SparkFunnelChart.tsx index a83abfae1..7f48b57b2 100644 --- a/packages/polaris-viz/src/components/SparkFunnelChart/SparkFunnelChart.tsx +++ b/packages/polaris-viz/src/components/SparkFunnelChart/SparkFunnelChart.tsx @@ -1,8 +1,4 @@ -import type { - XAxisOptions, - YAxisOptions, - ChartProps, -} from '@shopify/polaris-viz-core'; +import type {ChartProps} from '@shopify/polaris-viz-core'; import { DEFAULT_CHART_PROPS, ChartState, @@ -10,10 +6,6 @@ import { } from '@shopify/polaris-viz-core'; import {ChartContainer} from '../../components/ChartContainer'; -import { - getYAxisOptionsWithDefaults, - getXAxisOptionsWithDefaults, -} from '../../utilities'; import {ChartSkeleton} from '../'; import {Chart} from './Chart'; @@ -23,8 +15,6 @@ export type SparkFunnelChartProps = { reached: string; dropped: string; }; - xAxisOptions?: Pick; - yAxisOptions?: Pick; } & ChartProps; export function SparkFunnelChart(props: SparkFunnelChartProps) { @@ -33,8 +23,6 @@ export function SparkFunnelChart(props: SparkFunnelChartProps) { const { data, theme = defaultTheme, - xAxisOptions, - yAxisOptions, id, isAnimated, state, @@ -46,12 +34,6 @@ export function SparkFunnelChart(props: SparkFunnelChartProps) { ...props, }; - const xAxisOptionsForChart: Required = - getXAxisOptionsWithDefaults(xAxisOptions); - - const yAxisOptionsForChart: Required = - getYAxisOptionsWithDefaults(yAxisOptions); - return ( ) : ( - + )} ); diff --git a/packages/polaris-viz/src/storybook/constants.ts b/packages/polaris-viz/src/storybook/constants.ts index 4473211af..c4931e140 100644 --- a/packages/polaris-viz/src/storybook/constants.ts +++ b/packages/polaris-viz/src/storybook/constants.ts @@ -138,6 +138,19 @@ export const MAX_SERIES_ARGS = { }, }; +export const SERIES_NAME_FORMATTER_ARGS = { + description: 'A function that formats the series name in the chart.', +}; + +export const LABEL_FORMATTER_ARGS = { + description: 'A function that formats numeric values displayed in the chart.', +}; + +export const PERCENTAGE_FORMATTER_ARGS = { + description: + 'A function that formats percentage values displayed in the chart.', +}; + export const DEFAULT_CHART_CONTEXT: ChartContextValues = { shouldAnimate: false, characterWidths,