Skip to content

Commit 3eb7768

Browse files
authored
fix(flamegraph): Better positioning of flamegraph tooltip (#87789)
The tooltip on the flamegraph was always being positioned below the cursor. This is problematic if the frame is near the bottom of the screen as it'll position the tooltip off screen. This positions the tooltip above the cursor when the cursor is on the bottom half of the screen.
1 parent 89b55ac commit 3eb7768

17 files changed

+54
-128
lines changed

static/app/components/profiling/boundTooltip.tsx

Lines changed: 30 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,7 @@ const WIDTH_OFFSET = 8;
3131
function computeBestTooltipPlacement(
3232
cursor: vec2,
3333
tooltip: DOMRect,
34-
canvas: Rect,
35-
container: Rect
34+
canvas: FlamegraphCanvas
3635
): string {
3736
// This is because the cursor's origin is in the top left corner of the arrow, so we want
3837
// to offset it just enough so that the tooltip does not overlap with the arrow's tail.
@@ -41,39 +40,44 @@ function computeBestTooltipPlacement(
4140
const cursorLeft = cursor[0];
4241
const cursorTop = cursor[1];
4342

44-
// Cursor is relative to canvas, not container
45-
const cursorRelativeToContainer = cursorLeft + canvas.x;
43+
const canvasBounds = canvas.canvas.getBoundingClientRect();
4644

4745
let left =
48-
cursorRelativeToContainer > container.width / 2
49-
? cursorLeft - tooltip.width
50-
: cursorLeft + CURSOR_LEFT_OFFSET_PX;
51-
52-
const right = left + tooltip.width + canvas.left;
53-
54-
if (left + canvas.left - WIDTH_OFFSET <= 0) {
55-
left = -canvas.left + WIDTH_OFFSET;
56-
} else if (right >= container.width - WIDTH_OFFSET) {
57-
left = container.width - tooltip.width - canvas.left - WIDTH_OFFSET;
46+
// Cursor is relative to canvasBounds, not window
47+
cursorLeft + canvasBounds.left > window.innerWidth / 2
48+
? cursorLeft - tooltip.width // place tooltip to the right of cursor
49+
: cursorLeft + CURSOR_LEFT_OFFSET_PX; // placetooltip on the left of cursor
50+
51+
if (
52+
// the tooltip is overflowing on the left
53+
left + canvasBounds.left < WIDTH_OFFSET ||
54+
// the tooltip is overflowing on the right
55+
left + canvasBounds.left + tooltip.width >= window.innerWidth - WIDTH_OFFSET
56+
) {
57+
// align the tooltip to the left edge, this means it's still possible that we
58+
// overflow on the right in some cases
59+
left = -canvasBounds.left + WIDTH_OFFSET;
5860
}
5961

60-
return `translate(${left}px, ${cursorTop + CURSOR_TOP_OFFSET_PX}px)`;
62+
const top =
63+
// Cursor is relative to canvasBounds, not window
64+
cursorTop + canvasBounds.top > window.innerHeight / 2
65+
? cursorTop - tooltip.height // place tooltip above cursor
66+
: cursorTop + CURSOR_TOP_OFFSET_PX; // place tooltip below cursor
67+
68+
return `translate(${left}px, ${top}px)`;
6169
}
6270

6371
interface BoundTooltipProps {
6472
canvas: FlamegraphCanvas;
65-
canvasBounds: Rect;
6673
canvasView: CanvasView<any>;
6774
cursor: vec2;
6875
children?: React.ReactNode;
69-
containerBounds?: Rect;
7076
}
7177

7278
const DEFAULT_BOUNDS = Rect.Empty();
7379

7480
function BoundTooltip({
75-
containerBounds,
76-
canvasBounds,
7781
canvas,
7882
cursor,
7983
canvasView,
@@ -94,18 +98,11 @@ function BoundTooltip({
9498
canvas.physicalToLogicalSpace
9599
);
96100

97-
const containerBoundsRef = useRef<Rect>(containerBounds ?? DEFAULT_BOUNDS);
98-
99-
if (containerBounds) {
100-
containerBoundsRef.current = containerBounds;
101-
} else if (containerBoundsRef.current.isEmpty()) {
102-
const bodyRect = document.body.getBoundingClientRect();
103-
containerBoundsRef.current = new Rect(
104-
bodyRect.x,
105-
bodyRect.y,
106-
bodyRect.width,
107-
bodyRect.height
108-
);
101+
const containerBoundsRef = useRef<Rect>(DEFAULT_BOUNDS);
102+
103+
if (containerBoundsRef.current.isEmpty()) {
104+
// using the innerWidth and innerHeight here because we only want the size of the visible portions
105+
containerBoundsRef.current = new Rect(0, 0, window.innerWidth, window.innerHeight);
109106
}
110107

111108
const sizeCache = useRef<{size: DOMRect; value: React.ReactNode} | null>(null);
@@ -129,12 +126,11 @@ function BoundTooltip({
129126
node.style.transform = computeBestTooltipPlacement(
130127
logicalSpaceCursor,
131128
sizeCache.current.size,
132-
canvasBounds,
133-
containerBoundsRef.current
129+
canvas
134130
);
135131
});
136132
},
137-
[canvasBounds, logicalSpaceCursor, children]
133+
[canvas, logicalSpaceCursor, children]
138134
);
139135

140136
return (

static/app/components/profiling/flamegraph/aggregateFlamegraph.tsx

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,7 @@ export function AggregateFlamegraph(props: AggregateFlamegraphProps): ReactEleme
153153
return [flamegraphCanvasRef, flamegraphOverlayCanvasRef];
154154
}, [flamegraphCanvasRef, flamegraphOverlayCanvasRef]);
155155

156-
const flamegraphCanvasBounds = useResizeCanvasObserver(
156+
useResizeCanvasObserver(
157157
flamegraphCanvases,
158158
props.canvasPoolManager,
159159
flamegraphCanvas,
@@ -225,7 +225,6 @@ export function AggregateFlamegraph(props: AggregateFlamegraphProps): ReactEleme
225225
disableCallOrderSort
226226
disableColorCoding
227227
scheduler={props.scheduler}
228-
canvasBounds={flamegraphCanvasBounds}
229228
canvasPoolManager={props.canvasPoolManager}
230229
flamegraph={flamegraph}
231230
flamegraphRenderer={flamegraphRenderer}

static/app/components/profiling/flamegraph/continuousFlamegraph.tsx

Lines changed: 7 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1194,18 +1194,13 @@ export function ContinuousFlamegraph(): ReactElement {
11941194
return [spansCanvasRef];
11951195
}, [spansCanvasRef]);
11961196

1197-
const spansCanvasBounds = useResizeCanvasObserver(
1198-
spansCanvases,
1199-
canvasPoolManager,
1200-
spansCanvas,
1201-
spansView
1202-
);
1197+
useResizeCanvasObserver(spansCanvases, canvasPoolManager, spansCanvas, spansView);
12031198

12041199
const uiFramesCanvases = useMemo(() => {
12051200
return [uiFramesCanvasRef];
12061201
}, [uiFramesCanvasRef]);
12071202

1208-
const uiFramesCanvasBounds = useResizeCanvasObserver(
1203+
useResizeCanvasObserver(
12091204
uiFramesCanvases,
12101205
canvasPoolManager,
12111206
uiFramesCanvas,
@@ -1216,7 +1211,7 @@ export function ContinuousFlamegraph(): ReactElement {
12161211
return [batteryChartCanvasRef];
12171212
}, [batteryChartCanvasRef]);
12181213

1219-
const batteryChartCanvasBounds = useResizeCanvasObserver(
1214+
useResizeCanvasObserver(
12201215
batteryChartCanvases,
12211216
canvasPoolManager,
12221217
batteryChartCanvas,
@@ -1227,7 +1222,7 @@ export function ContinuousFlamegraph(): ReactElement {
12271222
return [cpuChartCanvasRef];
12281223
}, [cpuChartCanvasRef]);
12291224

1230-
const cpuChartCanvasBounds = useResizeCanvasObserver(
1225+
useResizeCanvasObserver(
12311226
cpuChartCanvases,
12321227
canvasPoolManager,
12331228
cpuChartCanvas,
@@ -1237,7 +1232,8 @@ export function ContinuousFlamegraph(): ReactElement {
12371232
const memoryChartCanvases = useMemo(() => {
12381233
return [memoryChartCanvasRef];
12391234
}, [memoryChartCanvasRef]);
1240-
const memoryChartCanvasBounds = useResizeCanvasObserver(
1235+
1236+
useResizeCanvasObserver(
12411237
memoryChartCanvases,
12421238
canvasPoolManager,
12431239
memoryChartCanvas,
@@ -1248,7 +1244,7 @@ export function ContinuousFlamegraph(): ReactElement {
12481244
return [flamegraphCanvasRef, flamegraphOverlayCanvasRef];
12491245
}, [flamegraphCanvasRef, flamegraphOverlayCanvasRef]);
12501246

1251-
const flamegraphCanvasBounds = useResizeCanvasObserver(
1247+
useResizeCanvasObserver(
12521248
flamegraphCanvases,
12531249
canvasPoolManager,
12541250
flamegraphCanvas,
@@ -1451,7 +1447,6 @@ export function ContinuousFlamegraph(): ReactElement {
14511447
hasUIFrames ? (
14521448
<FlamegraphUIFrames
14531449
status={profiles.type}
1454-
canvasBounds={uiFramesCanvasBounds}
14551450
canvasPoolManager={canvasPoolManager}
14561451
setUIFramesCanvasRef={setUIFramesCanvasRef}
14571452
uiFramesCanvasRef={uiFramesCanvasRef}
@@ -1469,7 +1464,6 @@ export function ContinuousFlamegraph(): ReactElement {
14691464
chartCanvasRef={batteryChartCanvasRef}
14701465
chartCanvas={batteryChartCanvas}
14711466
setChartCanvasRef={setBatteryChartCanvasRef}
1472-
canvasBounds={batteryChartCanvasBounds}
14731467
chartView={batteryChartView}
14741468
canvasPoolManager={canvasPoolManager}
14751469
chart={batteryChart}
@@ -1491,7 +1485,6 @@ export function ContinuousFlamegraph(): ReactElement {
14911485
chartCanvasRef={memoryChartCanvasRef}
14921486
chartCanvas={memoryChartCanvas}
14931487
setChartCanvasRef={setMemoryChartCanvasRef}
1494-
canvasBounds={memoryChartCanvasBounds}
14951488
chartView={memoryChartView}
14961489
canvasPoolManager={canvasPoolManager}
14971490
chart={memoryChart}
@@ -1517,7 +1510,6 @@ export function ContinuousFlamegraph(): ReactElement {
15171510
chartCanvasRef={cpuChartCanvasRef}
15181511
chartCanvas={cpuChartCanvas}
15191512
setChartCanvasRef={setCpuChartCanvasRef}
1520-
canvasBounds={cpuChartCanvasBounds}
15211513
chartView={cpuChartView}
15221514
canvasPoolManager={canvasPoolManager}
15231515
chart={CPUChart}
@@ -1539,7 +1531,6 @@ export function ContinuousFlamegraph(): ReactElement {
15391531
spans={
15401532
spanChart ? (
15411533
<FlamegraphSpans
1542-
canvasBounds={spansCanvasBounds}
15431534
spanChart={spanChart}
15441535
spansCanvas={spansCanvas}
15451536
spansCanvasRef={spansCanvasRef}
@@ -1572,7 +1563,6 @@ export function ContinuousFlamegraph(): ReactElement {
15721563
<FlamegraphZoomView
15731564
scheduler={scheduler}
15741565
profileGroup={profileGroup}
1575-
canvasBounds={flamegraphCanvasBounds}
15761566
canvasPoolManager={canvasPoolManager}
15771567
flamegraph={flamegraph}
15781568
flamegraphRenderer={flamegraphRenderer}

static/app/components/profiling/flamegraph/deprecatedAggregateFlamegraph.tsx

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -239,7 +239,7 @@ export function DeprecatedAggregateFlamegraph(
239239
return [flamegraphCanvasRef, flamegraphOverlayCanvasRef];
240240
}, [flamegraphCanvasRef, flamegraphOverlayCanvasRef]);
241241

242-
const flamegraphCanvasBounds = useResizeCanvasObserver(
242+
useResizeCanvasObserver(
243243
flamegraphCanvases,
244244
canvasPoolManager,
245245
flamegraphCanvas,
@@ -296,7 +296,6 @@ export function DeprecatedAggregateFlamegraph(
296296
<FlamegraphZoomView
297297
scheduler={scheduler}
298298
profileGroup={profileGroup}
299-
canvasBounds={flamegraphCanvasBounds}
300299
canvasPoolManager={canvasPoolManager}
301300
flamegraph={flamegraph}
302301
flamegraphRenderer={flamegraphRenderer}

static/app/components/profiling/flamegraph/differentialFlamegraph.tsx

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,7 @@ export function DifferentialFlamegraph(props: DifferentialFlamegraphProps): Reac
140140
return [flamegraphCanvasRef, flamegraphOverlayCanvasRef];
141141
}, [flamegraphCanvasRef, flamegraphOverlayCanvasRef]);
142142

143-
const flamegraphCanvasBounds = useResizeCanvasObserver(
143+
useResizeCanvasObserver(
144144
flamegraphCanvases,
145145
props.canvasPoolManager,
146146
flamegraphCanvas,
@@ -181,7 +181,6 @@ export function DifferentialFlamegraph(props: DifferentialFlamegraphProps): Reac
181181
disableGrid
182182
disableCallOrderSort
183183
disableColorCoding
184-
canvasBounds={flamegraphCanvasBounds}
185184
canvasPoolManager={props.canvasPoolManager}
186185
flamegraph={props.differentialFlamegraph}
187186
flamegraphRenderer={flamegraphRenderer}

static/app/components/profiling/flamegraph/flamegraph.tsx

Lines changed: 7 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1155,18 +1155,13 @@ function Flamegraph(): ReactElement {
11551155
return [spansCanvasRef];
11561156
}, [spansCanvasRef]);
11571157

1158-
const spansCanvasBounds = useResizeCanvasObserver(
1159-
spansCanvases,
1160-
canvasPoolManager,
1161-
spansCanvas,
1162-
spansView
1163-
);
1158+
useResizeCanvasObserver(spansCanvases, canvasPoolManager, spansCanvas, spansView);
11641159

11651160
const uiFramesCanvases = useMemo(() => {
11661161
return [uiFramesCanvasRef];
11671162
}, [uiFramesCanvasRef]);
11681163

1169-
const uiFramesCanvasBounds = useResizeCanvasObserver(
1164+
useResizeCanvasObserver(
11701165
uiFramesCanvases,
11711166
canvasPoolManager,
11721167
uiFramesCanvas,
@@ -1177,7 +1172,7 @@ function Flamegraph(): ReactElement {
11771172
return [batteryChartCanvasRef];
11781173
}, [batteryChartCanvasRef]);
11791174

1180-
const batteryChartCanvasBounds = useResizeCanvasObserver(
1175+
useResizeCanvasObserver(
11811176
batteryChartCanvases,
11821177
canvasPoolManager,
11831178
batteryChartCanvas,
@@ -1188,7 +1183,7 @@ function Flamegraph(): ReactElement {
11881183
return [cpuChartCanvasRef];
11891184
}, [cpuChartCanvasRef]);
11901185

1191-
const cpuChartCanvasBounds = useResizeCanvasObserver(
1186+
useResizeCanvasObserver(
11921187
cpuChartCanvases,
11931188
canvasPoolManager,
11941189
cpuChartCanvas,
@@ -1198,7 +1193,8 @@ function Flamegraph(): ReactElement {
11981193
const memoryChartCanvases = useMemo(() => {
11991194
return [memoryChartCanvasRef];
12001195
}, [memoryChartCanvasRef]);
1201-
const memoryChartCanvasBounds = useResizeCanvasObserver(
1196+
1197+
useResizeCanvasObserver(
12021198
memoryChartCanvases,
12031199
canvasPoolManager,
12041200
memoryChartCanvas,
@@ -1209,7 +1205,7 @@ function Flamegraph(): ReactElement {
12091205
return [flamegraphCanvasRef, flamegraphOverlayCanvasRef];
12101206
}, [flamegraphCanvasRef, flamegraphOverlayCanvasRef]);
12111207

1212-
const flamegraphCanvasBounds = useResizeCanvasObserver(
1208+
useResizeCanvasObserver(
12131209
flamegraphCanvases,
12141210
canvasPoolManager,
12151211
flamegraphCanvas,
@@ -1412,7 +1408,6 @@ function Flamegraph(): ReactElement {
14121408
hasUIFrames ? (
14131409
<FlamegraphUIFrames
14141410
status={profiles.type}
1415-
canvasBounds={uiFramesCanvasBounds}
14161411
canvasPoolManager={canvasPoolManager}
14171412
setUIFramesCanvasRef={setUIFramesCanvasRef}
14181413
uiFramesCanvasRef={uiFramesCanvasRef}
@@ -1430,7 +1425,6 @@ function Flamegraph(): ReactElement {
14301425
chartCanvasRef={batteryChartCanvasRef}
14311426
chartCanvas={batteryChartCanvas}
14321427
setChartCanvasRef={setBatteryChartCanvasRef}
1433-
canvasBounds={batteryChartCanvasBounds}
14341428
chartView={batteryChartView}
14351429
canvasPoolManager={canvasPoolManager}
14361430
chart={batteryChart}
@@ -1452,7 +1446,6 @@ function Flamegraph(): ReactElement {
14521446
chartCanvasRef={memoryChartCanvasRef}
14531447
chartCanvas={memoryChartCanvas}
14541448
setChartCanvasRef={setMemoryChartCanvasRef}
1455-
canvasBounds={memoryChartCanvasBounds}
14561449
chartView={memoryChartView}
14571450
canvasPoolManager={canvasPoolManager}
14581451
chart={memoryChart}
@@ -1478,7 +1471,6 @@ function Flamegraph(): ReactElement {
14781471
chartCanvasRef={cpuChartCanvasRef}
14791472
chartCanvas={cpuChartCanvas}
14801473
setChartCanvasRef={setCpuChartCanvasRef}
1481-
canvasBounds={cpuChartCanvasBounds}
14821474
chartView={cpuChartView}
14831475
canvasPoolManager={canvasPoolManager}
14841476
chart={CPUChart}
@@ -1500,7 +1492,6 @@ function Flamegraph(): ReactElement {
15001492
spans={
15011493
spanChart ? (
15021494
<FlamegraphSpans
1503-
canvasBounds={spansCanvasBounds}
15041495
spanChart={spanChart}
15051496
spansCanvas={spansCanvas}
15061497
spansCanvasRef={spansCanvasRef}
@@ -1533,7 +1524,6 @@ function Flamegraph(): ReactElement {
15331524
<FlamegraphZoomView
15341525
scheduler={scheduler}
15351526
profileGroup={profileGroup}
1536-
canvasBounds={flamegraphCanvasBounds}
15371527
canvasPoolManager={canvasPoolManager}
15381528
flamegraph={flamegraph}
15391529
flamegraphRenderer={flamegraphRenderer}

0 commit comments

Comments
 (0)