Search code examples
altair

How do I show all values in an aggregated tooltip?


enter image description here

I would like myval to show the name of each car for each aggregated year, ex. "chevrolet chevelle malibu".

The [object Object] thing appears to be JavaScript related.

import altair as alt
from vega_datasets import data
import pandas as pd
import pdb

df = data.cars()
alt.renderers.enable("altair_viewer")


mychart = (
    alt.Chart(df)
    .transform_joinaggregate(count="count(*)", myval="values(Name)", groupby=["Year"])
    .mark_bar()
    .encode(
        x=alt.X(
            "Year",
            timeUnit=alt.TimeUnitParams(unit="year", step=1),
            type="quantitative",
        ),
        y=alt.Y("count", type="quantitative"),
        tooltip=alt.Tooltip(["myval:N"]),
    )
)

mychart.show()

Solution

  • This is a great question, and I'm not sure there's a satisfactory answer. The reason this is displayed as [object Object], [object Object], etc. is because the values aggregate returns a list of the entire row for each value. So the full representation would be something like this:

    [{'Name': 'chevrolet chevelle malibu', 'Miles_per_Gallon': 18.0, 'Cylinders': 8, 'Displacement': 307.0, 'Horsepower': 130.0, 'Weight_in_lbs': 3504, 'Acceleration': 12.0, 'Year': 1970, 'Origin': 'USA'}, {'Name': 'buick skylark 320', 'Miles_per_Gallon': 15.0, 'Cylinders': 8, 'Displacement': 350.0, 'Horsepower': 165.0, 'Weight_in_lbs': 3693, 'Acceleration': 11.5, 'Year': 1970, 'Origin': 'USA'}, ...]
    

    and those are just the first two entries! So clearly it won't really fit in a tooltip. For what it's worth, newer versions of Vega improve on this (which you can see by viewing the equivalent chart in the vega editor) but it's still not what you're looking for.

    What you need is a way to extract just the name from each value in the list... and I'm sure that Vega-Lite transforms provide any good way to do that (the vega expression language does not have anything that resembles list comprehensions or function mapping).

    The best I can think of is something like this, to display, say, the first 4 values:

    enter image description here

    mychart = (
        alt.Chart(df)
        .transform_joinaggregate(count="count(*)", myval="values(Name)", groupby=["Year"])
        .transform_calculate(
            first_val="datum.myval[0].Name",
            second_val="datum.myval[1].Name",
            third_val="datum.myval[2].Name",
            fourth_val="datum.myval[3].Name",
          )
        .mark_bar()
        .encode(
            x=alt.X(
                "Year",
                timeUnit=alt.TimeUnitParams(unit="year", step=1),
                type="quantitative",
            ),
            y=alt.Y("count", type="quantitative"),
            tooltip=alt.Tooltip(["first_val:N", "second_val:N", "third_val:N", "fourth_val:N"]),
        )
    )
    

    Another option would be, instead of using a tooltip, to use a second chart that updates on mouseover:

    enter image description here

    base = (
        alt.Chart(df)
        .transform_joinaggregate(count="count(*)", values="values(Name)", groupby=["Year"])
    )
    
    selection = alt.selection_single(fields=['Year'], on='mouseover', empty='none')
    
    bars = (
        base    
        .mark_bar()
        .encode(
            x=alt.X(
                "Year:N",
                timeUnit=alt.TimeUnitParams(unit="year", step=1),
                type="quantitative",
            ),
            y=alt.Y("count", type="quantitative"),
        )
    ).add_selection(selection)
    
    text = (
        base
        .transform_filter(selection)
        .transform_flatten(['values'])
        .transform_calculate(Name="datum.values.Name")
        .mark_text()
        .encode(
            y=alt.Y('Name:N', axis=None),
            text='Name:N'
        )
    ).properties(width=300)
    
    chart2 = bars | text
    

    I'd be interested to see if anyone knows of a more complete solution.