bharat-choropleth 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md ADDED
@@ -0,0 +1,24 @@
1
+ # Changelog
2
+
3
+ ## 0.2.0
4
+
5
+ - Added an optional sub-district level below districts — tehsils, taluks,
6
+ mandals and blocks. `loadSubDistricts` is called only after a district is
7
+ activated, so that geometry stays code-split the way districts already are,
8
+ and `subDistrictDrillDownId` / `defaultSubDistrictDrillDownId` /
9
+ `onSubDistrictDrillDownChange` drive it controlled or uncontrolled.
10
+ - A `loadSubDistricts` that resolves `null` marks that district a leaf: the map
11
+ stays on the district view and selects it rather than opening an empty level,
12
+ and the district is not asked again. Omit the prop entirely and every district
13
+ stays a leaf, exactly as before.
14
+ - Exported `MapLevel` and `SubDistrictLoader`.
15
+ - **Possibly breaking for TypeScript callers:** `level` on `TooltipContext` and
16
+ `InsightContext` widened from `"state" | "district"` to `MapLevel`, which adds
17
+ `"subdistrict"`. A `switch` over it that the compiler checks for
18
+ exhaustiveness now needs the third case. Nothing changes at runtime for a
19
+ two-level map.
20
+
21
+ ## 0.1.0
22
+
23
+ - Initial public release of the React India state and district choropleth
24
+ component.
package/README.md CHANGED
@@ -2,7 +2,8 @@
2
2
 
3
3
  An accessible React SVG choropleth for India state and district dashboards.
4
4
  It renders a supplied state layer, supports keyboard and pointer inspection,
5
- and can lazy-load district layers when a user selects a state.
5
+ and can lazy-load district layers when a user selects a state, then sub-district
6
+ layers when a user selects a district.
6
7
 
7
8
  ## Install
8
9
 
@@ -56,6 +57,39 @@ selected stable state ID and returns another `MapLayer`:
56
57
  />
