こんな人にオススメ
plotly
でグラフ内に図形を配置したいんだけど、どうしたらいい?
ということで、今回はplotlyを使用してグラフ内に図形を配置する方法を解説する。方法は2つあって、プロット点として作成するというのと、レイアウトに図形として配置するというの。
前者だと通常のplotlyのプロットと同じように線を描いたり塗りつぶしたりしたらいいが凡例がついたり面倒。レイアウトに図形として配置する場合は凡例などがなくなるのでスッキリする。
もちろんプロットで表現する場合は凡例を消せばいいけど、わざわざ消すのも面倒。図形だとただそこにいるだけ。
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
まずは下準備としてのimport
関連。plotly_layout_template
は自作のplotly
のテンプレートで、これを使用することで簡単にキレイなグラフを作成することができる。
-
-
【随時更新 備忘録】plotlyのグラフ即席作成コード
続きを見る
go.Scatter
でプロット点として図形を作成
まずはgo.Scatter
でプロット点としての図形を作成する。これについては普通にグラフを作成する容量でコードを書いたらいい。
プロット点を作成する関数
def scatter(x, y): plot = go.Scatter(x=x, y=y, fill="toself",) return plot
今回は特にこだわることなく簡潔に関数を作成。塗りつぶしはtoself
として自分自身を塗りつぶす、すなわち図形内部を塗りつぶすように設定した。
グラフを保存する関数
# グラフ保存用の関数 def save(fig, config, save_name): pio.orca.config.executable = '/Applications/orca.app/Contents/MacOS/orca' pio.write_html(fig, f"{save_name}.html", config=config,) pio.write_image(fig, f"{save_name}.png")
グラフ保存も関数化。htmlファイルとpngファイルでグラフを保存する。htmlファイルでは自分で設定したconfig
を使用できるようにconfig
を設定。
pio.orca
、config
については以下参照。
-
-
【plotly&orca】plotlyで静止画保存(orca)
続きを見る
-
-
【plotly&config】グラフのツールバーを編集する
続きを見る
グラフ表示とグラフ保存をする関数
# グラフ表示とグラフ保存を一括で行う def fig_save(data, layout, save_name): config = template.plotly_config() fig = go.Figure(data=data, layout=layout) fig.show(config=config) save(fig=fig, config=config, save_name=save_name)
先程のグラフ保存用の関数に加えて、グラフ自体を表示するための処理を同じ関数で行う。グラフ表示にもグラフ保存にもconfig
が必要になるので予め変数として定義しておく。
あとはdata
とlayout
、config
を設定するだけ。
go.Scatter
から図形を作成する関数
# Scatterプロットから図形を作成 def scatter_shapes(x, y, title, save_name): plot = scatter( x=x, y=y, ) layout = go.Layout( template=template.plotly_layout(), title=title, ) fig_save(data=plot, layout=layout, save_name=save_name)
上で解説した関数を適用して、go.Scatter
を用いた図形描画の関数を作成。レイアウトのテンプレートで自作テンプレートのplotly_layout_template
を使用。タイトルは一応つけておいた。
図形を作成
# 連続すると線で繋がる scatter_shapes( x=[0, 1, 2, 0, 3, 3, 5, 5, 3], y=[0, 2, 0, 0, 0.5, 1.5, 1.5, 0.5, 0.5], title='sequence', save_name='scatter_lines_miss', )
本章はじめのグラフが上のコードで三角形と四角形の2種類の図形を作成したが、1つのgo.Scatterで賄う場合は2つの図形の切れ目となるデータ配列の間に、何かしら切れ目を示すデータを入れておかなくてはいけない。
例えば、以下では空白の''
、NaNのnp.nan
、None
を指定して、三角形と四角形の境目を決めた。
# 空白やNaN, Noneを入れると途切れさせることができる words = ('', np.nan, None) names = ('space', 'nan', 'none') for word, name in zip(words, names): scatter_shapes( x=[0, 1, 2, 0, word, 3, 3, 5, 5, 3], y=[0, 2, 0, 0, word, 0.5, 1.5, 1.5, 0.5, 0.5], title=f"'{word}'", save_name=name, )
このように切れ目を指定することで、図形を独立させて表示させることができる。
円もできちゃう
もちろん、ちゃんと計算してあげることで円形の作成もできる。この場合は角度とx
, y
の関係を知っておかないと厳しいところがある。
# 円を作成 deg = np.arange(0, 360, 0.5) rad = np.deg2rad(deg) r = 1 x = r * np.cos(rad) y = r * np.sin(rad) scatter_shapes( x=x, y=y, title='circle', save_name='circle', )
ここまで、プロット点として図形を作成したのだが、ただ単に図形が欲しいだけで、凡例もホバーもいらない場合、いちいちgo.Scatter
の設定で凡例・ホバーをオフにしないといけない。
さらに、全てgo.Scatter
で書いてしまうと、本来のデータ点と混ざる危険があるかもしれないかもしれない。ちゃんとコメントアウトしておけば問題ないことだけど。
そんな時はプロット点ではなく、レイアウトとして図形を作成してあげればいい。これが本題。
go.Layout
のshapes
で図形を作成
ということで、本題のgo.Layout
のshapes
で図形を描く方法について解説する。このshapes
の機能を使用することで、ただそこにいるだけの凡例もホバーもない図形を作成可能。
layout
への適用の仕方はupdatemenus
やslider
と同じ感じ。
-
-
【plotly&ボタン】plotlyのupdatemenusにbuttonsを追加
続きを見る
-
-
【plotly&スライダー】plotlyのslidersにスライダーを追加
続きを見る
図形作成用の関数
# shape作成用の関数 def set_shapes(type, x0, x1, y0, y1, color, width, dash, xref, yref, fillcolor=None, opacity=1,): shapes = dict( type=type, # 図形の種類 xref=xref, yref=yref, # xとyを、絶対座標に固定するか相対座標に固定するか x0=x0, x1=x1, y0=y0, y1=y1, # x, y座標値 line=dict(color=color, width=width, dash=dash), fillcolor=fillcolor, opacity=opacity, ) return shapes
いきなりだが本命の図形作成用の関数を作成。引数としては以下。
type
: 図形の種類(線、四角、三角、SVGスタイルでの描画(後述))x0
,y0
: それぞれx
,y
の開始位置x1
,y1
: それぞれx
,y
の終了位置color
: 図形の色。塗りつぶしできる図形の場合は枠線の色width
: 図形の線の太さ。塗りつぶしできる図形の場合は枠線の太さdash
: 図形の線種。塗りつぶしできる図形の場合は枠線の線種xref
: 図形のx
の基準点。座標を基準とする絶対座標か、グラフを基準とする相対座標かyref
:xref
のy
版fillcolor
: 図形の塗りつぶしの色。None
で塗りつぶしなしopacity
: 図形の透明度。0
で無色透明。None
は1
と同じ
あとはこれをlist
に入れてlayout
の引数に設定すればいい。次では実際にshpaes
を作成する。
図形作成
# shapes作成 def get_shape(): # shapesでレイアウト上の図形として作成 types = ('line', 'line', 'line', 'rect', 'circle') # 線と四角形と三角形 x0s = (1, 2, 0, 5, 0.6,) # xの始まり値 x1s = (1, 5, 0.8, 6, 1,) # xの終わり値 y0s = (1, 2, 0.5, 0.1, 0,) # yの始まり値 y1s = (2, 2, 1, 0.9, 3,) # yの終わり値 colors = ('red', 'blue', 'green', 'purple', 'deeppink',) widths = (3, 5, 2, 3, 3,) dashs = ('dashdot', 'solid', 'dot', 'dash', 'dash',) # refは、Noneとyは絶対位置、papaerは相対位置; xrefs = (None, 'x', 'paper', 'x', 'paper',) yrefs = (None, 'y', 'paper', 'paper', 'y',) # shapesの作成 shapes = [] for num in range(len(x0s)): shape = set_shapes( type=types[num], x0=x0s[num], x1=x1s[num], y0=y0s[num], y1=y1s[num], color=colors[num], width=widths[num], dash=dashs[num], xref=xrefs[num], yref=yrefs[num], ) shapes.append(shape) return shapes
色々な図形を作成。引数として指定するのが面倒だったので、関数内で図形の情報を記載。今回は5種類の図形を作成。先程のset_shapes
関数をここで使用。
グラフに図形を追加
def layout_shapes(save_name): plot = go.Scatter( mode='lines', # Scatterでは座標値に沿って移動(絶対位置) x=[1.3, 5.2], y=[1, 1.8], ) layout = go.Layout( template=template.plotly_layout(), title='lines', xaxis=dict(range=(0, 7)), yaxis=dict(range=(0, 3)), shapes=get_shape(), # shapesはlayoutで設定 ) fig_save(data=plot, layout=layout, save_name=save_name) layout_shapes(save_name='layout_shapes')
これまでの関数を使用してグラフ内に図形を描画。比較のためにgo.Scatterの図形も追加しておく。go.Scatterの図形は絶対座標として描画されるので、プロット領域を移動させるとついてくる。
一方で、xref
・yref
をpaper
に設定しているとグラフの相対座標として図形が固定されるので、プロット領域を移動させても図形はその場に留まる。
また、xref='x'
に、yref='paper'
とした四角形では、横方向には四角は移動するが、縦方向には移動しない。縦方向については相対座標になっているで固定になるからだ。
塗りつぶしを試してみる
一応、塗りつぶしも試してみる。まあ、fillcolor
を指定するか否かというだけだが。opacity
をNone
にすると1
と同じ効果が得られる。
def fill(save_name): x0s = (0, 2, 4) x1s = (1, 4, 8) y0s = (0, 0, 0) y1s = (1, 2, 4) colors = ('red', 'blue', 'green') # 枠線の色 fillcolors = ('violet', None, 'olive') # 塗りつぶしの色。Noneは塗りつぶしなし opacities = (0.5, None, None) # 透明度をNoneにすると1と同じ透明度になる shapes = [] for num in range(len(x0s)): shape = set_shapes( type='circle', x0=x0s[num], x1=x1s[num], y0=y0s[num], y1=y1s[num], color=colors[num], width=5, dash='solid', xref='x', yref='y', fillcolor=fillcolors[num], opacity=opacities[num], # 塗りつぶし関連 ) shapes.append(shape) layout = go.Layout( template=template.plotly_layout(), title='filled', shapes=shapes, ) fig_save(data=None, layout=layout, save_name=save_name) fill(save_name='filled')
type='path'
を試す
最後にshapes
の引数であるtype
が'path'
の状況について解説する。これは簡単にいうと、自分で自由度高く図形が描けるというもの。
文字の指定がややこしいので、ここでは解説しない。他のサイトなどを参照いただきたい。本記事では簡単な例を扱う。
線の図形を作成
基本的な使い方は以下の通り。
- 初期座標は「
M x0,y0
」と記述 - 「
x0,y0
」でも「x0, y0
」でも良い L
は直線を示すL1,0
なら、その時にいた点から(1, 0)
へ直線を引く- 操作間は半角スペースが必要
- 図形を閉じたい場合は最後に「
Z
」を記述
以下では関数の可変長引数args
を使用して、任意の数の座標で直線を繋いで図形を作成した。このコードでできる関数が上の図形。桜の花びらっぽいのが簡単にできる。
-
-
【python&関数化】defとかargsとかを使って関数を作成する
続きを見る
# 線のグラフを作成 def svg_lines(*args, save_name): lst = [] for arg in args: tmp = f"L{arg.replace(' ', '')}" # キレイにするために空白削除 lst.append(tmp) joined = ' '.join(lst) # それぞれの座標値を空白で結合 joined = joined[1:] # 初めのLはいらないので使用しない path = f"M {joined} Z" # 最初を示すMと最後を示すZを追加 shapes = dict( type='path', path=path, line_color='red', fillcolor='violet', ) layout = go.Layout( template=template.plotly_layout(), title='path', shapes=[shapes], ) fig_save(data=None, layout=layout, save_name=save_name) svg_lines( '0, 3', '1, 2', '0.5,1', '0, -0.1', '5, 2', save_name='svg_lines', )
色々な図形を作成
ということで、最後はtype='path'
を使用して色々な図形を作成してみた。本当はLのような図形設定がもっとあるんだけど、うまくいかなかったから除外。うまくいったのは以下。
L
:直線Q
: 2次元ベジェ曲線C
: 3次元ベジェ曲線V
: 推薦H
: 水平線
表示する座標領域が狭すぎる・広すぎることが問題かもしれないが、とりあえず上の図形を作成した。まずは使用した関数。シンプルにひたすらshapes
を作成。
def svg_path(save_name, **kwargs): shapes = [] for color, path in kwargs.items(): shape = dict( type='path', path=path, line_color=color, ) shapes.append(shape) layout = go.Layout( template=template.plotly_layout(), title='path', shapes=shapes, ) fig_save(data=None, layout=layout, save_name=save_name)
作成した図形の指定方法は以下。
svg_path( save_name='svg_kwargs', red='M 1,1 Q3,0 2,1', # Qは2次元ベジェ曲線 blue='M 2,2 Q5,2 4,3 Z', # Zで閉じた版 green='M 0,0 L0.5,0.5 L1,0 L1.5,0.5 L2,0 L2.5,0.5 L3,0', # ギザギザ purple='M 0,0 C1,3 2,4 5,3.5', # Cは3次元ベジェ曲線 darkorange='M3,3 V0', # Vは垂線 olive='M3,3 H0', # Hは水平線 )
凡例もホバーもない、邪魔されない図形
今回はplotly
のgo.Layout
内にshapes
を設定することで、グラフ内に図形を配置する方法について解説した。この図形は凡例もホバーもないただの図形。そこにいるだけの図形。
なので、予めずっとその場に固定させて邪魔しないでほしい場合や、常にプロット点に何かしたらの情報を図形として示したい時に使えそう。
執筆者は基本使わない。
関連記事
-
-
【plotly&add_vrect, hrect】グラフに垂直・水平の塗りつぶし
続きを見る
-
-
【plotly&buttons】ボタンを押すとプロット・背景の色が変わる変なグラフ
続きを見る
-
-
【plotly&グラフテーマ】plotly既存のthemasをグラフ化
続きを見る
-
-
【plt vs plotly】matplotlibとgoでグラフの比較
続きを見る