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.