// Alternate atlas views — Bar chart (descending) + Treemap (population-weighted). // Each component receives the same props as the choropleth so the control bar // can swap them without dragging different prop shapes. { const { LENSES, STATES, STATE_POPULATION, scoreFor, lensMetaFor } = window.PL_DATA; const NAME_FOR = Object.fromEntries(STATES.map(s => [s[0], s[1]])); // ============================================================================ // BAR CHART VIEW — sorted descending, all scored states // ============================================================================ function BarChartView({ lensId, mode = "passed", onSelect, activeState }) { // Only states with actual data for this lens const rows = STATES .map(s => s[0]) .filter(code => { const meta = lensMetaFor && lensMetaFor(code, lensId); return meta && meta.bill_count > 0; }) .map(code => { const meta = lensMetaFor(code, lensId); return { code, name: NAME_FOR[code] || code, score: scoreFor(code, lensId, mode), bills: meta?.bill_count || 0, passed: meta?.n_passed || 0, }; }) .sort((a, b) => b.score - a.score); if (rows.length === 0) { return ; } return (
Sorted descending — {rows.length} states with data
{rows.map((r, i) => { const color = window.PL_COLOR(r.score).color; const isActive = activeState === r.code; return ( ); })}
); } // ============================================================================ // TREEMAP VIEW — population-weighted (large states get larger areas) // ============================================================================ function TreemapView({ lensId, mode = "passed", onSelect, activeState }) { const wrapRef = React.useRef(null); const [size, setSize] = React.useState({ w: 1000, h: 600 }); React.useLayoutEffect(() => { if (!wrapRef.current) return; const update = () => { const r = wrapRef.current.getBoundingClientRect(); setSize({ w: Math.max(400, r.width), h: Math.max(360, r.height) }); }; update(); const ro = new ResizeObserver(update); ro.observe(wrapRef.current); return () => ro.disconnect(); }, []); // Build treemap leaves — every scored state, area = population, color = score const leaves = STATES .map(s => s[0]) .filter(code => STATE_POPULATION[code]) .filter(code => { const meta = lensMetaFor && lensMetaFor(code, lensId); return meta && meta.bill_count > 0; }) .map(code => ({ code, name: NAME_FOR[code] || code, pop: STATE_POPULATION[code], score: scoreFor(code, lensId, mode), bills: lensMetaFor(code, lensId)?.bill_count || 0, })); if (leaves.length === 0) { return ; } // d3.treemap layout const root = window.d3.hierarchy({ children: leaves }).sum(d => d.pop); const treemap = window.d3.treemap() .size([size.w, size.h]) .paddingInner(2) .round(true); treemap(root); return (
Sized by population · {leaves.length} states
{root.leaves().map(node => { const w = node.x1 - node.x0; const h = node.y1 - node.y0; const showLabel = w > 60 && h > 36; const color = window.PL_COLOR(node.data.score).color; const isActive = activeState === node.data.code; return ( onSelect && onSelect(node.data.code)}> {showLabel && ( <> {node.data.code} {node.data.score.toFixed(0)} · {node.data.pop.toFixed(1)}M )} {node.data.name} · score {node.data.score.toFixed(1)} · pop {node.data.pop}M · {node.data.bills} bills ); })}
); } // ============================================================================ // Shared "no data" view // ============================================================================ function NoDataView({ text }) { return (
{text}
); } window.PL_BarChartView = BarChartView; window.PL_TreemapView = TreemapView; }