Skip to content

Commit 6fbdf23

Browse files
authored
Merge pull request #2628 from plotly/fixup-rerange-axis-constraints
Fix relayout constrained axes + various multi-subplot perf improvements
2 parents 9d61443 + 72b6558 commit 6fbdf23

25 files changed

+270
-133
lines changed

src/components/colorbar/draw.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -418,7 +418,7 @@ module.exports = function draw(gd, id) {
418418
// this title call only handles side=right
419419
return Lib.syncOrAsync([
420420
function() {
421-
return Axes.doTicks(gd, cbAxisOut, true);
421+
return Axes.doTicksSingle(gd, cbAxisOut, true);
422422
},
423423
function() {
424424
if(['top', 'bottom'].indexOf(opts.titleside) === -1) {

src/components/grid/index.js

+3-3
Original file line numberDiff line numberDiff line change
@@ -225,9 +225,9 @@ function sizeDefaults(layoutIn, layoutOut) {
225225
var dfltGapY = hasSubplotGrid ? 0.3 : 0.1;
226226

227227
var dfltSideX, dfltSideY;
228-
if(isSplomGenerated) {
229-
dfltSideX = 'bottom';
230-
dfltSideY = 'left';
228+
if(isSplomGenerated && layoutOut._splomGridDflt) {
229+
dfltSideX = layoutOut._splomGridDflt.xside;
230+
dfltSideY = layoutOut._splomGridDflt.yside;
231231
}
232232

233233
gridOut._domains = {

src/plot_api/plot_api.js

+15-20
Original file line numberDiff line numberDiff line change
@@ -1689,19 +1689,9 @@ exports.relayout = function relayout(gd, astr, val) {
16891689
// subroutine of its own so that finalDraw always gets
16901690
// executed after drawData
16911691
seq.push(
1692-
// TODO
1693-
// no test fail when commenting out doAutoRangeAndConstraints,
1694-
// but I think we do need this (maybe just the enforce part?)
1695-
// Am I right?
1696-
// More info in:
1697-
// https://github.com/plotly/plotly.js/issues/2540
1698-
subroutines.doAutoRangeAndConstraints,
1699-
// TODO
1700-
// can target specific axes,
1701-
// do not have to redraw all axes here
1702-
// See:
1703-
// https://github.com/plotly/plotly.js/issues/2547
1704-
subroutines.doTicksRelayout,
1692+
function(gd) {
1693+
return subroutines.doTicksRelayout(gd, specs.rangesAltered);
1694+
},
17051695
subroutines.drawData,
17061696
subroutines.finalDraw
17071697
);
@@ -1728,6 +1718,10 @@ exports.relayout = function relayout(gd, astr, val) {
17281718
});
17291719
};
17301720

1721+
var AX_RANGE_RE = /^[xyz]axis[0-9]*\.range(\[[0|1]\])?$/;
1722+
var AX_AUTORANGE_RE = /^[xyz]axis[0-9]*\.autorange$/;
1723+
var AX_DOMAIN_RE = /^[xyz]axis[0-9]*\.domain(\[[0|1]\])?$/;
1724+
17311725
function _relayout(gd, aobj) {
17321726
var layout = gd.layout,
17331727
fullLayout = gd._fullLayout,
@@ -1806,7 +1800,7 @@ function _relayout(gd, aobj) {
18061800
var plen = p.parts.length;
18071801
// p.parts may end with an index integer if the property is an array
18081802
var pend = plen - 1;
1809-
while(pend > 0 && typeof p.parts[plen - 1] !== 'string') { pend--; }
1803+
while(pend > 0 && typeof p.parts[pend] !== 'string') pend--;
18101804
// last property in chain (leaf node)
18111805
var pleaf = p.parts[pend];
18121806
// leaf plus immediate parent
@@ -1841,11 +1835,11 @@ function _relayout(gd, aobj) {
18411835
fullLayout[ai] = gd._initialAutoSize[ai];
18421836
}
18431837
// check autorange vs range
1844-
else if(pleafPlus.match(/^[xyz]axis[0-9]*\.range(\[[0|1]\])?$/)) {
1838+
else if(pleafPlus.match(AX_RANGE_RE)) {
18451839
recordAlteredAxis(pleafPlus);
18461840
Lib.nestedProperty(fullLayout, ptrunk + '._inputRange').set(null);
18471841
}
1848-
else if(pleafPlus.match(/^[xyz]axis[0-9]*\.autorange$/)) {
1842+
else if(pleafPlus.match(AX_AUTORANGE_RE)) {
18491843
recordAlteredAxis(pleafPlus);
18501844
Lib.nestedProperty(fullLayout, ptrunk + '._inputRange').set(null);
18511845
var axFull = Lib.nestedProperty(fullLayout, ptrunk).get();
@@ -1855,7 +1849,7 @@ function _relayout(gd, aobj) {
18551849
axFull._input.domain = axFull._inputDomain.slice();
18561850
}
18571851
}
1858-
else if(pleafPlus.match(/^[xyz]axis[0-9]*\.domain(\[[0|1]\])?$/)) {
1852+
else if(pleafPlus.match(AX_DOMAIN_RE)) {
18591853
Lib.nestedProperty(fullLayout, ptrunk + '._inputDomain').set(null);
18601854
}
18611855

@@ -2061,6 +2055,7 @@ function _relayout(gd, aobj) {
20612055

20622056
return {
20632057
flags: flags,
2058+
rangesAltered: rangesAltered,
20642059
undoit: undoit,
20652060
redoit: redoit,
20662061
eventData: Lib.extendDeep({}, redoit)
@@ -2174,8 +2169,9 @@ exports.update = function update(gd, traceUpdate, layoutUpdate, _traces) {
21742169
if(relayoutFlags.layoutstyle) seq.push(subroutines.layoutStyles);
21752170
if(relayoutFlags.axrange) {
21762171
seq.push(
2177-
subroutines.doAutoRangeAndConstraints,
2178-
subroutines.doTicksRelayout,
2172+
function(gd) {
2173+
return subroutines.doTicksRelayout(gd, relayoutSpecs.rangesAltered);
2174+
},
21792175
subroutines.drawData,
21802176
subroutines.finalDraw
21812177
);
@@ -2340,7 +2336,6 @@ exports.react = function(gd, data, layout, config) {
23402336
if(relayoutFlags.layoutstyle) seq.push(subroutines.layoutStyles);
23412337
if(relayoutFlags.axrange) {
23422338
seq.push(
2343-
subroutines.doAutoRangeAndConstraints,
23442339
subroutines.doTicksRelayout,
23452340
subroutines.drawData,
23462341
subroutines.finalDraw

src/plot_api/subroutines.js

+54-3
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ exports.lsInner = function(gd) {
5050
var fullLayout = gd._fullLayout;
5151
var gs = fullLayout._size;
5252
var pad = gs.p;
53-
var axList = Axes.list(gd);
53+
var axList = Axes.list(gd, '', true);
5454

5555
// _has('cartesian') means SVG specifically, not GL2D - but GL2D
5656
// can still get here because it makes some of the SVG structure
@@ -95,6 +95,11 @@ exports.lsInner = function(gd) {
9595
ax._mainMirrorPosition = (ax.mirror && counterAx) ?
9696
getLinePosition(ax, counterAx,
9797
alignmentConstants.OPPOSITE_SIDE[ax.side]) : null;
98+
99+
// Figure out which subplot to draw ticks, labels, & axis lines on
100+
// do this as a separate loop so we already have all the
101+
// _mainAxis and _anchorAxis links set
102+
ax._mainSubplot = findMainSubplot(ax, fullLayout);
98103
}
99104

100105
fullLayout._paperdiv
@@ -324,6 +329,48 @@ exports.lsInner = function(gd) {
324329
return gd._promises.length && Promise.all(gd._promises);
325330
};
326331

332+
function findMainSubplot(ax, fullLayout) {
333+
var subplotList = fullLayout._subplots;
334+
var ids = subplotList.cartesian.concat(subplotList.gl2d || []);
335+
var mockGd = {_fullLayout: fullLayout};
336+
337+
var isX = ax._id.charAt(0) === 'x';
338+
var anchorAx = ax._mainAxis._anchorAxis;
339+
var mainSubplotID = '';
340+
var nextBestMainSubplotID = '';
341+
var anchorID = '';
342+
343+
// First try the main ID with the anchor
344+
if(anchorAx) {
345+
anchorID = anchorAx._mainAxis._id;
346+
mainSubplotID = isX ? (ax._id + anchorID) : (anchorID + ax._id);
347+
}
348+
349+
// Then look for a subplot with the counteraxis overlaying the anchor
350+
// If that fails just use the first subplot including this axis
351+
if(!mainSubplotID || !fullLayout._plots[mainSubplotID]) {
352+
mainSubplotID = '';
353+
354+
for(var j = 0; j < ids.length; j++) {
355+
var id = ids[j];
356+
var yIndex = id.indexOf('y');
357+
var idPart = isX ? id.substr(0, yIndex) : id.substr(yIndex);
358+
var counterPart = isX ? id.substr(yIndex) : id.substr(0, yIndex);
359+
360+
if(idPart === ax._id) {
361+
if(!nextBestMainSubplotID) nextBestMainSubplotID = id;
362+
var counterAx = Axes.getFromId(mockGd, counterPart);
363+
if(anchorID && counterAx.overlaying === anchorID) {
364+
mainSubplotID = id;
365+
break;
366+
}
367+
}
368+
}
369+
}
370+
371+
return mainSubplotID || nextBestMainSubplotID;
372+
}
373+
327374
function shouldShowLinesOrTicks(ax, subplot) {
328375
return (ax.ticks || ax.showline) &&
329376
(subplot === ax._mainSubplot || ax.mirror === 'all' || ax.mirror === 'allticks');
@@ -448,8 +495,12 @@ exports.doLegend = function(gd) {
448495
return Plots.previousPromises(gd);
449496
};
450497

451-
exports.doTicksRelayout = function(gd) {
452-
Axes.doTicks(gd, 'redraw');
498+
exports.doTicksRelayout = function(gd, rangesAltered) {
499+
if(rangesAltered) {
500+
Axes.doTicks(gd, Object.keys(rangesAltered), true);
501+
} else {
502+
Axes.doTicks(gd, 'redraw');
503+
}
453504

454505
if(gd._fullLayout._hasOnlyLargeSploms) {
455506
clearGlCanvases(gd);

src/plots/cartesian/axes.js

+84-43
Original file line numberDiff line numberDiff line change
@@ -1456,6 +1456,10 @@ axes.findSubplotsWithAxis = function(subplots, ax) {
14561456
// makeClipPaths: prepare clipPaths for all single axes and all possible xy pairings
14571457
axes.makeClipPaths = function(gd) {
14581458
var fullLayout = gd._fullLayout;
1459+
1460+
// for more info: https://github.com/plotly/plotly.js/issues/2595
1461+
if(fullLayout._hasOnlyLargeSploms) return;
1462+
14591463
var fullWidth = {_offset: 0, _length: fullLayout.width, _id: ''};
14601464
var fullHeight = {_offset: 0, _length: fullLayout.height, _id: ''};
14611465
var xaList = axes.list(gd, 'x', true);
@@ -1494,58 +1498,95 @@ axes.makeClipPaths = function(gd) {
14941498
});
14951499
};
14961500

1497-
// doTicks: draw ticks, grids, and tick labels
1498-
// axid: 'x', 'y', 'x2' etc,
1499-
// blank to do all,
1500-
// 'redraw' to force full redraw, and reset:
1501-
// ax._r (stored range for use by zoom/pan)
1502-
// ax._rl (stored linearized range for use by zoom/pan)
1503-
// or can pass in an axis object directly
1504-
axes.doTicks = function(gd, axid, skipTitle) {
1501+
/** Main multi-axis drawing routine!
1502+
*
1503+
* @param {DOM element} gd : graph div
1504+
* @param {string or array of strings} arg : polymorphic argument
1505+
* @param {boolean} skipTitle : optional flag to skip axis title draw/update
1506+
*
1507+
* Signature 1: Axes.doTicks(gd, 'redraw')
1508+
* use this to clear and redraw all axes on graph
1509+
*
1510+
* Signature 2: Axes.doTicks(gd, '')
1511+
* use this to draw all axes on graph w/o the selectAll().remove()
1512+
* of the 'redraw' signature
1513+
*
1514+
* Signature 3: Axes.doTicks(gd, [axId, axId2, ...])
1515+
* where the items are axis id string,
1516+
* use this to update multiple axes in one call
1517+
*
1518+
* N.B doTicks updates:
1519+
* - ax._r (stored range for use by zoom/pan)
1520+
* - ax._rl (stored linearized range for use by zoom/pan)
1521+
*/
1522+
axes.doTicks = function(gd, arg, skipTitle) {
15051523
var fullLayout = gd._fullLayout;
1506-
var ax;
1507-
var independent = false;
15081524

1509-
// allow passing an independent axis object instead of id
1510-
if(typeof axid === 'object') {
1511-
ax = axid;
1512-
axid = ax._id;
1513-
independent = true;
1525+
if(arg === 'redraw') {
1526+
fullLayout._paper.selectAll('g.subplot').each(function(subplot) {
1527+
var plotinfo = fullLayout._plots[subplot];
1528+
var xa = plotinfo.xaxis;
1529+
var ya = plotinfo.yaxis;
1530+
1531+
plotinfo.xaxislayer.selectAll('.' + xa._id + 'tick').remove();
1532+
plotinfo.yaxislayer.selectAll('.' + ya._id + 'tick').remove();
1533+
if(plotinfo.gridlayer) plotinfo.gridlayer.selectAll('path').remove();
1534+
if(plotinfo.zerolinelayer) plotinfo.zerolinelayer.selectAll('path').remove();
1535+
fullLayout._infolayer.select('.g-' + xa._id + 'title').remove();
1536+
fullLayout._infolayer.select('.g-' + ya._id + 'title').remove();
1537+
});
15141538
}
1515-
else {
1516-
ax = axes.getFromId(gd, axid);
1517-
1518-
if(axid === 'redraw') {
1519-
fullLayout._paper.selectAll('g.subplot').each(function(subplot) {
1520-
var plotinfo = fullLayout._plots[subplot];
1521-
var xa = plotinfo.xaxis;
1522-
var ya = plotinfo.yaxis;
1523-
1524-
plotinfo.xaxislayer.selectAll('.' + xa._id + 'tick').remove();
1525-
plotinfo.yaxislayer.selectAll('.' + ya._id + 'tick').remove();
1526-
if(plotinfo.gridlayer) plotinfo.gridlayer.selectAll('path').remove();
1527-
if(plotinfo.zerolinelayer) plotinfo.zerolinelayer.selectAll('path').remove();
1528-
fullLayout._infolayer.select('.g-' + xa._id + 'title').remove();
1529-
fullLayout._infolayer.select('.g-' + ya._id + 'title').remove();
1530-
});
1531-
}
15321539

1533-
if(!axid || axid === 'redraw') {
1534-
return Lib.syncOrAsync(axes.list(gd, '', true).map(function(ax) {
1535-
return function() {
1536-
if(!ax._id) return;
1537-
var axDone = axes.doTicks(gd, ax._id);
1538-
ax._r = ax.range.slice();
1539-
ax._rl = Lib.simpleMap(ax._r, ax.r2l);
1540-
return axDone;
1541-
};
1542-
}));
1543-
}
1540+
var axList = (!arg || arg === 'redraw') ? axes.listIds(gd) : arg;
1541+
1542+
Lib.syncOrAsync(axList.map(function(axid) {
1543+
return function() {
1544+
if(!axid) return;
1545+
1546+
var axDone = axes.doTicksSingle(gd, axid, skipTitle);
1547+
1548+
var ax = axes.getFromId(gd, axid);
1549+
ax._r = ax.range.slice();
1550+
ax._rl = Lib.simpleMap(ax._r, ax.r2l);
1551+
1552+
return axDone;
1553+
};
1554+
}));
1555+
};
1556+
1557+
/** Per axis drawing routine!
1558+
*
1559+
* This routine draws axis ticks and much more (... grids, labels, title etc.)
1560+
* Supports multiple argument signatures.
1561+
* N.B. this thing is async in general (because of MathJax rendering)
1562+
*
1563+
* @param {DOM element} gd : graph div
1564+
* @param {string or array of strings} arg : polymorphic argument
1565+
* @param {boolean} skipTitle : optional flag to skip axis title draw/update
1566+
* @return {promise}
1567+
*
1568+
* Signature 1: Axes.doTicks(gd, ax)
1569+
* where ax is an axis object as in fullLayout
1570+
*
1571+
* Signature 2: Axes.doTicks(gd, axId)
1572+
* where axId is a axis id string
1573+
*/
1574+
axes.doTicksSingle = function(gd, arg, skipTitle) {
1575+
var fullLayout = gd._fullLayout;
1576+
var independent = false;
1577+
var ax;
1578+
1579+
if(Lib.isPlainObject(arg)) {
1580+
ax = arg;
1581+
independent = true;
1582+
} else {
1583+
ax = axes.getFromId(gd, arg);
15441584
}
15451585

15461586
// set scaling to pixels
15471587
ax.setScale();
15481588

1589+
var axid = ax._id;
15491590
var axLetter = axid.charAt(0);
15501591
var counterLetter = axes.counterLetter(axid);
15511592
var vals = ax._vals = axes.calcTicks(ax);

src/plots/cartesian/dragbox.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ var FROM_TL = require('../../constants/alignment').FROM_TL;
2626

2727
var Plots = require('../plots');
2828

29-
var doTicks = require('./axes').doTicks;
29+
var doTicksSingle = require('./axes').doTicksSingle;
3030
var getFromId = require('./axis_ids').getFromId;
3131
var prepSelect = require('./select').prepSelect;
3232
var clearSelect = require('./select').clearSelect;
@@ -585,7 +585,7 @@ function makeDragBox(gd, plotinfo, x, y, w, h, ns, ew) {
585585
updates = {};
586586
for(i = 0; i < activeAxIds.length; i++) {
587587
var axId = activeAxIds[i];
588-
doTicks(gd, axId, true);
588+
doTicksSingle(gd, axId, true);
589589
var ax = getFromId(gd, axId);
590590
updates[ax._name + '.range[0]'] = ax.range[0];
591591
updates[ax._name + '.range[1]'] = ax.range[1];

src/plots/cartesian/transition_axes.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@ module.exports = function transitionAxes(gd, newLayout, transitionOpts, makeOnCo
125125
activeAxIds = [xa._id, ya._id];
126126

127127
for(i = 0; i < activeAxIds.length; i++) {
128-
Axes.doTicks(gd, activeAxIds[i], true);
128+
Axes.doTicksSingle(gd, activeAxIds[i], true);
129129
}
130130

131131
function redrawObjs(objArray, method, shortCircuit) {

0 commit comments

Comments
 (0)