8000 Introducing `Plotly.update` by etpinard · Pull Request #875 · plotly/plotly.js · GitHub
[go: up one dir, main page]

Skip to content
8000

Introducing Plotly.update #875

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 14 commits into from
Sep 7, 2016
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
plot api: refactor Plotly.restyle
- split flag-finding logic with plot-routine sequence building
  • Loading branch information
etpinard committed Aug 22, 2016
commit 497203d79ab43a9cbc4344a19e31697b59d0240f
223 changes: 110 additions & 113 deletions src/plot_api/plot_api.js
Original file line number Diff line number Diff line change
Expand Up @@ -1193,8 +1193,7 @@ Plotly.restyle = function restyle(gd, astr, val, traces) {
gd = helpers.getGraphDiv(gd);
helpers.clearPromiseQueue(gd);

var i, fullLayout = gd._fullLayout,
aobj = {};
var aobj = {};

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

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

var specs = _restyle(gd, aobj, traces),
flags = specs.flags;

// clear calcdata if required
if(flags.clearCalc) gd.calcdata = undefined;

// fill in redraw sequence
var seq = [];

if(flags.fullReplot) {
seq.push(Plotly.plot);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we will be doing a Plotly.plot, can we not break here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nop, gotta fire that plotly_restyle event.

}
else {
seq.push(Plots.previousPromises);

Plots.supplyDefaults(gd);

if(flags.dostyle) seq.push(subroutines.doTraceStyle);
if(flags.docolorbars) seq.push(subroutines.doColorBars);
}

Queue.add(gd,
restyle, [gd, specs.undoit, specs.traces],
restyle, [gd, specs.redoit, specs.traces]
);

var plotDone = Lib.syncOrAsync(seq, gd);
if(!plotDone || !plotDone.then) plotDone = Promise.resolve();

return plotDone.then(function() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Whoops forgot about this part.

gd.emit('plotly_restyle', specs.eventData);
return gd;
});
};

function _restyle(gd, aobj, traces) {
var fullLayout = gd._fullLayout,
fullData = gd._fullData,
data = gd.data,
i;

// fill up traces
if(isNumeric(traces)) traces = [traces];
else if(!Array.isArray(traces) || !traces.length) {
traces = gd.data.map(function(v, i) { return i; });
}
traces = data.map(function(_, i) { return i; });
}

// initialize flags
var flags = {
docalc: false,
docalcAutorange: false,
doplot: false,
dostyle: false,
docolorbars: false,
autorangeOn: false,
clearCalc: false,
fullReplot: false
};

// copies of the change (and previous values of anything affected)
// for the undo / redo queue
var redoit = {},
undoit = {},
axlist,
flagAxForDelete = {};

// recalcAttrs attributes need a full regeneration of calcdata
// as well as a replot, because the right objects may not exist,
Expand Down Expand Up @@ -1251,8 +1311,9 @@ Plotly.restyle = function restyle(gd, astr, val, traces) {
'line.showscale', 'line.cauto', 'line.autocolorscale', 'line.reversescale',
'marker.line.showscale', 'marker.line.cauto', 'marker.line.autocolorscale', 'marker.line.reversescale'
];

for(i = 0; i < traces.length; i++) {
if(Registry.traceIs(gd._fullData[traces[i]], 'box')) {
if(Registry.traceIs(fullData[traces[i]], 'box')) {
recalcAttrs.push('name');
break;
}
Expand All @@ -1266,6 +1327,7 @@ Plotly.restyle = function restyle(gd, astr, val, traces) {
'marker', 'marker.size', 'textfont',
'boxpoints', 'jitter', 'pointpos', 'whiskerwidth', 'boxmean'
];

// replotAttrs attributes need a replot (because different
// objects need to be made) but not a recalc
var replotAttrs = [
Expand All @@ -1286,24 +1348,16 @@ Plotly.restyle = function restyle(gd, astr, val, traces) {
'type', 'x', 'y', 'x0', 'y0', 'orientation', 'xaxis', 'yaxis'
];

// flags for which kind of update we need to do
var docalc = false,
docalcAutorange = false,
doplot = false,
dostyle = false,
docolorbars = false;
// copies of the change (and previous values of anything affected)
// for the undo / redo queue
var redoit = {},
undoit = {},
axlist,
flagAxForDelete = {};
var zscl = ['zmin', 'zmax'],
xbins = ['xbins.start', 'xbins.end', 'xbins.size'],
ybins = ['ybins.start', 'ybins.end', 'ybins.size'],
contourAttrs = ['contours.start', 'contours.end', 'contours.size'];

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

// make a new empty vals array for undoit
Expand All @@ -1312,9 +1366,11 @@ Plotly.restyle = f 10000 unction restyle(gd, astr, val, traces) {
// for autoranging multiple axes
function addToAxlist(axid) {
var axName = Plotly.Axes.id2name(axid);
if(axlist.indexOf(axName) === -1) { axlist.push(axName); }
if(axlist.indexOf(axName) === -1) axlist.push(axName);
}

function autorangeAttr(axName) { return 'LAYOUT' + axName + '.autorange'; }

function rangeAttr(axName) { return 'LAYOUT' + axName + '.range'; }

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

if(!(attr in undoit)) {
Expand All @@ -1347,10 +1403,6 @@ Plotly.restyle = function restyle(gd, astr, val, traces) {
extraparam.set(val);
}
}
var zscl = ['zmin', 'zmax'],
xbins = ['xbins.start', 'xbins.end', 'xbins.size'],
ybins = ['ybins.start', 'ybins.end', 'ybins.size'],
contourAttrs = ['contours.start', 'contours.end', 'contours.size'];

// now make the changes to gd.data (and occasionally gd.layout)
// and figure out what kind of graphics update we need to do
Expand All @@ -1361,6 +1413,7 @@ Plotly.restyle = function restyle(gd, astr, val, traces) {
param,
oldVal,
newVal;

redoit[ai] = vi;

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

// take no chances on transforms
if(ai.substr(0, 10) === 'transforms') docalc = true;
if(ai.substr(0, 10) === 'transforms') flags.docalc = true;

// set attribute in gd.data
undoit[ai] = a0();
for(i = 0; i < traces.length; i++) {
cont = gd.data[traces[i]];
contFull = gd._fullData[traces[i]];
cont = data[traces[i]];
contFull = fullData[traces[i]];
param = Lib.nestedProperty(cont, ai);
oldVal = param.get();
newVal = Array.isArray(vi) ? vi[i % vi.length] : vi;
Expand Down Expand Up @@ -1540,19 +1593,19 @@ Plotly.restyle = function restyle(gd, astr, val, traces) {
// check if we need to call axis type
if((traces.indexOf(0) !== -1) && (axtypeAttrs.indexOf(ai) !== -1)) {
Plotly.Axes.clearTypes(gd, traces);
docalc = true;
flags.docalc = true;
}

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

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

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

if(astr === 'type') {
if(ai === 'type') {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why does this change?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That fixes an undiscoved bug.

ai is more general than astr

astr is the second argument of Plotly.restyle(gd, astr, val), wherease ai is a key in { astr: val } object constructed before this loop.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That should've been a separate commit. My mistake. Good 👓

doextra(['autobinx', 'autobiny'], true, i);
}
}
Expand All @@ -1576,12 +1629,17 @@ Plotly.restyle = function restyle(gd, astr, val, traces) {
doextra(axlist.map(autorangeAttr), true, 0);
doextra(axlist.map(rangeAttr), [0, 1], 0);
}
docalc = true;
flags.docalc = true;
}
else if(replotAttrs.indexOf(ai) !== -1) doplot = true;
else if(autorangeAttrs.indexOf(ai) !== -1) docalcAutorange = true;
else if(replotAttrs.indexOf(ai) !== -1) flags.doplot = true;
else if(autorangeAttrs.indexOf(ai) !== -1) flags.docalcAutorange = true;
}

// do we need to force a recalc?
Plotly.Axes.list(gd).forEach(function(ax) {
if(ax.autorange) flags.autorangeOn = true;
});

// check axes we've flagged for possible deletion
// flagAxForDelete is a hash so we can make sure we only get each axis once
var axListForDelete = Object.keys(flagAxForDelete);
Expand All @@ -1590,9 +1648,10 @@ Plotly.restyle = function restyle(gd, astr, val, traces) {
var axId = axListForDelete[i],
axLetter = axId.charAt(0),
axAttr = axLetter + 'axis';
for(var j = 0; j < gd.data.length; j++) {
if(Registry.traceIs(gd.data[j], 'cartesian') &&
(gd.data[j][axAttr] || axLetter) === axId) {

for(var j = 0; j < data.length; j++) {
if(Registry.traceIs(data[j], 'cartesian') &&
(data[j][axAttr] || axLetter) === axId) {
continue axisLoop;
}
}
Expand All @@ -1601,83 +1660,21 @@ Plotly.restyle = function restyle(gd, astr, val, traces) {
doextra('LAYOUT' + Plotly.Axes.id2name(axId), null, 0);
}

// now all attribute mods are done, as are redo and undo
// so we can save them
Queue.add(gd, restyle, [gd, undoit, traces], restyle, [gd, redoit, traces]);

// do we need to force a recalc?
var autorangeOn = false;
Plotly.Axes.list(gd).forEach(function(ax) {
if(ax.autorange) autorangeOn = true;
});
if(docalc || dolayout || (docalcAutorange && autorangeOn)) {
gd.calcdata = undefined;
// combine a few flags together;
if(flags.docalc || (flags.docalcAutorange && flags.autorangeOn)) {
flags.clearCalc = true;
}

// now update the graphics
// a complete layout redraw takes care of plot and
var seq;
}
else if(docalc || doplot || docalcAutorange) {
seq = [Plotly.plot];
}
else {
Plots.supplyDefaults(gd);
seq = [Plots.previousPromises];
if(dostyle) {
seq.push(function doStyle() {
// first see if we need to do arraysToCalcdata
// call it regardless of what change we made, in case
// supplyDefaults brought in an array that was already
// in gd.data but not in gd._fullData previously
var i, cdi, arraysToCalcdata;
for(i = 0; i < gd.calcdata.length; i++) {
cdi = gd.calcdata[i];
arraysToCalcdata = (((cdi[0] || {}).trace || {})._module || {}).arraysToCalcdata;
if(arraysToCalcdata) arraysToCalcdata(cdi);
}

Plots.style(gd);
Registry.getComponentMethod('legend', 'draw')(gd);

return Plots.previousPromises(gd);
});
}
if(docolorbars) {
seq.push(function doColorBars() {
gd.calcdata.forEach(function(cd) {
if((cd[0].t || {}).cb) {
var trace = cd[0].trace,
cb = cd[0].t.cb;

if(Registry.traceIs(trace, 'contour')) {
cb.line({
width: trace.contours.showlines !== false ?
trace.line.width : 0,
dash: trace.line.dash,
color: trace.contours.coloring === 'line' ?
cb._opts.line.color : trace.line.color
});
}
if(Registry.traceIs(trace, 'markerColorscale')) {
cb.options(trace.marker.colorbar)();
}
else cb.options(trace.colorbar)();
}
});
return Plots.previousPromises(gd);
});
}
if(flags.docalc || flags.doplot || flags.docalcAutorange) {
flags.fullReplot = true;
}

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

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

return plotDone.then(function() {
gd.emit('plotly_restyle', Lib.extendDeepNoArrays([], [redoit, traces]));
return gd;
});
return {
flags: flags,
undoit: undoit,
redoit: redoit,
traces: traces,
eventData: Lib.extendDeepNoArrays([], [redoit, traces])
};
}

/**
Expand Down
0