Map View and Zoom in JavaScript
How to control the initial view of maps in JavaScript. Examples of auto-fitting a map to its data, setting an explicit view, and limiting how far users can zoom.
Plotly Studio: Transform any dataset into an interactive data application in minutes with AI. Try Plotly Studio now.
Since Plotly.js v4, both outline-based geo subplots and tile-based map subplots auto-fit
their initial view to the data they plot. Before v4, both opened on a default world view and you had to supply a view
yourself to frame your data.
Auto-fitting is controlled by fitbounds:
layout.geo.fitboundsdefaults to'locations'and also accepts'geojson'(to frame the whole GeoJSON feature set rather than just the visible locations) andfalse.layout.map.fitboundsdefaults to'locations'and acceptsfalse.choroplethmaptraces do not participate in auto-fitting, so a subplot containing one opens on the default view.
Supplying your own view attributes turns auto-fitting off for that subplot. On a tile map that means
map.center or map.zoom. On an outline map, which attributes count depends on the subplot:
| Subplot | Attributes that turn auto-fitting off |
|---|---|
Any scope other than 'world' |
center.lon, center.lat, projection.scale |
'world' scope, clipped projection (orthographic, conic conformal, stereographic, azimuthal, …) |
the above, plus projection.rotation.lon, projection.rotation.lat,
lonaxis.range, lataxis.range |
'world' scope, unclipped projection (equirectangular, mercator, robinson, …) |
the above, plus projection.rotation.lon, lonaxis.range,
lataxis.range |
Note the first row: on a scoped subplot whose projection is clipped, lonaxis.range and
lataxis.range do not turn auto-fitting off — they still clip the drawing region, but the view is
centered on the data. Set fitbounds: false to honor the ranges as the view.
Setting fitbounds: false without supplying a view restores the pre-v4 default view. The
'albers usa', 'craig', and 'satellite' projections never auto-fit.
With no center or zoom given, a map subplot frames the trace data on the first render. It re-fits when the data
changes — through Plotly.restyle of lon/lat, Plotly.addTraces, or Plotly.deleteTraces — until the user pans,
zooms, rotates, or tilts the map, after which the chosen view is preserved. Data that straddles the antimeridian is
framed the short way around, not across the whole globe.
var data = [{
type: 'scattermap',
mode: 'markers+text',
lon: [-122.4, -73.9, -87.6],
lat: [37.8, 40.7, 41.9],
text: ['San Francisco', 'New York', 'Chicago'],
textposition: 'top right',
marker: {size: 12, color: '#d62728'}
}];
var layout = {
map: {style: 'basic'},
showlegend: false,
width: 700,
height: 450,
margin: {t: 30, b: 0, l: 0, r: 0}
};
Plotly.newPlot('myDiv', data, layout);
Passing map.center or map.zoom opts the subplot out of auto-fitting and opens it on exactly the view you give,
even if that view is the default zoom: 1 centered on (0, 0). Setting map.fitbounds: false without a view has
the same effect as the pre-v4 default: a world view at zoom: 1.
map.bounds, which restricts how far users can pan, is independent of auto-fitting and can be combined with either.
var data = [{
type: 'scattermap',
mode: 'markers+text',
lon: [-122.4, -73.9, -87.6],
lat: [37.8, 40.7, 41.9],
text: ['San Francisco', 'New York', 'Chicago'],
textposition: 'top right',
marker: {size: 12, color: '#d62728'}
}];
var layout = {
map: {
style: 'basic',
center: {lon: -98, lat: 39},
zoom: 2.5
},
showlegend: false,
width: 700,
height: 450,
margin: {t: 30, b: 0, l: 0, r: 0}
};
Plotly.newPlot('myDiv', data, layout);
layout.geo.fitbounds defaults to 'locations', so an outline map frames the locations it plots rather than the
whole world. Set fitbounds: false to get the pre-v4 world view, or supply your own view attributes — center
and projection.scale always turn auto-fitting off, and projection.rotation, lonaxis.range, and
lataxis.range do too except on a scoped subplot with a clipped projection. See
Map View and Zoom for the full breakdown.
When fitbounds is active, the modebar's Reset view button returns to the fitted view rather than the world view.
Note that a location whose territory crosses the ±180° antimeridian — the United States (the Aleutian Islands), Russia, Fiji, New Zealand, and Antarctica — spans most of the globe's longitude, so a figure that includes one is fitted very wide. Set the view explicitly in that case.
var data = [{
type: 'choropleth',
locations: ['BRA', 'ARG', 'CHL', 'PER', 'COL', 'BOL'],
locationmode: 'ISO-3',
z: [6, 5, 4, 3, 2, 1],
colorscale: 'Blues',
showscale: false
}];
var layout = {
title: {text: 'Fitted to the Plotted Locations'},
geo: {
projection: {type: 'equirectangular'},
showcountries: true,
countrycolor: 'rgb(255, 255, 255)'
},
width: 700,
height: 450
};
Plotly.newPlot('myDiv', data, layout);
projection.minscale and projection.maxscale, new in Plotly.js v4, clamp how far users can zoom a geo subplot.
Both are multipliers of projection.scale: minscale: 0.5 stops zooming out past half the base scale and
maxscale: 4 stops zooming in past four times it. The defaults — minscale: 0 and no maxscale — leave zoom
unrestricted.
Try scrolling on the map below: it will not zoom out below half, or in beyond four times, its initial scale.
If the initial projection.scale falls outside the range you set, Plotly clamps it on the first render by dispatching
a zoom event, which emits one extra plotly_relayout. Listeners that distinguish user zooms from programmatic ones
should tolerate that single event at startup.
var data = [{
type: 'scattergeo',
mode: 'markers+text',
lon: [2.35, 13.4, 12.5, -3.7],
lat: [48.86, 52.52, 41.9, 40.42],
text: ['Paris', 'Berlin', 'Rome', 'Madrid'],
textposition: 'top center',
marker: {size: 10, color: '#636efa'}
}];
var layout = {
title: {text: 'Zoom Limited to 0.5x - 4x'},
geo: {
scope: 'europe',
resolution: 50,
showland: true,
landcolor: 'rgb(235, 235, 235)',
projection: {
scale: 1,
minscale: 0.5,
maxscale: 4
}
},
showlegend: false,
width: 700,
height: 500
};
Plotly.newPlot('myDiv', data, layout);