Skip to content

Commit 497203d

Browse files
committed
plot api: refactor Plotly.restyle
- split flag-finding logic with plot-routine sequence building
1 parent ce51b21 commit 497203d

File tree

1 file changed

+110
-113
lines changed

1 file changed

+110
-113
lines changed

src/plot_api/plot_api.js

Lines changed: 110 additions & 113 deletions
Original file line numberDiff line numberDiff line change
@@ -1193,8 +1193,7 @@ Plotly.restyle = function restyle(gd, astr, val, traces) {
11931193
gd = helpers.getGraphDiv(gd);
11941194
helpers.clearPromiseQueue(gd);
11951195

1196-
var i, fullLayout = gd._fullLayout,
1197-
aobj = {};
1196+
var aobj = {};
11981197

11991198
if(typeof astr === 'string') aobj[astr] = val;
12001199
else if(Lib.isPlainObject(astr)) {
@@ -1208,10 +1207,71 @@ Plotly.restyle = function restyle(gd, astr, val, traces) {
12081207

12091208
if(Object.keys(aobj).length) gd.changed = true;
12101209

1210+
var specs = _restyle(gd, aobj, traces),
1211+
flags = specs.flags;
1212+
1213+
// clear calcdata if required
1214+
if(flags.clearCalc) gd.calcdata = undefined;
1215+
1216+
// fill in redraw sequence
1217+
var seq = [];
1218+
1219+
if(flags.fullReplot) {
1220+
seq.push(Plotly.plot);
1221+
}
1222+
else {
1223+
seq.push(Plots.previousPromises);
1224+
1225+
Plots.supplyDefaults(gd);
1226+
1227+
if(flags.dostyle) seq.push(subroutines.doTraceStyle);
1228+
if(flags.docolorbars) seq.push(subroutines.doColorBars);
1229+
}
1230+
1231+
Queue.add(gd,
1232+
restyle, [gd, specs.undoit, specs.traces],
1233+
restyle, [gd, specs.redoit, specs.traces]
1234+
);
1235+
1236+
var plotDone = Lib.syncOrAsync(seq, gd);
1237+
if(!plotDone || !plotDone.then) plotDone = Promise.resolve();
1238+
1239+
return plotDone.then(function() {
1240+
gd.emit('plotly_restyle', specs.eventData);
1241+
return gd;
1242+
});
1243+
};
1244+
1245+
function _restyle(gd, aobj, traces) {
1246+
var fullLayout = gd._fullLayout,
1247+
fullData = gd._fullData,
1248+
data = gd.data,
1249+
i;
1250+
1251+
// fill up traces
12111252
if(isNumeric(traces)) traces = [traces];
12121253
else if(!Array.isArray(traces) || !traces.length) {
1213-
traces = gd.data.map(function(v, i) { return i; });
1214-
}
1254+
traces = data.map(function(_, i) { return i; });
1255+
}
1256+
1257+
// initialize flags
1258+
var flags = {
1259+
docalc: false,
1260+
docalcAutorange: false,
1261+
doplot: false,
1262+
dostyle: false,
1263+
docolorbars: false,
1264+
autorangeOn: false,
1265+
clearCalc: false,
1266+
fullReplot: false
1267+
};
1268+
1269+
// copies of the change (and previous values of anything affected)
1270+
// for the undo / redo queue
1271+
var redoit = {},
1272+
undoit = {},
1273+
axlist,
1274+
flagAxForDelete = {};
12151275

12161276
// recalcAttrs attributes need a full regeneration of calcdata
12171277
// as well as a replot, because the right objects may not exist,
@@ -1251,8 +1311,9 @@ Plotly.restyle = function restyle(gd, astr, val, traces) {
12511311
'line.showscale', 'line.cauto', 'line.autocolorscale', 'line.reversescale',
12521312
'marker.line.showscale', 'marker.line.cauto', 'marker.line.autocolorscale', 'marker.line.reversescale'
12531313
];
1314+
12541315
for(i = 0; i < traces.length; i++) {
1255-
if(Registry.traceIs(gd._fullData[traces[i]], 'box')) {
1316+
if(Registry.traceIs(fullData[traces[i]], 'box')) {
12561317
recalcAttrs.push('name');
12571318
break;
12581319
}
@@ -1266,6 +1327,7 @@ Plotly.restyle = function restyle(gd, astr, val, traces) {
12661327
'marker', 'marker.size', 'textfont',
12671328
'boxpoints', 'jitter', 'pointpos', 'whiskerwidth', 'boxmean'
12681329
];
1330+
12691331
// replotAttrs attributes need a replot (because different
12701332
// objects need to be made) but not a recalc
12711333
var replotAttrs = [
@@ -1286,24 +1348,16 @@ Plotly.restyle = function restyle(gd, astr, val, traces) {
12861348
'type', 'x', 'y', 'x0', 'y0', 'orientation', 'xaxis', 'yaxis'
12871349
];
12881350

1289-
// flags for which kind of update we need to do
1290-
var docalc = false,
1291-
docalcAutorange = false,
1292-
doplot = false,
1293-
dostyle = false,
1294-
docolorbars = false;
1295-
// copies of the change (and previous values of anything affected)
1296-
// for the undo / redo queue
1297-
var redoit = {},
1298-
undoit = {},
1299-
axlist,
1300-
flagAxForDelete = {};
1351+
var zscl = ['zmin', 'zmax'],
1352+
xbins = ['xbins.start', 'xbins.end', 'xbins.size'],
1353+
ybins = ['ybins.start', 'ybins.end', 'ybins.size'],
1354+
contourAttrs = ['contours.start', 'contours.end', 'contours.size'];
13011355

13021356
// At the moment, only cartesian, pie and ternary plot types can afford
13031357
// to not go through a full replot
13041358
var doPlotWhiteList = ['cartesian', 'pie', 'ternary'];
13051359
fullLayout._basePlotModules.forEach(function(_module) {
1306-
if(doPlotWhiteList.indexOf(_module.name) === -1) docalc = true;
1360+
if(doPlotWhiteList.indexOf(_module.name) === -1) flags.docalc = true;
13071361
});
13081362

13091363
// make a new empty vals array for undoit
@@ -1312,9 +1366,11 @@ Plotly.restyle = function restyle(gd, astr, val, traces) {
13121366
// for autoranging multiple axes
13131367
function addToAxlist(axid) {
13141368
var axName = Plotly.Axes.id2name(axid);
1315-
if(axlist.indexOf(axName) === -1) { axlist.push(axName); }
1369+
if(axlist.indexOf(axName) === -1) axlist.push(axName);
13161370
}
1371+
13171372
function autorangeAttr(axName) { return 'LAYOUT' + axName + '.autorange'; }
1373+
13181374
function rangeAttr(axName) { return 'LAYOUT' + axName + '.range'; }
13191375

13201376
// for attrs that interact (like scales & autoscales), save the
@@ -1334,7 +1390,7 @@ Plotly.restyle = function restyle(gd, astr, val, traces) {
13341390
if(attr.substr(0, 6) === 'LAYOUT') {
13351391
extraparam = Lib.nestedProperty(gd.layout, attr.replace('LAYOUT', ''));
13361392
} else {
1337-
extraparam = Lib.nestedProperty(gd.data[traces[i]], attr);
1393+
extraparam = Lib.nestedProperty(data[traces[i]], attr);
13381394
}
13391395

13401396
if(!(attr in undoit)) {
@@ -1347,10 +1403,6 @@ Plotly.restyle = function restyle(gd, astr, val, traces) {
13471403
extraparam.set(val);
13481404
}
13491405
}
1350-
var zscl = ['zmin', 'zmax'],
1351-
xbins = ['xbins.start', 'xbins.end', 'xbins.size'],
1352-
ybins = ['ybins.start', 'ybins.end', 'ybins.size'],
1353-
contourAttrs = ['contours.start', 'contours.end', 'contours.size'];
13541406

13551407
// now make the changes to gd.data (and occasionally gd.layout)
13561408
// and figure out what kind of graphics update we need to do
@@ -1361,6 +1413,7 @@ Plotly.restyle = function restyle(gd, astr, val, traces) {
13611413
param,
13621414
oldVal,
13631415
newVal;
1416+
13641417
redoit[ai] = vi;
13651418

13661419
if(ai.substr(0, 6) === 'LAYOUT') {
@@ -1371,18 +1424,18 @@ Plotly.restyle = function restyle(gd, astr, val, traces) {
13711424
param.set(Array.isArray(vi) ? vi[0] : vi);
13721425
// ironically, the layout attrs in restyle only require replot,
13731426
// not relayout
1374-
docalc = true;
1427+
flags.docalc = true;
13751428
continue;
13761429
}
13771430

13781431
// take no chances on transforms
1379-
if(ai.substr(0, 10) === 'transforms') docalc = true;
1432+
if(ai.substr(0, 10) === 'transforms') flags.docalc = true;
13801433

13811434
// set attribute in gd.data
13821435
undoit[ai] = a0();
13831436
for(i = 0; i < traces.length; i++) {
1384-
cont = gd.data[traces[i]];
1385-
contFull = gd._fullData[traces[i]];
1437+
cont = data[traces[i]];
1438+
contFull = fullData[traces[i]];
13861439
param = Lib.nestedProperty(cont, ai);
13871440
oldVal = param.get();
13881441
newVal = Array.isArray(vi) ? vi[i % vi.length] : vi;
@@ -1540,19 +1593,19 @@ Plotly.restyle = function restyle(gd, astr, val, traces) {
15401593
// check if we need to call axis type
15411594
if((traces.indexOf(0) !== -1) && (axtypeAttrs.indexOf(ai) !== -1)) {
15421595
Plotly.Axes.clearTypes(gd, traces);
1543-
docalc = true;
1596+
flags.docalc = true;
15441597
}
15451598

15461599
// switching from auto to manual binning or z scaling doesn't
15471600
// actually do anything but change what you see in the styling
15481601
// box. everything else at least needs to apply styles
15491602
if((['autobinx', 'autobiny', 'zauto'].indexOf(ai) === -1) ||
15501603
newVal !== false) {
1551-
dostyle = true;
1604+
flags.dostyle = true;
15521605
}
15531606
if(['colorbar', 'line'].indexOf(param.parts[0]) !== -1 ||
15541607
param.parts[0] === 'marker' && param.parts[1] === 'colorbar') {
1555-
docolorbars = true;
1608+
flags.docolorbars = true;
15561609
}
15571610

15581611
if(recalcAttrs.indexOf(ai) !== -1) {
@@ -1561,13 +1614,13 @@ Plotly.restyle = function restyle(gd, astr, val, traces) {
15611614
if(['orientation', 'type'].indexOf(ai) !== -1) {
15621615
axlist = [];
15631616
for(i = 0; i < traces.length; i++) {
1564-
var trace = gd.data[traces[i]];
1617+
var trace = data[traces[i]];
15651618

15661619
if(Registry.traceIs(trace, 'cartesian')) {
15671620
addToAxlist(trace.xaxis || 'x');
15681621
addToAxlist(trace.yaxis || 'y');
15691622

1570-
if(astr === 'type') {
1623+
if(ai === 'type') {
15711624
doextra(['autobinx', 'autobiny'], true, i);
15721625
}
15731626
}
@@ -1576,12 +1629,17 @@ Plotly.restyle = function restyle(gd, astr, val, traces) {
15761629
doextra(axlist.map(autorangeAttr), true, 0);
15771630
doextra(axlist.map(rangeAttr), [0, 1], 0);
15781631
}
1579-
docalc = true;
1632+
flags.docalc = true;
15801633
}
1581-
else if(replotAttrs.indexOf(ai) !== -1) doplot = true;
1582-
else if(autorangeAttrs.indexOf(ai) !== -1) docalcAutorange = true;
1634+
else if(replotAttrs.indexOf(ai) !== -1) flags.doplot = true;
1635+
else if(autorangeAttrs.indexOf(ai) !== -1) flags.docalcAutorange = true;
15831636
}
15841637

1638+
// do we need to force a recalc?
1639+
Plotly.Axes.list(gd).forEach(function(ax) {
1640+
if(ax.autorange) flags.autorangeOn = true;
1641+
});
1642+
15851643
// check axes we've flagged for possible deletion
15861644
// flagAxForDelete is a hash so we can make sure we only get each axis once
15871645
var axListForDelete = Object.keys(flagAxForDelete);
@@ -1590,9 +1648,10 @@ Plotly.restyle = function restyle(gd, astr, val, traces) {
15901648
var axId = axListForDelete[i],
15911649
axLetter = axId.charAt(0),
15921650
axAttr = axLetter + 'axis';
1593-
for(var j = 0; j < gd.data.length; j++) {
1594-
if(Registry.traceIs(gd.data[j], 'cartesian') &&
1595-
(gd.data[j][axAttr] || axLetter) === axId) {
1651+
1652+
for(var j = 0; j < data.length; j++) {
1653+
if(Registry.traceIs(data[j], 'cartesian') &&
1654+
(data[j][axAttr] || axLetter) === axId) {
15961655
continue axisLoop;
15971656
}
15981657
}
@@ -1601,83 +1660,21 @@ Plotly.restyle = function restyle(gd, astr, val, traces) {
16011660
doextra('LAYOUT' + Plotly.Axes.id2name(axId), null, 0);
16021661
}
16031662

1604-
// now all attribute mods are done, as are redo and undo
1605-
// so we can save them
1606-
Queue.add(gd, restyle, [gd, undoit, traces], restyle, [gd, redoit, traces]);
1607-
1608-
// do we need to force a recalc?
1609-
var autorangeOn = false;
1610-
Plotly.Axes.list(gd).forEach(function(ax) {
1611-
if(ax.autorange) autorangeOn = true;
1612-
});
1613-
if(docalc || dolayout || (docalcAutorange && autorangeOn)) {
1614-
gd.calcdata = undefined;
1663+
// combine a few flags together;
1664+
if(flags.docalc || (flags.docalcAutorange && flags.autorangeOn)) {
1665+
flags.clearCalc = true;
16151666
}
1616-
1617-
// now update the graphics
1618-
// a complete layout redraw takes care of plot and
1619-
var seq;
1620-
}
1621-
else if(docalc || doplot || docalcAutorange) {
1622-
seq = [Plotly.plot];
1623-
}
1624-
else {
1625-
Plots.supplyDefaults(gd);
1626-
seq = [Plots.previousPromises];
1627-
if(dostyle) {
1628-
seq.push(function doStyle() {
1629-
// first see if we need to do arraysToCalcdata
1630-
// call it regardless of what change we made, in case
1631-
// supplyDefaults brought in an array that was already
1632-
// in gd.data but not in gd._fullData previously
1633-
var i, cdi, arraysToCalcdata;
1634-
for(i = 0; i < gd.calcdata.length; i++) {
1635-
cdi = gd.calcdata[i];
1636-
arraysToCalcdata = (((cdi[0] || {}).trace || {})._module || {}).arraysToCalcdata;
1637-
if(arraysToCalcdata) arraysToCalcdata(cdi);
1638-
}
1639-
1640-
Plots.style(gd);
1641-
Registry.getComponentMethod('legend', 'draw')(gd);
1642-
1643-
return Plots.previousPromises(gd);
1644-
});
1645-
}
1646-
if(docolorbars) {
1647-
seq.push(function doColorBars() {
1648-
gd.calcdata.forEach(function(cd) {
1649-
if((cd[0].t || {}).cb) {
1650-
var trace = cd[0].trace,
1651-
cb = cd[0].t.cb;
1652-
1653-
if(Registry.traceIs(trace, 'contour')) {
1654-
cb.line({
1655-
width: trace.contours.showlines !== false ?
1656-
trace.line.width : 0,
1657-
dash: trace.line.dash,
1658-
color: trace.contours.coloring === 'line' ?
1659-
cb._opts.line.color : trace.line.color
1660-
});
1661-
}
1662-
if(Registry.traceIs(trace, 'markerColorscale')) {
1663-
cb.options(trace.marker.colorbar)();
1664-
}
1665-
else cb.options(trace.colorbar)();
1666-
}
1667-
});
1668-
return Plots.previousPromises(gd);
1669-
});
1670-
}
1667+
if(flags.docalc || flags.doplot || flags.docalcAutorange) {
1668+
flags.fullReplot = true;
16711669
}
16721670

1673-
var plotDone = Lib.syncOrAsync(seq, gd);
1674-
1675-
if(!plotDone || !plotDone.then) plotDone = Promise.resolve();
1676-
1677-
return plotDone.then(function() {
1678-
gd.emit('plotly_restyle', Lib.extendDeepNoArrays([], [redoit, traces]));
1679-
return gd;
1680-
});
1671+
return {
1672+
flags: flags,
1673+
undoit: undoit,
1674+
redoit: redoit,
1675+
traces: traces,
1676+
eventData: Lib.extendDeepNoArrays([], [redoit, traces])
1677+
};
16811678
}
16821679

16831680
/**

0 commit comments

Comments
 (0)