Skip to content

Commit be4e5a9

Browse files
authored
Merge pull request #4197 from plotly/misc-mapbox-fixes
A few mapbox fixes
2 parents 6f95ca1 + 3181cbe commit be4e5a9

File tree

5 files changed

+131
-8
lines changed

5 files changed

+131
-8
lines changed

src/plot_api/plot_api.js

+1
Original file line numberDiff line numberDiff line change
@@ -2657,6 +2657,7 @@ function react(gd, data, layout, config) {
26572657
function addFrames() { return exports.addFrames(gd, frames); }
26582658

26592659
gd = Lib.getGraphDiv(gd);
2660+
helpers.clearPromiseQueue(gd);
26602661

26612662
var oldFullData = gd._fullData;
26622663
var oldFullLayout = gd._fullLayout;

src/plots/mapbox/layers.js

+15-6
Original file line numberDiff line numberDiff line change
@@ -145,17 +145,26 @@ proto.removeLayer = function() {
145145

146146
proto.dispose = function() {
147147
var map = this.subplot.map;
148-
map.removeLayer(this.idLayer);
149-
map.removeSource(this.idSource);
148+
if(map.getLayer(this.idLayer)) map.removeLayer(this.idLayer);
149+
if(map.getSource(this.idSource)) map.removeSource(this.idSource);
150150
};
151151

152152
function isVisible(opts) {
153+
if(!opts.visible) return false;
154+
153155
var source = opts.source;
154156

155-
return opts.visible && (
156-
Lib.isPlainObject(source) ||
157-
((typeof source === 'string' || Array.isArray(source)) && source.length > 0)
158-
);
157+
if(Array.isArray(source) && source.length > 0) {
158+
for(var i = 0; i < source.length; i++) {
159+
if(typeof source[i] !== 'string' || source[i].length === 0) {
160+
return false;
161+
}
162+
}
163+
return true;
164+
}
165+
166+
return Lib.isPlainObject(source) ||
167+
(typeof source === 'string' && source.length > 0);
159168
}
160169

161170
function convertOpts(opts) {

src/traces/choropleth/hover.js

+3-2
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ function makeHoverInfo(pointData, trace, pt) {
5858
if(trace.hovertemplate) return;
5959

6060
var hoverinfo = pt.hi || trace.hoverinfo;
61+
var loc = String(pt.loc);
6162

6263
var parts = (hoverinfo === 'all') ?
6364
attributes.hoverinfo.flags :
@@ -72,10 +73,10 @@ function makeHoverInfo(pointData, trace, pt) {
7273
var text = [];
7374

7475
if(hasIdAsNameLabel) {
75-
pointData.nameOverride = pt.loc;
76+
pointData.nameOverride = loc;
7677
} else {
7778
if(hasName) pointData.nameOverride = trace.name;
78-
if(hasLocation) text.push(pt.loc);
79+
if(hasLocation) text.push(loc);
7980
}
8081

8182
if(hasZ) {

test/jasmine/tests/choroplethmapbox_test.js

+38
Original file line numberDiff line numberDiff line change
@@ -589,6 +589,44 @@ describe('@noCI Test choroplethmapbox hover:', function() {
589589
nums: '10.000',
590590
name: 'PROP::New York',
591591
evtPts: [{location: 'NY', z: 10, pointNumber: 0, curveNumber: 0, properties: {name: 'New York'}}]
592+
}, {
593+
desc: 'with "typeof number" locations[i] and feature id (in *name* label case)',
594+
patch: function() {
595+
var fig = Lib.extendDeep({}, require('@mocks/mapbox_choropleth-raw-geojson.json'));
596+
fig.data.shift();
597+
fig.data[0].locations = [100];
598+
fig.data[0].geojson.id = 100;
599+
return fig;
600+
},
601+
nums: '10',
602+
name: '100',
603+
evtPts: [{location: 100, z: 10, pointNumber: 0, curveNumber: 0}]
604+
}, {
605+
desc: 'with "typeof number" locations[i] and feature id (in *nums* label case)',
606+
patch: function() {
607+
var fig = Lib.extendDeep({}, require('@mocks/mapbox_choropleth-raw-geojson.json'));
608+
fig.data.shift();
609+
fig.data[0].locations = [100];
610+
fig.data[0].geojson.id = 100;
611+
fig.data[0].hoverinfo = 'location+name';
612+
return fig;
613+
},
614+
nums: '100',
615+
name: 'trace 0',
616+
evtPts: [{location: 100, z: 10, pointNumber: 0, curveNumber: 0}]
617+
}, {
618+
desc: 'with "typeof number" locations[i] and feature id (hovertemplate case)',
619+
patch: function() {
620+
var fig = Lib.extendDeep({}, require('@mocks/mapbox_choropleth-raw-geojson.json'));
621+
fig.data.shift();
622+
fig.data[0].locations = [100];
623+
fig.data[0].geojson.id = 100;
624+
fig.data[0].hovertemplate = '### %{location}<extra>%{location} ###</extra>';
625+
return fig;
626+
},
627+
nums: '### 100',
628+
name: '100 ###',
629+
evtPts: [{location: 100, z: 10, pointNumber: 0, curveNumber: 0}]
592630
}];
593631

594632
specs.forEach(function(s) {

test/jasmine/tests/mapbox_test.js

+74
Original file line numberDiff line numberDiff line change
@@ -942,6 +942,80 @@ describe('@noCI, mapbox plots', function() {
942942
.then(done);
943943
}, LONG_TIMEOUT_INTERVAL);
944944

945+
it('@gl should not wedge graph after reacting to invalid layer', function(done) {
946+
Plotly.react(gd, [{type: 'scattermapbox'}], {
947+
mapbox: {
948+
layers: [{ source: 'invalid' }]
949+
}
950+
})
951+
.then(function() {
952+
fail('The above Plotly.react promise should be rejected');
953+
})
954+
.catch(function() {
955+
expect(gd._promises.length).toBe(1, 'has 1 rejected promise in queue');
956+
})
957+
.then(function() {
958+
return Plotly.react(gd, [{type: 'scattermapbox'}], {
959+
mapbox: {
960+
layers: [{
961+
sourcetype: 'vector',
962+
sourcelayer: 'contour',
963+
source: 'mapbox://mapbox.mapbox-terrain-v2'
964+
}]
965+
}
966+
});
967+
})
968+
.then(function() {
969+
expect(gd._promises.length).toBe(0, 'rejected promise has been cleared');
970+
971+
var mapInfo = getMapInfo(gd);
972+
expect(mapInfo.layoutLayers.length).toBe(1, 'one layer');
973+
expect(mapInfo.layoutSources.length).toBe(1, 'one layer source');
974+
})
975+
.catch(failTest)
976+
.then(done);
977+
}, LONG_TIMEOUT_INTERVAL);
978+
979+
it('@gl should not attempt to remove non-existing layer sources', function(done) {
980+
function _assert(msg, exp) {
981+
return function() {
982+
var layerList = gd._fullLayout.mapbox._subplot.layerList;
983+
expect(layerList.length).toBe(exp, msg);
984+
};
985+
}
986+
987+
Plotly.react(gd, [{type: 'scattermapbox'}], {
988+
mapbox: { layers: [{}] }
989+
})
990+
.then(_assert('1 visible:false layer', 1))
991+
.then(function() {
992+
return Plotly.react(gd, [{type: 'scattermapbox'}], {
993+
mapbox: { layers: [] }
994+
});
995+
})
996+
.then(_assert('no layers', 0))
997+
.catch(failTest)
998+
.then(done);
999+
}, LONG_TIMEOUT_INTERVAL);
1000+
1001+
it('@gl should validate layout layer input', function(done) {
1002+
Plotly.newPlot(gd, [{type: 'scattermapbox'}], {
1003+
mapbox: {
1004+
layers: [{
1005+
sourcetype: 'raster',
1006+
source: ['']
1007+
}]
1008+
}
1009+
})
1010+
.then(function() {
1011+
var mapInfo = getMapInfo(gd);
1012+
expect(mapInfo.layoutLayers.length).toBe(0, 'no on-map layer');
1013+
expect(mapInfo.layoutSources.length).toBe(0, 'no map source');
1014+
})
1015+
.catch(failTest)
1016+
.then(done);
1017+
}, LONG_TIMEOUT_INTERVAL);
1018+
9451019
it('@gl should be able to update the access token', function(done) {
9461020
Plotly.relayout(gd, 'mapbox.accesstoken', 'wont-work').catch(function(err) {
9471021
expect(gd._fullLayout.mapbox.accesstoken).toEqual('wont-work');

0 commit comments

Comments
 (0)