カテゴリー

Plotly全般

【plotly&buttons】ボタンを押すとプロット・背景の色が変わる変なグラフ

2021年8月19日

こんな人にオススメ

plotlyのボタンを使ってプロットの色を変更するのってどうやったらできる?

ついでにプロットの背景色の変更もしてみたい!

ということで、今回はplotlyのボタンを用いたプロット点やグラフ背景の色を変更する方法について解説する。実は背景色について以下の記事で少しだけ解説している。

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

こんな人にオススメ plotlyでボ| ...

続きを見る

今回はこの背景色の変更に加え、プロット点自体の変更やその他の注意事項なども踏まえた、色にフォーカスした記事だ。

正直、ここまで細かい内容は気にしなくても良いとは思うが、それでも知っていると変なことになったと騒がなくてよくなる。知っていると知らないでは雲泥の差。

python環境は以下。

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

運営者のメガネです。YouTubeTwitterInstagramも運営してます。

自己紹介はこちらから、お問い合わせはこちら。

運営者メガネ

作成したコードの全文

下準備

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

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

x = np.array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
y = x

まずは下準備としてのimport関連。plotly_layout_templateは自作のplotlyテンプレート。以下の記事参照。

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

こんな人にオススメ plotlyって{ ...

続きを見る

用いるデータは直線データで、最終的には5つのデータを作成する予定。作成時には、1データずつ縦軸切片をズラして、他のグラフと重ならないようにする。

プロットデータ作成

def scatter(x, y, name, symbol, color):
    d = go.Scatter(
        mode='lines+markers',
        x=x, y=y,
        name=name,
        marker=dict(symbol=symbol, size=10,),
        line=dict(dash='solid', color=color, width=3,),
        hovertemplate=f"{name}<br>"
        + 'x: %{x} <br>'
        + 'y: %{y} <br>'
        + '<extra></extra>',
    )

    return d

plotlyのプロットデータを作成するための関数。最終的なグラフを描く際にプロットデータを作成してもいいが、ごちゃごちゃするので予め定義。関数化については以下参照。

【plotly&工夫】楽にグラフを描くためのplotlyの関数化

こんな人にオススメ plotlyでグ} ...

続きを見る

引数として以下のものをとる。

  • x: プロットデータの横軸の配列
  • y: プロットデータの縦軸の配列
  • name: プロットの名前
  • symbol: プロット点のマーカーの種類
  • color: プロット点・線の色

色切り替え用ボタン作成

def button_args(plot_color, bgcolor, title,
                xgridcolor, ygridcolor, legend_fontcolor):
    """プロットとグラフのプロット領域の背景色を変更するボタンの情報

    Parameters
    ----------
    plot_color : str
        プロットの色
    bgcolor : str
        プロット領域の背景の色
    title : str
        グラフタイトル
    xgridcolor : str
        x軸に垂直なグリッド線の色
    ygridcolor : str
        y軸に垂直なグリッド線の色
    legend_fontcolor : str
        凡例のフォントの色

    Returns
    -------
    list
        色変更用のボタン情報が入ったlist
    """

    args = [
        # プロット関連の設定
        {'marker.color': plot_color, 'line.color': plot_color},
        # レイアウト関連の設定
        {
            'plot_bgcolor': bgcolor,
            'title.text': title,
            'xaxis.gridcolor': xgridcolor,
            'yaxis.gridcolor': ygridcolor,
            'legend.font.color': legend_fontcolor,
            'legend.bordercolor': legend_fontcolor,
        },

    ]
    return args

ここが本記事の目玉となる部分。ボタンを押すと以下の項目が変更される。1つ目のdictがプロット関係、2つ目レイアウト関係を示す。

  • aegs内1つ目のdict
    • marker.color: プロット点の色
    • line.color: プロット線の色
  • args内2つ目のdict
    • plot_bgcolor: プロット領域の背景色
    • title.text: グラフタイトル
    • xaxis.gridcolor: 横軸に垂直なグリッド線の色
    • yaxis.gridcolor: 縦軸に垂直なグリッド線の色
    • legend.font.color: 凡例のフォントカラー
    • legend.bordercolor: 凡例の枠の色

marker.color, line.colorについて、それぞれ以下のように記述するとうまくいきそうに見えるが、実はこれだとうまくいかない。

{'marker': {'color': plot_color}, 'line': {'color': plot_color}}

