LoginSignup
4
8

More than 3 years have passed since last update.

plotly ボタンとスライダーを使用したグラフの作成

Last updated at Posted at 2020-11-12

やりたいこと

  • グラフ内にボタンを作成して、ボタンを押すことでプロットの表示/非表示,タイトルなどのレイアウトを操作する
  • 表示したプロットに対してスライダーで操作可能にする

button_slider_plotly.gif

環境

  • Mac OS
  • python 3.8.5
  • plotly 4.12.0

pip

pip install plotly
pip install numpy

ボタンのおさらい

button_plotly.gif

import numpy as np
import plotly.offline as offline
import plotly.graph_objs as go
import plotly

# 線の色取得
colors = plotly.colors.DEFAULT_PLOTLY_COLORS

x = np.linspace(-5, 5, 11)
data = []
for i in range(1, 4):
    trace = go.Scatter(
        x=x,
        y=x ** i,
        name="y{}".format(i),
        mode="lines",
        line=dict(color=colors[i], width=6),
        visible=False,
        showlegend=True
    )

    data.append(trace)
# 初期状態としてdataの0番目を見えるようにする
data[0].visible = True

"""
ボタン作成個所
キーの説明
active: 初期状態で押されているボタンを指定、buttonsキー値のリストインデックスを入力
type: ドロップダウンかボタンか ( "dropdown" | "buttons" ), default="dropdown"
buttons: ボタンの設定
"""
buttons = []
for ii in range(len(data)):
    visible = [False] * len(data)  # len(data)=3だから, visible=[False, False, False]
    visible[ii] = True  # ii番目だけTrueにする, ex) [True, False, False]
    button = dict(label=data[ii].name,
                  method="update",  # レイアウトを更新する為"update"
                  args=[dict(visible=visible),  # Trueにした個所のtraceのみ見える
                        dict(title="button_plotly_{}".format(data[ii].name),  # title更新
                             yaxis=dict(title="yaxis_{}".format(data[ii].name), showspikes=True))])  # y軸ラベル更新
    buttons.append(button)

updatemenus = [dict(active=0, type="buttons", buttons=buttons)]

layout = go.Layout(
    title="button_plotly_y1",
    xaxis=dict(title="xaxis", showspikes=True, domain=[0.05, 1.0]),
    yaxis=dict(title="yaxis_y1", showspikes=True),
    font=dict(size=16),
    updatemenus=updatemenus,
    showlegend=True)

fig = dict(data=data, layout=layout)
offline.plot(fig, auto_open=True, include_plotlyjs="cdn", filename=r"./button_plotly.html")

以下がボタンを作成している個所です。
updatemenus = [dict(active=0, type="buttons", "buttons"=buttons)]"buttons"=buttonsがボタンを押した時の情報(表示/非表示など)を作成している個所です。

buttonsはリストで、中にはレイアウト情報の辞書(下記ではbutton変数)が格納されています。

"""
ボタン作成個所
キーの説明
active: 初期状態で押されているボタンを指定、buttonsキー値のリストインデックスを入力
type: ドロップダウンかボタンか ( "dropdown" | "buttons" ), default="dropdown"
buttons: ボタンの設定
"""
buttons = []
for ii in range(len(data)):
    visible = [False] * len(data)  # len(data)=3だから, visible=[False, False, False]
    visible[ii] = True  # ii番目だけTrueにする, ex) [True, False, False]
    button = dict(label=data[ii].name,
                  method="update",  # レイアウトを更新する為"update"
                  args=[dict(visible=visible),  # Trueにした個所のtraceのみ見える
                        dict(title="button_plotly_{}".format(data[ii].name),  # title更新
                             yaxis=dict(title="yaxis_{}".format(data[ii].name), showspikes=True))])  # y軸ラベル更新
    buttons.append(button)

updatemenus = [dict(active=0, type="buttons", buttons=buttons)]


今回はボタンでプロットの表示/非表示、タイトル、y軸ラベルの切り替えをするように書いています。

visible=[False] * len(data)
visble[ii] = True

はどのプロットを表示するかを設定しています。

例えば、data=[trace1, trace2, trace3]visible=[True, False, False]の時、trace1のみ表示されます。

