{
  "$schema": "https://uipkge.dev/schema/registry-item.json",
  "name": "choropleth-map-chart",
  "title": "Choropleth Map Chart",
  "type": "registry:ui",
  "files": [
    {
      "path": "packages/registry-angular/components/charts/choropleth-map-chart/choropleth-map-chart.component.ts",
      "content": "import { Component, Input, OnInit, signal, ChangeDetectionStrategy } from '@angular/core'\nimport { cn } from '@/lib/utils'\nimport {\n  UiMapComponent,\n  UiMapSourceComponent,\n  UiMapLayerComponent,\n  UiMapMarkerComponent,\n  type MapVariant,\n} from '@/ui/map/map.component'\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\n/**\n * Angular port of the UIPKGE ChoroplethMapChart (React/Vue). A Mapbox globe/mercator map\n * with a value-shaded GeoJSON fill layer, dashed corridor links, pin markers, a projection\n * switcher, and a continuous value-scale legend. `accessToken` is passed through to the\n * inner map (React/Vue read it from env; Angular needs it as an input).\n */\n@Component({\n  changeDetection: ChangeDetectionStrategy.Eager,\n  selector: 'ui-choropleth-map-chart, [ui-choropleth-map-chart]',\n  standalone: true,\n  imports: [UiMapComponent, UiMapSourceComponent, UiMapLayerComponent, UiMapMarkerComponent],\n  host: {\n    '[attr.data-slot]': '\"choropleth-map-chart\"',\n    '[attr.data-uipkge]': '\"\"',\n    '[attr.aria-label]': 'ariaLabel || null',\n    '[class]': 'hostClass',\n    '[style.height]': 'heightStyle',\n  },\n  template: `\n    <ui-map\n      [accessToken]=\"accessToken\"\n      [variant]=\"variant\"\n      [projection]=\"currentProjection()\"\n      [center]=\"center\"\n      [zoom]=\"zoom\"\n      class=\"size-full\"\n    >\n      <ui-map-source id=\"choropleth-source\" type=\"geojson\" [data]=\"enrichedGeoJson\">\n        <ui-map-layer id=\"choropleth-fill\" type=\"fill\" [paint]=\"fillPaint\" />\n        <ui-map-layer id=\"choropleth-stroke\" type=\"line\" [paint]=\"strokePaint\" />\n      </ui-map-source>\n\n      @if (linksGeoJson) {\n        <ui-map-source id=\"choropleth-links-source\" type=\"geojson\" [data]=\"linksGeoJson\">\n          <ui-map-layer id=\"choropleth-links\" type=\"line\" [paint]=\"linkLinePaint\" />\n        </ui-map-source>\n      }\n\n      @for (pin of pins; track $index) {\n        <ui-map-marker [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.backgroundColor]=\"pin.color || 'oklch(0.65 0.20 145)'\"\n              [style.boxShadow]=\"'0 0 10px ' + (pin.color || '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        </ui-map-marker>\n      }\n    </ui-map>\n\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        (click)=\"toggleProjection()\"\n      >\n        <svg\n          xmlns=\"http://www.w3.org/2000/svg\"\n          width=\"24\"\n          height=\"24\"\n          viewBox=\"0 0 24 24\"\n          fill=\"none\"\n          stroke=\"currentColor\"\n          stroke-width=\"2\"\n          stroke-linecap=\"round\"\n          stroke-linejoin=\"round\"\n          class=\"lucide lucide-globe size-3.5\"\n          aria-hidden=\"true\"\n        >\n          <circle cx=\"12\" cy=\"12\" r=\"10\" />\n          <path d=\"M12 2a14.5 14.5 0 0 0 0 20 14.5 14.5 0 0 0 0-20\" />\n          <path d=\"M2 12h20\" />\n        </svg>\n        <span class=\"capitalize\">{{ currentProjection() }}</span>\n      </button>\n    </div>\n\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    }\n  `,\n})\nexport class UiChoroplethMapChartComponent implements OnInit {\n  @Input() geoJson: any\n  @Input() mapName = 'uipkge-map'\n  @Input() data: ChoroplethDatum[] = []\n  @Input() idField: 'id' | 'name' = 'id'\n  @Input() pins: ChoroplethPin[] = []\n  @Input() links: ChoroplethLink[] = []\n  @Input() showScale = true\n  @Input() height: number | string = 420\n  @Input() center: [number, number] = [0, 20]\n  @Input() zoom = 1.5\n  @Input() variant: MapVariant = 'dark'\n  @Input() projection: 'globe' | 'mercator' = 'globe'\n  /** Mapbox token, passed through to the inner map. */\n  @Input() accessToken = ''\n  /** Accessible name for the map region. */\n  @Input() ariaLabel?: string\n  @Input('class') className?: string\n\n  readonly currentProjection = signal<'globe' | 'mercator'>('globe')\n\n  /** React reads the initial projection once (useState); later input changes are ignored too. */\n  ngOnInit(): void {\n    this.currentProjection.set(this.projection)\n  }\n\n  toggleProjection(): void {\n    this.currentProjection.set(this.currentProjection() === 'globe' ? 'mercator' : 'globe')\n  }\n\n  get hostClass(): string {\n    return cn('block border-border bg-card group relative w-full overflow-hidden rounded-xl border shadow-xs', this.className)\n  }\n\n  get heightStyle(): string {\n    if (typeof this.height === 'number') return `${this.height}px`\n    return /^\\d+$/.test(String(this.height)) ? `${this.height}px` : String(this.height)\n  }\n\n  get dataMap(): Map<string, number> {\n    const map = new globalThis.Map<string, number>()\n    if (!this.data) return map\n    for (const d of this.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  get values(): number[] {\n    return this.data ? this.data.map((d) => d.value) : []\n  }\n\n  get minValue(): number {\n    return this.values.length ? Math.min(...this.values) : 0\n  }\n\n  get maxValue(): number {\n    return this.values.length ? Math.max(...this.values) : 100\n  }\n\n  get enrichedGeoJson(): any {\n    if (!this.geoJson || !this.geoJson.features) return this.geoJson\n    const map = this.dataMap\n    const features = this.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 = map.get(idVal) ?? map.get(nameVal) ?? null\n      return {\n        ...f,\n        properties: { ...f.properties, value: matchedVal, title: f.properties?.name || f.id || 'Region' },\n      }\n    })\n    return { ...this.geoJson, features }\n  }\n\n  get fillPaint(): Record<string, unknown> {\n    return {\n      'fill-color': [\n        'case',\n        ['!=', ['get', 'value'], null],\n        [\n          'interpolate',\n          ['linear'],\n          ['get', 'value'],\n          this.minValue,\n          'rgba(56, 189, 248, 0.25)',\n          this.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\n  get strokePaint(): Record<string, unknown> {\n    return { 'line-color': 'rgba(255, 255, 255, 0.25)', 'line-width': 1 }\n  }\n\n  get linksGeoJson(): Record<string, unknown> | null {\n    if (!this.links || !this.links.length) return null\n    return {\n      type: 'FeatureCollection',\n      features: this.links.map((link) => ({\n        type: 'Feature',\n        properties: { label: link.label },\n        geometry: { type: 'LineString', coordinates: [link.from, link.to] },\n      })),\n    }\n  }\n\n  get linkLinePaint(): Record<string, unknown> {\n    return { 'line-color': 'rgba(245, 158, 11, 0.75)', 'line-width': 2, 'line-dasharray': [2, 2] }\n  }\n}\n",
      "type": "registry:ui",
      "target": "~/src/app/components/ui/charts/choropleth-map-chart/choropleth-map-chart.component.ts"
    },
    {
      "path": "packages/registry-angular/components/charts/choropleth-map-chart/index.ts",
      "content": "export { UiChoroplethMapChartComponent } from './choropleth-map-chart.component'\n",
      "type": "registry:ui",
      "target": "~/src/app/components/ui/charts/choropleth-map-chart/index.ts"
    }
  ],
  "dependencies": [],
  "devDependencies": [],
  "registryDependencies": [
    "https://uipkge.dev/r/angular/map.json"
  ],
  "description": "Choropleth map on the Mapbox primitive with bring-your-own GeoJSON. Value-shaded fill layer with a continuous legend, dashed corridor links, pin markers, and a projection switcher. Theme-aware via registry tokens.",
  "categories": [
    "chart"
  ]
}