こんな人にオススメ
plotly
のボタンを使ってプロットの色を変更するのってどうやったらできる?
ついでにプロットの背景色の変更もしてみたい!
ということで、今回はplotly
のボタンを用いたプロット点やグラフ背景の色を変更する方法について解説する。実は背景色について以下の記事で少しだけ解説している。
-
-
【plotly&ボタン】plotlyのupdatemenusに2回押し対応のbuttonsを追加
続きを見る
今回はこの背景色の変更に加え、プロット点自体の変更やその他の注意事項なども踏まえた、色にフォーカスした記事だ。
正直、ここまで細かい内容は気にしなくても良いとは思うが、それでも知っていると変なことになったと騒がなくてよくなる。知っていると知らないでは雲泥の差。
python環境は以下。
- Python 3.9.6
- numpy 1.21.1
- plotly 5.1.0
- plotly-orca 3.4.2
作成したコードの全文
下準備
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のグラフ即席作成コード
続きを見る
用いるデータは直線データで、最終的には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の関数化
続きを見る
引数として以下のものをとる。
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")
最後にグラフ作成。色々と設定できるようにした結果、引数がエゲツないことになってしまった。一応、初期値を設定している引数が半分以上あるのでそこは指定しなくてもいい。
ややこしくなっているので、簡単にグラフ作成の流れを示しておく。
- プロットデータ作成:
scatter
関数 - ボタン作成:
get_buttons
関数、button_args
関数 - ボタンをグラフに配置:
updatemenus
- レイアウト作成:
layout
- グラフ表示:
fig
- グラフ保存:
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_colors
とfirst_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のボタンを押したら色々と色を変更できる方法について解説した。こんなコードいつ使うのかは割と疑問であるし執筆者自身もほぼ使ったことない。
背景色については一時期こんなんできるんだってことでしていたけど、結局面倒だからしなくなった。まあ経験としては残った。
でも、最後のやつとかはミスから知って、こうやって記事にできたから一回はこのような変な挙動を知って頭の片隅の端っこに置いておけばいつのひか使えるだろう。