{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "charts",
  "type": "registry:ui",
  "title": "Charts",
  "description": "Line, bar, distribution and sparkline on the chart primitives with the mark specs applied.",
  "registryDependencies": [
    "@wibo/chart"
  ],
  "files": [
    {
      "path": "src/components/charts.tsx",
      "type": "registry:ui",
      "content": "import type { ReactNode } from \"react\";\nimport { ChartContainer, ChartDataTable, ChartLegend, ChartLegendContent, ChartTooltip, ChartTooltipContent, Recharts, seriesColor, type ChartConfig } from \"@/components/ui/chart\";\n\n/**\n * The four ready-made charts of the design system (line, bar, distribution,\n * sparkline), each on the chart primitives with the mark specs applied: 2 px\n * lines, 8 px hover markers, 4 px rounded bar ends anchored to the baseline,\n * a 2 px surface gap between adjacent bars, recessive grid, a crosshair\n * tooltip, a legend for two or more series, and a hidden data table.\n */\ninterface SeriesChartProps<T extends Record<string, unknown>> {\n  data: T[];\n  /** The category or time key. */\n  xKey: keyof T & string;\n  /** Series keys with their labels; colours follow the slot order. */\n  series: ChartConfig;\n  title?: string;\n  description?: string;\n  height?: number;\n  /** Formats values in tooltip, axis and the hidden table (currency, percent). */\n  formatValue?: (value: number | string, key: string) => ReactNode;\n  /** Width of the value axis in pixels (44 by default): wider for long tick labels such as costs with four decimals. */\n  valueAxisWidth?: number;\n  formatLabel?: (label: ReactNode) => ReactNode;\n  className?: string;\n}\n\nfunction LineChart<T extends Record<string, unknown>>({ data, xKey, series, title, description, height, formatValue, formatLabel, valueAxisWidth = 44, className }: SeriesChartProps<T>) {\n  const keys = Object.keys(series);\n  return (\n    <ChartContainer config={series} title={title} description={description} height={height} className={className} table={<ChartDataTable data={data} xKey={xKey} config={series} caption={title} format={formatValue ? (v, k) => formatValue(v as number, k) : undefined} />}>\n      <Recharts.LineChart data={data} margin={{ top: 8, right: 8, bottom: 0, left: 0 }}>\n        <Recharts.CartesianGrid vertical={false} strokeDasharray=\"0\" />\n        <Recharts.XAxis dataKey={xKey as string} tickLine={false} axisLine={false} tickMargin={8} minTickGap={24} tickFormatter={formatLabel ? (v) => String(formatLabel(v)) : undefined} />\n        <Recharts.YAxis width={valueAxisWidth} tickLine={false} axisLine={false} tickMargin={4} tickFormatter={formatValue ? (v) => String(formatValue(v, keys[0] ?? \"\")) : undefined} />\n        <ChartTooltip cursor={{ strokeWidth: 1 }} content={<ChartTooltipContent valueFormatter={formatValue} labelFormatter={formatLabel ? (l) => formatLabel(l) : undefined} />} />\n        {keys.length > 1 ? <ChartLegend content={<ChartLegendContent />} /> : null}\n        {keys.map((key) => (\n          <Recharts.Line key={key} type=\"monotone\" dataKey={key} stroke={`var(--color-${key})`} strokeWidth={2} dot={false} activeDot={{ r: 4, strokeWidth: 2, stroke: \"var(--card)\" }} isAnimationActive={false} />\n        ))}\n      </Recharts.LineChart>\n    </ChartContainer>\n  );\n}\n\ninterface BarChartProps<T extends Record<string, unknown>> extends SeriesChartProps<T> {\n  /** Stack the series instead of grouping them. */\n  stacked?: boolean;\n  /** Horizontal bars (long category labels). */\n  horizontal?: boolean;\n}\n\nfunction BarChart<T extends Record<string, unknown>>({ data, xKey, series, title, description, height, formatValue, formatLabel, valueAxisWidth = 44, stacked, horizontal, className }: BarChartProps<T>) {\n  const keys = Object.keys(series);\n  const category = <Recharts.XAxis dataKey={xKey as string} tickLine={false} axisLine={false} tickMargin={8} minTickGap={16} tickFormatter={formatLabel ? (v) => String(formatLabel(v)) : undefined} />;\n  const value = <Recharts.YAxis width={valueAxisWidth} tickLine={false} axisLine={false} tickMargin={4} tickFormatter={formatValue ? (v) => String(formatValue(v, keys[0] ?? \"\")) : undefined} />;\n  return (\n    <ChartContainer config={series} title={title} description={description} height={height} className={className} table={<ChartDataTable data={data} xKey={xKey} config={series} caption={title} format={formatValue ? (v, k) => formatValue(v as number, k) : undefined} />}>\n      <Recharts.BarChart data={data} layout={horizontal ? \"vertical\" : \"horizontal\"} margin={{ top: 8, right: 8, bottom: 0, left: 0 }} barCategoryGap=\"20%\" barGap={2}>\n        <Recharts.CartesianGrid vertical={Boolean(horizontal)} horizontal={!horizontal} />\n        {horizontal ? (\n          <>\n            <Recharts.XAxis type=\"number\" tickLine={false} axisLine={false} tickMargin={4} tickFormatter={formatValue ? (v) => String(formatValue(v, keys[0] ?? \"\")) : undefined} />\n            <Recharts.YAxis type=\"category\" dataKey={xKey as string} width={96} tickLine={false} axisLine={false} />\n          </>\n        ) : (\n          <>\n            {category}\n            {value}\n          </>\n        )}\n        <ChartTooltip cursor={{ fill: \"var(--foreground)\", fillOpacity: 0.04 }} content={<ChartTooltipContent valueFormatter={formatValue} labelFormatter={formatLabel ? (l) => formatLabel(l) : undefined} />} />\n        {keys.length > 1 ? <ChartLegend content={<ChartLegendContent />} /> : null}\n        {keys.map((key, i) => (\n          <Recharts.Bar key={key} dataKey={key} fill={`var(--color-${key})`} stackId={stacked ? \"stack\" : undefined} radius={stacked && i < keys.length - 1 ? 0 : horizontal ? [0, 4, 4, 0] : [4, 4, 0, 0]} stroke=\"var(--card)\" strokeWidth={stacked ? 2 : 0} maxBarSize={48} isAnimationActive={false} />\n        ))}\n      </Recharts.BarChart>\n    </ChartContainer>\n  );\n}\n\ninterface DistributionChartProps {\n  /** Ordered bins with their counts, for example rating buckets or duration ranges. */\n  bins: { label: string; count: number; /** A reference share or count drawn as a line, for example the expected distribution. */ reference?: number }[];\n  title?: string;\n  description?: string;\n  height?: number;\n  countLabel?: ReactNode;\n  referenceLabel?: ReactNode;\n  className?: string;\n}\n\n/** One-hue histogram: magnitude only, so a single sequential colour; an optional reference curve as a thin line. */\nfunction DistributionChart({ bins, title, description, height = 200, countLabel = \"Count\", referenceLabel = \"Reference\", className }: DistributionChartProps) {\n  const hasReference = bins.some((b) => b.reference !== undefined);\n  const series: ChartConfig = { count: { label: countLabel, color: seriesColor(1) }, ...(hasReference ? { reference: { label: referenceLabel, color: \"var(--muted-foreground)\" } } : {}) };\n  return (\n    <ChartContainer config={series} title={title} description={description} height={height} className={className} table={<ChartDataTable data={bins} xKey=\"label\" config={series} caption={title} />}>\n      <Recharts.ComposedChart data={bins} margin={{ top: 8, right: 8, bottom: 0, left: 0 }} barCategoryGap=\"12%\">\n        <Recharts.CartesianGrid vertical={false} />\n        <Recharts.XAxis dataKey=\"label\" tickLine={false} axisLine={false} tickMargin={8} />\n        <Recharts.YAxis width={36} tickLine={false} axisLine={false} tickMargin={4} allowDecimals={false} />\n        <ChartTooltip cursor={{ fill: \"var(--foreground)\", fillOpacity: 0.04 }} content={<ChartTooltipContent />} />\n        {hasReference ? <ChartLegend content={<ChartLegendContent />} /> : null}\n        <Recharts.Bar dataKey=\"count\" fill=\"var(--color-count)\" radius={[4, 4, 0, 0]} maxBarSize={64} isAnimationActive={false} />\n        {hasReference ? <Recharts.Line type=\"monotone\" dataKey=\"reference\" stroke=\"var(--color-reference)\" strokeWidth={2} strokeDasharray=\"4 3\" dot={false} isAnimationActive={false} /> : null}\n      </Recharts.ComposedChart>\n    </ChartContainer>\n  );\n}\n\ninterface SparklineProps {\n  values: number[];\n  /** Accessible summary, for example \"Cost per day, last 30 days\". */\n  label: string;\n  width?: number;\n  height?: number;\n  /** Token colour; defaults to the first chart hue. */\n  color?: string;\n  className?: string;\n}\n\n/** A bare trend line for a stat card: a plain SVG polyline, no axes, grid or tooltip; the number next to it carries the value. */\nfunction Sparkline({ values, label, width = 96, height = 28, color = seriesColor(1), className }: SparklineProps) {\n  const min = Math.min(...values);\n  const max = Math.max(...values);\n  const span = max - min || 1;\n  const step = values.length > 1 ? (width - 4) / (values.length - 1) : 0;\n  const points = values.map((v, i) => `${(2 + i * step).toFixed(1)},${(height - 2 - ((v - min) / span) * (height - 4)).toFixed(1)}`).join(\" \");\n  return (\n    <svg role=\"img\" aria-label={label} width={width} height={height} viewBox={`0 0 ${width} ${height}`} className={className}>\n      <title>{label}</title>\n      {values.length > 0 ? <polyline points={points} fill=\"none\" stroke={color} strokeWidth={2} strokeLinejoin=\"round\" strokeLinecap=\"round\" /> : null}\n    </svg>\n  );\n}\n\nexport { LineChart, BarChart, DistributionChart, Sparkline };\nexport type { SeriesChartProps, BarChartProps, DistributionChartProps, SparklineProps };\n"
    }
  ]
}
