Skip to content

dragbox: sync ax._input.range during pan and scroll-zoom#2

Draft
robertcollar-kobold wants to merge 1 commit into
masterfrom
fix/dragbox-input-range-sync
Draft

dragbox: sync ax._input.range during pan and scroll-zoom#2
robertcollar-kobold wants to merge 1 commit into
masterfrom
fix/dragbox-input-range-sync

Conversation

@robertcollar-kobold
Copy link
Copy Markdown
Owner

Summary

scaleZoom and Plotly.relayout both maintain the invariant that ax.range and ax._input.range stay in sync. Several mutation sites in dragbox.js violate it, only writing ax.range:

  • zoomWheelOneAxis (mousewheel zoom)
  • dz (corner-handle zoom on drag)
  • updateMatchedAxRange (linked-axis update during drag)
  • dragAxList (pan)

ax._input.range is what gd.layout.<axis>.range references. Leaving it stale means user code reading gd.layout.<axis>.range from a plotly_relayouting handler sees the previous range while gd._fullLayout already reflects the in-progress gesture. Anything that handler does (recomputing positions, fetching tiles, updating annotations) is computed against an out-of-date viewport, and only catches up once dragTail runs the eventual full relayout.

The fix is the minimal extension of the existing assignments to also write ax._input.range, matching the pattern already used by scaleZoom and the relayout path in plot_api.js.

How the bug manifests

Concrete repro: a tile-pyramid demo using a plotly_relayouting handler that reads gd.layout.xaxis.range to decide which OSM tiles to fetch on each frame of a wheel-zoom gesture. Across a tile-pyramid level boundary the handler computed tiles for the old range, so when the new tiles loaded they were rendered at coordinates that didn't match the actual viewport — geographic features visibly jumped.

A minimal in-page check before/after:

plotDiv.on('plotly_relayouting', function() {
  console.log({
    layout:     plotDiv.layout.xaxis.range.slice(),
    fullLayout: plotDiv._fullLayout.xaxis.range.slice()
  });
});
// fire a wheel event...

Before the fix: layout reports [0.4, 0.6], fullLayout reports [0.4095, 0.5905].
After the fix: both report [0.4095, 0.5905].

Test plan

  • npm run test-jasmine -- cartesian_interact --nowatch --Chrome
  • npm run test-jasmine -- axes --nowatch --Chrome
  • Manual: open a chart with a plotly_relayouting handler logging both gd.layout and gd._fullLayout ranges; pan and wheel-zoom and verify they agree

…croll-zoom

scaleZoom() consistently writes both `ax.range` and `ax._input.range`
(scale_zoom.js:13), but several mutation sites in dragbox.js only wrote
`ax.range`:

  * zoomWheelOneAxis  (mousewheel zoom)
  * dz                (corner-handle zoom on drag)
  * updateMatchedAxRange  (linked-axis update during drag)
  * dragAxList        (pan)

`ax._input.range` is what `gd.layout.<axis>.range` references. Leaving it
stale means user code reading `gd.layout` from a `plotly_relayouting`
handler sees the *previous* range while `gd._fullLayout` already reflects
the in-progress gesture. Any work that handler does (recomputing
positions, fetching new tiles, updating annotations) is therefore
computed against an out-of-date viewport, and only "catches up" once
dragTail eventually runs a full relayout.

This is reproducible by attaching a relayouting handler that compares
gd.layout.xaxis.range to gd._fullLayout.xaxis.range during a wheel zoom
- before the fix the two diverge; after the fix they stay in sync.

The change is the minimal extension of the existing `ax.range = …`
assignments to also set `ax._input.range`, matching the pattern already
used by scaleZoom and Plotly.relayout.
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