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