// views/RegionalIndiaView.jsx - Visual 1: India Demand & Supply twin hex maps // Two panels side-by-side using a stylised hex-grid of states. // Panel A - demand side: state fill = supply reliability (poor/med/good); // circle marker = peak demand CAGR. // Panel B - supply side: state fill = RE generation in GWh (4 buckets, pale→dark green), // toggleable to RE as % of total generation; circle marker = BESS pipeline GWh. // Click any hex → state detail drawer slides in from the right. const { useState: useStateR, useMemo: useMemoR } = React; // ─── Hex geometry helpers ─── function hexPointsPointy(cx, cy, r) { const pts = []; for (let i = 0; i < 6; i++) { const a = (Math.PI / 180) * (60 * i - 30); pts.push([cx + r * Math.cos(a), cy + r * Math.sin(a)]); } return pts.map(p => p.join(',')).join(' '); } function hexCenter(col, row, r) { const w = Math.sqrt(3) * r; const h = 1.5 * r; const x = col * w + (row % 2 ? w / 2 : 0); const y = row * h; return { x, y }; } // ─── Encoding scales ─── const RELIABILITY_FILL = { poor: '#bf4f59', med: '#e6a23c', good: '#5a8c66', }; const RELIABILITY_LABEL = { poor: 'Poor (<18 h/day)', med: 'Medium (18-22 h/day)', good: 'Good (>22 h/day)', }; // 4 RE generation buckets (GWh) const RE_GWH_BUCKETS = [ { max: 1000, fill: '#dbe9d6', label: '< 1 GWh-thou (≤ 1,000 GWh)' }, { max: 10000, fill: '#a6cca2', label: '1,000-10,000 GWh' }, { max: 30000, fill: '#5a9c69', label: '10,000-30,000 GWh' }, { max: Infinity, fill: '#0e5e3a', label: '> 30,000 GWh' }, ]; // 4 RE % buckets const RE_PCT_BUCKETS = [ { max: 10, fill: '#dbe9d6', label: '< 10%' }, { max: 25, fill: '#a6cca2', label: '10-25%' }, { max: 50, fill: '#5a9c69', label: '25-50%' }, { max: Infinity, fill: '#0e5e3a', label: '> 50%' }, ]; function bucketFor(value, scale) { for (const b of scale) if (value <= b.max) return b; return scale[scale.length - 1]; } // ─── State areas (sq km) and size scaling ─── // Used to scale individual hex radii so big states (RJ, MP, MH) read large // and small states/UTs (DL, GA, SK) read small. const AREAS = { RJ:342239, MP:308245, MH:307713, UP:240928, GJ:196024, KA:191791, AP:162968, OD:155707, CG:135191, TN:130058, TG:112077, BR:94163, WB:88752, AR:83743, JH:79714, AS:78438, LA:59146, HP:55673, UK:53483, PB:50362, HR:44212, JK:42241, KL:38852, ML:22429, MN:22327, MZ:21081, NL:16579, TR:10491, SK:7096, GA:3702, DL:1484, }; const MAX_AREA = 342239; // Rajasthan function rForState(code, baseR) { const a = AREAS[code]; if (!a) return baseR * 0.7; // sqrt-scaled, clamped 0.42..0.95 of base radius (avoids overlap) const f = 0.42 + 0.55 * Math.sqrt(a / MAX_AREA); return baseR * Math.min(0.95, Math.max(0.42, f)); } // ─── State centroids (lat/lon → outer SVG coords) ─── // Approximate state centroids in decimal degrees, projected via equirectangular // into the same outer-SVG coord system used to render the India outline overlay. // Calibration: the rendered outline occupies x∈[47, 432], y∈[-34, 373] in outer // SVG coords, mapping to lon∈[68.1°, 97.4°E] and lat∈[37.1°, 6.7°N]. const LATLON = { JK:[33.8,76.0], LA:[34.5,77.7], HP:[31.8,77.2], UK:[30.0,79.0], PB:[31.0,75.5], HR:[29.0,76.1], DL:[28.7,77.1], RJ:[27.0,74.0], UP:[26.9,80.9], GJ:[22.7,71.6], MP:[23.6,78.7], BR:[25.7,85.3], JH:[23.6,85.3], WB:[22.6,87.9], OD:[20.2,85.4], CG:[21.3,81.9], MH:[19.7,75.7], TG:[17.9,79.3], AP:[15.9,79.7], KA:[14.9,76.0], GA:[15.3,74.1], KL:[10.3,76.4], TN:[11.1,78.7], SK:[27.6,88.5], AR:[28.2,94.7], NL:[26.2,94.5], AS:[26.2,92.9], ML:[25.5,91.4], MN:[24.7,93.9], TR:[23.8,91.7], MZ:[23.4,92.9], }; const LON_PER_X = (432 - 47) / (97.4 - 68.1); // ≈ 13.14 units/° const LAT_PER_Y = (373 - (-34)) / (37.1 - 6.7); // ≈ 13.39 units/° function geoCenter(code) { const ll = LATLON[code]; if (!ll) return { x: 0, y: 0 }; const [lat, lon] = ll; return { x: 47 + (lon - 68.1) * LON_PER_X, y: -34 + (37.1 - lat) * LAT_PER_Y, }; } // Circle marker radius - log-ish scaling to keep tiny states visible without // the big ones blowing out the map. function circleR_CAGR(cagr) { if (!cagr || cagr <= 0) return 0; // 3.5% → 6px, 6% → 11px, 8.5% → 16px (cap 18) return Math.min(18, 4 + (cagr - 3) * 2.6); } function circleR_BESS(gwh) { if (!gwh || gwh <= 0) return 0; // 0.3 → 4, 1 → 6, 3 → 10, 8 → 16, cap 20 return Math.min(20, 3 + Math.sqrt(gwh) * 5.5); } // ─── One hex map panel ─── function HexPanel({ panel, states, grid, mode, hovered, onHover, onPick }) { const r = grid.hexR; // Compute bounding box across all states const centers = states.map(s => geoCenter(s.code)); const pad = r + 16; const minX = Math.min(...centers.map(c => c.x)) - pad; const maxX = Math.max(...centers.map(c => c.x)) + pad; const minY = Math.min(...centers.map(c => c.y)) - pad; const maxY = Math.max(...centers.map(c => c.y)) + pad; const vbW = maxX - minX; const vbH = maxY - minY; function fillFor(s) { if (panel === 'A') { return RELIABILITY_FILL[s.demand.reliability] || '#888'; } // panel B if (mode === 'pct') return bucketFor(s.supply.rePercent, RE_PCT_BUCKETS).fill; return bucketFor(s.supply.reGenerationGWh, RE_GWH_BUCKETS).fill; } function circleR(s) { if (panel === 'A') return circleR_CAGR(s.demand.peakDemandCAGR); return circleR_BESS(s.supply.bessPipelineGWh); } function circleTitle(s) { if (panel === 'A') return `Peak demand CAGR ${s.demand.peakDemandCAGR}%`; return `BESS pipeline ${s.supply.bessPipelineGWh} GWh`; } return ( {/* Subtle cartographic ground */} {/* State shapes (real geographic polygons) */} {states.map(s => { const shape = (window.INDIA_STATE_SHAPES || {})[s.code]; if (!shape) return null; const [cx, cy] = shape.c; const sr = rForState(s.code, r); const isHover = hovered === s.code; return ( onHover(s.code)} onMouseLeave={() => onHover(null)} onClick={() => onPick(s)} style={{ cursor: 'pointer' }}> {shape.polys.map((pts, i) => ( ))} ); })} {/* Circle markers */} {states.map(s => { const cr = circleR(s); if (cr <= 0) return null; const { x, y } = geoCenter(s.code); return ( {circleTitle(s)} ); })} {/* Hydro symbol - Panel B only, shown when hydro > 10% of total RE generation */} {panel === 'B' && ( {states.map(s => { const hydro = s.supply.reByFuel.hydro; const total = s.supply.reGenerationGWh || 1; if (hydro / total <= 0.10) return null; const shape = (window.INDIA_STATE_SHAPES || {})[s.code]; if (!shape) return null; const [cx, cy] = shape.c; return ( Hydro in renewables mix ({Math.round(hydro / total * 100)}% · {hydro} GWh) ); })} )} ); } // ─── Detail drawer ─── function StateDrawer({ state, panel, mode, national, onClose }) { if (!state) return null; const d = state.demand, s = state.supply; const fuels = s.reByFuel; const total = (fuels.solar + fuels.wind + fuels.hydro + fuels.other) || 1; const fuelRows = [ { k:'Solar', v:fuels.solar, c:'#fdce07' }, { k:'Wind', v:fuels.wind, c:'#8bcdd2' }, { k:'Hydro', v:fuels.hydro, c:'#2a6a86' }, { k:'Other', v:fuels.other, c:'#aa6227' }, ]; return (
); } // ─── Main view ─── function RegionalIndiaView() { const data = window.INDIA_STATES; const states = data.states; const grid = data.grid; const [hovered, setHovered] = useStateR(null); const [selected, setSelected] = useStateR(null); const [selectedPanel, setSelectedPanel] = useStateR('A'); const [bMode, setBMode] = useStateR('gwh'); // 'gwh' | 'pct' const reScale = bMode === 'pct' ? RE_PCT_BUCKETS : RE_GWH_BUCKETS; return (
setSelected(null)}>
Illustrative State numbers shaped to the strategic narrative - to be replaced with primary-source data (CEA · Ember · MoP · energymap.in) before publication.
e.stopPropagation()}>
{/* Panel A */}
Panel A · Demand

