Skip to content

Commit 6dd68bd

Browse files
committed
first cut at updating automargin logic
1 parent 26278de commit 6dd68bd

File tree

17 files changed

+194
-206
lines changed

17 files changed

+194
-206
lines changed

src/components/rangeslider/draw.js

+2-21
Original file line numberDiff line numberDiff line change
@@ -61,15 +61,9 @@ module.exports = function(gd) {
6161

6262
// remove exiting sliders and their corresponding clip paths
6363
rangeSliders.exit().each(function(axisOpts) {
64-
var rangeSlider = d3.select(this),
65-
opts = axisOpts[constants.name];
66-
67-
rangeSlider.remove();
64+
var opts = axisOpts[constants.name];
6865
fullLayout._topdefs.select('#' + opts._clipId).remove();
69-
});
70-
71-
// remove push margin object(s)
72-
if(rangeSliders.exit().size()) clearPushMargins(gd);
66+
}).remove();
7367

7468
// return early if no range slider is visible
7569
if(rangeSliderData.length === 0) return;
@@ -602,16 +596,3 @@ function drawGrabbers(rangeSlider, gd, axisOpts, opts) {
602596
});
603597
grabAreaMax.attr('height', opts._height);
604598
}
605-
606-
function clearPushMargins(gd) {
607-
var pushMargins = gd._fullLayout._pushmargin || {},
608-
keys = Object.keys(pushMargins);
609-
610-
for(var i = 0; i < keys.length; i++) {
611-
var k = keys[i];
612-
613-
if(k.indexOf(constants.name) !== -1) {
614-
Plots.autoMargin(gd, k);
615-
}
616-
}
617-
}

src/components/updatemenus/draw.js