最後にgo.Layout(updatemenus=updatemenus)でグラフにボタンができます。

詳細
plotly ボタン付きグラフの作成 - Qiita

スライダーのおさらい

slider_plotly.gif

import numpy as np
import plotly.offline as offline
import plotly.graph_objs as go

# sin, cos波のtraceをを別々に保存
sin_data, cos_data = [], []
x = np.linspace(0, 10, 101)

for step in np.linspace(0, 5, 51):
    y = np.sin(step * x)
    y2 = np.cos(step * x)

    sin_trace = go.Scatter(
        x=x,
        y=y,
        name="sin {:.1f}Hz".format(step),
        line=dict(color="red", width=3),
        visible=False, )

    cos_trace = go.Scatter(
        x=x,
        y=y2,
        name="cos {:.1f}Hz".format(step),
        line=dict(color="blue", width=3),
        visible=False, )

    sin_data.append(sin_trace)
    cos_data.append(cos_trace)

# sin, cos共にデフォルトでindex=10のトレースを表示する
sin_data[10].visible = True
cos_data[10].visible = True

data = sin_data + cos_data

steps = []
"""
sin_data, cos_dataの表示/非表示を別々に設定した後、結合してvisibleキーに追加
例えばスライダーが2の時、
sin_visible = [False, False, True, False,...]
cos_visible = [False, False, True, False,...]
にした後2つを結合することで、sin, cos共にindex=2のtraceを表示できるようにする
"""
for s in range(len(sin_data)):
    # sin_data, cos_data共に一度全て非表示にする
    sin_visible, cos_visible = [False] * len(sin_data), [False] * len(cos_data)
    # それぞれs番目だけ見えるようにする
    sin_visible[s], cos_visible[s] = True, True
    step = dict(method="update",
                args=[{"visible": sin_visible + cos_visible},
                      {"title": "Simple slider step: {}".format(s)}]
                )
    steps.append(step)

sliders = [dict(active=10, currentvalue=dict(prefix="Frequency: "), pad=dict(t=50), steps=steps)]

layout = go.Layout(
    title="Simple slider step: 10",
    xaxis=dict(title="x"),
    yaxis=dict(title="y"),
    font=dict(size=16),
    hovermode='x unified',
    hoverlabel=dict(font_size=16),
    sliders=sliders,
    showlegend=True)

fig = dict(data=data, layout=layout)

offline.plot(fig, include_plotlyjs="cdn", filename="plots.html")

下記でスライダーを作成しています

steps = []
"""
sin_data, cos_dataの表示/非表示を別々に設定した後、結合してvisibleキーに追加
例えばスライダーが2の時、
sin_visible = [False, False, True, False,...]
cos_visible = [False, False, True, False,...]
にした後2つを結合することで、sin, cos共にindex=2のtraceを表示できるようにする
"""
for s in range(len(sin_data)):
    # sin_data, cos_data共に一度全て非表示にする
    sin_visible, cos_visible = [False] * len(sin_data), [False] * len(cos_data)
    # それぞれs番目だけ見えるようにする
    sin_visible[s], cos_visible[s] = True, True
    step = dict(method="update",
                args=[{"visible": sin_visible + cos_visible},
                      {"title": "Simple slider step: {}".format(s)}]
                )
    steps.append(step)

sliders = [dict(active=10, currentvalue=dict(prefix="Frequency: "), pad=dict(t=50), steps=steps)]

やっていることはボタンとあまり変わっていません。
sliders = [dict(active=10, currentvalue=dict(prefix="Frequency: "), pad=dict(t=50), steps=steps)]steps=stepsがスライダーを動かした時の情報(表示/非表示など)を作成している個所です。

stepsはリストで、中にはレイアウト情報の辞書(上記ではstep変数)が格納されています。

最後にgo.Layout(sliders=sliders)でグラフにスライダーができます。

詳細
plotly スライダーを使ったグラフの応用 - Qiita

サンプルグラフ

button_slider_plotly.gif

3つのボタンで

  • sin, cos両方(ALL)
  • sinのみ(SIN)
  • cosのみ(COS)

