カテゴリー

当サイトはアフィリエイトプログラムによる収益を得ています〈景品表示法に基づく表記です)

Plotly全般

【plotly&ボタン】グラフに複数種のボタンを追加

2021年8月10日

こんな人にオススメ

plotlyってグラフの切り替えとかできるボタンをつけられるけど、これって複数種可能?

複数の処理をボタンを分けて行いたい。

ということで、今回はplotlyで作ったグラフに複数種のボタンを追加する方法について解説する。以下の記事でグラフにボタンを追加するということを解説した。

【plotly&ボタン】plotlyのupdatemenusにbuttonsを追加

続きを見る

【plotly&ボタン】plotlyのupdatemenusに2回押し対応のbuttonsを追加

続きを見る

これらのグラフでは1種類のボタンを作成し、さらに後者では2回押した時の挙動を設定した。が、あくまでも1種類のボタン。本記事の方法を使用すれば、複数種のボタンを作成し、用途を分けられるようになる。

じゃあどうすれば複数種のボタンを作成できるのかというと、updatemenusを複数作成してそれらを結合してgo.Layoutに入れる。以下で具体的な内容を解説する。

python環境は以下。

  • Python 3.9.6
  • numpy 1.21.1
  • matplotlib 3.4.2
  • plotly 5.1.0
  • plotly-orca 3.4.2

運営者のメガネです。YouTubeTwitterInstagramも運営中。自己紹介お問い合わせページあります。

運営者メガネ

下準備

import sys
import numpy as np
import matplotlib.cm as cm
import plotly
import plotly.graph_objects as go
import plotly.io as pio

sys.path.append('../../')
import plotly_layout_template as template

まずは下準備としてのimport関連。plotly_layout_templateは自作テンプレートの入っているファイルで、本記事のコードの2つ上のディレクトリに置いているため'../../'で戻っている。詳細は以下参照。

【随時更新 備忘録】plotlyのグラフ即席作成コード

続きを見る

使用するデータ

x = np.arange(10)
ys = {}
for num in range(5):
    y = x + num
    ys[num] = y

今回使用するデータはシンプルな直線。これに0から4までの整数を足すことで1ずつ上にズラしている。中身を見てみると以下。

for name, y in ys.items():
    print(f"name: {name}, y: {y}")
# name: 0, y: [0 1 2 3 4 5 6 7 8 9]
# name: 1, y: [ 1  2  3  4  5  6  7  8  9 10]
# name: 2, y: [ 2  3  4  5  6  7  8  9 10 11]
# name: 3, y: [ 3  4  5  6  7  8  9 10 11 12]
# name: 4, y: [ 4  5  6  7  8  9 10 11 12 13]

go.Scatter作成や表示切り替えの関数を定義

まずはグラフを作成する際に使用する関数を定義する。必ずしも定義しなければいけないわけではないが、各処理を分けられるのでスッキリする。

今回は以下の5項目について予め定義する。後に解説する、もう少しややこしいグラフに使う関数はまた後ほど定義する。ここでは汎用的なものだけを解説する。

  • プロットデータの作成
  • プロットの表示・非表示の切り替えボタン作成
  • 作成したボタンをグラフに配置
  • グラフレイアウトの作成
  • 作成したグラフの保存

関数については以下参照。

【python&関数化】defとかargsとかを使って関数を作成する

続きを見る

go.Scatterのプロットデータの作成

def scatter(x, y, name, color):
    d = go.Scatter(
        mode='lines+markers',
        x=x, y=y,
        name=name,
        marker_color=color,
        hovertemplate=f"{name}<br>"
        + 'x: %{x} <br>'
        + 'y: %{y} <br>'
        + "<extra></extra>",
    )

    return d

引数x, yでプロットの横・縦の値を、nameでそのプロットの名称を、そしてcolorでそのプロットの色を設定する。他にも設定できる項目はあるが、最低限必要なものはこれくらい。

プロットは線と点を表すmode='lines+markers'で、hovertemplateでマウスオーバー時の表示項目を設定している。

プロット点の表示切り替えボタンの作成

def set_visible_buttons(labels: list, start=0):
    # ボタンの内容を作成
    buttons = []
    for num, label in enumerate(labels, start):
        # 一旦全てのプロットを非表示に
        visible = [False] * len(ys)

        if num:  # それぞれのプロットを表示するとき
            visible[num - 1] = True
        else:  # 全てのプロットを表示するとき
            visible = [True] * len(ys)

        button = dict(
            label=label, method='update',
            args=[dict(visible=visible), dict(title=label), ]
        )
        buttons.append(button)

    return buttons

