Skip to content
  • Sponsor plotly/plotly.js

  • Notifications You must be signed in to change notification settings
  • Fork 1.9k
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add min/max scale limits for geo plots #7371

Draft
wants to merge 12 commits into
base: master
Choose a base branch
from
1 change: 1 addition & 0 deletions draftlogs/7371_add.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- Add `minscale`, `maxscale` geo plot attributes [[#7371](https://github.com/plotly/plotly.js/pull/7371)]
15 changes: 14 additions & 1 deletion src/components/modebar/buttons.js
Original file line number Diff line number Diff line change
@@ -543,9 +543,22 @@ function handleGeo(gd, ev) {

if(attr === 'zoom') {
var scale = geoLayout.projection.scale;
var minscale = geoLayout.projection.minscale;
var maxscale = geoLayout.projection.maxscale === -1 ? Infinity : geoLayout.projection.maxscale;
var max = Math.max(minscale, maxscale);
var min = Math.min(minscale, maxscale);
var newScale = (val === 'in') ? 2 * scale : 0.5 * scale;

Registry.call('_guiRelayout', gd, id + '.projection.scale', newScale);
// make sure the scale is within the min/max bounds
if (newScale > max) {
newScale = max;
} else if (newScale < min) {
newScale = min;
}

if (newScale !== scale) {
Registry.call('_guiRelayout', gd, id + '.projection.scale', newScale);
}
}
}

15 changes: 14 additions & 1 deletion src/plots/geo/geo.js
Original file line number Diff line number Diff line change
@@ -484,7 +484,11 @@ proto.updateFx = function(fullLayout, geoLayout) {

if(dragMode === 'pan') {
bgRect.node().onmousedown = null;
bgRect.call(createGeoZoom(_this, geoLayout));
var zoom = createGeoZoom(_this, geoLayout)
bgRect.call(zoom);
// TODO: Figure out how to restrict when this transition occurs. Or is it a no-op if nothing has changed?
// Trigger transition to handle if minscale attribute isn't 0
zoom.event(bgRect)
bgRect.on('dblclick.zoom', zoomReset);
if(!gd._context._scrollZoom.geo) {
bgRect.on('wheel.zoom', null);
@@ -709,6 +713,15 @@ function getProjection(geoLayout) {

projection.precision(constants.precision);

// https://d3js.org/d3-zoom#zoom_scaleExtent
projection.scaleExtent = () => {
var minscale = projLayout.minscale;
var maxscale = projLayout.maxscale === -1 ? Infinity : projLayout.maxscale;
var max = Math.max(minscale, maxscale);
var min = Math.min(minscale, maxscale);
return [100 * min, 100 * max];
};

if(geoLayout._isSatellite) {
projection.tilt(projLayout.tilt).distance(projLayout.distance);
}
20 changes: 20 additions & 0 deletions src/plots/geo/layout_attributes.js
Original file line number Diff line number Diff line change
@@ -177,6 +177,26 @@ var attrs = module.exports = overrideAll({
'that fits the map\'s lon and lat ranges. '
].join(' ')
},
minscale: {
valType: 'number',
min: 0,
dflt: 0,
description: [
'Minimal zoom level of the map view.',
'A minscale of *0.5* (50%) corresponds to a zoom level',
'where the map has half the size of base zoom level.'
].join(' ')
},
maxscale: {
valType: 'number',
min: 0,
dflt: -1,
description: [
'Maximal zoom level of the map view.',
'A maxscale of *2* (200%) corresponds to a zoom level',
'where the map is twice as big as the base layer.'
].join(' ')
},
},
center: {
lon: {
4 changes: 4 additions & 0 deletions src/plots/geo/layout_defaults.js
Original file line number Diff line number Diff line change
@@ -161,6 +161,8 @@ function handleGeoDefaults(geoLayoutIn, geoLayoutOut, coerce, opts) {
}

coerce('projection.scale');
coerce('projection.minscale');
coerce('projection.maxscale');

show = coerce('showland', !visible ? false : undefined);
if(show) coerce('landcolor');
@@ -205,6 +207,8 @@ function handleGeoDefaults(geoLayoutIn, geoLayoutOut, coerce, opts) {
// clear attributes that will get auto-filled later
if(fitBounds) {
delete geoLayoutOut.projection.scale;
delete geoLayoutOut.projection.minscale;
delete geoLayoutOut.projection.maxscale;

if(isScoped) {
delete geoLayoutOut.center.lon;
22 changes: 17 additions & 5 deletions src/plots/geo/zoom.js
Original file line number Diff line number Diff line change
@@ -32,6 +32,7 @@ module.exports = createGeoZoom;
function initZoom(geo, projection) {
return d3.behavior.zoom()
.translate(projection.translate())
.scaleExtent(projection.scaleExtent())
.scale(projection.scale());
}

@@ -132,16 +133,21 @@ function zoomNonClipped(geo, projection) {
function handleZoomstart() {
d3.select(this).style(zoomstartStyle);

mouse0 = d3.mouse(this);
var rect = this.getBBox()
mouse0 = d3.event.sourceEvent
? d3.mouse(this)
: [rect.x + rect.width / 2, rect.y + rect.height / 2];
rotate0 = projection.rotate();
translate0 = projection.translate();
lastRotate = rotate0;
zoomPoint = position(mouse0);
}

function handleZoom() {
mouse1 = d3.mouse(this);

var rect = this.getBBox()
mouse1 = d3.event.sourceEvent
? d3.mouse(this)
: [rect.x + rect.width / 2, rect.y + rect.height / 2];
if(outside(mouse0)) {
zoom.scale(projection.scale());
zoom.translate(projection.translate());
@@ -210,7 +216,10 @@ function zoomClipped(geo, projection) {
zoom.on('zoomstart', function() {
d3.select(this).style(zoomstartStyle);

var mouse0 = d3.mouse(this);
var rect = this.getBBox()
var mouse0 = d3.event.sourceEvent
? d3.mouse(this)
: [rect.x + rect.width / 2, rect.y + rect.height / 2];
var rotate0 = projection.rotate();
var lastRotate = rotate0;
var translate0 = projection.translate();
@@ -219,7 +228,10 @@ function zoomClipped(geo, projection) {
zoomPoint = position(projection, mouse0);

zoomOn.call(zoom, 'zoom', function() {
var mouse1 = d3.mouse(this);
var rect = this.getBBox()
var mouse1 = d3.event.sourceEvent
? d3.mouse(this)
: [rect.x + rect.width / 2, rect.y + rect.height / 2];

projection.scale(view.k = d3.event.scale);

14 changes: 14 additions & 0 deletions test/plot-schema.json
Original file line number Diff line number Diff line change
@@ -2373,6 +2373,20 @@
"valType": "number"
},
"editType": "plot",
"maxscale": {
"description": "Maximal zoom level of the map view. A maxscale of *2* (200%) corresponds to a zoom level where the map is twice as big as the base layer.",
"dflt": -1,
"editType": "plot",
"min": 0,
"valType": "number"
},
"minscale": {
"description": "Minimal zoom level of the map view. A minscale of *0.5* (50%) corresponds to a zoom level where the map has half the size of base zoom level.",
"dflt": 0,
"editType": "plot",
"min": 0,
"valType": "number"
},
"parallels": {
"description": "For conic projection types only. Sets the parallels (tangent, secant) where the cone intersects the sphere.",
"editType": "plot",