Skip to content

Commit 4792f08

Browse files
committed
optimize lsInner for splom
... cutting down ~200ms for 50 dims sploms - do not append <rect.bg> to DOM, when plot_bgcolor === papep_bgcolor - do not append useless <g.plot> layer to DOM - do not try to setup plot area clip paths under hasOnlyLargeSplom regime - loop over subplots ids, instead of a costly selectAll() + .each()
1 parent 17e30d2 commit 4792f08

File tree

3 files changed

+149
-101
lines changed

3 files changed

+149
-101
lines changed

Diff for: src/plot_api/subroutines.js

+115-94
Original file line numberDiff line numberDiff line change
@@ -51,12 +51,25 @@ function lsInner(gd) {
5151
var gs = fullLayout._size;
5252
var pad = gs.p;
5353
var axList = Axes.list(gd, '', true);
54+
var i, subplot, plotinfo, xa, ya;
55+
56+
fullLayout._paperdiv.style({
57+
width: fullLayout.width + 'px',
58+
height: fullLayout.height + 'px'
59+
})
60+
.selectAll('.main-svg')
61+
.call(Drawing.setSize, fullLayout.width, fullLayout.height);
62+
gd._context.setBackground(gd, fullLayout.paper_bgcolor);
63+
64+
exports.drawMainTitle(gd);
65+
ModeBar.manage(gd);
5466

5567
// _has('cartesian') means SVG specifically, not GL2D - but GL2D
5668
// can still get here because it makes some of the SVG structure
5769
// for shared features like selections.
58-
var hasSVGCartesian = fullLayout._has('cartesian');
59-
var i;
70+
if(!fullLayout._has('cartesian')) {
71+
return gd._promises.length && Promise.all(gd._promises);
72+
}
6073

6174
function getLinePosition(ax, counterAx, side) {
6275
var lwHalf = ax._lw / 2;
@@ -103,25 +116,21 @@ function lsInner(gd) {
103116
ax._mainSubplot = findMainSubplot(ax, fullLayout);
104117
}
105118

106-
fullLayout._paperdiv
107-
.style({
108-
width: fullLayout.width + 'px',
109-
height: fullLayout.height + 'px'
110-
})
111-
.selectAll('.main-svg')
112-
.call(Drawing.setSize, fullLayout.width, fullLayout.height);
113-
114-
gd._context.setBackground(gd, fullLayout.paper_bgcolor);
115-
116-
var subplotSelection = fullLayout._paper.selectAll('g.subplot');
117-
118-
// figure out which backgrounds we need to draw, and in which layers
119-
// to put them
119+
// figure out which backgrounds we need to draw,
120+
// and in which layers to put them
120121
var lowerBackgroundIDs = [];
122+
var backgroundIds = [];
121123
var lowerDomains = [];
122-
subplotSelection.each(function(d) {
123-
var subplot = d[0];
124-
var plotinfo = fullLayout._plots[subplot];
124+
// no need to draw background when paper and plot color are the same color,
125+
// activate mode just for large splom (which benefit the most from this
126+
// optimization), but this could apply to all cartesian subplots.
127+
var noNeedForBg = (
128+
fullLayout._hasOnlyLargeSploms &&
129+
fullLayout.paper_bgcolor === fullLayout.plot_bgcolor
130+
);
131+
132+
for(subplot in fullLayout._plots) {
133+
plotinfo = fullLayout._plots[subplot];
125134

126135
if(plotinfo.mainplot) {
127136
// mainplot is a reference to the main plot this one is overlaid on
@@ -131,23 +140,26 @@ function lsInner(gd) {
131140
plotinfo.bg.remove();
132141
}
133142
plotinfo.bg = undefined;
134-
return;
135-
}
136-
137-
var xDomain = plotinfo.xaxis.domain;
138-
var yDomain = plotinfo.yaxis.domain;
139-
var plotgroup = plotinfo.plotgroup;
140-
141-
if(overlappingDomain(xDomain, yDomain, lowerDomains)) {
142-
var pgNode = plotgroup.node();
143-
var plotgroupBg = plotinfo.bg = Lib.ensureSingle(plotgroup, 'rect', 'bg');
144-
pgNode.insertBefore(plotgroupBg.node(), pgNode.childNodes[0]);
145143
} else {
146-
plotgroup.select('rect.bg').remove();
147-
lowerBackgroundIDs.push(subplot);
148-
lowerDomains.push([xDomain, yDomain]);
144+
var xDomain = plotinfo.xaxis.domain;
145+
var yDomain = plotinfo.yaxis.domain;
146+
var plotgroup = plotinfo.plotgroup;
147+
148+
if(overlappingDomain(xDomain, yDomain, lowerDomains)) {
149+
var pgNode = plotgroup.node();
150+
var plotgroupBg = plotinfo.bg = Lib.ensureSingle(plotgroup, 'rect', 'bg');
151+
pgNode.insertBefore(plotgroupBg.node(), pgNode.childNodes[0]);
152+
backgroundIds.push(subplot);
153+
} else {
154+
plotgroup.select('rect.bg').remove();
155+
lowerDomains.push([xDomain, yDomain]);
156+
if(!noNeedForBg) {
157+
lowerBackgroundIDs.push(subplot);
158+
backgroundIds.push(subplot);
159+
}
160+
}
149161
}
150-
});
162+
}
151163

152164
// now create all the lower-layer backgrounds at once now that
153165
// we have the list of subplots that need them
@@ -163,86 +175,97 @@ function lsInner(gd) {
163175
fullLayout._plots[subplot].bg = d3.select(this);
164176
});
165177

166-
subplotSelection.each(function(d) {
167-
var subplot = d[0];
168-
var plotinfo = fullLayout._plots[subplot];
169-
var xa = plotinfo.xaxis;
170-
var ya = plotinfo.yaxis;
178+
// style all backgrounds
179+
for(i = 0; i < backgroundIds.length; i++) {
180+
plotinfo = fullLayout._plots[backgroundIds[i]];
181+
xa = plotinfo.xaxis;
182+
ya = plotinfo.yaxis;
171183

172-
if(plotinfo.bg && hasSVGCartesian) {
184+
if(plotinfo.bg) {
173185
plotinfo.bg
174186
.call(Drawing.setRect,
175187
xa._offset - pad, ya._offset - pad,
176188
xa._length + 2 * pad, ya._length + 2 * pad)
177189
.call(Color.fill, fullLayout.plot_bgcolor)
178190
.style('stroke-width', 0);
179191
}
192+
}
180193

181-
// Clip so that data only shows up on the plot area.
182-
var clipId = plotinfo.clipId = 'clip' + fullLayout._uid + subplot + 'plot';
194+
if(!fullLayout._hasOnlyLargeSploms) {
195+
for(subplot in fullLayout._plots) {
196+
plotinfo = fullLayout._plots[subplot];
197+
xa = plotinfo.xaxis;
198+
ya = plotinfo.yaxis;
183199

184-
var plotClip = Lib.ensureSingleById(fullLayout._clips, 'clipPath', clipId, function(s) {
185-
s.classed('plotclip', true)
186-
.append('rect');
187-
});
200+
// Clip so that data only shows up on the plot area.
201+
var clipId = plotinfo.clipId = 'clip' + fullLayout._uid + subplot + 'plot';
188202

189-
plotinfo.clipRect = plotClip.select('rect').attr({
190-
width: xa._length,
191-
height: ya._length
192-
});
203+
var plotClip = Lib.ensureSingleById(fullLayout._clips, 'clipPath', clipId, function(s) {
204+
s.classed('plotclip', true)
205+
.append('rect');
206+
});
193207

194-
Drawing.setTranslate(plotinfo.plot, xa._offset, ya._offset);
208+
plotinfo.clipRect = plotClip.select('rect').attr({
209+
width: xa._length,
210+
height: ya._length
211+
});
195212

196-
var plotClipId;
197-
var layerClipId;
213+
Drawing.setTranslate(plotinfo.plot, xa._offset, ya._offset);
198214

199-
if(plotinfo._hasClipOnAxisFalse) {
200-
plotClipId = null;
201-
layerClipId = clipId;
202-
} else {
203-
plotClipId = clipId;
204-
layerClipId = null;
205-
}
215+
var plotClipId;
216+
var layerClipId;
206217

207-
Drawing.setClipUrl(plotinfo.plot, plotClipId);
218+
if(plotinfo._hasClipOnAxisFalse) {
219+
plotClipId = null;
220+
layerClipId = clipId;
221+
} else {
222+
plotClipId = clipId;
223+
layerClipId = null;
224+
}
208225

209-
// stash layer clipId value (null or same as clipId)
210-
// to DRY up Drawing.setClipUrl calls on trace-module and trace layers
211-
// downstream
212-
plotinfo.layerClipId = layerClipId;
226+
Drawing.setClipUrl(plotinfo.plot, plotClipId);
213227

214-
// figure out extra axis line and tick positions as needed
215-
if(!hasSVGCartesian) return;
228+
// stash layer clipId value (null or same as clipId)
229+
// to DRY up Drawing.setClipUrl calls on trace-module and trace layers
230+
// downstream
231+
plotinfo.layerClipId = layerClipId;
232+
}
233+
}
216234

217-
var xLinesXLeft, xLinesXRight, xLinesYBottom, xLinesYTop,
218-
leftYLineWidth, rightYLineWidth;
219-
var yLinesYBottom, yLinesYTop, yLinesXLeft, yLinesXRight,
220-
connectYBottom, connectYTop;
221-
var extraSubplot;
235+
var xLinesXLeft, xLinesXRight, xLinesYBottom, xLinesYTop,
236+
leftYLineWidth, rightYLineWidth;
237+
var yLinesYBottom, yLinesYTop, yLinesXLeft, yLinesXRight,
238+
connectYBottom, connectYTop;
239+
var extraSubplot;
222240

223-
function xLinePath(y) {
224-
return 'M' + xLinesXLeft + ',' + y + 'H' + xLinesXRight;
225-
}
241+
function xLinePath(y) {
242+
return 'M' + xLinesXLeft + ',' + y + 'H' + xLinesXRight;
243+
}
226244

227-
function xLinePathFree(y) {
228-
return 'M' + xa._offset + ',' + y + 'h' + xa._length;
229-
}
245+
function xLinePathFree(y) {
246+
return 'M' + xa._offset + ',' + y + 'h' + xa._length;
247+
}
230248

231-
function yLinePath(x) {
232-
return 'M' + x + ',' + yLinesYTop + 'V' + yLinesYBottom;
233-
}
249+
function yLinePath(x) {
250+
return 'M' + x + ',' + yLinesYTop + 'V' + yLinesYBottom;
251+
}
234252

235-
function yLinePathFree(x) {
236-
return 'M' + x + ',' + ya._offset + 'v' + ya._length;
237-
}
253+
function yLinePathFree(x) {
254+
return 'M' + x + ',' + ya._offset + 'v' + ya._length;
255+
}
238256

239-
function mainPath(ax, pathFn, pathFnFree) {
240-
if(!ax.showline || subplot !== ax._mainSubplot) return '';
241-
if(!ax._anchorAxis) return pathFnFree(ax._mainLinePosition);
242-
var out = pathFn(ax._mainLinePosition);
243-
if(ax.mirror) out += pathFn(ax._mainMirrorPosition);
244-
return out;
245-
}
257+
function mainPath(ax, pathFn, pathFnFree) {
258+
if(!ax.showline || subplot !== ax._mainSubplot) return '';
259+
if(!ax._anchorAxis) return pathFnFree(ax._mainLinePosition);
260+
var out = pathFn(ax._mainLinePosition);
261+
if(ax.mirror) out += pathFn(ax._mainMirrorPosition);
262+
return out;
263+
}
264+
265+
for(subplot in fullLayout._plots) {
266+
plotinfo = fullLayout._plots[subplot];
267+
xa = plotinfo.xaxis;
268+
ya = plotinfo.yaxis;
246269

247270
/*
248271
* x lines get longer where they meet y lines, to make a crisp corner.
@@ -323,11 +346,9 @@ function lsInner(gd) {
323346
ya.linecolor : 'rgba(0,0,0,0)');
324347
}
325348
plotinfo.ylines.attr('d', yPath);
326-
});
349+
}
327350

328351
Axes.makeClipPaths(gd);
329-
exports.drawMainTitle(gd);
330-
ModeBar.manage(gd);
331352

332353
return gd._promises.length && Promise.all(gd._promises);
333354
}

Diff for: src/plots/cartesian/index.js

-1
Original file line numberDiff line numberDiff line change
@@ -464,7 +464,6 @@ function makeSubplotLayer(gd, plotinfo) {
464464
// and other places
465465
// - we don't (x|y)lines and (x|y)axislayer for most subplots
466466
// usually just the bottom x and left y axes.
467-
plotinfo.plot = ensureSingle(plotgroup, 'g', 'plot');
468467
plotinfo.xlines = ensureSingle(plotgroup, 'path', 'xlines-above');
469468
plotinfo.ylines = ensureSingle(plotgroup, 'path', 'ylines-above');
470469
plotinfo.xaxislayer = ensureSingle(plotgroup, 'g', 'xaxislayer-above');

Diff for: test/jasmine/tests/splom_test.js

+34-6
Original file line numberDiff line numberDiff line change
@@ -525,7 +525,9 @@ describe('Test splom interactions:', function() {
525525

526526
function _assert(exp) {
527527
var msg = ' - call #' + cnt;
528-
var subplots = d3.selectAll('g.cartesianlayer > g.subplot');
528+
var gd3 = d3.select(gd);
529+
var subplots = gd3.selectAll('g.cartesianlayer > g.subplot');
530+
var bgs = gd3.selectAll('.bglayer > rect.bg');
529531

530532
expect(subplots.size())
531533
.toBe(exp.subplotCnt, '# of <g.subplot>' + msg);
@@ -546,22 +548,47 @@ describe('Test splom interactions:', function() {
546548
expect(!!gd._fullLayout._splomGrid)
547549
.toBe(exp.hasSplomGrid, 'has regl-line2d splom grid' + msg);
548550

551+
expect(bgs.size()).toBe(exp.bgCnt, '# of <rect.bg> ' + msg);
552+
549553
cnt++;
550554
}
551555

552556
Plotly.plot(gd, figLarge).then(function() {
553557
_assert({
554558
subplotCnt: 400,
555-
innerSubplotNodeCnt: 5,
556-
hasSplomGrid: true
559+
innerSubplotNodeCnt: 4,
560+
hasSplomGrid: true,
561+
bgCnt: 0
562+
});
563+
564+
return Plotly.relayout(gd, 'paper_bgcolor', 'red');
565+
})
566+
.then(function() {
567+
_assert({
568+
subplotCnt: 400,
569+
innerSubplotNodeCnt: 4,
570+
hasSplomGrid: true,
571+
bgCnt: 400
557572
});
573+
574+
return Plotly.relayout(gd, 'plot_bgcolor', 'red');
575+
})
576+
.then(function() {
577+
_assert({
578+
subplotCnt: 400,
579+
innerSubplotNodeCnt: 4,
580+
hasSplomGrid: true,
581+
bgCnt: 0
582+
});
583+
558584
return Plotly.restyle(gd, 'dimensions', [dimsSmall]);
559585
})
560586
.then(function() {
561587
_assert({
562588
subplotCnt: 25,
563589
innerSubplotNodeCnt: 17,
564-
hasSplomGrid: false
590+
hasSplomGrid: false,
591+
bgCnt: 25
565592
});
566593

567594
// make sure 'new' subplot layers are in order
@@ -591,9 +618,10 @@ describe('Test splom interactions:', function() {
591618
// new subplots though have reduced number of children.
592619
innerSubplotNodeCnt: function(d) {
593620
var p = d.match(SUBPLOT_PATTERN);
594-
return (p[1] > 5 || p[2] > 5) ? 5 : 17;
621+
return (p[1] > 5 || p[2] > 5) ? 4 : 17;
595622
},
596-
hasSplomGrid: true
623+
hasSplomGrid: true,
624+
bgCnt: 0
597625
});
598626
})
599627
.catch(failTest)

0 commit comments

Comments
 (0)