Reduce demo to 3 charts, fix back-button district name bug
Deploy to git-pages / deploy (push) Successful in 26s
Deploy to git-pages / deploy (push) Successful in 26s
Fixes the deep-link/back-button bug where the district name showed "Unknown District" on return: App.jsx was passing the LEAID as a text search query to searchDistricts() (a name search), which never matches a numeric ID. Resolves it instead via fetchDistrictEstimates(leaid, ...) and reads lea_name/state directly from the returned row - correct regardless of how the page was reached (fresh load, refresh, or browser back/forward). Cuts the demo from 6 charts to 3, per request: arrests over time (kept), arrest rate by student group restructured into Female/Male box-and-whisker panels (kept), and the posterior density ridge chart restructured from a 2x2 model-quadrant grid into a single selected model (dropdown, default three-year + referral rate) with Female/Male ridge columns. Removes DistrictVsNational, ModelDrawsComparison, and ExceedanceProbability entirely, along with the student-group filter (no longer needed - the remaining charts always show the full breakdown) and the national-rates fetch/plumbing that only those removed charts used. Also updates LoadingAnimation's copy and dedupes its STUDENT_GROUPS/ MODEL_QUADRANTS constants against the shared ones in useApi.js. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,26 +1,8 @@
|
||||
import { useState, useEffect } from 'react'
|
||||
import ChartPanel from './ChartPanel.jsx'
|
||||
import * as api from '../hooks/useApi.js'
|
||||
|
||||
// The 8 student groups we fetch for (race×sex combinations)
|
||||
const STUDENT_GROUPS = [
|
||||
{ race: 'WH', sex: 'F', label: 'White F' },
|
||||
{ race: 'WH', sex: 'M', label: 'White M' },
|
||||
{ race: 'BL', sex: 'F', label: 'Black F' },
|
||||
{ race: 'BL', sex: 'M', label: 'Black M' },
|
||||
{ race: 'HI', sex: 'F', label: 'Hispanic F' },
|
||||
{ race: 'HI', sex: 'M', label: 'Hispanic M' },
|
||||
{ race: 'AM', sex: 'F', label: 'AI/AN F' },
|
||||
{ race: 'AM', sex: 'M', label: 'AI/AN M' },
|
||||
]
|
||||
|
||||
// The "four quadrants" of models for distribution charts (Charts 4–6)
|
||||
const MODEL_QUADRANTS = [
|
||||
{ model: 'unified_m1_mod', label: 'One-year, baseline', col: 0, row: 0 },
|
||||
{ model: 'unified_m2_mod', label: 'One-year + covariate', col: 0, row: 1 },
|
||||
{ model: 'unified_m3_mod', label: 'Three-year, baseline', col: 1, row: 0 },
|
||||
{ model: 'unified_m4_mod', label: 'Three-year + covariate', col: 1, row: 1 },
|
||||
]
|
||||
import { STUDENT_GROUPS, MODEL_QUADRANTS } from '../hooks/useApi.js'
|
||||
import { shortGroupLabel } from '../utils/colors.js'
|
||||
|
||||
// CRDC waves to fetch (for Chart 1 — time series)
|
||||
const WAVES = ['21-22', '17-18', '15-16']
|
||||
@@ -35,11 +17,9 @@ export default function LoadingAnimation({ district, state }) {
|
||||
const [totalCalls, setTotalCalls] = useState(0)
|
||||
const [error, setError] = useState(null)
|
||||
|
||||
// Build the full list of API calls needed for all 6 charts
|
||||
// Build the full list of API calls needed for all 3 charts
|
||||
useEffect(() => {
|
||||
let cancelled = false
|
||||
let count = 0
|
||||
let total = 0
|
||||
|
||||
async function loadData() {
|
||||
try {
|
||||
@@ -50,14 +30,14 @@ export default function LoadingAnimation({ district, state }) {
|
||||
.catch(() => { /* individual failure doesn't block */ })
|
||||
)
|
||||
|
||||
// ——— Charts 2 & 3: Rate by group (8 groups × default model, most recent wave) ———
|
||||
// ——— Chart 2: Rate by group (8 groups × default model, most recent wave) ———
|
||||
const groupPromises = STUDENT_GROUPS.map((sg) =>
|
||||
fetchDistrictEstimatesBatch(district.leaid, '21-22', sg.race, sg.sex)
|
||||
.then(() => !cancelled && setLoadedCount(c => c + 1))
|
||||
.catch(() => {})
|
||||
)
|
||||
|
||||
// ——— Charts 4–6: Model comparison (8 groups × 4 quadrant models = 32 calls) ———
|
||||
// ——— Chart 3: posterior density, all 4 quadrant models so the dropdown can switch (8 groups × 4 = 32 calls) ———
|
||||
const modelPromises = MODEL_QUADRANTS.flatMap((quad) =>
|
||||
STUDENT_GROUPS.map((sg) =>
|
||||
fetchDistrictEstimatesBatch(district.leaid, '21-22', sg.race, sg.sex, quad.model)
|
||||
@@ -66,22 +46,16 @@ export default function LoadingAnimation({ district, state }) {
|
||||
)
|
||||
)
|
||||
|
||||
// ——— National rates (from static fixture, instant) ———
|
||||
const nationalPromise = fetch(import.meta.env.BASE_URL + 'data/national_rates.json')
|
||||
.then(r => r.json())
|
||||
.then(data => { /* cache for ChartPanel */ window.__NATIONAL_RATES__ = data })
|
||||
.catch(() => {})
|
||||
|
||||
// Set total before starting (for progress bar)
|
||||
const allPromises = [...wavePromises, ...groupPromises, ...modelPromises]
|
||||
if (!cancelled) setTotalCalls(allPromises.length + 1 /* national */)
|
||||
if (!cancelled) setTotalCalls(allPromises.length)
|
||||
|
||||
await Promise.all([...allPromises, nationalPromise])
|
||||
await Promise.all(allPromises)
|
||||
|
||||
if (!cancelled) {
|
||||
// All data loaded — ChartPanel renders in place of this component
|
||||
// We use a render prop pattern: return <ChartPanel /> when done
|
||||
setLoadedCount(allPromises.length + 1)
|
||||
setLoadedCount(allPromises.length)
|
||||
}
|
||||
} catch (err) {
|
||||
if (!cancelled) setError(err.message || 'Failed to load data')
|
||||
@@ -110,10 +84,14 @@ export default function LoadingAnimation({ district, state }) {
|
||||
// Build bar metadata for rendering
|
||||
const allBars = Array.from({ length: TOTAL_BARS }, (_, i) => {
|
||||
if (i < WAVES.length) return { subLabel: 'Time series', group: 'waves' }
|
||||
if (i < WAVES.length + STUDENT_GROUPS.length) return { subLabel: STUDENT_GROUPS[i - WAVES.length].label, group: 'groups' }
|
||||
if (i < WAVES.length + STUDENT_GROUPS.length) {
|
||||
const sg = STUDENT_GROUPS[i - WAVES.length]
|
||||
return { subLabel: shortGroupLabel(sg.race, sg.sex), group: 'groups' }
|
||||
}
|
||||
const quadIdx = Math.floor((i - WAVES.length - STUDENT_GROUPS.length) / STUDENT_GROUPS.length)
|
||||
const sgIdx = (i - WAVES.length - STUDENT_GROUPS.length) % STUDENT_GROUPS.length
|
||||
return { subLabel: STUDENT_GROUPS[sgIdx].label, group: 'models', modelIdx: quadIdx }
|
||||
const sg = STUDENT_GROUPS[sgIdx]
|
||||
return { subLabel: shortGroupLabel(sg.race, sg.sex), group: 'models', modelIdx: quadIdx }
|
||||
})
|
||||
|
||||
return (
|
||||
@@ -142,7 +120,7 @@ export default function LoadingAnimation({ district, state }) {
|
||||
marginTop: 'var(--space-4)'
|
||||
}}>
|
||||
{/* Column headers */}
|
||||
{['Time series (Chart 1)', 'Observed rates (Charts 2–3)', 'Model comparisons (Charts 4–6)'].map((h, i) => (
|
||||
{['Time series (Chart 1)', 'By student group (Chart 2)', 'Posterior density (Chart 3)'].map((h, i) => (
|
||||
<div key={i} style={{ textAlign: 'center', paddingBottom: 'var(--space-1)' }}>
|
||||
<span className="stat-label" style={{ display: 'block' }}>{h}</span>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user