の表示を切り替え後、見えているプロットに対してスライダーで周波数を変更します。

スライダーはALL, SIN, COSの3つを作成しておき、ボタンでレイアウトを更新する際にスライダーも入れ替えます。

import copy
import numpy as np
import plotly.offline as offline
import plotly.graph_objs as go

# sin, cos波のtraceをを別々に保存
sin_data, cos_data = [], []
x = np.linspace(0, 10, 101)

for step in np.linspace(0, 5, 51):
    y = np.sin(step * x)
    y2 = np.cos(step * x)

    sin_trace = go.Scatter(
        x=x,
        y=y,
        name="sin {:.1f}Hz".format(step),
        line=dict(color="red", width=3),
        visible=False, )

    cos_trace = go.Scatter(
        x=x,
        y=y2,
        name="cos {:.1f}Hz".format(step),
        line=dict(color="blue", width=3),
        visible=False, )

    sin_data.append(sin_trace)
    cos_data.append(cos_trace)

sin_data[10].visible = True
cos_data[10].visible = True

data = sin_data + cos_data

steps = {"ALL": [], "SIN": [], "COS": []}
"""
visible情報のリストは計4つ
sin_visible_false: sin波のvisibleが全てFalse 
cos_visible_false: cos波のvisibleが全てFalse 
sin_visible_true: sin波のindex=sのvisibleがTrue
cos_visible_true: cos波のindex=sのvisibleがTrue

表示したくないものはFalseにすればいいから、
sin, cos両方表示するときのvisible情報: sin_visible_true + cos_visible_true
sinのみ表示するときのvisible情報: sin_visible_true + cos_visible_false
cosのみ表示するときのvisible情報: sin_visible_false + cos_visible_true
"""
for s in range(len(sin_data)):
    # sin_data, cos_data共に一度全て非表示にする
    sin_visible_false, cos_visible_false = [False] * len(sin_data), [False] * len(cos_data)
    # sin_visible_false, cos_visible_falseのコピー作成、コピーのindex=sに対してTrueにする
    sin_visible_true, cos_visible_true = copy.copy(sin_visible_false), copy.copy(cos_visible_false)
    sin_visible_true[s], cos_visible_true[s] = True, True

    step_all = dict(method="update",
                    args=[{"visible": sin_visible_true + cos_visible_true},
                          {"title": "slider_button_ALL step: {}".format(s)}]
                    )
    step_sin = dict(method="update",
                    args=[{"visible": sin_visible_true + cos_visible_false},
                          {"title": "slider_button_SIN step: {}".format(s)}]
                    )

    step_cos = dict(method="update",
                    args=[{"visible": sin_visible_false + cos_visible_true},
                          {"title": "slider_button_COS step: {}".format(s)}]
                    )

    steps["ALL"].append(step_all)
    steps["SIN"].append(step_sin)
    steps["COS"].append(step_cos)

sliders = {}
for key, step in steps.items():
    slider = [dict(active=10, currentvalue=dict(prefix="Frequency: "), pad=dict(t=50), steps=step)]
    sliders[key] = slider

buttons = []
for key, slider in sliders.items():
    slider_active = slider[0]["active"]
    slider_visible = slider[0]["steps"][slider_active]["args"][0]["visible"]
    button = dict(label=key,
                  method="update",
                  args=[dict(visible=slider_visible),
                        dict(title="slider_button_{} step: {}".format(key, slider_active),
                             yaxis=dict(title="y {}".format(key)),
                             sliders=slider)])
    buttons.append(button)

updatemenus = [dict(active=0, type="buttons", buttons=buttons)]

layout = go.Layout(
    title="slider_button_ALL step: 10",
    xaxis=dict(title="x", domain=[0.05, 1]),
    yaxis=dict(title="y ALL"),
    font=dict(size=16),
    hovermode='x unified',
    hoverlabel=dict(font_size=16),
    sliders=sliders["ALL"],
    updatemenus=updatemenus,
    showlegend=True)

fig = dict(data=data, layout=layout)

offline.plot(fig, auto_open=True, include_plotlyjs="cdn", filename=r"./slider_button_plotly.html",
             config={'modeBarButtonsToAdd': ['drawline', 'drawopenpath', 'drawclosedpath', 'drawcircle', 'drawrect',
                                             'eraseshape']})