Northern India faces a challenge: some of the country's highest demand growth and lower grid performance and supply reliability.

Fill = hours of supply per day. Marker = peak demand CAGR.

{ e.stopPropagation(); }}> { setSelected(s); setSelectedPanel('A'); }} />
Fill - supply reliability
{Object.entries(RELIABILITY_LABEL).map(([k, l]) => ( {l} ))}
Source: MoP 24×7 Power for All (self-reported)
Marker - peak demand CAGR
4% 6% 8.5%
Source: CEA National Electricity Plan
{/* Panel B */}
Panel B · Supply

North-East India is trailing on renewables, and the BESS pipeline is following the same pattern, concentrating in the states that already lead on clean energy.

Fill = {bMode==='pct' ? 'RE as % of total generation' : 'RE generation in GWh'}. Marker = BESS pipeline (GWh).

{ e.stopPropagation(); }}> { setSelected(s); setSelectedPanel('B'); }} />
Fill - {bMode==='pct' ? 'RE as % of total generation' : 'RE generation (GWh)'}
{reScale.map((b, i) => ( {b.label} ))}
Source: Ember India Electricity Data Explorer
Symbol - hydro in RE mix
Hydro > 10% of RE generation
Marker - BESS pipeline (GWh)
0.5 2 6
Sources: energymap.in/storage · SECI
National BESS target · {data.national.bessTarget2030GWh} GWh by 2030 (CEA)
{selected && ( setSelected(null)} /> )}
The market-making story. RE generation is concentrated in the west and south (Rajasthan, Gujarat, Karnataka, Tamil Nadu). Demand growth and poor reliability are concentrated in the east (Bihar, Jharkhand, Odisha). The gap between Panel A and Panel B is the investable opportunity.
); } window.RegionalIndiaView = RegionalIndiaView;