Skip to content

Commit

Permalink
Merge pull request #5112 from plotly/new-d3-geo
Browse files Browse the repository at this point in the history
Upgrade to use d3-geo projections module
  • Loading branch information
archmoj authored Jun 30, 2021
2 parents af226c9 + 32aeac6 commit 17c9f0c
Show file tree
Hide file tree
Showing 8 changed files with 145 additions and 564 deletions.
25 changes: 22 additions & 3 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@
]
},
"dependencies": {
"@plotly/d3": "^3.6.1",
"@plotly/d3": "3.7.0",
"@plotly/d3-sankey": "0.7.2",
"@plotly/d3-sankey-circular": "0.33.1",
"@plotly/point-cluster": "^3.1.9",
Expand All @@ -78,6 +78,8 @@
"convex-hull": "^1.0.3",
"country-regex": "^1.1.0",
"d3-force": "^1.2.1",
"d3-geo": "^1.12.1",
"d3-geo-projection": "^2.9.0",
"d3-hierarchy": "^1.1.9",
"d3-interpolate": "^1.4.0",
"d3-time-format": "^2.2.3",
Expand Down
103 changes: 101 additions & 2 deletions src/plots/geo/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

// projection names to d3 function name
exports.projNames = {
// d3.geo.projection
'equirectangular': 'equirectangular',
'mercator': 'mercator',
'orthographic': 'orthographic',
Expand All @@ -24,7 +23,107 @@ exports.projNames = {
'albers usa': 'albersUsa',
'winkel tripel': 'winkel3',
'aitoff': 'aitoff',
'sinusoidal': 'sinusoidal'
'sinusoidal': 'sinusoidal',
/*
// potential projections that could be added to the API
'airy': 'airy',
// 'albers': 'albers',
'armadillo': 'armadillo',
'august': 'august',
'baker': 'baker',
'berghaus': 'berghaus',
'bertin1953': 'bertin1953',
'boggs': 'boggs',
'bonne': 'bonne',
'bottomley': 'bottomley',
'bromley': 'bromley',
// 'chamberlin': 'chamberlin',
'chamberlin africa': 'chamberlinAfrica',
'collignon': 'collignon',
'craig': 'craig',
'craster': 'craster',
'cylindrical equal area': 'cylindricalEqualArea',
'cylindrical stereographic': 'cylindricalStereographic',
'eckert1': 'eckert1',
'eckert2': 'eckert2',
'eckert3': 'eckert3',
'eckert5': 'eckert5',
'eckert6': 'eckert6',
'eisenlohr': 'eisenlohr',
'fahey': 'fahey',
'foucaut': 'foucaut',
'foucaut sinusoidal': 'foucautSinusoidal',
'gilbert': 'gilbert',
'gingery': 'gingery',
'ginzburg4': 'ginzburg4',
'ginzburg5': 'ginzburg5',
'ginzburg6': 'ginzburg6',
'ginzburg8': 'ginzburg8',
'ginzburg9': 'ginzburg9',
'gringorten': 'gringorten',
'guyou': 'guyou',
'hammer retroazimuthal': 'hammerRetroazimuthal',
'healpix': 'healpix',
'hill': 'hill',
'homolosine': 'homolosine',
'hufnagel': 'hufnagel',
'hyperelliptical': 'hyperelliptical',
'lagrange': 'lagrange',
'larrivee': 'larrivee',
'laskowski': 'laskowski',
'littrow': 'littrow',
'loximuthal': 'loximuthal',
// 'modified stereographic': 'modifiedStereographic',
'modified stereographic alaska': 'modifiedStereographicAlaska',
'modified stereographic gs48': 'modifiedStereographicGs48',
'modified stereographic gs50': 'modifiedStereographicGs50',
'modified stereographic miller': 'modifiedStereographicMiller',
'modified stereographic lee': 'modifiedStereographicLee',
'mt flat polar parabolic': 'mtFlatPolarParabolic',
'mt flat polar quartic': 'mtFlatPolarQuartic',
'mt flat polar sinusoidal': 'mtFlatPolarSinusoidal',
'natural earth1': 'naturalEarth1',
'natural earth2': 'naturalEarth2',
'nell hammer': 'nellHammer',
'nicolosi': 'nicolosi',
'patterson': 'patterson',
'polyconic': 'polyconic',
'rectangular polyconic': 'rectangularPolyconic',
'satellite': 'satellite',
'sinu mollweide': 'sinuMollweide',
'times': 'times',
// 'two point azimuthal': 'twoPointAzimuthal',
// 'two point azimuthalUsa': 'twoPointAzimuthalUsa',
// 'two point equidistant': 'twoPointEquidistant',
// 'two point equidistantUsa': 'twoPointEquidistantUsa',
'van der grinten': 'vanDerGrinten',
'van der grinten2': 'vanDerGrinten2',
'van der grinten3': 'vanDerGrinten3',
'van der grinten4': 'vanDerGrinten4',
// 'wagner': 'wagner',
'wagner4': 'wagner4',
'wagner6': 'wagner6',
// 'wagner7': 'wagner7',
'wiechel': 'wiechel',
'winkel3': 'winkel3',
// 'interrupt': 'interrupt',
'interrupted homolosine': 'interruptedHomolosine',
'interrupted sinusoidal': 'interruptedSinusoidal',
'interrupted boggs': 'interruptedBoggs',
'interrupted sinu mollweide': 'interruptedSinuMollweide',
'interrupted mollweide': 'interruptedMollweide',
'interrupted mollweide hemispheres': 'interruptedMollweideHemispheres',
'interrupted quartic authalic': 'interruptedQuarticAuthalic',
'polyhedral butterfly': 'polyhedralButterfly',
'polyhedral collignon': 'polyhedralCollignon',
'polyhedral waterman': 'polyhedralWaterman',
'gringorten quincuncial': 'gringortenQuincuncial',
'peirce quincuncial': 'peirceQuincuncial',
*/
};

// name of the axes
Expand Down
69 changes: 13 additions & 56 deletions src/plots/geo/geo.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@
/* global PlotlyGeoAssets:false */

var d3 = require('@plotly/d3');
var geo = require('d3-geo');
var geoPath = geo.geoPath;
var geoDistance = geo.geoDistance;
var geoProjection = require('d3-geo-projection');

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

require('./projections')(d3);

function Geo(opts) {
this.id = opts.id;
this.graphDiv = opts.graphDiv;
Expand Down Expand Up @@ -247,29 +249,6 @@ proto.updateProjection = function(geoCalcData, fullLayout) {
var s = this.fitScale = projection.scale();
var t = projection.translate();

if(
!isFinite(b[0][0]) || !isFinite(b[0][1]) ||
!isFinite(b[1][0]) || !isFinite(b[1][1]) ||
isNaN(t[0]) || isNaN(t[0])
) {
var attrToUnset = ['fitbounds', 'projection.rotation', 'center', 'lonaxis.range', 'lataxis.range'];
var msg = 'Invalid geo settings, relayout\'ing to default view.';
var updateObj = {};

// clear all attributes that could cause invalid bounds,
// clear viewInitial to update reset-view behavior

for(var i = 0; i < attrToUnset.length; i++) {
updateObj[this.id + '.' + attrToUnset[i]] = null;
}

this.viewInitial = null;

Lib.warn(msg);
gd._promises.push(Registry.call('relayout', gd, updateObj));
return msg;
}

if(geoLayout.fitbounds) {
var b2 = projection.getBounds(makeRangeBox(axLon.range, axLat.range));
var k2 = Math.min(
Expand Down Expand Up @@ -508,7 +487,7 @@ proto.updateFx = function(fullLayout, geoLayout) {
bgRect.on('mousemove', function() {
var lonlat = _this.projection.invert(Lib.getPositionFromD3Event());

if(!lonlat || isNaN(lonlat[0]) || isNaN(lonlat[1])) {
if(!lonlat) {
return dragElement.unhover(gd, d3.event);
}

Expand Down Expand Up @@ -648,9 +627,8 @@ proto.render = function() {
}
};

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

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

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

if(clipAngle) {
var r = projection.rotate();
var angle = d3.geo.distance(lonlat, [-r[0], -r[1]]);
var angle = geoDistance(lonlat, [-r[0], -r[1]]);
var maxAngle = clipAngle * Math.PI / 180;
return angle > maxAngle;
} else {
Expand All @@ -695,38 +677,13 @@ function getProjection(geoLayout) {
};

projection.getPath = function() {
return d3.geo.path().projection(projection);
return geoPath().projection(projection);
};

projection.getBounds = function(object) {
return projection.getPath().bounds(object);
};

// adapted from d3 v4:
// https://github.com/d3/d3-geo/blob/master/src/projection/fit.js
projection.fitExtent = function(extent, object) {
var w = extent[1][0] - extent[0][0];
var h = extent[1][1] - extent[0][1];
var clip = projection.clipExtent && projection.clipExtent();

projection
.scale(150)
.translate([0, 0]);

if(clip) projection.clipExtent(null);

var b = projection.getBounds(object);
var k = Math.min(w / (b[1][0] - b[0][0]), h / (b[1][1] - b[0][1]));
var x = +extent[0][0] + (w - k * (b[1][0] + b[0][0])) / 2;
var y = +extent[0][1] + (h - k * (b[1][1] + b[0][1])) / 2;

if(clip) projection.clipExtent(clip);

return projection
.scale(k * 150)
.translate([x, y]);
};

projection.precision(constants.precision);

if(clipAngle) {
Expand Down
Loading

0 comments on commit 17c9f0c

Please sign in to comment.