Files
ottyl/tickets/110-legend-click-to-focus-series.md
T

3.7 KiB

110 — Clicking a legend entry should focus that series, fading the rest

Summary

PanelChart's line and pie charts render a recharts <Legend> (webapp/src/components/PanelChart.tsx:134, :190) but it's purely decorative — clicking an entry does nothing. Bar charts have no <Legend> at all (:151-168), since each bar is its own labeled category on the x-axis rather than a separate series.

Add click-to-focus: clicking a legend entry marks that series "focused"; any focused series stay at full opacity/stroke while every other series fades. Clicking the same entry again (or clicking empty space in the legend, per whatever recharts exposes) clears focus and returns everyone to equal opacity — i.e. "no focused series" means "all at the same opacity", not "all faded".

Where

  • webapp/src/components/PanelChart.tsx:116-148 — line chart. Series are recharts <Line> elements keyed by keys (from toLineData, :60-78), colored via COLORS[i % COLORS.length] (:140). Fading a line means varying strokeOpacity (or swapping stroke for a color-mix'd faded variant) per key based on focus state.
  • webapp/src/components/PanelChart.tsx:170-193 — pie chart. Slices are <Cell> elements (:179-181) keyed by row index into rows (from squashRows, :50-57); fading a slice means varying its fillOpacity.
  • webapp/src/components/PanelChart.tsx:134, :190 — the two <Legend> usages. recharts' <Legend> takes an onClick handler on its payload entries ((entry) => void, where entry.dataKey / entry.value identifies which series was clicked) — this is the hook point rather than building a custom legend from scratch.
  • Bar charts (:151-168) have no legend to click, since bars aren't named series here — out of scope for this ticket unless the direction chosen below turns out to need bar-chart parity (see Scope).

Direction

  • Add focus state as a Set<string> (or single nullable string, if only one series can be focused at a time — decide during implementation) of series keys, local to PanelChart via useState. Reset it when series changes identity to avoid stale focus surviving a query change onto a chart with different series.
  • Wire the <Legend>'s onClick to toggle membership in that set.
  • Compute per-series opacity from the set: full opacity if the set is empty or the series is a member, faded (e.g. 0.25) otherwise. Thread that into <Line stroke=...> / <Cell fill=...> via strokeOpacity/fillOpacity rather than swapping the base color, so the existing COLORS palette and its color-mix theming stay untouched.
  • The legend entries themselves should visually reflect focus too (e.g. dim the faded entries' swatch/text) so it's clear what's focused without hunting across the plot — recharts' legend formatter prop can restyle each entry's label.

Scope

  • Line and pie charts only, matching where <Legend> already renders. Bar charts stay as-is — no legend, no per-bar fade — unless investigation shows recharts' bar <Legend> support is trivial to add alongside this; if so, note it as a follow-up rather than folding scope creep into this ticket.
  • Out of scope: persisting focus state across re-renders driven by polling (Dashboards.tsx's PanelPreview, :46-92, re-fetches on every poll tick) — focus should survive a data refresh as long as the series identity (legend key) is unchanged, but there's no need to persist it to localStorage or across a page reload.
  • Out of scope: multi-select "compare exactly these two series" UX beyond simple toggle-membership — keep it to click-to-add/click-to- remove from the focused set.