スライダーを動かした時のプロット表示を作成しています。

steps = {"ALL": [], "SIN": [], "COS": []}
"""
visible情報のリストは計4つ
sin_visible_false: sin波のvisibleが全てFalse 
cos_visible_false: cos波のvisibleが全てFalse 
sin_visible_true: sin波のindex=sのvisibleがTrue
cos_visible_true: cos波のindex=sのvisibleがTrue

表示したくないものはFalseにすればいいから、
sin, cos両方表示するときのvisible情報: sin_visible_true + cos_visible_true
sinのみ表示するときのvisible情報: sin_visible_true + cos_visible_false
cosのみ表示するときのvisible情報: sin_visible_false + cos_visible_true
"""
for s in range(len(sin_data)):
    # sin_data, cos_data共に一度全て非表示にする
    sin_visible_false, cos_visible_false = [False] * len(sin_data), [False] * len(cos_data)
    # sin_visible_false, cos_visible_falseのコピー作成、コピーのindex=sに対してTrueにする
    sin_visible_true, cos_visible_true = copy.copy(sin_visible_false), copy.copy(cos_visible_false)
    sin_visible_true[s], cos_visible_true[s] = True, True

    step_all = dict(method="update",
                    args=[{"visible": sin_visible_true + cos_visible_true},
                          {"title": "slider_button_ALL step: {}".format(s)}]
                    )
    step_sin = dict(method="update",
                    args=[{"visible": sin_visible_true + cos_visible_false},
                          {"title": "slider_button_SIN step: {}".format(s)}]
                    )

    step_cos = dict(method="update",
                    args=[{"visible": sin_visible_false + cos_visible_true},
                          {"title": "slider_button_COS step: {}".format(s)}]
                    )

    steps["ALL"].append(step_all)
    steps["SIN"].append(step_sin)
    steps["COS"].append(step_cos)

sliders = {}
for key, step in steps.items():
    slider = [dict(active=10, currentvalue=dict(prefix="Frequency: "), pad=dict(t=50), steps=step)]
    sliders[key] = slider

slidersの中身は{"ALL":ALLの時のスライダー, "SIN":SINの時のスライダー,...}となっています

buttons = []
for key, slider in sliders.items():
    slider_active = slider[0]["active"]
    slider_visible = slider[0]["steps"][slider_active]["args"][0]["visible"]
    button = dict(label=key,
                  method="update",
                  args=[dict(visible=slider_visible),
                        dict(title="slider_button_{} step: {}".format(key, slider_active),
                             yaxis=dict(title="y {}".format(key)),
                             sliders=slider)])
    buttons.append(button)

updatemenus = [dict(active=0, type="buttons", buttons=buttons)]

ボタンを作成しています。
slider_visible = slider[0]["steps"][slider_active]["args"][0]["visible"]はスライダーの初期位置のvisible情報を取得しています。

上のスクリプトの場合スライダーの初期位置を10としているので、ボタンを切り替えた直後の表示もそれに合わせないとスライダーの位置と表示が合わない為です。

layout = go.Layout(
    title="slider_button_ALL step: 10",
    xaxis=dict(title="x", domain=[0.05, 1]),
    yaxis=dict(title="y ALL"),
    font=dict(size=16),
    hovermode='x unified',
    hoverlabel=dict(font_size=16),
    sliders=sliders["ALL"],
    updatemenus=updatemenus,
    showlegend=True)

fig = dict(data=data, layout=layout)

offline.plot(fig, auto_open=True, include_plotlyjs="cdn", filename=r"./slider_button_plotly.html",
             config={'modeBarButtonsToAdd': ['drawline', 'drawopenpath', 'drawclosedpath', 'drawcircle', 'drawrect',
                                             'eraseshape']})

レイアウトを決めて出力しています。
デフォルトはALLを表示するようにしています。

config={...}は右上のお絵かきボタンなどを追加しています。

参考

公式
Custom Buttons | Python | Plotly
layout.updatemenus | Python | Plotly
Plotly Python Graphing Library | Python | Plotly

4
8
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
8