Skip to content
This repository was archived by the owner on Jun 6, 2025. It is now read-only.

Commit cdc1788

Browse files
committed
refactor solution
1 parent a873470 commit cdc1788

File tree

2 files changed

+95
-34
lines changed

2 files changed

+95
-34
lines changed

packages/polaris-viz/src/components/FunnelChartNext/components/FunnelChartLabels/FunnelChartLabels.tsx

Lines changed: 93 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ const LABEL_FONT_SIZE = 12;
1919
const VALUE_FONT_SIZE = 14;
2020
const VALUE_FONT_WEIGHT = 650;
2121
const TREND_INDICATOR_SPACING = 8;
22+
const VERTICAL_STACK_SPACING = 3;
23+
const MIN_CHART_WIDTH_FOR_RULE_3_PRIORITY = 400;
2224
export const LABEL_VERTICAL_OFFSET = 2;
2325

2426
const TEXT_COLOR = 'rgba(31, 33, 36, 1)';
@@ -38,6 +40,12 @@ export interface FunnelChartLabelsProps {
3840
renderScaleIconTooltipContent?: () => ReactNode;
3941
}
4042

43+
const LAYOUT_STRATEGY = {
44+
ONE_LINE_ALL: 'one_line_all',
45+
ONE_LINE_COUNTS_AND_TRENDS: 'one_line_counts_and_trends',
46+
VERTICAL_STACKING: 'vertical_stacking',
47+
} as const;
48+
4149
export function FunnelChartLabels({
4250
formattedValues,
4351
labels,
@@ -49,28 +57,24 @@ export function FunnelChartLabels({
4957
shouldApplyScaling,
5058
renderScaleIconTooltipContent,
5159
}: FunnelChartLabelsProps) {
52-
const {characterWidths} = useChartContext();
60+
const {characterWidths, containerBounds} = useChartContext();
61+
const chartContainerWidth = containerBounds?.width ?? 0;
5362
const [showTooltip, setShowTooltip] = useState(false);
5463

5564
const labelFontSize = useMemo(() => {
5665
const maxLabelWidth = Math.max(
5766
...labels.map((label) => estimateStringWidth(label, characterWidths)),
5867
);
59-
6068
return maxLabelWidth > labelWidth ? REDUCED_FONT_SIZE : LABEL_FONT_SIZE;
6169
}, [labels, characterWidths, labelWidth]);
6270

6371
const {layoutStrategy} = useMemo(() => {
64-
let allCanFitRule1 = true;
65-
let allCanFitRule2 = true;
66-
let anyTrendIndicatorExists = false;
67-
68-
for (let i = 0; i < labels.length; i++) {
72+
// Check if all items can fit in one Main Percentage, Counts, and TI (if present) on a single line.
73+
const canAllItemsFitRule1 = labels.every((_, i) => {
6974
const isLast = i === labels.length - 1;
7075
const currentTargetWidth = isLast
7176
? barWidth - GROUP_OFFSET * 2
7277
: labelWidth - GROUP_OFFSET * 2;
73-
7478
const currentPercentWidth = estimateStringWidthWithOffset(
7579
percentages[i],
7680
VALUE_FONT_SIZE,
@@ -85,34 +89,70 @@ export function FunnelChartLabels({
8589
trendIndicatorWidth: currentTrendWidth,
8690
trendIndicatorProps: currentTrendProps,
8791
} = getTrendIndicatorData(trends?.[i]?.reached);
88-
if (currentTrendProps) anyTrendIndicatorExists = true;
8992

90-
// Check Rule 1 for current item
91-
const canItemFitRule1 =
93+
return (
9294
currentPercentWidth +
9395
LINE_PADDING +
9496
currentCountStringWidth +
9597
(currentTrendProps
9698
? TREND_INDICATOR_SPACING + currentTrendWidth
9799
: 0) <
98-
currentTargetWidth;
100+
currentTargetWidth
101+
);
102+
});
103+
104+
if (canAllItemsFitRule1) {
105+
// All elements on one line
106+
return {layoutStrategy: LAYOUT_STRATEGY.ONE_LINE_ALL};
107+
}
99108

100-
if (!canItemFitRule1) allCanFitRule1 = false;
109+
// If chart width is very narrow, prioritize full stacking.
110+
if (chartContainerWidth < MIN_CHART_WIDTH_FOR_RULE_3_PRIORITY) {
111+
return {layoutStrategy: LAYOUT_STRATEGY.VERTICAL_STACKING};
112+
}
113+
114+
// Check if all items can fit Rule 2: Main Percentage (L1), Counts + TI (L2, side-by-side with space).
115+
const canAllItemsFitRule2 = labels.every((_, i) => {
116+
const isLast = i === labels.length - 1;
117+
const currentTargetWidth = isLast
118+
? barWidth - GROUP_OFFSET * 2
119+
: labelWidth - GROUP_OFFSET * 2;
120+
const currentCountStringWidth = estimateStringWidthWithOffset(
121+
formattedValues[i],
122+
VALUE_FONT_SIZE,
123+
VALUE_FONT_WEIGHT,
124+
);
125+
const {
126+
trendIndicatorWidth: currentTrendWidth,
127+
trendIndicatorProps: currentTrendProps,
128+
} = getTrendIndicatorData(trends?.[i]?.reached);
101129

102-
// Check Rule 2 for current item (only relevant if Rule 1 might fail for this item or others)
103-
const canItemFitRule2 =
104-
currentCountStringWidth + (currentTrendProps ? currentTrendWidth : 0) < // No TREND_INDICATOR_SPACING for Rule 2 adjacency
105-
currentTargetWidth; // Rule 2 checks fit on a *new line*, so full currentTargetWidth is available
130+
return (
131+
currentCountStringWidth +
132+
(currentTrendProps
133+
? TREND_INDICATOR_SPACING + currentTrendWidth
134+
: 0) <
135+
currentTargetWidth
136+
);
137+
});
106138

107-
if (!canItemFitRule2) allCanFitRule2 = false;
139+
if (canAllItemsFitRule2) {
140+
// Main% (L1), Counts + TI (L2, side-by-side with space)
141+
return {layoutStrategy: LAYOUT_STRATEGY.ONE_LINE_COUNTS_AND_TRENDS};
108142
}
109143

110-
if (allCanFitRule1) return {layoutStrategy: 'rule1'};
111-
if (allCanFitRule2) return {layoutStrategy: 'rule2'};
112-
// If neither Rule 1 nor Rule 2 can be applied globally
113-
// Rule 3: MainPerc on L1, Counts on L2, TI on L3 (if exists)
114-
return {layoutStrategy: 'rule3plusCounts'};
115-
}, [labels, percentages, formattedValues, trends, labelWidth, barWidth]);
144+
// Fall back to vertical stacking.
145+
// Main% (L1), Counts (L2), TI (L3)
146+
return {layoutStrategy: LAYOUT_STRATEGY.VERTICAL_STACKING};
147+
}, [
148+
labels,
149+
percentages,
150+
formattedValues,
151+
trends,
152+
labelWidth,
153+
barWidth,
154+
chartContainerWidth,
155+
]);
116156

117157
return (
118158
<Fragment>
@@ -172,20 +212,23 @@ export function FunnelChartLabels({
172212
x={showScaleIcon ? 20 : 0}
173213
/>
174214

215+
{/* Group for Main Percentage, Counts, and Trend Indicator */}
175216
<g transform={`translate(0,${LINE_HEIGHT + LINE_GAP})`}>
176217
<SingleTextLine
177218
color={TEXT_COLOR}
178219
text={percentages[index]}
179220
targetWidth={
180-
layoutStrategy === 'rule1' ? percentWidth : currentTargetWidth
221+
layoutStrategy === LAYOUT_STRATEGY.ONE_LINE_ALL
222+
? percentWidth
223+
: currentTargetWidth
181224
}
182225
textAnchor="start"
183226
fontSize={VALUE_FONT_SIZE}
184227
fontWeight={VALUE_FONT_WEIGHT}
185228
/>
186229

187230
{(() => {
188-
if (layoutStrategy === 'rule1') {
231+
if (layoutStrategy === LAYOUT_STRATEGY.ONE_LINE_ALL) {
189232
return (
190233
<Fragment>
191234
<SingleTextLine
@@ -214,9 +257,15 @@ export function FunnelChartLabels({
214257
)}
215258
</Fragment>
216259
);
217-
} else if (layoutStrategy === 'rule2') {
260+
} else if (
261+
layoutStrategy === LAYOUT_STRATEGY.ONE_LINE_COUNTS_AND_TRENDS
262+
) {
218263
return (
219-
<g transform={`translate(0, ${LINE_HEIGHT})`}>
264+
<g
265+
transform={`translate(0, ${
266+
LINE_HEIGHT + VERTICAL_STACK_SPACING
267+
})`}
268+
>
220269
<SingleTextLine
221270
color={VALUE_COLOR}
222271
text={formattedValues[index]}
@@ -232,17 +281,25 @@ export function FunnelChartLabels({
232281
/>
233282
{trendIndicatorProps && (
234283
<g
235-
transform={`translate(${countStringWidth}, ${-LABEL_VERTICAL_OFFSET})`}
284+
transform={`translate(${
285+
countStringWidth + TREND_INDICATOR_SPACING
286+
}, ${-LABEL_VERTICAL_OFFSET})`}
236287
>
237288
<TrendIndicator {...trendIndicatorProps} />
238289
</g>
239290
)}
240291
</g>
241292
);
242-
} else if (layoutStrategy === 'rule3plusCounts') {
293+
} else if (
294+
layoutStrategy === LAYOUT_STRATEGY.VERTICAL_STACKING
295+
) {
243296
return (
244297
<Fragment>
245-
<g transform={`translate(0, ${LINE_HEIGHT})`}>
298+
<g
299+
transform={`translate(0, ${
300+
LINE_HEIGHT + VERTICAL_STACK_SPACING
301+
})`}
302+
>
246303
<SingleTextLine
247304
color={VALUE_COLOR}
248305
text={formattedValues[index]}
@@ -254,7 +311,11 @@ export function FunnelChartLabels({
254311
/>
255312
</g>
256313
{trendIndicatorProps && (
257-
<g transform={`translate(0, ${LINE_HEIGHT * 2})`}>
314+
<g
315+
transform={`translate(0, ${
316+
LINE_HEIGHT * 2 + VERTICAL_STACK_SPACING * 2
317+
})`}
318+
>
258319
<g
259320
transform={`translate(0, ${-LABEL_VERTICAL_OFFSET})`}
260321
>

packages/polaris-viz/src/components/FunnelChartNext/components/FunnelChartLabels/tests/FunnelChartLabels.test.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ describe('<FunnelChartLabels />', () => {
9898
const component = wrapper(propsWithNarrowWidth);
9999
const texts = component.findAll(SingleTextLine);
100100

101-
expect(texts).toHaveLength(6);
101+
expect(texts).toHaveLength(9);
102102

103103
// Verify labels and percentages are still shown
104104
expect(component).toContainReactComponent(SingleTextLine, {
@@ -159,7 +159,7 @@ describe('<FunnelChartLabels />', () => {
159159
barWidth: 50,
160160
});
161161

162-
expect(component).toContainReactComponentTimes('g', 3, {
162+
expect(component).toContainReactComponentTimes('g', 0, {
163163
transform: expect.stringContaining(`${LINE_HEIGHT}`),
164164
});
165165
});

0 commit comments

Comments
 (0)