Skip to content
This repository has been archived by the owner on Jun 3, 2024. It is now read-only.

"Live Figure" - new Graph props for current state of figure #584

Open
alexcjohnson opened this issue Jul 17, 2019 · 8 comments
Open

"Live Figure" - new Graph props for current state of figure #584

alexcjohnson opened this issue Jul 17, 2019 · 8 comments

Comments

@alexcjohnson
Copy link
Collaborator

Currently the way to respond to zoom events, legend toggle events, etc. is to listen to relayoutData and restyleData. This gets super complicated though since it only contains the latest changes, which may be a partial zoom (single axis, single-ended) or completely unrelated to the information you actually care about. So to make a really robust app, the dev needs to manually accumulate these changes in a Store or something.

It would be way better to just expose the current state of the figure as its own prop. Then, to avoid infinite loops, the normal usage pattern would be to use relayoutData as an Input just to trigger the callback, and use liveFigure or whatever we call it as State.

But of course we don't want the entire figure, we need to at least exclude data arrays as they may be huge and slow things down.

The uirevision system collects attribute patterns that can be set by the UI - we could presumably piggyback off that, though that's reaching fairly deep into plotly.js unless we build this functionality directly into plotly.js. Alternatively we could clone the whole figure minus data arrays - or even just clip all arrays, though there are a few that we need to keep in their entirety (hiddenlabels, selectedpoints...)

@chriddyp
Copy link
Member

Would this change the way selectedData would work too? Since selectedpoints is part of the figure now?

@chriddyp chriddyp changed the title new Graph props for current state of figure "Live Figure" - new Graph props for current state of figure Feb 17, 2020
@chriddyp
Copy link
Member

Re selections - This API would be quite nice for writing apps with parcats selection. restyleData only contains the last selection, rather than the current state of the selected axes:
image

@emmanuelle
Copy link
Contributor

What should be the content of this prop? The whole layout, with a few exceptions (layout.mapbox.layers can be huge for example. Any others?). selectedData can be large too but it's quite useful (see the parcats example above) so probaby we should keep it? @nicolaskruchten

@alexcjohnson
Copy link
Collaborator Author

The key criterion, IMO, is that anything that can potentially be modified via GUI actions must be included, whether it actually was modified or not. So that would absolutely include selectedpoints despite its potentially large size.

Beyond that, we could imagine sending back either:

  • everything but the trace data arrays and a few large layout items like mapbox layers
  • only a whitelisted set of GUI-editable attributes
  • something in between that's easier to generate?

I don't offhand see any really simple option, but if we want to whitelist the editable attributes they can be found by looking for the _guiRelayout and _guiRestyle wrappers these edits use to work with the uirevision system.

The most robust way to do this might be to bake it into plotly.js - mark each of these attributes as editable within the attribute spec, which we could test during the _guiRelayout and _guiRestyle calls to ensure we haven't missed anything, then make a dedicated API method to traverse the figure and clone only those attributes.

The simpler way, but harder to keep in sync with new plotly.js features, would be to just write this partial clone into dcc.Graph with a fixed collection of attributes.

Search plotlyjs source for `_guiRe`
 ~/plotly/plotly.js/src> ag _guiRe
