Skip to content

Commit

Permalink
Address initial comments
Browse files Browse the repository at this point in the history
  • Loading branch information
michaelnesen committed Dec 16, 2024
1 parent 2e92b22 commit 57f2180
Show file tree
Hide file tree
Showing 17 changed files with 287 additions and 125 deletions.
3 changes: 1 addition & 2 deletions .eslintrc
Original file line number Diff line number Diff line change
Expand Up @@ -81,8 +81,7 @@
"UNSTABLE_telemetry"
]
}
],
"@shopify/strict-component-boundaries": "warn"
]
},
"overrides": [
{
Expand Down
1 change: 0 additions & 1 deletion packages/polaris-viz-core/src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ export const SMALL_CHART_HEIGHT = 125;
export const FONT_SIZE = 11;
export const TOUCH_FONT_SIZE = 12;

export const FONT_WEIGHT = 300;
export const FONT_FAMILY =
'Inter, -apple-system, "system-ui", "San Francisco", "Segoe UI", Roboto, "Helvetica Neue", sans-serif';

Expand Down
1 change: 0 additions & 1 deletion packages/polaris-viz-core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ export {
EMPTY_STATE_CHART_MAX,
EMPTY_STATE_CHART_MIN,
FONT_SIZE,
FONT_WEIGHT,
HORIZONTAL_BAR_LABEL_HEIGHT,
HORIZONTAL_BAR_LABEL_OFFSET,
HORIZONTAL_GROUP_LABEL_HEIGHT,
Expand Down
45 changes: 24 additions & 21 deletions packages/polaris-viz/src/components/FunnelChartNext/Chart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,8 @@ import {
LinearGradientWithStops,
useChartContext,
} from '@shopify/polaris-viz-core';
import {createPortal} from 'react-dom';

import {TOOLTIP_ID} from '../../constants';
import {useFunnelBarScaling} from '../../hooks';
import {useRootContainer} from '../../hooks/useRootContainer';
import {
FunnelChartConnector,
FunnelChartConnectorGradient,
Expand All @@ -24,7 +21,12 @@ import {FunnelChartSegment} from '../shared';
import {SingleTextLine} from '../Labels';
import {ChartElements} from '../ChartElements';

import {FunnelChartXAxisLabels, Tooltip, FunnelTooltip} from './components';
import {
FunnelChartXAxisLabels,
Tooltip,
FunnelTooltip,
TooltipWithPortal,
} from './components';
import type {FunnelChartNextProps} from './FunnelChartNext';
import {
TOOLTIP_WIDTH,
Expand All @@ -37,6 +39,8 @@ import {
GAP,
SHORT_TOOLTIP_HEIGHT,
TOOLTIP_HEIGHT,
SEGMENT_WIDTH_RATIO,
TOOLTIP_HORIZONTAL_OFFSET,
} from './constants';

export interface ChartProps {
Expand All @@ -45,6 +49,8 @@ export interface ChartProps {
xAxisOptions: Required<XAxisOptions>;
yAxisOptions: Required<YAxisOptions>;
enableScaling: boolean;
renderScaleIconTooltipContent?: () => ReactNode;
percentageFormatter?: (value: number) => string;
}

export function Chart({
Expand All @@ -53,6 +59,13 @@ export function Chart({
xAxisOptions,
yAxisOptions,
enableScaling,
renderScaleIconTooltipContent,
percentageFormatter = (value: number) => {
const formattedValue = yAxisOptions.labelFormatter(
isNaN(value) ? 0 : value,
);
return `${formattedValue}%`;
},
}: ChartProps) {
const [tooltipIndex, setTooltipIndex] = useState<number | null>(null);
const {containerBounds} = useChartContext();
Expand Down Expand Up @@ -96,7 +109,7 @@ export function Chart({
.domain(labels.map((_, index) => index.toString()));

const sectionWidth = xScale.bandwidth();
const barWidth = sectionWidth * 0.75;
const barWidth = sectionWidth * SEGMENT_WIDTH_RATIO;
const lineGradientId = useMemo(() => uniqueId('line-gradient'), []);

const lastPoint = dataSeries.at(-1);
Expand All @@ -110,14 +123,14 @@ export function Chart({
? (yAxisValue / firstPoint.value) * 100
: 0;

return formatPercentage(percentCalculation);
return percentageFormatter(percentCalculation);
});

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

const mainPercentage = formatPercentage(
const mainPercentage = percentageFormatter(
((lastPoint?.value ?? 0) / (firstPoint?.value ?? 0)) * 100,
);

Expand Down Expand Up @@ -162,6 +175,7 @@ export function Chart({
percentages={percentages}
xScale={labelXScale}
shouldApplyScaling={shouldApplyScaling}
renderScaleIconTooltipContent={renderScaleIconTooltipContent}
/>
</g>
)}
Expand Down Expand Up @@ -196,7 +210,7 @@ export function Chart({
height={drawableHeight}
index={index}
nextX={
(xScale(nextPoint?.key as string) ?? 0) - LINE_OFFSET
(xScale(nextPoint?.key.toString()) ?? 0) - LINE_OFFSET
}
nextY={drawableHeight - nextBarHeight}
startX={x + barWidth + GAP}
Expand Down Expand Up @@ -250,17 +264,16 @@ export function Chart({
isLast={tooltipIndex === dataSeries.length - 1}
tooltipLabels={tooltipLabels}
yAxisOptions={yAxisOptions}
percentageFormatter={percentageFormatter}
/>
</FunnelTooltip>
);

function getXPosition() {
if (tooltipIndex === 0) {
// Push the tooltip beside the bar
return chartX + barWidth + 10;
return chartX + barWidth + TOOLTIP_HORIZONTAL_OFFSET;
}

// Center the tooltip over the bar
const xOffset = (barWidth - TOOLTIP_WIDTH) / 2;
return chartX + (xScale(activeDataSeries.key.toString()) ?? 0) + xOffset;
}
Expand All @@ -276,14 +289,4 @@ export function Chart({
return yPosition - tooltipHeight;
}
}

function formatPercentage(value: number) {
return `${yAxisOptions.labelFormatter(isNaN(value) ? 0 : value)}%`;
}
}

function TooltipWithPortal({children}: {children: ReactNode}) {
const container = useRootContainer(TOOLTIP_ID);

return createPortal(children, container);
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
ChartState,
useChartContext,
} from '@shopify/polaris-viz-core';
import type {ReactNode} from 'react';

import {ChartContainer} from '../../components/ChartContainer';
import {
Expand All @@ -26,6 +27,8 @@ export type FunnelChartNextProps = {
xAxisOptions?: Pick<XAxisOptions, 'hide'>;
yAxisOptions?: Pick<YAxisOptions, 'labelFormatter'>;
enableScaling?: boolean;
renderScaleIconTooltipContent?: () => ReactNode;
percentageFormatter?: (value: number) => string;
} & ChartProps;

export function FunnelChartNext(props: FunnelChartNextProps) {
Expand All @@ -41,8 +44,10 @@ export function FunnelChartNext(props: FunnelChartNextProps) {
isAnimated,
state,
errorText,
onError,
tooltipLabels,
onError,
renderScaleIconTooltipContent,
percentageFormatter,
} = {
...DEFAULT_CHART_PROPS,
...props,
Expand Down Expand Up @@ -76,6 +81,8 @@ export function FunnelChartNext(props: FunnelChartNextProps) {
xAxisOptions={xAxisOptionsForChart}
yAxisOptions={yAxisOptionsForChart}
enableScaling={enableScaling}
renderScaleIconTooltipContent={renderScaleIconTooltipContent}
percentageFormatter={percentageFormatter}
/>
)}
</ChartContainer>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import {Fragment, useMemo} from 'react';
import type {ReactNode} from 'react';
import {Fragment, useMemo, useState} from 'react';
import type {ScaleBand} from 'd3-scale';
import {estimateStringWidth, useChartContext} from '@shopify/polaris-viz-core';

Expand All @@ -7,6 +8,7 @@ import {estimateStringWidthWithOffset} from '../../../utilities';
import {SingleTextLine} from '../../Labels';

import {ScaleIcon} from './ScaleIcon';
import {ScaleIconTooltip} from './ScaleIconTooltip';

const LINE_GAP = 5;
const LINE_PADDING = 10;
Expand All @@ -28,6 +30,7 @@ export interface FunnelChartXAxisLabelsProps {
percentages: string[];
xScale: ScaleBand<string>;
shouldApplyScaling: boolean;
renderScaleIconTooltipContent?: () => ReactNode;
}

export function FunnelChartXAxisLabels({
Expand All @@ -37,17 +40,17 @@ export function FunnelChartXAxisLabels({
percentages,
xScale,
shouldApplyScaling,
renderScaleIconTooltipContent,
}: FunnelChartXAxisLabelsProps) {
const {characterWidths} = useChartContext();
const [showTooltip, setShowTooltip] = useState(false);
const targetWidth = labelWidth - GROUP_OFFSET * 3;

const labelFontSize = useMemo(() => {
// Find the widest label
const maxLabelWidth = Math.max(
...labels.map((label) => estimateStringWidth(label, characterWidths)),
);

// If any label is too wide, reduce font size for all
return maxLabelWidth > labelWidth ? REDUCED_FONT_SIZE : LABEL_FONT_SIZE;
}, [labels, characterWidths, labelWidth]);

Expand Down Expand Up @@ -80,8 +83,19 @@ export function FunnelChartXAxisLabels({
key={index}
>
{showScaleIcon && (
<g transform="translate(0, -3)">
<g
transform="translate(0, -3)"
onMouseEnter={() => setShowTooltip(true)}
onMouseLeave={() => setShowTooltip(false)}
>
<ScaleIcon />
{showTooltip && renderScaleIconTooltipContent && (
<ScaleIconTooltip
renderScaleIconTooltipContent={
renderScaleIconTooltipContent
}
/>
)}
</g>
)}
<SingleTextLine
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import {DEFAULT_THEME_NAME, useChartContext} from '@shopify/polaris-viz-core';
import type {ReactNode} from 'react';
import {Fragment} from 'react';

import {FunnelTooltip, TooltipWithPortal} from '../components';
import {TooltipContentContainer} from '../../../components/TooltipContent';

const TOOLTIP_VERTICAL_OFFSET = 65;

interface ScaleIconTooltipProps {
renderScaleIconTooltipContent: () => ReactNode;
}

export function ScaleIconTooltip({
renderScaleIconTooltipContent,
}: ScaleIconTooltipProps) {
const {containerBounds} = useChartContext();
const {x, y} = containerBounds ?? {
x: 0,
y: 0,
};

return (
<TooltipWithPortal>
<FunnelTooltip x={x} y={y - TOOLTIP_VERTICAL_OFFSET}>
<TooltipContentContainer
maxWidth={200}
theme={DEFAULT_THEME_NAME}
color="white"
>
{() => <Fragment>{renderScaleIconTooltipContent()}</Fragment>}
</TooltipContentContainer>
</FunnelTooltip>
</TooltipWithPortal>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export interface TooltipContentProps {
isLast: boolean;
tooltipLabels: FunnelChartNextProps['tooltipLabels'];
yAxisOptions: Required<YAxisOptions>;
percentageFormatter: (value: number) => string;
}

interface Data {
Expand All @@ -33,6 +34,7 @@ export function Tooltip({
isLast,
yAxisOptions,
tooltipLabels,
percentageFormatter,
}: TooltipContentProps) {
const point = dataSeries[activeIndex];
const nextPoint = dataSeries[activeIndex + 1];
Expand Down Expand Up @@ -81,7 +83,7 @@ export function Tooltip({
<span>{value}</span>
{!isLast && (
<span>
<strong>{formatPercentage(percent)}</strong>
<strong>{percentageFormatter(percent)}</strong>
</span>
)}
</div>
Expand All @@ -93,8 +95,4 @@ export function Tooltip({
)}
</TooltipContentContainer>
);

function formatPercentage(value: number) {
return `${yAxisOptions.labelFormatter(isNaN(value) ? 0 : value)}%`;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import {createPortal} from 'react-dom';
import type {ReactNode} from 'react';

import {useRootContainer} from '../../../hooks/useRootContainer';
import {TOOLTIP_ID} from '../../../constants';

export function TooltipWithPortal({children}: {children: ReactNode}) {
const container = useRootContainer(TOOLTIP_ID);

return createPortal(children, container);
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export {FunnelChartXAxisLabels} from './FunnelChartXAxisLabels';
export {Tooltip} from './Tooltip';
export {FunnelTooltip} from './FunnelTooltip';
export {TooltipWithPortal} from './TooltipWithPortal';
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
export const FUNNEL_CONNECTOR_Y_OFFSET = 30;
export const TOOLTIP_WIDTH = 250;

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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export {META as default} from './meta';
import type {FunnelChartNextProps} from '../FunnelChartNext';

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

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

Expand All @@ -24,5 +25,12 @@ Default.args = {
reached: 'Reached this step',
dropped: 'Dropped off',
},
enableScaling: true,
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>
),
};
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ export const Template: Story<FunnelChartNextProps> = (
args: FunnelChartNextProps,
) => {
return (
<div style={{height: 400}}>
<div style={{height: 400, marginTop: 20}}>
<FunnelChartNext {...args} />
</div>
);
Expand Down
Loading

0 comments on commit 57f2180

Please sign in to comment.