押したボタンに対応したプロットを表示してその他を非表示にする設定を行う関数。labelsにボタンに書くラベルデータを入れて、そのラベル1つひとつに対して設定を行う。

今回は全プロットを表示するALLボタンも追加しているので少しややこしいが、num0の時はALLになるように設定している。詳しくは以前のボタンの記事参照。

ボタンをグラフに配置する関数の作成

def set_updatemenus(x, y, buttons, button_type='buttons', direction='right'):
    updatemenus = [
        dict(
            type=button_type, direction=direction,
            x=x, y=y, xanchor='center', yanchor='bottom',
            active=0, buttons=buttons,
        )
    ]

    return updatemenus

ボタンは作成するだけでは適用できなくて、それをグラフに配置しないといけない。そのための処理を行う関数がset_updatemenus関数。引数x, yにボタンを置く位置を、buttonsに作成したボタンのデータを入れる。

button_type'buttons'の場合は設定したボタンが並び、'dropdown'にするとボタンを押すと内容一覧が出てくるようになる。directionはボタンを置く方向。

レイアウトデータの作成

def set_layout(title: str, updatemenus: list):
    layout = go.Layout(
        template=template.plotly_layout(),
        title=dict(text=title,),
        xaxis=dict(title='x',),
        yaxis=dict(title='y', range=(-1, 15)),
        updatemenus=updatemenus,
    )

    return layout

グラフのレイアウト設定を行う関数。引数templateで自作のテンプレートを読み込んでいる。引数titleにグラフタイトルを入れ、さらに引数updatemenusに先程のset_updatemenus関数の返り値を入れることで、グラフにボタンを追加可能。

また、yaxisrangeでグラフの表示範囲を設定しているが、この設定をしないとset_visible_buttons関数で特定のグラフだけ表示した時に縦軸の範囲が変わってしまう。

グラフの保存処理の作成

def save(fig, name: str):
    pio.orca.config.executable = '/Applications/orca.app/Contents/MacOS/orca'
    pio.write_html(
        fig, f'{name}.html',
        auto_open=False, config=template.plotly_config()
    )
    pio.write_image(fig, f'{name}.png')

最後に作成したグラフの保存処理を関数化。今回はhtmlとpng形式で保存するが、pdfでもjpegでも可能。グラフ保存については以下で格闘の記録を残しているので参照。

plotlyでグラフを静止画として保存する際に出るポップアップ
【plotly&orca】plotlyで静止画保存(orca)

続きを見る

シンプルにプロットの表示を切り替える


まずはシンプルに、ボタンを押すとそのボタンに対応したプロットだけが表示されるグラフを作成。上のグラフでいうと「0」を押すと直線データに+0したプロットのみが、「ALL」を押すと全プロットが表示される。

このグラフに関しては以下の記事で詳しく解説している。プロットデータは違えど根本的な処理内容は変わらない。また、書き方も人それぞれだから仕組みだけわかればいい。

【plotly&ボタン】plotlyのupdatemenusにbuttonsを追加

続きを見る

ボタンに表示するラベルを作成

labels = ['ALL'] + list(ys)
print(labels)
# ['ALL', 0, 1, 2, 3, 4]

ボタンにはそれぞれを判別するためのラベルが必要だが、今回はysで定義したkeysをラベルとした。加えて、全グラフを表示するためのボタンとして'ALL'も作成した。

ラベルは何回も使用することもあるので、予め定義しておいた方が楽。

グラフを作成

def single_button():
    plot = []
    for num, y in ys.items():
        color = cm.jet(num / len(ys))
        color = plotly.colors.convert_to_RGB_255(color)
        color = f"rgb{color}"

        d = scatter(x=x, y=y, name=num, color=color)
        plot.append(d)

    # ボタンに使用するラベルを作成
    buttons = set_visible_buttons(labels=labels)
    # 作成したボタンをグラフに配置
    updatemenus = set_updatemenus(x=0.5, y=1.01, buttons=buttons)

    # レイアウトの設定
    layout = set_layout(title=labels[0], updatemenus=updatemenus)

    fig = go.Figure(data=plot, layout=layout)
    fig.show(config=template.plotly_config())

    # グラフの保存
    name = 'single_button'
    save(fig=fig, name=name)

single_button()

多くの処理を予め関数として定義したので、実際にグラフを描く際のコードはかなり短くなる。色はplotlyのカラースケールよりもmatplotlibの方が使い勝手がいいのであえてmatplotlibを使用。

あとは各関数に必要な引数を入れてあげれば良い。関数を予め定義するのは面倒かもしれないが、最終的にやりたい処理がスッキリ見えるからなかなか侮れない。

