// app.jsx - main shell, view router, filter pipeline const { useState, useMemo, useEffect } = React; // Microsoft Forms feedback survey - linked from the bar under every page. const FEEDBACK_URL = 'https://forms.cloud.microsoft/Pages/ResponsePage.aspx?id=Kh3WCJKMCEi47oT2rrORzsQUrBTAcQVFoWuIaSF_saNUQ1pCMkxHU0FGVldSM1ZKRUlHUEo4SE1QOC4u'; function LoginScreen({ onAuth }) { const [username, setUsername] = useState(''); const [password, setPassword] = useState(''); const [error, setError] = useState(''); function handleSubmit(e) { e.preventDefault(); if (username === 'gea' && password === 'system_map') { sessionStorage.setItem('gea_authed', '1'); onAuth(); } else { setError('Invalid username or password.'); setPassword(''); } } return (
Global Energy Alliance
Global Initiatives Atlas
Powered by the Global Energy Alliance and Systemiq
{error &&
{error}
}
); } // Tabs hidden from the UI (keep entries below if you want to re-enable): // { id: 'directory', num: '04', name: 'Directory', subtitle: 'All organisations as cards. Sort, search, drill in.' }, // { id: 'india-demand', num: '05', name: 'Regional System Map · India - Demand & Supply', subtitle: 'Two hex-map panels side-by-side: where demand is growing and reliability is failing (Panel A) vs where RE generation and the BESS pipeline sit (Panel B). The gap is the investable opportunity.' }, const VIEWS = [ { id: 'value-chain', num: '01', name: 'Energy Ecosystem Infographic', subtitle: "Schematic of the energy ecosystem, with from top to bottom highlighted the \"influencers\", the different barriers these \"influencers\" address and the energy ecosystem they are impacting. Each node is colour-coded by Global Energy Alliance's pillars (Energy access = yellow, Grid modernization = green dashed). Click a node for a description, or use the legend cards to highlight pillar-specific segments." }, { id: 'matrix', num: '02', name: 'Global Initiatives Heat Map', subtitle: 'A table view: which organizations work on each value-chain segment, by region — or by barrier (toggle the y-axis at the top). Sovereign governments, universities, global consultancies and contractors and utilities and operators excluded from this overview. Spot where coverage is thin and where overlap may exist at the press of a button and use the search bar to find relevant organizations. ' }, { id: 'network', num: '03', name: 'Global Collaboration Network', subtitle: 'Who works with whom. Reveals well-networked actors, isolated initiatives, and partnership gaps across the system. Use the search bar to find relevant organizations. Hover a node to highlight its partners. Click for full details. Edges are symmetric — each pair appears once. Nodes represent officially published MoU\'s, partnerships or joint programmes.' }, { id: 'india-value-chain', num: '04', name: 'India · Energy Ecosystem Infographic', subtitle: 'India-focused view of the energy ecosystem — same schematic structure as the global maps, scoped to the India context. Click value chain segments to reveal information about that value chain segment in India.' }, { id: 'india-players', num: '05', name: 'India · Industry players and initiatives', subtitle: 'Same energy ecosystem columns as the global maps. Top band: industry players. Bottom band: initiatives & programmes. Anchor organizations shown in bold.' }, { id: 'india-network', num: '06', name: 'India · Regional Collaboration Network', subtitle: 'Collaboration network of the global initiatives whose mandate covers India. Hover a node to highlight its partners. Click for full details. Edges are symmetric — each pair appears once. Nodes represent officially published MoU\'s, partnerships or joint programmes.' }, { id: 'methodology', num: '07', name: 'Methodology', subtitle: 'How organizations are categorised across this atlas, what the data does and does not cover, and the biases to keep in mind when reading every view. The ? bubbles throughout the tool link here.' }, ]; // Returns a relevance score (0 = no match). Higher = more relevant. function searchScore(org, q) { if (!q) return 0; const needle = q.toLowerCase().trim(); if (!needle) return 0; const name = (org.name || '').toLowerCase(); const desc = (org.description || '').toLowerCase(); const detail = (org.detail || '').toLowerCase(); const topics = (org.topics || []).map(s => String(s).toLowerCase()); const regions = (org.regions || []).map(s => String(s).toLowerCase()); const collabs = (org.collaborators || []).map(s => String(s).toLowerCase()); let score = 0; // Name signals - by far the strongest if (name === needle) score += 1000; else if (name.startsWith(needle)) score += 600; else { // Word-boundary match in name const re = new RegExp('\\b' + needle.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'i'); if (re.test(org.name || '')) score += 400; else if (name.includes(needle)) score += 200; } // Shorter names rank higher among name matches (more "exact-feeling") if (score > 0) score += Math.max(0, 40 - name.length); // Topic / region exact or contains if (topics.some(t => t === needle)) score += 120; else if (topics.some(t => t.includes(needle))) score += 50; if (regions.some(r => r === needle)) score += 100; else if (regions.some(r => r.includes(needle))) score += 40; // Known collaborators if (collabs.some(c => c.includes(needle))) score += 30; // Description / detail - weakest. Word-boundary stronger than substring. const wordRe = new RegExp('\\b' + needle.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'i'); if (wordRe.test(org.description || '')) score += 25; else if (desc.includes(needle)) score += 8; if (wordRe.test(org.detail || '')) score += 12; else if (detail.includes(needle)) score += 4; return score; } function searchMatches(org, q) { return searchScore(org, q) > 0; } function passes(org, filters) { const checkArr = (filterKey, orgKey) => { const f = filters[filterKey]; if (!f || !f.length) return true; return f.some((v) => org[orgKey].includes(v)); }; if (!checkArr('regions', 'regions')) return false; if (!checkArr('barriers', 'barriers')) return false; if (filters.types?.length && !filters.types.includes(org.type)) return false; if (filters.pillar?.length) { // 'both' orgs satisfy either 'grids' or 'powering' selection. // Selecting 'both' explicitly matches only orgs flagged as 'both'. const sel = filters.pillar; const op = org.pillar; const ok = sel.includes(op) || (op === 'both' && (sel.includes('grids') || sel.includes('powering'))); if (!ok) return false; } return true; } function App({ defaultView = 'matrix', autostartTour = false, onHome }) { const [activeView, setActiveView] = useState(defaultView); // India Network draws on the India dataset's own collaboration edges (from the // "Collaboration India" sheet, baked into INDIA_DATA.edges). We show only the // organisations that actually participate in a partnership (degree ≥ 1) so the // graph is the India collaboration network, not a cloud of isolated nodes. const dataset = useMemo(() => { if (activeView === 'india-players' || activeView === 'india-demand') return window.INDIA_DATA; if (activeView === 'india-network') { const base = window.INDIA_DATA; const edges = base.edges || []; const connected = new Set(); for (const e of edges) { connected.add(e.a); connected.add(e.b); } const initiatives = base.initiatives.filter(o => connected.has(o.id)); return { ...base, initiatives, edges }; } return window.INITIATIVES_DATA; }, [activeView]); const { initiatives, edges, facets } = dataset; const [selectedOrg, setSelectedOrg] = useState(null); const [filters, setFilters] = useState({ search: '', regions: [], topics: [], stages: [], barriers: [], types: [], funding: [], pillar: [], }); const [highlightOrgId, setHighlightOrgId] = useState(null); const [tourActive, setTourActive] = useState(autostartTour); // Methodology deep-linking - the ? bubbles across the tool call this to jump // to a specific methodology section. The bumped nonce forces a re-scroll even // when the same section is requested twice in a row. const [methodologyTarget, setMethodologyTarget] = useState(null); // { section, nonce } const openMethodology = (section) => { setMethodologyTarget(prev => ({ section: section || null, nonce: (prev?.nonce || 0) + 1 })); setActiveView('methodology'); }; // Clear highlight when search cleared or view changes useEffect(() => { setHighlightOrgId(null); }, [activeView]); useEffect(() => { if (!filters.search) setHighlightOrgId(null); }, [filters.search]); // Toggle body class so layout can make room for the panel useEffect(() => { const showPanel = !!filters.search && activeView !== 'directory'; document.body.classList.toggle('has-search-panel', showPanel); return () => document.body.classList.remove('has-search-panel'); }, [filters.search, activeView]); const filtered = useMemo(() => { let base = initiatives.filter((o) => passes(o, filters)); // In Directory view, search filters the cards directly (no side panel) // and orders them by relevance, overriding the directory's sort buttons if (activeView === 'directory' && filters.search) { const scored = base .map(o => ({ o, s: searchScore(o, filters.search) })) .filter(x => x.s > 0) .sort((a, b) => b.s - a.s || a.o.name.localeCompare(b.o.name)); base = scored.map(x => x.o); } return base; }, [initiatives, filters, activeView]); const searchResults = useMemo(() => { const q = (filters.search || '').trim(); if (!q) return []; const scored = initiatives .map(o => ({ o, s: searchScore(o, q) })) .filter(x => x.s > 0) .sort((a, b) => b.s - a.s || a.o.name.localeCompare(b.o.name)); return scored.slice(0, 80).map(x => x.o); }, [initiatives, filters.search]); const view = VIEWS.find((v) => v.id === activeView); const ViewComp = { matrix: window.MatrixView, network: window.NetworkView, directory: window.DirectoryView, 'value-chain': window.ValueChainView, 'india-value-chain': window.ValueChainIndiaView, 'india-network': window.IndiaNetworkView, 'india-demand': window.RegionalIndiaView, 'india-players': window.IndiaPlayersView, methodology: window.MethodologyView, }[activeView]; return (
Global Energy Alliance