こうするとmarkerの方はplot_colorで指定した色が1つずつプロット点に反映され、足りない部分は黒で補われる。要するに['red', 'blue']と色を指定すると以下のような色の割り振りとなる。

  • 1データ目: red
  • 2データ目: blue
  • 3データ目から最後のデータまで: 多分black

lineの方はplotlyでデフォルトで用意された青→オレンジ→緑→...の色が反映される。要するにplot_colorで指定した色は反映されず、強制的にデフォルトの色になるということ。謎。

したがって、ボタンを作成する際には「.」を使用して各項目のdict内の内容を変更するのが安全。

ボタン作成

def get_buttons(change_colors, change_bgcolor, first_colors, first_bgcolor,
                change_xgridcolor, change_ygridcolor,
                first_xgridcolor, first_ygridcolor,
                change_legend_fontcolor, first_legend_fontcolor):
    """グラフに追加するボタンを作成
    1. 全プロット表示
    2. 各プロット表示
    3. プロット、レイアウトの色変更

    Parameters
    ----------
    change_colors : tuple
        1回ボタンを押した時の各プロットの色
    change_bgcolor : str
        1回ボタンを押した時のプロット領域の背景色
    first_colors : tuple
        1回ボタンを押した後に再度ボタンを押した時の各プロットの色=初期色
    first_bgcolor : str
        1回ボタンを押した後に再度ボタンを押した時のプロット領域の背景色=初期色
    change_xgridcolor : str
        1回ボタンを押した時のx軸のグリッドの色
    change_ygridcolor : str
        1回ボタンを押した時のy軸のグリッドの色
    first_xgridcolor : str
        1回ボタンを押した後に再度ボタンを押した時のx軸のグリッド色=初期色
    first_ygridcolor : str
        1回ボタンを押した後に再度ボタンを押した時のy軸のグリッド色=初期色
    change_legend_fontcolor : str
        1回ボタンを押した時の凡例の文字色
    first_legend_fontcolor : str
        1回ボタンを押した後に再度ボタンを押した時の凡例の文字色

    Returns
    -------
    list
        上記1., 2., 3.のボタン情報を入れたlist
    """

    # グラフをダークモードにするボタン
    args_dct = dict(
        label='change color', method='update',
        # 1回目にボタンを押したときの設定
        args=button_args(
            plot_color=change_colors, bgcolor=change_bgcolor,
            title='1st click',
            xgridcolor=change_xgridcolor, ygridcolor=change_ygridcolor,
            legend_fontcolor=change_legend_fontcolor,
        ),
        # 2回目にボタンを押したときの設定
        args2=button_args(
            plot_color=first_colors, bgcolor=first_bgcolor,
            title='2nd click',
            xgridcolor=first_xgridcolor, ygridcolor=first_ygridcolor,
            legend_fontcolor=first_legend_fontcolor,
        ),
    )

    # args_dctはdictなのでlistに変更しておく
    buttons = [args_dct]
    return buttons

button_args関数ではボタンを押したときのグラフ内の色変化を記述した。get_buttons関数ではその他の設定やボタンのラベルを設定。button_args関数をここで使用。

今回はボタンを1回押すと色が変わり、再度押すと別の色になるように設定している。args2が2回目のボタンに相当する。別に4回目でも6回目でもいいけど。

changeと名付けられた引数が1回目のボタン、firstと名付けられた引数が2回目のボタンに対応するようにしている。firstは2回目のボタンで元の色に戻す予定だからこう名付けた。

グラフ作成

def buttons_color(save_name, first_colors, change_colors,
                  first_bgcolor, change_bgcolor,
                  change_xgridcolor='gray', change_ygridcolor='gray',
                  first_xgridcolor='gray', first_ygridcolor='gray',
                  change_legend_fontcolor='white',
                  first_legend_fontcolor='black',):

    # プロットデータ作成
    symbols = ['circle', 'hexagram', 'star', 'x', 'square']
    plot = []
    for num, (symbol, color) in enumerate(zip(symbols, first_colors)):
        name = f"data{num}"
        d = scatter(x=x, y=y + num, name=name, symbol=symbol, color=color)
        plot.append(d)

    # ボタン作成
    buttons = list(
        get_buttons(
            change_colors=change_colors, change_bgcolor=change_bgcolor,
            first_colors=first_colors, first_bgcolor=first_bgcolor,
            change_xgridcolor=change_xgridcolor,
            change_ygridcolor=change_ygridcolor,
            first_xgridcolor=first_xgridcolor,
            first_ygridcolor=first_ygridcolor,
            change_legend_fontcolor=change_legend_fontcolor,
            first_legend_fontcolor=first_legend_fontcolor,
        )
    )
    updatemenus = [
        dict(
            type="buttons", active=-1, direction="right",
            x=0.5, y=1.01, xanchor='center', yanchor='bottom',
            buttons=buttons,  # 作成したボタン情報はここに入れる
        )
    ]

    # レイアウト作成
    layout = go.Layout(
        template=template.plotly_layout(),
        title=dict(text='0 click',),
        xaxis=dict(title='x', gridcolor=first_xgridcolor,),
        yaxis=dict(title='y', gridcolor=first_ygridcolor,),
        updatemenus=updatemenus,
    )

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

    pio.write_html(fig, f"{save_name}.html")
    pio.write_image(fig, f"{save_name}.png")

