@@ -48,6 +48,7 @@ function Mapbox(gd, id) {
4848 this . styleObj = null ;
4949 this . traceHash = { } ;
5050 this . layerList = [ ] ;
51+ this . belowLookup = { } ;
5152}
5253
5354var proto = Mapbox . prototype ;
@@ -126,6 +127,7 @@ proto.createMap = function(calcData, fullLayout, resolve, reject) {
126127 promises = promises . concat ( self . fetchMapData ( calcData , fullLayout ) ) ;
127128
128129 Promise . all ( promises ) . then ( function ( ) {
130+ self . fillBelowLookup ( calcData , fullLayout ) ;
129131 self . updateData ( calcData ) ;
130132 self . updateLayout ( fullLayout ) ;
131133 self . resolveOnRender ( resolve ) ;
@@ -191,12 +193,94 @@ proto.updateMap = function(calcData, fullLayout, resolve, reject) {
191193 promises = promises . concat ( self . fetchMapData ( calcData , fullLayout ) ) ;
192194
193195 Promise . all ( promises ) . then ( function ( ) {
196+ self . fillBelowLookup ( calcData , fullLayout ) ;
194197 self . updateData ( calcData ) ;
195198 self . updateLayout ( fullLayout ) ;
196199 self . resolveOnRender ( resolve ) ;
197200 } ) . catch ( reject ) ;
198201} ;
199202
203+ proto . fillBelowLookup = function ( calcData , fullLayout ) {
204+ var opts = fullLayout [ this . id ] ;
205+ var layers = opts . layers ;
206+ var i , val ;
207+
208+ var belowLookup = this . belowLookup = { } ;
209+ var hasTraceAtTop = false ;
210+
211+ for ( i = 0 ; i < calcData . length ; i ++ ) {
212+ var trace = calcData [ i ] [ 0 ] . trace ;
213+ var _module = trace . _module ;
214+
215+ if ( typeof trace . below === 'string' ) {
216+ val = trace . below ;
217+ } else if ( _module . getBelow ) {
218+ // 'smart' default that depend the map's base layers
219+ val = _module . getBelow ( trace , this ) ;
220+ }
221+
222+ if ( val === '' ) {
223+ hasTraceAtTop = true ;
224+ }
225+
226+ belowLookup [ 'trace-' + trace . uid ] = val || '' ;
227+ }
228+
229+ for ( i = 0 ; i < layers . length ; i ++ ) {
230+ var item = layers [ i ] ;
231+
232+ if ( typeof item . below === 'string' ) {
233+ val = item . below ;
234+ } else if ( hasTraceAtTop ) {
235+ // if one or more trace(s) set `below:''` and
236+ // layers[i].below is unset,
237+ // place layer below traces
238+ val = 'traces' ;
239+ } else {
240+ val = '' ;
241+ }
242+
243+ belowLookup [ 'layout-' + i ] = val ;
244+ }
245+
246+ // N.B. If multiple layers have the 'below' value,
247+ // we must clear the stashed 'below' field in order
248+ // to make `traceHash[k].update()` and `layerList[i].update()`
249+ // remove/add the all those layers to have preserve
250+ // the correct layer ordering
251+ var val2list = { } ;
252+ var k , id ;
253+
254+ for ( k in belowLookup ) {
255+ val = belowLookup [ k ] ;
256+ if ( val2list [ val ] ) {
257+ val2list [ val ] . push ( k ) ;
258+ } else {
259+ val2list [ val ] = [ k ] ;
260+ }
261+ }
262+
263+ for ( val in val2list ) {
264+ var list = val2list [ val ] ;
265+ if ( list . length > 1 ) {
266+ for ( i = 0 ; i < list . length ; i ++ ) {
267+ k = list [ i ] ;
268+ if ( k . indexOf ( 'trace-' ) === 0 ) {
269+ id = k . split ( 'trace-' ) [ 1 ] ;
270+ if ( this . traceHash [ id ] ) {
271+ this . traceHash [ id ] . below = null ;
272+ }
273+ } else if ( k . indexOf ( 'layout-' ) === 0 ) {
274+ id = k . split ( 'layout-' ) [ 1 ] ;
275+ if ( this . layerList [ id ] ) {
276+ this . layerList [ id ] . below = null ;
277+ }
278+ }
279+ }
280+ }
281+ }
282+ } ;
283+
200284var traceType2orderIndex = {
201285 choroplethmapbox : 0 ,
202286 densitymapbox : 1 ,
@@ -207,6 +291,10 @@ proto.updateData = function(calcData) {
207291 var traceHash = this . traceHash ;
208292 var traceObj , trace , i , j ;
209293
294+ // Need to sort here by trace type here,
295+ // in case traces with different `type` have the same
296+ // below value, but sorting we ensure that
297+ // e.g. choroplethmapbox traces will be below scattermapbox traces
210298 var calcDataSorted = calcData . slice ( ) . sort ( function ( a , b ) {
211299 return (
212300 traceType2orderIndex [ a [ 0 ] . trace . type ] -
@@ -598,6 +686,40 @@ proto.setOptions = function(id, methodName, opts) {
598686 }
599687} ;
600688
689+ proto . getMapLayers = function ( ) {
690+ return this . map . getStyle ( ) . layers ;
691+ } ;
692+
693+ // convenience wrapper that first check in 'below' references
694+ // a layer that exist and then add the layer to the map,
695+ proto . addLayer = function ( opts , below ) {
696+ var map = this . map ;
697+
698+ if ( typeof below === 'string' ) {
699+ if ( below === '' ) {
700+ map . addLayer ( opts , below ) ;
701+ return ;
702+ }
703+
704+ var mapLayers = this . getMapLayers ( ) ;
705+ for ( var i = 0 ; i < mapLayers . length ; i ++ ) {
706+ if ( below === mapLayers [ i ] . id ) {
707+ map . addLayer ( opts , below ) ;
708+ return ;
709+ }
710+ }
711+
712+ Lib . warn ( [
713+ 'Trying to add layer with *below* value' ,
714+ below ,
715+ 'referencing a layer that does not exist' ,
716+ 'or that does not yet exist.'
717+ ] . join ( ' ' ) ) ;
718+ }
719+
720+ map . addLayer ( opts ) ;
721+ } ;
722+
601723// convenience method to project a [lon, lat] array to pixel coords
602724proto . project = function ( v ) {
603725 return this . map . project ( new mapboxgl . LngLat ( v [ 0 ] , v [ 1 ] ) ) ;
0 commit comments