diff --git a/src/charts/ArrestsOverTime.jsx b/src/charts/ArrestsOverTime.jsx
index da2d8e3..3c9b90b 100644
--- a/src/charts/ArrestsOverTime.jsx
+++ b/src/charts/ArrestsOverTime.jsx
@@ -1,23 +1,26 @@
import ChartLegend from '../components/ChartLegend.jsx'
import { OBSERVED_MARK_COLOR, MODELED_AGGREGATE_COLOR } from '../utils/colors.js'
import { niceTicks } from '../utils/niceTicks.js'
+import { MODEL_QUADRANT_LABEL } from '../utils/labels.js'
/**
- * Chart 1: Observed arrests by CRDC wave (line), overlaid with the
- * three-year model's predicted total (point-range) per wave — the
- * observed-vs-modeled comparison this app is built around.
+ * Chart 1: Observed arrests by CRDC wave (line, diamond markers — the same
+ * "diamond = observed" convention as every other chart), overlaid with one
+ * model's predicted total (point-range) per wave, aligned to the same x
+ * position as its observed point rather than dodged aside, so the two are
+ * read as a pair.
*/
const WAVE_LABELS = { '15-16': '2015–16', '17-18': '2017–18', '21-22': '2021–22' }
-const DODGE = 18 // px offset so the modeled point-range doesn't sit on top of the observed point
-const LABEL_CLEARANCE = 32 // px reserved above the taller of (observed, modeled upper) for its rate label
+const HEADROOM = 26 // px reserved at the top of the plot so a point's rate label has room to sit above it
-export default function ArrestsOverTime({ data }) {
+export default function ArrestsOverTime({ data, modelId }) {
const maxArrests = Math.max(
...data.map((d) => Math.max(d.arrests, d.modeledUpper ?? 0)),
1
)
const { ticks, niceMax } = niceTicks(maxArrests)
+ const modelLabel = MODEL_QUADRANT_LABEL[modelId] || modelId
const width = 360
const height = 280
@@ -26,17 +29,20 @@ export default function ArrestsOverTime({ data }) {
const innerHeight = height - margin.top - margin.bottom
const xScale = (i) => (i / Math.max(data.length - 1, 1)) * innerWidth
- // Reserves LABEL_CLEARANCE px at the top of the plot so a point's own rate
- // label never has to compete for space with the modeled whisker above it.
- const yScale = (val) => LABEL_CLEARANCE + (innerHeight - LABEL_CLEARANCE) * (1 - val / niceMax)
+ const yScale = (val) => HEADROOM + (innerHeight - HEADROOM) * (1 - val / niceMax)
return (
-
+
Arrests over time — observed vs. modeled
+ {modelLabel && (
+
+ Modeled intervals use the {modelLabel.toLowerCase()} model.
+