Search code examples
pythonaltairvega-lite

Layering charts removes marks from one chart


I have the following data:

  • Various measurements (each with a name) over a time-interval which I want to plot (one line each)
  • One measurement is called 'State' which I want to use to color the background of the chart (vertical slices)

The measurement dataframe looks approx like this:

                     Time Trend  Value
0     2021-03-24 16:10:20     A   -1.3
1     2021-03-24 16:10:35     A   -5.3
2     2021-03-24 16:10:50     A   -6.3
3     2021-03-24 16:11:05     A   -2.3
4     2021-03-24 22:39:05     A   -5.3
...                   ...   ...    ...
12443 2021-03-24 16:10:20     H    0.0
12444 2021-03-24 22:38:20     H    1.0
12445 2021-03-24 22:38:35     H    2.0
12446 2021-03-24 22:38:50     H    3.0
12447 2021-03-24 22:39:05     H    4.0

For this purpose, I extract the time-ranges where a certain state is "active", as here:

      State               Start                 End
0      Idle 2021-03-24 16:10:20 2021-03-24 16:10:50
1      Pump 2021-03-24 16:10:50 2021-03-24 16:20:05
...
5      Cool 2021-03-24 20:42:20 2021-03-24 22:01:35
6      Pump 2021-03-24 22:01:35 2021-03-24 22:02:50

I then create two charts which I would like to combine with alt.layer

  • One with lines for each Measurement (Trend)
  • One with rectangles for each State
 alt_st = (
        alt.Chart(state_df)
        .mark_rect()
        .encode(
            x=alt.X("Start", title="Time"),
            x2=alt.X2("End", title="Time"),
            y=alt.value(15),
            y2=alt.value(0),
            color=alt.Color("State:O", scale=get_state_scale(state_df.State.unique())),
        )
        # .add_selection(selection)
    )

rectangle state chart

chart = (
    alt.Chart(df)
    .mark_line()
    .encode(
        alt.X("Time"),
        alt.Y("Value"),
        color="Trend",
    )
)

measurement chart

Combining with alt.layer:

alt.layer(chart, alt_st).resolve_legend("independent")

Combined chart

As can be seen, the combined chart has a messed up legend and the marks (lines) from the first chart) are gone.

What I tried so far:

  • Looking at Issues in the GitHub repo of altair and Vega-Lite
  • Changing the order (doesn't help)
  • Using vConcat instead (doesn't help)
  • Removing the custom scale from the state-chart (this helped but the result wasn't pretty either)

The last thing I found out when trying to create a minimal example here.

without scale

I found a similar question (Altair: Layered Line Chart with Legend and Custom Colors) but couldn't work out how to apply the solution for my case.

My impression so far is that what I'm doing is very unidiomatic for Altair/Vega-Lite - if someone could point out the relevant concepts I'm missing I would be glad.


Solution

  • What you have at the end looks correct, but you would want to use resolve_scale(color='independent') instead of resolve_legend('independent') to separate the legends correctly:

    import altair as alt
    from vega_datasets import data
    
    source = data.iowa_electricity()
    # Create end year for mark_rect
    source['end_year'] = source.groupby('source')['year'].shift(-1)
    source = source.dropna()
    
    line = alt.Chart(source).mark_line().encode(
        x="year:T",
        y="net_generation:Q",
        color="source:N")
    
    bar = alt.Chart(source).mark_rect().encode(
        x="year:T",
        x2="end_year",
        color='year(year):O',
        y=alt.value(15),
        y2=alt.value(0)
    )
    
    (bar + line).resolve_scale(color='independent')
    

    enter image description here

    You can move the bar to the bottom with mark_rect(yOffset=270, y2Offset=300) (offsets are in pixels rather than chart units, the same as when you use alt.value for y and y2):

    enter image description here