{
  "$schema": "https://uipkge.dev/schema/registry-item.json",
  "name": "choropleth-map-chart",
  "title": "Choropleth Map Chart",
  "type": "registry:ui",
  "files": [
    {
      "path": "packages/registry-svelte/components/charts/choropleth-map-chart/ChoroplethMapChart.svelte",
      "content": "<script lang=\"ts\" module>\n  import type { HTMLAttributes } from 'svelte/elements'\n  import type { MapVariant } from '$lib/components/ui/map'\n\n  export interface ChoroplethDatum {\n    id: string\n    value: number\n    name?: string\n  }\n\n  export interface ChoroplethPin {\n    name: string\n    coord: [number, number]\n    color?: string\n  }\n\n  export interface ChoroplethLink {\n    from: [number, number]\n    to: [number, number]\n    label?: string\n  }\n\n  export interface ChoroplethMapChartProps extends HTMLAttributes<HTMLDivElement> {\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    ariaLabel?: string\n  }\n</script>\n\n<script lang=\"ts\">\n  import { Globe } from '@lucide/svelte'\n  import { Map, MapLayer, MapMarker, MapSource } from '$lib/components/ui/map'\n  import { cn } from '$lib/utils'\n\n  let {\n    class: className,\n    geoJson,\n    mapName: _mapName = 'uipkge-map',\n    data,\n    idField: _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 = 'globe',\n    ariaLabel,\n    ...restProps\n  }: ChoroplethMapChartProps = $props()\n\n  // `_mapName` / `_idField` are destructured aside (accepted for API parity\n  // with the Vue twin) so they never leak into `...restProps` and onto the DOM.\n  // Intentional one-shot seed: the switcher button owns the projection after mount.\n  // svelte-ignore state_referenced_locally\n  let currentProjection: 'globe' | 'mercator' = $state(projection)\n\n  function toggleProjection() {\n    currentProjection = currentProjection === 'globe' ? 'mercator' : 'globe'\n  }\n\n  const dataMap = $derived.by(() => {\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  })\n\n  const values = $derived(data ? data.map((d) => d.value) : [])\n  const minValue = $derived(values.length ? Math.min(...values) : 0)\n  const maxValue = $derived(values.length ? Math.max(...values) : 100)\n\n  const enrichedGeoJson = $derived.by(() => {\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  })\n\n  const fillPaint = $derived({\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    ],\n    'fill-opacity': 0.85,\n  })\n\n  const strokePaint = {\n    'line-color': 'rgba(255, 255, 255, 0.25)',\n    'line-width': 1,\n  }\n\n  const linksGeoJson = $derived.by(() => {\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  })\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 heightStyle = $derived(\n    typeof height === 'number' ? `${height}px` : /^\\d+$/.test(String(height)) ? `${height}px` : height,\n  )\n</script>\n\n<div\n  role=\"img\"\n  aria-label={ariaLabel || 'Chart'}\n  class={cn('border-border bg-card group relative w-full overflow-hidden rounded-xl border shadow-xs', className)}\n  style=\"height: {heightStyle};\"\n  {...restProps}\n>\n  <Map {variant} projection={currentProjection} {center} {zoom} class=\"size-full\">\n    <!-- GeoJSON Choropleth Source & Layers -->\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    <!-- Links Layer -->\n    {#if linksGeoJson}\n      <MapSource id=\"choropleth-links-source\" type=\"geojson\" data={linksGeoJson}>\n        <MapLayer id=\"choropleth-links\" type=\"line\" paint={linkLinePaint} />\n      </MapSource>\n    {/if}\n\n    <!-- Point Pins -->\n    {#each pins || [] as pin, i (i)}\n      <MapMarker lngLat={pin.coord} anchor=\"bottom\">\n        <div class=\"flex flex-col items-center\">\n          <span\n            class=\"size-3 rounded-full shadow-md ring-4\"\n            style=\"background-color: {pin.color || 'oklch(0.65 0.20 145)'}; box-shadow: 0 0 10px {pin.color ||\n              'oklch(0.65 0.20 145)'};\"\n          ></span>\n          <span\n            class=\"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          >\n            {pin.name}\n          </span>\n        </div>\n      </MapMarker>\n    {/each}\n  </Map>\n\n  <!-- Top-Right Projection Switcher -->\n  <div\n    class=\"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  >\n    <button\n      type=\"button\"\n      class=\"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 class=\"size-3.5\" />\n      <span class=\"capitalize\">{currentProjection}</span>\n    </button>\n  </div>\n\n  <!-- Bottom-Right Continuous Value Scale Legend -->\n  {#if showScale && values.length > 0}\n    <div\n      class=\"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    >\n      <span class=\"text-muted-foreground font-mono text-[11px]\">Range</span>\n      <span class=\"text-muted-foreground font-mono text-[10px]\">{minValue}</span>\n      <div class=\"h-2 w-24 rounded-full bg-gradient-to-r from-sky-400/30 to-sky-400\"></div>\n      <span class=\"text-foreground font-mono text-[10px] font-semibold\">{maxValue}</span>\n    </div>\n  {/if}\n</div>\n",
      "type": "registry:ui",
      "target": "~/src/lib/components/ui/charts/choropleth-map-chart/ChoroplethMapChart.svelte"
    },
    {
      "path": "packages/registry-svelte/components/charts/choropleth-map-chart/index.ts",
      "content": "export {\n  default as ChoroplethMapChart,\n  type ChoroplethDatum,\n  type ChoroplethLink,\n  type ChoroplethMapChartProps,\n  type ChoroplethPin,\n} from './ChoroplethMapChart.svelte'\n",
      "type": "registry:ui",
      "target": "~/src/lib/components/ui/charts/choropleth-map-chart/index.ts"
    }
  ],
  "dependencies": [
    "@lucide/svelte"
  ],
  "devDependencies": [],
  "registryDependencies": [
    "https://uipkge.dev/r/svelte/map.json"
  ],
  "description": "Choropleth map around the Mapbox-powered map primitive with bring-your-own GeoJSON. Value-shaded regions with a continuous scale legend, globe/mercator projection switcher, plus pin markers and dashed link lines. Theme-aware via registry tokens.",
  "categories": [
    "chart"
  ]
}