import { cn } from "cn" import { PolarAngleAxis, PolarGrid, PolarRadiusAxis, Radar, RadarChart, useChartWidth } from "recharts" import { ChartContainer, ChartLegend, ChartLegendContent, ChartTooltip, ChartTooltipContent } from "./chart" import { seriesToConfig, type ChartSeries } from "./series" import { fitText, textWidth } from "./text-fit" const LABEL_SIZE = 12 const LABEL_LINE = 14 /** Up to two lines: words fill the first line, the rest goes on the second line, shortened with an ellipsis if still too long. */ function wrapLabel(text: string, maxWidth: number): string[] { if (maxWidth <= 0) return [] if (textWidth(text, LABEL_SIZE) <= maxWidth) return [text] const words = text.split(/\s+/).filter(Boolean) let count = 0 let first = "" while (count < words.length) { const next = first ? `${first} ${words[count]}` : words[count] if (textWidth(next, LABEL_SIZE) > maxWidth) break first = next count += 1 } if (!first || count >= words.length) return [fitText(text, maxWidth, LABEL_SIZE)].filter(Boolean) return [first, fitText(words.slice(count).join(" "), maxWidth, LABEL_SIZE)].filter(Boolean) } type TickProps = { x?: number | string; y?: number | string; textAnchor?: "start" | "middle" | "end"; payload?: { value?: string | number } } /** Axis name as a custom label: wrapped to two lines and shortened to the room between the axis end and the chart edge. */ function RadarAxisLabel({ x, y, textAnchor = "middle", payload }: TickProps) { const width = useChartWidth() const px = Number(x) const py = Number(y) if (!Number.isFinite(px) || !Number.isFinite(py) || !width) return null const room = textAnchor === "start" ? width - px - 4 : textAnchor === "end" ? px - 4 : Math.min(px, width - px) * 2 - 8 const lines = wrapLabel(String(payload?.value ?? ""), room) if (lines.length === 0) return null // Lines grow away from the chart: upwards above the plot, downwards below, centered beside it. const first = py - ((lines.length - 1) * LABEL_LINE) / 2 return ( {String(payload?.value ?? "")} {lines.map((line, i) => ( {line} ))} ) } export type HMRadarChartProps = { data: ReadonlyArray> /** Field of each data row used as the axis name. */ categoryKey: string series: readonly ChartSeries[] /** Fill each series area (default). `false` draws only the lines. */ filled?: boolean /** Dot on every axis of every series. */ dots?: boolean /** Axis names as custom labels: wrapped to two lines, shortened to the room at the chart edge (full name in the label's title and the tooltip). */ customLabels?: boolean /** Upper end of the value axis, shared by every series (the axis starts at 0). Default 100. */ maxValue?: number /** Accessible name of the chart. */ label: string className?: string } /** Radar chart (shadcn Chart + Recharts): semi-transparent areas, outlines, optional dots, lines-only mode and custom axis labels. */ export function HMRadarChart({ data, categoryKey, series, filled = true, dots = false, customLabels = false, maxValue = 100, label, className }: HMRadarChartProps) { return ( []} outerRadius="62%"> } /> : undefined} /> } /> {series.map(item => ( ))} ) }