From 1d2ce2da93d296340f078ff23f6b1f389a8cec32 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Etienne=20T=C3=A9treault-Pinard?= Date: Tue, 26 Sep 2017 11:45:27 -0400 Subject: [PATCH] handle invalid geo setting that lead to invalid bounds - in that case, call Plotly.relayout to reset all attributes that could cause the invalid bounds. --- src/plots/geo/geo.js | 39 ++++++++++++++++------ test/jasmine/tests/geo_test.js | 59 ++++++++++++++++++++++++++++++++++ 2 files changed, 89 insertions(+), 9 deletions(-) diff --git a/src/plots/geo/geo.js b/src/plots/geo/geo.js index 1fea63020df..000b4b72014 100644 --- a/src/plots/geo/geo.js +++ b/src/plots/geo/geo.js @@ -41,6 +41,7 @@ function Geo(opts) { this.topojson = null; this.projection = null; + this.viewInitial = null; this.fitScale = null; this.bounds = null; this.midPt = null; @@ -119,6 +120,9 @@ proto.fetchTopojson = function() { proto.update = function(geoCalcData, fullLayout) { var geoLayout = fullLayout[this.id]; + var hasInvalidBounds = this.updateProjection(fullLayout, geoLayout); + if(hasInvalidBounds) return; + // important: maps with choropleth traces have a different layer order this.hasChoropleth = false; for(var i = 0; i < geoCalcData.length; i++) { @@ -128,9 +132,13 @@ proto.update = function(geoCalcData, fullLayout) { } } - this.updateProjection(fullLayout, geoLayout); + if(!this.viewInitial) { + this.saveViewInitial(geoLayout); + } + this.updateBaseLayers(fullLayout, geoLayout); this.updateDims(fullLayout, geoLayout); + this.updateFx(fullLayout, geoLayout); Plots.generalUpdatePerTraceModule(this, geoCalcData, geoLayout); @@ -142,7 +150,6 @@ proto.update = function(geoCalcData, fullLayout) { var choroplethLayer = this.layers.backplot.select('.choroplethlayer'); this.dataPaths.choropleth = choroplethLayer.selectAll('path'); - this.updateFx(fullLayout, geoLayout); this.render(); }; @@ -186,9 +193,23 @@ proto.updateProjection = function(fullLayout, geoLayout) { !isFinite(b[1][0]) || !isFinite(b[1][1]) || isNaN(t[0]) || isNaN(t[0]) ) { - Lib.warn('Invalid geo settings'); + var gd = this.graphDiv; + var attrToUnset = ['projection.rotation', 'center', 'lonaxis.range', 'lataxis.range']; + var msg = 'Invalid geo settings, relayout\'ing to default view.'; + var updateObj = {}; - // TODO fallback to default ??? + // clear all attribute 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(Plotly.relayout(gd, updateObj)); + return msg; } // px coordinates of view mid-point, @@ -472,26 +493,27 @@ proto.makeFramework = function() { exponentformat: 'B' }; Axes.setConvert(_this.mockAxis, fullLayout); +}; - var geoLayout = fullLayout[_this.id]; +proto.saveViewInitial = function(geoLayout) { var center = geoLayout.center || {}; var projLayout = geoLayout.projection; var rotation = projLayout.rotation || {}; if(geoLayout._isScoped) { - _this.viewInitial = { + this.viewInitial = { 'center.lon': center.lon, 'center.lat': center.lat, 'projection.scale': projLayout.scale }; } else if(geoLayout._isClipped) { - _this.viewInitial = { + this.viewInitial = { 'projection.scale': projLayout.scale, 'projection.rotation.lon': rotation.lon, 'projection.rotation.lat': rotation.lat }; } else { - _this.viewInitial = { + this.viewInitial = { 'center.lon': center.lon, 'center.lat': center.lat, 'projection.scale': projLayout.scale, @@ -574,7 +596,6 @@ function getProjection(geoLayout) { var maxAngle = clipAngle * Math.PI / 180; return angle > maxAngle; } else { - // TODO does this ever happen?? return false; } }; diff --git a/test/jasmine/tests/geo_test.js b/test/jasmine/tests/geo_test.js index bead6ba0d92..3045f9df56b 100644 --- a/test/jasmine/tests/geo_test.js +++ b/test/jasmine/tests/geo_test.js @@ -1231,6 +1231,65 @@ describe('Test geo interactions', function() { .then(done); }); + it('should plot to scope defaults when user setting lead to NaN map bounds', function(done) { + var gd = createGraphDiv(); + + spyOn(Lib, 'warn'); + + Plotly.plot(gd, [{ + type: 'scattergeo', + lon: [0], + lat: [0] + }], { + geo: { + projection: { + type: 'kavrayskiy7', + rotation: { + lat: 38.794799, + lon: -81.622334, + } + }, + center: { + lat: -81 + }, + lataxis: { + range: [38.794799, 45.122292] + }, + lonaxis: { + range: [-82.904731, -81.622334] + } + }, + width: 700, + heigth: 500 + }) + .then(function() { + var geoLayout = gd._fullLayout.geo; + var geo = geoLayout._subplot; + + expect(geoLayout.projection.rotation).toEqual({ + lon: 0, lat: 0, roll: 0, + }); + expect(geoLayout.center).toEqual({ + lon: 0, lat: 0 + }); + expect(geoLayout.lonaxis.range).toEqual([-180, 180]); + expect(geoLayout.lataxis.range).toEqual([-90, 90]); + + expect(geo.viewInitial).toEqual({ + 'projection.rotation.lon': 0, + 'center.lon': 0, + 'center.lat': 0, + 'projection.scale': 1 + }); + + expect(Lib.warn).toHaveBeenCalledTimes(1); + expect(Lib.warn).toHaveBeenCalledWith( + 'Invalid geo settings, relayout\'ing to default view.' + ); + }) + .catch(fail) + .then(done); + }); }); describe('Test event property of interactions on a geo plot:', function() {