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

ability to preserve user interaction state between redraws #198

Closed
chriddyp opened this issue May 8, 2018 · 18 comments
Closed

ability to preserve user interaction state between redraws #198

chriddyp opened this issue May 8, 2018 · 18 comments

Comments

@chriddyp
Copy link
Member

chriddyp commented May 8, 2018

discussion taken from here: https://community.plot.ly/t/preserve-trace-visibility-in-callback/5118/5

More generally, I wonder if we should just have some kind of flag that tells the graph whether it should reset user interactions or not (like zooming, panning, clicking on legends, clicking on mode bar items). In some cases, like if you were to switch the chart type or display completely different data, you’d want to reset the user interactions. In other cases, you wouldn’t necessarily want to.

@nicolaskruchten
Copy link
Contributor

+1 from the react-plotly.js perspective! Right now the way to achieve this is pretty boilerplate-y.

Much like my comment in #1849 I think this could/should be a flag to .react() that could control its behaviour, in this case telling plotly not to overwrite a certain basket of state if it's changed.

OTOH I could also see an argument for saying "no, do this at the upper level, in Dash or in react-plotly.js".

@chriddyp
Copy link
Member Author

We talked about this today and it looks like @alexcjohnson has a way forward

@alexcjohnson alexcjohnson self-assigned this Oct 18, 2018
@alexcjohnson
Copy link
Collaborator

For the record, here's what I'm implementing:

  • I've become convinced that this solution belongs inside Plotly.js - it has applicability outside Dash, and within plotly.js we can ensure impliedEdits are handled smoothly (like clearing autorange when range gets set) without having to do a whole extra diff. Also, this might be weird, but if an app is calling restyle or relayout for some changes and react for others, if this was handled outside Plotly.js we wouldn't be able to tell the difference between these changes, which presumably should NOT be persisted across react calls, and GUI-initiated changes that should.
  • We'll add an attribute to layout that, if it's truthy and did not change with the new Plotly.react call, causes GUI-initiated changes to persist despite what's in the new figure. If it's falsy (or omitted) or it DID change, we pass through the figure exactly as given, like we do now.
  • Docs will encourage using a string, something based on the content/meaning of the plot like 'cost vs time' so if the meaning changes to 'cost vs cumulative quantity' we will reset the GUI.
  • The logic is mostly fairly simple: any attribute that was changed by user interaction and has the same value in the original (before any user interaction) and new figures gets changed to the post-user-interaction value. But there will be edge cases to worry about, like if your figure sets an explicit xaxis.range and the user autoranged by GUI, then your new figure sets a different xaxis.range - we'll need to also clear autorange otherwise your new range would be discarded.

My question right now is what to call this attribute? A few things come to mind but I'm not super excited about any of them: uikey, guikey, masterkey, guipreservationkey, plotkey, plotversion??? cc @etpinard

@etpinard
Copy link

etpinard commented Oct 30, 2018

  • We'll add an attribute to layout that,

Interesting proposal. But if a user only wants to keep a "few" changes coming from user interactions? For example, keep the updated axis ranges, but not the legend visibilities.

@etpinard
Copy link

My question right now is what to call this attribute?

This new attribute has some similarities with datarevision, so I'm thinking of calling this fxrevision or uirevision, which I find intuitive:

  • if fig1 and fig2 have the same fxrevision that means the interaction will persist from one react call to another,
  • if fig1 and fig2 have different fxrevision, the interactions are "cleared" and therefore fig2 overrides the interactable attributes in fig1.

@alexcjohnson
Copy link
Collaborator

so I'm thinking of calling this fxrevision or uirevision

fx doesn't exist in the user-facing API, only in our internal nomenclature, so I'd rather use ui. You're right that mechanically this has some similarities to datarevision, and I could get behind uirevision, though based on chatting with @chriddyp and @nicolaskruchten I'm hesitant to use revision in the name, as it implies a progression, whereas this feature is about "here's the flavor of chart the user has asked for" and they may flip back and forth between the same values. That said they're going to need to pair this with datarevision changes most likely - so perhaps uirevision is as good as any other name and we just need to explain it well.

But if a user only wants to keep a "few" changes coming from user interactions? For example, keep the updated axis ranges, but not the legend visibilities.

I can see a lot of cool use cases for this, let's do it! We could add copies of the same attribute to specific containers, that would inherit from the base layout attribute. So trace.visible would be controlled by trace.uirevision, and using that you could hide a bunch of traces via the legend, then change data types (to see some other dimensions of the same data set) and the hidden traces would stay hidden. And axis.uirevision could be changed for just yaxis if you changed the y data while keeping the same x data, and then the y axis would reset its range but the x axis would stay zoomed in...

There are also layout.dragmode, hovermode - I suppose we could have those based on layout.uirevision, though that would make it difficult if you want to just preserve those and let everything else reset, since that's the uirevision that everything else is inheriting. Actually it could be nice to group all the modebar settings in one revision attribute - not just dragmode and hovermode but also axis.showspikes and all of these attributes inside 3D scenes. So, layout.modebaruirevision? That's pretty ugly... but I think it would be a great feature to have!

@chriddyp
Copy link
Member Author

(cc @plotly/dash as well)

@etpinard
Copy link

I vote 👍 for uirevision, but I don't have a strong opinion.

We could add copies of the same attribute to specific containers, that would inherit from the base layout attribute.

This sounds interesting, but maybe we'll need to make it even more granular.

