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

Should JupyterCharts fire a callback when the selection changes? #3238

Open
joelostblom opened this issue Oct 25, 2023 · 10 comments
Open

Should JupyterCharts fire a callback when the selection changes? #3238

joelostblom opened this issue Oct 25, 2023 · 10 comments

Comments

@joelostblom
Copy link
Contributor

joelostblom commented Oct 25, 2023

I'm experimenting with using Altair together with the Shiny dashboarding framework for Python, and thanks to the JupyterChart widget that @jonmmease added it seems like this is working out of the box! This is super convenient and means that Altair is one of the few (maybe the only?) viz package can trigger events from chart selections with Shiny.

One comment I received when trying to understand better how to make Altair and Shiny work nicely together was that it would be easier if jchart.observe(callback, "selections") would fire a callback when the selection changes. I am not familiar enough with the internals of JupyterChart to know if this is something that would be beneficial to add, so I wanted to bring it up here and hear if anyone has thoughts on this, mostly @jonmmease.

@jonmmease
Copy link
Contributor

Each individual selection is a traitlet property of the jchart.selections object, this means that you would do jchart.selections.observe(callback, "selectionA"), where the selection param is named selectionA.

Does this workflow not line up with what Shiny needs? It might be possible to make jchart.selections an Instance traitlet property and manually trigger callbacks when any individual selection changes, but it would be a bit of a hack since the jchart.selections object itself isn't replaced when a selection changes.

@cpsievert
Copy link

cpsievert commented Oct 25, 2023

Hi @jonmmease 👋 😄

Does this workflow not line up with what Shiny needs?

It does, I was just a bit surprised that jchart.observe(callback, "selections") doesn't fire (and thus, I think Shiny users will also surprised that reactive_read(jchart, "selections") doesn't invalidate a reactive context).

I don't have enough context to have a strong opinion on whether jchart.observe(callback, "selections") would be worthwhile supporting, and I can see how it wouldn't be recommended if you only want a particular selection type, but do I think it'd be helpful from a getting started perspective.

@mattijn
Copy link
Contributor

mattijn commented Oct 25, 2023

Awesome that you have a working example @cpsievert! I'm not so much in favour of supporting a general jchart.observe(callback, "selections"), since we are in reality observing params in Vega-lite. jchart.params.observe(callback, any) is something that come closest to my mind, but I don't see that being less confusing.

@jonmmease, I remember we discussed this at the VegaConf as well, currently we have to differentiate between params and selections to observe changes (xref vega/vegafusion#350 (comment)), eg:

jchart.params.observe(on_width_change, ["width"])
jchart.selections.observe(on_interval_change, ["interval"])

For me, and according the VL-docs, selections are a subset of params and I personally prefer to have the selection names available within jchart.params. What do you think about this? Could a good moment to introduce this change be the moment we can set selections states?

@cpsievert, I have one question if you don't mind. I was playing a bit with the shiny approach, but I'm not really sure how I can update widgets. I can reactive_read, but how to set something? For example, how would this example works in shiny given a toggle button. I tried the following, but this doesn't work, Open the Chart in the Shiny Editor

[..]
jchart = alt.JupyterChart(chart)
register_widget("my_chart", jchart)    

@reactive.event(input.toggle)
def _():
    jchart.chart = chart.mark_bar(color="crimson", cornerRadius=10)

@cpsievert
Copy link

@mattijn you were so close...you just need to use the @reactive.Effect() decorator in tandem with @reactive.event()

@mattijn
Copy link
Contributor

mattijn commented Oct 26, 2023

Ah great! Thanks @cpsievert!

Another way how we can make this more convenient is to accept an alt.Parameter in the observe instead of only a str. This avoids double bookkeeping of defined names within params/selections and declared variables of params/selections.
Example:

brush = alt.selection_interval() # no manual defined `name=`
chart = alt.Chart().add_params(
    brush
)
jchart = alt.JupyterChart(chart)
jchart.params.observe(callback, brush) # name of `brush` can be found using `brush.name`

But would this hold elsewhere? Eg. is the following still valid?

reactive_read(jchart.params, brush)

Would be great!

@cpsievert
Copy link

Nice, shinywidgets wouldn't support that (i.e., passing a non-string to observe()) out of the box, but it seems like it could.

Also, as long as we're stilling dreaming up an API, jchart.observe(callback, brush) would be pretty neat :)

@mattijn
Copy link
Contributor

mattijn commented Oct 26, 2023

Agree!

@shawnhu444
Copy link

@cpsievert,Indeed, it was unexpected for me to discover that the jchart.observe(callback, "selections") method does not trigger, which I believe will similarly catch Shiny users off guard when they notice that reactive_read(jchart, "selections") does not refresh the reactive environment.

Without more information, I don't feel qualified to firmly advocate for or against the support of jchart.observe(callback, "selections"). Although I understand the reasoning behind not promoting its use, especially when one is interested in monitoring only specific types of selections, I do think its inclusion could lower the initial learning curve for newcomers.

@mattijn
Copy link
Contributor

mattijn commented Nov 12, 2023

Hi @shawnhu444! Thanks for commenting. I'm not a Shiny user myself, but I'm trying to understand this further. Could you explain why you would expect that jchart.observe(callback, "selections") should trigger something? Is "selections" representing something in shiny and used in other circumstances?

@mattijn
Copy link
Contributor

mattijn commented Nov 12, 2023

Btw, I came to realise that it is not necessary to use a str when doing an observe on the jchart, but that an alt.Parameter is already accepted. That means the double bookkeeping is not necessary, one step closer in our dream API:)

import altair as alt
from vega_datasets import data
from ipywidgets import HTML, HBox

source = data.cars()
hover = alt.selection_point(on='mouseover', empty=False)

chart = alt.Chart(source).mark_circle(size=60).encode(
    x='Horsepower',
    y='Miles_per_Gallon',
    color='Origin',
    opacity=alt.condition(hover, alt.value(1), alt.value(0.1)),
    size=alt.condition(hover, alt.value(250), alt.value(50)),    
).add_params(hover)
jchart = alt.JupyterChart(chart)

table_widget = HTML(value=source.iloc[[]].T.to_html())

def my_callback(change):
    table_widget.value = source.iloc[change.new.value].T.to_html()

jchart.selections.observe(my_callback, hover)
HBox([jchart, table_widget])

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

No branches or pull requests

5 participants