I generate a line chart in Altair. I'd like to control which lines are "on top" of the stack of lines. In my example here, I wish for the red line to be on top (newest date) and then descend down to the yellow (oldest date) to be on the bottom.
I tried to control this with the sort
parameter of of alt.Color
but regardless of sort='ascending'
or sort='descending'
the order of the line overlap will not change.
How can I control this? Was hoping I can do this without sorting my source dataframe itself.
data = [{'review_date': dt.date(year=2022, month=2, day=24), 'a':19, 'b':17, 'c':12, 'd':8},
{'review_date': dt.date(year=2022, month=2, day=23), 'a':20, 'b':16, 'c':14, 'd':8},
{'review_date': dt.date(year=2022, month=2, day=22), 'a':22, 'b':16, 'c':14, 'd':10},
{'review_date': dt.date(year=2022, month=2, day=21), 'a':14, 'b':13, 'c':12, 'd':5},]
df = pd.DataFrame(data).melt(id_vars=['review_date'], value_name='price', var_name='contract')
df.review_date = pd.to_datetime(df.review_date)
domain = df.review_date.unique()
range_ = ['red', 'blue', 'gray', 'yellow']
alt.Chart(df, title='foo').mark_line().encode(
x=alt.X('contract:N'),
y=alt.Y('price:Q',scale=alt.Scale(zero=False)),
color=alt.Color('review_date:O', sort="ascending", scale=alt.Scale(domain=domain, range=range_) )
).interactive()
By default, graphical marks are plotted in the order they occur in the dataframe (as you noted), which means that the elements last in the dataframe will be plotted last and end up on top in the chart (called the highest "layer" or the highest "z-order"):
import pandas as pd
import altair as alt
df = pd.DataFrame({
'a': [1, 2, 1, 2],
'b': [1.1, 2.1, 1.0, 2.2],
'c': ['point1', 'point1', 'point2', 'point2']
})
alt.Chart(df).mark_circle(size=1000).encode(
x='a',
y='b',
color='c'
)
When you set the sort
parameter of the color encoding, you are not changing the z-order for the dots, you are only changing the order in which they are assigned a color. In the plot below, "point2" is still on top, but it is now blue instead of orange:
alt.Chart(df).mark_circle(size=1000).encode(
x='a',
y='b',
color=alt.Color('c', sort='descending')
)
If we wanted to change the z-ordering so that "point1" is on top, we would have to specify this with the order
encoding:
alt.Chart(df).mark_circle(size=1000).encode(
x='a',
y='b',
color='c',
order=alt.Order('c', sort='descending')
)
However, as you can read in the Vega-Lite documentation the order
encoding has a special behavior for stacked and path marks, including line mark, where it controls the order in which the points are connected in a line rather than their z-ordering/layering.
Therefore, I believe the only way you can achieve the desired behavior is by sorting that column. You can do this during chart construction:
alt.Chart(df).mark_line(size=10).encode(
x='a',
y='b',
color='c'
)
alt.Chart(df.sort_values('c', ascending=False)).mark_line(size=10).encode(
x='a',
y='b',
color='c'
)