bokeh / bokeh

Interactive Data Visualization in the browser, from Python
https://bokeh.org
BSD 3-Clause "New" or "Revised" License
19.37k stars 4.19k forks source link

[FEATURE] Interactive Legend: More mode options #8841

Open wochner opened 5 years ago

wochner commented 5 years ago

When there are many lines glyphs in one plot, it is hard to discern them. We can already highlight the glyphs making use of the click_policy and muted_glyph. However clicking through a long list of legend entries is tidious and it would be much more elegant to hover over them.

When hovering over legend entries, the corresponding glyph should be highlighted. @birdsarah has provided an example in a previous request.

bryevdv commented 5 years ago

I would propose to add a property to legend that governs the interactive policy:

bryevdv commented 5 years ago

Click policy could potentially also be differentiated:

The issue to solve with click_select is how do users get back to the original state, where nothing is muted/hidden?

wochner commented 5 years ago

An alternative to click select could be to give an option to

The glyphs can already be modified to be highlighted or muted, by setting different muted_glyph properties. As example, we can set alpha=0.5 and muted_alpha=1.

bryevdv commented 5 years ago

@wochner sorry I don't quite understand the relation to my question, which is really a UX one. If we have a mode where clicking on one item in a legend automatically mutes all the others, how do we get back to a state with nothing muted? We can't do it by clicking on any individual item, that will just repeat the process with muting everything else. So then... double clicking? right clicking? clicking outside the legend (but then what about interfering with taps)? some explicit button added to legends? Concerned specifically about UI discoverability.

wochner commented 5 years ago

I think a new property highlight could be added to the existing click_policy properties hide and mute. Then same behaviour as with mute, just inverted. You click on one element, others get muted, you click on same element again, others come back.

This should also work for multiple selctions. We would have to deselect all legend entries to get back to the natural state, but this is the same as we currently do with muted.

bryevdv commented 5 years ago

I might be ok with that for single selections, where the meaning of a click can always be expressed uniformly as: "toggle the muted status of everything besides this item". But admitting multiple selections means having to compose and reason about inverse sets and that gets complicated quickly and I don't think the meaning of any individual click in that case can be expressed succinctly (which leads me to be -1 on it) i.e. sometimes a click toggles everything else, sometimes it doesn't, it just depends on what you've clicked already up to that point.

wochner commented 5 years ago

I agree that the “toggle everything else” may become complex to implement and have side effects. Hence the suggestion to simply change the selected legend entry text to bold when ‘click_property = highlight’

bodacious-bill commented 5 years ago

I'd like to implement something like this, if even for my personal use.

How would I go about extending the Legend model or making a new version that could achieve this?

I'm having trouble combing through the source code and actually finding where the "legend" code hooks into the "figure" code, such as in the kwargs for the "line" function in this example:

https://bokeh.pydata.org/en/latest/docs/user_guide/interaction/legends.html

bryevdv commented 5 years ago

@bodacious-bill Almost all the work anywhere in Bokeh is actually done in BokehJS, which is written in TypeScript, e.g.

https://github.com/bokeh/bokeh/blob/master/bokehjs/src/lib/models/annotations/legend.ts

Typically on the Python side there is nothing more than defining properties on the classes. These properties get mirrored to BokehJS, and BokehJS uses their values to do whatever is needed in the browser. The User's Guide section on extending Bokeh is the best place to look for information about the relationship between Bokeh and BokehJS:

https://bokeh.pydata.org/en/latest/docs/user_guide/extensions.html

Then you would need to start going through the Developer's Guide to get set up and building BokehJS from source as a start:

https://bokeh.pydata.org/en/latest/docs/dev_guide.html

bill-coman commented 5 years ago

Regarding the click select concept, could the reset button not be used to restore initial state where nothing is muted?

Perhaps an initial click could mute all but the entry which was clicked. Subsequent clicks could then be used to unmute specific muted entries? Although I have no idea how difficult this might be to accomplish.

The reset button seems reasonable all the same?

Regards,

bryevdv commented 4 years ago

@tony-bony do you have any thoughts on this question:

The issue to solve with click_select is how do users get back to the original state, where nothing is muted/hidden?

A discoverable way to get back to the start state in the "inverse selection" mode is really all that is needed for someone to start working on this.

This was proposed:

The reset button seems reasonable all the same?

However I don't see how that can work as there is no mandate or guarantee that a reset tool be present, in general, and this kind of legend may be desired on a plot with no toolbar visible at all.

tony-bony commented 4 years ago

Good discussion.

I agree that new changes to user interaction with Legend should not rely on presence of the Reset toolbar button to restore the original state. Also restoring the original state should not come at an expensive cost of clicking on tens of legend items.

The most important is backwards compatibility

So the changes should just extend the current functionality, providing default values to new features so that if those are not used everything works as it is now.

New options could be possibly added to 'click_policy' like e.g. 'highlight' in combination with the existing two ones so we would end up e.g. with something like this:

click_policy = ['none', 'mute', 'hide', 'highlight_mute', 'highlight_hide']

So highlight the item that was clicked and then mute or hide the others.

The two new options sound pretty self-contradicting (highlight ? or mute?) so maybe it would be better to call it highlight_mute_others and highlight_hide_others ? So far the options was referring only to the legend item and corresponding glyph while new options try also to describe what happens to others...

Additionally a new hover_policy could be added to handle the mouseover interaction with exactly the same options:

hover_policy = ['none', 'mute', 'hide', 'highlight_mute', 'highlight_hide']

The user can hover over a legend item and click on it to freeze the current view. The default value would be none

For multi-selections on click user could use CTRL / CMD which would toggle the legend item that was clicked together with corresponding glyph.

It could be also nice if clicking on the glyph itself had the same effect as clicking on the corresponding legend item (this is what I currently do in my plots via a JS callback)

For restoring the original state a double click on the plot or on the legend could be used (my current JS callback implementation). Of course clicking the Reset button (if present) should do the job as well.

Easier said than done. With some assistance I could try to do implement this changes in BokehJS...

tony-bony commented 4 years ago

I think that it could be even better if we could just add a callbacks, like JS js_on_click(), js_on_event() and js_on_change() to a Legend and / or LegendItem models to make it more consistent with other Bokeh models. This could allow the user to add a custom behaviours on events like mouseenter, mouseleave, mousemove, wheel etc...

bryevdv commented 4 years ago

That also seems like a reasonable thing to add though it might make sense to split off into a separate issue. The inverse hover/select (this original issue) should also just work out of the box.

AntonBiryukovUofC commented 3 years ago

this would be a really awesome feature to have, as it aligns the interactive legend usage with most commercial viz software tools out there

Also, what is the easiest way to reset the selection for the legend?

p.js_on_event(DoubleTap, CustomJS(args=dict(p=p), code='p.reset.emit()'))

does not seem to reset the legend

ehknight commented 3 years ago

Hey all, is this issue still being worked on?

bryevdv commented 3 years ago

No one has ever done any implementation work yet, AFAIK.