bharat-choropleth 0.1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Bharat Choropleth contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,84 @@
1
+ # `bharat-choropleth`
2
+
3
+ An accessible React SVG choropleth for India state and district dashboards.
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.
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ npm install bharat-choropleth
11
+ ```
12
+
13
+ Import the stylesheet once in the application that mounts the map:
14
+
15
+ ```ts
16
+ import "bharat-choropleth/style.css";
17
+ ```
18
+
19
+ ## Use
20
+
21
+ The package intentionally contains no geographic boundary data. Provide a
22
+ GeoJSON feature collection or TopoJSON object plus stable IDs, labels, and
23
+ values from your data source:
24
+
25
+ ```tsx
26
+ import { IndiaChoropleth, type MapLayer } from "bharat-choropleth";
27
+ import statesTopology from "./states.topo.json";
28
+
29
+ const states: MapLayer = {
30
+ geometry: { topology: statesTopology, object: "states" },
31
+ getId: (feature) => String(feature.properties?.id),
32
+ getLabel: (feature) => String(feature.properties?.name),
33
+ getValue: (feature) => valuesByStateId[String(feature.properties?.id)] ?? null,
34
+ };
35
+
36
+ export function IndiaMap() {
37
+ return <IndiaChoropleth states={states} />;
38
+ }
39
+ ```
40
+
41
+ For lazy state-to-district navigation, add `loadDistricts`. It receives the
42
+ selected stable state ID and returns another `MapLayer`:
43
+
44
+ ```tsx
45
+ <IndiaChoropleth
46
+ states={states}
47
+ loadDistricts={async (stateId) => {
48
+ const topology = await import(`./districts/${stateId}.topo.json`);
49
+ return {
50
+ geometry: { topology: topology.default, object: "districts" },
51
+ getId: (feature) => String(feature.properties?.id),
52
+ getLabel: (feature) => String(feature.properties?.name),
53
+ getValue: (feature) => districtValues[String(feature.properties?.id)] ?? null,
54
+ };
55
+ }}
56
+ />
57
+ ```
58
+
59
+ ## Features
60
+
61
+ - Keyboard-accessible regions with Enter/Space activation and focus inspection.
62
+ - Tooltip, legend, breadcrumb, formatting, and insight render slots.
63
+ - Controlled or uncontrolled selection and drill-down state.
64
+ - Optional neutral reference overlays kept outside statistical values.
65
+ - GeoJSON and TopoJSON inputs, with `d3-geo` projection sized to the SVG.
66
+
67
+ `IndiaChoroplethProps` and its related layer/context types are exported for
68
+ TypeScript consumers. See the [repository](https://github.com/shashankbudem/bharat-choropleth)
69
+ for the full API, examples, boundary-data attribution, and the framework-free
70
+ [`bharat-choropleth-js`](https://www.npmjs.com/package/bharat-choropleth-js)
71
+ package.
72
+
73
+ ## Local development
74
+
75
+ ```bash
76
+ pnpm build
77
+ pnpm typecheck
78
+ pnpm test
79
+ ```
80
+
81
+ ## Licence
82
+
83
+ MIT. This npm package contains renderer code only. Geographic data used with
84
+ it may have separate licence and attribution requirements.
@@ -0,0 +1,170 @@
1
+ import * as react from 'react';
2
+ import { ReactNode } from 'react';
3
+ import { Feature, Geometry, GeoJsonProperties, FeatureCollection } from 'geojson';
4
+ import { Topology, GeometryObject } from 'topojson-specification';
5
+
6
+ /** A GeoJSON feature as consumed by the renderer. Keep properties data-provider defined. */
7
+ type MapFeature = Feature<Geometry, GeoJsonProperties>;
8
+ type MapFeatureCollection = FeatureCollection<Geometry, GeoJsonProperties>;
9
+ /**
10
+ * A layer can be plain GeoJSON, or TopoJSON plus the named object to unpack.
11
+ * Boundary data intentionally remains outside this package.
12
+ */
13
+ type GeometrySource = MapFeatureCollection | {
14
+ topology: Topology;
15
+ object: string | GeometryObject;
16
+ };
17
+ type ColorScale = readonly string[] | ((value: number | null, context: ColorContext) => string);
18
+ interface ColorContext {
19
+ min: number;
20
+ max: number;
21
+ feature: MapFeature;
22
+ id: string;
23
+ }
24
+ interface MapLayer {
25
+ /** GeoJSON or TopoJSON data for precisely this map level. */
26
+ geometry: GeometrySource;
27
+ /** Stable ID. Use LGD codes (not display names) for India boundary data. */
28
+ getId: (feature: MapFeature) => string;
29
+ /** Human-readable text used by labels and the default tooltip. */
30
+ getLabel: (feature: MapFeature) => string;
31
+ /** Return null for intentionally missing data. */
32
+ getValue: (feature: MapFeature) => number | null;
33
+ /** Optional change/secondary metric for render slots; never rendered by default. */
34
+ getMeta?: (feature: MapFeature) => Record<string, unknown> | undefined;
35
+ }
36
+ /**
37
+ * Non-statistical geometry shown only on the national map, for example a
38
+ * reference outline or claimed area that must not inherit choropleth values.
39
+ * It has no value accessor by design, so it can never inherit statistical data.
40
+ */
41
+ interface ReferenceOverlay {
42
+ geometry: GeometrySource;
43
+ getId: (feature: MapFeature) => string;
44
+ getLabel: (feature: MapFeature) => string;
45
+ /**
46
+ * Required accessible context, such as “National reference outline; hatched
47
+ * portions outside the statistical layer have no data.” The renderer uses it
48
+ * verbatim and does not infer status for the entire geometry.
49
+ */
50
+ getDescription: (feature: MapFeature) => string;
51
+ }
52
+ interface MapRegion {
53
+ id: string;
54
+ label: string;
55
+ value: number | null;
56
+ meta?: Record<string, unknown>;
57
+ feature: MapFeature;
58
+ }
59
+ interface TooltipContext extends MapRegion {
60
+ level: "state" | "district";
61
+ total: number;
62
+ share: number | null;
63
+ /** 1-based position among regions that have a value, highest first. Null when this region has no value. */
64
+ rank: number | null;
65
+ /** How many regions at this level have a value — the denominator for `rank`. */
66
+ rankedCount: number;
67
+ }
68
+ interface InsightContext extends TooltipContext {
69
+ selected: boolean;
70
+ }
71
+ type DistrictLoader = (stateId: string, state: MapRegion) => Promise<MapLayer>;
72
+ /** Lazily provides non-statistical context geometry for a selected state's district map. */
73
+ type DistrictReferenceOverlayLoader = (stateId: string, state: MapRegion) => Promise<ReferenceOverlay | null>;
74
+ interface IndiaChoroplethProps {
75
+ /** State/UT layer. The library does not bundle any geographic boundaries. */
76
+ states: MapLayer;
77
+ /**
78
+ * Optional non-statistical national geometry, rendered as a neutral hatch.
79
+ * It never receives a choropleth value, click handler, selection, or drill-down.
80
+ */
81
+ referenceOverlay?: ReferenceOverlay;
82
+ /** Called only after a state is requested, so district geometry can be code-split. */
83
+ loadDistricts?: DistrictLoader;
84
+ /**
85
+ * Optional lazy non-statistical context geometry for a district view. It is
86
+ * keyed to the drilled state, cancelled safely on navigation, and rendered
87
+ * only with that state's districts.
88
+ */
89
+ loadDistrictReferenceOverlay?: DistrictReferenceOverlayLoader;
90
+ /** Controlled state drill-down. Use null for the state map. */
91
+ drillDownId?: string | null;
92
+ /** Initial state drill-down when uncontrolled. */
93
+ defaultDrillDownId?: string | null;
94
+ onDrillDownChange?: (stateId: string | null, state?: MapRegion) => void;
95
+ /** Controlled selected feature (state ID on national level; district ID when drilled in). */
96
+ selectedId?: string | null;
97
+ defaultSelectedId?: string | null;
98
+ /** Fires with null when a click on open sea clears the selection. */
99
+ onSelectedChange?: (region: MapRegion | null, level: "state" | "district") => void;
100
+ /** Fires for hover and keyboard focus with the same shape payload. */
101
+ onInspect?: (region: MapRegion | null, level: "state" | "district") => void;
102
+ /** Receives tooltip-ready data, including the current scope total and share. */
103
+ onInsight?: (context: InsightContext | null) => void;
104
+ /** Receives every activation before state drill-down / district selection. */
105
+ onRegionClick?: (region: MapRegion, level: "state" | "district") => void;
106
+ /**
107
+ * Called for a click that hit no region and was not close enough to a small
108
+ * one. The component also clears its own uncontrolled selection on such a
109
+ * click, so clicking the sea drops the selection ring.
110
+ */
111
+ onBackgroundClick?: () => void;
112
+ /** Colors are data-driven; strings work as an ordered low-to-high ramp. */
113
+ colorScale?: ColorScale;
114
+ /** Format both tooltip and legend values. */
115
+ formatValue?: (value: number) => string;
116
+ renderTooltip?: (context: TooltipContext) => ReactNode;
117
+ renderInsights?: (context: InsightContext | null) => ReactNode;
118
+ /**
119
+ * Map-level visual chrome can be independently disabled/composed.
120
+ *
121
+ * On an interactive map the legend is also a filter: each swatch highlights
122
+ * the regions painted in it and dulls the rest, picked again or Escape to
123
+ * clear. A swatch with nothing in it is inert. Set `interactive: false` for a
124
+ * legend that is a key and nothing more.
125
+ */
126
+ showLegend?: boolean;
127
+ showBreadcrumb?: boolean;
128
+ legendLabels?: readonly [lower: string, higher: string];
129
+ /** Label paired with the neutral hatch in either map-level legend. */
130
+ referenceOverlayLegendLabel?: string;
131
+ /** State IDs whose statistical fill should visually merge with the reference overlay. */
132
+ referenceOverlayMergeIds?: readonly string[];
133
+ /** Render the non-statistical reference geometry as a hatch or neutral solid fill. */
134
+ referenceOverlayFill?: "hatch" | "solid";
135
+ /** Show formatted values at region centroids. */
136
+ showRegionValues?: boolean;
137
+ /**
138
+ * Grow any part smaller than this (view-box units) about its own centre, so it
139
+ * can be seen and clicked. Off by default.
140
+ *
141
+ * An archipelago cannot be drawn to scale and still be usable: Lakshadweep's
142
+ * islands are one to two kilometres across and spread over 250, so even
143
+ * drilled into they are a few pixels each. This trades exact size for
144
+ * visibility, keeping every part in its true position; growth is capped so a
145
+ * speck never reads as a real landmass.
146
+ *
147
+ * Regions with nowhere to grow into are left at their true size regardless —
148
+ * Puducherry is enclaves inside Tamil Nadu, and growing them would put a
149
+ * Puducherry of the wrong shape in the wrong place. Those fall back to the
150
+ * marker dot, which is a pointer target in its own right.
151
+ */
152
+ minPartExtent?: number;
153
+ /**
154
+ * Like [minPartExtent], but applied only after a state has been drilled into.
155
+ * When omitted, district layers inherit minPartExtent for backward compatibility.
156
+ */
157
+ minDistrictPartExtent?: number;
158
+ className?: string;
159
+ ariaLabel?: string;
160
+ /** Set false where a host app provides its own keyboard focus management. */
161
+ interactive?: boolean;
162
+ }
163
+
164
+ /**
165
+ * A data-agnostic, accessible SVG India map renderer. Import `@india-choropleth/react/style.css`
166
+ * once in the host app; data and boundaries intentionally remain separate.
167
+ */
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;
169
+
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 };