Fix scatter collapse after repeated box zoom#101
Conversation
Merging this PR will not alter performance
Comparing Footnotes
|
masenf
left a comment
There was a problem hiding this comment.
when zooming out, if the zoom level is already at 100%, the pan is reset as well. when this happens, it leaves the previous selection in place including the dot and tooltip.
Screen.Recording.2026-07-20.at.2.54.03.PM.mov
probably a task for another PR, but i'm wondering if we should let the outer page scroll when the chart is already zoomed out to mitigate ENG-10492 which is a bit annoying when browsing the docs. idk if it would be more jarring for a user actively zooming out to hit 100% and then suddenly scroll down the page.
The tooltip stored only the cursor's screen position, so any view change that happened without a pointermove — modebar Reset View, dblclick home, wheel zoom, linked views — left it floating at a stale location showing values for a point that was no longer there. Anchor it in data space instead (matplotlib's data-coordinate-annotation contract): record the hovered point's coordinates at pick time, reproject them on every draw so the tooltip rides its point through pans, zooms, and reset animations, and clip it when the anchor leaves the plot rect rather than clamping to an edge that misrepresents where the point is. The kernel's exact pick reply sharpens the f32-decoded anchor to f64 (§16), and every hide path clears the anchor so a later draw cannot resurrect a dismissed tooltip.
Greptile SummaryThis PR fixes scatter collapse after repeated anisotropic box zooms. The main changes are:
Confidence Score: 5/5This looks safe to merge. No blocking issues found in the changed code.
What T-Rex did
Important Files Changed
Reviews (6): Last reviewed commit: "fix(export): shut Chromium down via CDP ..." | Re-trigger Greptile |
…w-collapse-on-zoom # Conflicts: # examples/demo-advance.ipynb # js/src/53_interaction.js # python/xy/static/index.js # python/xy/static/standalone.js
…encoded linear mapping interaction.md §5 gains the zoom-limit rules this branch introduced (the §16 precision floor on zoom-in and the per-axis home-span stop on zoom-out); a new §7 specifies the tooltip's data-coordinate anchoring contract. design-dossier.md §16 records that linear axes stay offset-encoded through the vertex transform (only log axes decode). renderer-architecture.md module inventory resynced with the code.
…w-collapse-on-zoom
|
i busted a test try to merge origin/main, will push a patch to this branch |
…anchor _zoomAxisRange's zoom-out guard snapped an axis to its home *range* once it reached home span, discarding the translation from a cursor-anchored zoom chain. A zoom-in-high then zoom-out-low pans a free (pan-enabled) axis; the snap yanked it back to home. Cap the span at home while preserving the anchor instead, matching the spec (zoom-out stops at home span, §7.5). Positional containment remains the mechanism that pins locked axes to home. Also fix the zoom-precision probe's finite check: view.view now nests a `ranges` object (per-axis pan/zoom, #117), so Object.values(current) included a non-number. Check the numeric view coordinates directly.
…lose Chromium helper processes can outlive the terminated main process and write into the temp profile while rmtree walks it, failing CI teardown with ENOTEMPTY. Retry the cleanup, then abandon leftovers.
…ession close" This reverts commit e9552a6.
…cleanup SIGTERM killed the browser process out from under its helper processes, which kept flushing into the profile's Default/ while TemporaryDirectory cleanup walked it, failing CI teardown with ENOTEMPTY. Browser.close asks Chromium for an orderly exit — it flushes profile state and reaps its helpers before the main process exits — so waiting on the process leaves the profile quiescent. Also drop the crashpad handler, a detached process that writes into the profile on its own schedule. Signals remain only as escalation if the browser fails to exit.
Conflict in spec/api/interaction.md was additive on both sides: main's zoom-limit boundaries (#101) and this branch's axis-band scoping paragraph plus the Back/Forward modebar buttons. Kept both and refreshed the _zoomAxisRange line citation, which the merge shifted.
Reconcile the updatePayload re-home fix with main's Reflex events / cross-filtering work (#113) and the box-zoom scatter-collapse fix (#101). - js/src/56_animation.js: auto-merged — the re-home block (clear view0 before deriving the new home) and main's `_updatePickable()` refactor are separate hunks of updatePayload and coexist. The full-payload path still routes through updatePayload; main's new XYChart.jsx onPayload re-pins a navigated chart via the restore contract, while a dependent (never-navigated) chart — the §4 detail histogram — takes the re-home, which is what fixes zoom-out. - python/xy/static/{index,standalone}.js: regenerated from merged js/src. - tests/reflex_adapter/test_assets.py: took main's assertion — the wrapper now fits the live spec via `eventSpec(data.spec, cbRef.current)`, superseding the stale-literal fix carried on this branch. - spec/design/reflex-integration.md §4: keep the re-home note, reworded to defer viewport preservation to main's restore contract (the home must be the new spec's extents in both cases).
Motivation
Repeated anisotropic box zoom followed by Zoom Out could collapse a scatter plot into a horizontal band.
A box zoom can narrow X and Y by very different factors. Zoom Out previously expanded both axes geometrically without respecting each axis's original range, so the less-zoomed axis could grow far beyond its home span while the other axis was still zoomed in. That extreme aspect-ratio mismatch visually flattened the point cloud.
Description
js/src/53_interaction.js)examples/demo-advance.ipynb.tests/test_zoom_precision.py)python/xy/static/index.js,python/xy/static/standalone.js)The earlier GPU-coordinate precision workaround was removed because it was not needed for this failure.
Testing
node js/build.mjs --check— generated static bundles are fresh.uv run ruff check tests/test_zoom_precision.py— passed.uv run ruff format --check tests/test_zoom_precision.py— passed.uv run pytest tests/test_zoom_precision.py tests/test_static_client_security.py tests/test_scatter.py tests/test_components.py -q— 187 passed.125×, Y1.953×7.8125×, Y1×0.122×—eight times wider than its home spanCloses #87