Skip to content

Grey, persistent box/lasso/x-range/y-range selections#238

Merged
Alek99 merged 3 commits into
mainfrom
alek/grey-selection-bands
Jul 24, 2026
Merged

Grey, persistent box/lasso/x-range/y-range selections#238
Alek99 merged 3 commits into
mainfrom
alek/grey-selection-bands

Conversation

@Alek99

@Alek99 Alek99 commented Jul 24, 2026

Copy link
Copy Markdown
Member

What

Two changes to how selections look and behave.

1. Grey defaults matching the toolbar. The box, lasso, x-range, and y-range overlays defaulted to a blue (rgba(90,140,240,…)) that matched nothing else in the chrome, and box-zoom used a separate flat grey. All of them now default to the modebar's own text greys via scheme-aware internal tokens --xy-selection / --xy-selection-fill:

  • light: rgba(92,101,115,.6) stroke / …,.12) fill (#5c6573, the modebar text grey)
  • dark (.dark on the chart root or any ancestor): rgba(173,180,191,.6) / …,.12) (#adb4bf)

The public --chart-selection* / --chart-zoom-selection* tokens are unchanged and still override both schemes; only the built-in fallbacks moved.

2. Persistent box / x-range / y-range selections. Previously only the lasso stayed drawn after the gesture — box and range brushes vanished the instant the drag ended, leaving just the highlighted points. Now all three persist like the lasso: the data-space rectangle re-projects onto the selection overlay every draw (through pan and zoom) until the selection is cleared.

  • The overlay is shaped by a mode discriminator (box | x | y). Range brushes span the full plot on their free axis and drop the cross-axis border pair: x-range keeps only its left/right edges, y-range only its top/bottom — so each reads as the bounds it constrains.
  • mode round-trips through durable state as an optional selection.range.mode (a plain box omits it, keeping the existing {x0,x1,y0,y1} shape) and never affects the selected point set.
  • Box and lasso overlays stay mutually exclusive; double-click still clears; rows-selections clear both.

Spec and docs (spec/api/styling.md, spec/design/view-state.md, docs/styling/*) updated to match.

Verification

  • node js/build.mjs (tsc typecheck + bundle) passes
  • New browser probe test_rectangular_selection_persists_with_range_borders covers persistence through the real draw path, the range-edge borders, mode round-trip via applyState, rejection of a bad mode, and box/lasso mutual exclusion
  • 195 passed across test_view_state_client, test_static_client_security, test_components, test_ui_issue_regressions, test_modebar_select_drill, test_selection_rows, test_view_state
  • Browser-verified on a standalone HTML export: box drag stays drawn and re-projects on zoom; x-range shows only vertical edges spanning full height; y-range shows only horizontal edges spanning full width; grey flips to the dark palette under a .dark root class

Summary by CodeRabbit

  • New Features

    • Rectangular, x-range, and y-range selections now remain visible after redraws, cancellations, and state restoration.
    • Selection overlays can be restored through linked charts and saved view state.
    • Lasso and rectangular selections now correctly replace one another.
  • Bug Fixes

    • Improved clearing and restoration of selection overlays.
    • Added validation for selection range modes.
  • Style

    • Selection and zoom overlays now use scheme-aware neutral colors, including dark mode.

@greptile-apps

greptile-apps Bot commented Jul 24, 2026

Copy link
Copy Markdown

Greptile Summary

This PR updates chart selection styling and makes rectangular selections persist. The main changes are:

  • Scheme-aware grey defaults for selection and zoom overlays.
  • Persistent box, x-range, and y-range selection overlays that reproject on redraw.
  • Optional selection.range.mode state for x/y range brushes.
  • Linked chart selection handling that hydrates the persisted overlay state.
  • Docs, specs, and browser probes for the new selection behavior.

Confidence Score: 5/5

Safe to merge with minimal risk.

The changed code preserves public styling override precedence, validates the new range mode, clears mutually exclusive overlays consistently, and includes focused browser probes for the changed behavior.

No files require special attention.

T-Rex T-Rex Logs

What T-Rex did

  • The validation run started by producing the initial skip log to document skipped tests.
  • The Chromium-configured pytest session completed with a successful log, confirming tests passed under the chosen browser environment.
  • A browser-capture command log was created to record UI interactions used for validation.
  • A real standalone chart HTML file was generated for independent review and sharing.
  • Before and after screenshots, an after video, and JSON UI-state metrics were captured to validate the final visualization.

View all artifacts

T-Rex Ran code and verified through T-Rex

Important Files Changed

Filename Overview
js/src/20_theme.ts Adds scheme-aware internal selection CSS tokens and range-specific border rules while preserving public overrides.
js/src/50_chartview.ts Hydrates persisted selection overlay state for linked chart selection messages.
js/src/53_interaction.ts Persists and reprojects box/x/y selection overlays, including mutual exclusion with lasso selections.
js/src/57_viewstate.ts Extends view-state validation and application for optional x/y range selection modes.
tests/test_view_state_client.py Adds browser probes covering persisted rectangular/range selections and linked overlay hydration.

Reviews (3): Last reviewed commit: "Hydrate persisted selection overlay on l..." | Re-trigger Greptile

Comment thread spec/api/styling.md Outdated
@codspeed-hq

codspeed-hq Bot commented Jul 24, 2026

Copy link
Copy Markdown

Merging this PR will not alter performance

✅ 102 untouched benchmarks
⏩ 1 skipped benchmark1


Comparing alek/grey-selection-bands (6637b52) with main (9d5865a)

Open in CodSpeed

Footnotes

  1. 1 benchmark was skipped, so the baseline result was used instead. If it was deleted from the codebase, click here and archive it to remove it from the performance reports.

@Alek99 Alek99 changed the title Default selection bands to the modebar grey Grey, persistent box/lasso/x-range/y-range selections Jul 24, 2026
@Alek99

Alek99 commented Jul 24, 2026

Copy link
Copy Markdown
Member Author

The two red checks (Python 3.11 floor, Test (Rust + Python + JS)) are a pre-existing main breakage, not from this PR. All 20 failures are in tests/test_docs_examples.py / tests/test_verify_local.py asserting README prose and make shortcuts that #230 ("Refresh README for adoption", the tip of main) removed — main's own CI run at b6ab496 is red for the same reason. This PR's commits touch no README or doc-test files.

#236 ("Decouple CI tests from README", MERGEABLE) fixes exactly these. I merged #236 into this branch locally (no conflicts) and ran the previously-failing suites plus this PR's new selection test — 117 passed, including all 20 that CI reports red. So once #236 lands and this branch picks it up, CI goes green with no change needed here.

@Alek99

Alek99 commented Jul 24, 2026

Copy link
Copy Markdown
Member Author

Both Greptile findings addressed in be421f6:

P1 — Restore linked overlays. Fixed. The link-group handler in js/src/50_chartview.ts now hydrates the same persisted overlay state a local gesture produces: a linked range sets _boxSelection (with the band mode) and clears any lasso; a linked polygon sets _lassoPolygon and clears any box; clear wipes both. _selectLocal/_selectLocalPolygon already call draw(), so _drawNow now re-projects the band on the peer. I also made _sendSelect broadcast the range with its x/y mode so peers render the correct edge-only shape (the wire previously carried only {x0,x1,y0,y1}). This also fixes a latent bug where a linked selection left a stale opposite-type overlay drawn.

P2 — Document selection overrides. Fixed in spec/api/styling.md: the override sentence now lists --chart-selection / --chart-selection-fill and --chart-zoom-selection / --chart-zoom-selection-fill alongside the modebar/badge tokens, noting the CSS resolves each public token ahead of the internal --xy-selection* fallback. (Kept the current .6 border alpha rather than the .9 in the suggestion, which predated the later border-lightening commit.)

Added test_linked_selection_hydrates_persisted_overlay, which drives the real BroadcastChannel handler with the exact message shapes the sender emits (x-range, polygon, plain box, clear) and asserts overlay + durable state on the receiving peer. Also verified end-to-end in a browser with two link-grouped charts: an x-range brushed on one chart draws the grey vertical-edge band on the linked peer.

Alek99 added 3 commits July 24, 2026 10:10
Box, lasso, x-range, and y-range selection overlays defaulted to a blue
that matched nothing else in the chrome, while box-zoom used its own
flat grey. Route all of them through new scheme-aware internal tokens
(--xy-selection / --xy-selection-fill) set to the modebar's text greys:
#5c6573 in light mode, #adb4bf under a .dark root class. The public
--chart-selection* and --chart-zoom-selection* tokens still override
both schemes.
Box, x-range, and y-range brushes vanished the instant the drag ended,
leaving only the highlighted points; only the lasso stayed drawn. Make
all three persist like the lasso: the data-space rectangle re-projects
onto the selection overlay every draw (through pan/zoom) until the
selection is cleared.

The overlay is shaped by a mode discriminator (box | x | y). The range
brushes span the full plot on their free axis and drop the cross-axis
border pair — x-range keeps only its left/right edges, y-range only its
top/bottom — so each reads as the bounds it constrains. The mode
round-trips through durable state as an optional selection.range.mode
(a plain box omits it) and never affects the selected point set. Also
soften the band border to .6 alpha.

Box and lasso overlays stay mutually exclusive, double-click still
clears, and a browser probe covers persistence, edge borders, mode
round-trip, and rejection of a bad mode value.
Addresses Greptile review on the box-persistence change:

- P1 (correctness): a range selection received from a link-group peer
  updated _stateSelection and the local masks but never set _boxSelection,
  so _drawNow had nothing to re-project and the persistent band was
  absent on linked charts. The linked handler now hydrates the same
  overlay state a local gesture would — box/x/y range sets _boxSelection
  (clearing any lasso), a polygon sets _lassoPolygon (clearing any box),
  and clear wipes both — so peers show and re-project the overlay. The
  range broadcast now carries the x/y band mode so peers render the
  correct edge-only shape.
- P2 (docs): spec/api/styling.md now lists --chart-selection /
  --chart-selection-fill and --chart-zoom-selection /
  --chart-zoom-selection-fill in the public override sentence, matching
  the CSS which resolves each public token ahead of the --xy-selection*
  fallback.

Adds test_linked_selection_hydrates_persisted_overlay, driving the real
BroadcastChannel handler with the exact message shapes the sender emits
(x-range, polygon, plain box, clear) and asserting overlay + durable
state on the receiving peer. Verified end-to-end in-browser with two
link-grouped charts.
@Alek99
Alek99 force-pushed the alek/grey-selection-bands branch from be421f6 to 6637b52 Compare July 24, 2026 17:11
@coderabbitai

coderabbitai Bot commented Jul 24, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Rectangular selections now persist across redraws and view-state updates, support box/x/y modes, synchronize through linked selections, and remain mutually exclusive with lasso overlays. Selection styling uses scheme-aware tokens, with documentation and browser coverage updated accordingly.

Changes

Selection overlays

Layer / File(s) Summary
Selection contracts and scheme-aware styling
js/src/20_theme.ts, js/src/57_viewstate.ts, spec/api/styling.md, spec/design/view-state.md, docs/styling/*
Selection range modes and persistence semantics are documented and validated, while selection and zoom overlays use scheme-aware token fallbacks.
Selection interaction and overlay lifecycle
js/src/53_interaction.ts, js/src/50_chartview.ts
Box, x-range, and y-range selections are stored, rendered, restored after cancelled gestures, re-projected after redraws, and cleared when lasso or selection state replaces them.
Linked state hydration and end-to-end validation
js/src/50_chartview.ts, js/src/57_viewstate.ts, tests/test_view_state_client.py
Linked selections and applied view-state ranges restore the correct overlay mode, with browser probes covering persistence, clearing, validation, and overlay exclusivity.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant ChartView
  participant ViewState
  participant LinkedChart
  User->>ChartView: complete box/x/y range gesture
  ChartView->>ViewState: persist range and optional mode
  ChartView->>LinkedChart: broadcast selection.range
  LinkedChart->>ChartView: hydrate range overlay
  ChartView->>ChartView: re-render overlay after redraw
Loading

Suggested reviewers: carlosabadia

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: grey scheme-aware selection styling plus persistent box, lasso, and range selections.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch alek/grey-selection-bands

Comment @coderabbitai help to get the list of available commands.

@Alek99
Alek99 merged commit bf073b7 into main Jul 24, 2026
28 of 29 checks passed

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
js/src/53_interaction.ts (1)

197-218: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Box-zoom click/cancel can hide a persisted selection without restoring it.

previousLasso/previousBox are only snapshotted when mode.startsWith("select"), but end() (lines 279-280) and pointercancel (lines 338-339) unconditionally hide selRect/selLasso for any band, including "zoom" (box-zoom). Since a box-zoom gesture never captures previousBox/previousLasso, a stray click or a cancelled box-zoom drag while a box/x-range/y-range (or lasso) selection is active will hide that overlay with nothing to restore it (the "not moved"/else if (band.previousBox) branches at lines 315-321, and the equivalent in pointercancel at 343-346, never trigger for zoom mode). The overlay only comes back on the next unrelated _drawNow() call, which isn't guaranteed to happen soon — this undercuts the "persists until cleared" contract this PR adds for box/x/y-range selections specifically.

_boxSelection/_lassoPolygon are never mutated by a drag that stays under the 3px threshold (only the DOM preview is touched), so it's safe to snapshot them regardless of gesture mode.

🐛 Proposed fix: capture snapshots regardless of gesture mode
-        const previousLasso = mode.startsWith("select") && this._lassoPolygon
-          ? this._lassoPolygon.map((point) => [...point])
-          : null;
-        // A prior box/x/y selection is restored the same way as a prior lasso
-        // when the new gesture turns out to be a no-op click or is cancelled.
-        const previousBox = mode.startsWith("select") && this._boxSelection
-          ? { ...this._boxSelection }
-          : null;
+        const previousLasso = this._lassoPolygon
+          ? this._lassoPolygon.map((point) => [...point])
+          : null;
+        // A prior box/x/y selection is restored the same way as a prior lasso
+        // when the new gesture turns out to be a no-op click or is cancelled —
+        // including a box-zoom drag, which shares selRect/selLasso with the
+        // persisted overlay and would otherwise hide it with nothing to
+        // restore it.
+        const previousBox = this._boxSelection
+          ? { ...this._boxSelection }
+          : null;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@js/src/53_interaction.ts` around lines 197 - 218, Update the gesture
initialization block around previousLasso and previousBox to snapshot the active
_lassoPolygon and _boxSelection regardless of mode, rather than guarding them
with mode.startsWith("select"). This ensures the existing restoration branches
in end() and pointercancel restore persisted selections after no-op or cancelled
box-zoom gestures while preserving current selection snapshots.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@js/src/53_interaction.ts`:
- Around line 197-218: Update the gesture initialization block around
previousLasso and previousBox to snapshot the active _lassoPolygon and
_boxSelection regardless of mode, rather than guarding them with
mode.startsWith("select"). This ensures the existing restoration branches in
end() and pointercancel restore persisted selections after no-op or cancelled
box-zoom gestures while preserving current selection snapshots.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: e844a1be-404a-4160-8a85-978e60171427

📥 Commits

Reviewing files that changed from the base of the PR and between 9d5865a and 6637b52.

📒 Files selected for processing (9)
  • docs/styling/chrome-slots.md
  • docs/styling/themes-and-tokens.md
  • js/src/20_theme.ts
  • js/src/50_chartview.ts
  • js/src/53_interaction.ts
  • js/src/57_viewstate.ts
  • spec/api/styling.md
  • spec/design/view-state.md
  • tests/test_view_state_client.py

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