Skip to content

Commit 822cf8c

Browse files
committed
plot api: refactor Plotly.relayout
- in a similar way to Plotly.restyle
1 parent e1a989f commit 822cf8c

File tree

1 file changed

+105
-126
lines changed

1 file changed

+105
-126
lines changed

src/plot_api/plot_api.js

Lines changed: 105 additions & 126 deletions
Original file line numberDiff line numberDiff line change
@@ -1705,17 +1705,7 @@ Plotly.relayout = function relayout(gd, astr, val) {
17051705
return Promise.resolve(gd);
17061706
}
17071707

1708-
var layout = gd.layout,
1709-
fullLayout = gd._fullLayout,
1710-
aobj = {},
1711-
dolegend = false,
1712-
doticks = false,
1713-
dolayoutstyle = false,
1714-
doplot = false,
1715-
docalc = false,
1716-
domodebar = false,
1717-
newkey, axes, keys, xyref, scene, axisAttr, i;
1718-
1708+
var aobj = {};
17191709
if(typeof astr === 'string') aobj[astr] = val;
17201710
else if(Lib.isPlainObject(astr)) aobj = astr;
17211711
else {
@@ -1725,30 +1715,84 @@ Plotly.relayout = function relayout(gd, astr, val) {
17251715

17261716
if(Object.keys(aobj).length) gd.changed = true;
17271717

1728-
keys = Object.keys(aobj);
1729-
axes = Plotly.Axes.list(gd);
1718+
var specs = _relayout(gd, aobj),
1719+
flags = specs.flags;
1720+
1721+
// clear calcdata if required
1722+
if(flags.docalc) gd.calcdata = undefined;
1723+
1724+
// fill in redraw sequence
1725+
var seq = [];
1726+
1727+
if(flags.layoutReplot) {
1728+
seq.push(subroutines.layoutReplot);
1729+
}
1730+
else if(Object.keys(aobj).length) {
1731+
seq.push(Plots.previousPromises);
1732+
Plots.supplyDefaults(gd);
1733+
1734+
if(flags.dolegend) seq.push(subroutines.doLegend);
1735+
if(flags.dolayoutstyle) seq.push(subroutines.layoutStyles);
1736+
if(flags.doticks) seq.push(subroutines.doTicksRelayout);
1737+
if(flags.domodebar) seq.push(subroutines.doModeBar);
1738+
}
1739+
1740+
Queue.add(gd,
1741+
relayout, [gd, specs.undoit],
1742+
relayout, [gd, specs.redoit]
1743+
);
1744+
1745+
var plotDone = Lib.syncOrAsync(seq, gd);
1746+
if(!plotDone || !plotDone.then) plotDone = Promise.resolve(gd);
1747+
1748+
return plotDone.then(function() {
1749+
subroutines.setRangeSliderRange(gd, specs.eventData);
1750+
gd.emit('plotly_relayout', specs.eventData);
1751+
return gd;
1752+
});
1753+
};
1754+
1755+
function _relayout(gd, aobj) {
1756+
var layout = gd.layout,
1757+
fullLayout = gd._fullLayout,
1758+
keys = Object.keys(aobj),
1759+
axes = Plotly.Axes.list(gd),
1760+
i;
17301761

1762+
// look for 'allaxes', split out into all axes
1763+
// in case of 3D the axis are nested within a scene which is held in _id
17311764
for(i = 0; i < keys.length; i++) {
1732-
// look for 'allaxes', split out into all axes
17331765
if(keys[i].indexOf('allaxes') === 0) {
17341766
for(var j = 0; j < axes.length; j++) {
1735-
// in case of 3D the axis are nested within a scene which is held in _id
1736-
scene = axes[j]._id.substr(1);
1737-
axisAttr = (scene.indexOf('scene') !== -1) ? (scene + '.') : '';
1738-
newkey = keys[i].replace('allaxes', axisAttr + axes[j]._name);
1739-
if(!aobj[newkey]) { aobj[newkey] = aobj[keys[i]]; }
1767+
var scene = axes[j]._id.substr(1),
1768+
axisAttr = (scene.indexOf('scene') !== -1) ? (scene + '.') : '',
1769+
newkey = keys[i].replace('allaxes', axisAttr + axes[j]._name);
1770+
1771+
if(!aobj[newkey]) aobj[newkey] = aobj[keys[i]];
17401772
}
1741-
delete aobj[keys[i]];
1742-
}
1773+
17431774
delete aobj[keys[i]];
17441775
}
17451776
}
17461777

1778+
// initialize flags
1779+
var flags = {
1780+
dolegend: false,
1781+
doticks: false,
1782+
dolayoutstyle: false,
1783+
doplot: false,
1784+
docalc: false,
1785+
domodebar: false,
1786+
layoutReplot: false
1787+
};
1788+
17471789
// copies of the change (and previous values of anything affected)
17481790
// for the undo / redo queue
17491791
var redoit = {},
17501792
undoit = {};
17511793

1794+
var hw = ['height', 'width'];
1795+
17521796
// for attrs that interact (like scales & autoscales), save the
17531797
// old vals before making the change
17541798
// val=undefined will not set a value, just record what the value was.
@@ -1772,8 +1816,6 @@ Plotly.relayout = function relayout(gd, astr, val) {
17721816
return (fullLayout[axName] || {}).autorange;
17731817
}
17741818

1775-
var hw = ['height', 'width'];
1776-
17771819
// alter gd.layout
17781820
for(var ai in aobj) {
17791821
var p = Lib.nestedProperty(layout, ai),
@@ -1826,23 +1868,24 @@ Plotly.relayout = function relayout(gd, astr, val) {
18261868
doextra([ptrunk + '.tick0', ptrunk + '.dtick'], undefined);
18271869
}
18281870
else if(/[xy]axis[0-9]*?$/.test(pleaf) && !Object.keys(vi || {}).length) {
1829-
docalc = true;
1871+
flags.docalc = true;
18301872
}
18311873
else if(/[xy]axis[0-9]*\.categoryorder$/.test(pleafPlus)) {
1832-
docalc = true;
1874+
flags.docalc = true;
18331875
}
18341876
else if(/[xy]axis[0-9]*\.categoryarray/.test(pleafPlus)) {
1835-
docalc = true;
1877+
flags.docalc = true;
18361878
}
18371879

18381880
if(pleafPlus.indexOf('rangeslider') !== -1) {
1839-
docalc = true;
1881+
flags.docalc = true;
18401882
}
18411883

18421884
// toggling log without autorange: need to also recalculate ranges
18431885
// logical XOR (ie are we toggling log)
18441886
if(pleaf === 'type' && ((parentFull.type === 'log') !== (vi === 'log'))) {
18451887
var ax = parentIn;
1888+
18461889
if(!ax || !ax.range) {
18471890
doextra(ptrunk + '.autorange', true);
18481891
}
@@ -1881,8 +1924,8 @@ Plotly.relayout = function relayout(gd, astr, val) {
18811924
parentIn.range = [1, 0];
18821925
}
18831926

1884-
if(parentFull.autorange) docalc = true;
1885-
else doplot = true;
1927+
if(parentFull.autorange) flags.docalc = true;
1928+
else flags.doplot = true;
18861929
}
18871930
// send annotation and shape mods one-by-one through Annotations.draw(),
18881931
// don't set via nestedProperty
@@ -1912,7 +1955,7 @@ Plotly.relayout = function relayout(gd, astr, val) {
19121955

19131956
if((refAutorange(obji, 'x') || refAutorange(obji, 'y')) &&
19141957
!Lib.containsAny(ai, ['color', 'opacity', 'align', 'dash'])) {
1915-
docalc = true;
1958+
flags.docalc = true;
19161959
}
19171960

19181961
// TODO: combine all edits to a given annotation / shape into one call
@@ -1940,7 +1983,7 @@ Plotly.relayout = function relayout(gd, astr, val) {
19401983

19411984
for(i = 0; i < diff; i++) fullLayers.push({});
19421985

1943-
doplot = true;
1986+
flags.doplot = true;
19441987
}
19451988
else if(p.parts[0] === 'updatemenus') {
19461989
Lib.extendDeepAll(gd.layout, Lib.objectFromPath(ai, vi));
@@ -1949,165 +1992,101 @@ Plotly.relayout = function relayout(gd, astr, val) {
19491992
diff = (p.parts[2] + 1) - menus.length;
19501993

19511994
for(i = 0; i < diff; i++) menus.push({});
1952-
doplot = true;
1995+
flags.doplot = true;
19531996
}
19541997
// alter gd.layout
19551998
else {
19561999
// check whether we can short-circuit a full redraw
19572000
// 3d or geo at this point just needs to redraw.
1958-
if(p.parts[0].indexOf('scene') === 0) doplot = true;
1959-
else if(p.parts[0].indexOf('geo') === 0) doplot = true;
1960-
else if(p.parts[0].indexOf('ternary') === 0) doplot = true;
2001+
if(p.parts[0].indexOf('scene') === 0) flags.doplot = true;
2002+
else if(p.parts[0].indexOf('geo') === 0) flags.doplot = true;
2003+
else if(p.parts[0].indexOf('ternary') === 0) flags.doplot = true;
19612004
else if(fullLayout._has('gl2d') &&
19622005
(ai.indexOf('axis') !== -1 || p.parts[0] === 'plot_bgcolor')
1963-
) doplot = true;
1964-
else if(ai === 'hiddenlabels') docalc = true;
1965-
else if(p.parts[0].indexOf('legend') !== -1) dolegend = true;
1966-
else if(ai.indexOf('title') !== -1) doticks = true;
1967-
else if(p.parts[0].indexOf('bgcolor') !== -1) dolayoutstyle = true;
2006+
) flags.doplot = true;
2007+
else if(ai === 'hiddenlabels') flags.docalc = true;
2008+
else if(p.parts[0].indexOf('legend') !== -1) flags.dolegend = true;
2009+
else if(ai.indexOf('title') !== -1) flags.doticks = true;
2010+
else if(p.parts[0].indexOf('bgcolor') !== -1) flags.dolayoutstyle = true;
19682011
else if(p.parts.length > 1 &&
19692012
Lib.containsAny(p.parts[1], ['tick', 'exponent', 'grid', 'zeroline'])) {
1970-
doticks = true;
2013+
flags.doticks = true;
19712014
}
19722015
else if(ai.indexOf('.linewidth') !== -1 &&
19732016
ai.indexOf('axis') !== -1) {
1974-
doticks = dolayoutstyle = true;
2017+
flags.doticks = flags.dolayoutstyle = true;
19752018
}
19762019
else if(p.parts.length > 1 && p.parts[1].indexOf('line') !== -1) {
1977-
dolayoutstyle = true;
2020+
flags.dolayoutstyle = true;
19782021
}
19792022
else if(p.parts.length > 1 && p.parts[1] === 'mirror') {
1980-
doticks = dolayoutstyle = true;
2023+
flags.doticks = flags.dolayoutstyle = true;
19812024
}
19822025
else if(ai === 'margin.pad') {
1983-
doticks = dolayoutstyle = true;
2026+
flags.doticks = flags.dolayoutstyle = true;
19842027
}
19852028
else if(p.parts[0] === 'margin' ||
19862029
p.parts[1] === 'autorange' ||
19872030
p.parts[1] === 'rangemode' ||
19882031
p.parts[1] === 'type' ||
19892032
p.parts[1] === 'domain' ||
19902033
ai.match(/^(bar|box|font)/)) {
1991-
docalc = true;
2034+
flags.docalc = true;
19922035
}
19932036
/*
19942037
* hovermode and dragmode don't need any redrawing, since they just
1995-
* affect reaction to user input. everything else, assume full replot.
2038+
* affect reaction to user input, everything else, assume full replot.
19962039
* height, width, autosize get dealt with below. Except for the case of
19972040
* of subplots - scenes - which require scene.updateFx to be called.
19982041
*/
1999-
else if(['hovermode', 'dragmode'].indexOf(ai) !== -1) domodebar = true;
2042+
else if(['hovermode', 'dragmode'].indexOf(ai) !== -1) flags.domodebar = true;
20002043
else if(['hovermode', 'dragmode', 'height',
20012044
'width', 'autosize'].indexOf(ai) === -1) {
2002-
doplot = true;
2045+
flags.doplot = true;
20032046
}
20042047

20052048
p.set(vi);
20062049
}
20072050
}
2008-
// now all attribute mods are done, as are
2009-
// redo and undo so we can save them
2010-
Queue.add(gd, relayout, [gd, undoit], relayout, [gd, redoit]);
20112051

20122052
// calculate autosizing - if size hasn't changed,
20132053
// will remove h&w so we don't need to redraw
20142054
if(aobj.autosize) aobj = plotAutoSize(gd, aobj);
2055+
if(aobj.height || aobj.width || aobj.autosize) flags.docalc = true;
20152056

2016-
if(aobj.height || aobj.width || aobj.autosize) docalc = true;
2057+
if(flags.doplot || flags.docalc) {
2058+
flags.layoutReplot = true;
2059+
}
20172060

2018-
// redraw
2019-
// first check if there's still anything to do
2020-
var ak = Object.keys(aobj),
2021-
seq = [Plots.previousPromises];
2061+
// now all attribute mods are done, as are
2062+
// redo and undo so we can save them
20222063

2023-
if(doplot || docalc) {
2024-
seq.push(function layoutReplot() {
2025-
// force plot() to redo the layout
2026-
gd.layout = undefined;
2064+
return {
2065+
flags: flags,
2066+
undoit: undoit,
2067+
redoit: redoit,
2068+
eventData: Lib.extendDeep({}, redoit)
2069+
};
2070+
}
20272071

2028-
// force it to redo calcdata?
2029-
if(docalc) gd.calcdata = undefined;
20302072

2031-
// replot with the modified layout
2032-
return Plotly.plot(gd, '', layout);
2033-
});
20342073
}
2035-
else if(ak.length) {
2036-
// if we didn't need to redraw entirely, just do the needed parts
2037-
Plots.supplyDefaults(gd);
2038-
fullLayout = gd._fullLayout;
20392074

2040-
if(dolegend) {
2041-
seq.push(function doLegend() {
2042-
Registry.getComponentMethod('legend', 'draw')(gd);
2043-
return Plots.previousPromises(gd);
2044-
});
2045-
}
20462075

2047-
if(dolayoutstyle) seq.push(layoutStyles);
20482076

2049-
if(doticks) {
2050-
seq.push(function() {
2051-
Plotly.Axes.doTicks(gd, 'redraw');
2052-
drawMainTitle(gd);
2053-
return Plots.previousPromises(gd);
2054-
});
2055-
}
20562077

2057-
// this is decoupled enough it doesn't need async regardless
2058-
if(domodebar) {
2059-
var subplotIds;
2060-
ModeBar.manage(gd);
20612078

2062-
Plotly.Fx.supplyLayoutDefaults(gd.layout, fullLayout, gd._fullData);
2063-
Plotly.Fx.init(gd);
20642079

2065-
subplotIds = Plots.getSubplotIds(fullLayout, 'gl3d');
2066-
for(i = 0; i < subplotIds.length; i++) {
2067-
scene = fullLayout[subplotIds[i]]._scene;
2068-
scene.updateFx(fullLayout.dragmode, fullLayout.hovermode);
2069-
}
20702080

2071-
subplotIds = Plots.getSubplotIds(fullLayout, 'gl2d');
2072-
for(i = 0; i < subplotIds.length; i++) {
2073-
scene = fullLayout._plots[subplotIds[i]]._scene2d;
2074-
scene.updateFx(fullLayout);
2075-
}
2076-
2077-
subplotIds = Plots.getSubplotIds(fullLayout, 'geo');
2078-
for(i = 0; i < subplotIds.length; i++) {
2079-
var geo = fullLayout[subplotIds[i]]._geo;
2080-
geo.updateFx(fullLayout.hovermode);
2081-
}
2082-
}
20832081
}
20842082

2085-
function setRange(changes) {
2086-
2087-
var newMin = changes['xaxis.range'] ? changes['xaxis.range'][0] : changes['xaxis.range[0]'],
2088-
newMax = changes['xaxis.range'] ? changes['xaxis.range'][1] : changes['xaxis.range[1]'];
2089-
2090-
var rangeSlider = fullLayout.xaxis && fullLayout.xaxis.rangeslider ?
2091-
fullLayout.xaxis.rangeslider : {};
2092-
2093-
if(rangeSlider.visible) {
2094-
if(newMin || newMax) {
2095-
fullLayout.xaxis.rangeslider.setRange(newMin, newMax);
2096-
} else if(changes['xaxis.autorange']) {
2097-
fullLayout.xaxis.rangeslider.setRange();
2098-
}
2099-
}
21002083
}
21012084

2102-
var plotDone = Lib.syncOrAsync(seq, gd);
21032085

21042086
if(!plotDone || !plotDone.then) plotDone = Promise.resolve(gd);
21052087

21062088
return plotDone.then(function() {
2107-
var changes = Lib.extendDeep({}, redoit);
21082089

2109-
setRange(changes);
2110-
gd.emit('plotly_relayout', changes);
21112090

21122091
return gd;
21132092
});

0 commit comments

Comments
 (0)