Skip to content

Commit 52a3b09

Browse files
committed
Merge pull request #300 from plotly/expose-purge-method
Expose Plotly.purge method
2 parents 3bcd249 + ca6a892 commit 52a3b09

File tree

11 files changed

+161
-23
lines changed

11 files changed

+161
-23
lines changed

devtools/test_dashboard/buttons.js

+10-11
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
var Lib = require('@src/lib');
44

5-
var plotlist = document.getElementById('plot-list');
5+
var plotList = document.getElementById('plot-list');
66
var anchor = document.getElementById('embedded-graph');
77
var image = document.getElementById('embedded-image');
88

@@ -14,31 +14,30 @@ anchor.style.height = '600px';
1414
anchor.style.width = '1000px';
1515

1616
function plotButtons(plots, figDir) {
17-
1817
Object.keys(plots).forEach(function(plotname) {
19-
2018
var button = document.createElement('button');
2119

2220
button.style.cssFloat = 'left';
2321
button.style.width = '100px';
2422
button.style.height = '40px';
25-
2623
button.innerHTML = plotname;
2724

28-
plotlist.appendChild(button);
25+
plotList.appendChild(button);
2926

3027
button.addEventListener('click', function() {
31-
3228
var myImage = new Image();
3329
myImage.src = figDir + plotname + '.png';
3430

3531
image.innerHTML = '';
3632
image.appendChild(myImage);
3733

38-
39-
anchor.innerHTML = '';
34+
var currentGraphDiv = Tabs.getGraph();
35+
if(currentGraphDiv) Plotly.purge(currentGraphDiv);
4036

4137
gd = document.createElement('div');
38+
gd.id = 'graph';
39+
40+
anchor.innerHTML = '';
4241
anchor.appendChild(gd);
4342

4443
var plot = plots[plotname];
@@ -58,7 +57,7 @@ function plotButtons(plots, figDir) {
5857
snapshot.innerHTML = 'snapshot';
5958
snapshot.style.background = 'blue';
6059

61-
plotlist.appendChild(snapshot);
60+
plotList.appendChild(snapshot);
6261

6362
snapshot.addEventListener('click', function() {
6463

@@ -111,7 +110,7 @@ function plotButtons(plots, figDir) {
111110
pummelButton.style.marginLeft = '25px';
112111
pummelButton.innerHTML = 'pummel3d';
113112
pummelButton.style.background = 'blue';
114-
plotlist.appendChild(pummelButton);
113+
plotList.appendChild(pummelButton);
115114

116115
var i = 0;
117116
var mock = require('@mocks/gl3d_marker-color.json');
@@ -147,7 +146,7 @@ function plotButtons(plots, figDir) {
147146
scrapeButton.style.marginLeft = '25px';
148147
scrapeButton.innerHTML = 'scrape SVG';
149148
scrapeButton.style.background = 'blue';
150-
plotlist.appendChild(scrapeButton);
149+
plotList.appendChild(scrapeButton);
151150

152151
scrapeButton.addEventListener('click', function() {
153152
Plotly.Snapshot.toSVG(Tabs.get());

devtools/test_dashboard/index.html

+2
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
getGraph: function() {
2828
return document.getElementById('embedded-graph').children[0];
2929
},
30+
3031
fresh: function() {
3132
var anchor = document.getElementById('embedded-graph'),
3233
graphDiv = Tabs.getGraph();
@@ -37,6 +38,7 @@
3738

3839
return graphDiv;
3940
},
41+
4042
plotMock: function(mockName) {
4143
var mockURL = '../../test/image/mocks/' + mockName + '.json';
4244

src/core.js

+1
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ exports.prependTraces = Plotly.prependTraces;
2828
exports.addTraces = Plotly.addTraces;
2929
exports.deleteTraces = Plotly.deleteTraces;
3030
exports.moveTraces = Plotly.moveTraces;
31+
exports.purge = Plotly.purge;
3132
exports.setPlotConfig = require('./plot_api/set_plot_config');
3233
exports.register = Plotly.register;
3334

src/lib/events.js

+12
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,19 @@ var Events = {
114114
*/
115115
return jQueryHandlerValue !== undefined ? jQueryHandlerValue :
116116
nodeEventHandlerValue;
117+
},
118+
119+
purge: function(plotObj) {
120+
delete plotObj._ev;
121+
delete plotObj.on;
122+
delete plotObj.once;
123+
delete plotObj.removeListener;
124+
delete plotObj.removeAllListeners;
125+
delete plotObj.emit;
126+
127+
return plotObj;
117128
}
129+
118130
};
119131

120132
module.exports = Events;

src/plot_api/plot_api.js

+34-1
Original file line numberDiff line numberDiff line change
@@ -2406,6 +2406,39 @@ Plotly.relayout = function relayout(gd, astr, val) {
24062406
});
24072407
};
24082408

2409+
/**
2410+
* Purge a graph container div back to its initial pre-Plotly.plot state
2411+
*
2412+
* @param {string id or DOM element} gd
2413+
* the id or DOM element of the graph container div
2414+
*/
2415+
Plotly.purge = function purge(gd) {
2416+
gd = getGraphDiv(gd);
2417+
2418+
var fullLayout = gd._fullLayout || {},
2419+
fullData = gd._fullData || [];
2420+
2421+
// remove gl contexts
2422+
Plots.cleanPlot([], {}, fullData, fullLayout);
2423+
2424+
// purge properties
2425+
Plots.purge(gd);
2426+
2427+
// purge event emitter methods
2428+
Events.purge(gd);
2429+
2430+
// remove plot container
2431+
if(fullLayout._container) fullLayout._container.remove();
2432+
2433+
delete gd._context;
2434+
delete gd._replotPending;
2435+
delete gd._mouseDownTime;
2436+
delete gd._hmpixcount;
2437+
delete gd._hmlumcount;
2438+
2439+
return gd;
2440+
};
2441+
24092442
/**
24102443
* Reduce all reserved margin objects to a single required margin reservation.
24112444
*
@@ -2505,7 +2538,7 @@ function makePlotFramework(gd) {
25052538
// Make the svg container
25062539
fullLayout._paperdiv = fullLayout._container.selectAll('.svg-container').data([0]);
25072540
fullLayout._paperdiv.enter().append('div')
2508-
.classed('svg-container',true)
2541+
.classed('svg-container', true)
25092542
.style('position','relative');
25102543

25112544
// Initial autosize

src/plots/gl2d/index.js

+14
Original file line numberDiff line numberDiff line change
@@ -65,3 +65,17 @@ exports.plot = function plotGl2d(gd) {
6565
scene.plot(fullSubplotData, fullLayout, gd.layout);
6666
}
6767
};
68+
69+
exports.clean = function(newFullData, newFullLayout, oldFullData, oldFullLayout) {
70+
var oldSceneKeys = Plots.getSubplotIds(oldFullLayout, 'gl2d');
71+
72+
for(var i = 0; i < oldSceneKeys.length; i++) {
73+
var oldSubplot = oldFullLayout._plots[oldSceneKeys[i]],
74+
xaName = oldSubplot.xaxis._name,
75+
yaName = oldSubplot.yaxis._name;
76+
77+
if(!!oldSubplot._scene2d && (!newFullLayout[xaName] || !newFullLayout[yaName])) {
78+
oldSubplot._scene2d.destroy();
79+
}
80+
}
81+
};

src/plots/gl2d/scene2d.js

+8
Original file line numberDiff line numberDiff line change
@@ -292,6 +292,13 @@ proto.cameraChanged = function() {
292292

293293
proto.destroy = function() {
294294
this.glplot.dispose();
295+
296+
this.container.removeChild(this.canvas);
297+
this.container.removeChild(this.svgContainer);
298+
this.container.removeChild(this.mouseContainer);
299+
300+
this.glplot = null;
301+
this.stopped = true;
295302
};
296303

297304
proto.plot = function(fullData, fullLayout) {
@@ -405,6 +412,7 @@ proto.plot = function(fullData, fullLayout) {
405412

406413
proto.draw = function() {
407414
if(this.stopped) return;
415+
408416
requestAnimationFrame(this.redraw);
409417

410418
var glplot = this.glplot,

src/plots/plots.js

+5-8
Original file line numberDiff line numberDiff line change
@@ -492,7 +492,7 @@ plots.supplyDefaults = function(gd) {
492492
plots.supplyLayoutModuleDefaults(newLayout, newFullLayout, newFullData);
493493

494494
// clean subplots and other artifacts from previous plot calls
495-
cleanPlot(newFullData, newFullLayout, oldFullData, oldFullLayout);
495+
plots.cleanPlot(newFullData, newFullLayout, oldFullData, oldFullLayout);
496496

497497
/*
498498
* Relink functions and underscore attributes to promote consistency between
@@ -520,7 +520,7 @@ plots.supplyDefaults = function(gd) {
520520
}
521521
};
522522

523-
function cleanPlot(newFullData, newFullLayout, oldFullData, oldFullLayout) {
523+
plots.cleanPlot = function(newFullData, newFullLayout, oldFullData, oldFullLayout) {
524524
var i, j;
525525

526526
var plotTypes = Object.keys(subplotsRegistry);
@@ -560,7 +560,7 @@ function cleanPlot(newFullData, newFullLayout, oldFullData, oldFullLayout) {
560560
oldFullLayout._infolayer.selectAll('.cb' + oldUid).remove();
561561
}
562562
}
563-
}
563+
};
564564

565565
/**
566566
* Relink private _keys and keys with a function value from one layout
@@ -755,16 +755,13 @@ plots.supplyLayoutModuleDefaults = function(layoutIn, layoutOut, fullData) {
755755
}
756756
};
757757

758+
// Remove all plotly attributes from a div so it can be replotted fresh
759+
// TODO: these really need to be encapsulated into a much smaller set...
758760
plots.purge = function(gd) {
759-
// remove all plotly attributes from a div so it can be replotted fresh
760-
// TODO: these really need to be encapsulated into a much smaller set...
761761

762762
// note: we DO NOT remove _context because it doesn't change when we insert
763763
// a new plot, and may have been set outside of our scope.
764764

765-
// clean up the gl and geo containers
766-
// TODO unify subplot creation/update with d3.selection.order
767-
// and/or subplot ids
768765
var fullLayout = gd._fullLayout || {};
769766
if(fullLayout._glcontainer !== undefined) fullLayout._glcontainer.remove();
770767
if(fullLayout._geocontainer !== undefined) fullLayout._geocontainer.remove();

test/jasmine/tests/events_test.js

+7
Original file line numberDiff line numberDiff line change
@@ -181,8 +181,15 @@ describe('Events', function() {
181181
expect(eventBaton).toBe(3);
182182
expect(result).toBe('pong');
183183
});
184+
});
184185

186+
describe('purge', function() {
187+
it('should remove all method from the plotObj', function() {
188+
Events.init(plotObj);
189+
Events.purge(plotObj);
185190

191+
expect(plotObj).toEqual({});
192+
});
186193
});
187194

188195
});

test/jasmine/tests/gl_plot_interact_test.js

+47-3
Original file line numberDiff line numberDiff line change
@@ -36,14 +36,19 @@ describe('Test gl plot interactions', function() {
3636

3737
sceneIds = Plots.getSubplotIds(fullLayout, 'gl3d');
3838
sceneIds.forEach(function(id) {
39-
fullLayout[id]._scene.destroy();
39+
var scene = fullLayout[id]._scene;
40+
41+
if(scene.glplot) scene.destroy();
4042
});
4143

4244
sceneIds = Plots.getSubplotIds(fullLayout, 'gl2d');
4345
sceneIds.forEach(function(id) {
4446
var scene2d = fullLayout._plots[id]._scene2d;
45-
scene2d.stopped = true;
46-
scene2d.destroy();
47+
48+
if(scene2d.glplot) {
49+
scene2d.stopped = true;
50+
scene2d.destroy();
51+
}
4752
});
4853

4954
destroyGraphDiv();
@@ -369,4 +374,43 @@ describe('Test gl plot interactions', function() {
369374
});
370375

371376
});
377+
378+
describe('Plots.cleanPlot', function() {
379+
380+
it('should remove gl context from the graph div of a gl3d plot', function(done) {
381+
gd = createGraphDiv();
382+
383+
var mockData = [{
384+
type: 'scatter3d'
385+
}];
386+
387+
Plotly.plot(gd, mockData).then(function() {
388+
expect(gd._fullLayout.scene._scene.glplot).toBeDefined();
389+
390+
Plots.cleanPlot([], {}, gd._fullData, gd._fullLayout);
391+
expect(gd._fullLayout.scene._scene.glplot).toBe(null);
392+
393+
done();
394+
});
395+
});
396+
397+
it('should remove gl context from the graph div of a gl2d plot', function(done) {
398+
gd = createGraphDiv();
399+
400+
var mockData = [{
401+
type: 'scattergl',
402+
x: [1,2,3],
403+
y: [1,2,3]
404+
}];
405+
406+
Plotly.plot(gd, mockData).then(function() {
407+
expect(gd._fullLayout._plots.xy._scene2d.glplot).toBeDefined();
408+
409+
Plots.cleanPlot([], {}, gd._fullData, gd._fullLayout);
410+
expect(gd._fullLayout._plots.xy._scene2d.glplot).toBe(null);
411+
412+
done();
413+
});
414+
});
415+
});
372416
});

test/jasmine/tests/plot_api_test.js

+21
Original file line numberDiff line numberDiff line change
@@ -620,6 +620,27 @@ describe('Test plot api', function() {
620620
});
621621
});
622622

623+
describe('Plotly.purge', function() {
624+
625+
afterEach(destroyGraphDiv);
626+
627+
it('should return the graph div in its original state', function(done) {
628+
var gd = createGraphDiv();
629+
var initialKeys = Object.keys(gd);
630+
var intialHTML = gd.innerHTML;
631+
var mockData = [{ x: [1,2,3], y: [2,3,4] }];
632+
633+
Plotly.plot(gd, mockData).then(function() {
634+
Plotly.purge(gd);
635+
636+
expect(Object.keys(gd)).toEqual(initialKeys);
637+
expect(gd.innerHTML).toEqual(intialHTML);
638+
639+
done();
640+
});
641+
});
642+
});
643+
623644
describe('cleanData', function() {
624645
var gd;
625646

0 commit comments

Comments
 (0)