{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "recharts-pie-chart",
  "description": "Pie chart component with donut, gradient, and glow effects",
  "dependencies": [
    "recharts",
    "motion"
  ],
  "registryDependencies": [
    "@evilcharts/recharts-chart",
    "@evilcharts/recharts-tooltip",
    "@evilcharts/recharts-legend",
    "@evilcharts/recharts-background"
  ],
  "files": [
    {
      "path": "src/registry/charts/recharts-pie-chart.tsx",
      "content": "\"use client\";\n\nimport {\n  Children,\n  createContext,\n  isValidElement,\n  use,\n  useCallback,\n  useId,\n  useMemo,\n  useState,\n  type ComponentProps,\n  type FC,\n  type ReactElement,\n  type ReactNode,\n} from \"react\";\nimport {\n  LabelList as RechartsLabelList,\n  Pie as RechartsPie,\n  PieChart as RechartsPieChart,\n  Sector,\n  type PieSectorShapeProps,\n} from \"recharts\";\nimport {\n  ChartTooltip,\n  ChartTooltipContent,\n  type TooltipRoundness,\n  type TooltipVariant,\n} from \"@/registry/ui/recharts-tooltip\";\nimport {\n  type ChartConfig,\n  ChartContainer,\n  getColorsCount,\n  LoadingIndicator,\n} from \"@/registry/ui/recharts-chart\";\nimport { ChartLegend, ChartLegendContent, type ChartLegendVariant } from \"@/registry/ui/recharts-legend\";\nimport { ChartBackground, type BackgroundVariant } from \"@/registry/ui/recharts-background\";\nimport { motion } from \"motion/react\";\n\n// Constants\nconst LOADING_SECTORS = 5;\nconst LOADING_ANIMATION_DURATION = 2000; // full loading cycle duration in milliseconds\nconst DEFAULT_INNER_RADIUS = 0;\nconst DEFAULT_OUTER_RADIUS = \"80%\";\nconst DEFAULT_CORNER_RADIUS = 0;\nconst DEFAULT_PADDING_ANGLE = 0;\nconst DEFAULT_START_ANGLE = 0;\nconst DEFAULT_END_ANGLE = 360;\n// Stable empty-array reference so the `glowingSectors` default doesn't change every render\nconst EMPTY_GLOWING_SECTORS: string[] = [];\n\ntype LabelListProps = ComponentProps<typeof RechartsLabelList>;\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Shared context\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Shared state for every part of the chart. Lifted into <EvilPieChart /> so that\n * <Pie />, <Tooltip />, <Legend />, and friends can read it without prop drilling.\n * Sub-components are composed freely — the provider is the single source of truth.\n */\ntype PieChartContextValue = {\n  config: ChartConfig; // colors + labels for every sector\n  data: Record<string, unknown>[]; // rows rendered by the chart\n  dataKey: string; // key holding each sector's numeric value\n  nameKey: string; // key holding each sector's name\n  isLoading: boolean; // whether the chart shows its loading skeleton\n  selectedSector: string | null; // currently selected sector name, or null when none\n  selectSector: (sectorName: string | null) => void; // sets the selected sector\n};\n\nconst PieChartContext = createContext<PieChartContextValue | null>(null);\n\n// Reads the chart context, throwing a helpful error when used outside <EvilPieChart />\nfunction usePieChart() {\n  const context = use(PieChartContext);\n\n  if (!context) {\n    throw new Error(\n      \"Pie chart parts (<Pie />, <Tooltip />, …) must be used within <EvilPieChart />\",\n    );\n  }\n\n  return context;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Root container\n// ─────────────────────────────────────────────────────────────────────────────\n\ntype EvilPieChartProps<TData extends Record<string, unknown>> = {\n  config: ChartConfig; // sector colors + labels\n  data: TData[]; // rows rendered by the chart\n  dataKey: keyof TData & string; // key holding each sector's numeric value\n  nameKey: keyof TData & string; // key holding each sector's name\n  children: ReactNode; // composed parts — <Pie />, <Tooltip />, <Legend />, …\n  className?: string; // extra classes for the chart container\n  chartProps?: ComponentProps<typeof RechartsPieChart>; // escape hatch for the raw Recharts chart\n  defaultSelectedSector?: string | null; // sector selected on first render\n  onSelectionChange?: (selection: { dataKey: string; value: number } | null) => void; // fires when the selected sector changes\n  isLoading?: boolean; // shows the animated loading skeleton\n};\n\n/**\n * Root of the composible pie chart. Owns the data, the shared context, and the\n * loading skeleton. Everything visual — the pie itself, tooltip, legend, and an\n * optional background — is composed as children, so a consumer renders exactly\n * the parts they need.\n */\nexport function EvilPieChart<TData extends Record<string, unknown>>({\n  config,\n  data,\n  dataKey,\n  nameKey,\n  children,\n  className,\n  chartProps,\n  defaultSelectedSector = null,\n  onSelectionChange,\n  isLoading = false,\n}: EvilPieChartProps<TData>) {\n  const [selectedSector, setSelectedSector] = useState<string | null>(defaultSelectedSector);\n\n  // Updates selection state and notifies the parent with the sector's value\n  const selectSector = useCallback(\n    (sectorName: string | null) => {\n      setSelectedSector(sectorName);\n\n      if (sectorName === null) {\n        onSelectionChange?.(null);\n        return;\n      }\n\n      const selectedItem = data.find((item) => (item[nameKey] as string) === sectorName);\n\n      if (selectedItem) {\n        onSelectionChange?.({ dataKey: sectorName, value: selectedItem[dataKey] as number });\n      }\n    },\n    [data, dataKey, nameKey, onSelectionChange],\n  );\n\n  const contextValue = useMemo<PieChartContextValue>(\n    () => ({\n      config,\n      data,\n      dataKey,\n      nameKey,\n      isLoading,\n      selectedSector,\n      selectSector,\n    }),\n    [config, data, dataKey, nameKey, isLoading, selectedSector, selectSector],\n  );\n\n  return (\n    <PieChartContext value={contextValue}>\n      <ChartContainer className={className} config={config}>\n        <LoadingIndicator isLoading={isLoading} />\n        <RechartsPieChart id=\"evil-charts-pie-chart\" accessibilityLayer {...chartProps}>\n          {children}\n        </RechartsPieChart>\n      </ChartContainer>\n    </PieChartContext>\n  );\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Composible parts\n// ─────────────────────────────────────────────────────────────────────────────\n\ntype PieProps = {\n  variant?: PieVariant; // fill style for the pie's sectors\n  innerRadius?: number | string; // inner radius — set above 0 for a donut\n  outerRadius?: number | string; // outer radius of the pie\n  cornerRadius?: number; // border-radius of each sector in pixels\n  paddingAngle?: number; // gap between sectors in degrees — negative overlaps them\n  startAngle?: number; // angle the pie starts drawing from\n  endAngle?: number; // angle the pie stops drawing at\n  isClickable?: boolean; // lets sectors be selected by clicking them\n  glowingSectors?: string[]; // sector names that render with a soft outer glow\n  children?: ReactNode; // optional <Label /> composition for sector labels\n  pieProps?: Omit<ComponentProps<typeof RechartsPie>, \"data\" | \"dataKey\" | \"nameKey\">; // escape hatch for raw Recharts Pie props\n};\n\n/**\n * The pie series. Self-contained: it generates its own radial color gradients\n * and glow filters under a unique id, so any number of pies — each with its own\n * shape and clickability — can live on one page without style collisions. While\n * the chart is loading it renders an animated skeleton in place of the data.\n * Compose <Label /> inside it to draw labels on each sector.\n */\nfunction Pie({\n  variant = \"gradient\",\n  innerRadius = DEFAULT_INNER_RADIUS,\n  outerRadius = DEFAULT_OUTER_RADIUS,\n  cornerRadius = DEFAULT_CORNER_RADIUS,\n  paddingAngle = DEFAULT_PADDING_ANGLE,\n  startAngle = DEFAULT_START_ANGLE,\n  endAngle = DEFAULT_END_ANGLE,\n  isClickable = false,\n  glowingSectors = EMPTY_GLOWING_SECTORS,\n  children,\n  pieProps,\n}: PieProps) {\n  const { config, data, dataKey, nameKey, isLoading, selectedSector, selectSector } = usePieChart();\n  const id = useId().replace(/:/g, \"\"); // unique id scopes this pie's style defs\n\n  if (isLoading) {\n    return (\n      <RechartsPie\n        data={LOADING_PIE_DATA}\n        dataKey=\"value\"\n        nameKey=\"name\"\n        innerRadius={innerRadius}\n        outerRadius={outerRadius}\n        cornerRadius={cornerRadius}\n        paddingAngle={paddingAngle}\n        startAngle={startAngle}\n        endAngle={endAngle}\n        strokeWidth={0}\n        isAnimationActive={false}\n        shape={(props) => <AnimatedLoadingSector {...props} />}\n      />\n    );\n  }\n\n  const label = resolveLabel(children, dataKey);\n\n  const preparedData = data.map((item) => ({\n    ...item,\n    fill: `url(#${id}-colors-${item[nameKey] as string})`,\n  }));\n\n  return (\n    <>\n      <RechartsPie\n        data={preparedData}\n        dataKey={dataKey}\n        nameKey={nameKey}\n        innerRadius={innerRadius}\n        outerRadius={outerRadius}\n        cornerRadius={cornerRadius}\n        paddingAngle={paddingAngle}\n        startAngle={startAngle}\n        endAngle={endAngle}\n        strokeWidth={0}\n        isAnimationActive\n        style={isClickable ? { cursor: \"pointer\" } : undefined}\n        onClick={(_, index) => {\n          if (!isClickable) return;\n          const clickedName = data[index]?.[nameKey] as string;\n          // Clicking the selected sector clears the selection, otherwise selects it\n          selectSector(selectedSector === clickedName ? null : clickedName);\n        }}\n        shape={(props: PieSectorShapeProps) => {\n          const sectorName = data[props.index ?? 0]?.[nameKey] as string;\n          const isGlowing = glowingSectors.includes(sectorName);\n          const isDimmed = isClickable && selectedSector !== null && selectedSector !== sectorName;\n\n          return (\n            <Sector\n              {...props}\n              fill={`url(#${id}-colors-${sectorName})`}\n              filter={isGlowing ? `url(#${id}-glow-${sectorName})` : undefined}\n              stroke={paddingAngle < 0 ? \"var(--background)\" : \"none\"}\n              strokeWidth={paddingAngle < 0 ? 5 : 0}\n              opacity={isDimmed ? 0.15 : 1}\n              className=\"transition-opacity duration-200\"\n            />\n          );\n        }}\n        {...pieProps}\n      >\n        {label}\n      </RechartsPie>\n      <defs>\n        <RadialColorGradient id={id} config={config} variant={variant} />\n        {glowingSectors.length > 0 && <GlowFilter id={id} glowingSectors={glowingSectors} />}\n      </defs>\n    </>\n  );\n}\n\ntype LabelProps = {\n  dataKey?: string; // data key for the label text — defaults to the pie's value key\n  labelListProps?: Omit<LabelListProps, \"dataKey\">; // escape hatch for raw Recharts LabelList props\n};\n\n/**\n * Declares per-sector labels for the <Pie /> it is composed inside. It renders\n * nothing on its own — the parent <Pie /> reads its props and wires them into a\n * Recharts LabelList drawn over the sectors.\n */\nconst Label: FC<LabelProps> = () => null;\n\ntype TooltipProps = {\n  variant?: TooltipVariant; // visual style of the tooltip surface\n  roundness?: TooltipRoundness; // border-radius of the tooltip\n  defaultIndex?: number; // sector index shown by default with no hover\n};\n\n/**\n * The hover tooltip. Hidden automatically while the chart is loading.\n */\nfunction Tooltip({ variant, roundness, defaultIndex }: TooltipProps) {\n  const { isLoading, nameKey } = usePieChart();\n\n  if (isLoading) return null;\n\n  return (\n    <ChartTooltip\n      defaultIndex={defaultIndex}\n      content={\n        <ChartTooltipContent nameKey={nameKey} hideLabel roundness={roundness} variant={variant} />\n      }\n    />\n  );\n}\n\ntype LegendProps = {\n  variant?: ChartLegendVariant; // visual style of the legend indicators\n  align?: \"left\" | \"center\" | \"right\"; // horizontal placement\n  verticalAlign?: \"top\" | \"middle\" | \"bottom\"; // vertical placement\n  isClickable?: boolean; // lets each entry toggle selection of its sector\n};\n\n/**\n * The sector legend. When `isClickable` is set, each entry toggles selection of\n * its sector, driving the shared selection state read by the <Pie />.\n */\nfunction Legend({\n  variant,\n  align = \"center\",\n  verticalAlign = \"bottom\",\n  isClickable = false,\n}: LegendProps) {\n  const { nameKey, selectedSector, selectSector } = usePieChart();\n\n  return (\n    <ChartLegend\n      verticalAlign={verticalAlign}\n      align={align}\n      content={\n        <ChartLegendContent\n          selected={selectedSector}\n          onSelectChange={selectSector}\n          isClickable={isClickable}\n          nameKey={nameKey}\n          variant={variant}\n        />\n      }\n    />\n  );\n}\n\ntype BackgroundProps = {\n  variant?: BackgroundVariant; // background pattern style\n};\n\n/**\n * An optional decorative pattern drawn behind the pie. Compose it before the\n * <Pie /> so it sits underneath the sectors.\n */\nfunction Background({ variant = \"dots\" }: BackgroundProps) {\n  return <ChartBackground variant={variant} />;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Label helper\n// ─────────────────────────────────────────────────────────────────────────────\n\n// Pulls a <Label /> out of a pie's children into a Recharts LabelList element\nconst resolveLabel = (children: ReactNode, valueKey: string): ReactNode => {\n  let label: ReactNode = null;\n\n  Children.forEach(children, (child) => {\n    if (!isValidElement(child) || child.type !== Label) return;\n\n    const { dataKey, labelListProps } = (child as ReactElement<LabelProps>).props;\n\n    label = (\n      <RechartsLabelList\n        dataKey={dataKey ?? valueKey}\n        stroke=\"none\"\n        fontSize={12}\n        fontWeight={500}\n        fill=\"currentColor\"\n        className=\"fill-background\"\n        {...labelListProps}\n      />\n    );\n  });\n\n  return label;\n};\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Style definitions — one set per <Pie />, scoped to its unique id\n// ─────────────────────────────────────────────────────────────────────────────\n\ntype PieVariant = \"gradient\";\n\n/**\n * Radial-style color gradients, one per sector. Each sector's fill paints from\n * the gradient that matches its name, supporting both single and multi-color\n * config entries.\n */\nconst RadialColorGradient = ({\n  id,\n  config,\n}: {\n  id: string; // unique id of the owning <Pie />\n  config: ChartConfig; // sector colors the gradients are built from\n  variant: PieVariant; // fill style — currently always a diagonal color gradient\n}) => {\n  return (\n    <>\n      {Object.entries(config).map(([sectorKey, sectorConfig]) => {\n        const colorsCount = getColorsCount(sectorConfig);\n\n        return (\n          <linearGradient\n            key={`${id}-colors-${sectorKey}`}\n            id={`${id}-colors-${sectorKey}`}\n            x1=\"0\"\n            y1=\"0\"\n            x2=\"1\"\n            y2=\"1\"\n          >\n            {colorsCount === 1 ? (\n              <>\n                <stop offset=\"0%\" stopColor={`var(--color-${sectorKey}-0)`} />\n                <stop offset=\"100%\" stopColor={`var(--color-${sectorKey}-0)`} />\n              </>\n            ) : (\n              Array.from({ length: colorsCount }, (_, index) => {\n                const offset = `${(index / (colorsCount - 1)) * 100}%`;\n                return (\n                  <stop\n                    key={offset}\n                    offset={offset}\n                    stopColor={`var(--color-${sectorKey}-${index}, var(--color-${sectorKey}-0))`}\n                  />\n                );\n              })\n            )}\n          </linearGradient>\n        );\n      })}\n    </>\n  );\n};\n\n/** Soft outer-glow SVG filter, one per glowing sector. */\nconst GlowFilter = ({\n  id,\n  glowingSectors,\n}: {\n  id: string; // unique id of the owning <Pie />\n  glowingSectors: string[]; // sector names that should glow\n}) => {\n  return (\n    <>\n      {glowingSectors.map((sectorName) => (\n        <filter\n          key={`${id}-glow-${sectorName}`}\n          id={`${id}-glow-${sectorName}`}\n          x=\"-100%\"\n          y=\"-100%\"\n          width=\"300%\"\n          height=\"300%\"\n        >\n          <feGaussianBlur in=\"SourceGraphic\" stdDeviation=\"8\" result=\"blur\" />\n          <feColorMatrix\n            in=\"blur\"\n            type=\"matrix\"\n            values=\"1 0 0 0 0  0 1 0 0 0  0 0 1 0 0  0 0 0 0.5 0\"\n            result=\"glow\"\n          />\n          <feMerge>\n            <feMergeNode in=\"glow\" />\n            <feMergeNode in=\"SourceGraphic\" />\n          </feMerge>\n        </filter>\n      ))}\n    </>\n  );\n};\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Loading skeleton\n// ─────────────────────────────────────────────────────────────────────────────\n\n// Equal-sized sectors used to render the circular pulsing loading skeleton\nconst LOADING_PIE_DATA = Array.from({ length: LOADING_SECTORS }, (_, i) => ({\n  name: `loading${i}`,\n  value: 100 / LOADING_SECTORS,\n}));\n\n/**\n * A single skeleton sector shown while the chart is loading. Each sector pulses\n * with a staggered delay, producing a wave that travels around the pie.\n */\nconst AnimatedLoadingSector = (props: ComponentProps<typeof Sector> & { index?: number }) => {\n  const { index = 0, ...sectorProps } = props;\n\n  // Staggered delay so the pulse sweeps around the circle\n  const delay = (index / LOADING_SECTORS) * (LOADING_ANIMATION_DURATION / 1000);\n\n  return (\n    <motion.g\n      initial={{ opacity: 0.15 }}\n      animate={{ opacity: [0.15, 0.5, 0.15] }}\n      transition={{\n        duration: LOADING_ANIMATION_DURATION / 1000,\n        delay,\n        repeat: Infinity,\n        ease: \"easeInOut\",\n      }}\n    >\n      <Sector {...sectorProps} fill=\"currentColor\" />\n    </motion.g>\n  );\n};\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Static parts attached to the root\n// ─────────────────────────────────────────────────────────────────────────────\n\nEvilPieChart.Pie = Pie;\nEvilPieChart.Label = Label;\nEvilPieChart.Tooltip = Tooltip;\nEvilPieChart.Legend = Legend;\nEvilPieChart.Background = Background;\n",
      "type": "registry:component",
      "target": "components/evilcharts/charts/recharts-pie-chart.tsx"
    }
  ],
  "type": "registry:component"
}