環境
- Python 3.10.2
- bokeh 2.4.3
はじめに
Pythonのbokehライブラリを使って、以下のような折れ線グラフを作りたいです。
- グラフに10本以上の折れ線がある
- 凡例の項目を選択すると、折れ線の表示/非表示が切り替わる。
たとえば以下の様なグラフです。
import bokeh
import numpy
from bokeh.plotting import figure, output_file, save
import bokeh.palettes
output_file("graph.html")
x_data = numpy.arange(-20, 20, 0.1)
palette = bokeh.palettes.Category10[10]
fig = figure(x_axis_label="x", y_axis_label="y")
for i in range(10):
fig.line(x_data, numpy.sin(x_data + i), color=palette[i], legend_label=f"y=sin(x + {i})")
fig.legend.click_policy="hide"
save(fig)
やりたいこと
上記の折れ線グラフは、各折れ線が重なっていて見づらいので、注目している折れ線以外は非表示にしたいです。たとえば"y=sin(x + 5)"に注目する際は、凡例から"y=sin(x + 5)"以外の項目を選択して、注目していない折れ線を非表示にします。
しかし、折れ線の数が多いため、凡例からたくさんの項目を選択するのが面倒です。そこで、すべての折れ線を非表示にするボタンを追加することにします。
解決
bokehのButton Widgetを使って実装しました。
from bokeh.models import Button, CustomJS, GlyphRenderer
def create_button(glyph_list: list[GlyphRenderer]) -> Button:
button = Button(label="すべての折れ線を非表示にする")
args = {"glyph_list": glyph_list}
code = """
for (let glyph of glyph_list) {
glyph.visible = false
}
"""
button.js_on_click(CustomJS(code=code, args=args))
return button
# ~省略~
glyph_list = []
for i in range(10):
glyph = fig.line(x_data, numpy.sin(x_data + i), color=palette[i], legend_label=f"y=sin(x + {i})")
glyph_list.append(glyph)
fig.legend.click_policy = "hide"
button = create_button(glyph_list)
save([fig, button])
ボタンを押したときのコールバック処理では、line
関数の戻り値に対してvisible
プロパティをfalse
にしました。
line
関数の引数visible
の説明は、以下の通りです。
Whether the glyph should be rendered.
厳密にはvisible
プロパティの説明ではありませんが、たぶん内容は同じでしょう。
生成されたグラフでボタンを押すと、すべての折れ線が非表示になりました。また、凡例のすべての項目も薄い色の表示になりました。
なお、fig.legend.click_policy="mute"
で、凡例の項目を選択したときに折れ線を非表示ではなくミュート(薄い色の線にする)にしている場合は、ボタンのコールバック処理内ではmuted
プロパティをtrue
にする必要があります。
from bokeh.models import Button, CustomJS, GlyphRenderer
def create_button(glyph_list: list[GlyphRenderer]) -> Button:
button = Button(label="すべての折れ線をミュートにする")
args = {"glyph_list": glyph_list}
code = """
for (let glyph of glyph_list) {
glyph.muted= true
}
"""
button.js_on_click(CustomJS(code=code, args=args))
return button
# ~省略~
glyph_list = []
for i in range(10):
glyph = fig.line(x_data, numpy.sin(x_data + i), color=palette[i], legend_label=f"y=sin(x + {i})")
glyph_list.append(glyph)
fig.legend.click_policy = "mute"
button = create_button(glyph_list)
save([fig, button])