import React, { useState } from 'react'; import { createPortal } from 'react-dom'; import { useCharts } from './ChartsHook'; import { TooltipBubble } from '../Tooltip/TooltipBubble'; import { useTheme } from '../../core/theme/ThemeProvider'; interface TooltipSeries { key: string; label: string; color: string; accessor: (d: any) => number; } interface ChartTooltipProps { series: TooltipSeries[]; formatX?: (value: any) => string; formatY?: (value: any) => string; } export const ChartTooltip: React.FC = ({ series, formatX = (val) => val.toString(), formatY = (val) => val.toLocaleString(), }) => { const { dataset, xScale, yScale, dimensions, xAccessor } = useCharts(); const { theme } = useTheme(); const [hoveredData, setHoveredData] = useState<{ point: any; x: number; y: number; } | null>(null); const handleMouseMove = (event: React.MouseEvent) => { if (!dataset || dataset.length === 0) return; const { left } = event.currentTarget.getBoundingClientRect(); // The `left` from getBoundingClientRect on the rect already includes the parent SVG's // position and the transform's marginLeft. We just need to subtract it from // the viewport-relative clientX to get the mouse position relative to the bounded area. const mouseX = event.clientX - left; const hoveredXValue = xScale.invert(mouseX); // Find the closest data point by iterating through the data let closestPoint = dataset[0]; let minDiff = Math.abs(xAccessor(dataset[0], 0) - hoveredXValue); for (let i = 1; i < dataset.length; i++) { const diff = Math.abs(xAccessor(dataset[i], i) - hoveredXValue); if (diff < minDiff) { minDiff = diff; closestPoint = dataset[i]; } } setHoveredData({ point: closestPoint, x: event.clientX, y: event.clientY, }); }; const handleMouseLeave = () => { setHoveredData(null); }; const svgX = hoveredData ? xScale(xAccessor(hoveredData.point, 0)) : null; return ( {hoveredData && svgX !== null && ( <> {series.map(({ key, color, accessor }) => { const yValue = accessor(hoveredData.point); if (yValue === null || yValue === undefined) return null; return ( ) })} {createPortal( window.innerWidth / 2 ? '-110%' : '10%'}, -50%)`, }}>
{formatX(xAccessor(hoveredData.point, 0))}
{series.map(({ key, label, color, accessor }) => (
{label}: {formatY(accessor(hoveredData.point))} ))}
, document.body )} )} ); };