Skip to content

feat: multi-column Y-axis plot for series comparison#15

Merged
SpollaL merged 5 commits into
mainfrom
feat/multi-y-plot
Apr 20, 2026
Merged

feat: multi-column Y-axis plot for series comparison#15
SpollaL merged 5 commits into
mainfrom
feat/multi-y-plot

Conversation

@SpollaL
Copy link
Copy Markdown
Owner

@SpollaL SpollaL commented Apr 20, 2026

Summary

  • Adds a new PlotPickY mode that lets users select multiple Y-axis columns before picking X
  • PlotState.y_col replaced with y_cols: Vec<usize> to support multi-series rendering
  • Histogram plot type is disabled when multiple Y columns are selected (only Line/Bar cycle)
  • QA script (qa.sh) updated to cover the new multi-Y selection flow

Test plan

  • Press p in Normal mode → enters PlotPickY, navigate columns with / (or h/l), toggle selection with Space
  • Press Enter to advance to PlotPickX, Esc to go back to PlotPickY, Esc again to cancel
  • Single-Y selection: Line → Bar → Histogram cycle still works with t
  • Multi-Y selection: t cycles only Line ↔ Bar (no Histogram)
  • Run bash qa.sh and verify exit 0
  • Run cargo test

🤖 Generated with Claude Code

SpollaL and others added 2 commits April 20, 2026 18:54
Adds a new PlotPickY mode so users can select multiple Y columns before
picking the X axis. Each series is rendered in a distinct Catppuccin
Mocha accent color with a legend in the top-right corner. Histogram is
disabled when more than one Y column is selected (t cycles Line/Bar
only). Esc from PlotPickX now goes back to PlotPickY instead of
cancelling entirely.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@SpollaL SpollaL self-assigned this Apr 20, 2026
Removing .name() from datasets prevents ratatui's Chart from rendering
its own legend, which was doubling up with our custom render_plot_legend
and showing the last character of each series name in the line colour.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@SpollaL
Copy link
Copy Markdown
Owner Author

SpollaL commented Apr 20, 2026

@claude review this merge request

@claude
Copy link
Copy Markdown

claude Bot commented Apr 20, 2026

Claude finished @SpollaL's task in 2m 45s —— View job


PR Review: feat/multi-column Y-axis plot for series comparison

  • Gather context and read changed files
  • Review src/app.rs changes
  • Review src/events.rs changes
  • Review src/ui.rs changes
  • Review qa.sh and .githooks/pre-commit changes
  • Summarize findings

Overall this is a well-structured feature. The state machine changes are clean, the color palette integration is consistent, and the QA script coverage is thorough. A couple of issues to address before merging.

Bugs

1. first_len should come from nonempty, not all_seriessrc/ui.rs:1150

// current — wrong if first Y col is non-numeric
let first_len = all_series.first().map(|(d, _)| d.len()).unwrap_or(0);

// fix
let first_len = nonempty.first().map(|(_, d)| d.len()).unwrap_or(0);

If the first selected Y column has all-null or non-numeric data its series will be empty, making first_len = 0. Categorical X labels then won't render even for other valid series in the same plot. nonempty is computed a few lines later; first_len should use it instead.

Fix this →


2. Y-axis title regresses to "Value" for single-Y plotssrc/ui.rs:1278

// current — always generic
.title("Value")

// fix — restore column name for single-Y, generic for multi
.title(if app.plot.y_cols.len() == 1 {
    app.headers[app.plot.y_cols[0]].as_str()
} else {
    "Value"
})

Previously single-series plots showed the column name on the Y axis. That's now gone even when only one column is selected. The legend only appears for multi-Y, so single-Y users lose the axis label with no replacement.

Fix this →


Minor / Nits

3. Duplicate cycle_hint logicsrc/ui.rs:477 and src/ui.rs:1176

The cycle_hint string is computed identically in get_bar() and directly inside render_plot(). In Plot mode ui() returns early before get_bar() is called, so the Mode::Plot arm in get_bar() is actually unreachable dead code — render_plot() owns the status bar. The duplication won't cause a runtime bug today, but the two copies can drift. Worth either removing the dead Mode::Plot arm from get_bar() and shortcut_bar(), or adding a comment noting that Plot mode renders its own bar.


4. No unit tests for PlotPickY togglesrc/app_tests.rs

The Space-toggle logic in events.rs (add/remove from y_cols) is a new non-trivial invariant — e.g. toggling the same column twice should return to the original state. There are no tests for this. Given the existing test pattern in app_tests.rs, a small test in the plot_tests module would guard against future regressions.


5. CLAUDE.md PlotState table is stale

The architecture table in CLAUDE.md still shows y_col and doesn't list PlotPickY in the Mode variants section. Not blocking, but keeping the docs in sync with the code is worth a one-line update.


