plotly スライダーを使ったグラフの応用

# はじめに
plotlyでスライダー付きグラフをよく作成するのでメモです。

１つのプロットをスライダーで動かすのは公式にコードが乗っているので、複数同時に動かしたい時などのちょっとした応用を記載します。

公式サイト（スライダーの説明ページ）
[Sliders | Python | Plotly](https://plotly.com/python/sliders/)

# 環境

Mac OS
python 3.8.5
plotly 4.12.0

# pip, import
pipから以下をインストールします。記事でnumpyを使用する為一緒にインストールしています。

```terminal
pip install plotly
pip install numpy
```
```python
import numpy as np
import plotly.offline as offline
import plotly.graph_objs as go
```

# plotly　スライダーの基本
公式にも載っていますが、一応

<p class="codepen" data-height="265" data-theme-id="light" data-default-tab="html,result" data-user="shunta-m" data-slug-hash="eYzMqye" style="height: 265px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;" data-pen-title="simple slider">
  <span>See the Pen <a href="https://codepen.io/shunta-m/pen/eYzMqye">
  simple slider</a> by shunta-m (<a href="https://codepen.io/shunta-m">@shunta-m</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<script async src="https://static.codepen.io/assets/embed/ei.js"></script>


trace(プロット)作成 -> レイアウト作成 -> グラフ作成の順で書いています

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

# プロットを保存するリスト
data = []
x = np.linspace(0, 10, 101)
# プロットの作成
# 0, 0.1, ... , 5までのプロットを作成する
for step in np.linspace(0, 5, 51):
    y = np.sin(step * x)

    trace = go.Scatter(
        x=x,
        y=y,
        name="sin step{:.1f}".format(step),
        line=dict(color="red", width=3),
        visible=False, )  # 見えない状態にする
    data.append(trace)

# 初期状態として、10番目のプロットを見えるようにしておく
data[10].visible = True

# スライダーの位置によってどこを表示/非表示にするかを保存するリスト
steps = []
"""
for文内でどこを見えるようにするか決めている
例えばスライダーが10にいる時、10番目のプロットが見えればいいので
一度全てを非表示 -> 10番目プロットのvisibleをTrueにして見えるようにする
"""
for s in range(len(data)):
    step = dict(method="update",
                args=[{"visible": [False] * len(data)},  # 一度全て非表示
                      {"title": "Simple slider step: {}".format(s)}]  # スライダーの位置によってタイトルも変更する
                )
    step["args"][0]["visible"][s] = True  # 対応するものだけを見えるようにする
    # ↑で作ったs番目だけが見えるレイアウトをstepsに追加
    steps.append(step)

sliders = [dict(
    active=10,  # スライダーの初期値
    currentvalue=dict(prefix="Frequency: "),
    pad=dict(t=50),  # スライダーの占有範囲, 数値を大きくする程スライダーの範囲が大きくなる
    steps=steps)]  # stepsが持っている表示/非表示の情報を使う

layout = go.Layout(
    title="Simple slider step: 10",  # 初期状態のタイトル
    xaxis=dict(title="x"),
    yaxis=dict(title="y"),
    font=dict(family="Meiryo", size=16),  # グラフ内のフォントをメイリオ、サイズを16へ
    # ホバーモード（グラフにカーソルを近づけた時の数値の見え方）の設定
    # 与えることができる文字列は "x", "y", "closest", False, "x unified", "y unified"
    hovermode="x unified",
    hoverlabel=dict(font_size=16),  # ホバーモードの文字サイズ設定
    sliders=sliders,  # スライダー追加
    showlegend=True)  # 凡例表示をTrueに

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

# 出力
offline.plot(fig, auto_open=True, include_plotlyjs="cdn", filename="simple_slider.html")

```

# 複数プロットを動かす
sin波、cos波を両方動かしてみます。


![plots.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/779817/9dcc750f-9c3b-6adb-10f9-b27904df4248.gif)


```python
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 = []
"""
sin_data, cos_dataの表示/非表示を別々に設定した後、結合してvisibleキーに追加
例えばスライダーが2の時、
sin_visible = [False, False, True, False,...]
cos_visible = [False, False, True, False,...]
にした後2つを結合することで、sin, cos共に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(family="Meiryo", 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")

```

# 一部プロットを動かす
同じグラフ内にスライダーで動かしたいもの、動かしたくないものがある時

![plots2.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/779817/3c778c81-f4b6-78b7-310b-ce1a4338945b.gif)


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

data = []
sin_data, cos_data = [], []
x = np.linspace(0, 10, 101)

# 固定プロット
trace = go.Scatter(
    x=x,
    y=2 * np.cos(x),
    name="2 * cos",
    line=dict(color="green", width=3),
    visible=True, )
data.append(trace)  # dataの0番目が固定プロット情報

# スライダープロット
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 = []
"""
固定プロットは常に表示すればいいので、visibleを常にTrueにする
    ->dataの0番目(固定プロット)をTrueにする
"""
for s in range(len(sin_data)):
    sin_visible, cos_visible = [False] * len(sin_data), [False] * len(cos_data)
    sin_visible[s], cos_visible[s] = True, True
    step = dict(method="update",
                args=[{"visible": [True] + sin_visible + cos_visible},  # 0番目は常にTrue
                      {"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(family="Meiryo", 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="plot2.html")

```

# 途中から非表示にする
スライダーが40以上になるとcosが消えます。

![plots3.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/779817/39cc4280-b6a6-aeb9-1138-823722cdf996.gif)


単純に40以上の時cos_traceのx, yの値をNoneにしています。

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

x = np.linspace(0, 10, 101)

data = []
sin_data, cos_data = [], []

trace = go.Scatter(
    x=x,
    y=2 * np.cos(x),
    name="2 * cos",
    line=dict(color="green", width=3),
    visible=True, )
data.append(trace)

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, )

    if step < 4:
        cos_trace = go.Scatter(
            x=x,
            y=y2,
            name="cos {:.1f}Hz".format(step),
            line=dict(color="blue", width=3),
            visible=False, )
    else:
        cos_trace = go.Scatter(x=None, y=None)  # 4以上はNone

    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 = []
for s in range(len(sin_data)):
    sin_visible, cos_visible = [False] * len(sin_data), [False] * len(cos_data)
    sin_visible[s], cos_visible[s] = True, True
    step = dict(method="update",
                args=[{"visible": [True] + 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(family="Meiryo", 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="plots3.html")

```

# 参考サイト
公式サイト
[Plotly Python Graphing Library | Python | Plotly](https://plotly.com/python/)

codepen
[Plotlyを使った描画結果をQiita内にCodePen埋め込むことで確認する - Qiita](https://qiita.com/wakame1367/items/b78b7f758ce163081362)
[Qiitaで記事にCodePenが埋め込めるようになりました - Qiita](https://qiita.com/Qiita/items/edae7417214c8e957f54)