Skip to content

add support for min/max scale limits #5192

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

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 14 additions & 1 deletion src/components/modebar/buttons.js
Original file line number Diff line number Diff line change
Expand Up @@ -548,9 +548,22 @@ function handleGeo(gd, ev) {

if(attr === 'zoom') {
var scale = geoLayout.projection.scale;
var minscale = geoLayout.projection.minscale;
var maxscale = geoLayout.projection.maxscale;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now we need to handle

if(maxscale === null) maxscale = Infinity;

or

if(maxscale === -1) maxscale = Infinity;

if we decide to use -1 instead of null which is not a number.
@alexcjohnson your idea on which one is better?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With e722947 I changed the default to -1 instead of null. Scales with less than 0 make no sense here so this should be a valid solution.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suppose -1 as a default is OK if we really need a "no max" value - it's better than null which behaves oddly in relayout.

But I wonder if we can't pick a decent default that's more zoomed in than is generally ever useful. I have occasionally gotten in a situation by accidentally scroll zooming so far in that everything freezes up because the SVG engine is having trouble drawing all the off-screen elements. If we cover 99% of cases and avoid this pitfall I'd say that's a reasonable tradeoff, and avoids making a coded value. Something like 10,000?

Similarly, is there a default we could use for minscale, to avoid zooming the whole map away to a point? 0.1 perhaps?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Something like 10,000?
Similarly, is there a default we could use for minscale, to avoid zooming the whole map away to a point? 0.1 perhaps?

Makes sense!
(I'll have a look tomorrow. It's getting late her in europe 😴)


if(maxscale === -1) maxscale = Infinity;
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 > maxscale) {
newScale = maxscale;
} else if(newScale < minscale) {
newScale = minscale;
}

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

Expand Down
8 changes: 8 additions & 0 deletions src/plots/geo/geo.js
Original file line number Diff line number Diff line change
Expand Up @@ -709,6 +709,14 @@ function getProjection(geoLayout) {

projection.precision(constants.precision);

// https://github.com/d3/d3-zoom/blob/master/README.md#zoom_scaleExtent
projection.scaleExtent = function() {
var minscale = projLayout.minscale;
var maxscale = projLayout.maxscale;
if(maxscale === -1) maxscale = Infinity;
return [100 * minscale, 100 * maxscale];
};

if(geoLayout._isSatellite) {
projection.tilt(projLayout.tilt).distance(projLayout.distance);
}
Expand Down
20 changes: 20 additions & 0 deletions src/plots/geo/layout_attributes.js
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,26 @@ var attrs = module.exports = overrideAll({
'that fits the map\'s lon and lat ranges. '
].join(' ')
},
minscale: {
valType: 'number',
min: 0,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't minscale have a max range of 1?

Copy link
Contributor Author

@mojoaxel mojoaxel Oct 26, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think so. Maybe it also make sense in some circumstances to only allow scaling between e.g. 2..3.

Then of course the initial scale should also set so one value between 2..3. This check is still missing! What would be a good place to check if scale is within the given min/max bounds and reset it otherwise? Is layout_defaults.js the right place?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, layout_defaults is the right place - note that coerce returns the set value. Also we should check that minscale <= maxscale

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also we probably need to check when the scale is automatically determined from the contents of the map whether it's within these bounds, and push it into the range if not. I'm not quite sure where that happens, possibly multiple places in geo.js depending on the situation.

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: {
Expand Down
4 changes: 4 additions & 0 deletions src/plots/geo/layout_defaults.js
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand Down Expand Up @@ -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;
Expand Down
1 change: 1 addition & 0 deletions src/plots/geo/zoom.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ module.exports = createGeoZoom;
function initZoom(geo, projection) {
return d3.behavior.zoom()
.translate(projection.translate())
.scaleExtent(projection.scaleExtent())
.scale(projection.scale());
}

Expand Down
14 changes: 14 additions & 0 deletions test/plot-schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -2585,6 +2585,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",
Expand Down