Skip to content

Commit

Permalink
optimize lsInner for splom
Browse files Browse the repository at this point in the history
... 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()
  • Loading branch information
etpinard committed Sep 28, 2018
1 parent 17e30d2 commit 4792f08
Show file tree
Hide file tree
Showing 3 changed files with 149 additions and 101 deletions.
209 changes: 115 additions & 94 deletions src/plot_api/subroutines.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,12 +51,25 @@ function lsInner(gd) {
var gs = fullLayout._size;
var pad = gs.p;
var axList = Axes.list(gd, '', true);
var i, subplot, plotinfo, xa, ya;

fullLayout._paperdiv.style({
width: fullLayout.width + 'px',
height: fullLayout.height + 'px'
})
.selectAll('.main-svg')
.call(Drawing.setSize, fullLayout.width, fullLayout.height);
gd._context.setBackground(gd, fullLayout.paper_bgcolor);

exports.drawMainTitle(gd);
ModeBar.manage(gd);

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

function getLinePosition(ax, counterAx, side) {
var lwHalf = ax._lw / 2;
Expand Down Expand Up @@ -103,25 +116,21 @@ function lsInner(gd) {
ax._mainSubplot = findMainSubplot(ax, fullLayout);
}

fullLayout._paperdiv
.style({
width: fullLayout.width + 'px',
height: fullLayout.height + 'px'
})
.selectAll('.main-svg')
.call(Drawing.setSize, fullLayout.width, fullLayout.height);

gd._context.setBackground(gd, fullLayout.paper_bgcolor);

var subplotSelection = fullLayout._paper.selectAll('g.subplot');

// figure out which backgrounds we need to draw, and in which layers
// to put them
// figure out which backgrounds we need to draw,
// and in which layers to put them
var lowerBackgroundIDs = [];
var backgroundIds = [];
var lowerDomains = [];
subplotSelection.each(function(d) {
var subplot = d[0];
var plotinfo = fullLayout._plots[subplot];
// no need to draw background when paper and plot color are the same color,
// activate mode just for large splom (which benefit the most from this
// optimization), but this could apply to all cartesian subplots.
var noNeedForBg = (
fullLayout._hasOnlyLargeSploms &&
fullLayout.paper_bgcolor === fullLayout.plot_bgcolor
);

for(subplot in fullLayout._plots) {
plotinfo = fullLayout._plots[subplot];

if(plotinfo.mainplot) {
// mainplot is a reference to the main plot this one is overlaid on
Expand All @@ -131,23 +140,26 @@ function lsInner(gd) {
plotinfo.bg.remove();
}
plotinfo.bg = undefined;
return;
}

var xDomain = plotinfo.xaxis.domain;
var yDomain = plotinfo.yaxis.domain;
var plotgroup = plotinfo.plotgroup;

if(overlappingDomain(xDomain, yDomain, lowerDomains)) {
var pgNode = plotgroup.node();
var plotgroupBg = plotinfo.bg = Lib.ensureSingle(plotgroup, 'rect', 'bg');
pgNode.insertBefore(plotgroupBg.node(), pgNode.childNodes[0]);
} else {
plotgroup.select('rect.bg').remove();
lowerBackgroundIDs.push(subplot);
lowerDomains.push([xDomain, yDomain]);
var xDomain = plotinfo.xaxis.domain;
var yDomain = plotinfo.yaxis.domain;
var plotgroup = plotinfo.plotgroup;

if(overlappingDomain(xDomain, yDomain, lowerDomains)) {
var pgNode = plotgroup.node();
var plotgroupBg = plotinfo.bg = Lib.ensureSingle(plotgroup, 'rect', 'bg');
pgNode.insertBefore(plotgroupBg.node(), pgNode.childNodes[0]);
backgroundIds.push(subplot);
} else {
plotgroup.select('rect.bg').remove();
lowerDomains.push([xDomain, yDomain]);
if(!noNeedForBg) {
lowerBackgroundIDs.push(subplot);
backgroundIds.push(subplot);
}
}
}
});
}

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

subplotSelection.each(function(d) {
var subplot = d[0];
var plotinfo = fullLayout._plots[subplot];
var xa = plotinfo.xaxis;
var ya = plotinfo.yaxis;
// style all backgrounds
for(i = 0; i < backgroundIds.length; i++) {
plotinfo = fullLayout._plots[backgroundIds[i]];
xa = plotinfo.xaxis;
ya = plotinfo.yaxis;

if(plotinfo.bg && hasSVGCartesian) {
if(plotinfo.bg) {
plotinfo.bg
.call(Drawing.setRect,
xa._offset - pad, ya._offset - pad,
xa._length + 2 * pad, ya._length + 2 * pad)
.call(Color.fill, fullLayout.plot_bgcolor)
.style('stroke-width', 0);
}
}

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

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

plotinfo.clipRect = plotClip.select('rect').attr({
width: xa._length,
height: ya._length
});
var plotClip = Lib.ensureSingleById(fullLayout._clips, 'clipPath', clipId, function(s) {
s.classed('plotclip', true)
.append('rect');
});

Drawing.setTranslate(plotinfo.plot, xa._offset, ya._offset);
plotinfo.clipRect = plotClip.select('rect').attr({
width: xa._length,
height: ya._length
});

var plotClipId;
var layerClipId;
Drawing.setTranslate(plotinfo.plot, xa._offset, ya._offset);

if(plotinfo._hasClipOnAxisFalse) {
plotClipId = null;
layerClipId = clipId;
} else {
plotClipId = clipId;
layerClipId = null;
}
var plotClipId;
var layerClipId;

Drawing.setClipUrl(plotinfo.plot, plotClipId);
if(plotinfo._hasClipOnAxisFalse) {
plotClipId = null;
layerClipId = clipId;
} else {
plotClipId = clipId;
layerClipId = null;
}

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

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

var xLinesXLeft, xLinesXRight, xLinesYBottom, xLinesYTop,
leftYLineWidth, rightYLineWidth;
var yLinesYBottom, yLinesYTop, yLinesXLeft, yLinesXRight,
connectYBottom, connectYTop;
var extraSubplot;
var xLinesXLeft, xLinesXRight, xLinesYBottom, xLinesYTop,
leftYLineWidth, rightYLineWidth;
var yLinesYBottom, yLinesYTop, yLinesXLeft, yLinesXRight,
connectYBottom, connectYTop;
var extraSubplot;

function xLinePath(y) {
return 'M' + xLinesXLeft + ',' + y + 'H' + xLinesXRight;
}
function xLinePath(y) {
return 'M' + xLinesXLeft + ',' + y + 'H' + xLinesXRight;
}

function xLinePathFree(y) {
return 'M' + xa._offset + ',' + y + 'h' + xa._length;
}
function xLinePathFree(y) {
return 'M' + xa._offset + ',' + y + 'h' + xa._length;
}

function yLinePath(x) {
return 'M' + x + ',' + yLinesYTop + 'V' + yLinesYBottom;
}
function yLinePath(x) {
return 'M' + x + ',' + yLinesYTop + 'V' + yLinesYBottom;
}

function yLinePathFree(x) {
return 'M' + x + ',' + ya._offset + 'v' + ya._length;
}
function yLinePathFree(x) {
return 'M' + x + ',' + ya._offset + 'v' + ya._length;
}

function mainPath(ax, pathFn, pathFnFree) {
if(!ax.showline || subplot !== ax._mainSubplot) return '';
if(!ax._anchorAxis) return pathFnFree(ax._mainLinePosition);
var out = pathFn(ax._mainLinePosition);
if(ax.mirror) out += pathFn(ax._mainMirrorPosition);
return out;
}
function mainPath(ax, pathFn, pathFnFree) {
if(!ax.showline || subplot !== ax._mainSubplot) return '';
if(!ax._anchorAxis) return pathFnFree(ax._mainLinePosition);
var out = pathFn(ax._mainLinePosition);
if(ax.mirror) out += pathFn(ax._mainMirrorPosition);
return out;
}

for(subplot in fullLayout._plots) {
plotinfo = fullLayout._plots[subplot];
xa = plotinfo.xaxis;
ya = plotinfo.yaxis;

/*
* x lines get longer where they meet y lines, to make a crisp corner.
Expand Down Expand Up @@ -323,11 +346,9 @@ function lsInner(gd) {
ya.linecolor : 'rgba(0,0,0,0)');
}
plotinfo.ylines.attr('d', yPath);
});
}

Axes.makeClipPaths(gd);
exports.drawMainTitle(gd);
ModeBar.manage(gd);

return gd._promises.length && Promise.all(gd._promises);
}
Expand Down
1 change: 0 additions & 1 deletion src/plots/cartesian/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -464,7 +464,6 @@ function makeSubplotLayer(gd, plotinfo) {
// and other places
// - we don't (x|y)lines and (x|y)axislayer for most subplots
// usually just the bottom x and left y axes.
plotinfo.plot = ensureSingle(plotgroup, 'g', 'plot');
plotinfo.xlines = ensureSingle(plotgroup, 'path', 'xlines-above');
plotinfo.ylines = ensureSingle(plotgroup, 'path', 'ylines-above');
plotinfo.xaxislayer = ensureSingle(plotgroup, 'g', 'xaxislayer-above');
Expand Down
40 changes: 34 additions & 6 deletions test/jasmine/tests/splom_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -525,7 +525,9 @@ describe('Test splom interactions:', function() {

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

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

expect(bgs.size()).toBe(exp.bgCnt, '# of <rect.bg> ' + msg);

cnt++;
}

Plotly.plot(gd, figLarge).then(function() {
_assert({
subplotCnt: 400,
innerSubplotNodeCnt: 5,
hasSplomGrid: true
innerSubplotNodeCnt: 4,
hasSplomGrid: true,
bgCnt: 0
});

return Plotly.relayout(gd, 'paper_bgcolor', 'red');
})
.then(function() {
_assert({
subplotCnt: 400,
innerSubplotNodeCnt: 4,
hasSplomGrid: true,
bgCnt: 400
});

return Plotly.relayout(gd, 'plot_bgcolor', 'red');
})
.then(function() {
_assert({
subplotCnt: 400,
innerSubplotNodeCnt: 4,
hasSplomGrid: true,
bgCnt: 0
});

return Plotly.restyle(gd, 'dimensions', [dimsSmall]);
})
.then(function() {
_assert({
subplotCnt: 25,
innerSubplotNodeCnt: 17,
hasSplomGrid: false
hasSplomGrid: false,
bgCnt: 25
});

// make sure 'new' subplot layers are in order
Expand Down Expand Up @@ -591,9 +618,10 @@ describe('Test splom interactions:', function() {
// new subplots though have reduced number of children.
innerSubplotNodeCnt: function(d) {
var p = d.match(SUBPLOT_PATTERN);
return (p[1] > 5 || p[2] > 5) ? 5 : 17;
return (p[1] > 5 || p[2] > 5) ? 4 : 17;
},
hasSplomGrid: true
hasSplomGrid: true,
bgCnt: 0
});
})
.catch(failTest)
Expand Down

0 comments on commit 4792f08

Please sign in to comment.