Skip to content

Commit aa4510b

Browse files
committed
Fix geo projections losing their D3 default rotation
Geo.updateProjection unconditionally called projection.rotate([-rotation.lon, -rotation.lat, rotation.roll]), so the default projection.rotation attributes ([0, 0, 0]) discarded the rotation the d3 projection factory ships with. Six projections are affected (albers, bertin1953, gringorten and peirce quincuncial, sinu mollweide, wiechel). Capture the factory rotation when the projection wrapper is created and compose the user rotation on top of it. Projections whose d3 default is the identity are bit-for-bit unchanged; an explicit rotation now rotates relative to the projection's canonical orientation instead of replacing it. Fixes #7949
1 parent 95bfea1 commit aa4510b

2 files changed

Lines changed: 47 additions & 1 deletion

File tree

src/plots/geo/geo.js

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -304,7 +304,11 @@ proto.updateProjection = function(geoCalcData, fullLayout) {
304304
// set 'pre-fit' projection
305305
projection
306306
.center([center.lon - rotation.lon, center.lat - rotation.lat])
307-
.rotate([-rotation.lon, -rotation.lat, rotation.roll])
307+
.rotate([
308+
-rotation.lon + projection.defaultRotation[0],
309+
-rotation.lat + projection.defaultRotation[1],
310+
rotation.roll + projection.defaultRotation[2]
311+
])
308312
.parallels(projLayout.parallels);
309313

310314
// fit projection 'scale' and 'translate' to set lon/lat ranges
@@ -716,6 +720,12 @@ function getProjection(geoLayout) {
716720
projName = 'geo' + Lib.titleCase(projName);
717721
var projFn = geo[projName] || geoProjection[projName];
718722
var projection = projFn();
723+
// Capture the rotation the projection factory ships with (identity for
724+
// most projections, but e.g. albers defaults to [96, 0, 0]) so that the
725+
// default projection.rotation attributes do not discard the canonical
726+
// orientation. See #7949.
727+
var projDefaultRotation = typeof projection.rotate === 'function' ?
728+
projection.rotate() : [0, 0, 0];
719729

720730
var clipAngle =
721731
geoLayout._isSatellite ? Math.acos(1 / projLayout.distance) * 180 / Math.PI :
@@ -754,6 +764,8 @@ function getProjection(geoLayout) {
754764
return projection.getPath().bounds(object);
755765
};
756766

767+
projection.defaultRotation = projDefaultRotation;
768+
757769
projection.precision(constants.precision);
758770

759771
if(geoLayout._isSatellite) {

test/jasmine/tests/geo_test.js

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2876,3 +2876,37 @@ describe('plotly_relayouting', function() {
28762876
});
28772877
});
28782878
});
2879+
2880+
describe('Test geo projection D3 default rotation (#7949)', function() {
2881+
var gd;
2882+
2883+
beforeEach(function() { gd = createGraphDiv(); });
2884+
afterEach(destroyGraphDiv);
2885+
2886+
function subplot() { return gd._fullLayout.geo._subplot; }
2887+
2888+
it('preserves the projection factory rotation at default projection.rotation', function(done) {
2889+
Plotly.newPlot(gd, [{ type: 'scattergeo', lon: [], lat: [] }], {
2890+
geo: { projection: { type: 'albers' } }
2891+
}).then(function() {
2892+
// d3-geo albers ships with rotation [96, 0, 0]; plotly's default
2893+
// projection.rotation attributes must not discard it
2894+
expect(subplot().projection.defaultRotation).toEqual([96, 0, 0]);
2895+
expect(subplot().projection.rotate()).toEqual([96, 0, 0]);
2896+
}).then(function() {
2897+
return Plotly.relayout(gd, { 'geo.projection.rotation.lon': 10 });
2898+
}).then(function() {
2899+
// user rotation composes on top of the factory rotation
2900+
expect(subplot().projection.rotate()[0]).toBe(96 - 10);
2901+
}).then(done, done.fail);
2902+
});
2903+
2904+
it('keeps identity-rotation projections exactly as before', function(done) {
2905+
Plotly.newPlot(gd, [{ type: 'scattergeo', lon: [], lat: [] }], {
2906+
geo: { projection: { type: 'mercator' } }
2907+
}).then(function() {
2908+
expect(subplot().projection.defaultRotation).toEqual([0, 0, 0]);
2909+
expect(subplot().projection.rotate()).toEqual([0, 0, 0]);
2910+
}).then(done, done.fail);
2911+
});
2912+
});

0 commit comments

Comments
 (0)