Skip to content

Commit 17c9f0c

Browse files
authored
Merge pull request #5112 from plotly/new-d3-geo
Upgrade to use d3-geo projections module
2 parents af226c9 + 32aeac6 commit 17c9f0c

File tree

8 files changed

+145
-564
lines changed

8 files changed

+145
-564
lines changed

package-lock.json

+22-3
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+3-1
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@
6262
]
6363
},
6464
"dependencies": {
65-
"@plotly/d3": "^3.6.1",
65+
"@plotly/d3": "3.7.0",
6666
"@plotly/d3-sankey": "0.7.2",
6767
"@plotly/d3-sankey-circular": "0.33.1",
6868
"@plotly/point-cluster": "^3.1.9",
@@ -78,6 +78,8 @@
7878
"convex-hull": "^1.0.3",
7979
"country-regex": "^1.1.0",
8080
"d3-force": "^1.2.1",
81+
"d3-geo": "^1.12.1",
82+
"d3-geo-projection": "^2.9.0",
8183
"d3-hierarchy": "^1.1.9",
8284
"d3-interpolate": "^1.4.0",
8385
"d3-time-format": "^2.2.3",

src/plots/geo/constants.js

+101-2
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
// projection names to d3 function name
44
exports.projNames = {
5-
// d3.geo.projection
65
'equirectangular': 'equirectangular',
76
'mercator': 'mercator',
87
'orthographic': 'orthographic',
@@ -24,7 +23,107 @@ exports.projNames = {
2423
'albers usa': 'albersUsa',
2524
'winkel tripel': 'winkel3',
2625
'aitoff': 'aitoff',
27-
'sinusoidal': 'sinusoidal'
26+
'sinusoidal': 'sinusoidal',
27+
/*
28+
// potential projections that could be added to the API
29+
30+
'airy': 'airy',
31+
// 'albers': 'albers',
32+
'armadillo': 'armadillo',
33+
'august': 'august',
34+
'baker': 'baker',
35+
'berghaus': 'berghaus',
36+
'bertin1953': 'bertin1953',
37+
'boggs': 'boggs',
38+
'bonne': 'bonne',
39+
'bottomley': 'bottomley',
40+
'bromley': 'bromley',
41+
// 'chamberlin': 'chamberlin',
42+
'chamberlin africa': 'chamberlinAfrica',
43+
'collignon': 'collignon',
44+
'craig': 'craig',
45+
'craster': 'craster',
46+
'cylindrical equal area': 'cylindricalEqualArea',
47+
'cylindrical stereographic': 'cylindricalStereographic',
48+
'eckert1': 'eckert1',
49+
'eckert2': 'eckert2',
50+
'eckert3': 'eckert3',
51+
'eckert5': 'eckert5',
52+
'eckert6': 'eckert6',
53+
'eisenlohr': 'eisenlohr',
54+
'fahey': 'fahey',
55+
'foucaut': 'foucaut',
56+
'foucaut sinusoidal': 'foucautSinusoidal',
57+
'gilbert': 'gilbert',
58+
'gingery': 'gingery',
59+
'ginzburg4': 'ginzburg4',
60+
'ginzburg5': 'ginzburg5',
61+
'ginzburg6': 'ginzburg6',
62+
'ginzburg8': 'ginzburg8',
63+
'ginzburg9': 'ginzburg9',
64+
'gringorten': 'gringorten',
65+
'guyou': 'guyou',
66+
'hammer retroazimuthal': 'hammerRetroazimuthal',
67+
'healpix': 'healpix',
68+
'hill': 'hill',
69+
'homolosine': 'homolosine',
70+
'hufnagel': 'hufnagel',
71+
'hyperelliptical': 'hyperelliptical',
72+
'lagrange': 'lagrange',
73+
'larrivee': 'larrivee',
74+
'laskowski': 'laskowski',
75+
'littrow': 'littrow',
76+
'loximuthal': 'loximuthal',
77+
// 'modified stereographic': 'modifiedStereographic',
78+
'modified stereographic alaska': 'modifiedStereographicAlaska',
79+
'modified stereographic gs48': 'modifiedStereographicGs48',
80+
'modified stereographic gs50': 'modifiedStereographicGs50',
81+
'modified stereographic miller': 'modifiedStereographicMiller',
82+
'modified stereographic lee': 'modifiedStereographicLee',
83+
'mt flat polar parabolic': 'mtFlatPolarParabolic',
84+
'mt flat polar quartic': 'mtFlatPolarQuartic',
85+
'mt flat polar sinusoidal': 'mtFlatPolarSinusoidal',
86+
'natural earth1': 'naturalEarth1',
87+
'natural earth2': 'naturalEarth2',
88+
'nell hammer': 'nellHammer',
89+
'nicolosi': 'nicolosi',
90+
'patterson': 'patterson',
91+
'polyconic': 'polyconic',
92+
'rectangular polyconic': 'rectangularPolyconic',
93+
'satellite': 'satellite',
94+
'sinu mollweide': 'sinuMollweide',
95+
'times': 'times',
96+
// 'two point azimuthal': 'twoPointAzimuthal',
97+
// 'two point azimuthalUsa': 'twoPointAzimuthalUsa',
98+
// 'two point equidistant': 'twoPointEquidistant',
99+
// 'two point equidistantUsa': 'twoPointEquidistantUsa',
100+
'van der grinten': 'vanDerGrinten',
101+
'van der grinten2': 'vanDerGrinten2',
102+
'van der grinten3': 'vanDerGrinten3',
103+
'van der grinten4': 'vanDerGrinten4',
104+
// 'wagner': 'wagner',
105+
'wagner4': 'wagner4',
106+
'wagner6': 'wagner6',
107+
// 'wagner7': 'wagner7',
108+
'wiechel': 'wiechel',
109+
'winkel3': 'winkel3',
110+
111+
// 'interrupt': 'interrupt',
112+
'interrupted homolosine': 'interruptedHomolosine',
113+
'interrupted sinusoidal': 'interruptedSinusoidal',
114+
'interrupted boggs': 'interruptedBoggs',
115+
'interrupted sinu mollweide': 'interruptedSinuMollweide',
116+
'interrupted mollweide': 'interruptedMollweide',
117+
'interrupted mollweide hemispheres': 'interruptedMollweideHemispheres',
118+
'interrupted quartic authalic': 'interruptedQuarticAuthalic',
119+
120+
'polyhedral butterfly': 'polyhedralButterfly',
121+
'polyhedral collignon': 'polyhedralCollignon',
122+
'polyhedral waterman': 'polyhedralWaterman',
123+
124+
'gringorten quincuncial': 'gringortenQuincuncial',
125+
'peirce quincuncial': 'peirceQuincuncial',
126+
*/
28127
};
29128

30129
// name of the axes

src/plots/geo/geo.js

+13-56
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@
33
/* global PlotlyGeoAssets:false */
44

55
var d3 = require('@plotly/d3');
6+
var geo = require('d3-geo');
7+
var geoPath = geo.geoPath;
8+
var geoDistance = geo.geoDistance;
9+
var geoProjection = require('d3-geo-projection');
610

711
var Registry = require('../../registry');
812
var Lib = require('../../lib');
@@ -25,8 +29,6 @@ var geoUtils = require('../../lib/geo_location_utils');
2529
var topojsonUtils = require('../../lib/topojson_utils');
2630
var topojsonFeature = require('topojson-client').feature;
2731

28-
require('./projections')(d3);
29-
3032
function Geo(opts) {
3133
this.id = opts.id;
3234
this.graphDiv = opts.graphDiv;
@@ -247,29 +249,6 @@ proto.updateProjection = function(geoCalcData, fullLayout) {
247249
var s = this.fitScale = projection.scale();
248250
var t = projection.translate();
249251

250-
if(
251-
!isFinite(b[0][0]) || !isFinite(b[0][1]) ||
252-
!isFinite(b[1][0]) || !isFinite(b[1][1]) ||
253-
isNaN(t[0]) || isNaN(t[0])
254-
) {
255-
var attrToUnset = ['fitbounds', 'projection.rotation', 'center', 'lonaxis.range', 'lataxis.range'];
256-
var msg = 'Invalid geo settings, relayout\'ing to default view.';
257-
var updateObj = {};
258-
259-
// clear all attributes that could cause invalid bounds,
260-
// clear viewInitial to update reset-view behavior
261-
262-
for(var i = 0; i < attrToUnset.length; i++) {
263-
updateObj[this.id + '.' + attrToUnset[i]] = null;
264-
}
265-
266-
this.viewInitial = null;
267-
268-
Lib.warn(msg);
269-
gd._promises.push(Registry.call('relayout', gd, updateObj));
270-
return msg;
271-
}
272-
273252
if(geoLayout.fitbounds) {
274253
var b2 = projection.getBounds(makeRangeBox(axLon.range, axLat.range));
275254
var k2 = Math.min(
@@ -508,7 +487,7 @@ proto.updateFx = function(fullLayout, geoLayout) {
508487
bgRect.on('mousemove', function() {
509488
var lonlat = _this.projection.invert(Lib.getPositionFromD3Event());
510489

511-
if(!lonlat || isNaN(lonlat[0]) || isNaN(lonlat[1])) {
490+
if(!lonlat) {
512491
return dragElement.unhover(gd, d3.event);
513492
}
514493

@@ -648,9 +627,8 @@ proto.render = function() {
648627
}
649628
};
650629

651-
// Helper that wraps d3.geo[/* projection name /*]() which:
630+
// Helper that wraps d3[geo + /* Projection name /*]() which:
652631
//
653-
// - adds 'fitExtent' (available in d3 v4)
654632
// - adds 'getPath', 'getBounds' convenience methods
655633
// - scopes logic related to 'clipAngle'
656634
// - adds 'isLonLatOverEdges' method
@@ -663,7 +641,11 @@ function getProjection(geoLayout) {
663641
var projLayout = geoLayout.projection;
664642
var projType = projLayout.type;
665643

666-
var projection = d3.geo[constants.projNames[projType]]();
644+
var projName = constants.projNames[projType];
645+
// uppercase the first letter and add geo to the start of method name
646+
projName = 'geo' + projName.charAt(0).toUpperCase() + projName.slice(1);
647+
var projFn = geo[projName] || geoProjection[projName];
648+
var projection = projFn();
667649

668650
var clipAngle = geoLayout._isClipped ?
669651
constants.lonaxisSpan[projType] / 2 :
@@ -686,7 +668,7 @@ function getProjection(geoLayout) {
686668

687669
if(clipAngle) {
688670
var r = projection.rotate();
689-
var angle = d3.geo.distance(lonlat, [-r[0], -r[1]]);
671+
var angle = geoDistance(lonlat, [-r[0], -r[1]]);
690672
var maxAngle = clipAngle * Math.PI / 180;
691673
return angle > maxAngle;
692674
} else {
@@ -695,38 +677,13 @@ function getProjection(geoLayout) {
695677
};
696678

697679
projection.getPath = function() {
698-
return d3.geo.path().projection(projection);
680+
return geoPath().projection(projection);
699681
};
700682

701683
projection.getBounds = function(object) {
702684
return projection.getPath().bounds(object);
703685
};
704686

705-
// adapted from d3 v4:
706-
// https://github.com/d3/d3-geo/blob/master/src/projection/fit.js
707-
projection.fitExtent = function(extent, object) {
708-
var w = extent[1][0] - extent[0][0];
709-
var h = extent[1][1] - extent[0][1];
710-
var clip = projection.clipExtent && projection.clipExtent();
711-
712-
projection
713-
.scale(150)
714-
.translate([0, 0]);
715-
716-
if(clip) projection.clipExtent(null);
717-
718-
var b = projection.getBounds(object);
719-
var k = Math.min(w / (b[1][0] - b[0][0]), h / (b[1][1] - b[0][1]));
720-
var x = +extent[0][0] + (w - k * (b[1][0] + b[0][0])) / 2;
721-
var y = +extent[0][1] + (h - k * (b[1][1] + b[0][1])) / 2;
722-
723-
if(clip) projection.clipExtent(clip);
724-
725-
return projection
726-
.scale(k * 150)
727-
.translate([x, y]);
728-
};
729-
730687
projection.precision(constants.precision);
731688

732689
if(clipAngle) {

0 commit comments

Comments
 (0)