Skip to content
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

feat: Extend .interactive() with tooltip and legend #3394

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from

Conversation

joelostblom
Copy link
Contributor

@joelostblom joelostblom commented Apr 4, 2024

These are some initial ideas for how we could extend the .interactive() method to become more useful. I think having a clickable legend and a tooltip by default would cover many of the basic interactive needs for situations when one does not want more fine-grained control.

vscode-zoom-bug-2024-04-04_09.01.58.mp4

We would still need to add this to the concat charts etc, but I thought I would add to #3393 by creating an example of what this could look like in the code.

I think the addition of the tooltip is pretty straightforward and harmless. The addition of the legend raises the question of what would happen when there is an existing opacity encoding. My suggestion would be to detect this and print a message that the interactive legend has been automatically disabled (and setting legend=False) would suppress that message.

I chose to link the legend click to opacity as I thought this was less intrusive than color or a transform filter, but happy to hear other thoughts (I know that e.g. plotly filters points based on legend clicks)

close #3393

Comment on lines +3089 to +3103
# Detect common legend encodings used in the spec
# legend = [
# enc
# for enc in interactive_chart.encoding.to_dict(validate=False).keys()
# if enc
# in [
# "angle",
# "radius",
# "color",
# "fill",
# "shape",
# "size",
# "stroke",
# ]
# ]
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I am thinking it's more elegant if we set the legend encodings only to encodings that actually exist in the spec. However, to_dict(validate=False) currently does not work for channels as per #3075.

It is possible to do something like this instead, but it seems clunky:

[
    enc for enc in vars(chart.encoding)['_kwds']
    if not isinstance(chart.encoding[enc], alt.utils.schemapi.UndefinedType)
]

It is an order of magnitude faster thought, so maybe that it the way to go:

image

@binste
Copy link
Contributor

binste commented Apr 8, 2024

I'll need to find some more time to review and be helpful but I really like the idea, thanks for getting started on this! I received the feedback from Plotly users that Plotly does more "interactive" things out of the box so I think there could be demand for this.

@dwootton
Copy link
Contributor

dwootton commented Apr 12, 2024

Really excited to see the progress on .interactive! Stefan makes a great point – ease of "getting to first interaction" makes for a positive first impression. This aligns with the user feedback we gathered during Altair Express testing.

Since this PR is still a draft, consider the following discussion points rather than direct implementation feedback:

Behavior Across Concatenated Views: How should filtering behave in multi-view charts? In Altair Express, confusion arose when interactions were chart-specific. Propagating interactions across the entire spec proved more intuitive (ie alx.highlight_brush() + my_multi_view_chart added a brush to each chart and cross filtered the entire visualization. Would a similar approach make sense here?

For example, I would probably expect that my histogram should filter to just the selected Origins:

concat_views_behavior.mp4

Interaction Effect: I love the opacity fade effect for scatterplots. Feels intuitive and looks nice when plots are relatively balanced like the cars datasets. However, for broader chart types, I've found filtering generally provides better usability. For example with bar charts, fades don’t rescale the y axis and as such they're less helpful for investigating lower-count bars.

altair_bar_filter.mp4

Filtering out elements could be more useful and would align with plotly's behavior. In our user study, people seemed to prefer filtering when there were outliers in their data.

outlier_filter.mp4

Great work bringing up changes for .interactive; Let me know your thoughts on these points. Happy to brainstorm potential on trade-offs. Longer term might make sense to roll up some of altair express's code to .interactive.

@joelostblom
Copy link
Contributor Author

Thanks both! I agree that this first interactive impression is important and it's great to hear that this is backed up by your research findings @dylan. Thank you so much for sharing some of the insights from your research!

Interaction Effect: I love the opacity fade effect for scatterplots. Feels intuitive and looks nice when plots are relatively balanced like the cars datasets. However, for broader chart types, I've found filtering generally provides better usability. ...

I 100% agree with this. I think the fade looks nicer and it doesn't have the effect of the axes domain rescaling upon selection with creates a somewhat jarring/jumpy behavior at times. But it is not as general and as you point out, it is not at all useful when there are outliers compressing the rest of the data. I actually switched a personal project over to highlighting to filtering just yesterday for this exact reason and I will update the PR do do the same thing here.

Behavior Across Concatenated Views: How should filtering behave in multi-view charts? ...

This is a great insight and something I didn't even think about. Thanks for sharing it! My preference would be propagation across the entire spec and with filtering instead of opacity highlighting that would be more straightforward.

Longer term might make sense to roll up some of altair express's code to .interactive.

I'm very receptive to plan ahead for this if there are things you already have in mind and you think we should take into account when designing this PR (and you're more than welcome to help out implementing it too if you have time available).


What do you all think about keeping the highlighting behavior on control clicking the legend and having filtering on regular click? On the one hand I worry that we are overloading the legend interaction, but on the other hand, I don't think this would get in the way for people who only clicks the legend without holding control. I can't help but to really like the visual effect of highlighting...

If either of you have more ideas of what we could do here, I would be happy to adjust this PR further with the goal of giving the most helpful default interactivity for everyone who is not interested in setting up their own bindings. One aspect I think would be useful would be to have the cursor change to a "clickable hand" when hovering the legend to indicate that it is indeed. It seems as if this could be a simple change as marks in Vega/Vega-Lite already supports a cursor parameter, but after a brief glance at the source, I can't say that it is immediately obvious what to do so maybe we have to wait for vega/vega-lite#4155...

@mattijn
Copy link
Contributor

mattijn commented Apr 13, 2024

Given the extensive research that has been put in place on this topic by @dwootton, and his offer to investigate how we can integrate this into the .interactive() method. I think we should not rush this and come up with a deliberate approach that is in-line with the best practices discovered by altair-express.

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

Successfully merging this pull request may close these issues.

Add more features to .interactive()?
4 participants