Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
6.1 KiB
Cross-subject metrics plot — interaction enhancements
Date: 2026-06-01
Component: frontend/src/components/AnalysisCharts.jsx → ExperimentAnalysisCharts
Rendered on: frontend/src/pages/ExperimentDetail.jsx
Summary
Add four client-side interaction features to the "Cross-subject metrics" plot (two
Recharts LineCharts — count-per-session and success-rate — where each line is one
subject, colored by group):
- A toggleable sidebar listing subjects grouped by their group, with per-subject and per-group checkboxes to show/hide each subject's line.
- Clicking a subject's legend entry toggles that subject's line visibility.
- The hover tooltip orders its rows to match the vertical stacking of the points (highest value at top).
- Hovering a subject's legend entry (or sidebar row) highlights that subject's line.
Constraints
- Frontend only. No backend, API, or database changes. All state is client-side React state, not persisted.
- No regressions. The existing global controls (X axis, Color by, tick step), the
per-chart customize bars (size, Y range, X zoom, per-chart X-field override), the
Y-metric selector, and the sibling
SubjectAnalysisChartscomponent are untouched. - Scope is limited to
ExperimentAnalysisCharts.
Visibility model (decided)
Visibility is global per-subject: toggling a subject anywhere — sidebar checkbox, either chart's legend — hides or shows it in both charts simultaneously. "Per plot" in the original request resolves to per-subject, not per-chart.
Shared state (lifted into ExperimentAnalysisCharts)
hiddenSubjects: Set<animalId>— empty = all visible. Global across both charts. Stale ids (subject removed) are harmless: they match no line.highlightedSubject: animalId | null— transient hover state, shared by both charts and the sidebar.sidebarOpen: boolean— defaultfalse.
Helpers: toggleHidden(id), setGroupHidden(groupSubjectIds, hidden), showAll(),
setHighlight(id | null).
Layout
A toggle button (▸ Subjects) is added to the existing global-controls row. When open,
the card body becomes a flex row:
- Charts area:
flex-1; the existingResponsiveContainer width="100%"re-flows the charts narrower automatically. - Sidebar panel: ~210px fixed width,
border-l, top-aligned with the card.
┌─ Cross-subject metrics ───────── X axis▾ Color by▾ Tick [▸ Subjects] ─┐
│ │ SUBJECTS Show all │
│ ┌──────────────────────────────┐ │ ┌───────────────────────┐ │
│ │ Count chart (re-flows) │ │ │ ▣ Group A (3)│ │
│ └──────────────────────────────┘ │ │ ☑ ● Mouse-A1 │ │
│ ┌──────────────────────────────┐ │ │ ☑ ● Mouse-A2 │ │
│ │ Success-rate chart │ │ │ ☐ ● Mouse-A3 │ │
│ └──────────────────────────────┘ │ │ ▢ Group B (2)│ │
│ │ │ ☐ ● Mouse-B1 │ │
│ │ └───────────────────────┘ │
└─────────────────────────────────────────────────────────────────────────────┘
Sidebar contents
- Grouping reuses the current
Color byfield via the existinglines[].group. WithColor by = none, all subjects fall under a single "—" group. - Subject row: checkbox (checked = visible) + color swatch (
lines[].color) + name. Hovering the row setshighlightedSubject. - Group row: tri-state checkbox — checked if all in group visible, empty if all hidden, indeterminate if mixed. Clicking toggles the whole group. Shows group name + count.
- "Show all" header link — clears
hiddenSubjects; shown only when something is hidden.
Behavior details
-
Visibility — every subject
<Line>is rendered always (in both charts) withhide={hiddenSubjects.has(l.id)}instead of filtering thelinesarray. Recharts keeps a greyed, still-clickable legend entry for hidden lines and excludes them from the tooltip payload automatically. -
Legend click —
<Legend onClick={(o) => toggleHidden(o.dataKey)} />on both charts.dataKeyisl.id(lines usedataKey={l.id} name={l.id}). -
Tooltip ordering — the existing custom
ExpTooltipsortspayloadbyvaluedescending before rendering its rows, so the topmost line is first. Null/hidden subjects are already excluded. Applies to both the count and rate tooltips. -
Legend hover highlight —
<Legend onMouseEnter={(o) => setHighlight(o.dataKey)} onMouseLeave={() => setHighlight(null)} />. Each line'sstrokeWidthis3.5whenhighlightedSubject === l.id, else2(other lines unchanged — "thicken hovered only"). Highlight applies in both charts and is also driven by sidebar-row hover.
Testing
- Manual verification in the running app (
ExperimentDetailpage with an experiment that has animals and saved analysis metrics):- Open/close sidebar; charts re-flow.
- Toggle a subject checkbox → line disappears in both charts; legend entry greys out.
- Group checkbox toggles all members; shows indeterminate when mixed.
- "Show all" clears hidden state.
- Click a legend entry → toggles in both charts.
- Hover tooltip rows are ordered highest-value-first.
- Hover a legend entry / sidebar row → that line thickens in both charts.
- Confirm existing controls (X axis, Color by, tick step, per-chart customize, Y-metric) still work.