Skip to content

Commit

Permalink
Refactor funnel chart props interface
Browse files Browse the repository at this point in the history
  • Loading branch information
michaelnesen committed Dec 17, 2024
1 parent 5cd25ea commit 82dc4b4
Show file tree
Hide file tree
Showing 12 changed files with 103 additions and 144 deletions.
62 changes: 28 additions & 34 deletions packages/polaris-viz/src/components/FunnelChartNext/Chart.tsx
Original file line number Diff line number Diff line change
@@ -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,
Expand Down Expand Up @@ -46,22 +42,22 @@ import {
export interface ChartProps {
data: DataSeries[];
tooltipLabels: FunnelChartNextProps['tooltipLabels'];
xAxisOptions: Required<XAxisOptions>;
yAxisOptions: Required<YAxisOptions>;
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<number | null>(null);
const {containerBounds} = useChartContext();
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -128,7 +124,7 @@ export function Chart({
});

const formattedValues = dataSeries.map((dataPoint) => {
return yAxisOptions.labelFormatter(dataPoint.value);
return labelFormatter(dataPoint.value);
});

const mainPercentage = percentageFormatter(
Expand Down Expand Up @@ -167,20 +163,18 @@ export function Chart({
textAnchor="start"
/>

{xAxisOptions.hide === false && (
<g transform={`translate(0,${PERCENTAGE_SUMMARY_HEIGHT})`}>
<FunnelChartXAxisLabels
formattedValues={formattedValues}
labels={labels}
labelWidth={sectionWidth}
barWidth={barWidth}
percentages={percentages}
xScale={labelXScale}
shouldApplyScaling={shouldApplyScaling}
renderScaleIconTooltipContent={renderScaleIconTooltipContent}
/>
</g>
)}
<g transform={`translate(0,${PERCENTAGE_SUMMARY_HEIGHT})`}>
<FunnelChartXAxisLabels
formattedValues={formattedValues}
labels={labels}
labelWidth={sectionWidth}
barWidth={barWidth}
percentages={percentages}
xScale={labelXScale}
shouldApplyScaling={shouldApplyScaling}
renderScaleIconTooltipContent={renderScaleIconTooltipContent}
/>
</g>

{dataSeries.map((dataPoint, index: number) => {
const nextPoint = dataSeries[index + 1];
Expand All @@ -194,9 +188,9 @@ export function Chart({
<Fragment key={dataPoint.key}>
<g key={dataPoint.key} role="listitem">
<FunnelChartSegment
ariaLabel={`${xAxisOptions.labelFormatter(
ariaLabel={`${seriesNameFormatter(
dataPoint.key,
)}: ${yAxisOptions.labelFormatter(dataPoint.value)}`}
)}: ${labelFormatter(dataPoint.value)}`}
barHeight={barHeight}
barWidth={barWidth}
index={index}
Expand Down Expand Up @@ -265,7 +259,7 @@ export function Chart({
dataSeries={dataSeries}
isLast={tooltipIndex === dataSeries.length - 1}
tooltipLabels={tooltipLabels}
yAxisOptions={yAxisOptions}
labelFormatter={labelFormatter}
percentageFormatter={percentageFormatter}
/>
</FunnelTooltip>
Expand Down
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -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';
Expand All @@ -24,39 +16,35 @@ export type FunnelChartNextProps = {
reached: string;
dropped: string;
};
xAxisOptions?: Pick<XAxisOptions, 'hide'>;
yAxisOptions?: Pick<YAxisOptions, 'labelFormatter'>;
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<XAxisOptions> =
getXAxisOptionsWithDefaults(xAxisOptions);

const yAxisOptionsForChart: Required<YAxisOptions> =
getYAxisOptionsWithDefaults(yAxisOptions);

return (
<ChartContainer
data={data}
Expand All @@ -76,10 +64,10 @@ export function FunnelChartNext(props: FunnelChartNextProps) {
<Chart
data={data}
tooltipLabels={tooltipLabels}
xAxisOptions={xAxisOptionsForChart}
yAxisOptions={yAxisOptionsForChart}
renderScaleIconTooltipContent={renderScaleIconTooltipContent}
seriesNameFormatter={seriesNameFormatter}
labelFormatter={labelFormatter}
percentageFormatter={percentageFormatter}
renderScaleIconTooltipContent={renderScaleIconTooltipContent}
/>
)}
</ChartContainer>
Expand Down
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -17,7 +17,7 @@ export interface TooltipContentProps {
dataSeries: DataPoint[];
isLast: boolean;
tooltipLabels: FunnelChartNextProps['tooltipLabels'];
yAxisOptions: Required<YAxisOptions>;
labelFormatter: LabelFormatter;
percentageFormatter: (value: number) => string;
}

Expand All @@ -32,8 +32,8 @@ export function Tooltip({
activeIndex,
dataSeries,
isLast,
yAxisOptions,
tooltipLabels,
labelFormatter,
percentageFormatter,
}: TooltipContentProps) {
const point = dataSeries[activeIndex];
Expand All @@ -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,
},
Expand All @@ -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,
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,16 @@ import {Fragment} from 'react';

export const Default: Story<FunnelChartNextProps> = 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',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,18 +15,16 @@ import type {FunnelChartNextProps} from '../FunnelChartNext';

export const Default: Story<FunnelChartNextProps> = 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',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,11 @@ import {META} from './meta';

export const ZeroValues: Story<FunnelChartNextProps> = 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 = {
Expand All @@ -45,7 +43,7 @@ ZeroValues.args = {
name: 'Conversion rates',
},
],
yAxisOptions: yAxisOptions,
labelFormatter,
tooltipLabels: {
reached: 'Reached this step',
dropped: 'Dropped off',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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,
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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('<Chart />', () => {
Expand Down Expand Up @@ -71,10 +65,8 @@ describe('<Chart />', () => {
<ChartContext.Provider value={mockContext}>
<Chart
{...defaultProps}
xAxisOptions={{
...defaultProps.xAxisOptions,
labelFormatter: customFormatter,
}}
seriesNameFormatter={customFormatter}
labelFormatter={customFormatter}
/>
</ChartContext.Provider>,
);
Expand Down
Loading

0 comments on commit 82dc4b4

Please sign in to comment.