Altair mark_text 位置基于条件
Altair mark_text position based on condition
我有这张图表,其中 mark_text
表示每个柱的值。有一个滑块可以及时移动 back/forth。
我想根据 Y
值更改文本的位置(条形中的数字):如果它大于某个值,它将位于条形顶部的正下方(baseline='line-top
),否则就在上面 (baseline='line-bottom'
)。
这样,随着时间的推移,文本将 below/above 显示在栏的顶部,具体取决于 Y
。
我想我必须使用某些条件或表达式,但我不知道如何使用。
import numpy as np
import pandas as pd
import altair as alt
np.random.seed(0)
################################################################################
model_keys = ['M1', 'M2']
scene_keys = ['S1', 'S2']
layer_keys = ['L1', 'L2']
time_keys = [1, 2, 3]
ys = []
models = []
dataset = []
layers = []
scenes = []
times = []
for sc in scene_keys:
for m in model_keys:
for l in layer_keys:
for s in range(10):
y = np.random.rand(10) / 10
if m == 'M1':
y *= 10
if l == 'L1':
y *= 5
for t in time_keys:
y += 1
ys += list(y)
scenes += [sc] * len(y)
models += [m] * len(y)
layers += [l] * len(y)
times += [t] * len(y)
# ------------------------------------------------------------------------------
df = pd.DataFrame({'Y': ys,
'Model': models,
'Layer': layers,
'Scenes': scenes,
'Time': times})
bars = alt.Chart(df, width=100, height=90).mark_bar().encode(
x=alt.X('Scenes:N',
title=None,
axis=alt.Axis(
grid=False,
title=None,
labels=False,
),
),
y=alt.Y('Y:Q',
aggregate='mean',
axis=alt.Axis(
grid=True,
title='Y',
titleFontWeight='normal',
),
),
)
text = alt.Chart(df).mark_text(align='center',
baseline='line-top',
color='black',
dy=5,
fontSize=13,
).encode(
x=alt.X('Scenes:N'),
y=alt.Y('mean(Y):Q'),
text=alt.Text('mean(Y):Q',
format='.1f'
),
)
bars = bars + text
bars = bars.facet(
row=alt.Row('Model:N',
title=None,
),
column=alt.Column('Layer:N',
title=None,
),
spacing={"row": 10, "column": 10},
)
slider = alt.binding_range(
min=1,
max=3,
step=1,
name='Time',
)
selector = alt.selection_single(
name='Selector',
fields=['Time'],
bind=slider,
init={'Time': 3},
)
bars = bars.add_selection(
selector
).transform_filter(
selector
)
bars.save('test.html')
不幸的是,您只能使用带有编码的条件,而不是(还)带有标记属性,因此无法动态更改文本基线。但是,您可以在条上方添加第二个文本标记,然后将两个文本标记的不透明度编码设置为取决于 y 的值,以便条上方或条下方的文本可见。
我相信您需要在单独的转换中计算平均 y 值,以便您可以访问条件表达式中的列名。我还认为,当时间值发生变化时,图表需要相互构建才能正确过滤,但我在这两点上都不是 100%。
考虑到上述情况,这样的事情会起作用:
bars = alt.Chart(df, width=100, height=90).mark_bar().transform_aggregate(
y_mean = 'mean(Y)',
groupby=['Scenes']
).encode(
x=alt.X(
'Scenes:N',
title=None,
axis=alt.Axis(
grid=False,
title=None,
labels=False,
),
),
y=alt.Y(
'y_mean:Q',
axis=alt.Axis(
grid=True,
title='Y',
titleFontWeight='normal',
),
),
)
text = bars.mark_text(
align='center',
baseline='line-top',
color='black',
dy=5,
fontSize=13,
).encode(
text=alt.Text( 'y_mean:Q', format='.1f'),
opacity=alt.condition( 'datum.y_mean < 3', alt.value(1), alt.value(0))
)
text2 = text.mark_text(
align='center',
baseline='line-bottom',
fontSize=13,
).encode(
opacity=alt.condition( 'datum.y_mean >= 3', alt.value(1), alt.value(0))
)
bars = bars + text + text2
bars = bars.facet(
row=alt.Row('Model:N', title=None),
column=alt.Column('Layer:N', title=None),
spacing={"row": 10, "column": 10},
)
slider = alt.binding_range(
min=1,
max=3,
step=1,
name='Time',
)
selector = alt.selection_single(
name='Selector',
fields=['Time'],
bind=slider,
init={'Time': 2},
)
bars = bars.add_selection(
selector
).transform_filter(
selector
)
bars
我有这张图表,其中 mark_text
表示每个柱的值。有一个滑块可以及时移动 back/forth。
我想根据 Y
值更改文本的位置(条形中的数字):如果它大于某个值,它将位于条形顶部的正下方(baseline='line-top
),否则就在上面 (baseline='line-bottom'
)。
这样,随着时间的推移,文本将 below/above 显示在栏的顶部,具体取决于 Y
。
我想我必须使用某些条件或表达式,但我不知道如何使用。
import numpy as np
import pandas as pd
import altair as alt
np.random.seed(0)
################################################################################
model_keys = ['M1', 'M2']
scene_keys = ['S1', 'S2']
layer_keys = ['L1', 'L2']
time_keys = [1, 2, 3]
ys = []
models = []
dataset = []
layers = []
scenes = []
times = []
for sc in scene_keys:
for m in model_keys:
for l in layer_keys:
for s in range(10):
y = np.random.rand(10) / 10
if m == 'M1':
y *= 10
if l == 'L1':
y *= 5
for t in time_keys:
y += 1
ys += list(y)
scenes += [sc] * len(y)
models += [m] * len(y)
layers += [l] * len(y)
times += [t] * len(y)
# ------------------------------------------------------------------------------
df = pd.DataFrame({'Y': ys,
'Model': models,
'Layer': layers,
'Scenes': scenes,
'Time': times})
bars = alt.Chart(df, width=100, height=90).mark_bar().encode(
x=alt.X('Scenes:N',
title=None,
axis=alt.Axis(
grid=False,
title=None,
labels=False,
),
),
y=alt.Y('Y:Q',
aggregate='mean',
axis=alt.Axis(
grid=True,
title='Y',
titleFontWeight='normal',
),
),
)
text = alt.Chart(df).mark_text(align='center',
baseline='line-top',
color='black',
dy=5,
fontSize=13,
).encode(
x=alt.X('Scenes:N'),
y=alt.Y('mean(Y):Q'),
text=alt.Text('mean(Y):Q',
format='.1f'
),
)
bars = bars + text
bars = bars.facet(
row=alt.Row('Model:N',
title=None,
),
column=alt.Column('Layer:N',
title=None,
),
spacing={"row": 10, "column": 10},
)
slider = alt.binding_range(
min=1,
max=3,
step=1,
name='Time',
)
selector = alt.selection_single(
name='Selector',
fields=['Time'],
bind=slider,
init={'Time': 3},
)
bars = bars.add_selection(
selector
).transform_filter(
selector
)
bars.save('test.html')
不幸的是,您只能使用带有编码的条件,而不是(还)带有标记属性,因此无法动态更改文本基线。但是,您可以在条上方添加第二个文本标记,然后将两个文本标记的不透明度编码设置为取决于 y 的值,以便条上方或条下方的文本可见。
我相信您需要在单独的转换中计算平均 y 值,以便您可以访问条件表达式中的列名。我还认为,当时间值发生变化时,图表需要相互构建才能正确过滤,但我在这两点上都不是 100%。
考虑到上述情况,这样的事情会起作用:
bars = alt.Chart(df, width=100, height=90).mark_bar().transform_aggregate(
y_mean = 'mean(Y)',
groupby=['Scenes']
).encode(
x=alt.X(
'Scenes:N',
title=None,
axis=alt.Axis(
grid=False,
title=None,
labels=False,
),
),
y=alt.Y(
'y_mean:Q',
axis=alt.Axis(
grid=True,
title='Y',
titleFontWeight='normal',
),
),
)
text = bars.mark_text(
align='center',
baseline='line-top',
color='black',
dy=5,
fontSize=13,
).encode(
text=alt.Text( 'y_mean:Q', format='.1f'),
opacity=alt.condition( 'datum.y_mean < 3', alt.value(1), alt.value(0))
)
text2 = text.mark_text(
align='center',
baseline='line-bottom',
fontSize=13,
).encode(
opacity=alt.condition( 'datum.y_mean >= 3', alt.value(1), alt.value(0))
)
bars = bars + text + text2
bars = bars.facet(
row=alt.Row('Model:N', title=None),
column=alt.Column('Layer:N', title=None),
spacing={"row": 10, "column": 10},
)
slider = alt.binding_range(
min=1,
max=3,
step=1,
name='Time',
)
selector = alt.selection_single(
name='Selector',
fields=['Time'],
bind=slider,
init={'Time': 2},
)
bars = bars.add_selection(
selector
).transform_filter(
selector
)
bars