import { cn } from "cn" import { CartesianGrid, LabelList, Line, LineChart, XAxis, YAxis } from "recharts" import { ChartContainer, ChartLegend, ChartLegendContent, ChartTooltip, ChartTooltipContent } from "./chart" import { POINT_AXIS_PADDING, PointColorTooltip, PointDot, PointValueLabel } from "./line-points" import { seriesToConfig, type ChartSeries } from "./series" export type HMLineChartProps = { data: ReadonlyArray> /** Field of each data row used for the category (X) axis. */ categoryKey: string series: readonly ChartSeries[] /** Line interpolation for every series. `monotone` (default) is a smooth curve that never overshoots the data; `linear` joins the points with straight segments; `step` runs horizontal then vertical between points. */ curve?: "monotone" | "linear" | "step" /** Show the small default Dot on every point (default). `false` draws only the line; the active point and tooltip still appear on hover. Ignored when `pointLabels` or `pointColorKey` draw their own Dots. */ dots?: boolean /** Draw a larger Dot on every point and its value next to it. With several series the higher value is labelled above its Dot and the lower * one below, and labels are thinned on narrow charts (the tooltip always shows every value). */ pointLabels?: boolean /** Per-point colors (CSS colors, e.g. HM palette tokens) for Dots, labels and tooltip indicators; lines and the legend keep the series color. * A string names the row field for the first series; an object maps a series key to its row field. Implies the larger Dot. */ pointColorKey?: string | Readonly> /** Accessible name of the chart. */ label: string className?: string } /** Line chart (shadcn Chart + Recharts) with HM series colors, line-cursor tooltip and legend. */ export function HMLineChart({ data, categoryKey, series, curve = "monotone", dots = true, pointLabels = false, pointColorKey, label, className, }: HMLineChartProps) { const colorKeys: Readonly> = typeof pointColorKey === "string" ? (series[0] ? { [series[0].key]: pointColorKey } : {}) : (pointColorKey ?? {}) const customPoints = pointLabels || Object.keys(colorKeys).length > 0 const seriesKeys = series.map(item => item.key) // A line without Dots keeps the same inner room as the Dot variants, so both align when shown side by side. const roomy = customPoints || !dots return ( []} margin={{ top: roomy ? 24 : 8, right: 12, left: 0, bottom: 0 }} > {/* Inner padding keeps the first and last Dot, label and active point from being clipped at the plot edge. */} 0 ? : } /> } /> {series.map((item, index) => { const color = `var(--color-${item.key})` const colorKey = colorKeys[item.key] return ( : dots ? { r: 3, fill: color, strokeWidth: 0 } : false} activeDot={customPoints ? : { r: 5 }} > {pointLabels ? ( } /> ) : null} ) })} ) }