Files
experiments-database/docs/superpowers/specs/2026-06-01-cross-subject-plot-interactions-design.md
Experiments DB Dev 3095628479 docs: design for cross-subject plot interactions
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-01 08:45:46 -04:00

6.1 KiB

Cross-subject metrics plot — interaction enhancements

Date: 2026-06-01 Component: frontend/src/components/AnalysisCharts.jsxExperimentAnalysisCharts 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):

  1. A toggleable sidebar listing subjects grouped by their group, with per-subject and per-group checkboxes to show/hide each subject's line.
  2. Clicking a subject's legend entry toggles that subject's line visibility.
  3. The hover tooltip orders its rows to match the vertical stacking of the points (highest value at top).
  4. 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 SubjectAnalysisCharts component 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 — default false.

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 existing ResponsiveContainer 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 by field via the existing lines[].group. With Color by = none, all subjects fall under a single "—" group.
  • Subject row: checkbox (checked = visible) + color swatch (lines[].color) + name. Hovering the row sets highlightedSubject.
  • 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

  1. Visibility — every subject <Line> is rendered always (in both charts) with hide={hiddenSubjects.has(l.id)} instead of filtering the lines array. Recharts keeps a greyed, still-clickable legend entry for hidden lines and excludes them from the tooltip payload automatically.

  2. Legend click<Legend onClick={(o) => toggleHidden(o.dataKey)} /> on both charts. dataKey is l.id (lines use dataKey={l.id} name={l.id}).

  3. Tooltip ordering — the existing custom ExpTooltip sorts payload by value descending before rendering its rows, so the topmost line is first. Null/hidden subjects are already excluded. Applies to both the count and rate tooltips.

  4. Legend hover highlight<Legend onMouseEnter={(o) => setHighlight(o.dataKey)} onMouseLeave={() => setHighlight(null)} />. Each line's strokeWidth is 3.5 when highlightedSubject === l.id, else 2 (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 (ExperimentDetail page 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.