Altair:根据列表中的标签成员身份将条件应用于轴标签颜色

Altair: Apply condition to axis label color based on label membership in a list

提问人:multipitch 提问时间:11/16/2023 最后编辑:toyota Supramultipitch 更新时间:11/16/2023 访问量:34

问:

我有一个调度图,其中 Y 轴是分类的(每个 Y 条目对应于一台设备,x 轴显示时间)。

我有一个包含调度冲突的设备列表。 对于有冲突的装备列表中的每个装备项目,我想在剧情上用红色突出显示装备名称。

我找不到一种方法来应用检查列表中成员身份的测试。

这里发布了一个类似的问题: 在 altair 图中为一些 x 标签着色? - 但是这个解决方案涉及将测试编码为一个字符串,我猜这个字符串是由 altair 解释的。“is in”测试似乎在这种结构中不起作用。

下面是一个最小的工作示例:

import streamlit as st
import altair as alt
import pandas as pd

data = [
    {"task": "Task 1", "start": 1, "finish": 10, "equipment": "XXX-101"},
    {"task": "Task 2", "start": 9, "finish": 20, "equipment": "XXX-101"},
    {"task": "Task 3", "start": 6, "finish": 8, "equipment": "XXX-102"},
    {"task": "Task 4", "start": 9, "finish": 12, "equipment": "XXX-102"},
    {"task": "Task 5", "start": 13, "finish": 18, "equipment": "XXX-102"},
    {"task": "Task 6", "start": 5, "finish": 15, "equipment": "XXX-103"},
    {"task": "Task 7", "start": 16, "finish": 18, "equipment": "XXX-103"},
    {"task": "Task 8", "start": 6, "finish": 8, "equipment": "XXX-104"},
    {"task": "Task 9", "start": 4, "finish": 12, "equipment": "XXX-104"},
    {"task": "Task 10", "start": 11, "finish": 16, "equipment": "XXX-104"},
]
dataframe = pd.DataFrame(data)

equipment_has_clashes = ["XXX-101", "XXX-104"]  # determined by another algorithm


chart = (
    alt.Chart(dataframe)
    .mark_bar(height=20)
    .encode(
        x=alt.X("start").title("time"),
        x2=("finish"),
        y=alt.Y(
            "equipment",
            axis=alt.Axis(
                labelColor=alt.condition(
                    # Want to apply test equivalent to "equipment is in equipment_has_clashes"
                    predicate="datum",
                    if_true=alt.value("red"),
                    if_false=alt.value("black"),
                )
            ),
        ),
        tooltip=[alt.Tooltip(t) for t in ["task", "equipment", "start", "finish"]],
        color=alt.Color("task", type="nominal").scale(scheme="category20"),
    )
    .properties(height=alt.Step(30))
    .configure_view(strokeWidth=1)
    .configure_axis(domain=True, grid=True)
    .interactive()
)

st.altair_chart(chart, use_container_width=True)

Python 熊猫 streamlit Altair

评论


答:

2赞 Suraj Shourie 11/16/2023 #1

也许不是最优雅的解决方案,但您可以在此处的解决方案中使用“OR”运算符传递每个条件。

cond = " || ".join([f'datum.value == "{x}"'  for x in equipment_has_clashes])

并将其传递到您的:labelColor

            axis=alt.Axis(
                labelColor=alt.condition(cond, alt.value('red'), alt.value('blue')) 
                ),

输出:

enter image description here

另一种解决方案可能是使用这样的东西,但我无法实现它。

评论

0赞 multipitch 11/16/2023
感谢您的建议 - 似乎工作正常。
0赞 multipitch 11/16/2023 #2

看来谓语表达式必须用 vega 表达式语言编写。 没有定义的成员资格测试函数,但使用 似乎提供了一个有效的技巧:https://vega.github.io/vega/docs/expressions/#indexofindexof

我将equipment_has_clashes列表作为字符串提供给 vega 表达式:

predicate = f"indexof({equipment_has_clashes}, datum.value) >= 0"
print(predicate)

这将给出以下谓词表达式:

indexof(['XXX-101', 'XXX-104'], datum.value) >= 0

我相应地修改了我的代码,如下所示:

import streamlit as st
import altair as alt
import pandas as pd

data = [
    {"task": "Task 1", "start": 1, "finish": 10, "equipment": "XXX-101"},
    {"task": "Task 2", "start": 9, "finish": 20, "equipment": "XXX-101"},
    {"task": "Task 3", "start": 6, "finish": 8, "equipment": "XXX-102"},
    {"task": "Task 4", "start": 9, "finish": 12, "equipment": "XXX-102"},
    {"task": "Task 5", "start": 13, "finish": 18, "equipment": "XXX-102"},
    {"task": "Task 6", "start": 5, "finish": 15, "equipment": "XXX-103"},
    {"task": "Task 7", "start": 16, "finish": 18, "equipment": "XXX-103"},
    {"task": "Task 8", "start": 6, "finish": 8, "equipment": "XXX-104"},
    {"task": "Task 9", "start": 4, "finish": 12, "equipment": "XXX-104"},
    {"task": "Task 10", "start": 11, "finish": 16, "equipment": "XXX-104"},
]
dataframe = pd.DataFrame(data)

equipment_has_clashes = ["XXX-101", "XXX-104"]  # determined by another algorithm

predicate = f"indexof({equipment_has_clashes}, datum.value) >= 0"

chart = (
    alt.Chart(dataframe)
    .mark_bar(height=20)
    .encode(
        x=alt.X("start").title("time"),
        x2=("finish"),
        y=alt.Y(
            "equipment",
            axis=alt.Axis(
                labelColor=alt.condition(
                    predicate=predicate,
                    if_true=alt.value("red"),
                    if_false=alt.value("black"),
                )
            ),
        ),
        tooltip=[alt.Tooltip(t) for t in ["task", "equipment", "start", "finish"]],
        color=alt.Color("task", type="nominal").scale(scheme="category20"),
    )
    .properties(height=alt.Step(30))
    .configure_view(strokeWidth=1)
    .configure_axis(domain=True, grid=True)
    .interactive()
)

st.altair_chart(chart, use_container_width=True)

enter image description here