Skip to content

Add fitbounds support for map subplots#7911

Open
kirthi-b wants to merge 1 commit into
plotly:v4.0from
kirthi-b:feat/map-fitbounds
Open

Add fitbounds support for map subplots#7911
kirthi-b wants to merge 1 commit into
plotly:v4.0from
kirthi-b:feat/map-fitbounds

Conversation

@kirthi-b

Copy link
Copy Markdown
Contributor

Adds layout.map.fitbounds for the MapLibre-backed map subplot (scattermap, choroplethmap, densitymap), so center and zoom can be computed automatically from trace data instead of set by hand. This closes #3434, which has been open since 2019 asking for the mapbox/map equivalent of geo.fitbounds.

What this does

Setting fitbounds to 'locations' or 'geojson' computes a lon/lat bounding box from the visible map-type traces on that subplot and feeds it into MapLibre's cameraForBounds to get a center and zoom. This follows the same two-mode split as geo.fitbounds:

  • 'locations' uses the matched geometry/points for each trace (marker positions for scattermap/densitymap, matched features for choroplethmap)
  • 'geojson' uses the trace's entire input geojson, for choroplethmap

Design constraints from the issue thread

etpinard pointed at #4419 (the geo fitbounds PR) as the reference implementation to port. I looked at how geo.js computes its bounding box and feeds it into the d3 projection, and adapted the same idea for MapLibre's camera model rather than copying the geo code directly, since the two subplot types don't share a rendering path.

alexcjohnson's comments set out the constraints I tried to follow closely:

  • No new attribute value should mutate the user's figure. The auto-fit computation only ever writes into the internal fullLayout copy (the same object geo.js already mutates for its own fitbounds), never into gd.layout or the object the user passed in. layout.map.fitbounds itself is a new attribute (matching how geo.fitbounds shipped), but the values it computes stay off the user's input.
  • Auto-fit should only apply when center/zoom aren't explicitly set. layout_defaults.js checks the raw user input before coercion to decide this (_fitboundsAuto), the same "absence of an explicit value is what triggers auto" logic used by geo.fitbounds and by cartesian autorange. If you set fitbounds and an explicit center or zoom, your value wins and fitbounds is a no-op.
  • Clear/recompute properly on relayout. Once the user pans or zooms the map by hand, fitbounds gets flipped back to false on that interaction (mirrors geo's zoom handler, which does the same set('fitbounds', false) after a GUI zoom). Otherwise every subsequent redraw would fight the user's manual positioning.

Scope

Kept this to what geo's fitbounds precedent covers: the bounding-box computation and the render/relayout wiring for a single map subplot. I did not touch bearing/pitch (geo has no analogous rotation-fitting concept beyond what's already there), and I didn't attempt anything for the older mapbox-gl subplot (src/plots/mapbox/), since the issue and #4419 are both scoped to fitting bounds, not to backporting a feature to the legacy trace types.

One known gap: double-click "reset view" now captures its snapshot after the first fitbounds computation runs (so resetting lands back on the fitted framing, not the pre-fit default center/zoom of [0,0]/zoom 1). I moved the viewInitial capture in src/plots/map/index.js into Map.prototype.createMap for this reason, so it runs after fitbounds resolves.

Testing

Added a fitbounds block to the layout-defaults tests in map_test.js (auto-fit flag behavior across explicit-center / explicit-zoom / disabled cases) plus three integration tests: auto-computed center/zoom from trace data, explicit center/zoom overriding fitbounds, and fitbounds getting cleared after a simulated user pan/zoom. All pass locally via npm run test-jasmine -- map --nowatch (Chrome, karma), along with the full existing map suite. One pre-existing unrelated test (should respond drag / scroll / double-click interactions) fails on a clean, unmodified checkout in my environment too, so it's a pre-existing local/Chrome-version flake, not something this PR introduces.

What I did not verify: actual visual/rendering correctness in a live browser beyond the automated jasmine assertions (the CI image/pixel-diff suite wasn't run here). The center/zoom values are checked programmatically (bounding-box containment, non-default zoom) rather than by eyeballing a rendered map.

Fixes #3434

@camdecoster

Copy link
Copy Markdown
Contributor

Thanks for the PR! We recently made some changes to the map traces to auto-fit for non-choropleth plots. This is in the release branch for the upcoming v4.0. Would you be willing to target that branch with your PR and rework your PR?

Extend layout.map.fitbounds to cover choroplethmap traces, which the
v4.0 auto-fit currently skips. With 'locations' (the default) the view
fits to the bounding box of the geometries matched by the trace's
locations; a new 'geojson' value fits to the entire input geojson
instead. Point traces (scattermap, densitymap) are unaffected.

Bounds are read at supply-defaults time from the resolved trace geojson,
so a URL geojson that has not been fetched yet is skipped quietly rather
than driving the fit or logging an error.
@kirthi-b
kirthi-b force-pushed the feat/map-fitbounds branch from e789d86 to 692fd96 Compare July 22, 2026 18:04
@kirthi-b
kirthi-b changed the base branch from master to v4.0 July 22, 2026 18:04
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.

Auto center/zoom for scattermapbox

2 participants