Skip to content

Commit ce5644f

Browse files
committed
fix: flip tooltip to left/top when near chart container edges
1 parent b269742 commit ce5644f

2 files changed

Lines changed: 22 additions & 11 deletions

File tree

packages/app/src/hooks/useChartTooltipHandlers.ts

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import * as d3 from 'd3';
22
import { useCallback } from 'react';
3+
import { computeTooltipPosition } from '@/lib/d3-chart/layers/scatter-points';
34
import { useStickyTooltip } from './useStickyTooltip';
45

56
export type RulerType = 'vertical' | 'horizontal' | 'crosshair' | 'none';
@@ -271,9 +272,10 @@ export function useChartTooltipHandlers<TData>(): ChartTooltipHandlers<TData> {
271272
if (isPinned()) return;
272273

273274
const rect = containerElement.getBoundingClientRect();
274-
tooltipElement
275-
.style('left', `${event.clientX - rect.left + 10}px`)
276-
.style('top', `${event.clientY - rect.top + 10}px`);
275+
const mx = event.clientX - rect.left;
276+
const my = event.clientY - rect.top;
277+
const pos = computeTooltipPosition(mx, my, tooltipElement, containerElement);
278+
tooltipElement.style('left', `${pos.left}px`).style('top', `${pos.top}px`);
277279
})
278280
.on('mouseleave', function (_event, d) {
279281
if (isPinned()) return;
@@ -292,15 +294,18 @@ export function useChartTooltipHandlers<TData>(): ChartTooltipHandlers<TData> {
292294
.on('click', function (event, d) {
293295
event.stopPropagation();
294296

295-
// Position tooltip near the clicked point
297+
// Set content first so dimensions are available for position calc
296298
const rect = containerElement.getBoundingClientRect();
299+
const mx = event.clientX - rect.left;
300+
const my = event.clientY - rect.top;
301+
tooltipElement.html(config.generateTooltipContent(d, true));
302+
const pos = computeTooltipPosition(mx, my, tooltipElement, containerElement);
297303
tooltipElement
298-
.style('left', `${event.clientX - rect.left + 10}px`)
299-
.style('top', `${event.clientY - rect.top + 10}px`)
304+
.style('left', `${pos.left}px`)
305+
.style('top', `${pos.top}px`)
300306
.style('opacity', 1)
301307
.style('display', 'block')
302-
.style('pointer-events', 'auto')
303-
.html(config.generateTooltipContent(d, true));
308+
.style('pointer-events', 'auto');
304309

305310
// Position rulers at the clicked point
306311
const { curX: currentXScale, curY: currentYScale } = getZoomedScales();

packages/app/src/lib/d3-chart/layers/scatter-points.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -224,7 +224,7 @@ export function attachScatterTooltipHandlers<
224224
});
225225
}
226226

227-
/** Compute tooltip left/top, flipping to the opposite side when near container edges. */
227+
/** Compute tooltip left/top, flipping when it would overflow the chart container. */
228228
export function computeTooltipPosition(
229229
mx: number,
230230
my: number,
@@ -235,8 +235,14 @@ export function computeTooltipPosition(
235235
offset = 10,
236236
): { left: number; top: number } {
237237
const node = tooltip.node();
238-
const tw = node?.offsetWidth ?? 0;
239-
const th = node?.offsetHeight ?? 0;
238+
if (!node) return { left: mx + offset, top: my + offset };
239+
240+
// Ensure tooltip is measurable
241+
node.style.display = 'block';
242+
243+
// Force reflow so we get real dimensions
244+
const tw = node.getBoundingClientRect().width || node.offsetWidth;
245+
const th = node.getBoundingClientRect().height || node.offsetHeight;
240246
const cw = container.clientWidth;
241247
const ch = container.clientHeight;
242248

0 commit comments

Comments
 (0)