PageSourceSearch

https://mareas.inocar.mil.ec/marea_regional_GRASP/plotly-binding-4.10.4/plotly.js

js inocar.mil.ec collected 2026-09-24 18:30:50 UTC 33,758 bytes, 941 lines download raw bytes

1
2HTMLWidgets.widget({
3  name: "plotly",
4  type: "output",
5
6  initialize: function(el, width, height) {
7    return {};
8  },
9
10  resize: function(el, width, height, instance) {
11    if (instance.autosize) {
12      var width = instance.width || width;
13      var height = instance.height || height;
14      Plotly.relayout(el.id, {width: width, height: height});
15    }
16  },  
17  
18  renderValue: function(el, x, instance) {
19    
20    // Plotly.relayout() mutates the plot input object, so make sure to 
21    // keep a reference to the user-supplied width/height *before*
22    // we call Plotly.plot();
23    var lay = x.layout || {};
24    instance.width = lay.width;
25    instance.height = lay.height;
26    instance.autosize = lay.autosize || true;
27    
28    /* 
29    / 'inform the world' about highlighting options this is so other
30    / crosstalk libraries have a chance to respond to special settings 
31    / such as persistent selection. 
32    / AFAIK, leaflet is the only library with such intergration
33    / https://github.com/rstudio/leaflet/pull/346/files#diff-ad0c2d51ce5fdf8c90c7395b102f4265R154
34    */
35    var ctConfig = crosstalk.var('plotlyCrosstalkOpts').set(x.highlight);
36      
37    if (typeof(window) !== "undefined") {
38      // make sure plots don't get created outside the network (for on-prem)
39      window.PLOTLYENV = window.PLOTLYENV || {};
40      window.PLOTLYENV.BASE_URL = x.base_url;
41      
42      // Enable persistent selection when shift key is down
43      // https://stackoverflow.com/questions/1828613/check-if-a-key-is-down
44      var persistOnShift = function(e) {
45        if (!e) window.event;
46        if (e.shiftKey) { 
47          x.highlight.persistent = true; 
48          x.highlight.persistentShift = true;
49        } else {
50          x.highlight.persistent = false; 
51          x.highlight.persistentShift = false;
52        }
53      };
54      
55      // Only relevant if we haven't forced persistent mode at command line
56      if (!x.highlight.persistent) {
57        window.onmousemove = persistOnShift;
58      }
59    }
60
61    var graphDiv = document.getElementById(el.id);
62    
63    // TODO: move the control panel injection strategy inside here...
64    HTMLWidgets.addPostRenderHandler(function() {
65      
66      // lower the z-index of the modebar to prevent it from highjacking hover
67      // (TODO: do this via CSS?)
68      // https://github.com/ropensci/plotly/issues/956
69      // https://www.w3schools.com/jsref/prop_style_zindex.asp
70      var modebars = document.querySelectorAll(".js-plotly-plot .plotly .modebar");
71      for (var i = 0; i < modebars.length; i++) {
72        modebars[i].style.zIndex = 1;
73      }
74    });
75      
76      // inject a "control panel" holding selectize/dynamic color widget(s)
77    if ((x.selectize || x.highlight.dynamic) && !instance.plotly) {
78      var flex = document.createElement("div");
79      flex.class = "plotly-crosstalk-control-panel";
80      flex.style = "display: flex; flex-wrap: wrap";
81      
82      // inject the colourpicker HTML container into the flexbox
83      if (x.highlight.dynamic) {
84        var pickerDiv = document.createElement("div");
85        
86        var pickerInput = document.createElement("input");
87        pickerInput.id = el.id + "-colourpicker";
88        pickerInput.placeholder = "asdasd";
89        
90        var pickerLabel = document.createElement("label");
91        pickerLabel.for = pickerInput.id;
92        pickerLabel.innerHTML = "Brush color&nbsp;&nbsp;";
93        
94        pickerDiv.appendChild(pickerLabel);
95        pickerDiv.appendChild(pickerInput);
96        flex.appendChild(pickerDiv);
97      }
98      
99      // inject selectize HTML containers (one for every crosstalk group)
100      if (x.selectize) {
101        var ids = Object.keys(x.selectize);
102        
103        for (var i = 0; i < ids.length; i++) {
104          var container = document.createElement("div");
105          container.id = ids[i];
106          container.style = "width: 80%; height: 10%";
107          container.class = "form-group crosstalk-input-plotly-highlight";
108          
109          var label = document.createElement("label");
110          label.for = ids[i];
111          label.innerHTML = x.selectize[ids[i]].group;
112          label.class = "control-label";
113          
114          var selectDiv = document.createElement("div");
115          var select = document.createElement("select");
116          select.multiple = true;
117          
118          selectDiv.appendChild(select);
119          container.appendChild(label);
120          container.appendChild(selectDiv);
121          flex.appendChild(container);
122        }
123      }
124      
125      // finally, insert the flexbox inside the htmlwidget container,
126      // but before the plotly graph div
127      graphDiv.parentElement.insertBefore(flex, graphDiv);
128      
129      if (x.highlight.dynamic) {
130        var picker = $("#" + pickerInput.id);
131        var colors = x.highlight.color || [];
132        // TODO: let users specify options?
133        var opts = {
134          value: colors[0],
135          showColour: "both",
136          palette: "limited",
137          allowedCols: colors.join(" "),
138          width: "20%",
139          height: "10%"
140        };
141        picker.colourpicker({changeDelay: 0});
142        picker.colourpicker("settings", opts);
143        picker.colourpicker("value", opts.value);
144        // inform crosstalk about a change in the current selection colour
145        var grps = x.highlight.ctGroups || [];
146        for (var i = 0; i < grps.length; i++) {
147          crosstalk.group(grps[i]).var('plotlySelectionColour')
148            .set(picker.colourpicker('value'));
149        }
150        picker.on("change", function() {
151          for (var i = 0; i < grps.length; i++) {
152            crosstalk.group(grps[i]).var('plotlySelectionColour')
153              .set(picker.colourpicker('value'));
154          }
155        });
156      }
157    }
158    
159    // if no plot exists yet, create one with a particular configuration
160    if (!instance.plotly) {
161      
162      var plot = Plotly.newPlot(graphDiv, x);
163      instance.plotly = true;
164      
165    } else if (x.layout.transition) {
166      
167      var plot = Plotly.react(graphDiv, x);
168    
169    } else {
170      
171      // this is essentially equivalent to Plotly.newPlot(), but avoids creating 
172      // a new webgl context
173      // https://github.com/plotly/plotly.js/blob/2b24f9def901831e61282076cf3f835598d56f0e/src/plot_api/plot_api.js#L531-L532
174
175      // TODO: restore crosstalk selections?
176      Plotly.purge(graphDiv);
177      // TODO: why is this necessary to get crosstalk working?
178      graphDiv.data = undefined;
179      graphDiv.layout = undefined;
180      var plot = Plotly.newPlot(graphDiv, x);
181    }
182    
183    // Trigger plotly.js calls defined via `plotlyProxy()`
184    plot.then(function() {
185      if (HTMLWidgets.shinyMode) {
186        Shiny.addCustomMessageHandler("plotly-calls", function(msg) {
187          var gd = document.getElementById(msg.id);
188          if (!gd) {
189            throw new Error("Couldn't find plotly graph with id: " + msg.id);
190          }
191          // This isn't an official plotly.js method, but it's the only current way to 
192          // change just the configuration of a plot 
193          // https://community.plot.ly/t/update-config-function/9057
194          if (msg.method == "reconfig") {
195            Plotly.react(gd, gd.data, gd.layout, msg.args);
196            return;
197          }
198          if (!Plotly[msg.method]) {
199            throw new Error("Unknown method " + msg.method);
200          }
201          var args = [gd].concat(msg.args);
202          Plotly[msg.method].apply(null, args);
203        });
204      }
205      
206      // plotly's mapbox API doesn't currently support setting bounding boxes
207      // https://www.mapbox.com/mapbox-gl-js/example/fitbounds/
208      // so we do this manually...
209      // TODO: make sure this triggers on a redraw and relayout as well as on initial draw
210      var mapboxIDs = graphDiv._fullLayout._subplots.mapbox || [];
211      for (var i = 0; i < mapboxIDs.length; i++) {
212        var id = mapboxIDs[i];
213        var mapOpts = x.layout[id] || {};
214        var args = mapOpts._fitBounds || {};
215        if (!args) {
216          continue;
217        }
218        var mapObj = graphDiv._fullLayout[id]._subplot.map;
219        mapObj.fitBounds(args.bounds, args.options);
220      }
221      
222    });
223    
224    // Attach attributes (e.g., "key", "z") to plotly event data
225    function eventDataWithKey(eventData) {
226      if (eventData === undefined || !eventData.hasOwnProperty("points")) {
227        return null;
228      }
229      return eventData.points.map(function(pt) {
230        var obj = {
231          curveNumber: pt.curveNumber, 
232          pointNumber: pt.pointNumber, 
233          x: pt.x,
234          y: pt.y
235        };
236        
237        // If 'z' is reported with the event data, then use it!
238        if (pt.hasOwnProperty("z")) {
239          obj.z = pt.z;
240        }
241        
242        if (pt.hasOwnProperty("customdata")) {
243          obj.customdata = pt.customdata;
244        }
245        
246        /* 
247          TL;DR: (I think) we have to select the graph div (again) to attach keys...
248          
249          Why? Remember that crosstalk will dynamically add/delete traces 
250          (see traceManager.prototype.updateSelection() below)
251          For this reason, we can't simply grab keys from x.data (like we did previously)
252          Moreover, we can't use _fullData, since that doesn't include 
253          unofficial attributes. It's true that click/hover events fire with 
254          pt.data, but drag events don't...
255        */
256        var gd = document.getElementById(el.id);
257        var trace = gd.data[pt.curveNumber];
258        
259        if (!trace._isSimpleKey) {
260          var attrsToAttach = ["key"];
261        } else {
262          // simple keys fire the whole key
263          obj.key = trace.key;
264          var attrsToAttach = [];
265        }
266        
267        for (var i = 0; i < attrsToAttach.length; i++) {
268          var attr = trace[attrsToAttach[i]];
269          if (Array.isArray(attr)) {
270            if (typeof pt.pointNumber === "number") {
271              obj[attrsToAttach[i]] = attr[pt.pointNumber];
272            } else if (Array.isArray(pt.pointNumber)) {
273              obj[attrsToAttach[i]] = attr[pt.pointNumber[0]][pt.pointNumber[1]];
274            } else if (Array.isArray(pt.pointNumbers)) {
275              obj[attrsToAttach[i]] = pt.pointNumbers.map(function(idx) { return attr[idx]; });
276            }
277          }
278        }
279        return obj;
280      });
281    }
282    
283    
284    var legendEventData = function(d) {
285      // if legendgroup is not relevant just return the trace
286      var trace = d.data[d.curveNumber];
287      if (!trace.legendgroup) return trace;
288      
289      // if legendgroup was specified, return all traces that match the group
290      var legendgrps = d.data.map(function(trace){ return trace.legendgroup; });
291      var traces = [];
292      for (i = 0; i < legendgrps.length; i++) {
293        if (legendgrps[i] == trace.legendgroup) {
294          traces.push(d.data[i]);
295        }
296      }
297      
298      return traces;
299    };
300
301    
302    // send user input event data to shiny
303    if (HTMLWidgets.shinyMode && Shiny.setInputValue) {
304      
305      // Some events clear other input values
306      // TODO: always register these?
307      var eventClearMap = {
308        plotly_deselect: ["plotly_selected", "plotly_selecting", "plotly_brushed", "plotly_brushing", "plotly_click"],
309        plotly_unhover: ["plotly_hover"],
310        plotly_doubleclick: ["plotly_click"]
311      };
312    
313      Object.keys(eventClearMap).map(function(evt) {
314        graphDiv.on(evt, function() {
315          var inputsToClear = eventClearMap[evt];
316          inputsToClear.map(function(input) {
317            Shiny.setInputValue(input + "-" + x.source, null, {priority: "event"});
318          });
319        });
320      });
321      
322      var eventDataFunctionMap = {
323        plotly_click: eventDataWithKey,
324        plotly_sunburstclick: eventDataWithKey,
325        plotly_hover: eventDataWithKey,
326        plotly_unhover: eventDataWithKey,
327        // If 'plotly_selected' has already been fired, and you click
328        // on the plot afterwards, this event fires `undefined`?!?
329        // That might be considered a plotly.js bug, but it doesn't make 
330        // sense for this input change to occur if `d` is falsy because,
331        // even in the empty selection case, `d` is truthy (an object),
332        // and the 'plotly_deselect' event will reset this input
333        plotly_selected: function(d) { if (d) { return eventDataWithKey(d); } },
334        plotly_selecting: function(d) { if (d) { return eventDataWithKey(d); } },
335        plotly_brushed: function(d) {
336          if (d) { return d.range ? d.range : d.lassoPoints; }
337        },
338        plotly_brushing: function(d) {
339          if (d) { return d.range ? d.range : d.lassoPoints; }
340        },
341        plotly_legendclick: legendEventData,
342        plotly_legenddoubleclick: legendEventData,
343        plotly_clickannotation: function(d) { return d.fullAnnotation }
344      };
345      
346      var registerShinyValue = function(event) {
347        var eventDataPreProcessor = eventDataFunctionMap[event] || function(d) { return d ? d : el.id };
348        // some events are unique to the R package
349        var plotlyJSevent = (event == "plotly_brushed") ? "plotly_selected" : (event == "plotly_brushing") ? "plotly_selecting" : event;
350        // register the event
351        graphDiv.on(plotlyJSevent, function(d) {
352          Shiny.setInputValue(
353            event + "-" + x.source,
354            JSON.stringify(eventDataPreProcessor(d)),
355            {priority: "event"}
356          );
357        });
358      }
359    
360      var shinyEvents = x.shinyEvents || [];
361      shinyEvents.map(registerShinyValue);
362    }
363    
364    // Given an array of {curveNumber: x, pointNumber: y} objects,
365    // return a hash of {
366    //   set1: {value: [key1, key2, ...], _isSimpleKey: false}, 
367    //   set2: {value: [key3, key4, ...], _isSimpleKey: false}
368    // }
369    function pointsToKeys(points) {
370      var keysBySet = {};
371      for (var i = 0; i < points.length; i++) {
372        
373        var trace = graphDiv.data[points[i].curveNumber];
374        if (!trace.key || !trace.set) {
375          continue;
376        }
377        
378        // set defaults for this keySet
379        // note that we don't track the nested property (yet) since we always 
380        // emit the union -- http://cpsievert.github.io/talks/20161212b/#21
381        keysBySet[trace.set] = keysBySet[trace.set] || {
382          value: [],
383          _isSimpleKey: trace._isSimpleKey
384        };
385        
386        // Use pointNumber by default, but aggregated traces should emit pointNumbers
387        var ptNum = points[i].pointNumber;
388        var hasPtNum = typeof ptNum === "number";
389        var ptNum = hasPtNum ? ptNum : points[i].pointNumbers;
390        
391        // selecting a point of a "simple" trace means: select the 
392        // entire key attached to this trace, which is useful for,
393        // say clicking on a fitted line to select corresponding observations 
394        var key = trace._isSimpleKey ? trace.key : Array.isArray(ptNum) ? ptNum.map(function(idx) { return trace.key[idx]; }) : trace.key[ptNum];
395        // http://stackoverflow.com/questions/10865025/merge-flatten-an-array-of-arrays-in-javascript
396        var keyFlat = trace._isNestedKey ? [].concat.apply([], key) : key;
397        
398        // TODO: better to only add new values?
399        keysBySet[trace.set].value = keysBySet[trace.set].value.concat(keyFlat);
400      }
401      
402      return keysBySet;
403    }
404    
405    
406    x.highlight.color = x.highlight.color || [];
407    // make sure highlight color is an array
408    if (!Array.isArray(x.highlight.color)) {
409      x.highlight.color = [x.highlight.color];
410    }
411
412    var traceManager = new TraceManager(graphDiv, x.highlight);
413
414    // Gather all *unique* sets.
415    var allSets = [];
416    for (var curveIdx = 0; curveIdx < x.data.length; curveIdx++) {
417      var newSet = x.data[curveIdx].set;
418      if (newSet) {
419        if (allSets.indexOf(newSet) === -1) {
420          allSets.push(newSet);
421        }
422      }
423    }
424
425    // register event listeners for all sets
426    for (var i = 0; i < allSets.length; i++) {
427      
428      var set = allSets[i];
429      var selection = new crosstalk.SelectionHandle(set);
430      var filter = new crosstalk.FilterHandle(set);
431      
432      var filterChange = function(e) {
433        removeBrush(el);
434        traceManager.updateFilter(set, e.value);
435      };
436      filter.on("change", filterChange);
437      
438      
439      var selectionChange = function(e) {
440        
441        // Workaround for 'plotly_selected' now firing previously selected
442        // points (in addition to new ones) when holding shift key. In our case,
443        // we just want the new keys 
444        if (x.highlight.on === "plotly_selected" && x.highlight.persistentShift) {
445          // https://stackoverflow.com/questions/1187518/how-to-get-the-difference-between-two-arrays-in-javascript
446          Array.prototype.diff = function(a) {
447              return this.filter(function(i) {return a.indexOf(i) < 0;});
448          };
449          e.value = e.value.diff(e.oldValue);
450        }
451        
452        // array of "event objects" tracking the selection history
453        // this is used to avoid adding redundant selections
454        var selectionHistory = crosstalk.var("plotlySelectionHistory").get() || [];
455        
456        // Construct an event object "defining" the current event. 
457        var event = {
458          receiverID: traceManager.gd.id,
459          plotlySelectionColour: crosstalk.group(set).var("plotlySelectionColour").get()
460        };
461        event[set] = e.value;
462        // TODO: is there a smarter way to check object equality?
463        if (selectionHistory.length > 0) {
464          var ev = JSON.stringify(event);
465          for (var i = 0; i < selectionHistory.length; i++) {
466            var sel = JSON.stringify(selectionHistory[i]);
467            if (sel == ev) {
468              return;
469            }
470          }
471        }
472        
473        // accumulate history for persistent selection
474        if (!x.highlight.persistent) {
475          selectionHistory = [event];
476        } else {
477          selectionHistory.push(event);
478        }
479        crosstalk.var("plotlySelectionHistory").set(selectionHistory);
480        
481        // do the actual updating of traces, frames, and the selectize widget
482        traceManager.updateSelection(set, e.value);
483        // https://github.com/selectize/selectize.js/blob/master/docs/api.md#methods_items
484        if (x.selectize) {
485          if (!x.highlight.persistent || e.value === null) {
486            selectize.clear(true);
487          }
488          selectize.addItems(e.value, true);
489          selectize.close();
490        }
491      }
492      selection.on("change", selectionChange);
493      
494      // Set a crosstalk variable selection value, triggering an update
495      var turnOn = function(e) {
496        if (e) {
497          var selectedKeys = pointsToKeys(e.points);
498          // Keys are group names, values are array of selected keys from group.
499          for (var set in selectedKeys) {
500            if (selectedKeys.hasOwnProperty(set)) {
501              selection.set(selectedKeys[set].value, {sender: el});
502            }
503          }
504        }
505      };
506      if (x.highlight.debounce > 0) {
507        turnOn = debounce(turnOn, x.highlight.debounce);
508      }
509      graphDiv.on(x.highlight.on, turnOn);
510      
511      graphDiv.on(x.highlight.off, function turnOff(e) {
512        // remove any visual clues
513        removeBrush(el);
514        // remove any selection history
515        crosstalk.var("plotlySelectionHistory").set(null);
516        // trigger the actual removal of selection traces
517        selection.set(null, {sender: el});
518      });
519          
520      // register a callback for selectize so that there is bi-directional
521      // communication between the widget and direct manipulation events
522      if (x.selectize) {
523        var selectizeID = Object.keys(x.selectize)[i];
524        var options = x.selectize[selectizeID];
525        var first = [{value: "", label: "(All)"}];
526        var opts = $.extend({
527            options: first.concat(options.items),
528            searchField: "label",
529            valueField: "value",
530            labelField: "label",
531            maxItems: 50
532          }, 
533          options
534        );
535        var select = $("#" + selectizeID).find("select")[0];
536        var selectize = $(select).selectize(opts)[0].selectize;
537        // NOTE: this callback is triggered when *directly* altering 
538        // dropdown items
539        selectize.on("change", function() {
540          var currentItems = traceManager.groupSelections[set] || [];
541          if (!x.highlight.persistent) {
542            removeBrush(el);
543            for (var i = 0; i < currentItems.length; i++) {
544              selectize.removeItem(currentItems[i], true);
545            }
546          }
547          var newItems = selectize.items.filter(function(idx) { 
548            return currentItems.indexOf(idx) < 0;
549          });
550          if (newItems.length > 0) {
551            traceManager.updateSelection(set, newItems);
552          } else {
553            // Item has been removed...
554            // TODO: this logic won't work for dynamically changing palette 
555            traceManager.updateSelection(set, null);
556            traceManager.updateSelection(set, selectize.items);
557          }
558        });
559      }
560    } // end of selectionChange
561    
562  } // end of renderValue
563}); // end of widget definition
564
565/**
566 * @param graphDiv The Plotly graph div
567 * @param highlight An object with options for updating selection(s)
568 */
569function TraceManager(graphDiv, highlight) {
570  // The Plotly graph div
571  this.gd = graphDiv;
572
573  // Preserve the original data.
574  // TODO: try using Lib.extendFlat() as done in  
575  // https://github.com/plotly/plotly.js/pull/1136 
576  this.origData = JSON.parse(JSON.stringify(graphDiv.data));
577  
578  // avoid doing this over and over
579  this.origOpacity = [];
580  for (var i = 0; i < this.origData.length; i++) {
581    this.origOpacity[i] = this.origData[i].opacity === 0 ? 0 : (this.origData[i].opacity || 1);
582  }
583
584  // key: group name, value: null or array of keys representing the
585  // most recently received selection for that group.
586  this.groupSelections = {};
587  
588  // selection parameters (e.g., transient versus persistent selection)
589  this.highlight = highlight;
590}
591
592TraceManager.prototype.close = function() {
593  // TODO: Unhook all event handlers
594};
595
596TraceManager.prototype.updateFilter = function(group, keys) {
597
598  if (typeof(keys) === "undefined" || keys === null) {
599    
600    this.gd.data = JSON.parse(JSON.stringify(this.origData));
601    
602  } else {
603  
604    var traces = [];
605    for (var i = 0; i < this.origData.length; i++) {
606      var trace = this.origData[i];
607      if (!trace.key || trace.set !== group) {
608        continue;
609      }
610      var matchFunc = getMatchFunc(trace);
611      var matches = matchFunc(trace.key, keys);
612      
613      if (matches.length > 0) {
614        if (!trace._isSimpleKey) {
615          // subsetArrayAttrs doesn't mutate trace (it makes a modified clone)
616          trace = subsetArrayAttrs(trace, matches);
617        }
618        traces.push(trace);
619      }
620    }
621    this.gd.data = traces;
622  }
623  
624  Plotly.redraw(this.gd);
625  
626  // NOTE: we purposely do _not_ restore selection(s), since on filter,
627  // axis likely will update, changing the pixel -> data mapping, leading 
628  // to a likely mismatch in the brush outline and highlighted marks
629  
630};
631
632TraceManager.prototype.updateSelection = function(group, keys) {
633  
634  if (keys !== null && !Array.isArray(keys)) {
635    throw new Error("Invalid keys argument; null or array expected");
636  }
637  
638  // if selection has been cleared, or if this is transient
639  // selection, delete the "selection traces"
640  var nNewTraces = this.gd.data.length - this.origData.length;
641  if (keys === null || !this.highlight.persistent && nNewTraces > 0) {
642    var tracesToRemove = [];
643    for (var i = 0; i < this.gd.data.length; i++) {
644      if (this.gd.data[i]._isCrosstalkTrace) tracesToRemove.push(i);
645    }
646    Plotly.deleteTraces(this.gd, tracesToRemove);
647    this.groupSelections[group] = keys;
648  } else {
649    // add to the groupSelection, rather than overwriting it
650    // TODO: can this be removed?
651    this.groupSelections[group] = this.groupSelections[group] || [];
652    for (var i = 0; i < keys.length; i++) {
653      var k = keys[i];
654      if (this.groupSelections[group].indexOf(k) < 0) {
655        this.groupSelections[group].push(k);
656      }
657    }
658  }
659  
660  if (keys === null) {
661    
662    Plotly.restyle(this.gd, {"opacity": this.origOpacity});
663    
664  } else if (keys.length >= 1) {
665    
666    // placeholder for new "selection traces"
667    var traces = [];
668    // this variable is set in R/highlight.R
669    var selectionColour = crosstalk.group(group).var("plotlySelectionColour").get() || 
670      this.highlight.color[0];
671
672    for (var i = 0; i < this.origData.length; i++) {
673      // TODO: try using Lib.extendFlat() as done in  
674      // https://github.com/plotly/plotly.js/pull/1136 
675      var trace = JSON.parse(JSON.stringify(this.gd.data[i]));
676      if (!trace.key || trace.set !== group) {
677        continue;
678      }
679      // Get sorted array of matching indices in trace.key
680      var matchFunc = getMatchFunc(trace);
681      var matches = matchFunc(trace.key, keys);
682      
683      if (matches.length > 0) {
684        // If this is a "simple" key, that means select the entire trace
685        if (!trace._isSimpleKey) {
686          trace = subsetArrayAttrs(trace, matches);
687        }
688        // reach into the full trace object so we can properly reflect the 
689        // selection attributes in every view
690        var d = this.gd._fullData[i];
691        
692        /* 
693        / Recursively inherit selection attributes from various sources, 
694        / in order of preference:
695        /  (1) official plotly.js selected attribute
696        /  (2) highlight(selected = attrs_selected(...))
697        */
698        // TODO: it would be neat to have a dropdown to dynamically specify these!
699        $.extend(true, trace, this.highlight.selected);
700        
701        // if it is defined, override color with the "dynamic brush color""
702        if (d.marker) {
703          trace.marker = trace.marker || {};
704          trace.marker.color =  selectionColour || trace.marker.color || d.marker.color;
705        }
706        if (d.line) {
707          trace.line = trace.line || {};
708          trace.line.color =  selectionColour || trace.line.color || d.line.color;
709        }
710        if (d.textfont) {
711          trace.textfont = trace.textfont || {};
712          trace.textfont.color =  selectionColour || trace.textfont.color || d.textfont.color;
713        }
714        if (d.fillcolor) {
715          // TODO: should selectionColour inherit alpha from the existing fillcolor?
716          trace.fillcolor = selectionColour || trace.fillcolor || d.fillcolor;
717        }
718        // attach a sensible name/legendgroup
719        trace.name = trace.name || keys.join("<br />");
720        trace.legendgroup = trace.legendgroup || keys.join("<br />");
721        
722        // keep track of mapping between this new trace and the trace it targets
723        // (necessary for updating frames to reflect the selection traces)
724        trace._originalIndex = i;
725        trace._newIndex = this.gd._fullData.length + traces.length;
726        trace._isCrosstalkTrace = true;
727        traces.push(trace);
728      }
729    }
730    
731    if (traces.length > 0) {
732      
733      Plotly.addTraces(this.gd, traces).then(function(gd) {
734        // incrementally add selection traces to frames
735        // (this is heavily inspired by Plotly.Plots.modifyFrames() 
736        // in src/plots/plots.js)
737        var _hash = gd._transitionData._frameHash;
738        var _frames = gd._transitionData._frames || [];
739        
740        for (var i = 0; i < _frames.length; i++) {
741          
742          // add to _frames[i].traces *if* this frame references selected trace(s)
743          var newIndices = [];
744          for (var j = 0; j < traces.length; j++) {
745            var tr = traces[j];
746            if (_frames[i].traces.indexOf(tr._originalIndex) > -1) {
747              newIndices.push(tr._newIndex);
748              _frames[i].traces.push(tr._newIndex);
749            }
750          }
751          
752          // nothing to do...
753          if (newIndices.length === 0) {
754            continue;
755          }
756          
757          var ctr = 0;
758          var nFrameTraces = _frames[i].data.length;
759          
760          for (var j = 0; j < nFrameTraces; j++) {
761            var frameTrace = _frames[i].data[j];
762            if (!frameTrace.key || frameTrace.set !== group) {
763              continue;
764            }
765            
766            var matchFunc = getMatchFunc(frameTrace);
767            var matches = matchFunc(frameTrace.key, keys);
768            
769            if (matches.length > 0) {
770              if (!trace._isSimpleKey) {
771                frameTrace = subsetArrayAttrs(frameTrace, matches);
772              }
773              var d = gd._fullData[newIndices[ctr]];
774              if (d.marker) {
775                frameTrace.marker = d.marker;
776              }
777              if (d.line) {
778                frameTrace.line = d.line;
779              }
780              if (d.textfont) {
781                frameTrace.textfont = d.textfont;
782              }
783              ctr = ctr + 1;
784              _frames[i].data.push(frameTrace);
785            }
786          }
787          
788          // update gd._transitionData._frameHash
789          _hash[_frames[i].name] = _frames[i];
790        }
791      
792      });
793      
794      // dim traces that have a set matching the set of selection sets
795      var tracesToDim = [],
796          opacities = [],
797          sets = Object.keys(this.groupSelections),
798          n = this.origData.length;
799          
800      for (var i = 0; i < n; i++) {
801        var opacity = this.origOpacity[i] || 1;
802        // have we already dimmed this trace? Or is this even worth doing?
803        if (opacity !== this.gd._fullData[i].opacity || this.highlight.opacityDim === 1) {
804          continue;
805        }
806        // is this set an element of the set of selection sets?
807        var matches = findMatches(sets, [this.gd.data[i].set]);
808        if (matches.length) {
809          tracesToDim.push(i);
810          opacities.push(opacity * this.highlight.opacityDim);
811        }
812      }
813      
814      if (tracesToDim.length > 0) {
815        Plotly.restyle(this.gd, {"opacity": opacities}, tracesToDim);
816        // turn off the selected/unselected API
817        Plotly.restyle(this.gd, {"selectedpoints": null});
818      }
819      
820    }
821    
822  }
823};
824
825/* 
826Note: in all of these match functions, we assume needleSet (i.e. the selected keys)
827is a 1D (or flat) array. The real difference is the meaning of haystack.
828findMatches() does the usual thing you'd expect for 
829linked brushing on a scatterplot matrix. findSimpleMatches() returns a match iff 
830haystack is a subset of the needleSet. findNestedMatches() returns 
831*/
832
833function getMatchFunc(trace) {
834  return (trace._isNestedKey) ? findNestedMatches : 
835    (trace._isSimpleKey) ? findSimpleMatches : findMatches;
836}
837
838// find matches for "flat" keys
839function findMatches(haystack, needleSet) {
840  var matches = [];
841  haystack.forEach(function(obj, i) {
842    if (obj === null || needleSet.indexOf(obj) >= 0) {
843      matches.push(i);
844    }
845  });
846  return matches;
847}
848
849// find matches for "simple" keys
850function findSimpleMatches(haystack, needleSet) {
851  var match = haystack.every(function(val) {
852    return val === null || needleSet.indexOf(val) >= 0;
853  });
854  // yes, this doesn't make much sense other than conforming 
855  // to the output type of the other match functions
856  return (match) ? [0] : []
857}
858
859// find matches for a "nested" haystack (2D arrays)
860function findNestedMatches(haystack, needleSet) {
861  var matches = [];
862  for (var i = 0; i < haystack.length; i++) {
863    var hay = haystack[i];
864    var match = hay.every(function(val) { 
865      return val === null || needleSet.indexOf(val) >= 0; 
866    });
867    if (match) {
868      matches.push(i);
869    }
870  }
871  return matches;
872}
873
874function isPlainObject(obj) {
875  return (
876    Object.prototype.toString.call(obj) === '[object Object]' &&
877    Object.getPrototypeOf(obj) === Object.prototype
878  );
879}
880
881function subsetArrayAttrs(obj, indices) {
882  var newObj = {};
883  Object.keys(obj).forEach(function(k) {
884    var val = obj[k];
885
886    if (k.charAt(0) === "_") {
887      newObj[k] = val;
888    } else if (k === "transforms" && Array.isArray(val)) {
889      newObj[k] = val.map(function(transform) {
890        return subsetArrayAttrs(transform, indices);
891      });
892    } else if (k === "colorscale" && Array.isArray(val)) {
893      newObj[k] = val;
894    } else if (isPlainObject(val)) {
895      newObj[k] = subsetArrayAttrs(val, indices);
896    } else if (Array.isArray(val)) {
897      newObj[k] = subsetArray(val, indices);
898    } else {
899      newObj[k] = val;
900    }
901  });
902  return newObj;
903}
904
905function subsetArray(arr, indices) {
906  var result = [];
907  for (var i = 0; i < indices.length; i++) {
908    result.push(arr[indices[i]]);
909  }
910  return result;
911}
912
913// Convenience function for removing plotly's brush 
914function removeBrush(el) {
915  var outlines = el.querySelectorAll(".select-outline");
916  for (var i = 0; i < outlines.length; i++) {
917    outlines[i].remove();
918  }
919}
920
921
922// https://davidwalsh.name/javascript-debounce-function
923
924// Returns a function, that, as long as it continues to be invoked, will not
925// be triggered. The function will be called after it stops being called for
926// N milliseconds. If `immediate` is passed, trigger the function on the
927// leading edge, instead of the trailing.
928function debounce(func, wait, immediate) {
929	var timeout;
930	return function() {
931		var context = this, args = arguments;
932		var later = function() {
933			timeout = null;
934			if (!immediate) func.apply(context, args);
935		};
936		var callNow = immediate && !timeout;
937		clearTimeout(timeout);
938		timeout = setTimeout(later, wait);
939		if (callNow) func.apply(context, args);
940	};
941};

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.