{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "radial-chart-card",
  "title": "Radial Chart",
  "description": "Concentric rings, gauges, or a stacked half-gauge of parts-to-whole.",
  "registryDependencies": [
    "https://components.exe.xyz/r/chart-card.json"
  ],
  "files": [
    {
      "path": "src/components/charts/radial-chart-card.tsx",
      "content": "import { useState } from \"react\"\n\nimport {\n  ChartCard,\n  ChartTiles,\n  chartColor,\n  describeArc,\n  formatNumber,\n  polar,\n  useActiveRange,\n} from \"@/components/charts/chart-card\"\n\nexport type RadialDatum = { label: string; value: number; color?: string }\n\nconst DEFAULT_DATA: RadialDatum[] = [\n  { label: \"Other\", value: 90 },\n  { label: \"Edge\", value: 173 },\n  { label: \"Firefox\", value: 187 },\n  { label: \"Safari\", value: 200 },\n  { label: \"Chrome\", value: 275 },\n]\n\nfunction RingPlot({\n  data,\n  max,\n  variant,\n  activeId,\n  onActiveChange,\n}: {\n  data: RadialDatum[]\n  max: number\n  variant: \"rings\" | \"labels\" | \"grid\" | \"gauge\" | \"solid\" | \"stacked\"\n  activeId: string | null\n  onActiveChange: (id: string | null) => void\n}) {\n  const cx = 120\n  const cy = 120\n  const total = data.reduce((sum, row) => sum + row.value, 0)\n\n  if (variant === \"gauge\" || variant === \"solid\") {\n    const value = data[0]?.value ?? 0\n    const progress = Math.min(1, value / max)\n    const start = 225\n    const sweep = 270 * progress\n    const track = describeArc(cx, cy, 72, 225, 495)\n    const arc = describeArc(cx, cy, 72, start, start + sweep)\n    const pct = Math.round(progress * 100)\n    return (\n      <svg viewBox=\"0 0 240 240\" className=\"mx-auto h-56 w-56\">\n        {variant === \"solid\" ? (\n          <circle cx={cx} cy={cy} r={88} fill=\"var(--muted)\" />\n        ) : null}\n        <path\n          d={track}\n          fill=\"none\"\n          stroke=\"var(--muted)\"\n          strokeWidth={variant === \"solid\" ? 18 : 12}\n          strokeLinecap=\"butt\"\n        />\n        <path\n          d={arc}\n          fill=\"none\"\n          stroke={chartColor(0, data[0]?.color)}\n          strokeWidth={variant === \"solid\" ? 18 : 12}\n          strokeLinecap=\"butt\"\n        />\n        <text\n          x={cx}\n          y={cy - 4}\n          textAnchor=\"middle\"\n          fill=\"var(--foreground)\"\n          fontSize={28}\n          fontFamily=\"var(--font-heading)\"\n          fontWeight={600}\n        >\n          {pct}%\n        </text>\n        <text\n          x={cx}\n          y={cy + 16}\n          textAnchor=\"middle\"\n          fill=\"var(--muted-foreground)\"\n          fontSize={11}\n          fontFamily=\"var(--font-mono)\"\n        >\n          {formatNumber(value)} / {formatNumber(max)}\n        </text>\n      </svg>\n    )\n  }\n\n  if (variant === \"stacked\") {\n    const start = 180\n    const sweepTotal = 180\n    let cursor = start\n    const hovered = data.find((row) => row.label === activeId) ?? data[0]\n    const share = total === 0 ? 0 : Math.round((hovered.value / total) * 100)\n    return (\n      <svg viewBox=\"0 0 240 200\" className=\"mx-auto h-48 w-56\">\n        <path\n          d={describeArc(cx, 130, 78, 180, 360)}\n          fill=\"none\"\n          stroke=\"var(--muted)\"\n          strokeWidth={16}\n        />\n        {data.map((row, index) => {\n          const slice = total === 0 ? 0 : (row.value / total) * sweepTotal\n          const d = describeArc(cx, 130, 78, cursor, cursor + slice)\n          cursor += slice\n          const dimmed = activeId != null && activeId !== row.label\n          return (\n            <path\n              key={row.label}\n              d={d}\n              fill=\"none\"\n              stroke={chartColor(index, row.color)}\n              strokeWidth={16}\n              opacity={dimmed ? 0.28 : 1}\n              className=\"cursor-pointer\"\n              onMouseEnter={() => onActiveChange(row.label)}\n              onMouseLeave={() => onActiveChange(null)}\n            />\n          )\n        })}\n        <text\n          x={cx}\n          y={118}\n          textAnchor=\"middle\"\n          fill=\"var(--foreground)\"\n          fontSize={24}\n          fontFamily=\"var(--font-heading)\"\n          fontWeight={600}\n        >\n          {share}%\n        </text>\n        <text\n          x={cx}\n          y={138}\n          textAnchor=\"middle\"\n          fill=\"var(--muted-foreground)\"\n          fontSize={11}\n          fontFamily=\"var(--font-mono)\"\n        >\n          {hovered.label}\n        </text>\n      </svg>\n    )\n  }\n\n  const inner = 28\n  const gap = 8\n  const stroke = 9\n  const rings = data.map((row, index) => {\n    const r = inner + index * (stroke + gap)\n    return { ...row, r, color: chartColor(index, row.color) }\n  })\n\n  return (\n    <svg viewBox=\"0 0 240 240\" className=\"mx-auto h-56 w-56\">\n      {variant === \"grid\"\n        ? [40, 56, 72, 88, 104].map((r) => (\n            <circle\n              key={r}\n              cx={cx}\n              cy={cy}\n              r={r}\n              fill=\"none\"\n              stroke=\"var(--border)\"\n              strokeDasharray=\"2 3\"\n            />\n          ))\n        : null}\n      {rings.map((row) => {\n        const progress = Math.min(0.999, row.value / max)\n        const dimmed = activeId != null && activeId !== row.label\n        const end = polar(cx, cy, row.r, progress * 360)\n        return (\n          <g\n            key={row.label}\n            opacity={dimmed ? 0.28 : 1}\n            className=\"cursor-pointer\"\n            onMouseEnter={() => onActiveChange(row.label)}\n            onMouseLeave={() => onActiveChange(null)}\n          >\n            {variant !== \"grid\" ? (\n              <circle\n                cx={cx}\n                cy={cy}\n                r={row.r}\n                fill=\"none\"\n                stroke=\"var(--muted)\"\n                strokeWidth={stroke}\n              />\n            ) : null}\n            <path\n              d={describeArc(cx, cy, row.r, 0, progress * 360)}\n              fill=\"none\"\n              stroke={row.color}\n              strokeWidth={stroke}\n            />\n            {variant === \"labels\" ? (\n              <text\n                x={end.x + 8}\n                y={end.y + 3}\n                fill=\"var(--muted-foreground)\"\n                fontSize={10}\n                fontFamily=\"var(--font-mono)\"\n              >\n                {row.label}\n              </text>\n            ) : null}\n          </g>\n        )\n      })}\n    </svg>\n  )\n}\n\nexport function RadialChartCard({\n  title = \"Visitors\",\n  variant = \"rings\",\n  tiles = true,\n  data,\n  max,\n  headline,\n  delta = 5.2,\n  range,\n  ranges,\n}: {\n  title?: string\n  variant?: \"rings\" | \"labels\" | \"grid\" | \"gauge\" | \"solid\" | \"stacked\"\n  tiles?: boolean\n  data?: RadialDatum[]\n  max?: number\n  headline?: number\n  delta?: number\n  range?: string\n  ranges?: {\n    id: string\n    label: string\n    data: RadialDatum[]\n    max?: number\n    delta?: number\n    headline?: number\n  }[]\n}) {\n  const { id, setId, active } = useActiveRange(ranges, range)\n  const plotData = active?.data ?? data ?? DEFAULT_DATA\n  const largest = Math.max(1, ...plotData.map((row) => row.value))\n  const plotMax = active?.max ?? max ?? Math.round(largest * 1.1)\n  const plotHeadline =\n    active?.headline ??\n    headline ??\n    plotData.reduce((sum, row) => sum + row.value, 0)\n  const plotDelta = active?.delta ?? delta\n  const [activeId, setActiveId] = useState<string | null>(null)\n  const summary = `${title} ${formatNumber(plotHeadline)}. ${plotData\n    .map((row) => `${row.label} ${formatNumber(row.value)}`)\n    .join(\", \")}`\n\n  return (\n    <ChartCard\n      title={title}\n      headline={plotHeadline}\n      delta={plotDelta}\n      period={ranges ? undefined : \"Last 7 days\"}\n      ranges={ranges}\n      range={id}\n      onRangeChange={setId}\n      summary={summary}\n      tiles={\n        tiles ? (\n          <ChartTiles\n            activeId={activeId}\n            onActiveChange={setActiveId}\n            items={plotData.map((row, index) => ({\n              id: row.label,\n              label: row.label,\n              value: formatNumber(row.value),\n              color: chartColor(index, row.color),\n            }))}\n          />\n        ) : undefined\n      }\n    >\n      <div role=\"img\" aria-label={summary}>\n        <RingPlot\n          data={plotData}\n          max={plotMax}\n          variant={variant}\n          activeId={activeId}\n          onActiveChange={setActiveId}\n        />\n      </div>\n    </ChartCard>\n  )\n}\n",
      "type": "registry:component"
    }
  ],
  "type": "registry:block"
}