{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "choropleth-map-chart",
  "title": "Choropleth Map Chart",
  "type": "registry:ui",
  "files": [
    {
      "path": "packages/registry-react/components/charts/choropleth-map-chart/ChoroplethMapChart.tsx",
      "content": "'use client'\n\nimport * as React from 'react'\nimport { Map, MapMarker, MapSource, MapLayer, type MapVariant } from '@/components/ui/map'\nimport { cn } from '@/lib/utils'\nimport { Globe } from 'lucide-react'\n\nexport interface ChoroplethDatum {\n  id: string\n  value: number\n  name?: string\n}\n\nexport interface ChoroplethPin {\n  name: string\n  coord: [number, number]\n  color?: string\n}\n\nexport interface ChoroplethLink {\n  from: [number, number]\n  to: [number, number]\n  label?: string\n}\n\nexport interface ChoroplethMapChartProps {\n  geoJson: any\n  mapName?: string\n  data?: ChoroplethDatum[]\n  idField?: 'id' | 'name'\n  pins?: ChoroplethPin[]\n  links?: ChoroplethLink[]\n  showScale?: boolean\n  height?: number | string\n  center?: [number, number]\n  zoom?: number\n  variant?: MapVariant\n  projection?: 'globe' | 'mercator'\n  className?: string\n  ariaLabel?: string\n}\n\nexport function ChoroplethMapChart({\n  geoJson,\n  mapName = 'uipkge-map',\n  data = [],\n  idField = 'id',\n  pins = [],\n  links = [],\n  showScale = true,\n  height = 420,\n  center = [0, 20],\n  zoom = 1.5,\n  variant = 'dark',\n  projection: initialProjection = 'globe',\n  className,\n}: ChoroplethMapChartProps) {\n  const [projection, setProjection] = React.useState<'globe' | 'mercator'>(initialProjection)\n\n  const dataMap = React.useMemo(() => {\n    const map = new globalThis.Map<string, number>()\n    if (!data) return map\n    for (const d of data) {\n      map.set(String(d.id).toLowerCase(), d.value)\n      if (d.name) map.set(String(d.name).toLowerCase(), d.value)\n    }\n    return map\n  }, [data])\n\n  const values = React.useMemo(() => (data ? data.map((d) => d.value) : []), [data])\n  const minValue = React.useMemo(() => (values.length ? Math.min(...values) : 0), [values])\n  const maxValue = React.useMemo(() => (values.length ? Math.max(...values) : 100), [values])\n\n  const enrichedGeoJson = React.useMemo(() => {\n    if (!geoJson || !geoJson.features) return geoJson\n    const features = geoJson.features.map((f: any) => {\n      const idVal = f.id !== undefined ? String(f.id).toLowerCase() : ''\n      const nameVal = f.properties?.name ? String(f.properties.name).toLowerCase() : ''\n      const matchedVal = dataMap.get(idVal) ?? dataMap.get(nameVal) ?? null\n\n      return {\n        ...f,\n        properties: {\n          ...f.properties,\n          value: matchedVal,\n          title: f.properties?.name || f.id || 'Region',\n        },\n      }\n    })\n\n    return {\n      ...geoJson,\n      features,\n    }\n  }, [geoJson, dataMap])\n\n  const fillPaint = React.useMemo(\n    () => ({\n      'fill-color': [\n        'case',\n        ['!=', ['get', 'value'], null],\n        [\n          'interpolate',\n          ['linear'],\n          ['get', 'value'],\n          minValue,\n          'rgba(56, 189, 248, 0.25)',\n          maxValue,\n          'rgba(56, 189, 248, 0.9)',\n        ],\n        'rgba(255, 255, 255, 0.04)',\n      ] as any,\n      'fill-opacity': 0.85,\n    }),\n    [minValue, maxValue],\n  )\n\n  const strokePaint = {\n    'line-color': 'rgba(255, 255, 255, 0.25)',\n    'line-width': 1,\n  }\n\n  const linksGeoJson = React.useMemo(() => {\n    if (!links || !links.length) return null\n    return {\n      type: 'FeatureCollection',\n      features: links.map((link) => ({\n        type: 'Feature',\n        properties: { label: link.label },\n        geometry: {\n          type: 'LineString',\n          coordinates: [link.from, link.to],\n        },\n      })),\n    }\n  }, [links])\n\n  const linkLinePaint = {\n    'line-color': 'rgba(245, 158, 11, 0.75)',\n    'line-width': 2,\n    'line-dasharray': [2, 2],\n  }\n\n  const toggleProjection = () => {\n    setProjection((prev) => (prev === 'globe' ? 'mercator' : 'globe'))\n  }\n\n  return (\n    <div\n      className={cn(\n        'border-border bg-card group relative w-full overflow-hidden rounded-xl border shadow-xs',\n        className,\n      )}\n      style={{\n        height: typeof height === 'number' ? `${height}px` : /^\\d+$/.test(String(height)) ? `${height}px` : height,\n      }}\n    >\n      <Map variant={variant} projection={projection} center={center} zoom={zoom} className=\"size-full\">\n        <MapSource id=\"choropleth-source\" type=\"geojson\" data={enrichedGeoJson}>\n          <MapLayer id=\"choropleth-fill\" type=\"fill\" paint={fillPaint} />\n          <MapLayer id=\"choropleth-stroke\" type=\"line\" paint={strokePaint} />\n        </MapSource>\n\n        {linksGeoJson && (\n          <MapSource id=\"choropleth-links-source\" type=\"geojson\" data={linksGeoJson}>\n            <MapLayer id=\"choropleth-links\" type=\"line\" paint={linkLinePaint} />\n          </MapSource>\n        )}\n\n        {pins.map((pin, i) => (\n          <MapMarker key={i} longitude={pin.coord[0]} latitude={pin.coord[1]} anchor=\"bottom\">\n            <div className=\"flex flex-col items-center\">\n              <span\n                className=\"size-3 rounded-full shadow-md ring-4\"\n                style={{\n                  backgroundColor: pin.color || 'oklch(0.65 0.20 145)',\n                  boxShadow: `0 0 10px ${pin.color || 'oklch(0.65 0.20 145)'}`,\n                }}\n              />\n              <span className=\"border-border/80 bg-background/90 mt-1 rounded border px-1.5 py-0.5 font-mono text-[10px] font-semibold shadow-xs backdrop-blur-xs\">\n                {pin.name}\n              </span>\n            </div>\n          </MapMarker>\n        ))}\n      </Map>\n\n      <div className=\"border-border/70 bg-card/85 absolute top-3 right-3 z-10 flex items-center gap-1 rounded-lg border p-1 shadow-xs backdrop-blur-md\">\n        <button\n          type=\"button\"\n          className=\"text-muted-foreground hover:bg-muted hover:text-foreground flex items-center gap-1.5 rounded-md px-2.5 py-1 text-xs font-medium transition\"\n          onClick={toggleProjection}\n        >\n          <Globe className=\"size-3.5\" />\n          <span className=\"capitalize\">{projection}</span>\n        </button>\n      </div>\n\n      {showScale && values.length > 0 && (\n        <div className=\"border-border/70 bg-card/85 absolute right-3 bottom-3 z-10 hidden items-center gap-2.5 rounded-lg border px-3 py-2 text-xs shadow-xs backdrop-blur-md sm:flex\">\n          <span className=\"text-muted-foreground font-mono text-[11px]\">Range</span>\n          <span className=\"text-muted-foreground font-mono text-[10px]\">{minValue}</span>\n          <div className=\"h-2 w-24 rounded-full bg-gradient-to-r from-sky-400/30 to-sky-400\" />\n          <span className=\"text-foreground font-mono text-[10px] font-semibold\">{maxValue}</span>\n        </div>\n      )}\n    </div>\n  )\n}\n\nexport default ChoroplethMapChart\n",
      "type": "registry:ui",
      "target": "~/components/ui/charts/choropleth-map-chart/ChoroplethMapChart.tsx"
    },
    {
      "path": "packages/registry-react/components/charts/choropleth-map-chart/index.ts",
      "content": "export {\n  ChoroplethMapChart,\n  type ChoroplethMapChartProps,\n  type ChoroplethDatum,\n  type ChoroplethPin,\n  type ChoroplethLink,\n} from './ChoroplethMapChart'\n",
      "type": "registry:ui",
      "target": "~/components/ui/charts/choropleth-map-chart/index.ts"
    }
  ],
  "dependencies": [
    "lucide-react"
  ],
  "devDependencies": [],
  "registryDependencies": [
    "https://uipkge.dev/r/react/map.json"
  ],
  "description": "React mirror of @uipkge/choropleth-map-chart — see the Vue registry item for the canonical description.",
  "categories": [
    "chart"
  ]
}