And axis.uirevision could be changed for just yaxis if you changed the y data while keeping the same x data, and then the y axis would reset its range but the x axis would stay zoomed in...

So axis.uirevision would also make spikes settings persists? Or should spikes fall under modebaruirevision

Moreover, what about "ui" changes made though updatemenus (e.g. https://codepen.io/etpinard/pen/pbxkNb) or layout sliders? Should these be allowed to persist?

@alexcjohnson
Copy link
Collaborator

Oh, I forgot that we have a layout.modebar now - currently it's just style attributes, all the settings it controls are elsewhere... but I don't think that should stop us from putting a uirevision in there to control dragmode/hovermode/axis.showspikes and whatever else is controlled primarily by the modebar (it has zoom controls too but those we'll handle per axis).

So yeah, we can have layout.uirevision as the global default but every gui-editable attribute will actually be controlled by one of the container properties.

So axis.uirevision would also make spikes settings persists? Or should spikes fall under modebaruirevision

As mentioned above, I think spikes belong with modebar.uirevision - the rationale being that axis ranges depend on the data linked to an axis, whereas spikes, despite being defined within the axis container, are really about how the user wants to interact with the whole plot.

Similarly, for trace visibility - I'm starting to think that instead of (or at least intermediate to) putting a uirevision in each trace we might want to use legend.uirevision to control this, since (a) that's where users interact with trace visibility (b) it'd be a pain to include this in every trace, (c) pies use layout.hiddenlabels for this rather than a trace attribute.

Trying to figure out what happens if you add/remove (non-pie) traces and yet want visibility to persist. It works fine if the alterations are at the end of gd.data but if you insert or remove a trace in the middle or at the beginning (which would likely happen for example if you have a multi-select or set of checkboxes to determine which traces to show) - I'm thinking perhaps we map the old-to-new visibilities via trace.uid - which already persists in trace order if they're not provided, but you can override them if you care.

Moreover, what about "ui" changes made though updatemenus or layout sliders? Should these be allowed to persist?

That seems like a can of worms, and I doubt anyone interested in this feature would be using updatemenus or sliders, they'd be using an off-plot control that does the same thing. So my gut reaction is no, don't persist these.

What about editable: true (or some partial version of that)? That's everything from plot, axis, and colorbar titles to annotation and shape positions. Those I definitely can see wanting to persist. It might not be obvious though in every case which uirevision attribute would control it... but we can cross that bridge when we get there. There might be some that are controlled by the base layout.uirevision, we'll see as this goes along.

@etpinard
Copy link

  • layout.uirevision
  • layout.legend.uirevision
  • layout.modebar.uirevision
  • layout.?axis?.uirevision

all sound great 🏆

I doubt anyone interested in this feature would be using updatemenus or sliders, they'd be using an off-plot control that does the same thing. So my gut reaction is no, don't persist these.

Yeah, that's what I'm thinking to. I just wanted to make we weren't missing anything.

What about editable: true (or some partial version of that)? That's everything from plot, axis, and colorbar titles to annotation and shape positions. Those I definitely can see wanting to persist.

Good point here. Yes, those should be able to persist. Maybe we could group them together as layout.editrevision?

@chriddyp
Copy link
Member Author

Another edge case to consider would be selected points: ability to persist selections through updates. For now, that'd just mean the points/bars but in the feature that might include the actual rectangle (plotly/plotly.js#1851)

@alexcjohnson
Copy link
Collaborator

layout.editrevision?

Another edge case to consider would be selected points: ability to persist selections through updates.

Alright, seems like we all agree on the general framework, but some of these specifics we'll need to figure out one by one. For example I'm not sure all the edits should be controlled by the same revision id, perhaps moving an annotation on a certain axis should be controlled by the revision of that axis? Or maybe that's too complicated... anyway I'll see how it's working and we can discuss further in a PR.

@nicolaskruchten
Copy link
Contributor

So the main use-case I see is actually people wanting things to be preserved in general and not actually resetting them all that often, although obviously the ability to do is important. Under the current proposal, this would be accomplished by simply setting layout.uirevision = "the revision" and leaving it that way? Alternatively, one could cause only a subset of interactions to be preserved by setting-and-forgetting the other, sub-uirevision fields individually rather than the top-level one?

@alexcjohnson
Copy link
Collaborator

Yep @nicolaskruchten, both of those would work as you're describing.

@nicolaskruchten
Copy link
Contributor

Cool. Could they just set uirevision = true, out of curiosity?

Maybe uistate or uistatekey or uistateid would be a good name?

@alexcjohnson
Copy link
Collaborator

Cool. Could they just set uirevision = true, out of curiosity?

Yes, just like datarevision it will be valType: 'any', we're just going to test for newVal !== oldVal and neither one === undefined.

Maybe uistate or uistatekey or uistateid would be a good name?

uistate has almost the opposite meaning, I'd say - we're essentially saying "if didn't change, then ignore the ui state in the new figure and keep what the user created." The other two have reasonable meaning but I like @etpinard's goal to avoid 3-word attribute names.

@etpinard
Copy link

etpinard commented Nov 1, 2018

uistate has almost the opposite meaning, I'd say - we're essentially saying "if didn't change, then ignore the ui state in the new figure and keep what the user created."

👍

The other two have reasonable meaning but I like @etpinard's goal to avoid 3-word attribute names.

👍

@etpinard
Copy link

Completed in plotly/plotly.js#3236, set to be released in plotly.js@1.43.0

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

4 participants