Interaction
Interaction
Making charts respond to the mouse. Chartlyx ships two tooltip components (one for Cartesian, one for polar), plus the raw hover primitives for anything custom.
Tooltip
Drop <Tooltip> as the last child of
<ChartContainer>. It renders an invisible overlay
that captures mouse events, finds the nearest data point, and calls
your render function with the point’s pixel position and datum.
<Tooltip<Row> indicatorStroke="#5eead4" dotFill="#5eead4">
{({ x, y, datum }) => (
<g transform={`translate(${x + 12}, ${y - 50})`}>
<rect width={140} height={44} rx={6} fill="#0f172a" stroke="#334155" />
<text x={12} y={20} fontSize={11} fill="#94a3b8">{datum.month}</text>
<text x={12} y={36} fontSize={13} fill="#5eead4">${datum.revenue}</text>
</g>
)}
</Tooltip> Placing the tooltip last matters: SVG paints and hit-tests in document order, so the invisible rect ends up on top and catches every pointer event cleanly.
PolarTooltip
The polar equivalent. Reads the currently-hovered slice or polygon from
the polar context and passes it to your render function. HTML content
via <foreignObject> works well for multi-line pie
callouts.
<PolarTooltip<Row>>
{({ datum, cx, cy }) => (
<foreignObject x={cx - 65} y={cy - 28} width={130} height={56}>
<div className="tooltip-card">
{datum.category}: ${datum.revenue}
</div>
</foreignObject>
)}
</PolarTooltip> Nearest-point lookup
For linear and time scales, Chartlyx uses d3-array’s
bisector: an O(log n) binary search
to locate the nearest data point to the mouse. Even with 100,000
points, lookup stays sub-microsecond.
Band scales fall back to an O(n) pixel-distance loop
because there is no meaningful pixel-to-category inverse. Category
counts are usually small, so this is fine in practice.
Mouse position is read via
event.clientX - rect.left against the overlay’s
bounding rect: a cross-browser reliable pattern that avoids
offsetX quirks on SVG in Firefox.
Custom hover UI
For scenarios where <Tooltip> is too opinionated,
you can build your own by combining a plain SVG rect with the chart
context. This lets you build crosshairs, multi-series callouts, or
highlight overlays.
function Crosshair() {
const { data, xScale, yScale, width, height, margin } = useChartContext();
const [active, setActive] = useState<number | null>(null);
return (
<>
{active !== null && (
<line
x1={xScale(data[active].month)}
x2={xScale(data[active].month)}
y1={margin.top}
y2={height - margin.bottom}
stroke="#5eead4"
strokeDasharray="4 4"
/>
)}
<rect
x={margin.left} y={margin.top}
width={width - margin.left - margin.right}
height={height - margin.top - margin.bottom}
fill="transparent"
onMouseMove={/* ... find nearest index ... */}
onMouseLeave={() => setActive(null)}
/>
</>
);
}
A public useHoverState hook is on the roadmap for
consumers who want the nearest-point math without building the pointer
overlay from scratch. Open an issue if you need it now.