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 => (
))}
)
}