実際にできるグラフが本章始めのグラフで、ボタンを押すことでプロットの切り替えができる。

2つのボタンでプロットの表示を切り替える


本題の複数種ボタンの作成だが、まずは2種類から。本記事を見ている環境によっては上のグラフのボタンが重なるかもしれないが、実際にグラフを作成するといい感じのボタン配置になるはず。

このグラフでは1つ目のグラフのALL, 0-4のボタンを2グループに分けた。わざわざ分ける必要はないけど、パッと思いついた方法がコレ。何か系統の異なるデータを使用している時はこんな感じで作成すると良いだろう。

複数のボタンを作成にはupdatemenusを結合

def double_buttons():
    plot = []
    for num, y in ys.items():
        color = cm.jet(num / len(ys))
        color = plotly.colors.convert_to_RGB_255(color)
        color = f"rgb{color}"

        d = scatter(x=x, y=y, name=num, color=color)
        plot.append(d)

    # プロット線切り替えボタン作成
    # ALLと0
    buttons1 = set_visible_buttons(labels=labels[0:2], start=0)
    updatemenus1 = set_updatemenus(x=0.5, y=1.01, buttons=buttons1)
    # 1, 2, 3, 4
    buttons2 = set_visible_buttons(labels=labels[2:6], start=2)
    updatemenus2 = set_updatemenus(x=0.5, y=1.05, buttons=buttons2)

    # ボタンは結合して1つにしておく
    updatemenus = updatemenus1 + updatemenus2
    print(updatemenus)

    # レイアウトの設定
    layout = set_layout(title=labels[0], updatemenus=updatemenus)

    fig = go.Figure(data=plot, layout=layout)
    fig.show(config=template.plotly_config())

    # グラフの保存
    name = 'double_buttons'
    save(fig=fig, name=name)

double_buttons()

既に書いたが、複数種のボタンを作成するには、複数ボタンに対応したupdatemenusを結合する。まず、上のコードではupdatemenus1ALL, 0を、updatemenus21, 2, 3, 4を担当している。

これらupdatemenus1, 2を結合し、結合したものをlayoutの引数に入れるだけ。結構シンプル。ただし、結合する際に無駄にlistが入っていたりするとエラー。

エラーと正常の場合のupdatemenusの値を以下に示す。エラーの場合は例えばupdatemenusの結合を次のようにした場合。無駄にlistが入ってエラーの原因に。

updatemenus = []
updatemenus.append(updatemenus1)
updatemenus.append(updatemenus2)

updatemenusを結合することでできるグラフが上のグラフというわけだ。

2つ目のボタンでloglinearを切り替え


プロットの表示・非表示はできたので、ここでは軸のタイプを変更する方法について解説する。具体的には、縦軸を線形(linear)と対数(log)に変更するボタンを追加する。

今回の例ではあまり効果は出ないが、実際にlinearスケールで書いたグラフをlogにしたい時はボタン1つで切り替えができるので楽。このグラフでも記事に載せるとボタンが被るかもしれないが、グラフ化するといい感じになる。

ボタンのレイアウト部分に変更内容を書く

def set_log_buttons():
    # ボタンの内容を作成
    buttons = []

    # logとlinearで表示範囲を指定する
    range_dct = {'linear': (-1, 15), 'log': (np.log10(0.9), np.log10(15))}
    for y_type in range_dct:
        button = dict(
            label=y_type, method='update',
            args=[
                dict(),
                # dict(yaxis=dict(type=y_type, range=range_dct[y_type],),),
                # の代わりに以下尿に「.」を使用することができる
                {'yaxis.type': y_type, 'yaxis.range': range_dct[y_type], },
            ]
        )
        buttons.append(button)

    return buttons

今回はプロット点に対する処理ではなく、レイアウトに関する処理なのでさっきと勝手が異なる。ボタンにレイアウトの操作を入れるにはargsの2つ目のdictを編集すればいい。

既にプロットの表示・非表示でグラフタイトルの設定で使用したが、ここにyaxisのタイプをlogとlinearに変更する内容を書けばいい。

注意するべき点が、plotlyでは対数の軸範囲制限(range)をする際にはnp.log10((軸の値))としなければいけないという点。シンプルに15と入れると軸の値が$10^{15}$になって悲惨なことになる。

縦軸のタイプを変更するグラフ

