pythonvega-litealtair

Use brush for transform_calculate in interactive altair char


I have an interactive plot in altair/vega where I can select points and I see a pie chart with the ratio of the colors of the selected points.

import altair as alt
import numpy as np
import polars as pl

selection = alt.selection_interval(encodings=["x"])

base = (
    alt.Chart(
        pl.DataFrame(
            {
                "x": list(np.random.rand(100)),
                "y": list(np.random.rand(100)),
                "class": list(np.random.choice(["A", "B"], 100)),
            }
        )
    )
    .mark_point(filled=True)
    .encode(
        color=alt.condition(
            selection, alt.Color("class:N"), alt.value("lightgray")
        ),
    )
    .add_params(selection)
)

alt.hconcat(
    base.encode(x="x:Q", y="y:Q"),
    (
        base.transform_filter(selection)
        .mark_arc()
        .encode(theta="count()", color="class:N")
    ),
)

The outcome looks like this: enter image description here

Now I'd like to add two more charts that show the ratio of selected / unselected points for each color. I.e. one pie chart that is orange / gray and one pie chart that is blue / gray with ratios depending on the number of selected points.

I tried to use the selection like this

    (
        base.mark_arc().encode(
            theta="count()",
            color=alt.condition(
                selection, alt.Color("class:N"), alt.value("gray")
            ),
            row="class:N",
        )
    ),

But it's not what I want:

enter image description here

What's the best way to add the pie charts I want?


Solution

  • There might be an easier way to do this, but the following works:

    alt.hconcat(
        base.encode(x="x:Q", y="y:Q"),
        (
            base.mark_arc(theta=4)
            .transform_joinaggregate(class_total='count()', groupby=['class'])
            .transform_filter(selection)
            # Including class_total in the groupby just so that column is not dropped since we need it in the calculate
            .transform_aggregate(count_after_filter='count()', groupby=['class', 'class_total'])
            .transform_calculate(proportion_selected=alt.datum.count_after_filter / alt.datum.class_total)
            .encode(
                # Setting the domain to reflect that we computed a proportion
                theta=alt.Theta('proportion_selected:Q').scale(domain=(0, 1)),
                row='class',
                color='class'
                # tooltip='proportion_selected:Q'
            )
        )
    )
    

    enter image description here

    Together with your initial chart, this could make for an interesting gallery example if you want to create a PR.

    For a grey background, I think you would have to layer with a completely filled in grey chart, similar to this layered histogram here https://altair-viz.github.io/gallery/interval_selection_map_quakes.html (should just be alt.Chart().mark_arc(color='lightgrey')