Skip to content

Commit

Permalink
Add trends to FunnelChartNext
Browse files Browse the repository at this point in the history
  • Loading branch information
michaelnesen committed Jan 28, 2025
1 parent a648037 commit 007faea
Show file tree
Hide file tree
Showing 17 changed files with 238 additions and 48 deletions.
2 changes: 1 addition & 1 deletion packages/polaris-viz-core/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ export interface DataGroup {
yAxisOptions?: YAxisOptions;
}

export type Shape = 'Line' | 'Bar';
export type Shape = 'Line' | 'Bar' | 'Circle';

export type LineStyle = 'solid' | 'dotted' | 'dashed';

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,10 @@ import {
TOOLTIP_HEIGHT,
TOOLTIP_WIDTH,
} from './constants';
import type {FunnelChartDataSeries} from './types';

export interface ChartProps {
data: DataSeries[];
data: FunnelChartDataSeries[];
tooltipLabels: {
reached: string;
dropped: string;
Expand All @@ -65,6 +66,8 @@ export function Chart({
const [tooltipIndex, setTooltipIndex] = useState<number | null>(null);
const {containerBounds} = useChartContext();
const dataSeries = data[0].data;
const trends = data[0].metadata?.trends;
console.log('trendsss', trends);
const xValues = dataSeries.map(({key}) => key) as string[];
const yValues = dataSeries.map(({value}) => value) as [number, number];
const sanitizedYValues = yValues.map((value) => Math.max(0, value));
Expand Down Expand Up @@ -164,6 +167,7 @@ export function Chart({
xScale={labelXScale}
shouldApplyScaling={shouldApplyScaling}
renderScaleIconTooltipContent={renderScaleIconTooltipContent}
trends={trends}
/>
</g>

Expand Down Expand Up @@ -239,6 +243,7 @@ export function Chart({
<Tooltip
activeIndex={tooltipIndex}
dataSeries={dataSeries}
trends={trends}
tooltipLabels={tooltipLabels}
labelFormatter={labelFormatter}
percentageFormatter={percentageFormatter}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {ChartContainer} from '../../components/ChartContainer';
import {ChartSkeleton} from '../';

import {Chart} from './Chart';
import type {FunnelChartDataSeries} from './types';

const DEFAULT_LABEL_FORMATTER: LabelFormatter = (value) => `${value}`;
const DEFAULT_TOOLTIP_LABELS = {
Expand All @@ -18,6 +19,7 @@ const DEFAULT_TOOLTIP_LABELS = {
} as const;

export type FunnelChartNextProps = {
data: FunnelChartDataSeries[];
tooltipLabels?: {
reached: string;
dropped: string;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ import {Fragment, useMemo, useState} from 'react';
import type {ScaleBand} from 'd3-scale';
import {estimateStringWidth, useChartContext} from '@shopify/polaris-viz-core';

import {getTrendIndicatorData} from '../../../../utilities/getTrendIndicatorData';
import {TrendIndicator} from '../../../TrendIndicator';
import type {FunnelChartMetaData} from '../../types';
import {LINE_HEIGHT} from '../../../../constants';
import {estimateStringWidthWithOffset} from '../../../../utilities';
import {SingleTextLine} from '../../../Labels';
Expand All @@ -16,6 +19,8 @@ const LABEL_FONT_SIZE = 12;
const PERCENT_FONT_SIZE = 14;
const PERCENT_FONT_WEIGHT = 650;
const VALUE_FONT_SIZE = 11;
const TREND_INDICATOR_SPACING = 8;
const BUFFER_PADDING = 8;

const TEXT_COLOR = 'rgba(31, 33, 36, 1)';
const VALUE_COLOR = 'rgba(97, 97, 97, 1)';
Expand All @@ -28,6 +33,7 @@ export interface FunnelChartLabelsProps {
labelWidth: number;
barWidth: number;
percentages: string[];
trends?: FunnelChartMetaData['trends'];
xScale: ScaleBand<string>;
shouldApplyScaling: boolean;
renderScaleIconTooltipContent?: () => ReactNode;
Expand All @@ -39,6 +45,7 @@ export function FunnelChartLabels({
labelWidth,
barWidth,
percentages,
trends,
xScale,
shouldApplyScaling,
renderScaleIconTooltipContent,
Expand Down Expand Up @@ -76,9 +83,23 @@ export function FunnelChartLabels({
VALUE_FONT_SIZE,
);

const totalPercentAndValueWidth = percentWidth + formattedValueWidth;
const {trendIndicatorProps, trendIndicatorWidth} =
getTrendIndicatorData(trends?.[index]?.reached);

// Position trend indicator at the right edge
const trendIndicatorX = targetWidth - trendIndicatorWidth;

// First check if we can show the trend indicator
const shouldShowTrendIndicator =
trendIndicatorProps != null &&
percentWidth + TREND_INDICATOR_SPACING < trendIndicatorX;

// Then check if we can show formatted value without overlapping trend indicator
const availableWidthForValue = shouldShowTrendIndicator
? trendIndicatorX - TREND_INDICATOR_SPACING - BUFFER_PADDING
: targetWidth;
const shouldShowFormattedValue =
totalPercentAndValueWidth < targetWidth;
percentWidth + formattedValueWidth < availableWidthForValue;

return (
<g
Expand Down Expand Up @@ -127,11 +148,16 @@ export function FunnelChartLabels({
text={formattedValues[index]}
targetWidth={targetWidth}
x={percentWidth + LINE_PADDING}
y={1}
y={2}
textAnchor="start"
fontSize={VALUE_FONT_SIZE}
/>
)}
{shouldShowTrendIndicator && (
<g transform={`translate(${trendIndicatorX}, ${-2})`}>
<TrendIndicator {...trendIndicatorProps} />
</g>
)}
</g>
</g>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,33 +1,50 @@
.Rows {
display: grid;
grid-template-columns: 1fr 1fr;
display: flex;
flex-direction: column;
gap: 8px;

> *:not(:last-child)::after {
content: '';
display: block;
width: 255px;
height: 1px;
background: #e3e3e3;
margin: 8px auto 0;
}
}

.Row {
font-size: 12px;
display: grid;
grid-column: 1 / -1;
grid-template-columns: subgrid;
color: rgba(97, 97, 97, 1);
align-items: center;
display: grid;
grid-template-columns: 20px auto;
}

.Keys {
display: flex;
grid-column: 1 / -1;
display: grid;
grid-template-columns: subgrid;
align-items: center;
gap: 4px;
}

.Values {
display: flex;
align-items: center;
justify-content: flex-end;
gap: 4px;
align-items: flex-start;
gap: 8px;
grid-column: 2;
font-weight: 600;
padding-top: 2px;

> span {
flex: 0 0 auto;
}

.TrendIndicator {
margin-left: auto;
}

strong {
font-weight: 600;
font-weight: 500;
color: rgba(31, 33, 36, 1);
}
}
Original file line number Diff line number Diff line change
@@ -1,19 +1,23 @@
import {Fragment} from 'react';
import type {Color, DataPoint, LabelFormatter} from '@shopify/polaris-viz-core';
import {DEFAULT_THEME_NAME} from '@shopify/polaris-viz-core';
import {DEFAULT_THEME_NAME, useTheme} from '@shopify/polaris-viz-core';

import {getTrendIndicatorData} from '../../../../utilities/getTrendIndicatorData';
import type {FunnelChartMetaData} from '../../types';
import {TOOLTIP_WIDTH} from '../../constants';
import {FUNNEL_CHART_CONNECTOR_GRADIENT} from '../../../shared/FunnelChartConnector';
import {FUNNEL_CHART_SEGMENT_FILL} from '../../../shared/FunnelChartSegment';
import {SeriesIcon} from '../../../shared/SeriesIcon';
import {calculateDropOff} from '../../utilities/calculate-dropoff';
import {TooltipContentContainer, TooltipTitle} from '../../../TooltipContent';
import {TrendIndicator} from '../../../TrendIndicator';

import styles from './Tooltip.scss';

export interface TooltipContentProps {
activeIndex: number;
dataSeries: DataPoint[];
trends: FunnelChartMetaData['trends'];
tooltipLabels: {
reached: string;
dropped: string;
Expand All @@ -32,10 +36,13 @@ interface Data {
export function Tooltip({
activeIndex,
dataSeries,
trends,
tooltipLabels,
labelFormatter,
percentageFormatter,
}: TooltipContentProps) {
const selectedTheme = useTheme(DEFAULT_THEME_NAME);

const point = dataSeries[activeIndex];
const previousPoint = dataSeries[activeIndex - 1];
const isFirst = activeIndex === 0;
Expand Down Expand Up @@ -65,12 +72,14 @@ export function Tooltip({
return (
<TooltipContentContainer
maxWidth={TOOLTIP_WIDTH}
minWidth={TOOLTIP_WIDTH}
theme={DEFAULT_THEME_NAME}
>
{() => (
<Fragment>
<TooltipTitle
theme={DEFAULT_THEME_NAME}
color={selectedTheme.tooltip.textColor}
aria-label={`Step: ${point.key}`}
>
{point.key}
Expand All @@ -81,21 +90,34 @@ export function Tooltip({
percent,
)}`;

const {trendIndicatorProps} = getTrendIndicatorData(
index === 0
? trends?.[activeIndex]?.reached
: trends?.[activeIndex]?.dropped,
);

return (
<div
className={styles.Row}
key={`row-${index}-${key}`}
aria-label={ariaLabel}
>
<div className={styles.Keys}>
<SeriesIcon color={color!} shape="Bar" />
<SeriesIcon color={color!} shape="Circle" />
<span>{key}</span>
</div>
<div className={styles.Values}>
<span>{value}</span>
<span>
<strong>{value}</strong>
</span>
<span>
<strong>{percentageFormatter(percent)}</strong>
</span>
{trendIndicatorProps && (
<div className={styles.TrendIndicator}>
<TrendIndicator {...trendIndicatorProps} />
</div>
)}
</div>
</div>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ export const SEGMENT_WIDTH_RATIO = 0.75;
export const TOOLTIP_HORIZONTAL_OFFSET = 10;
export const LINE_OFFSET = 3;
export const LINE_WIDTH = 1;
export const TOOLTIP_HEIGHT = 90;
export const TOOLTIP_HEIGHT = 130;
export const SHORT_TOOLTIP_HEIGHT = 65;
export const GAP = 1;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,35 +4,11 @@ export {META as default} from './meta';

import type {FunnelChartNextProps} from '../FunnelChartNext';

import {DEFAULT_DATA, Template} from './data';
import {Fragment} from 'react';
import {DEFAULT_DATA, Template, DEFAULT_PROPS} from './data';

export const Default: Story<FunnelChartNextProps> = Template.bind({});

const labelFormatter = (value) => {
return new Intl.NumberFormat('en', {
style: 'decimal',
maximumFractionDigits: 2,
}).format(Number(value));
};

const percentageFormatter = (value) => `${labelFormatter(value)}%`;

Default.args = {
data: DEFAULT_DATA,
tooltipLabels: {
reached: 'Reached this step',
dropped: 'Dropped off',
},
showTooltip: true,
labelFormatter,
percentageFormatter,
renderScaleIconTooltipContent: () => (
<Fragment>
<div>Truncated Sessions</div>{' '}
<p style={{color: 'black', fontSize: '12px', lineHeight: '12px'}}>
Sessions were drawn to scale to better represent the funnel
</p>
</Fragment>
),
...DEFAULT_PROPS,
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import type {Story} from '@storybook/react';
import type {DataSeries} from '@shopify/polaris-viz-core';

import type {FunnelChartNextProps} from '../FunnelChartNext';
import {DEFAULT_DATA, DEFAULT_PROPS, Template} from './data';
import {META} from './meta';

export default META;

const DATA_WITH_TRENDS: DataSeries[] = [
{
...DEFAULT_DATA[0],
metadata: {
trends: {
0: {
reached: {
value: '10%',
trend: 'positive',
direction: 'upward',
},
dropped: {
value: '-25%',
trend: 'negative',
direction: 'downward',
},
},
1: {
reached: {
value: '30%',
trend: 'positive',
direction: 'upward',
},
dropped: {
value: '-10%',
trend: 'negative',
direction: 'downward',
},
},
},
},
},
];

export const WithTrendIndicators: Story<FunnelChartNextProps> = Template.bind(
{},
);

WithTrendIndicators.args = {
data: DATA_WITH_TRENDS,
...DEFAULT_PROPS,
};
Loading

0 comments on commit 007faea

Please sign in to comment.