Skip to content

feat(reports): render RL charts in switchable 2D/3D tabs#41

Merged
jjroelofs merged 2 commits into
1.xfrom
feat/rl-charts-tabbed-2d-3d
Apr 14, 2026
Merged

feat(reports): render RL charts in switchable 2D/3D tabs#41
jjroelofs merged 2 commits into
1.xfrom
feat/rl-charts-tabbed-2d-3d

Conversation

@jjroelofs
Copy link
Copy Markdown
Contributor

Summary

  • Both the 2D line chart and the 3D posterior landscape now render behind a tablist on the experiment reports page. Switching tabs never reloads the page.
  • chart_line_threshold becomes a tab-default preference instead of a visibility gate: numArms > chart_line_threshold opens on the 3D tab, everything else opens on 2D.
  • Closes the workflow papercut where admins had to change the threshold config just to peek at the other visualization.

What changed

  • templates/rl-charts.html.twig: tablist + panes with role="tab" / role="tabpanel" / aria-selected / aria-controls wiring. Shared date filter moved above the tabs (removes the duplicate filter block the old two-box layout carried).
  • css/rl-charts.css: tab button styling with .is-active, pane box layout, :focus-visible outline for keyboard users. Drops the old .rl-chart-row / .rl-chart-box classes.
  • js/rl-plotly-charts.js:
    • 2D and 3D rendering split into render2d() / render3d() closures.
    • Active tab renders immediately; inactive tab renders lazily on first activation so Plotly.newPlot has a visible container to measure.
    • Every tab switch calls Plotly.Plots.resize() on the newly-visible chart to recover any sizing drift from the hidden state.
    • once('rl-chart-tabs') wiring for click and ArrowLeft/ArrowRight keyboard navigation.

Design notes

  • Plotly refuses to size correctly when its target has display: none, which is why the inactive pane holds a never-touched container until first activation — not a hidden but rendered chart. First-activation render appears instant to the user because the tab is already visible by the time render3d() runs.
  • The 2D chart's 20-trace cap and legend-hide-above-threshold rules are unchanged, so large experiments still degrade readability gracefully.

Test plan

  • Open an experiment with 2–9 variants: 2D tab opens by default, clicking 3D renders the surface plot without reload.
  • Open an experiment with 10+ variants: 3D tab opens by default, clicking 2D renders the top-20 line chart without reload.
  • Resize browser while a chart is active — both charts resize via the existing debounced Plotly.Plots.resize() path.
  • Arrow-key navigation on the tabs moves focus and activates panes.
  • Screen reader announces the tab change (role=tab / aria-selected wired correctly).
  • Mobile/narrow layout: tab buttons wrap cleanly.
  • Confirm experiments with date_filter still show the single shared filter above the tabs with no duplication.

Jurriaan Roelofs added 2 commits April 14, 2026 16:20
Before, the reports page rendered either the 2D line chart OR the 3D
posterior landscape based on whether arm count was at or above
chart_line_threshold. Admins who wanted the other view had to change
the threshold and reload.

Now both charts are always available behind tab controls; the threshold
decides only which tab is active on first paint. Switching tabs never
reloads the page.

Implementation:

- templates/rl-charts.html.twig: tablist + panes with proper
  role/aria-selected/aria-controls wiring, shared date filter above
  the tabs (removes duplicate filter blocks from the old two-box
  layout).
- css/rl-charts.css: tab button styling with is-active state, pane
  box layout, :focus-visible outline for keyboard users. Old
  .rl-chart-row/.rl-chart-box classes dropped.
- js/rl-plotly-charts.js: 2D and 3D render logic split into
  render2d()/render3d() closures. The active tab renders immediately;
  the inactive tab's chart is rendered lazily on its first activation
  so Plotly.newPlot has a visible container to size against. Every
  subsequent tab switch calls Plotly.Plots.resize() against the
  newly-visible chart. Tab click + ArrowLeft/ArrowRight keyboard
  navigation are both wired via once('rl-chart-tabs').

Default tab selection: numArms > chart_line_threshold picks '3d',
everything else picks '2d'. The 2D chart still caps at 20 traces for
readability and still hides its legend when numArms exceeds the
threshold.
Plotly throws "Something went wrong with axis scaling" when the 3D
surface is rendered against a container with 0 height, which happens
on first tab activation because getResponsiveConfig() was called at
init while the 3D pane was still display:none. Reading the height
inside render2d()/render3d() picks up the real clientHeight once the
pane is active. Fallback raised from 200px to 70vh to match the
min-height in rl-charts.css.
@jjroelofs jjroelofs force-pushed the feat/rl-charts-tabbed-2d-3d branch from a68ea82 to 190e11a Compare April 14, 2026 14:20
@jjroelofs jjroelofs merged commit fa68d22 into 1.x Apr 14, 2026
3 checks passed
@jjroelofs jjroelofs deleted the feat/rl-charts-tabbed-2d-3d branch April 14, 2026 14:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant