You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
a version of .newPlot that does the .update logic behind the scenes #1850
In Dash, I'd like the Graph component to do smart restyle and relayout updates when I call newPlot or plot (or some new method). Dash users deal with figures and not diffs.
Programming this logic myself is a lot of work that other groups could benefit from if it was done centrally in plotly.js. I wrote a lot of the logic in the workspace and it took a while to work through all the kinks. Some of the edge cases included:
calling newPlot instead of update at the start
calling addTraces manually instead of update
calling newPlot instead of update when switching between 2D, 3D, or ternary
calling newPlot when switching to a financial chart type
deleting cartesian axes when swithing to ternary or mapbox
Arriving at the delta spec between two subsequent specs sounds similar to a DOM diffing algo. With Étienne's Plotly.react it'd be black box to Dash / Python but internally, at least from some point onward, it'd be good to handle updates incrementally. The current Plotly.restyle calls occupy the middle ground, not granular enough for the fastest updates and not aggregate enough to allow full restatement of a plot.
This has been bugging me a bit, but the one problem I do see with this is that there's not currently a internal/virtual state to diff against. That is, gd.datais the state so that if you change your original inpt data and pass it to Plotly.react again, you can update the plot, but you're not really diffing anything at all.
One alternative is to allow passing the diffs themselves (like what the workspace does—specific attrs to update). The downside here is that you're just doing react's job for it, which does help you integrate plotly into a react app, but it's a bit pointless and awkward, declaratively speaking.
In order to really say it's a properly reactified, we'd have to duplicate the state internally. That is, we'd clone gd.data so that the copy Plotly works with is not the same that you passed in. This has the advantage that if you pass in a modified data (which at the moment would also identically be gd.data which is why it's weird now), then it can properly diff it and perform only the necessary updates. It would also mean plotly would never mutate your input data, which would be nice.
The downside is that it requires a full copy of the data (with the possible exception of data arrays which could potentially be aliased).
The most natural way to accomplish this would be to simply always clone the input when you plot. Perhaps Plotly.react could do this cloning and handle calling Plotly.newPlot on the initial plot. In fact, this approach would basically mean that when you use use Plotly.react, it'd be the only API command you'd interface with.
The alternative is that the react component could manage the cloned data.
Feel free to skim as I'm not sure if it's relevant here, but in addition to the API perimeter (input data in long arrays) there are also lots of calculations that happen inside the plots and traces, and some kind of diffing would be interesting there as well, for example, new data that breaches current axis domains may need to trigger axis tick etc. recalc, but if new data is within current bounds, it doesn't. Wouldn't the react / diffing metaphor break down because:
plotly.js plots have view (DOM scene graph) and model aspects intermingled,
data arrays can be very large, even when resulting DOM elements are few (eg. consolidated into bins, box plots, heat maps)
which could maybe tackled by separating the API to a purely view layer (e.g. you specify the boxplot quantiles) and a purely model layer (e.g. calculating quartiles, doing the binning).
The model layer could be plain JS like right now (serviceable!), or could use some reactive concept, MobX, most.js or our crosslink to avoid unneeded recalculations. The view layer would rely on structures that, in cardinality (number of data points / objects) would correspond to the DOM scenegraph structures such as markers.
...before I forget, Mike Bostock's heavy use of functional components is somewhere in between full batch recalculation that can be achieved by plain JS (imperatively or via functional programming), and the use of a reactive library. The setter methods can be written such a way that other inputs are considered constant, ie. some manual code can perform a more efficient update. Internally the functional component can cache (eagerly or lazily) things however it fits best.
but the one problem I do see with this is that there's not currently a internal/virtual state to diff against. That is, gd.data is the state so that if you change your original inpt data and pass it to Plotly.react again, you can update the plot, but you're not really diffing anything at all.
@rreusser - This is assuming that the user is modifying their data and layout in-place rather than creating new data and layout instances, right?
In other words, this is assuming the user is doing something like:
And so therefore gd.data[0]['x'] == data[0]['x'] even though gd.data[0]['x'][1] !== data[0]['x'][1]. Am I understanding this correctly?
In my case (Dash), we're creating entirely new data and layout instances. It's more like:
data = [{'x': [1, 2, 3]}]
Plotly.react(data)
data = [{'x': [1, 10, 3]}]
Plotly.react(data)
In which case, the objects will always be different (gd.data !== data) but the values and the keys may be the same. And so the diff would be constructed by comparing each of the values.
It's not perfectly clear to me whether the marker will end up blue or not. Perhaps you shouldn't do this, but this approach doesn't prevent you from being able to do that. That's why I thought perhaps Plotly.react should never pass the input object to plotly itself (i.e. gd.data). That would have the downside of potentially needing to clone all the input data as well. (That's where I said we could maybe skip data_array cloning and just duplicate references, but then you'd still be able to mutate data after the fact.)
An alternative that has some interesting (good and bad) side effects: diff fullData and fullLayout, which we already clone all the time. We'd still need to figure out something to do with data arrays which we don't clone...
Activity
etpinard commentedon Jul 4, 2017
Those are arguably
updatebugs.etpinard commentedon Jul 4, 2017
I'd vote for adding a new
Plotlymethod (roll the 🥁 )Plotly.react(gd, fig).monfera commentedon Jul 4, 2017
Arriving at the delta spec between two subsequent specs sounds similar to a DOM diffing algo. With Étienne's
Plotly.reactit'd be black box to Dash / Python but internally, at least from some point onward, it'd be good to handle updates incrementally. The currentPlotly.restylecalls occupy the middle ground, not granular enough for the fastest updates and not aggregate enough to allow full restatement of a plot.rreusser commentedon Aug 23, 2017
This has been bugging me a bit, but the one problem I do see with this is that there's not currently a internal/virtual state to diff against. That is,
gd.datais the state so that if you change your original inptdataand pass it toPlotly.reactagain, you can update the plot, but you're not really diffing anything at all.One alternative is to allow passing the diffs themselves (like what the workspace does—specific attrs to update). The downside here is that you're just doing react's job for it, which does help you integrate plotly into a react app, but it's a bit pointless and awkward, declaratively speaking.
In order to really say it's a properly reactified, we'd have to duplicate the state internally. That is, we'd clone
gd.dataso that the copy Plotly works with is not the same that you passed in. This has the advantage that if you pass in a modifieddata(which at the moment would also identically begd.datawhich is why it's weird now), then it can properly diff it and perform only the necessary updates. It would also mean plotly would never mutate your input data, which would be nice.The downside is that it requires a full copy of the data (with the possible exception of data arrays which could potentially be aliased).
The most natural way to accomplish this would be to simply always clone the input when you plot. Perhaps
Plotly.reactcould do this cloning and handle callingPlotly.newPloton the initial plot. In fact, this approach would basically mean that when you use use Plotly.react, it'd be the only API command you'd interface with.The alternative is that the react component could manage the cloned data.
/cc @etpinard @alexcjohnson @bpostlethwaite
monfera commentedon Aug 23, 2017
Feel free to skim as I'm not sure if it's relevant here, but in addition to the API perimeter (input data in long arrays) there are also lots of calculations that happen inside the plots and traces, and some kind of diffing would be interesting there as well, for example, new data that breaches current axis domains may need to trigger axis tick etc. recalc, but if new data is within current bounds, it doesn't. Wouldn't the
react/ diffing metaphor break down because:plotly.jsplots haveview(DOM scene graph) andmodelaspects intermingled,dataarrays can be very large, even when resulting DOM elements are few (eg. consolidated into bins, box plots, heat maps)which could maybe tackled by separating the API to a purely
viewlayer (e.g. you specify the boxplot quantiles) and a purelymodellayer (e.g. calculating quartiles, doing the binning).The
modellayer could be plain JS like right now (serviceable!), or could use some reactive concept,MobX,most.jsor ourcrosslinkto avoid unneeded recalculations. Theviewlayer would rely on structures that, in cardinality (number of data points / objects) would correspond to the DOM scenegraph structures such as markers.monfera commentedon Aug 23, 2017
...before I forget, Mike Bostock's heavy use of functional components is somewhere in between full batch recalculation that can be achieved by plain JS (imperatively or via functional programming), and the use of a reactive library. The setter methods can be written such a way that other inputs are considered constant, ie. some manual code can perform a more efficient update. Internally the functional component can cache (eagerly or lazily) things however it fits best.
chriddyp commentedon Aug 23, 2017
@rreusser - This is assuming that the user is modifying their
dataandlayoutin-place rather than creating newdataandlayoutinstances, right?In other words, this is assuming the user is doing something like:
And so therefore
gd.data[0]['x'] == data[0]['x']even thoughgd.data[0]['x'][1] !== data[0]['x'][1]. Am I understanding this correctly?In my case (Dash), we're creating entirely new data and layout instances. It's more like:
In which case, the objects will always be different (
gd.data !== data) but the values and the keys may be the same. And so the diff would be constructed by comparing each of the values.rreusser commentedon Aug 23, 2017
@chriddyp that's correct. The loophole is this case:
It's not perfectly clear to me whether the marker will end up blue or not. Perhaps you shouldn't do this, but this approach doesn't prevent you from being able to do that. That's why I thought perhaps
Plotly.reactshould never pass the input object to plotly itself (i.e.gd.data). That would have the downside of potentially needing to clone all the input data as well. (That's where I said we could maybe skip data_array cloning and just duplicate references, but then you'd still be able to mutate data after the fact.)rreusser commentedon Aug 23, 2017
Side note: lots of people (mis?)use the plotly API this way:
alexcjohnson commentedon Aug 24, 2017
An alternative that has some interesting (good and bad) side effects: diff
fullDataandfullLayout, which we already clone all the time. We'd still need to figure out something to do with data arrays which we don't clone...alexcjohnson commentedon Feb 12, 2018
Closed by #2341