traces/sankey/base_plot.js
126:            Registry.call('_guiRestyle', gd, {

traces/sankey/render.js
756:    Registry.call('_guiRestyle', gd, {

components/legend/handle_click.js
126:        Registry.call('_guiRelayout', gd, 'hiddenlabels', hiddenSlices);
235:        Registry.call('_guiRestyle', gd, attrUpdate, attrIndices);

components/legend/draw.js
333:                            Registry.call('_guiRelayout', gd, {'legend.x': xf, 'legend.y': yf});
441:                return Registry.call('_guiRestyle', gd, update, traceIndex);

components/modebar/buttons.js
315:    Registry.call('_guiRelayout', gd, aobj);
371:    Registry.call('_guiRelayout', gd, layoutUpdate);
428:    Registry.call('_guiRelayout', gd, aobj);
482:    Registry.call('_guiRelayout', gd, layoutUpdate);
538:            Registry.call('_guiRelayout', gd, id + '.projection.scale', newScale);
581:    Registry.call('_guiRelayout', gd, 'hovermode', newHover);
618:        Registry.call('_guiRelayout', gd, layoutUpdate);
652:        Registry.call('_guiRelayout', gd, setSpikelineVisibility(gd));
713:    Registry.call('_guiRelayout', gd, aObj);
733:    Registry.call('_guiRelayout', gd, aObj);

components/shapes/draw_newshape/display_outlines.js
52:            Registry.call((opts || {}).redrawing ? 'relayout' : '_guiRelayout', gd, updateObject);

components/shapes/draw.js
386:        Registry.call('_guiRelayout', gd, editHelpers.getUpdateObj());
768:        Registry.call('_guiRelayout', gd, {

components/rangeslider/draw.js
323:        Registry.call('_guiRelayout', gd, axisOpts._name + '.range', [dataMin, dataMax]);

components/titles/index.js
245:                    Registry.call('_guiRestyle', gd, prop, text, traceIndex);
247:                    Registry.call('_guiRelayout', gd, prop, text);

components/annotations/draw.js
641:                        Registry.call('_guiRelayout', gd, getUpdateObj());
731:                    Registry.call('_guiRelayout', gd, getUpdateObj());
756:                Registry.call('_guiRelayout', gd, getUpdateObj());

components/rangeselector/draw.js
71:                Registry.call('_guiRelayout', gd, update);

components/colorbar/draw.js
607:                    Registry.call('_guiRestyle', gd, update, opts._traceIndex);
609:                    Registry.call('_guiRelayout', gd, update);

plot_api/index.js
19:exports._guiRestyle = main._guiRestyle;
20:exports._guiRelayout = main._guiRelayout;

plots/cartesian/dragbox.js
305:                            Registry.call('_guiRelayout', gd, attrStr, v);
829:        Registry.call('_guiRelayout', gd, attrs);
845:                Registry.call('_guiRelayout', gd, updates);

plot_api/plot_api.js
3896:exports._guiRelayout = guiEdit(relayout);
3897:exports._guiRestyle = guiEdit(restyle);

plots/cartesian/select.js
627:                Registry.call('_guiRelayout', gd, {

plots/ternary/ternary.js
568:            Registry.call('_guiRelayout', gd, makeUpdate({a: 0, b: 0, c: 0}));
680:        Registry.call('_guiRelayout', gd, makeUpdate(mins));
754:        Registry.call('_guiRelayout', gd, makeUpdate(mins));

plots/polar/polar.js
904:        Registry.call('_guiRelayout', gd, updateObj);
930:            Registry.call('_guiRelayout', gd, updateObj);
1074:            Registry.call('_guiRelayout', gd, _this.id + '.radialaxis.angle', angle1);
1076:            Registry.call('_guiRelayout', gd, _this.id + '.radialaxis.range[' + rngIndex + ']', rprime);
1288:        Registry.call('_guiRelayout', gd, updateObj);

plots/geo/geo.js
450:        Registry.call('_guiRelayout', gd, updateObj);

@nicolaskruchten
Copy link
Contributor

everything but the trace data arrays and a few large layout items like mapbox layers

this seems pretty easy, no? basically exclude everything with an src key next to it, plus mapbox layers and layout.images.src or something

@nicolaskruchten
Copy link
Contributor

BTW this issue is titled "live figure" but I think a better name would be "slim figure" :)

@alexcjohnson
Copy link
Collaborator Author

I'm a little hesitant to include "everything but the big entries" unless there's a clear use case for it. If we say "we're just including the values that can be changed by the GUI" that necessarily provides all the info about what users have done on the graph which is the primary reason for this prop, minimizes the overhead of generating this object, and makes it completely obvious which attributes are and are not included.

Anyway if we do go that route:

exclude everything with an src key next to it

As far as plotlyjs is concerned those keys are just an artifact of schema generation; any attribute that's a data_array or has the arrayOk flag gets a corresponding src. So yes, we could use that same logic to clone without those attributes if we build this inside plotlyjs. Maybe the arrayOk attributes we'd still include if they aren't arrays? From DCC we would have a hard time accessing that info.

plus mapbox layers and layout.images.src or something

Yes, we'd want to comb through the layout schema to see if there's anything else in this category, but should be straightforward.

@Kevinckyy
Copy link

Is there any progress on this? I have got stuck in achieving selected legends to sync two graphs. 😢

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests

5 participants