def double_buttons_log():
    plot = []
    for num, y in ys.items():
        color = cm.jet(num / len(ys))
        color = plotly.colors.convert_to_RGB_255(color)
        color = f"rgb{color}"

        d = scatter(x=x, y=y, name=num, color=color)
        plot.append(d)

    # プロット線切り替えボタン作成
    buttons1 = set_visible_buttons(labels=labels, start=0)
    updatemenus1 = set_updatemenus(x=0.5, y=1.01, buttons=buttons1)

    # logとlienar切り替え用のボタンを作成
    buttons2 = set_log_buttons()
    updatemenus2 = set_updatemenus(x=0.5, y=1.05, buttons=buttons2)

    # ボタンは結合して1つにしておく
    updatemenus = updatemenus1 + updatemenus2

    # レイアウトの設定
    layout = set_layout(title=labels[0], updatemenus=updatemenus)

    fig = go.Figure(data=plot, layout=layout)
    fig.show(config=template.plotly_config())

    # グラフの保存
    name = 'double_buttons_log'
    save(fig=fig, name=name)

double_buttons_log()

縦軸のタイプを変更するグラフを作成するためのコードは上。今までのものと遜色ないようなグラフになっている。しかし、これでタイプが変更できたのでより見やすいグラフができるだろう。

プロットの色を変更するボタンを追加


最後に、先ほどの軸タイプボタンに加えて、プロットの色を変更するボタンを追加する。色変更のボタンは今までのbuttonsではなくdropdownにしたので、押したら内容一覧が出てくる。

どこで活用するのかと言われると怪しいところもあるけど、こんなこともできるんだくらいに見ていってほしい。

各ボタンでプロットの色を自動作成

colors_scales = {
    'jet': cm.jet, 'viridis': cm.viridis, 'plasma': cm.plasma,
    'Reds': cm.Reds, 'spring': cm.spring, 'cool': cm.cool,
    'Pastel1': cm.Pastel1, 'Set1': cm.Set1, 'prism': cm.prism,
}

今更ではあるけど、今回のグラフの色は全てカラースケール(カラーマップ?)を使用している。例えばcm.jetというカラースケールの0に該当する色を使用してねという感じ。0は紺色っぽい色。

で、ここでのボタンはこのカラースケールを変更するというもの。変数colors_scalesで色々とカラースケールを取得したので、コレを使用して色を変更する。

プロット関係なのでargsの1つ目の要素に書く

def set_color_buttons(colors_scales: dict):

    buttons = []
    for label, color_map in colors_scales.items():
        colors = []
        for num, _ in enumerate(ys):
            color = color_map(num / len(ys))
            color = plotly.colors.convert_to_RGB_255(color)
            colors.append(f"rgb{color}")

        button = dict(
            label=label, method='update',
            args=[
                {'marker.color': colors},
                dict(title=label),
            ]
        )
        buttons.append(button)

    return buttons

プロットの色はプロット関係なので、ボタンのargsの1つ目のdictに入れる。2つ目のdictはいつも通りグラフタイトルを入れている。

また、今回はプロットの色ということだが、matplotlibplotlyで色の扱い方が異なるので、for numの後でplotlyの色の扱いに合わせている。色についての詳細は以下。

【plotly&色の自動調節】入力したRGBをrgba(R, G, B, a)に自動変換する

続きを見る

プロット表示・非表示、軸タイプ変更、プロット色変更グラフ

def double_buttons_color():
    plot = []
    for num, y in ys.items():
        color = cm.jet(num / len(ys))
        color = plotly.colors.convert_to_RGB_255(color)
        color = f"rgb{color}"

        d = scatter(x=x, y=y, name=num, color=color)
        plot.append(d)

    # プロット線切り替えボタン作成
    buttons1 = set_visible_buttons(labels=labels, start=0)
    updatemenus1 = set_updatemenus(x=0.5, y=1.01, buttons=buttons1)

    # logとlienar切り替え用のボタンを作成
    buttons2 = set_log_buttons()
    updatemenus2 = set_updatemenus(x=0.54, y=1.05, buttons=buttons2)

    # プロット色を変更するボタン作成
    buttons3 = set_color_buttons(colors_scales=colors_scales)
    updatemenus3 = set_updatemenus(
        x=0.46, y=1.05, buttons=buttons3,
        button_type='dropdown', direction='down',
    )

    # ボタンは結合して1つにしておく
    updatemenus = updatemenus1 + updatemenus2 + updatemenus3

    # レイアウトの設定
    layout = set_layout(title='jet', updatemenus=updatemenus)

    fig = go.Figure(data=plot, layout=layout)
    fig.show(config=template.plotly_config())

    # グラフの保存
    name = 'double_buttons_color'
    save(fig=fig, name=name)

double_buttons_color()

ということで、本記事で使用したボタンを全部使ったグラフコードが上。プロット表示・非表示、軸タイプ変更、プロット色変更となかなかモリモリの内容。