57
58
  ```
58
59
 
60
+ Add `loadSubDistricts` for a third level below districts. It receives the district
61
+ ID, the district region, and the state ID it sits in:
62
+
63
+ ```tsx
64
+ <IndiaChoropleth
65
+ states={states}
66
+ loadDistricts={loadDistricts}
67
+ loadSubDistricts={async (districtId) => {
68
+ const topology = await import(`./subdistricts/${districtId}.topo.json`);
69
+ return {
70
+ geometry: { topology: topology.default, object: "subdistricts" },
71
+ getId: (feature) => String(feature.properties?.id),
72
+ getLabel: (feature) => String(feature.properties?.name),
73
+ getValue: (feature) => subDistrictValues[String(feature.properties?.id)] ?? null,
74
+ };
75
+ }}
76
+ />
77
+ ```
78
+
79
+ Return `null` for a district that has no sub-district level. Not every district has
80
+ one, and a district that returns `null` is left as a leaf — the map stays on the
81
+ district view and selects it, rather than opening a level with nothing in it. Once a
82
+ district has answered `null` it stops offering the level and is not asked again.
83
+
84
+ Without `loadSubDistricts`, a district is a leaf and activation only selects it,
85
+ exactly as before.
86
+
87
+ The breadcrumb gains a third segment. Its back step goes up exactly one level;
88
+ "All states" is the one-step return to the national map. Controlled usage adds
89
+ `subDistrictDrillDownId` / `onSubDistrictDrillDownChange`, with
90
+ `defaultSubDistrictDrillDownId` as the uncontrolled path — and because a district ID
91
+ means nothing outside the state it came from, changing `drillDownId` clears it.
92
+
59
93
  ## Features
60
94
 
61
95
  - Keyboard-accessible regions with Enter/Space activation and focus inspection.
package/dist/index.d.ts CHANGED
@@ -56,8 +56,10 @@ interface MapRegion {
56
56
  meta?: Record<string, unknown>;
57
57
  feature: MapFeature;
58
58
  }
59
+ /** The map level a region belongs to. Sub-districts are tehsils / taluks / mandals / blocks. */
60
+ type MapLevel = "state" | "district" | "subdistrict";
59
61
  interface TooltipContext extends MapRegion {
60
- level: "state" | "district";
62
+ level: MapLevel;
61
63
  total: number;
62
64
  share: number | null;
63
65
  /** 1-based position among regions that have a value, highest first. Null when this region has no value. */
@@ -71,6 +73,16 @@ interface InsightContext extends TooltipContext {
71
73
  type DistrictLoader = (stateId: string, state: MapRegion) => Promise<MapLayer>;
72
74
  /** Lazily provides non-statistical context geometry for a selected state's district map. */
73
75
  type DistrictReferenceOverlayLoader = (stateId: string, state: MapRegion) => Promise<ReferenceOverlay | null>;
76
+ /**
77
+ * Called only after a district is activated, so sub-district geometry can be
78
+ * code-split the same way districts are.
79
+ *
80
+ * Return `null` for a district that has no sub-district level. Not every district
81
+ * has one — a boundary source can omit them, or hold none that fall inside the
82
+ * district at all — and a district that returns `null` is left as a leaf: the map
83
+ * stays on the district view and selects it, rather than opening an empty level.
84
+ */
85
+ type SubDistrictLoader = (districtId: string, district: MapRegion, stateId: string) => Promise<MapLayer | null>;
74
86
  interface IndiaChoroplethProps {
75
87
  /** State/UT layer. The library does not bundle any geographic boundaries. */
76
88
  states: MapLayer;
@@ -81,6 +93,11 @@ interface IndiaChoroplethProps {
81
93
  referenceOverlay?: ReferenceOverlay;
82
94
  /** Called only after a state is requested, so district geometry can be code-split. */
83
95
  loadDistricts?: DistrictLoader;
96
+ /**
97
+ * Called only after a district is activated, enabling a third level. Without it
98
+ * a district is a leaf and activation only selects it, exactly as before.
99
+ */
100
+ loadSubDistricts?: SubDistrictLoader;
84
101
  /**
85
102
  * Optional lazy non-statistical context geometry for a district view. It is
86
103
  * keyed to the drilled state, cancelled safely on navigation, and rendered
@@ -92,17 +109,27 @@ interface IndiaChoroplethProps {
92
109
  /** Initial state drill-down when uncontrolled. */
93
110
  defaultDrillDownId?: string | null;
94
111
  onDrillDownChange?: (stateId: string | null, state?: MapRegion) => void;
95
- /** Controlled selected feature (state ID on national level; district ID when drilled in). */
112
+ /**
113
+ * Controlled district drill-down, the level below `drillDownId`. Use null for
114
+ * the district map. It is only meaningful while a state is drilled into, and is
115
+ * cleared whenever `drillDownId` changes, since a district id from one state
116
+ * means nothing in another.
117
+ */
118
+ subDistrictDrillDownId?: string | null;
119
+ /** Initial district drill-down when uncontrolled. */
120
+ defaultSubDistrictDrillDownId?: string | null;
121
+ onSubDistrictDrillDownChange?: (districtId: string | null, district?: MapRegion) => void;
122
+ /** Controlled selected feature: the id of a region at whichever level is showing. */
96
123
  selectedId?: string | null;
97
124
  defaultSelectedId?: string | null;
98
125
  /** Fires with null when a click on open sea clears the selection. */
99
- onSelectedChange?: (region: MapRegion | null, level: "state" | "district") => void;
126
+ onSelectedChange?: (region: MapRegion | null, level: MapLevel) => void;
100
127
  /** Fires for hover and keyboard focus with the same shape payload. */
101
- onInspect?: (region: MapRegion | null, level: "state" | "district") => void;
128
+ onInspect?: (region: MapRegion | null, level: MapLevel) => void;
102
129
  /** Receives tooltip-ready data, including the current scope total and share. */
103
130
  onInsight?: (context: InsightContext | null) => void;
104
131
  /** Receives every activation before state drill-down / district selection. */
105
- onRegionClick?: (region: MapRegion, level: "state" | "district") => void;
132
+ onRegionClick?: (region: MapRegion, level: MapLevel) => void;
106
133
  /**
107
134
  * Called for a click that hit no region and was not close enough to a small
108
135
  * one. The component also clears its own uncontrolled selection on such a
@@ -153,6 +180,9 @@ interface IndiaChoroplethProps {
153
180
  /**
154
181
  * Like [minPartExtent], but applied only after a state has been drilled into.
155
182
  * When omitted, district layers inherit minPartExtent for backward compatibility.
183
+ *
184
+ * Sub-district layers inherit this same value rather than taking a knob of their
185
+ * own: they are drawn at the same zoom as districts and want the same treatment.
156
186
  */
157
187
  minDistrictPartExtent?: number;
158
188
  className?: string;
@@ -165,6 +195,6 @@ interface IndiaChoroplethProps {
165
195
  * A data-agnostic, accessible SVG India map renderer. Import `@india-choropleth/react/style.css`
166
196
  * once in the host app; data and boundaries intentionally remain separate.
167
197
  */
168
- declare function IndiaChoropleth({ states, referenceOverlay, loadDistricts, loadDistrictReferenceOverlay, drillDownId, defaultDrillDownId, onDrillDownChange, selectedId, defaultSelectedId, onSelectedChange, onInspect, onInsight, onRegionClick, onBackgroundClick, colorScale, formatValue, renderTooltip, renderInsights, showLegend, showBreadcrumb, legendLabels, referenceOverlayLegendLabel, referenceOverlayMergeIds, referenceOverlayFill, showRegionValues, minPartExtent, minDistrictPartExtent, className, ariaLabel, interactive, }: IndiaChoroplethProps): react.JSX.Element;
198
+ declare function IndiaChoropleth({ states, referenceOverlay, loadDistricts, loadSubDistricts, loadDistrictReferenceOverlay, drillDownId, defaultDrillDownId, onDrillDownChange, subDistrictDrillDownId, defaultSubDistrictDrillDownId, onSubDistrictDrillDownChange, selectedId, defaultSelectedId, onSelectedChange, onInspect, onInsight, onRegionClick, onBackgroundClick, colorScale, formatValue, renderTooltip, renderInsights, showLegend, showBreadcrumb, legendLabels, referenceOverlayLegendLabel, referenceOverlayMergeIds, referenceOverlayFill, showRegionValues, minPartExtent, minDistrictPartExtent, className, ariaLabel, interactive, }: IndiaChoroplethProps): react.JSX.Element;
169
199
 
170
- export { type ColorContext, type ColorScale, type DistrictLoader, type DistrictReferenceOverlayLoader, type GeometrySource, IndiaChoropleth, type IndiaChoroplethProps, type InsightContext, type MapFeature, type MapFeatureCollection, type MapLayer, type MapRegion, type ReferenceOverlay, type TooltipContext };
200
+ export { type ColorContext, type ColorScale, type DistrictLoader, type DistrictReferenceOverlayLoader, type GeometrySource, IndiaChoropleth, type IndiaChoroplethProps, type InsightContext, type MapFeature, type MapFeatureCollection, type MapLayer, type MapLevel, type MapRegion, type ReferenceOverlay, type SubDistrictLoader, type TooltipContext };
package/dist/index.js CHANGED
@@ -334,10 +334,14 @@ function IndiaChoropleth({
334
334
  states,
335
335
  referenceOverlay,
336
336
  loadDistricts,
337
+ loadSubDistricts,
337
338
  loadDistrictReferenceOverlay,
338
339
  drillDownId,
339
340
  defaultDrillDownId = null,
340
341
  onDrillDownChange,
342
+ subDistrictDrillDownId,
343
+ defaultSubDistrictDrillDownId = null,
344
+ onSubDistrictDrillDownChange,
341
345
  selectedId,
342
346
  defaultSelectedId = null,
343
347
  onSelectedChange,
@@ -367,11 +371,22 @@ function IndiaChoropleth({
367
371
  const tooltipAnchorRef = useRef(null);
368
372
  const hatchId = `${useId()}-reference-hatch`;
369
373
  const [activeDrillDownId, setActiveDrillDownId] = useControllableState(drillDownId, defaultDrillDownId);
374
+ const [activeSubDrillDownId, setActiveSubDrillDownId] = useControllableState(subDistrictDrillDownId, defaultSubDistrictDrillDownId);
370
375
  const [activeSelectedId, setActiveSelectedId] = useControllableState(selectedId, defaultSelectedId);
371
376
  const [loadedDistricts, setLoadedDistricts] = useState2(null);
377
+ const [loadedSubDistricts, setLoadedSubDistricts] = useState2(null);
378
+ const [leafDistrictIds, setLeafDistrictIds] = useState2(() => /* @__PURE__ */ new Set());
379
+ const priorSubDistrictLoader = useRef(loadSubDistricts);
380
+ useEffect(() => {
381
+ if (priorSubDistrictLoader.current === loadSubDistricts) return;
382
+ priorSubDistrictLoader.current = loadSubDistricts;
383
+ setLeafDistrictIds(/* @__PURE__ */ new Set());
384
+ }, [loadSubDistricts]);
372
385
  const [loadedDistrictReferenceOverlay, setLoadedDistrictReferenceOverlay] = useState2(null);
373
386
  const [loadingState, setLoadingState] = useState2(null);
387
+ const [loadingDistrict, setLoadingDistrict] = useState2(null);
374
388
  const [loadError, setLoadError] = useState2(null);
389
+ const [subLoadError, setSubLoadError] = useState2(null);
375
390
  const pathRefs = useRef({});
376
391
  const restoreFocusId = useRef(null);
377
392
  const stateCollection = useMemo(() => asFeatureCollection(states.geometry), [states]);
@@ -393,7 +408,6 @@ function IndiaChoropleth({
393
408
  [activeDrillDownId, stateRegions]
394
409
  );
395
410
  const isDrillRequested = Boolean(drilledState && activeDrillDownId);
396
- const level = isDrillRequested ? "district" : "state";
397
411
  const districtLayer = loadedDistricts?.stateId === activeDrillDownId ? loadedDistricts.layer : null;
398
412
  const districtReferenceOverlay = loadedDistrictReferenceOverlay?.stateId === activeDrillDownId ? loadedDistrictReferenceOverlay.overlay : null;
399
413
  const districtCollection = useMemo(
@@ -408,10 +422,30 @@ function IndiaChoropleth({
408
422
  () => districtCollection ? makeProjection({ type: "FeatureCollection", features: [...districtCollection.features, ...districtReferenceCollection?.features ?? []] }) : null,
409
423
  [districtCollection, districtReferenceCollection]
410
424
  );
411
- const regions = useMemo(
412
- () => level === "district" && districtLayer && districtProjection ? prepareLayer(districtLayer, districtProjection, minDistrictPartExtent ?? minPartExtent) : level === "state" ? stateRegions : [],
413
- [districtLayer, districtProjection, level, minDistrictPartExtent, minPartExtent, stateRegions]
425
+ const districtRegions = useMemo(
426
+ () => districtLayer && districtProjection ? prepareLayer(districtLayer, districtProjection, minDistrictPartExtent ?? minPartExtent) : [],
427
+ [districtLayer, districtProjection, minDistrictPartExtent, minPartExtent]
428
+ );
429
+ const drilledDistrict = useMemo(
430
+ () => districtRegions.find((region) => region.id === activeSubDrillDownId) ?? null,
431
+ [activeSubDrillDownId, districtRegions]
432
+ );
433
+ const isSubDrillRequested = Boolean(isDrillRequested && drilledDistrict && activeSubDrillDownId);
434
+ const level = isSubDrillRequested ? "subdistrict" : isDrillRequested ? "district" : "state";
435
+ const subDistrictLayer = loadedSubDistricts?.districtId === activeSubDrillDownId ? loadedSubDistricts.layer : null;
436
+ const subDistrictCollection = useMemo(
437
+ () => subDistrictLayer ? asFeatureCollection(subDistrictLayer.geometry) : null,
438
+ [subDistrictLayer]
439
+ );
440
+ const subDistrictProjection = useMemo(
441
+ () => subDistrictCollection ? makeProjection(subDistrictCollection) : null,
442
+ [subDistrictCollection]
414
443
  );
444
+ const subDistrictRegions = useMemo(
445
+ () => subDistrictLayer && subDistrictProjection ? prepareLayer(subDistrictLayer, subDistrictProjection, minDistrictPartExtent ?? minPartExtent) : [],
446
+ [minDistrictPartExtent, minPartExtent, subDistrictLayer, subDistrictProjection]
447
+ );
448
+ const regions = level === "subdistrict" ? subDistrictRegions : level === "district" ? districtRegions : stateRegions;
415
449
  const districtReferenceRegions = useMemo(
416
450
  () => level === "district" && districtReferenceOverlay && districtProjection ? prepareReferenceOverlay(districtReferenceOverlay, districtProjection) : [],
417
451
  [districtProjection, districtReferenceOverlay, level]
@@ -434,7 +468,7 @@ function IndiaChoropleth({
434
468
  const colorScaleKey = typeof colorScale === "function" ? "function" : colorScale.join(",");
435
469
  useEffect(() => {
436
470
  setActiveBucket(null);
437
- }, [activeDrillDownId, colorScaleKey, level]);
471
+ }, [activeDrillDownId, activeSubDrillDownId, colorScaleKey, level]);
438
472
  const highlighted = useMemo(
439
473
  () => filterBucket === null ? null : new Set(regions.filter((region) => swatchIndexOf(region.value, min, max, legendColors.length) === filterBucket).map((region) => region.id)),
440
474
  [filterBucket, legendColors.length, max, min, regions]
@@ -482,9 +516,48 @@ function IndiaChoropleth({
482
516
  cancelled = true;
483
517
  };
484
518
  }, [activeDrillDownId, loadDistrictReferenceOverlay, stateRegions]);
519
+ useEffect(() => {
520
+ let cancelled = false;
521
+ if (!activeSubDrillDownId || !loadSubDistricts) {
522
+ setLoadedSubDistricts(null);
523
+ setLoadingDistrict(null);
524
+ setSubLoadError(null);
525
+ return;
526
+ }
527
+ const sourceDistrict = districtRegions.find((region) => region.id === activeSubDrillDownId);
528
+ if (!sourceDistrict || !activeDrillDownId) return;
529
+ setLoadingDistrict(activeSubDrillDownId);
530
+ setSubLoadError(null);
531
+ setLoadedSubDistricts(null);
532
+ loadSubDistricts(activeSubDrillDownId, sourceDistrict, activeDrillDownId).then((loaded) => {
533
+ if (cancelled) return;
534
+ if (!loaded) {
535
+ setLeafDistrictIds((known) => known.has(activeSubDrillDownId) ? known : new Set(known).add(activeSubDrillDownId));
536
+ setActiveSubDrillDownId(null);
537
+ setActiveSelectedId(sourceDistrict.id);
538
+ onSubDistrictDrillDownChange?.(null, sourceDistrict);
539
+ return;
540
+ }
541
+ setLoadedSubDistricts({ districtId: activeSubDrillDownId, layer: loaded });
542
+ }).catch((error) => {
543
+ if (!cancelled) setSubLoadError(error instanceof Error ? error : new Error("Unable to load sub-districts."));
544
+ }).finally(() => {
545
+ if (!cancelled) setLoadingDistrict(null);
546
+ });
547
+ return () => {
548
+ cancelled = true;
549
+ };
550
+ }, [activeDrillDownId, activeSubDrillDownId, districtRegions, loadSubDistricts]);
551
+ const priorDrillDownId = useRef(activeDrillDownId);
552
+ useEffect(() => {
553
+ if (priorDrillDownId.current === activeDrillDownId) return;
554
+ priorDrillDownId.current = activeDrillDownId;
555
+ setActiveSubDrillDownId(null);
556
+ }, [activeDrillDownId]);
485
557
  useEffect(() => {
486
558
  const regionId = restoreFocusId.current;
487
- if (!regionId || level !== "state") return;
559
+ if (!regionId) return;
560
+ if (!regions.some((region) => region.id === regionId)) return;
488
561
  restoreFocusId.current = null;
489
562
  pathRefs.current[regionId]?.focus();
490
563
  }, [level, regions]);
@@ -510,17 +583,46 @@ function IndiaChoropleth({
510
583
  setActiveSelectedId(null);
511
584
  setActiveDrillDownId(region.id);
512
585
  onDrillDownChange?.(region.id, region);
586
+ return;
587
+ }
588
+ if (level === "district" && loadSubDistricts && !leafDistrictIds.has(region.id)) {
589
+ setActiveSelectedId(null);
590
+ setActiveSubDrillDownId(region.id);
591
+ onSubDistrictDrillDownChange?.(region.id, region);
513
592
  }
514
593
  };
515
594
  const goBack = () => {
595
+ if (level === "subdistrict") {
596
+ const priorDistrict = drilledDistrict ?? void 0;
597
+ setActiveSubDrillDownId(null);
598
+ setActiveSelectedId(priorDistrict?.id ?? null);
599
+ inspectedIdRef.current = priorDistrict?.id ?? null;
600
+ setInspectedId(priorDistrict?.id ?? null);
601
+ restoreFocusId.current = priorDistrict?.id ?? null;
602
+ onSubDistrictDrillDownChange?.(null, priorDistrict);
603
+ return;
604
+ }
516
605
  const priorState = drilledState ?? void 0;
517
606
  setActiveDrillDownId(null);
607
+ setActiveSubDrillDownId(null);
518
608
  setActiveSelectedId(priorState?.id ?? null);
519
609
  inspectedIdRef.current = priorState?.id ?? null;
520
610
  setInspectedId(priorState?.id ?? null);
521
611
  restoreFocusId.current = priorState?.id ?? null;
522
612
  onDrillDownChange?.(null, priorState);
523
613
  };
614
+ const goToStates = () => {
615
+ const priorState = drilledState ?? void 0;
616
+ const wasDrilledDistrict = drilledDistrict ?? void 0;
617
+ setActiveSubDrillDownId(null);
618
+ setActiveDrillDownId(null);
619
+ setActiveSelectedId(priorState?.id ?? null);
620
+ inspectedIdRef.current = priorState?.id ?? null;
621
+ setInspectedId(priorState?.id ?? null);
622
+ restoreFocusId.current = priorState?.id ?? null;
623
+ if (wasDrilledDistrict) onSubDistrictDrillDownChange?.(null, wasDrilledDistrict);
624
+ onDrillDownChange?.(null, priorState);
625
+ };
524
626
  const toTooltipContext = useCallback2((region) => {
525
627
  const valued = regions.filter((candidate) => candidate.value !== null);
526
628
  const rank = region.value === null ? null : valued.filter((candidate) => (candidate.value ?? 0) > (region.value ?? 0)).length + 1;
@@ -623,7 +725,9 @@ function IndiaChoropleth({
623
725
  onSelectedChange?.(null, level);
624
726
  }
625
727
  };
626
- const canDrill = Boolean(loadDistricts && level === "state");
728
+ const canDrill = level === "state" ? Boolean(loadDistricts) : level === "district" ? Boolean(loadSubDistricts) : false;
729
+ const drillActionLabel = level === "state" ? "Activate to view districts." : "Activate to view sub-districts.";
730
+ const regionCanDrill = (id) => canDrill && !(level === "district" && leafDistrictIds.has(id));
627
731
  const visibleReferenceRegions = level === "state" ? referenceRegions : districtReferenceRegions;
628
732
  const mergedReferenceIds = useMemo(() => new Set(referenceOverlayMergeIds), [referenceOverlayMergeIds]);
629
733
  useEffect(() => {
@@ -642,16 +746,20 @@ function IndiaChoropleth({
642
746
  if (dx !== 0) anchor.style.setProperty("--india-map-tooltip-dx", `${Math.round(dx)}px`);
643
747
  if (side === "below") anchor.style.setProperty("--india-map-tooltip-dy", `${TOOLTIP_GAP_PX}px`);
644
748
  }, [tooltipContext?.id, tooltipContext?.value, tooltipContext?.label, renderTooltip]);
645
- return /* @__PURE__ */ jsxs("section", { className: ["india-choropleth", !interactive && "india-choropleth--static", className].filter(Boolean).join(" "), "aria-busy": loadingState ? "true" : void 0, children: [
749
+ return /* @__PURE__ */ jsxs("section", { className: ["india-choropleth", !interactive && "india-choropleth--static", className].filter(Boolean).join(" "), "aria-busy": loadingState || loadingDistrict ? "true" : void 0, children: [
646
750
  showBreadcrumb ? /* @__PURE__ */ jsx("div", { className: "india-choropleth__toolbar", children: /* @__PURE__ */ jsxs("nav", { className: "india-choropleth__breadcrumb", "aria-label": "Map hierarchy", children: [
647
- drilledState ? /* @__PURE__ */ jsx("button", { className: "india-choropleth__back", type: "button", onClick: goBack, children: "All states" }) : /* @__PURE__ */ jsx("span", { children: "All states" }),
751
+ drilledState ? /* @__PURE__ */ jsx("button", { className: "india-choropleth__back", type: "button", onClick: goToStates, children: "All states" }) : /* @__PURE__ */ jsx("span", { children: "All states" }),
648
752
  drilledState ? /* @__PURE__ */ jsxs(Fragment, { children: [
649
753
  /* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: "/" }),
650
- /* @__PURE__ */ jsx("span", { "aria-current": "page", children: drilledState.label })
754
+ isSubDrillRequested ? /* @__PURE__ */ jsx("button", { className: "india-choropleth__back", type: "button", onClick: goBack, children: drilledState.label }) : /* @__PURE__ */ jsx("span", { "aria-current": "page", children: drilledState.label })
755
+ ] }) : null,
756
+ isSubDrillRequested && drilledDistrict ? /* @__PURE__ */ jsxs(Fragment, { children: [
757
+ /* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: "/" }),
758
+ /* @__PURE__ */ jsx("span", { "aria-current": "page", children: drilledDistrict.label })
651
759
  ] }) : null
652
760
  ] }) }) : null,
653
761
  /* @__PURE__ */ jsxs("div", { ref: canvasRef, className: "india-choropleth__canvas", onMouseLeave: interactive ? () => inspect(null) : void 0, children: [
654
- isDrillRequested && (!districtLayer || loadError) ? /* @__PURE__ */ jsx("div", { className: "india-choropleth__status", role: loadError ? "alert" : "status", children: loadError ? loadError.message : loadingState ? "Loading districts\u2026" : "District data is unavailable for this state." }) : regions.length === 0 ? /* @__PURE__ */ jsx("div", { className: "india-choropleth__status", role: "status", children: "No district data is available for this state." }) : /* @__PURE__ */ jsxs(
762
+ isSubDrillRequested && (!subDistrictLayer || subLoadError) ? /* @__PURE__ */ jsx("div", { className: "india-choropleth__status", role: subLoadError ? "alert" : "status", children: subLoadError ? subLoadError.message : loadingDistrict ? "Loading sub-districts\u2026" : "Sub-district data is unavailable for this district." }) : isDrillRequested && (!districtLayer || loadError) ? /* @__PURE__ */ jsx("div", { className: "india-choropleth__status", role: loadError ? "alert" : "status", children: loadError ? loadError.message : loadingState ? "Loading districts\u2026" : "District data is unavailable for this state." }) : regions.length === 0 ? /* @__PURE__ */ jsx("div", { className: "india-choropleth__status", role: "status", children: level === "subdistrict" ? "No sub-district data is available for this district." : "No district data is available for this state." }) : /* @__PURE__ */ jsxs(
655
763
  "svg",
656
764
  {
657
765
  className: `india-choropleth__svg${filterBucket !== null ? " india-choropleth__svg--filtered" : ""}`,
@@ -698,7 +806,7 @@ function IndiaChoropleth({
698
806
  regions.map((region) => {
699
807
  const isInspected = region.id === inspected?.id;
700
808
  const isSelected = region.id === selected?.id;
701
- const action = canDrill ? "Activate to view districts." : "Activate to select.";
809
+ const action = regionCanDrill(region.id) ? drillActionLabel : "Activate to select.";
702
810
  const textValue = region.value === null ? "No data" : formatValue(region.value);
703
811
  return /* @__PURE__ */ jsx(
704
812
  "path",
@@ -755,7 +863,7 @@ function IndiaChoropleth({
755
863
  },
756
864
  p.region.id
757
865
  )) }),
758
- /* @__PURE__ */ jsx("g", { className: `india-choropleth__region-values${level === "district" ? " india-choropleth__region-values--district" : ""}`, "aria-hidden": "true", children: valuePlacements.map((p) => /* @__PURE__ */ jsx(
866
+ /* @__PURE__ */ jsx("g", { className: `india-choropleth__region-values${level === "state" ? "" : " india-choropleth__region-values--district"}`, "aria-hidden": "true", children: valuePlacements.map((p) => /* @__PURE__ */ jsx(
759
867
  "text",
760
868
  {
761
869
  className: isDimmed(p.region.id) ? "india-choropleth__dimmed" : void 0,
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/IndiaChoropleth.tsx","../src/geometry.ts","../src/legend.ts","../src/tooltip-position.ts","../src/small-regions.ts","../src/useControllableState.ts"],"sourcesContent":["import { geoMercator, geoPath, type GeoProjection } from \"d3-geo\";\nimport { useCallback, useEffect, useId, useLayoutEffect, useMemo, useRef, useState, type MouseEvent as ReactMouseEvent } from \"react\";\nimport { asFeatureCollection, totalOf } from \"./geometry\";\nimport { legendBucketLabel, legendBuckets, swatchIndexOf } from \"./legend\";\nimport { placeTooltip } from \"./tooltip-position\";\nimport {\n boundsOfRing,\n distanceToParts,\n keepsTrueGeometry,\n labelPointFor,\n enlargeSmallParts,\n largestRingExtent,\n placeOutsideLabel,\n ringsToPath,\n scatteredHitArea,\n type Box,\n type Point,\n} from \"./small-regions\";\nimport type {\n ColorContext,\n ColorScale,\n IndiaChoroplethProps,\n InsightContext,\n MapFeature,\n MapFeatureCollection,\n MapLayer,\n MapRegion,\n ReferenceOverlay,\n TooltipContext,\n} from \"./types\";\nimport { useControllableState } from \"./useControllableState\";\n\nconst VIEWBOX = { width: 960, height: 640, padding: 28 };\nconst DEFAULT_COLORS = [\"#d9f1ed\", \"#b9e3dd\", \"#8fd1c8\", \"#5bb9ae\", \"#2f9c90\", \"#147b71\", \"#075b55\"] as const;\nconst DEFAULT_FORMAT = new Intl.NumberFormat(\"en-IN\").format;\n// Space between the region centroid and the tooltip edge. Mirrors the .75rem in style.css.\nconst TOOLTIP_GAP_PX = 12;\n// Small-region handling, in view-box units. Mirrors the DOM and Dart renderers.\nconst SMALL_REGION_EXTENT = 22;\nconst SMALL_REGION_CLICK_RADIUS = 14;\nconst MIN_REGION_MARKER_SIZE = 7;\n\ntype PreparedRegion = MapRegion & {\n path: string;\n /**\n * Hull covering a scattered region's parts and the space between them, drawn\n * invisibly under every outline so hovering the water inside Lakshadweep\n * reaches Lakshadweep. Null for regions that are one part or big enough to\n * point at directly.\n */\n hitPath: string | null;\n centroid: [number, number];\n /** Bounding box of each separate part, for measuring how close a click landed. */\n partBounds: Box[];\n /** Longest side of the largest part — the measure of \"too small to use\". */\n extent: number;\n};\n\n/**\n * Project a feature's rings into view-box coordinates. Needed alongside the SVG\n * path string because the small-region helpers work on coordinates, and a path\n * string cannot be measured per part.\n */\nfunction projectedRings(feature: MapFeature, projection: GeoProjection): Point[][] {\n const geometry = feature.geometry;\n if (!geometry) return [];\n const polygons =\n geometry.type === \"Polygon\" ? [geometry.coordinates]\n : geometry.type === \"MultiPolygon\" ? geometry.coordinates\n : [];\n const rings: Point[][] = [];\n for (const polygon of polygons) {\n for (const ring of polygon) {\n const projected: Point[] = [];\n for (const position of ring) {\n const point = projection(position as [number, number]);\n if (point && Number.isFinite(point[0]) && Number.isFinite(point[1])) projected.push([point[0], point[1]]);\n }\n if (projected.length > 0) rings.push(projected);\n }\n }\n return rings;\n}\ntype PreparedReferenceOverlay = { id: string; label: string; description: string; path: string };\n\nfunction colorFor(value: number | null, region: MapRegion, min: number, max: number, scale: ColorScale): string {\n if (typeof scale === \"function\") {\n const context: ColorContext = { min, max, feature: region.feature, id: region.id };\n return scale(value, context);\n }\n // The same index the legend filters by, deliberately: \"highlight the regions\n // painted in this colour\" has to be true by construction, not by two formulas\n // that happen to agree until one of them is tweaked.\n const index = swatchIndexOf(value, min, max, scale.length);\n return index === null ? \"var(--india-map-empty)\" : scale[index] ?? \"var(--india-map-empty)\";\n}\n\nfunction makeProjection(collection: MapFeatureCollection): GeoProjection {\n return geoMercator().fitExtent(\n [[VIEWBOX.padding, VIEWBOX.padding], [VIEWBOX.width - VIEWBOX.padding, VIEWBOX.height - VIEWBOX.padding]],\n collection,\n );\n}\n\nfunction prepareLayer(\n layer: MapLayer,\n projection = makeProjection(asFeatureCollection(layer.geometry)),\n minPartExtent = 0,\n): PreparedRegion[] {\n const collection = asFeatureCollection(layer.geometry);\n const path = geoPath(projection);\n return collection.features.map((feature) => {\n const centroid = path.centroid(feature) as [number, number];\n const bounds = path.bounds(feature);\n const fallbackCentroid: [number, number] = [\n (bounds[0][0] + bounds[1][0]) / 2,\n (bounds[0][1] + bounds[1][1]) / 2,\n ];\n const region: MapRegion = {\n id: layer.getId(feature),\n label: layer.getLabel(feature),\n value: layer.getValue(feature),\n meta: layer.getMeta?.(feature),\n feature,\n };\n // Puducherry and anything else on the keep-true list is drawn as it really\n // is, however small, because there is no room around it to grow into.\n const exaggerate = minPartExtent > 0 && !keepsTrueGeometry(region.id);\n const rings = exaggerate\n ? enlargeSmallParts(projectedRings(feature, projection), minPartExtent)\n : projectedRings(feature, projection);\n const fallback: [number, number] = centroid.every(Number.isFinite) ? centroid : fallbackCentroid;\n const hull = scatteredHitArea(rings, SMALL_REGION_EXTENT);\n // With exaggeration on, the drawn outline has to come from the moved rings\n // rather than d3's path generator, so what is drawn, measured, labelled and\n // clicked are all the same geometry.\n return {\n ...region,\n path: exaggerate ? ringsToPath(rings) : (path(feature) ?? \"\"),\n hitPath: hull ? ringsToPath([hull]) : null,\n // The largest part's centroid, not the whole feature's: averaging across\n // parts puts an island group's label out at sea between its islands.\n centroid: rings.length > 0 ? (labelPointFor(rings, fallback) as [number, number]) : fallback,\n partBounds: rings.map(boundsOfRing),\n extent: largestRingExtent(rings),\n };\n });\n}\n\nfunction prepareReferenceOverlay(overlay: ReferenceOverlay, projection: GeoProjection): PreparedReferenceOverlay[] {\n const path = geoPath(projection);\n return asFeatureCollection(overlay.geometry).features.map((feature) => ({\n id: overlay.getId(feature),\n label: overlay.getLabel(feature),\n description: overlay.getDescription(feature),\n path: path(feature) ?? \"\",\n }));\n}\n\nfunction ordinal(n: number): string {\n const lastTwo = n % 100;\n if (lastTwo >= 11 && lastTwo <= 13) return `${n}th`;\n return `${n}${[\"th\", \"st\", \"nd\", \"rd\"][n % 10] ?? \"th\"}`;\n}\n\nfunction defaultTooltip(context: TooltipContext, formatValue: (value: number) => string) {\n return (\n <>\n <strong>{context.label}</strong>\n {/* Same wording as the region's own aria-label, so the two never disagree. */}\n <b>{context.value === null ? \"No data\" : formatValue(context.value)}</b>\n {context.share !== null ? (\n <>\n {/* A bar makes the share readable at a glance; it repeats the number\n beside it, so it's decorative and hidden from assistive tech. */}\n <span className=\"india-choropleth__tooltip-bar\" aria-hidden=\"true\">\n <span style={{ width: `${Math.max(context.share, 1.5)}%` }} />\n </span>\n <small>\n {[\n `${context.share.toFixed(1)}% of total`,\n ...(context.rank !== null ? [`${ordinal(context.rank)} of ${context.rankedCount}`] : []),\n ].join(\" · \")}\n </small>\n </>\n ) : null}\n </>\n );\n}\n\n/**\n * A data-agnostic, accessible SVG India map renderer. Import `@india-choropleth/react/style.css`\n * once in the host app; data and boundaries intentionally remain separate.\n */\nexport function IndiaChoropleth({\n states,\n referenceOverlay,\n loadDistricts,\n loadDistrictReferenceOverlay,\n drillDownId,\n defaultDrillDownId = null,\n onDrillDownChange,\n selectedId,\n defaultSelectedId = null,\n onSelectedChange,\n onInspect,\n onInsight,\n onRegionClick,\n onBackgroundClick,\n colorScale = DEFAULT_COLORS,\n formatValue = DEFAULT_FORMAT,\n renderTooltip,\n renderInsights,\n showLegend = true,\n showBreadcrumb = true,\n legendLabels = [\"Lower\", \"Higher\"],\n referenceOverlayLegendLabel = \"Reference context · data unavailable\",\n referenceOverlayMergeIds = [],\n referenceOverlayFill = \"hatch\",\n showRegionValues = false,\n minPartExtent = 0,\n minDistrictPartExtent,\n className,\n ariaLabel = \"Interactive choropleth map\",\n interactive = true,\n}: IndiaChoroplethProps) {\n const tooltipId = useId();\n const canvasRef = useRef<HTMLDivElement | null>(null);\n const tooltipAnchorRef = useRef<HTMLDivElement | null>(null);\n const hatchId = `${useId()}-reference-hatch`;\n const [activeDrillDownId, setActiveDrillDownId] = useControllableState(drillDownId, defaultDrillDownId);\n const [activeSelectedId, setActiveSelectedId] = useControllableState(selectedId, defaultSelectedId);\n const [loadedDistricts, setLoadedDistricts] = useState<{ stateId: string; layer: MapLayer } | null>(null);\n const [loadedDistrictReferenceOverlay, setLoadedDistrictReferenceOverlay] = useState<{ stateId: string; overlay: ReferenceOverlay | null } | null>(null);\n const [loadingState, setLoadingState] = useState<string | null>(null);\n const [loadError, setLoadError] = useState<Error | null>(null);\n const pathRefs = useRef<Record<string, SVGPathElement | null>>({});\n const restoreFocusId = useRef<string | null>(null);\n\n const stateCollection = useMemo(() => asFeatureCollection(states.geometry), [states]);\n const referenceCollection = useMemo(\n () => referenceOverlay ? asFeatureCollection(referenceOverlay.geometry) : null,\n [referenceOverlay],\n );\n const nationalProjection = useMemo(\n () => makeProjection({ type: \"FeatureCollection\", features: [...stateCollection.features, ...(referenceCollection?.features ?? [])] }),\n [referenceCollection, stateCollection],\n );\n const stateRegions = useMemo(() => prepareLayer(states, nationalProjection, minPartExtent), [minPartExtent, nationalProjection, states]);\n const referenceRegions = useMemo(\n () => referenceOverlay ? prepareReferenceOverlay(referenceOverlay, nationalProjection) : [],\n [nationalProjection, referenceOverlay],\n );\n const drilledState = useMemo(\n () => stateRegions.find((region) => region.id === activeDrillDownId) ?? null,\n [activeDrillDownId, stateRegions],\n );\n const isDrillRequested = Boolean(drilledState && activeDrillDownId);\n const level = isDrillRequested ? \"district\" : \"state\";\n const districtLayer = loadedDistricts?.stateId === activeDrillDownId ? loadedDistricts.layer : null;\n const districtReferenceOverlay = loadedDistrictReferenceOverlay?.stateId === activeDrillDownId\n ? loadedDistrictReferenceOverlay.overlay\n : null;\n const districtCollection = useMemo(\n () => districtLayer ? asFeatureCollection(districtLayer.geometry) : null,\n [districtLayer],\n );\n const districtReferenceCollection = useMemo(\n () => districtReferenceOverlay ? asFeatureCollection(districtReferenceOverlay.geometry) : null,\n [districtReferenceOverlay],\n );\n const districtProjection = useMemo(\n () => districtCollection ? makeProjection({ type: \"FeatureCollection\", features: [...districtCollection.features, ...(districtReferenceCollection?.features ?? [])] }) : null,\n [districtCollection, districtReferenceCollection],\n );\n const regions = useMemo(\n () => level === \"district\" && districtLayer && districtProjection\n ? prepareLayer(districtLayer, districtProjection, minDistrictPartExtent ?? minPartExtent)\n : level === \"state\" ? stateRegions : [],\n [districtLayer, districtProjection, level, minDistrictPartExtent, minPartExtent, stateRegions],\n );\n const districtReferenceRegions = useMemo(\n () => level === \"district\" && districtReferenceOverlay && districtProjection ? prepareReferenceOverlay(districtReferenceOverlay, districtProjection) : [],\n [districtProjection, districtReferenceOverlay, level],\n );\n const selected = regions.find((region) => region.id === activeSelectedId) ?? null;\n const [inspectedId, setInspectedId] = useState<string | null>(null);\n const inspectedIdRef = useRef<string | null>(null);\n // Hover/focus only — no fallback to `selected`, so the floating tooltip clears\n // when the pointer/focus leaves instead of sticking on the selected region.\n const inspected = regions.find((region) => region.id === inspectedId) ?? null;\n\n const values = useMemo(() => regions.map((region) => region.value).filter((value): value is number => value !== null), [regions]);\n const total = useMemo(() => totalOf(values), [values]);\n const min = values.length ? Math.min(...values) : 0;\n const max = values.length ? Math.max(...values) : 0;\n\n // The legend doubles as a filter. A function colour scale has no swatches of\n // its own, so the default ramp stands in and the bands still read low to high.\n const legendColors = typeof colorScale === \"function\" ? DEFAULT_COLORS : colorScale;\n const buckets = useMemo(\n () => legendBuckets(legendColors, regions.map((region) => region.value), min, max),\n [legendColors, max, min, regions],\n );\n const [activeBucket, setActiveBucket] = useState<number | null>(null);\n // Clamped here rather than only in the effect below: effects run after paint,\n // so a stale index would dim against the previous level's bands for a frame.\n const filterBucket = activeBucket !== null && activeBucket < buckets.length ? activeBucket : null;\n // Keyed on the ramp's contents, not its identity — a host passing an inline\n // array literal would otherwise clear the filter on every re-render.\n const colorScaleKey = typeof colorScale === \"function\" ? \"function\" : colorScale.join(\",\");\n // Bands come from this level's own min and max, and change with the ramp, so an\n // index picked under one of them means something else under another.\n useEffect(() => { setActiveBucket(null); }, [activeDrillDownId, colorScaleKey, level]);\n\n const highlighted = useMemo(\n () => filterBucket === null\n ? null\n : new Set(regions\n .filter((region) => swatchIndexOf(region.value, min, max, legendColors.length) === filterBucket)\n .map((region) => region.id)),\n [filterBucket, legendColors.length, max, min, regions],\n );\n // Dimmed regions stay hoverable: the filter is about where the eye goes, and a\n // region you can see is a region whose number should still be reachable.\n const isDimmed = (id: string) => highlighted !== null && !highlighted.has(id);\n\n useEffect(() => {\n let cancelled = false;\n if (!activeDrillDownId || !loadDistricts) {\n setLoadedDistricts(null);\n setLoadingState(null);\n setLoadError(null);\n return;\n }\n const sourceState = stateRegions.find((region) => region.id === activeDrillDownId);\n if (!sourceState) return;\n setLoadingState(activeDrillDownId);\n setLoadError(null);\n setLoadedDistricts(null);\n loadDistricts(activeDrillDownId, sourceState)\n .then((loaded) => { if (!cancelled) setLoadedDistricts({ stateId: activeDrillDownId, layer: loaded }); })\n .catch((error: unknown) => { if (!cancelled) setLoadError(error instanceof Error ? error : new Error(\"Unable to load districts.\")); })\n .finally(() => { if (!cancelled) setLoadingState(null); });\n return () => { cancelled = true; };\n }, [activeDrillDownId, loadDistricts, stateRegions]);\n\n useEffect(() => {\n let cancelled = false;\n if (!activeDrillDownId || !loadDistrictReferenceOverlay) {\n setLoadedDistrictReferenceOverlay(null);\n return;\n }\n const sourceState = stateRegions.find((region) => region.id === activeDrillDownId);\n if (!sourceState) return;\n setLoadedDistrictReferenceOverlay(null);\n loadDistrictReferenceOverlay(activeDrillDownId, sourceState)\n .then((overlay) => { if (!cancelled) setLoadedDistrictReferenceOverlay({ stateId: activeDrillDownId, overlay }); })\n // Optional reference context must not prevent a usable district data view.\n .catch(() => { if (!cancelled) setLoadedDistrictReferenceOverlay({ stateId: activeDrillDownId, overlay: null }); });\n return () => { cancelled = true; };\n }, [activeDrillDownId, loadDistrictReferenceOverlay, stateRegions]);\n\n useEffect(() => {\n const regionId = restoreFocusId.current;\n if (!regionId || level !== \"state\") return;\n restoreFocusId.current = null;\n pathRefs.current[regionId]?.focus();\n }, [level, regions]);\n\n useEffect(() => {\n if (!inspectedId) return;\n if (!regions.some((region) => region.id === inspectedId)) {\n inspectedIdRef.current = null;\n setInspectedId(null);\n }\n }, [inspectedId, regions]);\n\n // Mirrors `inspectedId` synchronously so a single gesture that exits both a region\n // and the canvas doesn't report the clear twice. Deliberately limited to redundant\n // clears: re-inspecting the same region must still notify, because focus restoration\n // after breadcrumb-back re-inspects the region `goBack` already primed.\n const inspect = (region: PreparedRegion | null) => {\n const nextId = region?.id ?? null;\n if (nextId === null && inspectedIdRef.current === null) return;\n inspectedIdRef.current = nextId;\n setInspectedId(nextId);\n onInspect?.(region ?? null, level);\n };\n\n const activate = (region: PreparedRegion) => {\n onRegionClick?.(region, level);\n setActiveSelectedId(region.id);\n onSelectedChange?.(region, level);\n if (level === \"state\" && loadDistricts) {\n setActiveSelectedId(null);\n setActiveDrillDownId(region.id);\n onDrillDownChange?.(region.id, region);\n }\n };\n\n const goBack = () => {\n const priorState = drilledState ?? undefined;\n setActiveDrillDownId(null);\n setActiveSelectedId(priorState?.id ?? null);\n inspectedIdRef.current = priorState?.id ?? null;\n setInspectedId(priorState?.id ?? null);\n restoreFocusId.current = priorState?.id ?? null;\n onDrillDownChange?.(null, priorState);\n };\n\n // Shared by the tooltip and the insight panel so the two can never disagree\n // about share or rank — they only differ in which region they describe.\n const toTooltipContext = useCallback((region: PreparedRegion): TooltipContext => {\n const valued = regions.filter((candidate) => candidate.value !== null);\n // Ties share the better rank (\"2nd of 36\" twice, then 4th), which is what a\n // reader expects from a leaderboard and avoids an arbitrary tiebreak.\n const rank = region.value === null\n ? null\n : valued.filter((candidate) => (candidate.value ?? 0) > (region.value ?? 0)).length + 1;\n return {\n ...region,\n level,\n total,\n share: region.value === null || total === 0 ? null : (region.value / total) * 100,\n rank,\n rankedCount: valued.length,\n };\n }, [level, regions, total]);\n\n const tooltipContext = useMemo<TooltipContext | null>(\n () => inspected ? toTooltipContext(inspected) : null,\n [inspected, toTooltipContext],\n );\n // Unlike the tooltip, the host-owned insight panel is meant to stay informative\n // when nothing is hovered, so it still falls back to the selected region.\n const insightSource = inspected ?? selected;\n const insightContext = useMemo<InsightContext | null>(() => insightSource\n ? { ...toTooltipContext(insightSource), selected: insightSource.id === selected?.id }\n : null, [insightSource, selected?.id, toTooltipContext]);\n /**\n * Where each region's value label goes, and whether it needs a leader line.\n * Small regions are moved into clear space beside themselves; everything else\n * keeps its number at its centroid.\n */\n const valuePlacements = useMemo(() => {\n const coversAnother = (region: PreparedRegion, at: Point, halfWidth: number, halfHeight: number) => {\n const probes: Point[] = [\n at,\n [at[0] - halfWidth, at[1] - halfHeight],\n [at[0] + halfWidth, at[1] + halfHeight],\n [at[0] + halfWidth, at[1] - halfHeight],\n [at[0] - halfWidth, at[1] + halfHeight],\n ];\n return regions.some((other) => other.id !== region.id && other.partBounds.some(([minX, minY, maxX, maxY]) =>\n probes.some((probe) => probe[0] >= minX && probe[0] <= maxX && probe[1] >= minY && probe[1] <= maxY)));\n };\n\n return regions.map((region) => {\n const text = region.value === null ? \"—\" : formatValue(region.value);\n // Glyph metrics without measuring the DOM: the stylesheet sets 11px bold,\n // and digits in that face are close enough to half-em wide for placement.\n const halfWidth = Math.max(text.length * 3.1, 3);\n const halfHeight = 5.5;\n const isSmall = region.extent > 0 && region.extent < SMALL_REGION_EXTENT;\n const fitsInside = region.partBounds.some(([minX, minY, maxX, maxY]) =>\n maxX - minX >= halfWidth * 2 && maxY - minY >= halfHeight * 2);\n\n if (!isSmall && fitsInside) return { region, text, at: region.centroid as Point, leader: null };\n\n const outside = isSmall\n ? placeOutsideLabel({\n anchor: region.centroid,\n clearance: Math.max(region.extent, MIN_REGION_MARKER_SIZE) / 2 + 4 + halfWidth,\n halfSize: [halfWidth, halfHeight],\n viewBox: [VIEWBOX.width, VIEWBOX.height],\n centre: [VIEWBOX.width / 2, VIEWBOX.height / 2],\n // Moving a label out only helps if there is open space to move it into.\n // Goa and Puducherry have sea beside them; Delhi is ringed by other\n // states, so its number stays put rather than landing on a neighbour.\n isBlocked: (candidate) => coversAnother(region, candidate, halfWidth, halfHeight),\n })\n : null;\n\n if (!outside) {\n return fitsInside ? { region, text, at: region.centroid as Point, leader: null } : null;\n }\n\n const gap = Math.max(region.extent, MIN_REGION_MARKER_SIZE) / 2 + 1;\n const dx = outside[0] - region.centroid[0];\n const dy = outside[1] - region.centroid[1];\n const length = Math.hypot(dx, dy);\n if (length <= gap + halfWidth) return { region, text, at: outside, leader: null };\n return {\n region,\n text,\n at: outside,\n leader: [\n [region.centroid[0] + (dx / length) * gap, region.centroid[1] + (dy / length) * gap],\n [outside[0] - (dx / length) * (halfWidth + 1.5), outside[1] - (dy / length) * (halfWidth + 1.5)],\n ] as [Point, Point],\n };\n }).filter((placement): placement is NonNullable<typeof placement> => placement !== null);\n }, [formatValue, regions]);\n\n const smallMarkers = useMemo(\n () => regions.filter((region) => region.extent > 0 && region.extent < MIN_REGION_MARKER_SIZE),\n [regions],\n );\n\n /**\n * A click that reaches the svg itself missed every region path. Either it\n * landed near a small one — Goa, Puducherry, the island groups, all awkward to\n * hit — or it is a click on open sea, which clears the selection.\n */\n const handleBackgroundClick = (event: ReactMouseEvent<SVGSVGElement>) => {\n if (event.target !== event.currentTarget) return; // a region handled it\n // An unlaid-out svg (or a test environment that reports zero-size rects) has\n // no usable coordinates, so the proximity step is skipped — but the click is\n // still a click on the background and must clear the selection.\n const rect = event.currentTarget.getBoundingClientRect();\n const point: Point | null = rect.width > 0 && rect.height > 0\n ? (() => {\n // The svg scales its view box to fit while preserving aspect ratio, so\n // the scale is the smaller ratio and the remainder is centring.\n const scale = Math.min(rect.width / VIEWBOX.width, rect.height / VIEWBOX.height);\n return [\n (event.clientX - rect.left - (rect.width - VIEWBOX.width * scale) / 2) / scale,\n (event.clientY - rect.top - (rect.height - VIEWBOX.height * scale) / 2) / scale,\n ] as Point;\n })()\n : null;\n\n let nearest: PreparedRegion | null = null;\n if (point) {\n let nearestDistance = Infinity;\n for (const region of regions) {\n if (region.extent <= 0 || region.extent >= SMALL_REGION_EXTENT) continue;\n const distance = distanceToParts(point, region.partBounds);\n if (distance <= SMALL_REGION_CLICK_RADIUS && distance < nearestDistance) {\n nearestDistance = distance;\n nearest = region;\n }\n }\n }\n if (nearest) { activate(nearest); return; }\n\n onBackgroundClick?.();\n if (activeSelectedId !== null) {\n setActiveSelectedId(null);\n onSelectedChange?.(null, level);\n }\n };\n\n const canDrill = Boolean(loadDistricts && level === \"state\");\n const visibleReferenceRegions = level === \"state\" ? referenceRegions : districtReferenceRegions;\n const mergedReferenceIds = useMemo(() => new Set(referenceOverlayMergeIds), [referenceOverlayMergeIds]);\n\n useEffect(() => { onInsight?.(insightContext); }, [insightContext, onInsight]);\n\n // Nudge the anchored tooltip back inside the map. This runs in a layout effect,\n // not an effect, so the un-nudged position never paints — that one frame would be\n // a visible jump on exactly the edge regions this exists to fix. Keyed on the\n // content, not just the region, because the box is sized by what's in it.\n useLayoutEffect(() => {\n const anchor = tooltipAnchorRef.current;\n const canvas = canvasRef.current;\n if (!anchor || !canvas) return;\n\n // Measure the default placement (centered, above), so the correction is\n // computed against a known starting point rather than the last region's.\n anchor.style.removeProperty(\"--india-map-tooltip-dx\");\n anchor.style.removeProperty(\"--india-map-tooltip-dy\");\n\n const tooltipRect = anchor.getBoundingClientRect();\n const boundsRect = canvas.getBoundingClientRect();\n if (tooltipRect.width === 0 || boundsRect.width === 0) return; // not laid out (hidden, or jsdom)\n\n const { dx, side } = placeTooltip(tooltipRect, boundsRect, TOOLTIP_GAP_PX);\n if (dx !== 0) anchor.style.setProperty(\"--india-map-tooltip-dx\", `${Math.round(dx)}px`);\n if (side === \"below\") anchor.style.setProperty(\"--india-map-tooltip-dy\", `${TOOLTIP_GAP_PX}px`);\n }, [tooltipContext?.id, tooltipContext?.value, tooltipContext?.label, renderTooltip]);\n\n return (\n <section className={[\"india-choropleth\", !interactive && \"india-choropleth--static\", className].filter(Boolean).join(\" \")} aria-busy={loadingState ? \"true\" : undefined}>\n {showBreadcrumb ? (\n <div className=\"india-choropleth__toolbar\">\n <nav className=\"india-choropleth__breadcrumb\" aria-label=\"Map hierarchy\">\n {drilledState ? <button className=\"india-choropleth__back\" type=\"button\" onClick={goBack}>All states</button> : <span>All states</span>}\n {drilledState ? <><span aria-hidden=\"true\">/</span><span aria-current=\"page\">{drilledState.label}</span></> : null}\n </nav>\n </div>\n ) : null}\n <div ref={canvasRef} className=\"india-choropleth__canvas\" onMouseLeave={interactive ? () => inspect(null) : undefined}>\n {isDrillRequested && (!districtLayer || loadError) ? (\n <div className=\"india-choropleth__status\" role={loadError ? \"alert\" : \"status\"}>\n {loadError ? loadError.message : loadingState ? \"Loading districts…\" : \"District data is unavailable for this state.\"}\n </div>\n ) : regions.length === 0 ? (\n <div className=\"india-choropleth__status\" role=\"status\">No district data is available for this state.</div>\n ) : (\n <svg\n className={`india-choropleth__svg${filterBucket !== null ? \" india-choropleth__svg--filtered\" : \"\"}`}\n viewBox={`0 0 ${VIEWBOX.width} ${VIEWBOX.height}`}\n role=\"group\"\n aria-label={ariaLabel}\n onKeyDown={interactive ? (event) => { if (event.key === \"Escape\") { event.preventDefault(); inspect(null); } } : undefined}\n onClick={interactive ? handleBackgroundClick : undefined}\n >\n {visibleReferenceRegions.length > 0 ? (\n <>\n <defs>\n <pattern id={hatchId} width=\"8\" height=\"8\" patternUnits=\"userSpaceOnUse\" patternTransform=\"rotate(45)\">\n <rect width=\"8\" height=\"8\" fill=\"var(--india-map-reference-bg)\" />\n <line x1=\"0\" y1=\"0\" x2=\"0\" y2=\"8\" stroke=\"var(--india-map-reference-hatch)\" strokeWidth=\"2\" />\n </pattern>\n </defs>\n <g className=\"india-choropleth__reference-fill\" role=\"group\" aria-label=\"Non-statistical reference context.\">\n {visibleReferenceRegions.map((region) => (\n <path\n key={region.id}\n d={region.path}\n fill={referenceOverlayFill === \"solid\" ? \"var(--india-map-reference-bg)\" : `url(#${hatchId})`}\n aria-label={`${region.label}.${region.description ? ` ${region.description}` : \"\"}`}\n role=\"img\"\n />\n ))}\n </g>\n </>\n ) : null}\n {/* Hit areas for scattered regions, first so that every real outline is\n painted on top of them: the hull spanning Lakshadweep's islands is\n open sea, but Puducherry's spans the Tamil Nadu coast, and a hull\n must never take a pointer from a region that is actually there.\n Not focusable — keyboard focus has no coordinates, so the one tab\n stop stays on the region itself. */}\n {interactive ? (\n <g className=\"india-choropleth__hit-areas\" aria-hidden=\"true\">\n {regions.filter((region) => region.hitPath).map((region) => (\n <path\n key={region.id}\n d={region.hitPath!}\n fill=\"none\"\n pointerEvents=\"all\"\n tabIndex={-1}\n onMouseEnter={() => inspect(region)}\n onMouseLeave={() => inspect(null)}\n onClick={() => activate(region)}\n />\n ))}\n </g>\n ) : null}\n {regions.map((region) => {\n const isInspected = region.id === inspected?.id;\n const isSelected = region.id === selected?.id;\n const action = canDrill ? \"Activate to view districts.\" : \"Activate to select.\";\n const textValue = region.value === null ? \"No data\" : formatValue(region.value);\n return (\n <path\n key={region.id}\n className={`india-choropleth__region${isInspected ? \" india-choropleth__region--inspected\" : \"\"}${isSelected ? \" india-choropleth__region--selected\" : \"\"}${mergedReferenceIds.has(region.id) ? \" india-choropleth__region--reference-merged\" : \"\"}${isDimmed(region.id) ? \" india-choropleth__dimmed\" : \"\"}`}\n d={region.path}\n fill={colorFor(region.value, region, min, max, colorScale)}\n tabIndex={interactive ? 0 : -1}\n role={interactive ? \"button\" : undefined}\n aria-label={`${region.label}, ${textValue}. ${action}`}\n aria-pressed={interactive ? isSelected : undefined}\n aria-describedby={isInspected ? tooltipId : undefined}\n ref={(element) => { pathRefs.current[region.id] = element; }}\n onMouseEnter={interactive ? () => inspect(region) : undefined}\n // The canvas is much wider than the drawn map, so leaving a region\n // usually lands on blank canvas rather than leaving the canvas at all.\n // Without this the last-hovered tooltip stays pinned indefinitely.\n // Moving straight to a sibling region dispatches this leave and that\n // region's enter from the same native event, so the tooltip switches\n // in one batch instead of blanking.\n onMouseLeave={interactive ? () => inspect(null) : undefined}\n onFocus={interactive ? () => inspect(region) : undefined}\n // Tabbing to a sibling region re-inspects it synchronously right after\n // this fires, so clearing here only matters when focus leaves the map.\n onBlur={interactive ? () => inspect(null) : undefined}\n onClick={interactive ? () => activate(region) : undefined}\n onKeyDown={interactive ? (event) => {\n if (event.key === \"Enter\" || event.key === \" \") { event.preventDefault(); activate(region); }\n } : undefined}\n />\n );\n })}\n {/* A region whose largest part is smaller than the marker is invisible at\n this scale — Puducherry's enclaves are a couple of units — so it gets\n a dot in its own colour instead of nothing at all. The dot takes the\n pointer as well: it is painted over whatever is beneath it, so it is\n what the reader sees and aims at, and the outline it stands in for is\n too small to hover. Focus stays on the region path, its one tab stop. */}\n {smallMarkers.length > 0 ? (\n <g className=\"india-choropleth__small-markers\" aria-hidden=\"true\">\n {smallMarkers.map((region) => (\n <circle\n key={region.id}\n className={isDimmed(region.id) ? \"india-choropleth__dimmed\" : undefined}\n cx={region.centroid[0]}\n cy={region.centroid[1]}\n r={MIN_REGION_MARKER_SIZE / 2}\n fill={colorFor(region.value, region, min, max, colorScale)}\n onMouseEnter={interactive ? () => inspect(region) : undefined}\n onMouseLeave={interactive ? () => inspect(null) : undefined}\n onClick={interactive ? () => activate(region) : undefined}\n />\n ))}\n </g>\n ) : null}\n {showRegionValues ? (\n <>\n <g className=\"india-choropleth__value-leaders\" aria-hidden=\"true\">\n {valuePlacements.filter((p) => p.leader).map((p) => (\n <line\n key={p.region.id}\n className={isDimmed(p.region.id) ? \"india-choropleth__dimmed\" : undefined}\n x1={p.leader![0][0]} y1={p.leader![0][1]} x2={p.leader![1][0]} y2={p.leader![1][1]}\n />\n ))}\n </g>\n <g className={`india-choropleth__region-values${level === \"district\" ? \" india-choropleth__region-values--district\" : \"\"}`} aria-hidden=\"true\">\n {valuePlacements.map((p) => (\n <text\n key={p.region.id}\n className={isDimmed(p.region.id) ? \"india-choropleth__dimmed\" : undefined}\n x={p.at[0]} y={p.at[1]} textAnchor=\"middle\" dominantBaseline=\"central\"\n >\n {p.text}\n </text>\n ))}\n </g>\n </>\n ) : null}\n {visibleReferenceRegions.length > 0 ? (\n <g className=\"india-choropleth__reference-outline\" aria-hidden=\"true\">\n {visibleReferenceRegions.map((region) => <path key={region.id} d={region.path} fill=\"none\" />)}\n </g>\n ) : null}\n {/* Selection is drawn as a ring on top of everything, rather than by recoloring\n the region: on a choropleth the fill *is* the data, so overwriting it made the\n selected region's color stop meaning anything. Hovering a region lifts it 2px,\n so the ring matches that lift when the selected region is also the inspected\n one — otherwise the fill slides out from under its own outline. */}\n {selected ? (\n <g\n className={`india-choropleth__selection${selected.id === inspected?.id ? \" india-choropleth__selection--lifted\" : \"\"}`}\n aria-hidden=\"true\"\n >\n <path className=\"india-choropleth__selection-halo\" d={selected.path} fill=\"none\" />\n <path className=\"india-choropleth__selection-ring\" d={selected.path} fill=\"none\" />\n </g>\n ) : null}\n </svg>\n )}\n {tooltipContext && regions.length > 0 ? (\n <div ref={tooltipAnchorRef} className=\"india-choropleth__tooltip-anchor\" style={{ left: `${(inspected!.centroid[0] / VIEWBOX.width) * 100}%`, top: `${(inspected!.centroid[1] / VIEWBOX.height) * 100}%` }}>\n {/* Deliberately not `role=\"status\"`. As a live region it re-announced the whole\n tooltip on every hover *and* every focus move, and the content is richer now.\n The region aria-label already carries label + value, and `aria-describedby`\n reads this box out on focus — once, on demand. */}\n <div id={tooltipId} className=\"india-choropleth__tooltip\">\n {renderTooltip ? renderTooltip(tooltipContext) : defaultTooltip(tooltipContext, formatValue)}\n </div>\n </div>\n ) : null}\n </div>\n {showLegend ? (\n <div\n className=\"india-choropleth__legend\"\n role=\"group\"\n aria-label={`Color scale: ${legendLabels[0]} to ${legendLabels[1]} values`}\n onKeyDown={interactive ? (event) => { if (event.key === \"Escape\") { event.preventDefault(); setActiveBucket(null); } } : undefined}\n >\n <span>{legendLabels[0]}</span><div className=\"india-choropleth__swatches\" aria-hidden={interactive ? undefined : true}>\n {/* Each swatch filters the map to its own band. An empty band is left\n as a swatch you can still read — silently skipping it would hide\n the fact that the ramp has a gap there — but it does nothing,\n because filtering to nothing just dims the whole map. */}\n {buckets.map((bucket) => interactive ? (\n <button\n key={bucket.index}\n type=\"button\"\n className={`india-choropleth__swatch${filterBucket === bucket.index ? \" india-choropleth__swatch--active\" : \"\"}${filterBucket !== null && filterBucket !== bucket.index ? \" india-choropleth__swatch--muted\" : \"\"}`}\n style={{ backgroundColor: bucket.color }}\n aria-pressed={filterBucket === bucket.index}\n aria-disabled={bucket.matches === 0 ? true : undefined}\n aria-label={legendBucketLabel(bucket, formatValue)}\n // Sighted readers get the same sentence the accessible name carries,\n // which is the only place an empty band explains itself now that it\n // is no longer faded.\n title={legendBucketLabel(bucket, formatValue)}\n onClick={() => {\n if (bucket.matches === 0) return;\n setActiveBucket(filterBucket === bucket.index ? null : bucket.index);\n }}\n />\n ) : (\n <i className=\"india-choropleth__swatch\" key={bucket.index} style={{ backgroundColor: bucket.color }} />\n ))}\n </div><span>{legendLabels[1]}</span>\n {visibleReferenceRegions.length > 0 ? <><i className={`india-choropleth__reference-key${referenceOverlayFill === \"solid\" ? \" india-choropleth__reference-key--solid\" : \"\"}`} aria-hidden=\"true\" /><span>{referenceOverlayLegendLabel}</span></> : null}\n </div>\n ) : null}\n {renderInsights ? <aside className=\"india-choropleth__insights\" aria-live=\"polite\">{renderInsights(insightContext)}</aside> : null}\n </section>\n );\n}\n","import { feature as topoFeature } from \"topojson-client\";\nimport type { GeometryObject } from \"topojson-specification\";\nimport type { GeometrySource, MapFeatureCollection } from \"./types\";\n\nexport function asFeatureCollection(source: GeometrySource): MapFeatureCollection {\n if (\"type\" in source && source.type === \"FeatureCollection\") {\n return source;\n }\n\n const topoSource = source as Exclude<GeometrySource, MapFeatureCollection>;\n\n const object = (typeof topoSource.object === \"string\"\n ? topoSource.topology.objects[topoSource.object]!\n : topoSource.object) as unknown as GeometryObject;\n if (!object) {\n throw new Error(\"The named TopoJSON object does not exist in this topology.\");\n }\n\n const unpacked = topoFeature(topoSource.topology, object);\n return unpacked.type === \"FeatureCollection\"\n ? (unpacked as MapFeatureCollection)\n : { type: \"FeatureCollection\", features: [unpacked] };\n}\n\nexport function totalOf(values: readonly (number | null)[]) {\n return values.reduce<number>((total, value) => total + (value ?? 0), 0);\n}\n","/**\n * The legend, as a filter.\n *\n * Every value on the map is painted from one of the ramp's colours. That makes\n * the legend a ready-made set of value bands, and picking a band is the\n * question a reader of a choropleth actually has: *which regions are the dark\n * ones?* These helpers answer it — which swatch a value belongs to, what each\n * swatch stands for, and how many regions land there.\n *\n * [swatchIndexOf] is the single definition of that mapping: the renderers pick\n * a region's fill with it too, so \"highlight the regions painted in this\n * colour\" is true by construction rather than by two formulas agreeing.\n *\n * Pure functions of values, so the DOM renderer and the React component share\n * one definition of the behaviour.\n */\n\n/** One swatch: the colour, and what the map actually has in it. */\nexport interface LegendBucket {\n index: number;\n color: string;\n /**\n * Lowest and highest value that lands here, or null when nothing does.\n *\n * The band the ramp *nominally* covers is a half-step either side of the\n * swatch's own stop, which lands on numbers like \"24.333 to 31\" that appear\n * nowhere in the data. What a reader wants to know is what picking this\n * swatch will give them, so the range is measured from the regions in it.\n */\n from: number | null;\n to: number | null;\n /** How many regions land here. Zero means the swatch would filter to nothing. */\n matches: number;\n}\n\n/**\n * Which swatch a value is painted from, or null when there is nothing to paint\n * — no value, or no ramp to paint it with.\n *\n * A ramp of one colour, or data with no spread at all, collapses to the top\n * swatch: there is a single band and every value is in it.\n */\nexport function swatchIndexOf(value: number | null, min: number, max: number, count: number): number | null {\n if (value === null || count <= 0) return null;\n if (count === 1 || max === min) return count - 1;\n const index = Math.round(((value - min) / (max - min)) * (count - 1));\n return Math.min(Math.max(index, 0), count - 1);\n}\n\n/**\n * The ramp described swatch by swatch.\n *\n * With a function colour scale the renderers show the default ramp, because a\n * function has no swatches to show. The bands are still the honest reading of\n * \"lower to higher\"; the swatch colour just isn't any region's actual fill.\n */\nexport function legendBuckets(\n colors: readonly string[],\n values: readonly (number | null)[],\n min: number,\n max: number,\n): LegendBucket[] {\n const count = colors.length;\n return colors.map((color, index) => {\n const members = values.filter((value): value is number => swatchIndexOf(value, min, max, count) === index);\n return {\n index,\n color,\n from: members.length ? Math.min(...members) : null,\n to: members.length ? Math.max(...members) : null,\n matches: members.length,\n };\n });\n}\n\n/**\n * What a swatch does, in words — the accessible name for its control, since the\n * colour itself carries the meaning and a screen reader cannot see it.\n */\nexport function legendBucketLabel(bucket: LegendBucket, formatValue: (value: number) => string): string {\n if (bucket.matches === 0 || bucket.from === null || bucket.to === null) return \"No regions in this band\";\n const range = bucket.from === bucket.to\n ? formatValue(bucket.from)\n : `${formatValue(bucket.from)} to ${formatValue(bucket.to)}`;\n return `Highlight ${bucket.matches} ${bucket.matches === 1 ? \"region\" : \"regions\"}, ${range}`;\n}\n\n","/**\n * Edge handling for the centroid-anchored tooltip.\n *\n * The tooltip is anchored to the region's centroid rather than the cursor on\n * purpose: hover and keyboard focus run through the same inspect path, and a\n * focused region has no cursor position to follow. That anchor still has to be\n * nudged so the box never leaves the map — a state at the top of the map (Jammu\n * & Kashmir on a narrow screen) would otherwise render *above* the map, over\n * whatever the host page put there.\n *\n * Kept as a pure function of two rects so both the DOM renderer and the React\n * component can share the same math.\n */\n\nexport interface Rect {\n left: number;\n right: number;\n top: number;\n bottom: number;\n width: number;\n height: number;\n}\n\nexport interface TooltipPlacement {\n /** Horizontal correction in px, applied on top of the centering translate. */\n dx: number;\n /** Which side of the centroid the tooltip sits on. */\n side: \"above\" | \"below\";\n}\n\n/**\n * @param tooltip The tooltip's rect as currently laid out (centered above the centroid).\n * @param bounds The area the tooltip must stay inside — the map canvas.\n * @param gap Space between the centroid and the tooltip edge, in px.\n * @param padding Minimum breathing room between the tooltip and the bounds edge, in px.\n */\nexport function placeTooltip(tooltip: Rect, bounds: Rect, gap: number, padding = 4): TooltipPlacement {\n let dx = 0;\n // A tooltip wider than the space available can't satisfy both edges; favor the\n // left one so the region name (which leads the content) stays readable.\n if (tooltip.right > bounds.right - padding) dx = bounds.right - padding - tooltip.right;\n if (tooltip.left + dx < bounds.left + padding) dx = bounds.left + padding - tooltip.left;\n\n // Flip below only when there genuinely isn't room above. The flipped position\n // is `gap` below the centroid, which is `height + 2 * gap` further down.\n const side: TooltipPlacement[\"side\"] = tooltip.top < bounds.top + padding ? \"below\" : \"above\";\n\n return { dx, side };\n}\n","/**\n * Geometry for regions that are too small to use normally.\n *\n * Goa is a few view-box units across, Puducherry is scattered enclaves, and\n * Lakshadweep's islands are a fraction of a pixel at national scale. Left alone\n * they are invisible, unclickable, and their value labels either don't fit or\n * land on a neighbour. These helpers back the three fixes for that: a marker so\n * they can be seen, a click buffer so they can be hit, and label placement that\n * moves a number into open space beside the region.\n *\n * Pure functions of projected coordinates, so the DOM renderer, the React\n * component and the Dart port can all share one definition of the behaviour.\n */\n\nexport type Point = readonly [number, number];\n\n/** Axis-aligned box as `[minX, minY, maxX, maxY]`. */\nexport type Box = readonly [number, number, number, number];\n\n/**\n * Regions kept at their true size even when they are small enough to qualify\n * for exaggeration.\n *\n * Exaggeration assumes a region has room to grow into. Puducherry does not: it\n * is four coastal enclaves *inside* Tamil Nadu, so growing them to the\n * visibility threshold pushes each one several units into the state around it,\n * and the map ends up showing a Puducherry that is the wrong shape in the wrong\n * place. Lakshadweep's islands grow into open sea, where nothing is displaced,\n * which is the case the feature was built for.\n *\n * Matched on the id containing the name because host data brings its own id\n * scheme; the bundled layer uses `in-cs-34-puducherry`. A district id inside the\n * UT matches too, which is a no-op: drilled into, its districts fill the map and\n * are far past the threshold that would have grown them.\n */\nconst TRUE_GEOMETRY_REGIONS = [\"puducherry\"];\n\nexport function keepsTrueGeometry(id: string): boolean {\n const normalized = id.toLowerCase();\n return TRUE_GEOMETRY_REGIONS.some((name) => normalized.includes(name));\n}\n\nexport function boundsOfRing(ring: readonly Point[]): Box {\n let minX = Infinity;\n let minY = Infinity;\n let maxX = -Infinity;\n let maxY = -Infinity;\n for (const [x, y] of ring) {\n if (x < minX) minX = x;\n if (x > maxX) maxX = x;\n if (y < minY) minY = y;\n if (y > maxY) maxY = y;\n }\n return [minX, minY, maxX, maxY];\n}\n\n/**\n * Longest side of the largest ring's box. This is the measure of \"small\", not\n * the feature's overall bounds: an island group's bounds can be large while\n * every island in it is sub-pixel.\n */\nexport function largestRingExtent(rings: readonly (readonly Point[])[]): number {\n let largest = 0;\n for (const ring of rings) {\n if (ring.length === 0) continue;\n const [minX, minY, maxX, maxY] = boundsOfRing(ring);\n const extent = Math.max(maxX - minX, maxY - minY);\n if (extent > largest) largest = extent;\n }\n return largest;\n}\n\nfunction signedArea(ring: readonly Point[]): number {\n let total = 0;\n for (let i = 0; i < ring.length; i++) {\n const [ax, ay] = ring[i]!;\n const [bx, by] = ring[(i + 1) % ring.length]!;\n total += ax * by - bx * ay;\n }\n return total / 2;\n}\n\n/**\n * Area-weighted centroid of the largest ring.\n *\n * Deliberately not the whole feature's centroid: averaging across parts pulls\n * Andaman & Nicobar's label out to sea between its islands, and Gujarat's off\n * its own coast.\n */\nexport function labelPointFor(rings: readonly (readonly Point[])[], fallback: Point): Point {\n let largest: readonly Point[] | null = null;\n let largestArea = 0;\n for (const ring of rings) {\n if (ring.length < 3) continue;\n const area = Math.abs(signedArea(ring));\n if (area > largestArea) {\n largestArea = area;\n largest = ring;\n }\n }\n if (!largest || largestArea === 0) return fallback;\n\n const area = signedArea(largest);\n let cx = 0;\n let cy = 0;\n for (let i = 0; i < largest.length; i++) {\n const [ax, ay] = largest[i]!;\n const [bx, by] = largest[(i + 1) % largest.length]!;\n const cross = ax * by - bx * ay;\n cx += (ax + bx) * cross;\n cy += (ay + by) * cross;\n }\n const centroid: Point = [cx / (6 * area), cy / (6 * area)];\n return Number.isFinite(centroid[0]) && Number.isFinite(centroid[1]) ? centroid : fallback;\n}\n\n/** Distance from a point to the nearest edge of a box; zero when inside it. */\nexport function distanceToBox(point: Point, box: Box): number {\n const [x, y] = point;\n const [minX, minY, maxX, maxY] = box;\n const dx = Math.max(minX - x, 0) + Math.max(x - maxX, 0);\n const dy = Math.max(minY - y, 0) + Math.max(y - maxY, 0);\n return Math.hypot(dx, dy);\n}\n\n/** Distance to the nearest of a region's parts. */\nexport function distanceToParts(point: Point, parts: readonly Box[]): number {\n let nearest = Infinity;\n for (const part of parts) {\n const distance = distanceToBox(point, part);\n if (distance < nearest) nearest = distance;\n }\n return nearest;\n}\n\n/**\n * Angles to try when placing a small region's label, as turns from the\n * away-from-centre direction: straight out first, then progressively to either\n * side, and back inward only as a last resort.\n *\n * One fixed direction is not enough. Puducherry sits south-east of the map's\n * middle, so the radial direction runs inland into Tamil Nadu while its open\n * water is due east.\n */\nconst LABEL_SEARCH_TURNS = [\n 0,\n Math.PI / 6, -Math.PI / 6,\n Math.PI / 3, -Math.PI / 3,\n Math.PI / 2, -Math.PI / 2,\n (2 * Math.PI) / 3, -(2 * Math.PI) / 3,\n (5 * Math.PI) / 6, -(5 * Math.PI) / 6,\n Math.PI,\n] as const;\n\nexport interface OutsideLabelOptions {\n /** Where the region is, in view-box units. */\n anchor: Point;\n /** How far the label must clear the region itself. */\n clearance: number;\n /** Half the label's width and height, used to keep it inside the view box. */\n halfSize: Point;\n /** The view box the label must stay within, as `[width, height]`. */\n viewBox: Point;\n /** Direction is measured away from this point — normally the view box's middle. */\n centre: Point;\n /** Returns true when a label centred here would cover another region. */\n isBlocked: (candidate: Point) => boolean;\n}\n\n/**\n * Find a clear spot beside a region for its value label, or null when the\n * region is hemmed in on every side (Delhi, ringed by other states) and the\n * label is better left where it was.\n */\nexport function placeOutsideLabel(options: OutsideLabelOptions): Point | null {\n const { anchor, clearance, halfSize, viewBox, centre, isBlocked } = options;\n const dx = anchor[0] - centre[0];\n const dy = anchor[1] - centre[1];\n const base = dx === 0 && dy === 0 ? 0 : Math.atan2(dy, dx);\n\n for (const turn of LABEL_SEARCH_TURNS) {\n const angle = base + turn;\n const candidate: Point = [\n clamp(anchor[0] + Math.cos(angle) * clearance, halfSize[0], viewBox[0] - halfSize[0]),\n clamp(anchor[1] + Math.sin(angle) * clearance, halfSize[1], viewBox[1] - halfSize[1]),\n ];\n if (!isBlocked(candidate)) return candidate;\n }\n return null;\n}\n\nfunction clamp(value: number, lower: number, upper: number): number {\n return Math.min(Math.max(value, lower), upper);\n}\n\n/**\n * Grow a feature whose whole geometry is too small to see, about each part's\n * own centre.\n *\n * Two guards make this safe, and both were learned the hard way:\n *\n * 1. It applies only when the feature's *largest* part is under [minExtent] —\n * that is, the whole region is tiny. West Bengal is a large state whose\n * Sundarbans delta is fourteen small islets; enlarging those individually\n * blew them up into an overlapping mess across the river mouth. A region is\n * either small enough to need help or it is not.\n * 2. Once a feature qualifies, each part grows about its own centre until it is\n * visible. This preserves every part's location and aspect ratio instead of\n * turning a dispersed archipelago into one large, misplaced blob. The cap\n * keeps the tiniest specks from reading as real landmass.\n *\n * Growth is capped by [maxScale] so a speck never reads as a real landmass.\n */\nexport function enlargeSmallParts(\n rings: readonly (readonly Point[])[],\n minExtent: number,\n maxScale = 8,\n): Point[][] {\n const unchanged = () => rings.map((ring) => [...ring]);\n if (minExtent <= 0) return unchanged();\n // Only a feature that is small *as a whole* qualifies.\n const largest = largestRingExtent(rings);\n if (largest <= 0 || largest >= minExtent) return unchanged();\n\n return rings.map((ring) => {\n const [minX, minY, maxX, maxY] = boundsOfRing(ring);\n const extent = Math.max(maxX - minX, maxY - minY);\n const scale = extent <= 0 ? 1 : Math.min(minExtent / extent, maxScale);\n const cx = (minX + maxX) / 2;\n const cy = (minY + maxY) / 2;\n return ring.map(([x, y]) => [cx + (x - cx) * scale, cy + (y - cy) * scale] as Point);\n });\n}\n\n/**\n * Convex hull of a set of points, as one closed ring (Andrew's monotone chain).\n * Returns the input when there is nothing to wrap — fewer than three distinct\n * points, or all of them collinear.\n */\nexport function convexHull(points: readonly Point[]): Point[] {\n const sorted = [...points].sort((a, b) => a[0] - b[0] || a[1] - b[1]);\n const unique: Point[] = [];\n for (const point of sorted) {\n const prior = unique.at(-1);\n if (!prior || prior[0] !== point[0] || prior[1] !== point[1]) unique.push(point);\n }\n if (unique.length < 3) return unique.map((point) => [...point] as Point);\n\n const turn = (o: Point, a: Point, b: Point) =>\n (a[0] - o[0]) * (b[1] - o[1]) - (a[1] - o[1]) * (b[0] - o[0]);\n const half = (ordered: readonly Point[]) => {\n const chain: Point[] = [];\n for (const point of ordered) {\n while (chain.length >= 2 && turn(chain[chain.length - 2]!, chain[chain.length - 1]!, point) <= 0) chain.pop();\n chain.push(point);\n }\n return chain;\n };\n\n const lower = half(unique);\n const upper = half([...unique].reverse());\n // Each chain repeats the other's first point, so drop both endpoints once.\n const hull = [...lower.slice(0, -1), ...upper.slice(0, -1)];\n return hull.length >= 3 ? hull : unique.map((point) => [...point] as Point);\n}\n\n/**\n * One continuous hit area for a region scattered across separate parts: the\n * convex hull of everything it is drawn as.\n *\n * Lakshadweep is twenty specks in open sea. Even exaggerated they are a poor\n * pointer target, and the water they enclose is how the group reads on the map,\n * so treating that water as part of the region is what a reader expects. The\n * hull is safe to be generous with only because every renderer tests it *after*\n * every real outline has missed, so it can never take a hover from a neighbour\n * it happens to span.\n *\n * Null when the region needs no help: a single part, or already big enough to\n * point at directly ([maxExtent], the same \"too small to use\" threshold the\n * click buffer and value labels work from).\n */\nexport function scatteredHitArea(\n rings: readonly (readonly Point[])[],\n maxExtent: number,\n): Point[] | null {\n if (rings.length < 2 || maxExtent <= 0) return null;\n const extent = largestRingExtent(rings);\n if (extent <= 0 || extent >= maxExtent) return null;\n const hull = convexHull(rings.flat());\n return hull.length >= 3 ? hull : null;\n}\n\n/** An SVG path string for a set of rings, used when exaggeration has moved them. */\nexport function ringsToPath(rings: readonly (readonly Point[])[]): string {\n let d = \"\";\n for (const ring of rings) {\n if (ring.length === 0) continue;\n d += `M${ring[0]![0]},${ring[0]![1]}`;\n for (let i = 1; i < ring.length; i++) d += `L${ring[i]![0]},${ring[i]![1]}`;\n d += \"Z\";\n }\n return d;\n}\n","import { useCallback, useState } from \"react\";\n\nexport function useControllableState<T>(controlled: T | undefined, initial: T) {\n const [uncontrolled, setUncontrolled] = useState<T>(initial);\n const value = controlled === undefined ? uncontrolled : controlled;\n const setValue = useCallback((next: T) => {\n if (controlled === undefined) setUncontrolled(next);\n }, [controlled]);\n return [value, setValue] as const;\n}\n"],"mappings":"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","names":["useCallback","useState","area","useState","useCallback"]}
1
+ {"version":3,"sources":["../src/IndiaChoropleth.tsx","../src/geometry.ts","../src/legend.ts","../src/tooltip-position.ts","../src/small-regions.ts","../src/useControllableState.ts"],"sourcesContent":["import { geoMercator, geoPath, type GeoProjection } from \"d3-geo\";\nimport { useCallback, useEffect, useId, useLayoutEffect, useMemo, useRef, useState, type MouseEvent as ReactMouseEvent } from \"react\";\nimport { asFeatureCollection, totalOf } from \"./geometry\";\nimport { legendBucketLabel, legendBuckets, swatchIndexOf } from \"./legend\";\nimport { placeTooltip } from \"./tooltip-position\";\nimport {\n boundsOfRing,\n distanceToParts,\n keepsTrueGeometry,\n labelPointFor,\n enlargeSmallParts,\n largestRingExtent,\n placeOutsideLabel,\n ringsToPath,\n scatteredHitArea,\n type Box,\n type Point,\n} from \"./small-regions\";\nimport type {\n ColorContext,\n ColorScale,\n IndiaChoroplethProps,\n InsightContext,\n MapFeature,\n MapFeatureCollection,\n MapLayer,\n MapLevel,\n MapRegion,\n ReferenceOverlay,\n TooltipContext,\n} from \"./types\";\nimport { useControllableState } from \"./useControllableState\";\n\nconst VIEWBOX = { width: 960, height: 640, padding: 28 };\nconst DEFAULT_COLORS = [\"#d9f1ed\", \"#b9e3dd\", \"#8fd1c8\", \"#5bb9ae\", \"#2f9c90\", \"#147b71\", \"#075b55\"] as const;\nconst DEFAULT_FORMAT = new Intl.NumberFormat(\"en-IN\").format;\n// Space between the region centroid and the tooltip edge. Mirrors the .75rem in style.css.\nconst TOOLTIP_GAP_PX = 12;\n// Small-region handling, in view-box units. Mirrors the DOM and Dart renderers.\nconst SMALL_REGION_EXTENT = 22;\nconst SMALL_REGION_CLICK_RADIUS = 14;\nconst MIN_REGION_MARKER_SIZE = 7;\n\ntype PreparedRegion = MapRegion & {\n path: string;\n /**\n * Hull covering a scattered region's parts and the space between them, drawn\n * invisibly under every outline so hovering the water inside Lakshadweep\n * reaches Lakshadweep. Null for regions that are one part or big enough to\n * point at directly.\n */\n hitPath: string | null;\n centroid: [number, number];\n /** Bounding box of each separate part, for measuring how close a click landed. */\n partBounds: Box[];\n /** Longest side of the largest part — the measure of \"too small to use\". */\n extent: number;\n};\n\n/**\n * Project a feature's rings into view-box coordinates. Needed alongside the SVG\n * path string because the small-region helpers work on coordinates, and a path\n * string cannot be measured per part.\n */\nfunction projectedRings(feature: MapFeature, projection: GeoProjection): Point[][] {\n const geometry = feature.geometry;\n if (!geometry) return [];\n const polygons =\n geometry.type === \"Polygon\" ? [geometry.coordinates]\n : geometry.type === \"MultiPolygon\" ? geometry.coordinates\n : [];\n const rings: Point[][] = [];\n for (const polygon of polygons) {\n for (const ring of polygon) {\n const projected: Point[] = [];\n for (const position of ring) {\n const point = projection(position as [number, number]);\n if (point && Number.isFinite(point[0]) && Number.isFinite(point[1])) projected.push([point[0], point[1]]);\n }\n if (projected.length > 0) rings.push(projected);\n }\n }\n return rings;\n}\ntype PreparedReferenceOverlay = { id: string; label: string; description: string; path: string };\n\nfunction colorFor(value: number | null, region: MapRegion, min: number, max: number, scale: ColorScale): string {\n if (typeof scale === \"function\") {\n const context: ColorContext = { min, max, feature: region.feature, id: region.id };\n return scale(value, context);\n }\n // The same index the legend filters by, deliberately: \"highlight the regions\n // painted in this colour\" has to be true by construction, not by two formulas\n // that happen to agree until one of them is tweaked.\n const index = swatchIndexOf(value, min, max, scale.length);\n return index === null ? \"var(--india-map-empty)\" : scale[index] ?? \"var(--india-map-empty)\";\n}\n\nfunction makeProjection(collection: MapFeatureCollection): GeoProjection {\n return geoMercator().fitExtent(\n [[VIEWBOX.padding, VIEWBOX.padding], [VIEWBOX.width - VIEWBOX.padding, VIEWBOX.height - VIEWBOX.padding]],\n collection,\n );\n}\n\nfunction prepareLayer(\n layer: MapLayer,\n projection = makeProjection(asFeatureCollection(layer.geometry)),\n minPartExtent = 0,\n): PreparedRegion[] {\n const collection = asFeatureCollection(layer.geometry);\n const path = geoPath(projection);\n return collection.features.map((feature) => {\n const centroid = path.centroid(feature) as [number, number];\n const bounds = path.bounds(feature);\n const fallbackCentroid: [number, number] = [\n (bounds[0][0] + bounds[1][0]) / 2,\n (bounds[0][1] + bounds[1][1]) / 2,\n ];\n const region: MapRegion = {\n id: layer.getId(feature),\n label: layer.getLabel(feature),\n value: layer.getValue(feature),\n meta: layer.getMeta?.(feature),\n feature,\n };\n // Puducherry and anything else on the keep-true list is drawn as it really\n // is, however small, because there is no room around it to grow into.\n const exaggerate = minPartExtent > 0 && !keepsTrueGeometry(region.id);\n const rings = exaggerate\n ? enlargeSmallParts(projectedRings(feature, projection), minPartExtent)\n : projectedRings(feature, projection);\n const fallback: [number, number] = centroid.every(Number.isFinite) ? centroid : fallbackCentroid;\n const hull = scatteredHitArea(rings, SMALL_REGION_EXTENT);\n // With exaggeration on, the drawn outline has to come from the moved rings\n // rather than d3's path generator, so what is drawn, measured, labelled and\n // clicked are all the same geometry.\n return {\n ...region,\n path: exaggerate ? ringsToPath(rings) : (path(feature) ?? \"\"),\n hitPath: hull ? ringsToPath([hull]) : null,\n // The largest part's centroid, not the whole feature's: averaging across\n // parts puts an island group's label out at sea between its islands.\n centroid: rings.length > 0 ? (labelPointFor(rings, fallback) as [number, number]) : fallback,\n partBounds: rings.map(boundsOfRing),\n extent: largestRingExtent(rings),\n };\n });\n}\n\nfunction prepareReferenceOverlay(overlay: ReferenceOverlay, projection: GeoProjection): PreparedReferenceOverlay[] {\n const path = geoPath(projection);\n return asFeatureCollection(overlay.geometry).features.map((feature) => ({\n id: overlay.getId(feature),\n label: overlay.getLabel(feature),\n description: overlay.getDescription(feature),\n path: path(feature) ?? \"\",\n }));\n}\n\nfunction ordinal(n: number): string {\n const lastTwo = n % 100;\n if (lastTwo >= 11 && lastTwo <= 13) return `${n}th`;\n return `${n}${[\"th\", \"st\", \"nd\", \"rd\"][n % 10] ?? \"th\"}`;\n}\n\nfunction defaultTooltip(context: TooltipContext, formatValue: (value: number) => string) {\n return (\n <>\n <strong>{context.label}</strong>\n {/* Same wording as the region's own aria-label, so the two never disagree. */}\n <b>{context.value === null ? \"No data\" : formatValue(context.value)}</b>\n {context.share !== null ? (\n <>\n {/* A bar makes the share readable at a glance; it repeats the number\n beside it, so it's decorative and hidden from assistive tech. */}\n <span className=\"india-choropleth__tooltip-bar\" aria-hidden=\"true\">\n <span style={{ width: `${Math.max(context.share, 1.5)}%` }} />\n </span>\n <small>\n {[\n `${context.share.toFixed(1)}% of total`,\n ...(context.rank !== null ? [`${ordinal(context.rank)} of ${context.rankedCount}`] : []),\n ].join(\" · \")}\n </small>\n </>\n ) : null}\n </>\n );\n}\n\n/**\n * A data-agnostic, accessible SVG India map renderer. Import `@india-choropleth/react/style.css`\n * once in the host app; data and boundaries intentionally remain separate.\n */\nexport function IndiaChoropleth({\n states,\n referenceOverlay,\n loadDistricts,\n loadSubDistricts,\n loadDistrictReferenceOverlay,\n drillDownId,\n defaultDrillDownId = null,\n onDrillDownChange,\n subDistrictDrillDownId,\n defaultSubDistrictDrillDownId = null,\n onSubDistrictDrillDownChange,\n selectedId,\n defaultSelectedId = null,\n onSelectedChange,\n onInspect,\n onInsight,\n onRegionClick,\n onBackgroundClick,\n colorScale = DEFAULT_COLORS,\n formatValue = DEFAULT_FORMAT,\n renderTooltip,\n renderInsights,\n showLegend = true,\n showBreadcrumb = true,\n legendLabels = [\"Lower\", \"Higher\"],\n referenceOverlayLegendLabel = \"Reference context · data unavailable\",\n referenceOverlayMergeIds = [],\n referenceOverlayFill = \"hatch\",\n showRegionValues = false,\n minPartExtent = 0,\n minDistrictPartExtent,\n className,\n ariaLabel = \"Interactive choropleth map\",\n interactive = true,\n}: IndiaChoroplethProps) {\n const tooltipId = useId();\n const canvasRef = useRef<HTMLDivElement | null>(null);\n const tooltipAnchorRef = useRef<HTMLDivElement | null>(null);\n const hatchId = `${useId()}-reference-hatch`;\n const [activeDrillDownId, setActiveDrillDownId] = useControllableState(drillDownId, defaultDrillDownId);\n const [activeSubDrillDownId, setActiveSubDrillDownId] = useControllableState(subDistrictDrillDownId, defaultSubDistrictDrillDownId);\n const [activeSelectedId, setActiveSelectedId] = useControllableState(selectedId, defaultSelectedId);\n const [loadedDistricts, setLoadedDistricts] = useState<{ stateId: string; layer: MapLayer } | null>(null);\n const [loadedSubDistricts, setLoadedSubDistricts] = useState<{ districtId: string; layer: MapLayer } | null>(null);\n /**\n * Districts the loader has already answered `null` for. The renderer cannot know\n * which districts are leaves without asking, so the first activation asks — but\n * after that the region should stop announcing a level it will not open. Held as\n * state rather than a ref so learning it repaints the label, and cleared when the\n * loader changes, since a different source may well have sub-districts for them.\n */\n const [leafDistrictIds, setLeafDistrictIds] = useState<ReadonlySet<string>>(() => new Set());\n const priorSubDistrictLoader = useRef(loadSubDistricts);\n useEffect(() => {\n if (priorSubDistrictLoader.current === loadSubDistricts) return;\n priorSubDistrictLoader.current = loadSubDistricts;\n setLeafDistrictIds(new Set());\n }, [loadSubDistricts]);\n const [loadedDistrictReferenceOverlay, setLoadedDistrictReferenceOverlay] = useState<{ stateId: string; overlay: ReferenceOverlay | null } | null>(null);\n const [loadingState, setLoadingState] = useState<string | null>(null);\n const [loadingDistrict, setLoadingDistrict] = useState<string | null>(null);\n const [loadError, setLoadError] = useState<Error | null>(null);\n const [subLoadError, setSubLoadError] = useState<Error | null>(null);\n const pathRefs = useRef<Record<string, SVGPathElement | null>>({});\n const restoreFocusId = useRef<string | null>(null);\n\n const stateCollection = useMemo(() => asFeatureCollection(states.geometry), [states]);\n const referenceCollection = useMemo(\n () => referenceOverlay ? asFeatureCollection(referenceOverlay.geometry) : null,\n [referenceOverlay],\n );\n const nationalProjection = useMemo(\n () => makeProjection({ type: \"FeatureCollection\", features: [...stateCollection.features, ...(referenceCollection?.features ?? [])] }),\n [referenceCollection, stateCollection],\n );\n const stateRegions = useMemo(() => prepareLayer(states, nationalProjection, minPartExtent), [minPartExtent, nationalProjection, states]);\n const referenceRegions = useMemo(\n () => referenceOverlay ? prepareReferenceOverlay(referenceOverlay, nationalProjection) : [],\n [nationalProjection, referenceOverlay],\n );\n const drilledState = useMemo(\n () => stateRegions.find((region) => region.id === activeDrillDownId) ?? null,\n [activeDrillDownId, stateRegions],\n );\n const isDrillRequested = Boolean(drilledState && activeDrillDownId);\n const districtLayer = loadedDistricts?.stateId === activeDrillDownId ? loadedDistricts.layer : null;\n const districtReferenceOverlay = loadedDistrictReferenceOverlay?.stateId === activeDrillDownId\n ? loadedDistrictReferenceOverlay.overlay\n : null;\n const districtCollection = useMemo(\n () => districtLayer ? asFeatureCollection(districtLayer.geometry) : null,\n [districtLayer],\n );\n const districtReferenceCollection = useMemo(\n () => districtReferenceOverlay ? asFeatureCollection(districtReferenceOverlay.geometry) : null,\n [districtReferenceOverlay],\n );\n const districtProjection = useMemo(\n () => districtCollection ? makeProjection({ type: \"FeatureCollection\", features: [...districtCollection.features, ...(districtReferenceCollection?.features ?? [])] }) : null,\n [districtCollection, districtReferenceCollection],\n );\n // Districts are prepared whenever their layer is loaded rather than only while\n // they are the visible level, because the district below them has to be\n // resolvable — by id, for the breadcrumb and for the loader — from one level down.\n const districtRegions = useMemo(\n () => districtLayer && districtProjection\n ? prepareLayer(districtLayer, districtProjection, minDistrictPartExtent ?? minPartExtent)\n : [],\n [districtLayer, districtProjection, minDistrictPartExtent, minPartExtent],\n );\n const drilledDistrict = useMemo(\n () => districtRegions.find((region) => region.id === activeSubDrillDownId) ?? null,\n [activeSubDrillDownId, districtRegions],\n );\n const isSubDrillRequested = Boolean(isDrillRequested && drilledDistrict && activeSubDrillDownId);\n const level: MapLevel = isSubDrillRequested ? \"subdistrict\" : isDrillRequested ? \"district\" : \"state\";\n\n const subDistrictLayer = loadedSubDistricts?.districtId === activeSubDrillDownId ? loadedSubDistricts.layer : null;\n const subDistrictCollection = useMemo(\n () => subDistrictLayer ? asFeatureCollection(subDistrictLayer.geometry) : null,\n [subDistrictLayer],\n );\n const subDistrictProjection = useMemo(\n () => subDistrictCollection ? makeProjection(subDistrictCollection) : null,\n [subDistrictCollection],\n );\n // Sub-districts share the district knob rather than adding a fourth: they are\n // drawn at the same zoom as districts and want the same small-part treatment.\n const subDistrictRegions = useMemo(\n () => subDistrictLayer && subDistrictProjection\n ? prepareLayer(subDistrictLayer, subDistrictProjection, minDistrictPartExtent ?? minPartExtent)\n : [],\n [minDistrictPartExtent, minPartExtent, subDistrictLayer, subDistrictProjection],\n );\n\n const regions = level === \"subdistrict\" ? subDistrictRegions : level === \"district\" ? districtRegions : stateRegions;\n const districtReferenceRegions = useMemo(\n () => level === \"district\" && districtReferenceOverlay && districtProjection ? prepareReferenceOverlay(districtReferenceOverlay, districtProjection) : [],\n [districtProjection, districtReferenceOverlay, level],\n );\n const selected = regions.find((region) => region.id === activeSelectedId) ?? null;\n const [inspectedId, setInspectedId] = useState<string | null>(null);\n const inspectedIdRef = useRef<string | null>(null);\n // Hover/focus only — no fallback to `selected`, so the floating tooltip clears\n // when the pointer/focus leaves instead of sticking on the selected region.\n const inspected = regions.find((region) => region.id === inspectedId) ?? null;\n\n const values = useMemo(() => regions.map((region) => region.value).filter((value): value is number => value !== null), [regions]);\n const total = useMemo(() => totalOf(values), [values]);\n const min = values.length ? Math.min(...values) : 0;\n const max = values.length ? Math.max(...values) : 0;\n\n // The legend doubles as a filter. A function colour scale has no swatches of\n // its own, so the default ramp stands in and the bands still read low to high.\n const legendColors = typeof colorScale === \"function\" ? DEFAULT_COLORS : colorScale;\n const buckets = useMemo(\n () => legendBuckets(legendColors, regions.map((region) => region.value), min, max),\n [legendColors, max, min, regions],\n );\n const [activeBucket, setActiveBucket] = useState<number | null>(null);\n // Clamped here rather than only in the effect below: effects run after paint,\n // so a stale index would dim against the previous level's bands for a frame.\n const filterBucket = activeBucket !== null && activeBucket < buckets.length ? activeBucket : null;\n // Keyed on the ramp's contents, not its identity — a host passing an inline\n // array literal would otherwise clear the filter on every re-render.\n const colorScaleKey = typeof colorScale === \"function\" ? \"function\" : colorScale.join(\",\");\n // Bands come from this level's own min and max, and change with the ramp, so an\n // index picked under one of them means something else under another.\n useEffect(() => { setActiveBucket(null); }, [activeDrillDownId, activeSubDrillDownId, colorScaleKey, level]);\n\n const highlighted = useMemo(\n () => filterBucket === null\n ? null\n : new Set(regions\n .filter((region) => swatchIndexOf(region.value, min, max, legendColors.length) === filterBucket)\n .map((region) => region.id)),\n [filterBucket, legendColors.length, max, min, regions],\n );\n // Dimmed regions stay hoverable: the filter is about where the eye goes, and a\n // region you can see is a region whose number should still be reachable.\n const isDimmed = (id: string) => highlighted !== null && !highlighted.has(id);\n\n useEffect(() => {\n let cancelled = false;\n if (!activeDrillDownId || !loadDistricts) {\n setLoadedDistricts(null);\n setLoadingState(null);\n setLoadError(null);\n return;\n }\n const sourceState = stateRegions.find((region) => region.id === activeDrillDownId);\n if (!sourceState) return;\n setLoadingState(activeDrillDownId);\n setLoadError(null);\n setLoadedDistricts(null);\n loadDistricts(activeDrillDownId, sourceState)\n .then((loaded) => { if (!cancelled) setLoadedDistricts({ stateId: activeDrillDownId, layer: loaded }); })\n .catch((error: unknown) => { if (!cancelled) setLoadError(error instanceof Error ? error : new Error(\"Unable to load districts.\")); })\n .finally(() => { if (!cancelled) setLoadingState(null); });\n return () => { cancelled = true; };\n }, [activeDrillDownId, loadDistricts, stateRegions]);\n\n useEffect(() => {\n let cancelled = false;\n if (!activeDrillDownId || !loadDistrictReferenceOverlay) {\n setLoadedDistrictReferenceOverlay(null);\n return;\n }\n const sourceState = stateRegions.find((region) => region.id === activeDrillDownId);\n if (!sourceState) return;\n setLoadedDistrictReferenceOverlay(null);\n loadDistrictReferenceOverlay(activeDrillDownId, sourceState)\n .then((overlay) => { if (!cancelled) setLoadedDistrictReferenceOverlay({ stateId: activeDrillDownId, overlay }); })\n // Optional reference context must not prevent a usable district data view.\n .catch(() => { if (!cancelled) setLoadedDistrictReferenceOverlay({ stateId: activeDrillDownId, overlay: null }); });\n return () => { cancelled = true; };\n }, [activeDrillDownId, loadDistrictReferenceOverlay, stateRegions]);\n\n useEffect(() => {\n let cancelled = false;\n if (!activeSubDrillDownId || !loadSubDistricts) {\n setLoadedSubDistricts(null);\n setLoadingDistrict(null);\n setSubLoadError(null);\n return;\n }\n const sourceDistrict = districtRegions.find((region) => region.id === activeSubDrillDownId);\n // The district layer has not arrived yet, so there is nothing to load from.\n // This effect re-runs once it does.\n if (!sourceDistrict || !activeDrillDownId) return;\n setLoadingDistrict(activeSubDrillDownId);\n setSubLoadError(null);\n setLoadedSubDistricts(null);\n loadSubDistricts(activeSubDrillDownId, sourceDistrict, activeDrillDownId)\n .then((loaded) => {\n if (cancelled) return;\n if (!loaded) {\n // This district is a leaf. Step back to the district view and leave it\n // selected, rather than opening a level with nothing in it.\n setLeafDistrictIds((known) => known.has(activeSubDrillDownId) ? known : new Set(known).add(activeSubDrillDownId));\n setActiveSubDrillDownId(null);\n setActiveSelectedId(sourceDistrict.id);\n onSubDistrictDrillDownChange?.(null, sourceDistrict);\n return;\n }\n setLoadedSubDistricts({ districtId: activeSubDrillDownId, layer: loaded });\n })\n .catch((error: unknown) => { if (!cancelled) setSubLoadError(error instanceof Error ? error : new Error(\"Unable to load sub-districts.\")); })\n .finally(() => { if (!cancelled) setLoadingDistrict(null); });\n return () => { cancelled = true; };\n // `onSubDistrictDrillDownChange` and the setters are deliberately not\n // dependencies: a host passing an inline callback would otherwise refetch on\n // every render.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [activeDrillDownId, activeSubDrillDownId, districtRegions, loadSubDistricts]);\n\n // A district id only means something inside the state it came from, so leaving\n // or changing the state drops the level below it. Seeded with the mount-time\n // value so an initial state + district pair survives: this must fire on a\n // change, not on arrival.\n const priorDrillDownId = useRef(activeDrillDownId);\n useEffect(() => {\n if (priorDrillDownId.current === activeDrillDownId) return;\n priorDrillDownId.current = activeDrillDownId;\n setActiveSubDrillDownId(null);\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [activeDrillDownId]);\n\n useEffect(() => {\n const regionId = restoreFocusId.current;\n if (!regionId) return;\n // Focus is restored onto the region that was just stepped out of, so it waits\n // until the level holding that region is the one being drawn.\n if (!regions.some((region) => region.id === regionId)) return;\n restoreFocusId.current = null;\n pathRefs.current[regionId]?.focus();\n }, [level, regions]);\n\n useEffect(() => {\n if (!inspectedId) return;\n if (!regions.some((region) => region.id === inspectedId)) {\n inspectedIdRef.current = null;\n setInspectedId(null);\n }\n }, [inspectedId, regions]);\n\n // Mirrors `inspectedId` synchronously so a single gesture that exits both a region\n // and the canvas doesn't report the clear twice. Deliberately limited to redundant\n // clears: re-inspecting the same region must still notify, because focus restoration\n // after breadcrumb-back re-inspects the region `goBack` already primed.\n const inspect = (region: PreparedRegion | null) => {\n const nextId = region?.id ?? null;\n if (nextId === null && inspectedIdRef.current === null) return;\n inspectedIdRef.current = nextId;\n setInspectedId(nextId);\n onInspect?.(region ?? null, level);\n };\n\n const activate = (region: PreparedRegion) => {\n onRegionClick?.(region, level);\n setActiveSelectedId(region.id);\n onSelectedChange?.(region, level);\n if (level === \"state\" && loadDistricts) {\n setActiveSelectedId(null);\n setActiveDrillDownId(region.id);\n onDrillDownChange?.(region.id, region);\n return;\n }\n // A district is a leaf unless the host offers a level below it. Whether this\n // particular district actually has one is only known once the loader answers,\n // so the drill is entered optimistically and stepped back out if it returns null.\n if (level === \"district\" && loadSubDistricts && !leafDistrictIds.has(region.id)) {\n setActiveSelectedId(null);\n setActiveSubDrillDownId(region.id);\n onSubDistrictDrillDownChange?.(region.id, region);\n }\n };\n\n /** Steps up exactly one level, so the breadcrumb and the back button agree. */\n const goBack = () => {\n if (level === \"subdistrict\") {\n const priorDistrict = drilledDistrict ?? undefined;\n setActiveSubDrillDownId(null);\n setActiveSelectedId(priorDistrict?.id ?? null);\n inspectedIdRef.current = priorDistrict?.id ?? null;\n setInspectedId(priorDistrict?.id ?? null);\n restoreFocusId.current = priorDistrict?.id ?? null;\n onSubDistrictDrillDownChange?.(null, priorDistrict);\n return;\n }\n const priorState = drilledState ?? undefined;\n setActiveDrillDownId(null);\n setActiveSubDrillDownId(null);\n setActiveSelectedId(priorState?.id ?? null);\n inspectedIdRef.current = priorState?.id ?? null;\n setInspectedId(priorState?.id ?? null);\n restoreFocusId.current = priorState?.id ?? null;\n onDrillDownChange?.(null, priorState);\n };\n\n /** Jumps straight to the national map from any level. */\n const goToStates = () => {\n const priorState = drilledState ?? undefined;\n const wasDrilledDistrict = drilledDistrict ?? undefined;\n setActiveSubDrillDownId(null);\n setActiveDrillDownId(null);\n setActiveSelectedId(priorState?.id ?? null);\n inspectedIdRef.current = priorState?.id ?? null;\n setInspectedId(priorState?.id ?? null);\n restoreFocusId.current = priorState?.id ?? null;\n if (wasDrilledDistrict) onSubDistrictDrillDownChange?.(null, wasDrilledDistrict);\n onDrillDownChange?.(null, priorState);\n };\n\n // Shared by the tooltip and the insight panel so the two can never disagree\n // about share or rank — they only differ in which region they describe.\n const toTooltipContext = useCallback((region: PreparedRegion): TooltipContext => {\n const valued = regions.filter((candidate) => candidate.value !== null);\n // Ties share the better rank (\"2nd of 36\" twice, then 4th), which is what a\n // reader expects from a leaderboard and avoids an arbitrary tiebreak.\n const rank = region.value === null\n ? null\n : valued.filter((candidate) => (candidate.value ?? 0) > (region.value ?? 0)).length + 1;\n return {\n ...region,\n level,\n total,\n share: region.value === null || total === 0 ? null : (region.value / total) * 100,\n rank,\n rankedCount: valued.length,\n };\n }, [level, regions, total]);\n\n const tooltipContext = useMemo<TooltipContext | null>(\n () => inspected ? toTooltipContext(inspected) : null,\n [inspected, toTooltipContext],\n );\n // Unlike the tooltip, the host-owned insight panel is meant to stay informative\n // when nothing is hovered, so it still falls back to the selected region.\n const insightSource = inspected ?? selected;\n const insightContext = useMemo<InsightContext | null>(() => insightSource\n ? { ...toTooltipContext(insightSource), selected: insightSource.id === selected?.id }\n : null, [insightSource, selected?.id, toTooltipContext]);\n /**\n * Where each region's value label goes, and whether it needs a leader line.\n * Small regions are moved into clear space beside themselves; everything else\n * keeps its number at its centroid.\n */\n const valuePlacements = useMemo(() => {\n const coversAnother = (region: PreparedRegion, at: Point, halfWidth: number, halfHeight: number) => {\n const probes: Point[] = [\n at,\n [at[0] - halfWidth, at[1] - halfHeight],\n [at[0] + halfWidth, at[1] + halfHeight],\n [at[0] + halfWidth, at[1] - halfHeight],\n [at[0] - halfWidth, at[1] + halfHeight],\n ];\n return regions.some((other) => other.id !== region.id && other.partBounds.some(([minX, minY, maxX, maxY]) =>\n probes.some((probe) => probe[0] >= minX && probe[0] <= maxX && probe[1] >= minY && probe[1] <= maxY)));\n };\n\n return regions.map((region) => {\n const text = region.value === null ? \"—\" : formatValue(region.value);\n // Glyph metrics without measuring the DOM: the stylesheet sets 11px bold,\n // and digits in that face are close enough to half-em wide for placement.\n const halfWidth = Math.max(text.length * 3.1, 3);\n const halfHeight = 5.5;\n const isSmall = region.extent > 0 && region.extent < SMALL_REGION_EXTENT;\n const fitsInside = region.partBounds.some(([minX, minY, maxX, maxY]) =>\n maxX - minX >= halfWidth * 2 && maxY - minY >= halfHeight * 2);\n\n if (!isSmall && fitsInside) return { region, text, at: region.centroid as Point, leader: null };\n\n const outside = isSmall\n ? placeOutsideLabel({\n anchor: region.centroid,\n clearance: Math.max(region.extent, MIN_REGION_MARKER_SIZE) / 2 + 4 + halfWidth,\n halfSize: [halfWidth, halfHeight],\n viewBox: [VIEWBOX.width, VIEWBOX.height],\n centre: [VIEWBOX.width / 2, VIEWBOX.height / 2],\n // Moving a label out only helps if there is open space to move it into.\n // Goa and Puducherry have sea beside them; Delhi is ringed by other\n // states, so its number stays put rather than landing on a neighbour.\n isBlocked: (candidate) => coversAnother(region, candidate, halfWidth, halfHeight),\n })\n : null;\n\n if (!outside) {\n return fitsInside ? { region, text, at: region.centroid as Point, leader: null } : null;\n }\n\n const gap = Math.max(region.extent, MIN_REGION_MARKER_SIZE) / 2 + 1;\n const dx = outside[0] - region.centroid[0];\n const dy = outside[1] - region.centroid[1];\n const length = Math.hypot(dx, dy);\n if (length <= gap + halfWidth) return { region, text, at: outside, leader: null };\n return {\n region,\n text,\n at: outside,\n leader: [\n [region.centroid[0] + (dx / length) * gap, region.centroid[1] + (dy / length) * gap],\n [outside[0] - (dx / length) * (halfWidth + 1.5), outside[1] - (dy / length) * (halfWidth + 1.5)],\n ] as [Point, Point],\n };\n }).filter((placement): placement is NonNullable<typeof placement> => placement !== null);\n }, [formatValue, regions]);\n\n const smallMarkers = useMemo(\n () => regions.filter((region) => region.extent > 0 && region.extent < MIN_REGION_MARKER_SIZE),\n [regions],\n );\n\n /**\n * A click that reaches the svg itself missed every region path. Either it\n * landed near a small one — Goa, Puducherry, the island groups, all awkward to\n * hit — or it is a click on open sea, which clears the selection.\n */\n const handleBackgroundClick = (event: ReactMouseEvent<SVGSVGElement>) => {\n if (event.target !== event.currentTarget) return; // a region handled it\n // An unlaid-out svg (or a test environment that reports zero-size rects) has\n // no usable coordinates, so the proximity step is skipped — but the click is\n // still a click on the background and must clear the selection.\n const rect = event.currentTarget.getBoundingClientRect();\n const point: Point | null = rect.width > 0 && rect.height > 0\n ? (() => {\n // The svg scales its view box to fit while preserving aspect ratio, so\n // the scale is the smaller ratio and the remainder is centring.\n const scale = Math.min(rect.width / VIEWBOX.width, rect.height / VIEWBOX.height);\n return [\n (event.clientX - rect.left - (rect.width - VIEWBOX.width * scale) / 2) / scale,\n (event.clientY - rect.top - (rect.height - VIEWBOX.height * scale) / 2) / scale,\n ] as Point;\n })()\n : null;\n\n let nearest: PreparedRegion | null = null;\n if (point) {\n let nearestDistance = Infinity;\n for (const region of regions) {\n if (region.extent <= 0 || region.extent >= SMALL_REGION_EXTENT) continue;\n const distance = distanceToParts(point, region.partBounds);\n if (distance <= SMALL_REGION_CLICK_RADIUS && distance < nearestDistance) {\n nearestDistance = distance;\n nearest = region;\n }\n }\n }\n if (nearest) { activate(nearest); return; }\n\n onBackgroundClick?.();\n if (activeSelectedId !== null) {\n setActiveSelectedId(null);\n onSelectedChange?.(null, level);\n }\n };\n\n const canDrill = level === \"state\" ? Boolean(loadDistricts) : level === \"district\" ? Boolean(loadSubDistricts) : false;\n const drillActionLabel = level === \"state\" ? \"Activate to view districts.\" : \"Activate to view sub-districts.\";\n const regionCanDrill = (id: string) => canDrill && !(level === \"district\" && leafDistrictIds.has(id));\n const visibleReferenceRegions = level === \"state\" ? referenceRegions : districtReferenceRegions;\n const mergedReferenceIds = useMemo(() => new Set(referenceOverlayMergeIds), [referenceOverlayMergeIds]);\n\n useEffect(() => { onInsight?.(insightContext); }, [insightContext, onInsight]);\n\n // Nudge the anchored tooltip back inside the map. This runs in a layout effect,\n // not an effect, so the un-nudged position never paints — that one frame would be\n // a visible jump on exactly the edge regions this exists to fix. Keyed on the\n // content, not just the region, because the box is sized by what's in it.\n useLayoutEffect(() => {\n const anchor = tooltipAnchorRef.current;\n const canvas = canvasRef.current;\n if (!anchor || !canvas) return;\n\n // Measure the default placement (centered, above), so the correction is\n // computed against a known starting point rather than the last region's.\n anchor.style.removeProperty(\"--india-map-tooltip-dx\");\n anchor.style.removeProperty(\"--india-map-tooltip-dy\");\n\n const tooltipRect = anchor.getBoundingClientRect();\n const boundsRect = canvas.getBoundingClientRect();\n if (tooltipRect.width === 0 || boundsRect.width === 0) return; // not laid out (hidden, or jsdom)\n\n const { dx, side } = placeTooltip(tooltipRect, boundsRect, TOOLTIP_GAP_PX);\n if (dx !== 0) anchor.style.setProperty(\"--india-map-tooltip-dx\", `${Math.round(dx)}px`);\n if (side === \"below\") anchor.style.setProperty(\"--india-map-tooltip-dy\", `${TOOLTIP_GAP_PX}px`);\n }, [tooltipContext?.id, tooltipContext?.value, tooltipContext?.label, renderTooltip]);\n\n return (\n <section className={[\"india-choropleth\", !interactive && \"india-choropleth--static\", className].filter(Boolean).join(\" \")} aria-busy={loadingState || loadingDistrict ? \"true\" : undefined}>\n {showBreadcrumb ? (\n <div className=\"india-choropleth__toolbar\">\n <nav className=\"india-choropleth__breadcrumb\" aria-label=\"Map hierarchy\">\n {drilledState\n ? <button className=\"india-choropleth__back\" type=\"button\" onClick={goToStates}>All states</button>\n : <span>All states</span>}\n {drilledState ? (\n <>\n <span aria-hidden=\"true\">/</span>\n {/* The state is a link back only once there is a level below it to\n come back from; on the district view it is where you already are. */}\n {isSubDrillRequested\n ? <button className=\"india-choropleth__back\" type=\"button\" onClick={goBack}>{drilledState.label}</button>\n : <span aria-current=\"page\">{drilledState.label}</span>}\n </>\n ) : null}\n {isSubDrillRequested && drilledDistrict ? (\n <><span aria-hidden=\"true\">/</span><span aria-current=\"page\">{drilledDistrict.label}</span></>\n ) : null}\n </nav>\n </div>\n ) : null}\n <div ref={canvasRef} className=\"india-choropleth__canvas\" onMouseLeave={interactive ? () => inspect(null) : undefined}>\n {isSubDrillRequested && (!subDistrictLayer || subLoadError) ? (\n <div className=\"india-choropleth__status\" role={subLoadError ? \"alert\" : \"status\"}>\n {subLoadError ? subLoadError.message : loadingDistrict ? \"Loading sub-districts…\" : \"Sub-district data is unavailable for this district.\"}\n </div>\n ) : isDrillRequested && (!districtLayer || loadError) ? (\n <div className=\"india-choropleth__status\" role={loadError ? \"alert\" : \"status\"}>\n {loadError ? loadError.message : loadingState ? \"Loading districts…\" : \"District data is unavailable for this state.\"}\n </div>\n ) : regions.length === 0 ? (\n <div className=\"india-choropleth__status\" role=\"status\">\n {level === \"subdistrict\" ? \"No sub-district data is available for this district.\" : \"No district data is available for this state.\"}\n </div>\n ) : (\n <svg\n className={`india-choropleth__svg${filterBucket !== null ? \" india-choropleth__svg--filtered\" : \"\"}`}\n viewBox={`0 0 ${VIEWBOX.width} ${VIEWBOX.height}`}\n role=\"group\"\n aria-label={ariaLabel}\n onKeyDown={interactive ? (event) => { if (event.key === \"Escape\") { event.preventDefault(); inspect(null); } } : undefined}\n onClick={interactive ? handleBackgroundClick : undefined}\n >\n {visibleReferenceRegions.length > 0 ? (\n <>\n <defs>\n <pattern id={hatchId} width=\"8\" height=\"8\" patternUnits=\"userSpaceOnUse\" patternTransform=\"rotate(45)\">\n <rect width=\"8\" height=\"8\" fill=\"var(--india-map-reference-bg)\" />\n <line x1=\"0\" y1=\"0\" x2=\"0\" y2=\"8\" stroke=\"var(--india-map-reference-hatch)\" strokeWidth=\"2\" />\n </pattern>\n </defs>\n <g className=\"india-choropleth__reference-fill\" role=\"group\" aria-label=\"Non-statistical reference context.\">\n {visibleReferenceRegions.map((region) => (\n <path\n key={region.id}\n d={region.path}\n fill={referenceOverlayFill === \"solid\" ? \"var(--india-map-reference-bg)\" : `url(#${hatchId})`}\n aria-label={`${region.label}.${region.description ? ` ${region.description}` : \"\"}`}\n role=\"img\"\n />\n ))}\n </g>\n </>\n ) : null}\n {/* Hit areas for scattered regions, first so that every real outline is\n painted on top of them: the hull spanning Lakshadweep's islands is\n open sea, but Puducherry's spans the Tamil Nadu coast, and a hull\n must never take a pointer from a region that is actually there.\n Not focusable — keyboard focus has no coordinates, so the one tab\n stop stays on the region itself. */}\n {interactive ? (\n <g className=\"india-choropleth__hit-areas\" aria-hidden=\"true\">\n {regions.filter((region) => region.hitPath).map((region) => (\n <path\n key={region.id}\n d={region.hitPath!}\n fill=\"none\"\n pointerEvents=\"all\"\n tabIndex={-1}\n onMouseEnter={() => inspect(region)}\n onMouseLeave={() => inspect(null)}\n onClick={() => activate(region)}\n />\n ))}\n </g>\n ) : null}\n {regions.map((region) => {\n const isInspected = region.id === inspected?.id;\n const isSelected = region.id === selected?.id;\n const action = regionCanDrill(region.id) ? drillActionLabel : \"Activate to select.\";\n const textValue = region.value === null ? \"No data\" : formatValue(region.value);\n return (\n <path\n key={region.id}\n className={`india-choropleth__region${isInspected ? \" india-choropleth__region--inspected\" : \"\"}${isSelected ? \" india-choropleth__region--selected\" : \"\"}${mergedReferenceIds.has(region.id) ? \" india-choropleth__region--reference-merged\" : \"\"}${isDimmed(region.id) ? \" india-choropleth__dimmed\" : \"\"}`}\n d={region.path}\n fill={colorFor(region.value, region, min, max, colorScale)}\n tabIndex={interactive ? 0 : -1}\n role={interactive ? \"button\" : undefined}\n aria-label={`${region.label}, ${textValue}. ${action}`}\n aria-pressed={interactive ? isSelected : undefined}\n aria-describedby={isInspected ? tooltipId : undefined}\n ref={(element) => { pathRefs.current[region.id] = element; }}\n onMouseEnter={interactive ? () => inspect(region) : undefined}\n // The canvas is much wider than the drawn map, so leaving a region\n // usually lands on blank canvas rather than leaving the canvas at all.\n // Without this the last-hovered tooltip stays pinned indefinitely.\n // Moving straight to a sibling region dispatches this leave and that\n // region's enter from the same native event, so the tooltip switches\n // in one batch instead of blanking.\n onMouseLeave={interactive ? () => inspect(null) : undefined}\n onFocus={interactive ? () => inspect(region) : undefined}\n // Tabbing to a sibling region re-inspects it synchronously right after\n // this fires, so clearing here only matters when focus leaves the map.\n onBlur={interactive ? () => inspect(null) : undefined}\n onClick={interactive ? () => activate(region) : undefined}\n onKeyDown={interactive ? (event) => {\n if (event.key === \"Enter\" || event.key === \" \") { event.preventDefault(); activate(region); }\n } : undefined}\n />\n );\n })}\n {/* A region whose largest part is smaller than the marker is invisible at\n this scale — Puducherry's enclaves are a couple of units — so it gets\n a dot in its own colour instead of nothing at all. The dot takes the\n pointer as well: it is painted over whatever is beneath it, so it is\n what the reader sees and aims at, and the outline it stands in for is\n too small to hover. Focus stays on the region path, its one tab stop. */}\n {smallMarkers.length > 0 ? (\n <g className=\"india-choropleth__small-markers\" aria-hidden=\"true\">\n {smallMarkers.map((region) => (\n <circle\n key={region.id}\n className={isDimmed(region.id) ? \"india-choropleth__dimmed\" : undefined}\n cx={region.centroid[0]}\n cy={region.centroid[1]}\n r={MIN_REGION_MARKER_SIZE / 2}\n fill={colorFor(region.value, region, min, max, colorScale)}\n onMouseEnter={interactive ? () => inspect(region) : undefined}\n onMouseLeave={interactive ? () => inspect(null) : undefined}\n onClick={interactive ? () => activate(region) : undefined}\n />\n ))}\n </g>\n ) : null}\n {showRegionValues ? (\n <>\n <g className=\"india-choropleth__value-leaders\" aria-hidden=\"true\">\n {valuePlacements.filter((p) => p.leader).map((p) => (\n <line\n key={p.region.id}\n className={isDimmed(p.region.id) ? \"india-choropleth__dimmed\" : undefined}\n x1={p.leader![0][0]} y1={p.leader![0][1]} x2={p.leader![1][0]} y2={p.leader![1][1]}\n />\n ))}\n </g>\n <g className={`india-choropleth__region-values${level === \"state\" ? \"\" : \" india-choropleth__region-values--district\"}`} aria-hidden=\"true\">\n {valuePlacements.map((p) => (\n <text\n key={p.region.id}\n className={isDimmed(p.region.id) ? \"india-choropleth__dimmed\" : undefined}\n x={p.at[0]} y={p.at[1]} textAnchor=\"middle\" dominantBaseline=\"central\"\n >\n {p.text}\n </text>\n ))}\n </g>\n </>\n ) : null}\n {visibleReferenceRegions.length > 0 ? (\n <g className=\"india-choropleth__reference-outline\" aria-hidden=\"true\">\n {visibleReferenceRegions.map((region) => <path key={region.id} d={region.path} fill=\"none\" />)}\n </g>\n ) : null}\n {/* Selection is drawn as a ring on top of everything, rather than by recoloring\n the region: on a choropleth the fill *is* the data, so overwriting it made the\n selected region's color stop meaning anything. Hovering a region lifts it 2px,\n so the ring matches that lift when the selected region is also the inspected\n one — otherwise the fill slides out from under its own outline. */}\n {selected ? (\n <g\n className={`india-choropleth__selection${selected.id === inspected?.id ? \" india-choropleth__selection--lifted\" : \"\"}`}\n aria-hidden=\"true\"\n >\n <path className=\"india-choropleth__selection-halo\" d={selected.path} fill=\"none\" />\n <path className=\"india-choropleth__selection-ring\" d={selected.path} fill=\"none\" />\n </g>\n ) : null}\n </svg>\n )}\n {tooltipContext && regions.length > 0 ? (\n <div ref={tooltipAnchorRef} className=\"india-choropleth__tooltip-anchor\" style={{ left: `${(inspected!.centroid[0] / VIEWBOX.width) * 100}%`, top: `${(inspected!.centroid[1] / VIEWBOX.height) * 100}%` }}>\n {/* Deliberately not `role=\"status\"`. As a live region it re-announced the whole\n tooltip on every hover *and* every focus move, and the content is richer now.\n The region aria-label already carries label + value, and `aria-describedby`\n reads this box out on focus — once, on demand. */}\n <div id={tooltipId} className=\"india-choropleth__tooltip\">\n {renderTooltip ? renderTooltip(tooltipContext) : defaultTooltip(tooltipContext, formatValue)}\n </div>\n </div>\n ) : null}\n </div>\n {showLegend ? (\n <div\n className=\"india-choropleth__legend\"\n role=\"group\"\n aria-label={`Color scale: ${legendLabels[0]} to ${legendLabels[1]} values`}\n onKeyDown={interactive ? (event) => { if (event.key === \"Escape\") { event.preventDefault(); setActiveBucket(null); } } : undefined}\n >\n <span>{legendLabels[0]}</span><div className=\"india-choropleth__swatches\" aria-hidden={interactive ? undefined : true}>\n {/* Each swatch filters the map to its own band. An empty band is left\n as a swatch you can still read — silently skipping it would hide\n the fact that the ramp has a gap there — but it does nothing,\n because filtering to nothing just dims the whole map. */}\n {buckets.map((bucket) => interactive ? (\n <button\n key={bucket.index}\n type=\"button\"\n className={`india-choropleth__swatch${filterBucket === bucket.index ? \" india-choropleth__swatch--active\" : \"\"}${filterBucket !== null && filterBucket !== bucket.index ? \" india-choropleth__swatch--muted\" : \"\"}`}\n style={{ backgroundColor: bucket.color }}\n aria-pressed={filterBucket === bucket.index}\n aria-disabled={bucket.matches === 0 ? true : undefined}\n aria-label={legendBucketLabel(bucket, formatValue)}\n // Sighted readers get the same sentence the accessible name carries,\n // which is the only place an empty band explains itself now that it\n // is no longer faded.\n title={legendBucketLabel(bucket, formatValue)}\n onClick={() => {\n if (bucket.matches === 0) return;\n setActiveBucket(filterBucket === bucket.index ? null : bucket.index);\n }}\n />\n ) : (\n <i className=\"india-choropleth__swatch\" key={bucket.index} style={{ backgroundColor: bucket.color }} />\n ))}\n </div><span>{legendLabels[1]}</span>\n {visibleReferenceRegions.length > 0 ? <><i className={`india-choropleth__reference-key${referenceOverlayFill === \"solid\" ? \" india-choropleth__reference-key--solid\" : \"\"}`} aria-hidden=\"true\" /><span>{referenceOverlayLegendLabel}</span></> : null}\n </div>\n ) : null}\n {renderInsights ? <aside className=\"india-choropleth__insights\" aria-live=\"polite\">{renderInsights(insightContext)}</aside> : null}\n </section>\n );\n}\n","import { feature as topoFeature } from \"topojson-client\";\nimport type { GeometryObject } from \"topojson-specification\";\nimport type { GeometrySource, MapFeatureCollection } from \"./types\";\n\nexport function asFeatureCollection(source: GeometrySource): MapFeatureCollection {\n if (\"type\" in source && source.type === \"FeatureCollection\") {\n return source;\n }\n\n const topoSource = source as Exclude<GeometrySource, MapFeatureCollection>;\n\n const object = (typeof topoSource.object === \"string\"\n ? topoSource.topology.objects[topoSource.object]!\n : topoSource.object) as unknown as GeometryObject;\n if (!object) {\n throw new Error(\"The named TopoJSON object does not exist in this topology.\");\n }\n\n const unpacked = topoFeature(topoSource.topology, object);\n return unpacked.type === \"FeatureCollection\"\n ? (unpacked as MapFeatureCollection)\n : { type: \"FeatureCollection\", features: [unpacked] };\n}\n\nexport function totalOf(values: readonly (number | null)[]) {\n return values.reduce<number>((total, value) => total + (value ?? 0), 0);\n}\n","/**\n * The legend, as a filter.\n *\n * Every value on the map is painted from one of the ramp's colours. That makes\n * the legend a ready-made set of value bands, and picking a band is the\n * question a reader of a choropleth actually has: *which regions are the dark\n * ones?* These helpers answer it — which swatch a value belongs to, what each\n * swatch stands for, and how many regions land there.\n *\n * [swatchIndexOf] is the single definition of that mapping: the renderers pick\n * a region's fill with it too, so \"highlight the regions painted in this\n * colour\" is true by construction rather than by two formulas agreeing.\n *\n * Pure functions of values, so the DOM renderer and the React component share\n * one definition of the behaviour.\n */\n\n/** One swatch: the colour, and what the map actually has in it. */\nexport interface LegendBucket {\n index: number;\n color: string;\n /**\n * Lowest and highest value that lands here, or null when nothing does.\n *\n * The band the ramp *nominally* covers is a half-step either side of the\n * swatch's own stop, which lands on numbers like \"24.333 to 31\" that appear\n * nowhere in the data. What a reader wants to know is what picking this\n * swatch will give them, so the range is measured from the regions in it.\n */\n from: number | null;\n to: number | null;\n /** How many regions land here. Zero means the swatch would filter to nothing. */\n matches: number;\n}\n\n/**\n * Which swatch a value is painted from, or null when there is nothing to paint\n * — no value, or no ramp to paint it with.\n *\n * A ramp of one colour, or data with no spread at all, collapses to the top\n * swatch: there is a single band and every value is in it.\n */\nexport function swatchIndexOf(value: number | null, min: number, max: number, count: number): number | null {\n if (value === null || count <= 0) return null;\n if (count === 1 || max === min) return count - 1;\n const index = Math.round(((value - min) / (max - min)) * (count - 1));\n return Math.min(Math.max(index, 0), count - 1);\n}\n\n/**\n * The ramp described swatch by swatch.\n *\n * With a function colour scale the renderers show the default ramp, because a\n * function has no swatches to show. The bands are still the honest reading of\n * \"lower to higher\"; the swatch colour just isn't any region's actual fill.\n */\nexport function legendBuckets(\n colors: readonly string[],\n values: readonly (number | null)[],\n min: number,\n max: number,\n): LegendBucket[] {\n const count = colors.length;\n return colors.map((color, index) => {\n const members = values.filter((value): value is number => swatchIndexOf(value, min, max, count) === index);\n return {\n index,\n color,\n from: members.length ? Math.min(...members) : null,\n to: members.length ? Math.max(...members) : null,\n matches: members.length,\n };\n });\n}\n\n/**\n * What a swatch does, in words — the accessible name for its control, since the\n * colour itself carries the meaning and a screen reader cannot see it.\n */\nexport function legendBucketLabel(bucket: LegendBucket, formatValue: (value: number) => string): string {\n if (bucket.matches === 0 || bucket.from === null || bucket.to === null) return \"No regions in this band\";\n const range = bucket.from === bucket.to\n ? formatValue(bucket.from)\n : `${formatValue(bucket.from)} to ${formatValue(bucket.to)}`;\n return `Highlight ${bucket.matches} ${bucket.matches === 1 ? \"region\" : \"regions\"}, ${range}`;\n}\n\n","/**\n * Edge handling for the centroid-anchored tooltip.\n *\n * The tooltip is anchored to the region's centroid rather than the cursor on\n * purpose: hover and keyboard focus run through the same inspect path, and a\n * focused region has no cursor position to follow. That anchor still has to be\n * nudged so the box never leaves the map — a state at the top of the map (Jammu\n * & Kashmir on a narrow screen) would otherwise render *above* the map, over\n * whatever the host page put there.\n *\n * Kept as a pure function of two rects so both the DOM renderer and the React\n * component can share the same math.\n */\n\nexport interface Rect {\n left: number;\n right: number;\n top: number;\n bottom: number;\n width: number;\n height: number;\n}\n\nexport interface TooltipPlacement {\n /** Horizontal correction in px, applied on top of the centering translate. */\n dx: number;\n /** Which side of the centroid the tooltip sits on. */\n side: \"above\" | \"below\";\n}\n\n/**\n * @param tooltip The tooltip's rect as currently laid out (centered above the centroid).\n * @param bounds The area the tooltip must stay inside — the map canvas.\n * @param gap Space between the centroid and the tooltip edge, in px.\n * @param padding Minimum breathing room between the tooltip and the bounds edge, in px.\n */\nexport function placeTooltip(tooltip: Rect, bounds: Rect, gap: number, padding = 4): TooltipPlacement {\n let dx = 0;\n // A tooltip wider than the space available can't satisfy both edges; favor the\n // left one so the region name (which leads the content) stays readable.\n if (tooltip.right > bounds.right - padding) dx = bounds.right - padding - tooltip.right;\n if (tooltip.left + dx < bounds.left + padding) dx = bounds.left + padding - tooltip.left;\n\n // Flip below only when there genuinely isn't room above. The flipped position\n // is `gap` below the centroid, which is `height + 2 * gap` further down.\n const side: TooltipPlacement[\"side\"] = tooltip.top < bounds.top + padding ? \"below\" : \"above\";\n\n return { dx, side };\n}\n","/**\n * Geometry for regions that are too small to use normally.\n *\n * Goa is a few view-box units across, Puducherry is scattered enclaves, and\n * Lakshadweep's islands are a fraction of a pixel at national scale. Left alone\n * they are invisible, unclickable, and their value labels either don't fit or\n * land on a neighbour. These helpers back the three fixes for that: a marker so\n * they can be seen, a click buffer so they can be hit, and label placement that\n * moves a number into open space beside the region.\n *\n * Pure functions of projected coordinates, so the DOM renderer, the React\n * component and the Dart port can all share one definition of the behaviour.\n */\n\nexport type Point = readonly [number, number];\n\n/** Axis-aligned box as `[minX, minY, maxX, maxY]`. */\nexport type Box = readonly [number, number, number, number];\n\n/**\n * Regions kept at their true size even when they are small enough to qualify\n * for exaggeration.\n *\n * Exaggeration assumes a region has room to grow into. Puducherry does not: it\n * is four coastal enclaves *inside* Tamil Nadu, so growing them to the\n * visibility threshold pushes each one several units into the state around it,\n * and the map ends up showing a Puducherry that is the wrong shape in the wrong\n * place. Lakshadweep's islands grow into open sea, where nothing is displaced,\n * which is the case the feature was built for.\n *\n * Matched on the id containing the name because host data brings its own id\n * scheme; the bundled layer uses `in-cs-34-puducherry`. A district id inside the\n * UT matches too, which is a no-op: drilled into, its districts fill the map and\n * are far past the threshold that would have grown them.\n */\nconst TRUE_GEOMETRY_REGIONS = [\"puducherry\"];\n\nexport function keepsTrueGeometry(id: string): boolean {\n const normalized = id.toLowerCase();\n return TRUE_GEOMETRY_REGIONS.some((name) => normalized.includes(name));\n}\n\nexport function boundsOfRing(ring: readonly Point[]): Box {\n let minX = Infinity;\n let minY = Infinity;\n let maxX = -Infinity;\n let maxY = -Infinity;\n for (const [x, y] of ring) {\n if (x < minX) minX = x;\n if (x > maxX) maxX = x;\n if (y < minY) minY = y;\n if (y > maxY) maxY = y;\n }\n return [minX, minY, maxX, maxY];\n}\n\n/**\n * Longest side of the largest ring's box. This is the measure of \"small\", not\n * the feature's overall bounds: an island group's bounds can be large while\n * every island in it is sub-pixel.\n */\nexport function largestRingExtent(rings: readonly (readonly Point[])[]): number {\n let largest = 0;\n for (const ring of rings) {\n if (ring.length === 0) continue;\n const [minX, minY, maxX, maxY] = boundsOfRing(ring);\n const extent = Math.max(maxX - minX, maxY - minY);\n if (extent > largest) largest = extent;\n }\n return largest;\n}\n\nfunction signedArea(ring: readonly Point[]): number {\n let total = 0;\n for (let i = 0; i < ring.length; i++) {\n const [ax, ay] = ring[i]!;\n const [bx, by] = ring[(i + 1) % ring.length]!;\n total += ax * by - bx * ay;\n }\n return total / 2;\n}\n\n/**\n * Area-weighted centroid of the largest ring.\n *\n * Deliberately not the whole feature's centroid: averaging across parts pulls\n * Andaman & Nicobar's label out to sea between its islands, and Gujarat's off\n * its own coast.\n */\nexport function labelPointFor(rings: readonly (readonly Point[])[], fallback: Point): Point {\n let largest: readonly Point[] | null = null;\n let largestArea = 0;\n for (const ring of rings) {\n if (ring.length < 3) continue;\n const area = Math.abs(signedArea(ring));\n if (area > largestArea) {\n largestArea = area;\n largest = ring;\n }\n }\n if (!largest || largestArea === 0) return fallback;\n\n const area = signedArea(largest);\n let cx = 0;\n let cy = 0;\n for (let i = 0; i < largest.length; i++) {\n const [ax, ay] = largest[i]!;\n const [bx, by] = largest[(i + 1) % largest.length]!;\n const cross = ax * by - bx * ay;\n cx += (ax + bx) * cross;\n cy += (ay + by) * cross;\n }\n const centroid: Point = [cx / (6 * area), cy / (6 * area)];\n return Number.isFinite(centroid[0]) && Number.isFinite(centroid[1]) ? centroid : fallback;\n}\n\n/** Distance from a point to the nearest edge of a box; zero when inside it. */\nexport function distanceToBox(point: Point, box: Box): number {\n const [x, y] = point;\n const [minX, minY, maxX, maxY] = box;\n const dx = Math.max(minX - x, 0) + Math.max(x - maxX, 0);\n const dy = Math.max(minY - y, 0) + Math.max(y - maxY, 0);\n return Math.hypot(dx, dy);\n}\n\n/** Distance to the nearest of a region's parts. */\nexport function distanceToParts(point: Point, parts: readonly Box[]): number {\n let nearest = Infinity;\n for (const part of parts) {\n const distance = distanceToBox(point, part);\n if (distance < nearest) nearest = distance;\n }\n return nearest;\n}\n\n/**\n * Angles to try when placing a small region's label, as turns from the\n * away-from-centre direction: straight out first, then progressively to either\n * side, and back inward only as a last resort.\n *\n * One fixed direction is not enough. Puducherry sits south-east of the map's\n * middle, so the radial direction runs inland into Tamil Nadu while its open\n * water is due east.\n */\nconst LABEL_SEARCH_TURNS = [\n 0,\n Math.PI / 6, -Math.PI / 6,\n Math.PI / 3, -Math.PI / 3,\n Math.PI / 2, -Math.PI / 2,\n (2 * Math.PI) / 3, -(2 * Math.PI) / 3,\n (5 * Math.PI) / 6, -(5 * Math.PI) / 6,\n Math.PI,\n] as const;\n\nexport interface OutsideLabelOptions {\n /** Where the region is, in view-box units. */\n anchor: Point;\n /** How far the label must clear the region itself. */\n clearance: number;\n /** Half the label's width and height, used to keep it inside the view box. */\n halfSize: Point;\n /** The view box the label must stay within, as `[width, height]`. */\n viewBox: Point;\n /** Direction is measured away from this point — normally the view box's middle. */\n centre: Point;\n /** Returns true when a label centred here would cover another region. */\n isBlocked: (candidate: Point) => boolean;\n}\n\n/**\n * Find a clear spot beside a region for its value label, or null when the\n * region is hemmed in on every side (Delhi, ringed by other states) and the\n * label is better left where it was.\n */\nexport function placeOutsideLabel(options: OutsideLabelOptions): Point | null {\n const { anchor, clearance, halfSize, viewBox, centre, isBlocked } = options;\n const dx = anchor[0] - centre[0];\n const dy = anchor[1] - centre[1];\n const base = dx === 0 && dy === 0 ? 0 : Math.atan2(dy, dx);\n\n for (const turn of LABEL_SEARCH_TURNS) {\n const angle = base + turn;\n const candidate: Point = [\n clamp(anchor[0] + Math.cos(angle) * clearance, halfSize[0], viewBox[0] - halfSize[0]),\n clamp(anchor[1] + Math.sin(angle) * clearance, halfSize[1], viewBox[1] - halfSize[1]),\n ];\n if (!isBlocked(candidate)) return candidate;\n }\n return null;\n}\n\nfunction clamp(value: number, lower: number, upper: number): number {\n return Math.min(Math.max(value, lower), upper);\n}\n\n/**\n * Grow a feature whose whole geometry is too small to see, about each part's\n * own centre.\n *\n * Two guards make this safe, and both were learned the hard way:\n *\n * 1. It applies only when the feature's *largest* part is under [minExtent] —\n * that is, the whole region is tiny. West Bengal is a large state whose\n * Sundarbans delta is fourteen small islets; enlarging those individually\n * blew them up into an overlapping mess across the river mouth. A region is\n * either small enough to need help or it is not.\n * 2. Once a feature qualifies, each part grows about its own centre until it is\n * visible. This preserves every part's location and aspect ratio instead of\n * turning a dispersed archipelago into one large, misplaced blob. The cap\n * keeps the tiniest specks from reading as real landmass.\n *\n * Growth is capped by [maxScale] so a speck never reads as a real landmass.\n */\nexport function enlargeSmallParts(\n rings: readonly (readonly Point[])[],\n minExtent: number,\n maxScale = 8,\n): Point[][] {\n const unchanged = () => rings.map((ring) => [...ring]);\n if (minExtent <= 0) return unchanged();\n // Only a feature that is small *as a whole* qualifies.\n const largest = largestRingExtent(rings);\n if (largest <= 0 || largest >= minExtent) return unchanged();\n\n return rings.map((ring) => {\n const [minX, minY, maxX, maxY] = boundsOfRing(ring);\n const extent = Math.max(maxX - minX, maxY - minY);\n const scale = extent <= 0 ? 1 : Math.min(minExtent / extent, maxScale);\n const cx = (minX + maxX) / 2;\n const cy = (minY + maxY) / 2;\n return ring.map(([x, y]) => [cx + (x - cx) * scale, cy + (y - cy) * scale] as Point);\n });\n}\n\n/**\n * Convex hull of a set of points, as one closed ring (Andrew's monotone chain).\n * Returns the input when there is nothing to wrap — fewer than three distinct\n * points, or all of them collinear.\n */\nexport function convexHull(points: readonly Point[]): Point[] {\n const sorted = [...points].sort((a, b) => a[0] - b[0] || a[1] - b[1]);\n const unique: Point[] = [];\n for (const point of sorted) {\n const prior = unique.at(-1);\n if (!prior || prior[0] !== point[0] || prior[1] !== point[1]) unique.push(point);\n }\n if (unique.length < 3) return unique.map((point) => [...point] as Point);\n\n const turn = (o: Point, a: Point, b: Point) =>\n (a[0] - o[0]) * (b[1] - o[1]) - (a[1] - o[1]) * (b[0] - o[0]);\n const half = (ordered: readonly Point[]) => {\n const chain: Point[] = [];\n for (const point of ordered) {\n while (chain.length >= 2 && turn(chain[chain.length - 2]!, chain[chain.length - 1]!, point) <= 0) chain.pop();\n chain.push(point);\n }\n return chain;\n };\n\n const lower = half(unique);\n const upper = half([...unique].reverse());\n // Each chain repeats the other's first point, so drop both endpoints once.\n const hull = [...lower.slice(0, -1), ...upper.slice(0, -1)];\n return hull.length >= 3 ? hull : unique.map((point) => [...point] as Point);\n}\n\n/**\n * One continuous hit area for a region scattered across separate parts: the\n * convex hull of everything it is drawn as.\n *\n * Lakshadweep is twenty specks in open sea. Even exaggerated they are a poor\n * pointer target, and the water they enclose is how the group reads on the map,\n * so treating that water as part of the region is what a reader expects. The\n * hull is safe to be generous with only because every renderer tests it *after*\n * every real outline has missed, so it can never take a hover from a neighbour\n * it happens to span.\n *\n * Null when the region needs no help: a single part, or already big enough to\n * point at directly ([maxExtent], the same \"too small to use\" threshold the\n * click buffer and value labels work from).\n */\nexport function scatteredHitArea(\n rings: readonly (readonly Point[])[],\n maxExtent: number,\n): Point[] | null {\n if (rings.length < 2 || maxExtent <= 0) return null;\n const extent = largestRingExtent(rings);\n if (extent <= 0 || extent >= maxExtent) return null;\n const hull = convexHull(rings.flat());\n return hull.length >= 3 ? hull : null;\n}\n\n/** An SVG path string for a set of rings, used when exaggeration has moved them. */\nexport function ringsToPath(rings: readonly (readonly Point[])[]): string {\n let d = \"\";\n for (const ring of rings) {\n if (ring.length === 0) continue;\n d += `M${ring[0]![0]},${ring[0]![1]}`;\n for (let i = 1; i < ring.length; i++) d += `L${ring[i]![0]},${ring[i]![1]}`;\n d += \"Z\";\n }\n return d;\n}\n","import { useCallback, useState } from \"react\";\n\nexport function useControllableState<T>(controlled: T | undefined, initial: T) {\n const [uncontrolled, setUncontrolled] = useState<T>(initial);\n const value = controlled === undefined ? uncontrolled : controlled;\n const setValue = useCallback((next: T) => {\n if (controlled === undefined) setUncontrolled(next);\n }, [controlled]);\n return [value, setValue] as const;\n}\n"],"mappings":"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","names":["useCallback","useState","area","useState","useCallback"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bharat-choropleth",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Accessible, dependency-light SVG choropleths for India drill-down dashboards.",
5
5
  "keywords": [
6
6
  "choropleth",
@@ -25,7 +25,7 @@
25
25
  },
26
26
  "type": "module",
27
27
  "sideEffects": ["./dist/style.css"],
28
- "files": ["dist"],
28
+ "files": ["dist", "CHANGELOG.md"],
29
29
  "main": "./dist/index.js",
30
30
  "module": "./dist/index.js",
31
31
  "types": "./dist/index.d.ts",