{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "dot-plot",
  "title": "Dot Plot",
  "description": "A distribution drawn as columns of stacked dots, with the peak columns at full strength.",
  "files": [
    {
      "path": "registry/dashboardcn/ui/dot-plot.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\n\nimport { cn } from \"@/lib/utils\"\n\nexport interface DotPlotProps extends React.ComponentProps<\"div\"> {\n  /** One value per column, in order. */\n  data: number[]\n  /** Optional label per column, shown in the tooltip and used for the accessible name. */\n  labels?: string[]\n  /** The value that fills a column. Defaults to the largest value in `data`. */\n  max?: number\n  /** Number of dots in a full column. */\n  rows?: number\n  /** Any CSS color. Defaults to chart-1. */\n  color?: string\n  /**\n   * Columns at or above this fraction of `max` are drawn at full strength and the\n   * rest are faded, so the peak stands out. Set to 0 to draw every column at full strength.\n   */\n  emphasis?: number\n  /** Opacity of the faded columns. */\n  fadedOpacity?: number\n  /** Show a tooltip with the column's label and value while it is hovered. */\n  showTooltip?: boolean\n  /** Formats the value in the tooltip. Defaults to a plain grouped number. */\n  valueFormatter?: (value: number) => string\n  /** Index of the hovered column, to control it from outside. */\n  activeIndex?: number | null\n  /** Called when the hovered column changes, with null when the pointer leaves. */\n  onActiveIndexChange?: (index: number | null) => void\n}\n\nconst defaultFormatter = (value: number) => value.toLocaleString(\"en-US\")\n\n/**\n * A distribution drawn as columns of stacked dots, with the peak columns at full strength.\n * Hovering a column lights it up, fades the others, and shows its value.\n */\nfunction DotPlot({\n  data,\n  labels,\n  max,\n  rows = 6,\n  color = \"var(--chart-1)\",\n  emphasis = 0.5,\n  fadedOpacity = 0.35,\n  showTooltip = true,\n  valueFormatter = defaultFormatter,\n  activeIndex,\n  onActiveIndexChange,\n  className,\n  ...props\n}: DotPlotProps) {\n  const [internal, setInternal] = React.useState<number | null>(null)\n  const active = activeIndex === undefined ? internal : activeIndex\n  const setActive = (index: number | null) => {\n    setInternal(index)\n    onActiveIndexChange?.(index)\n  }\n\n  const top = max ?? Math.max(0, ...data)\n  const peak = data.indexOf(Math.max(...data))\n  const peakLabel = labels?.[peak]\n\n  return (\n    <div\n      data-slot=\"dot-plot\"\n      role=\"img\"\n      aria-label={\n        peakLabel !== undefined\n          ? `Distribution across ${data.length} points, peaking at ${peakLabel}`\n          : `Distribution across ${data.length} points`\n      }\n      onPointerLeave={() => setActive(null)}\n      className={cn(\n        // Dot size and gap are variables so they can be tuned from className, e.g. \"[--dot-size:0.75rem]\".\n        \"flex w-full justify-center [--dot-gap:0.25rem] [--dot-size:0.625rem]\",\n        className\n      )}\n      {...props}\n    >\n      {data.map((value, index) => {\n        const fraction = Math.min(1, Math.max(0, value / (top || 1)))\n        // Any non-zero value shows at least one dot so small values still register.\n        const count = fraction === 0 ? 0 : Math.max(1, Math.round(fraction * rows))\n        const strong = emphasis <= 0 || fraction >= emphasis\n        // While a column is hovered it alone is lit; otherwise the emphasized columns are.\n        const lit = active === null ? strong : active === index\n        const label = labels?.[index]\n        return (\n          <div\n            key={index}\n            data-slot=\"dot-plot-column\"\n            data-emphasis={strong}\n            data-active={active === index}\n            onPointerEnter={() => setActive(index)}\n            // Columns stretch to the full height and carry the gap as padding, so the hover target has no dead zones.\n            // They share the width equally and cap at the dot size, so dots shrink in narrow cards instead of overflowing.\n            className=\"relative flex min-w-0 flex-1 cursor-default flex-col-reverse justify-start gap-(--dot-gap) px-[calc(var(--dot-gap)/2)] max-w-[calc(var(--dot-size)+var(--dot-gap))]\"\n          >\n            {Array.from({ length: count }, (_, dot) => (\n              <span\n                key={dot}\n                className=\"aspect-square w-full rounded-full transition-opacity\"\n                style={{ backgroundColor: color, opacity: lit ? 1 : fadedOpacity }}\n              />\n            ))}\n            {showTooltip && active === index ? (\n              <span\n                role=\"tooltip\"\n                className=\"bg-popover text-popover-foreground pointer-events-none absolute bottom-full left-1/2 z-10 mb-1.5 flex -translate-x-1/2 items-baseline gap-1.5 rounded-md border px-2 py-1 text-xs whitespace-nowrap shadow-md\"\n              >\n                {label !== undefined ? (\n                  <span className=\"text-muted-foreground\">{label}</span>\n                ) : null}\n                <span className=\"font-medium tabular-nums\">{valueFormatter(value)}</span>\n              </span>\n            ) : null}\n          </div>\n        )\n      })}\n    </div>\n  )\n}\n\nexport { DotPlot }\n",
      "type": "registry:ui"
    }
  ],
  "type": "registry:ui"
}