最後にグラフ作成。色々と設定できるようにした結果、引数がエゲツないことになってしまった。一応、初期値を設定している引数が半分以上あるのでそこは指定しなくてもいい。

ややこしくなっているので、簡単にグラフ作成の流れを示しておく。

  1. プロットデータ作成: scatter関数
  2. ボタン作成: get_buttons関数、button_args関数
  3. ボタンをグラフに配置: updatemenus
  4. レイアウト作成: layout
  5. グラフ表示: fig
  6. グラフ保存: pio

ボタンを押すとプロット点・グラフ背景の色が変わるグラフ


まずはボタンを押すとプロット点やグラフ背景の色が変わるグラフから。このグラフの「change color」と書かれたボタンを押すと、プロット点と背景の色が変わる。

各色は以下のように設定している。基本、背景が無色透明ならプロットの色は暗めに、背景が黒ならプロット点は明るい色に設定している。あとはグリッド色は明るくしておいた。

# ボタンを押すと背景・プロット点の色が変わるグラフ
change_colors = ('whitesmoke', 'violet', 'yellow', 'plum', 'lightpink')
# 初期カラー
first_colors = ('black', 'blueviolet', 'darkred', 'indigo', 'darkblue')
# 1回ボタンを押した時は真っ黒
change_bgcolor = 'rgba(0, 0, 0, 1)'
# 再度ボタンを押した時は無色透明
first_bgcolor = 'rgba(0, 0, 0, 0)'

buttons_color(
    save_name='buttons_all_colors',
    first_colors=first_colors, change_colors=change_colors,
    first_bgcolor=first_bgcolor, change_bgcolor=change_bgcolor,
    change_xgridcolor='lime', change_ygridcolor='blue',
)

プロットの数より色の数が多いと無視


基本はさっきと同じだけど、change_colorsfirst_colorsで作成した色の数がプロットの数より多い場合ははみ出た色は無視され使われない。

ここではボタンを押したときにcyanが、再度押されたときにはredが反映される予定だったが、実際のグラフではこれらの色は使用されていない。

まあはみ出たから当然なのかもしれないけど。

# プロットの数より色の数の方が多い時ははみ出た色は無視される
change_colors = ('whitesmoke', 'violet', 'yellow', 'plum', 'lightpink', 'cyan')
# 初期カラー
first_colors = ('black', 'blueviolet', 'darkred', 'indigo', 'darkblue', 'red')
# 1回ボタンを押した時は真っ黒
change_bgcolor = 'rgba(0, 0, 0, 1)'
# 再度ボタンを押した時は無色透明
first_bgcolor = 'rgba(0, 0, 0, 0)'

buttons_color(
    save_name='buttons_over_colors',
    first_colors=first_colors, change_colors=change_colors,
    first_bgcolor=first_bgcolor, change_bgcolor=change_bgcolor,
    change_xgridcolor='lime', change_ygridcolor='blue',
)

色が足りないと指定した色を繰り返す


逆に色が足りない場合は、指定した色を繰り返して表示される。ここではyellow, violet, cyanを使用したが、これだけだとプロット数5には足りない。

ではエラーになるかといえばそうではなく、この3色が繰り返された5つのプロットの色が決定される。だから今回だとyellow, violet, cyan, yellow, violetという色になる。

# 全プロットに対して、変更後の色が足りない
change_colors = ['yellow', 'violet', 'cyan']
# 初期カラー
first_colors = ('black', 'blueviolet', 'darkred', 'indigo', 'darkblue')
# 1回ボタンを押した時は無色透明
change_bgcolor = 'rgba(0, 0, 0, 0)'
# 再度ボタンを押した時も無色透明
first_bgcolor = 'rgba(0, 0, 0, 0)'

