Skip to content

Commit 152d742

Browse files
committed
fix #1698 - cross antimeridian lon range
- correctly handle rangeBox computations for lon range that cross anitmeridian - improve projection.rotation.lon default such that custom lonaxis.range get a reasonable default projection - add `geo_across-antimeridian` mock - fixup `geo_fill` mock for new projection.rotation.lon dflt
1 parent e1c4fba commit 152d742

File tree

7 files changed

+282
-91
lines changed

7 files changed

+282
-91
lines changed

src/plots/geo/geo2.js

+5-7
Original file line numberDiff line numberDiff line change
@@ -505,6 +505,7 @@ proto.render = function() {
505505
function getProjection(geoLayout) {
506506
var projLayout = geoLayout.projection;
507507
var projType = projLayout.type;
508+
508509
var projection = d3.geo[constants.projNames[projType]]();
509510

510511
var clipAngle = constants.lonaxisSpan[projType] ?
@@ -599,19 +600,16 @@ function makeGraticule(axisName, geoLayout) {
599600
// with well-defined direction
600601
//
601602
// Note that clipPad padding is added around range to avoid aliasing.
602-
//
603-
// TODO is this enough to handle ALL cases?
604-
// -- this makes scaling less precise than using d3.geo.graticule
605-
// as great circles can overshoot the boundary
606-
// (that's not a big deal I think)
607-
//
608-
// See https://github.com/plotly/plotly.js/issues/1698
609603
function makeRangeBox(lon, lat) {
610604
var clipPad = constants.clipPad;
611605
var lon0 = lon[0] + clipPad;
612606
var lon1 = lon[1] - clipPad;
613607
var lat0 = lat[0] + clipPad;
614608
var lat1 = lat[1] - clipPad;
609+
610+
// to cross antimeridian w/o ambiguity
611+
if(lon0 > 0 && lon1 < 0) lon1 += 360;
612+
615613
var dlon4 = (lon1 - lon0) / 4;
616614

617615
return {

src/plots/geo/layout/defaults.js

+58-78
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,12 @@
99

1010
'use strict';
1111

12-
var Lib = require('../../../lib');
1312
var handleSubplotDefaults = require('../../subplot_defaults');
1413
var constants = require('../constants');
1514
var layoutAttributes = require('./layout_attributes');
1615

16+
var axesNames = constants.axesNames;
17+
1718
module.exports = function supplyLayoutDefaults(layoutIn, layoutOut, fullData) {
1819
handleSubplotDefaults(layoutIn, layoutOut, fullData, {
1920
type: 'geo',
@@ -28,21 +29,62 @@ function handleGeoDefaults(geoLayoutIn, geoLayoutOut, coerce) {
2829

2930
var resolution = coerce('resolution');
3031
var scope = coerce('scope');
31-
var isScoped = (scope !== 'world');
3232
var scopeParams = constants.scopeDefaults[scope];
3333

3434
var projType = coerce('projection.type', scopeParams.projType);
35-
var isAlbersUsa = projType === 'albers usa';
36-
var isConic = projType.indexOf('conic') !== -1;
35+
var isAlbersUsa = geoLayoutOut._isAlbersUsa = projType === 'albers usa';
3736

38-
if(isConic) {
39-
var dfltProjParallels = scopeParams.projParallels || [0, 60];
40-
coerce('projection.parallels', dfltProjParallels);
37+
// no other scopes are allowed for 'albers usa' projection
38+
if(isAlbersUsa) scope = geoLayoutOut.scope = 'usa';
39+
40+
var isScoped = geoLayoutOut._isScoped = (scope !== 'world');
41+
var isConic = geoLayoutOut._isConic = projType.indexOf('conic') !== -1;
42+
geoLayoutOut._isClipped = !!constants.lonaxisSpan[projType];
43+
44+
for(var i = 0; i < axesNames.length; i++) {
45+
var axisName = axesNames[i];
46+
var dtickDflt = [30, 10][i];
47+
var rangeDflt;
48+
49+
if(isScoped) {
50+
rangeDflt = scopeParams[axisName + 'Range'];
51+
} else {
52+
var dfltSpans = constants[axisName + 'Span'];
53+
var hSpan = (dfltSpans[projType] || dfltSpans['*']) / 2;
54+
var rot = coerce(
55+
'projection.rotation.' + axisName.substr(0, 3),
56+
scopeParams.projRotate[i]
57+
);
58+
rangeDflt = [rot - hSpan, rot + hSpan];
59+
}
60+
61+
var range = coerce(axisName + '.range', rangeDflt);
62+
63+
coerce(axisName + '.tick0', range[0]);
64+
coerce(axisName + '.dtick', dtickDflt);
65+
66+
show = coerce(axisName + '.showgrid');
67+
if(show) {
68+
coerce(axisName + '.gridcolor');
69+
coerce(axisName + '.gridwidth');
70+
}
4171
}
4272

73+
var lonRange = geoLayoutOut.lonaxis.range;
74+
var latRange = geoLayoutOut.lataxis.range;
75+
76+
// to cross antimeridian w/o ambiguity
77+
var lon0 = lonRange[0];
78+
var lon1 = lonRange[1];
79+
if(lon0 > 0 && lon1 < 0) lon1 += 360;
80+
81+
var centerLon = lon0 + (lon1 - lon0) / 2;
82+
var projLon;
83+
4384
if(!isAlbersUsa) {
44-
var dfltProjRotate = scopeParams.projRotate || [0, 0, 0];
45-
coerce('projection.rotation.lon', dfltProjRotate[0]);
85+
var dfltProjRotate = isScoped ? scopeParams.projRotate : [centerLon, 0, 0];
86+
87+
projLon = coerce('projection.rotation.lon', dfltProjRotate[0]);
4688
coerce('projection.rotation.lat', dfltProjRotate[1]);
4789
coerce('projection.rotation.roll', dfltProjRotate[2]);
4890

@@ -54,19 +96,17 @@ function handleGeoDefaults(geoLayoutIn, geoLayoutOut, coerce) {
5496

5597
show = coerce('showocean');
5698
if(show) coerce('oceancolor');
57-
} else {
58-
geoLayoutOut.scope = 'usa';
5999
}
60100

61-
coerce('projection.scale');
62-
63-
geoAxisDefaults(geoLayoutIn, geoLayoutOut);
101+
coerce('center.lon', isScoped ? centerLon : projLon);
102+
coerce('center.lat', latRange[0] + (latRange[1] - latRange[0]) / 2);
64103

65-
var lonRange = geoLayoutOut.lonaxis.range;
66-
coerce('center.lon', lonRange[0] + (lonRange[1] - lonRange[0]) / 2);
104+
if(isConic) {
105+
var dfltProjParallels = scopeParams.projParallels || [0, 60];
106+
coerce('projection.parallels', dfltProjParallels);
107+
}
67108

68-
var latRange = geoLayoutOut.lataxis.range;
69-
coerce('center.lat', latRange[0] + (latRange[1] - latRange[0]) / 2);
109+
coerce('projection.scale');
70110

71111
show = coerce('showland');
72112
if(show) coerce('landcolor');
@@ -105,64 +145,4 @@ function handleGeoDefaults(geoLayoutIn, geoLayoutOut, coerce) {
105145
}
106146

107147
coerce('bgcolor');
108-
109-
// bind a few helper field that are used downstream
110-
geoLayoutOut._isClipped = !!constants.lonaxisSpan[projType];
111-
geoLayoutOut._isScoped = isScoped;
112-
geoLayoutOut._isConic = isConic;
113-
}
114-
115-
function geoAxisDefaults(geoLayoutIn, geoLayoutOut) {
116-
var axesNames = constants.axesNames;
117-
var axisName, axisIn, axisOut;
118-
119-
function coerce(attr, dflt) {
120-
return Lib.coerce(axisIn, axisOut, layoutAttributes[axisName], attr, dflt);
121-
}
122-
123-
function getRangeDflt() {
124-
var scope = geoLayoutOut.scope;
125-
126-
if(scope === 'world') {
127-
var projLayout = geoLayoutOut.projection;
128-
var projType = projLayout.type;
129-
var projRotation = projLayout.rotation;
130-
var dfltSpans = constants[axisName + 'Span'];
131-
132-
var halfSpan = dfltSpans[projType] !== undefined ?
133-
dfltSpans[projType] / 2 :
134-
dfltSpans['*'] / 2;
135-
136-
var rotateAngle = axisName === 'lonaxis' ?
137-
projRotation.lon :
138-
projRotation.lat;
139-
140-
return [rotateAngle - halfSpan, rotateAngle + halfSpan];
141-
} else {
142-
return constants.scopeDefaults[scope][axisName + 'Range'];
143-
}
144-
}
145-
146-
for(var i = 0; i < axesNames.length; i++) {
147-
axisName = axesNames[i];
148-
axisIn = geoLayoutIn[axisName] || {};
149-
axisOut = {};
150-
151-
var rangeDflt = getRangeDflt();
152-
var range = coerce('range', rangeDflt);
153-
154-
Lib.noneOrAll(axisIn.range, axisOut.range, [0, 1]);
155-
156-
coerce('tick0', range[0]);
157-
coerce('dtick', axisName === 'lonaxis' ? 30 : 10);
158-
159-
var show = coerce('showgrid');
160-
if(show) {
161-
coerce('gridcolor');
162-
coerce('gridwidth');
163-
}
164-
165-
geoLayoutOut[axisName] = axisOut;
166-
geoLayoutOut[axisName]._fullRange = rangeDflt;
167-
}
168148
}
33.1 KB
Loading

test/image/baselines/geo_fill.png

-6.54 KB
Loading
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
{
2+
"data": [
3+
{
4+
"type": "scattergeo",
5+
"lon": [170, 180, -180, -170],
6+
"lat": [-50, -45, -10, -20.5],
7+
"marker": { "size": 20 }
8+
},
9+
{
10+
"type": "choropleth",
11+
"locations": ["NZL", "AUS"],
12+
"z": [0, 1],
13+
"showscale": false
14+
},
15+
{
16+
"type": "scattergeo",
17+
"lon": [170, 180, -180, -170],
18+
"lat": [-50, -45, -10, -20.5],
19+
"marker": { "size": 20 },
20+
"geo": "geo2"
21+
},
22+
{
23+
"type": "choropleth",
24+
"locations": ["NZL", "AUS"],
25+
"z": [0, 1],
26+
"showscale": false,
27+
"geo": "geo2"
28+
}
29+
],
30+
"layout": {
31+
"geo": {
32+
"domain": {
33+
"x": [0, 0.5],
34+
"y": [0, 1]
35+
},
36+
"type": "miller",
37+
"showocean": true,
38+
"lonaxis": {
39+
"showgrid": true,
40+
"dtick": 2,
41+
"range": [120, -120]
42+
},
43+
"projection": {
44+
"scale": 2
45+
},
46+
"center": {
47+
"lat": -45
48+
}
49+
},
50+
"geo2": {
51+
"domain": {
52+
"x": [0.5, 1],
53+
"y": [0, 1]
54+
},
55+
"type": "miller",
56+
"showocean": true,
57+
"lonaxis": {
58+
"showgrid": true,
59+
"dtick": 2,
60+
"range": [150, -150]
61+
},
62+
"lataxis": {
63+
"range": [-90, 0]
64+
}
65+
},
66+
"annotations": [{
67+
"showarrow": false,
68+
"xref": "paper",
69+
"yref": "paper",
70+
"x": 0.25,
71+
"y": 1,
72+
"xanchor": "center",
73+
"yanchor": "bottom",
74+
"text": "set <i>lonaxis.range</i>, <i>center.lon</i> and <i>projection.scale</i>"
75+
}, {
76+
"showarrow": false,
77+
"xref": "paper",
78+
"yref": "paper",
79+
"x": 0.75,
80+
"y": 1,
81+
"xanchor": "center",
82+
"yanchor": "bottom",
83+
"text": "set <i>lonaxis.range</i> and <i>lataxis.range</i>"
84+
}],
85+
"showlegend": false,
86+
"width": 700,
87+
"height": 440
88+
}
89+
}

test/image/mocks/geo_fill.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,8 @@
9191
"layout": {
9292
"geo": {
9393
"projection": {
94-
"type": "natural earth"
94+
"type": "natural earth",
95+
"rotation": {"lon": 0}
9596
},
9697
"lonaxis": {
9798
"range": [-80, -65]

0 commit comments

Comments
 (0)