このコードでできるグラフが本章初めのグラフとなる。見る環境でボタンが重なるのはどうにかして改善したいところ。

複数ボタンを使うことで自由度が上げられる

今回はplotlyのグラフに複数種のボタンを追加する方法について解説した。シンプルにupdatemenusを結合したらできたので結構びっくりした。

ボタンを使用することでplotlyのグラフの自由度は上がるが、複数種のボタンを使用することで用途を分けることができる。つまり、さらに自由度が増すということだ。

自由度が増すからもっと頭を使うことになるし、煩雑にならないような見た目も気にするようになる。選択の自由と頭の体操、老化予防には効くのかもしれない、知らんけど。

関連記事


【plotly&ボタン】plotlyのupdatemenusにbuttonsを追加

続きを見る


【plotly&config】グラフのツールバーを編集する

続きを見る


【plotly&ボタン】plotlyのupdatemenusに2回押し対応のbuttonsを追加

続きを見る


【plotly&スライダー】plotlyのslidersにスライダーを追加

続きを見る


【plotly&make_subplots】pythonのplotlyで複数グラフを1つの画像に描く

続きを見る


【plotly&rangeslider/rangeselector】レンジスライダーとレンジセレクターで時系列を見やすく

続きを見る


【plotly&fill】goで領域を塗りつぶし

続きを見る


【plotly&3D】goで3Dグラフを作成

続きを見る


自作したplotlyのマーカーのシンボル一覧
【plotly&マーカー】plotlyのマーカーのシンボル

続きを見る

ガジェット

2023/9/18

【デスクツアー2022下半期】モノは少なく、でも効率的に Desk Updating #0

今回はガジェットブロガーなのにデスク環境を構築していない執筆者の ...

ライフハック

2023/9/16

【Audible vs YouTube Premium】耳で聴く音声学習コンテンツを比較

ワイヤレスイヤホンが普及し耳で学習することへのハードルが格段に下 ...

完全ワイヤレスイヤホン(TWS)

2023/9/18

【SENNHEISER MOMENTUM True Wireless 3レビュー】全てが整ったイヤホン

今回は高音質・高機能なSENNHEISERのフラグシップ完全ワイヤレスイヤホン「SENNH ...

ライフハック

2023/3/11

【YouTube Premiumとは】メリットしかないから全員入れ

今回はYouTube Premiumを実際に使ってみてどうなのか、どんなメリット/デメリット ...

マウス

2023/8/17

【Logicool MX ERGOレビュー】疲れない作業効率重視トラックボールマウス

こんな人におすすめ トラックボールマウスの王道Logicool MX ERGOが気になるけどऩ ...

ベストバイ

2023/9/18

【ベストバイ2022】今年買って良かったモノのトップ10

2022年ベストバイ この1年を振り返って執筆者は何を買ったのか。ガジェッ& ...

スマホ

2023/1/15

【楽天モバイル×povo2.0の併用】月1,000円の保険付きデュアルSIM運用

こんな人におすすめ 楽天モバイルとpovo2.0のデュアルSIM運用って実際のとこ ...

マウス

2023/9/16

【Logicool MX ERGO vs MX Master 3】ERGOをメインにした決定的な理由

こんな疑問・お悩みを持っている人におすすめ 執筆者はLogicoolのハイエンӠ ...

macOSアプリケーション

2022/9/30

【Chrome拡張機能】便利で効率的に作業できるおすすめの拡張機能を18個紹介する

こんな人におすすめ Chromeの拡張機能を入れたいけど、調べても同じような ...

macOSアプリケーション

2023/5/3

【Automator活用術】Macで生産性を上げる作業の自動化術

今回はMacに標準でインストールされているアプリ「Automator」を使ってできる ...

Pythonを学びたいけど独学できる時間なんてない人へのすゝめ

執筆者は大学の研究室・大学院にて独学でPythonを習得した。

でも社会人になったら独学で行うには時間も体力もなくて大変だ。

時間がない社会人だからこそプロの教えを乞うのが効率的。

ここでは色んなタイプに合ったプログラミングスクールの紹介をする。

  • この記事を書いた人

メガネ

Webエンジニア駆け出し。独学のPythonで天文学系の大学院を修了。常時金欠のガジェット好きでM2 Pro MacBook Pro(30万円) x Galaxy S22 Ultra(17万円)使いの狂人。自己紹介と半生→変わって楽しいの繰り返しレビュー依頼など→お問い合わせ運営者情報、TwitterX@m_ten_pa、 YouTube@megatenpa、 Threads@megatenpa

-Plotly全般
-, ,