+28-42
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,10 @@ module.exports = function draw(gd) {
5454
* ...
5555
*/
5656

57+
function clearAutoMargin(menuOpts) {
58+
Plots.autoMargin(gd, autoMarginId(menuOpts));
59+
}
60+
5761
// draw update menu container
5862
var menus = fullLayout._menulayer
5963
.selectAll('g.' + constants.containerClassName)
@@ -63,10 +67,15 @@ module.exports = function draw(gd) {
6367
.classed(constants.containerClassName, true)
6468
.style('cursor', 'pointer');
6569

66-
menus.exit().remove();
67-
68-
// remove push margin object(s)
69-
if(menus.exit().size()) clearPushMargins(gd);
70+
menus.exit().each(function() {
71+
// Most components don't need to explicitly remove autoMargin, because
72+
// marginPushers does this - but updatemenu updates don't go through
73+
// a full replot so we need to explicitly remove it.
74+
// This is for removing *all* updatemenus, removing individuals is
75+
// handled below, in hederGroups.exit
76+
d3.select(this).selectAll('g.' + constants.headerGroupClassName)
77+
.each(clearAutoMargin);
78+
}).remove();
7079

7180
// return early if no update menus are visible
7281
if(menuData.length === 0) return;
@@ -97,21 +106,13 @@ module.exports = function draw(gd) {
97106
if(headerGroups.enter().size()) {
98107
// make sure gButton is on top of all headers
99108
gButton.node().parentNode.appendChild(gButton.node());
100-
101-
gButton
102-
.call(removeAllButtons)
103-
.attr(constants.menuIndexAttrName, '-1');
109+
gButton.call(removeAllButtons);
104110
}
105111

106112
headerGroups.exit().each(function(menuOpts) {
107-
d3.select(this).remove();
108-
109-
gButton
110-
.call(removeAllButtons)
111-
.attr(constants.menuIndexAttrName, '-1');
112-
113-
Plots.autoMargin(gd, constants.autoMarginIdRoot + menuOpts._index);
114-
});
113+
gButton.call(removeAllButtons);
114+
clearAutoMargin(menuOpts);
115+
}).remove();
115116

116117
// draw headers!
117118
headerGroups.each(function(menuOpts) {
@@ -219,16 +220,8 @@ function drawHeader(gd, gHeader, gButton, scrollBox, menuOpts) {
219220
});
220221

221222
header.on('click', function() {
222-
gButton.call(removeAllButtons);
223-
224-
225-
// if this menu is active, fold the dropdown container
226-
// otherwise, make this menu active
227-
gButton.attr(
228-
constants.menuIndexAttrName,
229-
isActive(gButton, menuOpts) ?
230-
-1 :
231-
String(menuOpts._index)
223+
gButton.call(removeAllButtons,
224+
String(isActive(gButton, menuOpts) ? -1 : menuOpts._index)
232225
);
233226

234227
drawButtons(gd, gHeader, gButton, scrollBox, menuOpts);
@@ -608,7 +601,7 @@ function findDimensions(gd, menuOpts) {
608601
dims.lx = Math.round(dims.lx);
609602
dims.ly = Math.round(dims.ly);
610603

611-
Plots.autoMargin(gd, constants.autoMarginIdRoot + menuOpts._index, {
604+
Plots.autoMargin(gd, autoMarginId(menuOpts), {
612605
x: menuOpts.x,
613606
y: menuOpts.y,
614607
l: paddedWidth * ({right: 1, center: 0.5}[xanchor] || 0),
@@ -618,6 +611,10 @@ function findDimensions(gd, menuOpts) {
618611
});
619612
}
620613

614+
function autoMarginId(menuOpts) {
615+
return constants.autoMarginIdRoot + menuOpts._index;
616+
}
617+
621618
// set item positions (mutates posOpts)
622619
function setItemPosition(item, menuOpts, posOpts, overrideOpts) {
623620
overrideOpts = overrideOpts || {};
@@ -655,19 +652,8 @@ function setItemPosition(item, menuOpts, posOpts, overrideOpts) {
655652
posOpts.index++;
656653
}
657654

658-
function removeAllButtons(gButton) {
659-
gButton.selectAll('g.' + constants.dropdownButtonClassName).remove();
660-
}
661-
662-
function clearPushMargins(gd) {
663-
var pushMargins = gd._fullLayout._pushmargin || {};
664-
var keys = Object.keys(pushMargins);
665-
666-
for(var i = 0; i < keys.length; i++) {
667-
var k = keys[i];
668-
669-
if(k.indexOf(constants.autoMarginIdRoot) !== -1) {
670-
Plots.autoMargin(gd, k);
671-
}
672-
}
655+
function removeAllButtons(gButton, newMenuIndexAttr) {
656+
gButton
657+
.attr(constants.menuIndexAttrName, newMenuIndexAttr || '-1')
658+
.selectAll('g.' + constants.dropdownButtonClassName).remove();
673659
}

src/plot_api/edit_types.js

+1-2
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ var layoutOpts = {
3535
valType: 'flaglist',
3636
extras: ['none'],
3737
flags: [
38-
'calc', 'calcIfAutorange', 'plot', 'legend', 'ticks', 'axrange', 'margins',
38+
'calc', 'calcIfAutorange', 'plot', 'legend', 'ticks', 'axrange',
3939
'layoutstyle', 'modebar', 'camera', 'arraydraw'
4040
],
4141
description: [
@@ -47,7 +47,6 @@ var layoutOpts = {
4747
'*plot* calls `Plotly.plot` but without first clearing `gd.calcdata`.',
4848
'*legend* only redraws the legend.',
4949
'*ticks* only redraws axis ticks, labels, and gridlines.',
50-
'*margins* recomputes ticklabel automargins.',
5150
'*axrange* minimal sequence when updating axis ranges.',
5251
'*layoutstyle* reapplies global and SVG cartesian axis styles.',
5352
'*modebar* just updates the modebar.',

src/plot_api/helpers.js

-9
Original file line numberDiff line numberDiff line change
@@ -594,12 +594,3 @@ exports.clearAxisTypes = function(gd, traces, layoutUpdate) {
594594
}
595595
}
596596
};
597-
598-
exports.clearAxisAutomargins = function(gd) {
599-
var keys = Object.keys(gd._fullLayout._pushmargin);
600-
for(var i = 0; i < keys.length; i++) {
601-
if(keys[i].indexOf('automargin') !== -1) {
602-
delete gd._fullLayout._pushmargin[keys[i]];
603-
}
604-
}
605-
};

src/plot_api/plot_api.js

+32-32
Original file line numberDiff line numberDiff line change
@@ -252,10 +252,15 @@ exports.plot = function(gd, data, layout, config) {
252252
var calcdata = gd.calcdata;
253253
var i, cd, trace;
254254

255-
Registry.getComponentMethod('legend', 'draw')(gd);
256-
Registry.getComponentMethod('rangeselector', 'draw')(gd);
257-
Registry.getComponentMethod('sliders', 'draw')(gd);
258-
Registry.getComponentMethod('updatemenus', 'draw')(gd);
255+
// First reset the list of things that are allowed to change the margins
256+
// So any deleted traces or components will be wiped out of the
257+
// automargin calculation.
258+
// This means *every* margin pusher must be listed here, even if it
259+
// doesn't actually try to push the margins until later.
260+
Plots.clearAutoMarginIds(gd);
261+
262+
subroutines.drawMarginPushers(gd);
263+
Axes.allowAutoMargin(gd);
259264

260265
for(i = 0; i < calcdata.length; i++) {
261266
cd = calcdata[i];
@@ -354,6 +359,10 @@ exports.plot = function(gd, data, layout, config) {
354359
initInteractions,
355360
Plots.addLinks,
356361
Plots.rehover,
362+
// TODO: shouldn't really need doAutoMargin here, but without it some
363+
// edits don't manage to tweak the margins the way they should
364+
// in the tests, the only one that presently breaks is axis.position
365+
Plots.doAutoMargin,
357366
Plots.previousPromises
358367
);
359368

@@ -1666,7 +1675,6 @@ exports.relayout = function relayout(gd, astr, val) {
16661675

16671676
// clear calcdata if required
16681677
if(flags.calc) gd.calcdata = undefined;
1669-
if(flags.margins) helpers.clearAxisAutomargins(gd);
16701678

16711679
// fill in redraw sequence
16721680

@@ -1685,16 +1693,7 @@ exports.relayout = function relayout(gd, astr, val) {
16851693
if(flags.layoutstyle) seq.push(subroutines.layoutStyles);
16861694

16871695
if(flags.axrange) {
1688-
// N.B. leave as sequence of subroutines (for now) instead of
1689-
// subroutine of its own so that finalDraw always gets
1690-
// executed after drawData
1691-
seq.push(
1692-
function(gd) {
1693-
return subroutines.doTicksRelayout(gd, specs.rangesAltered);
1694-
},
1695-
subroutines.drawData,
1696-
subroutines.finalDraw
1697-
);
1696+
addAxRangeSequence(seq, specs.rangesAltered);
16981697
}
16991698

17001699
if(flags.ticks) seq.push(subroutines.doTicksRelayout);
@@ -1718,6 +1717,22 @@ exports.relayout = function relayout(gd, astr, val) {
17181717
});
17191718
};
17201719

1720+
function addAxRangeSequence(seq, rangesAltered) {
1721+
// N.B. leave as sequence of subroutines (for now) instead of
1722+
// subroutine of its own so that finalDraw always gets
1723+
// executed after drawData
1724+
var doTicks = rangesAltered ?
1725+
function(gd) { return subroutines.doTicksRelayout(gd, rangesAltered); } :
1726+
subroutines.doTicksRelayout;
1727+
1728+
seq.push(
1729+
doTicks,
1730+
subroutines.drawData,
1731+
subroutines.finalDraw,
1732+
subroutines.drawMarginPushers
1733+
);
1734+
}
1735+
17211736
var AX_RANGE_RE = /^[xyz]axis[0-9]*\.range(\[[0|1]\])?$/;
17221737
var AX_AUTORANGE_RE = /^[xyz]axis[0-9]*\.autorange$/;
17231738
var AX_DOMAIN_RE = /^[xyz]axis[0-9]*\.domain(\[[0|1]\])?$/;
@@ -2137,7 +2152,6 @@ exports.update = function update(gd, traceUpdate, layoutUpdate, _traces) {
21372152
// clear calcdata and/or axis types if required
21382153
if(restyleFlags.clearCalc || relayoutFlags.calc) gd.calcdata = undefined;
21392154
if(restyleFlags.clearAxisTypes) helpers.clearAxisTypes(gd, traces, layoutUpdate);
2140-
if(relayoutFlags.margins) helpers.clearAxisAutomargins(gd);
21412155

21422156
// fill in redraw sequence
21432157
var seq = [];
@@ -2168,13 +2182,7 @@ exports.update = function update(gd, traceUpdate, layoutUpdate, _traces) {
21682182
if(relayoutFlags.legend) seq.push(subroutines.doLegend);
21692183
if(relayoutFlags.layoutstyle) seq.push(subroutines.layoutStyles);
21702184
if(relayoutFlags.axrange) {
2171-
seq.push(
2172-
function(gd) {
2173-
return subroutines.doTicksRelayout(gd, relayoutSpecs.rangesAltered);
2174-
},
2175-
subroutines.drawData,
2176-
subroutines.finalDraw
2177-
);
2185+
addAxRangeSequence(seq, relayoutSpecs.rangesAltered);
21782186
}
21792187
if(relayoutFlags.ticks) seq.push(subroutines.doTicksRelayout);
21802188
if(relayoutFlags.modebar) seq.push(subroutines.doModeBar);
@@ -2291,8 +2299,6 @@ exports.react = function(gd, data, layout, config) {
22912299
// otherwise do the calcdata updates and calcTransform array remaps that we skipped earlier
22922300
else Plots.supplyDefaultsUpdateCalc(gd.calcdata, newFullData);
22932301

2294-
if(relayoutFlags.margins) helpers.clearAxisAutomargins(gd);
2295-
22962302
// Note: what restyle/relayout use impliedEdits and clearAxisTypes for
22972303
// must be handled by the user when using Plotly.react.
22982304

@@ -2334,13 +2340,7 @@ exports.react = function(gd, data, layout, config) {
23342340
if(restyleFlags.colorbars) seq.push(subroutines.doColorBars);
23352341
if(relayoutFlags.legend) seq.push(subroutines.doLegend);
23362342
if(relayoutFlags.layoutstyle) seq.push(subroutines.layoutStyles);
2337-
if(relayoutFlags.axrange) {
2338-
seq.push(
2339-
subroutines.doTicksRelayout,
2340-
subroutines.drawData,
2341-
subroutines.finalDraw
2342-
);
2343-
}
2343+
if(relayoutFlags.axrange) addAxRangeSequence(seq);
23442344
if(relayoutFlags.ticks) seq.push(subroutines.doTicksRelayout);
23452345
if(relayoutFlags.modebar) seq.push(subroutines.doModeBar);
23462346
if(relayoutFlags.camera) seq.push(subroutines.doCamera);

src/plot_api/subroutines.js

+12-1
Original file line numberDiff line numberDiff line change
@@ -589,8 +589,19 @@ exports.finalDraw = function(gd) {
589589
Registry.getComponentMethod('shapes', 'draw')(gd);
590590
Registry.getComponentMethod('images', 'draw')(gd);
591591
Registry.getComponentMethod('annotations', 'draw')(gd);
592-
Registry.getComponentMethod('legend', 'draw')(gd);
592+
// TODO: rangesliders really belong in marginPushers but they need to be
593+
// drawn after data - can we at least get the margin pushing part separated
594+
// out and done earlier?
593595
Registry.getComponentMethod('rangeslider', 'draw')(gd);
596+
// TODO: rangeselector only needs to be here (in addition to drawMarginPushers)
597+
// because the margins need to be fully determined before we can call
598+
// autorange and update axis ranges (which rangeselector needs to know which
599+
// button is active). Can we break out its automargin step from its draw step?
600+
Registry.getComponentMethod('rangeselector', 'draw')(gd);
601+
};
602+
603+
exports.drawMarginPushers = function(gd) {
604+
Registry.getComponentMethod('legend', 'draw')(gd);
594605
Registry.getComponentMethod('rangeselector', 'draw')(gd);
595606
Registry.getComponentMethod('sliders', 'draw')(gd);
596607
Registry.getComponentMethod('updatemenus', 'draw')(gd);

src/plots/cartesian/axes.js

+28-6
Original file line numberDiff line numberDiff line change
@@ -2007,8 +2007,12 @@ axes.doTicksSingle = function(gd, arg, skipTitle) {
20072007
}
20082008

20092009
function doAutoMargins() {
2010-
if(!ax.automargin) { return; }
2010+
var pushKey = ax._name + '.automargin';
20112011
if(axLetter !== 'x' && axLetter !== 'y') { return; }
2012+
if(!ax.automargin) {
2013+
Plots.autoMargin(gd, pushKey);
2014+
return;
2015+
}
20122016

20132017
var s = ax.side[0];
20142018
var push = {x: 0, y: 0, r: 0, l: 0, t: 0, b: 0};
@@ -2028,11 +2032,7 @@ axes.doTicksSingle = function(gd, arg, skipTitle) {
20282032
push[s] += ax.titlefont.size;
20292033
}
20302034

2031-
var pushKey = ax._name + '.automargin';
2032-
var prevPush = fullLayout._pushmargin[pushKey];
2033-
if(!prevPush || prevPush[s].size < push[s]) {
2034-
Plots.autoMargin(gd, pushKey, push);
2035-
}
2035+
Plots.autoMargin(gd, pushKey, push);
20362036
}
20372037

20382038
var done = Lib.syncOrAsync([
@@ -2255,6 +2255,28 @@ axes.doTicksSingle = function(gd, arg, skipTitle) {
22552255
}
22562256
};
22572257

2258+
/**
2259+
* Find all margin pushers for 2D axes and reserve them for later use
2260+
* Both label and rangeslider automargin calculations happen later so
2261+
* we need to explicitly allow their ids in order to not delete them.
2262+
*
2263+
* TODO: can we pull the actual automargin calls forward to avoid this hack?
2264+
* We're probably also doing multiple redraws in this case, would be faster
2265+
* if we can just do the whole calculation ahead of time and draw once.
2266+
*/
2267+
axes.allowAutoMargin = function(gd) {
2268+
var axList = axes.list(gd, '', true);
2269+
for(var i = 0; i < axList.length; i++) {
2270+
var ax = axList[i];
2271+
if(ax.automargin) {
2272+
Plots.allowAutoMargin(gd, ax._name + '.automargin');
2273+
}
2274+
if(ax.rangeslider && ax.rangeslider.visible) {
2275+
Plots.allowAutoMargin(gd, 'rangeslider' + ax._id);
2276+
}
2277+
}
2278+
};
2279+
22582280
// swap all the presentation attributes of the axes showing these traces
22592281
axes.swap = function(gd, traces) {
22602282
var axGroups = makeAxisGroups(gd, traces);

0 commit comments

Comments
 (0)