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

Commit dad039b

Browse files
committed
Use a portal for horizontal tooltips
1 parent a648037 commit dad039b

File tree

6 files changed

+80
-87
lines changed

6 files changed

+80
-87
lines changed

packages/polaris-viz/src/components/BarChart/stories/playground/ExternalTooltip.stories.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ export const ExternalTooltipWithFrame: Story<BarChartProps> =
7777
TemplateWithFrame.bind({});
7878

7979
ExternalTooltip.args = {
80+
direction: 'horizontal',
8081
data: [
8182
{
8283
name: 'Apr 1 – Apr 14, 2020',

packages/polaris-viz/src/components/ChartContainer/ChartContainer.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,8 @@ export const ChartContainer = (props: Props) => {
9898
<div
9999
className={styles.ChartContainer}
100100
style={{
101-
background: chartContainer.backgroundColor,
101+
// background: chartContainer.backgroundColor,
102+
background: 'red',
102103
padding,
103104
borderRadius: chartContainer.borderRadius,
104105
}}
@@ -126,6 +127,7 @@ export const ChartContainer = (props: Props) => {
126127
<div
127128
className={styles.Chart}
128129
style={{
130+
background: 'blue',
129131
height: value.containerBounds.height,
130132
width: value.containerBounds.width,
131133
}}

packages/polaris-viz/src/components/HorizontalBarChart/Chart.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,8 @@ export function Chart({
129129
return maxes;
130130
}, [data, areAllNegative]);
131131

132+
console.log({highestValueForSeries});
133+
132134
const {stackedValues, stackedMin, stackedMax} = useHorizontalStackedValues({
133135
isStacked,
134136
data,
@@ -299,11 +301,13 @@ export function Chart({
299301
data={data}
300302
focusElementDataType={DataType.BarGroup}
301303
getMarkup={getTooltipMarkup}
302-
margin={ChartMargin}
304+
margin={{...ChartMargin, Top: chartYPosition}}
303305
parentElement={svgRef}
304306
longestSeriesIndex={longestSeriesIndex}
307+
highestValueForSeries={highestValueForSeries}
305308
xScale={xScale}
306309
type={type}
310+
usePortal
307311
/>
308312
)}
309313

packages/polaris-viz/src/components/TooltipWrapper/TooltipWrapper.tsx

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ interface BaseProps {
3737
xScale: ScaleLinear<number, number> | ScaleBand<string>;
3838
bandwidth?: number;
3939
onIndexChange?: (index: number | null) => void;
40+
highestValueForSeries?: number[];
4041
id?: string;
4142
type?: ChartType;
4243
yScale?: ScaleLinear<number, number>;
@@ -56,6 +57,7 @@ function TooltipWrapperRaw(props: BaseProps) {
5657
type,
5758
xScale,
5859
yScale,
60+
highestValueForSeries,
5961
} = props;
6062
const {scrollContainer, isTouchDevice, containerBounds} = useChartContext();
6163
const [position, setPosition] = useState<TooltipPosition>({
@@ -111,13 +113,19 @@ function TooltipWrapperRaw(props: BaseProps) {
111113
case InternalChartType.HorizontalBar:
112114
return getHorizontalBarChartTooltipPosition({
113115
chartBounds,
116+
containerBounds,
114117
data,
115118
event,
116119
eventType,
117120
index,
118121
longestSeriesIndex,
119122
type,
120123
xScale: xScale as ScaleLinear<number, number>,
124+
highestValueForSeries,
125+
bandwidth,
126+
scrollY: scrollContainer
127+
? scrollContainer.scrollTop
128+
: window.scrollY,
121129
});
122130
case InternalChartType.Bar:
123131
default:
@@ -136,6 +144,8 @@ function TooltipWrapperRaw(props: BaseProps) {
136144
}
137145
},
138146
[
147+
highestValueForSeries,
148+
bandwidth,
139149
chartBounds,
140150
containerBounds,
141151
chartType,
Lines changed: 34 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
import type {BoundingRect, Dimensions} from '@shopify/polaris-viz-core';
2-
import {HORIZONTAL_GROUP_LABEL_HEIGHT} from '@shopify/polaris-viz-core';
1+
import type {Dimensions} from '@shopify/polaris-viz-core';
2+
import {clamp, HORIZONTAL_GROUP_LABEL_HEIGHT} from '@shopify/polaris-viz-core';
33

4-
import {TOOLTIP_MARGIN} from '../constants';
4+
import {SCROLLBAR_WIDTH, TOOLTIP_MARGIN} from '../constants';
55
import type {AlteredPositionProps, AlteredPositionReturn} from '../types';
66

77
export function getAlteredHorizontalBarPosition(
@@ -20,100 +20,59 @@ function getNegativeOffset(props: AlteredPositionProps): AlteredPositionReturn {
2020
const yOffset = (bandwidth - tooltipDimensions.height) / 2;
2121

2222
const y = currentY - tooltipDimensions.height;
23+
2324
if (flippedX - tooltipDimensions.width < 0) {
24-
return {x: flippedX, y: y < 0 ? 0 : y};
25+
return clampPosition({
26+
x: flippedX,
27+
y: y < 0 ? 0 : y,
28+
tooltipDimensions,
29+
});
2530
}
2631

27-
return {
32+
return clampPosition({
2833
x: flippedX - tooltipDimensions.width - TOOLTIP_MARGIN,
2934
y: currentY + HORIZONTAL_GROUP_LABEL_HEIGHT + yOffset,
30-
};
35+
tooltipDimensions,
36+
});
3137
}
3238

3339
function getPositiveOffset(props: AlteredPositionProps): AlteredPositionReturn {
34-
const {bandwidth, currentX, currentY, tooltipDimensions, chartBounds} = props;
40+
const {currentX, currentY} = props;
3541

36-
const isOutside = isOutsideBounds({
42+
return clampPosition({
3743
x: currentX,
3844
y: currentY,
39-
tooltipDimensions,
40-
chartBounds,
45+
tooltipDimensions: props.tooltipDimensions,
4146
});
42-
43-
if (isOutside.top && isOutside.right) {
44-
return {
45-
x: chartBounds.width - tooltipDimensions.width,
46-
y: 0,
47-
};
48-
}
49-
50-
if (isOutside.top && !isOutside.right) {
51-
return {
52-
x: currentX + TOOLTIP_MARGIN,
53-
y: 0,
54-
};
55-
}
56-
57-
if (!isOutside.right && !isOutside.bottom) {
58-
const yOffset = (bandwidth - tooltipDimensions.height) / 2;
59-
return {
60-
x: currentX + TOOLTIP_MARGIN,
61-
y: currentY + HORIZONTAL_GROUP_LABEL_HEIGHT + yOffset,
62-
};
63-
}
64-
65-
if (isOutside.right) {
66-
const x = currentX - tooltipDimensions.width;
67-
const y =
68-
currentY -
69-
tooltipDimensions.height +
70-
HORIZONTAL_GROUP_LABEL_HEIGHT -
71-
TOOLTIP_MARGIN;
72-
73-
if (y < 0) {
74-
return {
75-
x,
76-
y: bandwidth + HORIZONTAL_GROUP_LABEL_HEIGHT + TOOLTIP_MARGIN,
77-
};
78-
}
79-
80-
return {
81-
x,
82-
y,
83-
};
84-
}
85-
86-
if (isOutside.bottom) {
87-
return {
88-
x: currentX + TOOLTIP_MARGIN,
89-
y:
90-
chartBounds.height -
91-
tooltipDimensions.height -
92-
HORIZONTAL_GROUP_LABEL_HEIGHT,
93-
};
94-
}
95-
96-
return {x: currentX, y: currentY};
9747
}
9848

99-
function isOutsideBounds({
49+
function clampPosition({
10050
x,
10151
y,
10252
tooltipDimensions,
103-
chartBounds,
10453
}: {
10554
x: number;
10655
y: number;
10756
tooltipDimensions: Dimensions;
108-
chartBounds: BoundingRect;
10957
}) {
110-
const right = x + TOOLTIP_MARGIN + tooltipDimensions.width;
111-
const bottom = y + tooltipDimensions.height;
112-
11358
return {
114-
left: x <= 0,
115-
right: right > chartBounds.width,
116-
bottom: bottom > chartBounds.height,
117-
top: y <= 0,
59+
x: clamp({
60+
amount: x,
61+
min: TOOLTIP_MARGIN,
62+
max:
63+
window.innerWidth -
64+
tooltipDimensions.width -
65+
TOOLTIP_MARGIN -
66+
SCROLLBAR_WIDTH,
67+
}),
68+
y: clamp({
69+
amount: y,
70+
min: window.scrollY + TOOLTIP_MARGIN,
71+
max:
72+
window.scrollY +
73+
window.innerHeight -
74+
tooltipDimensions.height -
75+
TOOLTIP_MARGIN,
76+
}),
11877
};
11978
}

packages/polaris-viz/src/components/TooltipWrapper/utilities/getHorizontalBarChartTooltipPosition.ts

Lines changed: 27 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,40 @@
11
import type {ScaleLinear} from 'd3-scale';
2+
import type {BoundingRect} from '@shopify/polaris-viz-core';
3+
import {Y_AXIS_CHART_SPACING} from '@shopify/polaris-viz-core';
24

35
import {getStackedValuesFromDataSeries} from '../../../utilities/getStackedValuesFromDataSeries';
46
import type {TooltipPosition, TooltipPositionParams} from '../types';
57
import {TOOLTIP_POSITION_DEFAULT_RETURN} from '../constants';
68

79
import {eventPointNative} from './eventPoint';
810

11+
const SPACING = 10;
12+
913
interface Props extends Omit<TooltipPositionParams, 'xScale'> {
14+
bandwidth: number;
15+
containerBounds: BoundingRect;
16+
highestValueForSeries: number[];
17+
scrollY: number;
1018
xScale: ScaleLinear<number, number>;
1119
}
1220

1321
export function getHorizontalBarChartTooltipPosition({
1422
chartBounds,
23+
containerBounds,
1524
data,
1625
event,
1726
eventType,
1827
index,
1928
longestSeriesIndex,
2029
type,
2130
xScale,
31+
scrollY,
32+
highestValueForSeries,
33+
bandwidth,
2234
}: Props): TooltipPosition {
23-
const groupHeight = chartBounds.height / data[longestSeriesIndex].data.length;
35+
const groupHeight =
36+
(chartBounds.height - Y_AXIS_CHART_SPACING) /
37+
data[longestSeriesIndex].data.length;
2438
const isStacked = type === 'stacked';
2539

2640
if (eventType === 'mouse' && event) {
@@ -32,7 +46,7 @@ export function getHorizontalBarChartTooltipPosition({
3246

3347
const {svgY} = point;
3448

35-
const currentPoint = svgY - 0;
49+
const currentPoint = svgY - scrollY;
3650
const currentIndex = Math.floor(currentPoint / groupHeight);
3751

3852
if (
@@ -42,14 +56,17 @@ export function getHorizontalBarChartTooltipPosition({
4256
return TOOLTIP_POSITION_DEFAULT_RETURN;
4357
}
4458

45-
return formatPositionForTooltip(currentIndex);
59+
return formatPositionForTooltip(currentIndex, containerBounds);
4660
} else if (index != null) {
47-
return formatPositionForTooltip(index);
61+
return formatPositionForTooltip(index, containerBounds);
4862
}
4963

5064
return TOOLTIP_POSITION_DEFAULT_RETURN;
5165

52-
function formatPositionForTooltip(index: number): TooltipPosition {
66+
function formatPositionForTooltip(
67+
index: number,
68+
containerBounds: BoundingRect,
69+
): TooltipPosition {
5370
if (isStacked) {
5471
const {formattedStackedValues} = getStackedValuesFromDataSeries(data);
5572

@@ -64,18 +81,18 @@ export function getHorizontalBarChartTooltipPosition({
6481
}, xScale(0));
6582

6683
return {
67-
x: chartBounds.x + x,
68-
y: chartBounds.y + groupHeight * index,
84+
x: containerBounds.x + x,
85+
y: containerBounds.y + groupHeight * index,
6986
activeIndex: index,
7087
};
7188
}
7289

73-
const highestValue = data[longestSeriesIndex].data[index].value ?? 0;
74-
const x = chartBounds.x + (xScale(highestValue ?? 0) ?? 0);
90+
const highestValue = highestValueForSeries[index] ?? 0;
91+
const x = containerBounds.x + (xScale(highestValue ?? 0) ?? 0) + SPACING;
7592

7693
return {
7794
x: highestValue < 0 ? -x : x,
78-
y: groupHeight * index,
95+
y: containerBounds.y + groupHeight * index - bandwidth * index,
7996
activeIndex: index,
8097
};
8198
}

0 commit comments

Comments
 (0)