buttons_color(
    save_name='buttons_lack_colors',
    first_colors=first_colors, change_colors=change_colors,
    first_bgcolor=first_bgcolor, change_bgcolor=change_bgcolor,
    change_legend_fontcolor='black'  # 凡例のフォントカラーを黒にする
)

色を2次元配列にするとプロットごとに色が変わる


最後は実際に執筆者が陥ったミスから知った内容。これまでは指定する色は1次元配列にしていたが、これを2次元配列にすると色の割り振りがデータごとではなくなる。

各データのプロット点ごとに色が割り振られる、ここでは色を5つ指定しているので初めの5データの色がこの色となる。そして、はみ出たプロット点の色は黒になる。

change_colors = ('whitesmoke', 'violet', 'yellow', 'plum', 'lightpink')
# 初期カラー
first_colors = ('black', 'blueviolet', 'darkred', 'indigo', 'darkblue')

# 2次元にする
change_colors = (change_colors,)

# 1回ボタンを押した時は無色透明
change_bgcolor = 'rgba(0, 0, 0, 0)'
# 再度ボタンを押した時も無色透明
first_bgcolor = 'rgba(0, 0, 0, 0)'

buttons_color(
    save_name='buttons_2d_colors',
    first_colors=first_colors, change_colors=change_colors,
    first_bgcolor=first_bgcolor, change_bgcolor=change_bgcolor,
    change_legend_fontcolor='black'  # 凡例のフォントカラーを黒にする
)

実際に執筆者はこの現象でなぜ色がここまで異なるのかがわからなかった。当時は今回の関数のように小分けにコードを作成してなくてデバッグもせずに悶々としていた。

今ではコードを関数で小分けにしているのでかなり見やすくなったし、ミスにも気づきやすくなった。やっぱり小分けにして考えるのは大切なのかもしれない。

ほぼ100使わないけど知ってたら対処できる

今回はplotlyのupdatemenusのボタンを押したら色々と色を変更できる方法について解説した。こんなコードいつ使うのかは割と疑問であるし執筆者自身もほぼ使ったことない。

背景色については一時期こんなんできるんだってことでしていたけど、結局面倒だからしなくなった。まあ経験としては残った。

でも、最後のやつとかはミスから知って、こうやって記事にできたから一回はこのような変な挙動を知って頭の片隅の端っこに置いておけばいつのひか使えるだろう。

スイッチボット

2022/9/11

【SwitchBotロックレビュー】これからのスタンダードになりうるスマートロック

こんな人にオススメ SwitchBotからスマートロック「SwitchBotロック」が発売された ...

生活に役立つ

2022/10/25

【メガネ厳選】クソ便利に使っているサービスやアイテム達

このページでは執筆者「メガネ」が実際に使って便利だと感じているサ ...

マウス

2022/9/11

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

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

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

2022/11/21

【ながら聴きイヤホン比較】SONY LinkBuds、ambie、BoCoはどれがおすすめ?

こんな人におすすめ 耳を塞がない開放型のイヤホンに完全ワイヤレスӟ ...

macOSアプリケーション

2022/10/15

【M1 Mac】MacBook Proに入れている便利でニッチなアプリを21個紹介する

こんな人におすすめ MacBookを購入してLINEとか必要最低限のアプリは入れた。 ...

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

2022/10/23

【SENNHEISER MOMENTUM True Wireless 3レビュー】高レベルでバランス型の高音質イヤホン

こんな人におすすめ SENNHEISER MOMENTUM True Wireless 3って実際のところどうなの? 評判は良い ...

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

2022/11/21

【SONY WF-1000XM4レビュー】神とゴミのハーフ&ハーフ

こんな人におすすめ SONYのフラグシップモデル「SONY WF-1000XM4」ってどれくらい性 ...

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

2022/8/19

【Nothing ear (1)レビュー】ライトな完成度、アップデートに期待

こんな人にオススメ 完全ワイヤレスイヤホン(TWS)でスケルトンボディ ...

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

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

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

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

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

  • この記事を書いた人

メガネ

ベンチャー企業のWebエンジニア駆け出し。独学のPythonで天文学系の大学院を修了→新卒を1.5年で辞める→転職→今に至る。
常時金欠のガジェット好きでM1 MacBook Pro x Galaxy S22 Ultraの狂人。
人見知りで根暗だったけど、人生楽しもうと思って良い方向に狂う→人生が楽しい

ガジェットのレビューとPythonコードを記事にしています。ぜひ楽しんでください🦊
自己紹介と半生→変わって楽しいの繰り返し

-Plotly全般
-, ,