What looks good

  • State machine transitions are clean: Normal → PlotPickY → PlotPickX → Plot, with Esc walking back correctly at each step
  • Pre-selecting the current column on p is ergonomic; the status bar shows the live selection list clearly
  • series_color() maps series indices to the Catppuccin Mocha palette consistently with the rest of the codebase
  • Legend bounds-checking in render_plot_legend() prevents overflow on small terminals
  • Series color indices are stable over the nonempty filter (empty series don't shift the color of later ones)
  • Histogram correctly disabled for multi-Y (both in event handling and in the status bar hint)
  • QA script coverage (J1–J4) is thorough and well-labelled
  • Pre-commit hook sourcing $HOME/.cargo/env is the right fix for CI environments where cargo isn't in PATH

@SpollaL
Copy link
Copy Markdown
Owner Author

SpollaL commented Apr 20, 2026

Claude finished @SpollaL's task in 2m 45s —— View job

PR Review: feat/multi-column Y-axis plot for series comparison

  • Gather context and read changed files
  • Review src/app.rs changes
  • Review src/events.rs changes
  • Review src/ui.rs changes
  • Review qa.sh and .githooks/pre-commit changes
  • Summarize findings

Overall this is a well-structured feature. The state machine changes are clean, the color palette integration is consistent, and the QA script coverage is thorough. A couple of issues to address before merging.

Bugs

1. first_len should come from nonempty, not all_seriessrc/ui.rs:1150

// current — wrong if first Y col is non-numeric
let first_len = all_series.first().map(|(d, _)| d.len()).unwrap_or(0);

// fix
let first_len = nonempty.first().map(|(_, d)| d.len()).unwrap_or(0);

If the first selected Y column has all-null or non-numeric data its series will be empty, making first_len = 0. Categorical X labels then won't render even for other valid series in the same plot. nonempty is computed a few lines later; first_len should use it instead.

Fix this →

2. Y-axis title regresses to "Value" for single-Y plotssrc/ui.rs:1278

// current — always generic
.title("Value")

// fix — restore column name for single-Y, generic for multi
.title(if app.plot.y_cols.len() == 1 {
    app.headers[app.plot.y_cols[0]].as_str()
} else {
    "Value"
})

Previously single-series plots showed the column name on the Y axis. That's now gone even when only one column is selected. The legend only appears for multi-Y, so single-Y users lose the axis label with no replacement.

Fix this →

Minor / Nits

3. Duplicate cycle_hint logicsrc/ui.rs:477 and src/ui.rs:1176

The cycle_hint string is computed identically in get_bar() and directly inside render_plot(). In Plot mode ui() returns early before get_bar() is called, so the Mode::Plot arm in get_bar() is actually unreachable dead code — render_plot() owns the status bar. The duplication won't cause a runtime bug today, but the two copies can drift. Worth either removing the dead Mode::Plot arm from get_bar() and shortcut_bar(), or adding a comment noting that Plot mode renders its own bar.

4. No unit tests for PlotPickY togglesrc/app_tests.rs

The Space-toggle logic in events.rs (add/remove from y_cols) is a new non-trivial invariant — e.g. toggling the same column twice should return to the original state. There are no tests for this. Given the existing test pattern in app_tests.rs, a small test in the plot_tests module would guard against future regressions.

5. CLAUDE.md PlotState table is stale

The architecture table in CLAUDE.md still shows y_col and doesn't list PlotPickY in the Mode variants section. Not blocking, but keeping the docs in sync with the code is worth a one-line update.

What looks good

  • State machine transitions are clean: Normal → PlotPickY → PlotPickX → Plot, with Esc walking back correctly at each step
  • Pre-selecting the current column on p is ergonomic; the status bar shows the live selection list clearly
  • series_color() maps series indices to the Catppuccin Mocha palette consistently with the rest of the codebase
  • Legend bounds-checking in render_plot_legend() prevents overflow on small terminals
  • Series color indices are stable over the nonempty filter (empty series don't shift the color of later ones)
  • Histogram correctly disabled for multi-Y (both in event handling and in the status bar hint)
  • QA script coverage (J1–J4) is thorough and well-labelled
  • Pre-commit hook sourcing $HOME/.cargo/env is the right fix for CI environments where cargo isn't in PATH

@claude address this issues

@claude
Copy link
Copy Markdown

claude Bot commented Apr 20, 2026

Claude finished @SpollaL's task in 5m 14s —— View job


Addressing the review issues.

Tasks

  • Gather context and read relevant files
  • Fix Bug 1: first_len uses all_series instead of nonempty (src/ui.rs)
  • Fix Bug 2: Y-axis title always shows "Value" for single-Y plots (src/ui.rs)
  • Fix Minor 3: Remove dead Mode::Plot arm from get_bar() / shortcut_bar()
  • Fix Minor 4: Add unit tests for PlotPickY Space-toggle logic (src/app_tests.rs)
  • Fix Minor 5: Update CLAUDE.md PlotState table and Mode variants
  • Run cargo test and cargo clippy
  • Commit and push

@SpollaL
Copy link
Copy Markdown
Owner Author

SpollaL commented Apr 20, 2026

Claude finished @SpollaL's task in 5m 14s —— View job

Addressing the review issues. 435229200-5ac382c7-e004-429b-8e35-7feb3e8f9c6f.gif?jwt=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3NzY3MDc4OTAsIm5iZiI6MTc3NjcwNzU5MCwicGF0aCI6Ii8zMDU5MjQ5LzQzNTIyOTIwMC01YWMzODJjNy1lMDA0LTQyOWItOGUzNS03ZmViM2U4ZjljNmYuZ2lmP1gtQW16LUFsZ29yaXRobT1BV1M0LUhNQUMtU0hBMjU2JlgtQW16LUNyZWRlbnRpYWw9QUtJQVZDT0RZTFNBNTNQUUs0WkElMkYyMDI2MDQyMCUyRnVzLWVhc3QtMSUyRnMzJTJGYXdzNF9yZXF1ZXN0JlgtQW16LURhdGU9MjAyNjA0MjBUMTc1MzEwWiZYLUFtei1FeHBpcmVzPTMwMCZYLUFtei1TaWduYXR1cmU9MzRjNjQ3ZWUwNTBmMTQwMmIzZjIxOTQwYzIzNTFiZTI5OWIyY2NjZGY5Y2NhYWQ4MDM1OTcyMzFhMWRhNjViNyZYLUFtei1TaWduZWRIZWFkZXJzPWhvc3QmcmVzcG9uc2UtY29udGVudC10eXBlPWltYWdlJTJGZ2lmIn0.DuT9Ehtli26wF-uwdqmcgXFnNmR9S6HdEnCKnyUP2io

Tasks

  • Gather context and read relevant files
  • Fix Bug 1: first_len uses all_series instead of nonempty (src/ui.rs)
  • Fix Bug 2: Y-axis title always shows "Value" for single-Y plots (src/ui.rs)
  • Fix Minor 3: Remove dead Mode::Plot arm from get_bar() / shortcut_bar()
  • Fix Minor 4: Add unit tests for PlotPickY Space-toggle logic (src/app_tests.rs)
  • Fix Minor 5: Update CLAUDE.md PlotState table and Mode variants
  • Run cargo test and cargo clippy
  • Commit and push

@claude have you committed this changes?

@claude
Copy link
Copy Markdown

claude Bot commented Apr 20, 2026

Claude finished @SpollaL's task in 3m 57s —— View job


All 5 review issues have been addressed and pushed in commit aa61c2c.
Here's a summary of what was done:

Tasks

  • Check git status — no changes had been committed by the previous run
  • Bug 1: first_len now uses nonempty (moved before first_len) — categorical X labels render correctly even when the first Y column has no numeric data (src/ui.rs)
  • Bug 2: Y-axis title restored to column name for single-Y plots; only falls back to "Value" when multiple Y columns are selected (src/ui.rs)
  • Minor 3: Removed dead Mode::Plot arms from get_bar() and shortcut_bar(), replaced with unreachable!() to document the invariant that ui() returns early before these functions are called in Plot mode (src/ui.rs)
  • Minor 4: Added 4 unit tests for PlotPickY Space-toggle logic to the plot_tests module: add column, remove column, double-toggle restores state, multi-column independence (src/app_tests.rs)
  • Minor 5: Updated CLAUDE.md — PlotState table now shows y_cols (not y_col), and PlotPickY added to the Mode variants list (CLAUDE.md)
  • Commit and push — aa61c2c on feat/multi-y-plot

Note: cargo test and cargo clippy require approval in this environment — CI will validate on the pushed commit.
|

github-actions Bot and others added 2 commits April 20, 2026 17:57
- Bug 1: move nonempty before first_len so categorical X labels render
  correctly when the first Y column has no numeric data
- Bug 2: restore column-name Y-axis title for single-Y plots (was
  regressed to generic "Value")
- Minor 3: replace dead Mode::Plot arms in get_bar/shortcut_bar with
  unreachable!() since ui() returns early for Plot mode
- Minor 4: add PlotPickY Space-toggle unit tests to plot_tests module
- Minor 5: update CLAUDE.md PlotState table (y_col→y_cols) and add
  PlotPickY to Mode variants list

Co-authored-by: Luca Spolladore <SpollaL@users.noreply.github.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@SpollaL SpollaL merged commit e7320f1 into main Apr 20, 2026
12 checks passed
@SpollaL SpollaL deleted the feat/multi-y-plot branch April 20, 2026 18:06
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