{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "composed-chart",
  "title": "Composed Chart",
  "description": "Mix areas, lines, and bars in one chart with dual axes, reference lines, peak markers, and hatched bars.",
  "dependencies": [
    "recharts"
  ],
  "registryDependencies": [
    "chart"
  ],
  "files": [
    {
      "path": "registry/dashboardcn/ui/composed-chart.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\nimport {\n  Area,\n  Bar,\n  CartesianGrid,\n  ComposedChart as RechartsComposedChart,\n  Line,\n  ReferenceDot,\n  ReferenceLine,\n  XAxis,\n  YAxis,\n} from \"recharts\"\n\nimport { cn } from \"@/lib/utils\"\nimport {\n  ChartContainer,\n  ChartLegend,\n  ChartLegendContent,\n  ChartTooltip,\n  ChartTooltipContent,\n  type ChartConfig,\n} from \"@/components/ui/chart\"\n\nexport interface ComposedSeries {\n  /** Key in each data row. */\n  key: string\n  label: string\n  type?: \"area\" | \"line\" | \"bar\"\n  /** Any CSS color. Defaults to chart-1 through chart-5 in order. */\n  color?: string\n  /** Plot against the right axis. */\n  axis?: \"left\" | \"right\"\n  /** Series with the same stackId stack on top of each other. */\n  stackId?: string\n  /** Bars only: fill with a diagonal hatch instead of a solid color. */\n  pattern?: \"solid\" | \"hatched\"\n  /** Lines and areas only. */\n  dashed?: boolean\n  curve?: \"monotone\" | \"linear\" | \"step\"\n  /** Lines only: draw a dot on every point. */\n  dots?: boolean\n  /** Mark the highest point of this series with a dot. */\n  highlightMax?: boolean\n}\n\nexport interface ReferenceLineSpec {\n  /** Horizontal line at this y value. */\n  y?: number\n  /** Vertical line at this x value. */\n  x?: string | number\n  label?: string\n  /** Any CSS color. Defaults to the muted foreground. */\n  color?: string\n  dashed?: boolean\n  axis?: \"left\" | \"right\"\n}\n\nexport interface ComposedChartProps\n  extends Omit<React.ComponentProps<typeof ChartContainer>, \"config\" | \"children\"> {\n  data: Record<string, unknown>[]\n  /** Key of the x-axis value in each row. */\n  xKey: string\n  series: ComposedSeries[]\n  referenceLines?: ReferenceLineSpec[]\n  showGrid?: boolean\n  showYAxis?: boolean\n  showLegend?: boolean\n  showTooltip?: boolean\n  xFormatter?: (value: unknown) => string\n  yFormatter?: (value: number) => string\n  rightYFormatter?: (value: number) => string\n  barRadius?: number\n  /** Animate series after hydration and when their data changes. Respects reduced motion. */\n  animate?: boolean\n  animationDuration?: number\n  /** \"zero\" starts the y-axis at 0; \"auto\" fits it to the data. */\n  yDomain?: \"zero\" | \"auto\" | [number, number]\n}\n\nconst reducedMotionQuery = \"(prefers-reduced-motion: reduce)\"\n\nfunction subscribeToReducedMotion(onChange: () => void) {\n  const query = window.matchMedia(reducedMotionQuery)\n  query.addEventListener(\"change\", onChange)\n  return () => query.removeEventListener(\"change\", onChange)\n}\n\nfunction getReducedMotionSnapshot() {\n  return window.matchMedia(reducedMotionQuery).matches\n}\n\n/** The server snapshot disables motion so hydration starts from identical markup. */\nfunction getReducedMotionServerSnapshot() {\n  return true\n}\n\nfunction defaultXFormatter(value: unknown) {\n  if (typeof value === \"string\" && /^\\d{4}-\\d{2}-\\d{2}/.test(value)) {\n    const [y, m, d] = value.split(\"-\").map(Number)\n    return new Date(y!, m! - 1, d).toLocaleDateString(\"en-US\", {\n      month: \"short\",\n      day: \"numeric\",\n    })\n  }\n  return String(value)\n}\n\nfunction ComposedChart({\n  data,\n  xKey,\n  series,\n  referenceLines = [],\n  showGrid = true,\n  showYAxis = false,\n  showLegend = false,\n  showTooltip = true,\n  xFormatter = defaultXFormatter,\n  yFormatter,\n  rightYFormatter,\n  barRadius = 4,\n  animate = true,\n  animationDuration = 500,\n  yDomain = \"zero\",\n  className,\n  ...props\n}: ComposedChartProps) {\n  const id = React.useId()\n  const reducedMotion = React.useSyncExternalStore(\n    subscribeToReducedMotion,\n    getReducedMotionSnapshot,\n    getReducedMotionServerSnapshot\n  )\n  const config = Object.fromEntries(\n    series.map((s, index) => [\n      s.key,\n      { label: s.label, color: s.color ?? `var(--chart-${(index % 5) + 1})` },\n    ])\n  ) satisfies ChartConfig\n  const hasRightAxis = series.some((s) => s.axis === \"right\")\n  const domain =\n    yDomain === \"auto\" ? ([\"auto\", \"auto\"] as const) : yDomain === \"zero\" ? undefined : yDomain\n\n  const maxPoints = series\n    .filter((s) => s.highlightMax)\n    .map((s) => {\n      let best: { x: unknown; y: number } | null = null\n      for (const row of data) {\n        const y = Number(row[s.key])\n        if (Number.isFinite(y) && (!best || y > best.y)) best = { x: row[xKey], y }\n      }\n      return best ? { ...best, key: s.key, axis: s.axis ?? \"left\" } : null\n    })\n    .filter((p): p is NonNullable<typeof p> => p !== null)\n\n  return (\n    <ChartContainer\n      data-slot=\"composed-chart\"\n      config={config}\n      className={cn(\"aspect-auto h-64 w-full\", className)}\n      {...props}\n    >\n      <RechartsComposedChart\n        data={data}\n        margin={{ top: 8, right: hasRightAxis ? 0 : 8, bottom: 0, left: showYAxis ? 0 : 8 }}\n      >\n        <defs>\n          {series.map((s) => (\n            <React.Fragment key={s.key}>\n              <linearGradient id={`${id}-fill-${s.key}`} x1=\"0\" y1=\"0\" x2=\"0\" y2=\"1\">\n                <stop offset=\"5%\" stopColor={`var(--color-${s.key})`} stopOpacity={0.5} />\n                <stop offset=\"95%\" stopColor={`var(--color-${s.key})`} stopOpacity={0.05} />\n              </linearGradient>\n              <pattern\n                id={`${id}-hatch-${s.key}`}\n                patternUnits=\"userSpaceOnUse\"\n                width={6}\n                height={6}\n                patternTransform=\"rotate(45)\"\n              >\n                <rect width={6} height={6} fill={`var(--color-${s.key})`} fillOpacity={0.15} />\n                <rect width={2} height={6} fill={`var(--color-${s.key})`} />\n              </pattern>\n            </React.Fragment>\n          ))}\n        </defs>\n        {showGrid ? <CartesianGrid vertical={false} /> : null}\n        <XAxis\n          dataKey={xKey}\n          tickLine={false}\n          axisLine={false}\n          tickMargin={8}\n          minTickGap={32}\n          tickFormatter={xFormatter}\n        />\n        <YAxis\n          yAxisId=\"left\"\n          domain={domain}\n          hide={!showYAxis}\n          tickLine={false}\n          axisLine={false}\n          tickMargin={8}\n          width={44}\n          tickFormatter={yFormatter}\n        />\n        {hasRightAxis ? (\n          <YAxis\n            yAxisId=\"right\"\n            orientation=\"right\"\n            domain={domain}\n            hide={!showYAxis}\n            tickLine={false}\n            axisLine={false}\n            tickMargin={8}\n            width={44}\n            tickFormatter={rightYFormatter ?? yFormatter}\n          />\n        ) : null}\n        {showTooltip ? (\n          <ChartTooltip\n            cursor={false}\n            content={\n              <ChartTooltipContent\n                indicator=\"dot\"\n                labelFormatter={(value) => xFormatter(value)}\n                formatter={(value, name, item) => {\n                  const s = series.find((entry) => entry.key === name)\n                  const fmt = s?.axis === \"right\" ? (rightYFormatter ?? yFormatter) : yFormatter\n                  return (\n                    <div className=\"flex w-full items-center gap-2\">\n                      <span\n                        className=\"size-2 shrink-0 rounded-[2px]\"\n                        style={{ backgroundColor: item.color }}\n                      />\n                      <span className=\"text-muted-foreground flex-1\">\n                        {config[name as string]?.label ?? name}\n                      </span>\n                      <span className=\"font-mono font-medium tabular-nums\">\n                        {fmt ? fmt(Number(value)) : String(value)}\n                      </span>\n                    </div>\n                  )\n                }}\n              />\n            }\n          />\n        ) : null}\n        {showLegend ? <ChartLegend content={<ChartLegendContent />} /> : null}\n        {series.map((s) => {\n          const color = `var(--color-${s.key})`\n          const common = {\n            dataKey: s.key,\n            yAxisId: s.axis ?? \"left\",\n            stackId: s.stackId,\n            isAnimationActive: animate && !reducedMotion,\n            animationDuration,\n            animationEasing: \"ease-out\" as const,\n          }\n          if (s.type === \"bar\") {\n            return (\n              <Bar\n                key={s.key}\n                {...common}\n                fill={s.pattern === \"hatched\" ? `url(#${id}-hatch-${s.key})` : color}\n                stroke={s.pattern === \"hatched\" ? color : undefined}\n                strokeWidth={s.pattern === \"hatched\" ? 1 : 0}\n                radius={s.stackId ? 0 : barRadius}\n              />\n            )\n          }\n          if (s.type === \"line\") {\n            return (\n              <Line\n                key={s.key}\n                {...common}\n                type={s.curve ?? \"monotone\"}\n                stroke={color}\n                strokeWidth={2}\n                strokeDasharray={s.dashed ? \"4 4\" : undefined}\n                dot={s.dots ? { r: 3, fill: color, strokeWidth: 0 } : false}\n                activeDot={{ r: 4 }}\n              />\n            )\n          }\n          return (\n            <Area\n              key={s.key}\n              {...common}\n              type={s.curve ?? \"monotone\"}\n              stroke={color}\n              strokeWidth={2}\n              strokeDasharray={s.dashed ? \"4 4\" : undefined}\n              fill={`url(#${id}-fill-${s.key})`}\n              dot={false}\n            />\n          )\n        })}\n        {referenceLines.map((line, index) => (\n          <ReferenceLine\n            key={index}\n            y={line.y}\n            x={line.x}\n            yAxisId={line.axis ?? \"left\"}\n            stroke={line.color ?? \"var(--muted-foreground)\"}\n            strokeDasharray={line.dashed === false ? undefined : \"4 4\"}\n            label={\n              line.label\n                ? {\n                    value: line.label,\n                    position: line.y !== undefined ? \"insideTopRight\" : \"insideTopLeft\",\n                    fill: line.color ?? \"var(--muted-foreground)\",\n                    fontSize: 11,\n                  }\n                : undefined\n            }\n          />\n        ))}\n        {maxPoints.map((point) => (\n          <ReferenceDot\n            key={point.key}\n            x={point.x as string | number}\n            y={point.y}\n            yAxisId={point.axis}\n            r={4}\n            fill={`var(--color-${point.key})`}\n            stroke=\"var(--background)\"\n            strokeWidth={2}\n          />\n        ))}\n      </RechartsComposedChart>\n    </ChartContainer>\n  )\n}\n\nexport { ComposedChart }\n",
      "type": "registry:ui"
    }
  ],
  "type": "registry:ui"
}