{view.name}

{view.subtitle}

{activeView !== 'value-chain' && activeView !== 'india-value-chain' && (
setFilters({ ...filters, search: e.target.value })} /> {filters.search && ( )}
)}
{activeView === 'india-demand' ? ( ) : activeView === 'methodology' ? ( ) : filtered.length === 0 ? (

No initiatives match

) : ViewComp ? ( ) : null}
Spotted something off, or have a suggestion? Share feedback
{filters.search && activeView !== 'directory' && ( setHighlightOrgId(o.id)} onOpen={(o) => setSelectedOrg(o)} onClose={() => { setFilters({ ...filters, search: '' }); setHighlightOrgId(null); }} facets={facets} /> )} setSelectedOrg(null)} onPickOrg={setSelectedOrg} /> {tourActive && ( setTourActive(false)} /> )}
); } function Root() { const [authed, setAuthed] = useState(() => sessionStorage.getItem('gea_authed') === '1'); const [activeView, setActiveView] = useState(null); // null = landing page const [autostartTour, setAutostartTour] = useState(false); if (!authed) return setAuthed(true)} />; if (!activeView) return ( { setAutostartTour(true); setActiveView('value-chain'); }} /> ); return ( { setAutostartTour(false); setActiveView(null); }} /> ); } ReactDOM.createRoot(document.getElementById('root')).render();