Skip to content

Commit 776ddca

Browse files
authored
Merge pull request #2574 from plotly/cartesian-trace-deletion
Better cartesian trace updates and removal
2 parents c0b38da + f771310 commit 776ddca

File tree

31 files changed

+709
-591
lines changed

31 files changed

+709
-591
lines changed

src/components/rangeslider/draw.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -448,8 +448,8 @@ function drawRangePlot(rangeSlider, gd, axisOpts, opts) {
448448

449449
Plots.supplyDefaults(mockFigure);
450450

451-
var xa = mockFigure._fullLayout.xaxis,
452-
ya = mockFigure._fullLayout[oppAxisName];
451+
var xa = mockFigure._fullLayout.xaxis;
452+
var ya = mockFigure._fullLayout[oppAxisName];
453453

454454
var plotinfo = {
455455
id: id,

src/plot_api/subroutines.js

+5-33
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@ var Titles = require('../components/titles');
2121
var ModeBar = require('../components/modebar');
2222

2323
var Axes = require('../plots/cartesian/axes');
24-
var cartesianConstants = require('../plots/cartesian/constants');
2524
var alignmentConstants = require('../constants/alignment');
2625
var axisConstraints = require('../plots/cartesian/constraints');
2726
var enforceAxisConstraints = axisConstraints.enforce;
@@ -199,15 +198,9 @@ exports.lsInner = function(gd) {
199198

200199
Drawing.setClipUrl(plotinfo.plot, plotClipId);
201200

202-
for(i = 0; i < cartesianConstants.traceLayerClasses.length; i++) {
203-
var layer = cartesianConstants.traceLayerClasses[i];
204-
if(layer !== 'scatterlayer' && layer !== 'barlayer') {
205-
plotinfo.plot.selectAll('g.' + layer).call(Drawing.setClipUrl, layerClipId);
206-
}
207-
}
208-
209201
// stash layer clipId value (null or same as clipId)
210-
// to DRY up Drawing.setClipUrl calls downstream
202+
// to DRY up Drawing.setClipUrl calls on trace-module and trace layers
203+
// downstream
211204
plotinfo.layerClipId = layerClipId;
212205

213206
// figure out extra axis line and tick positions as needed
@@ -495,34 +488,13 @@ exports.doCamera = function(gd) {
495488
exports.drawData = function(gd) {
496489
var fullLayout = gd._fullLayout;
497490
var calcdata = gd.calcdata;
498-
var rangesliderContainers = fullLayout._infolayer.selectAll('g.rangeslider-container');
499491
var i;
500492

501-
// in case of traces that were heatmaps or contour maps
502-
// previously, remove them and their colorbars explicitly
493+
// remove old colorbars explicitly
503494
for(i = 0; i < calcdata.length; i++) {
504495
var trace = calcdata[i][0].trace;
505-
var isVisible = (trace.visible === true);
506-
var uid = trace.uid;
507-
508-
if(!isVisible || !Registry.traceIs(trace, '2dMap')) {
509-
var query = (
510-
'.hm' + uid +
511-
',.contour' + uid +
512-
',#clip' + uid
513-
);
514-
515-
fullLayout._paper
516-
.selectAll(query)
517-
.remove();
518-
519-
rangesliderContainers
520-
.selectAll(query)
521-
.remove();
522-
}
523-
524-
if(!isVisible || !trace._module.colorbar) {
525-
fullLayout._infolayer.selectAll('.cb' + uid).remove();
496+
if(trace.visible !== true || !trace._module.colorbar) {
497+
fullLayout._infolayer.select('.cb' + trace.uid).remove();
526498
}
527499
}
528500

src/plots/cartesian/constants.js

+4-3
Original file line numberDiff line numberDiff line change
@@ -61,15 +61,16 @@ module.exports = {
6161
DFLTRANGEY: [-1, 4],
6262

6363
// Layers to keep trace types in the right order
64+
// N.B. each 'unique' plot method must have its own layer
6465
traceLayerClasses: [
65-
'imagelayer',
66-
'maplayer',
66+
'heatmaplayer',
67+
'contourcarpetlayer', 'contourlayer',
6768
'barlayer',
6869
'carpetlayer',
6970
'violinlayer',
7071
'boxlayer',
7172
'ohlclayer',
72-
'scatterlayer'
73+
'scattercarpetlayer', 'scatterlayer'
7374
],
7475

7576
layerValue2layerClass: {

src/plots/cartesian/index.js

+87-99
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,13 @@
1010
'use strict';
1111

1212
var d3 = require('d3');
13+
14+
var Registry = require('../../registry');
1315
var Lib = require('../../lib');
1416
var Plots = require('../plots');
15-
var getModuleCalcData = require('../get_data').getModuleCalcData;
17+
var Drawing = require('../../components/drawing');
1618

19+
var getModuleCalcData = require('../get_data').getModuleCalcData;
1720
var axisIds = require('./axis_ids');
1821
var constants = require('./constants');
1922
var xmlnsNamespaces = require('../../constants/xmlns_namespaces');
@@ -129,23 +132,20 @@ exports.plot = function(gd, traces, transitionOpts, makeOnCompleteCallback) {
129132
// traces are removed
130133
if(!Array.isArray(traces)) {
131134
traces = [];
132-
133-
for(i = 0; i < calcdata.length; i++) {
134-
traces.push(i);
135-
}
135+
for(i = 0; i < calcdata.length; i++) traces.push(i);
136136
}
137137

138138
for(i = 0; i < subplots.length; i++) {
139-
var subplot = subplots[i],
140-
subplotInfo = fullLayout._plots[subplot];
139+
var subplot = subplots[i];
140+
var subplotInfo = fullLayout._plots[subplot];
141141

142142
// Get all calcdata for this subplot:
143143
var cdSubplot = [];
144144
var pcd;
145145

146146
for(var j = 0; j < calcdata.length; j++) {
147-
var cd = calcdata[j],
148-
trace = cd[0].trace;
147+
var cd = calcdata[j];
148+
var trace = cd[0].trace;
149149

150150
// Skip trace if whitelist provided and it's not whitelisted:
151151
// if (Array.isArray(traces) && traces.indexOf(i) === -1) continue;
@@ -182,115 +182,110 @@ exports.plot = function(gd, traces, transitionOpts, makeOnCompleteCallback) {
182182
};
183183

184184
function plotOne(gd, plotinfo, cdSubplot, transitionOpts, makeOnCompleteCallback) {
185-
var fullLayout = gd._fullLayout,
186-
modules = fullLayout._modules;
187-
188-
// remove old traces, then redraw everything
189-
//
190-
// TODO: scatterlayer is manually excluded from this since it knows how
191-
// to update instead of fully removing and redrawing every time. The
192-
// remaining plot traces should also be able to do this. Once implemented,
193-
// we won't need this - which should sometimes be a big speedup.
194-
if(plotinfo.plot) {
195-
plotinfo.plot.selectAll('g:not(.scatterlayer):not(.ohlclayer)').selectAll('g.trace').remove();
185+
var traceLayerClasses = constants.traceLayerClasses;
186+
var fullLayout = gd._fullLayout;
187+
var modules = fullLayout._modules;
188+
var _module, cdModuleAndOthers, cdModule;
189+
190+
var layerData = [];
191+
192+
for(var i = 0; i < modules.length; i++) {
193+
_module = modules[i];
194+
var name = _module.name;
195+
196+
if(Registry.modules[name].categories.svg) {
197+
var className = (_module.layerName || name + 'layer');
198+
var plotMethod = _module.plot;
199+
200+
// plot all traces of this type on this subplot at once
201+
cdModuleAndOthers = getModuleCalcData(cdSubplot, plotMethod);
202+
cdModule = cdModuleAndOthers[0];
203+
// don't need to search the found traces again - in fact we need to NOT
204+
// so that if two modules share the same plotter we don't double-plot
205+
cdSubplot = cdModuleAndOthers[1];
206+
207+
if(cdModule.length) {
208+
layerData.push({
209+
i: traceLayerClasses.indexOf(className),
210+
className: className,
211+
plotMethod: plotMethod,
212+
cdModule: cdModule
213+
});
214+
}
215+
}
196216
}
197217

198-
// plot all traces for each module at once
199-
for(var j = 0; j < modules.length; j++) {
200-
var _module = modules[j];
218+
layerData.sort(function(a, b) { return a.i - b.i; });
219+
220+
var layers = plotinfo.plot.selectAll('g.mlayer')
221+
.data(layerData, function(d) { return d.className; });
201222

202-
// skip over non-cartesian trace modules
203-
if(!_module.plot || _module.basePlotModule.name !== 'cartesian') continue;
223+
layers.enter().append('g')
224+
.attr('class', function(d) { return d.className; })
225+
.classed('mlayer', true);
204226

205-
// plot all traces of this type on this subplot at once
206-
var cdModuleAndOthers = getModuleCalcData(cdSubplot, _module);
207-
var cdModule = cdModuleAndOthers[0];
208-
// don't need to search the found traces again - in fact we need to NOT
209-
// so that if two modules share the same plotter we don't double-plot
210-
cdSubplot = cdModuleAndOthers[1];
227+
layers.exit().remove();
211228

212-
_module.plot(gd, plotinfo, cdModule, transitionOpts, makeOnCompleteCallback);
229+
layers.order();
230+
231+
layers.each(function(d) {
232+
var sel = d3.select(this);
233+
var className = d.className;
234+
235+
d.plotMethod(
236+
gd, plotinfo, d.cdModule, sel,
237+
transitionOpts, makeOnCompleteCallback
238+
);
239+
240+
// layers that allow `cliponaxis: false`
241+
if(className !== 'scatterlayer' && className !== 'barlayer') {
242+
Drawing.setClipUrl(sel, plotinfo.layerClipId);
243+
}
244+
});
245+
246+
// call Scattergl.plot separately
247+
if(fullLayout._has('scattergl')) {
248+
_module = Registry.getModule('scattergl');
249+
cdModule = getModuleCalcData(cdSubplot, _module)[0];
250+
_module.plot(gd, plotinfo, cdModule);
213251
}
214252
}
215253

216254
exports.clean = function(newFullData, newFullLayout, oldFullData, oldFullLayout) {
217-
var oldModules = oldFullLayout._modules || [];
218-
var newModules = newFullLayout._modules || [];
219255
var oldPlots = oldFullLayout._plots || {};
220-
221-
var hadScatter, hasScatter;
222-
var hadOHLC, hasOHLC;
223-
var hadGl, hasGl;
224-
var i, k, subplotInfo, moduleName;
256+
var newPlots = newFullLayout._plots || {};
257+
var oldSubplotList = oldFullLayout._subplots || {};
258+
var plotinfo;
259+
var i, k;
225260

226261
// when going from a large splom graph to something else,
227262
// we need to clear <g subplot> so that the new cartesian subplot
228263
// can have the correct layer ordering
229264
if(oldFullLayout._hasOnlyLargeSploms && !newFullLayout._hasOnlyLargeSploms) {
230265
for(k in oldPlots) {
231-
subplotInfo = oldPlots[k];
232-
if(subplotInfo.plotgroup) subplotInfo.plotgroup.remove();
266+
plotinfo = oldPlots[k];
267+
if(plotinfo.plotgroup) plotinfo.plotgroup.remove();
233268
}
234269
}
235270

236-
for(i = 0; i < oldModules.length; i++) {
237-
moduleName = oldModules[i].name;
238-
if(moduleName === 'scatter') hadScatter = true;
239-
else if(moduleName === 'scattergl') hadGl = true;
240-
else if(moduleName === 'ohlc') hadOHLC = true;
241-
}
242-
243-
for(i = 0; i < newModules.length; i++) {
244-
moduleName = newModules[i].name;
245-
if(moduleName === 'scatter') hasScatter = true;
246-
else if(moduleName === 'scattergl') hasGl = true;
247-
else if(moduleName === 'ohlc') hasOHLC = true;
248-
}
249-
250-
var layersToEmpty = [];
251-
if(hadScatter && !hasScatter) layersToEmpty.push('g.scatterlayer');
252-
if(hadOHLC && !hasOHLC) layersToEmpty.push('g.ohlclayer');
253-
254-
if(layersToEmpty.length) {
255-
for(var layeri = 0; layeri < layersToEmpty.length; layeri++) {
256-
for(k in oldPlots) {
257-
subplotInfo = oldPlots[k];
258-
if(subplotInfo.plot) {
259-
subplotInfo.plot.select(layersToEmpty[layeri])
260-
.selectAll('g.trace')
261-
.remove();
262-
}
263-
}
264-
265-
oldFullLayout._infolayer.selectAll('g.rangeslider-container')
266-
.select(layersToEmpty[layeri])
267-
.selectAll('g.trace')
268-
.remove();
269-
}
270-
}
271+
var hadGl = (oldFullLayout._has && oldFullLayout._has('gl'));
272+
var hasGl = (newFullLayout._has && newFullLayout._has('gl'));
271273

272274
if(hadGl && !hasGl) {
273275
for(k in oldPlots) {
274-
subplotInfo = oldPlots[k];
275-
276-
if(subplotInfo._scene) {
277-
subplotInfo._scene.destroy();
278-
}
276+
plotinfo = oldPlots[k];
277+
if(plotinfo._scene) plotinfo._scene.destroy();
279278
}
280279
}
281280

282-
var oldSubplotList = oldFullLayout._subplots || {};
283-
var newSubplotList = newFullLayout._subplots || {xaxis: [], yaxis: []};
284-
285281
// delete any titles we don't need anymore
286282
// check if axis list has changed, and if so clear old titles
287283
if(oldSubplotList.xaxis && oldSubplotList.yaxis) {
288-
var oldAxIDs = oldSubplotList.xaxis.concat(oldSubplotList.yaxis);
289-
var newAxIDs = newSubplotList.xaxis.concat(newSubplotList.yaxis);
290-
284+
var oldAxIDs = axisIds.listIds({_fullLayout: oldFullLayout});
291285
for(i = 0; i < oldAxIDs.length; i++) {
292-
if(newAxIDs.indexOf(oldAxIDs[i]) === -1) {
293-
oldFullLayout._infolayer.selectAll('.g-' + oldAxIDs[i] + 'title').remove();
286+
var oldAxId = oldAxIDs[i];
287+
if(!newFullLayout[axisIds.id2name(oldAxId)]) {
288+
oldFullLayout._infolayer.selectAll('.g-' + oldAxId + 'title').remove();
294289
}
295290
}
296291
}
@@ -308,7 +303,7 @@ exports.clean = function(newFullData, newFullLayout, oldFullData, oldFullLayout)
308303
else if(oldSubplotList.cartesian) {
309304
for(i = 0; i < oldSubplotList.cartesian.length; i++) {
310305
var oldSubplotId = oldSubplotList.cartesian[i];
311-
if(newSubplotList.cartesian.indexOf(oldSubplotId) === -1) {
306+
if(!newPlots[oldSubplotId]) {
312307
var selector = '.' + oldSubplotId + ',.' + oldSubplotId + '-x,.' + oldSubplotId + '-y';
313308
oldFullLayout._cartesianlayer.selectAll(selector).remove();
314309
removeSubplotExtras(oldSubplotId, oldFullLayout);
@@ -482,10 +477,6 @@ function makeSubplotLayer(gd, plotinfo) {
482477
ensureSingleAndAddDatum(plotinfo.gridlayer, 'g', plotinfo.xaxis._id);
483478
ensureSingleAndAddDatum(plotinfo.gridlayer, 'g', plotinfo.yaxis._id);
484479
plotinfo.gridlayer.selectAll('g').sort(axisIds.idSort);
485-
486-
for(var i = 0; i < constants.traceLayerClasses.length; i++) {
487-
ensureSingle(plotinfo.plot, 'g', constants.traceLayerClasses[i]);
488-
}
489480
}
490481

491482
plotinfo.xlines
@@ -516,11 +507,8 @@ function purgeSubplotLayers(layers, fullLayout) {
516507

517508
// must remove overlaid subplot trace layers 'manually'
518509

519-
var subplots = fullLayout._plots;
520-
var subplotIds = Object.keys(subplots);
521-
522-
for(var i = 0; i < subplotIds.length; i++) {
523-
var subplotInfo = subplots[subplotIds[i]];
510+
for(var k in fullLayout._plots) {
511+
var subplotInfo = fullLayout._plots[k];
524512
var overlays = subplotInfo.overlays || [];
525513

526514
for(var j = 0; j < overlays.length; j++) {

0 commit comments

Comments
 (0)