カテゴリー

Plotly全般

【plotly&shapes】レイアウトとしてグラフ内に図形を配置する

2021年9月4日

こんな人にオススメ

plotlyでグラフ内に図形を配置したいんだけど、どうしたらいい?

ということで、今回はplotlyを使用してグラフ内に図形を配置する方法を解説する。方法は2つあって、プロット点として作成するというのと、レイアウトに図形として配置するというの。

前者だと通常のplotlyのプロットと同じように線を描いたり塗りつぶしたりしたらいいが凡例がついたり面倒。レイアウトに図形として配置する場合は凡例などがなくなるのでスッキリする。

もちろんプロットで表現する場合は凡例を消せばいいけど、わざわざ消すのも面倒。図形だとただそこにいるだけ。

python環境は以下。

  • Python 3.9.6
  • numpy 1.21.1
  • plotly 5.1.0
  • plotly-orca 3.4.2
スポンサーリンク
スポンサーリンク

運営者のメガネと申します。TwitterInstagramも運営中。

自己紹介はこちらから、お問い合わせはこちらからお願いいたします。

運営者メガネ

作成したコード全文

下準備

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.orcaconfigについては以下参照。

plotlyでグラフを静止画として保存する際に出るポップアップ
【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が必要になるので予め変数として定義しておく。

あとはdatalayoutconfigを設定するだけ。

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.nanNoneを指定して、三角形と四角形の境目を決めた。

# 空白や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.Layoutshapesで図形を作成


ということで、本題のgo.Layoutshapesで図形を描く方法について解説する。このshapesの機能を使用することで、ただそこにいるだけの凡例もホバーもない図形を作成可能。

layoutへの適用の仕方はupdatemenussliderと同じ感じ。

【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: xrefy
  • fillcolor: 図形の塗りつぶしの色。Noneで塗りつぶしなし
  • opacity: 図形の透明度。0で無色透明。None1と同じ

あとはこれを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の図形は絶対座標として描画されるので、プロット領域を移動させるとついてくる。

一方で、xrefyrefpaperに設定しているとグラフの相対座標として図形が固定されるので、プロット領域を移動させても図形はその場に留まる。

また、xref='x'に、yref='paper'とした四角形では、横方向には四角は移動するが、縦方向には移動しない。縦方向については相対座標になっているで固定になるからだ。

塗りつぶしを試してみる


一応、塗りつぶしも試してみる。まあ、fillcolorを指定するか否かというだけだが。opacityNoneにすると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は水平線
)

凡例もホバーもない、邪魔されない図形

今回はplotlygo.Layout内にshapesを設定することで、グラフ内に図形を配置する方法について解説した。この図形は凡例もホバーもないただの図形。そこにいるだけの図形。

なので、予めずっとその場に固定させて邪魔しないでほしい場合や、常にプロット点に何かしたらの情報を図形として示したい時に使えそう。

執筆者は基本使わない。

関連記事

【plotly&add_vrect, hrect】グラフに垂直・水平の塗りつぶし

続きを見る

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

続きを見る

【plotly&グラフテーマ】plotly既存のthemasをグラフ化

続きを見る

【plt vs plotly】matplotlibとgoでグラフの比較

続きを見る

関連コンテンツ

スポンサーリンク

Amazonのお買い物で損したない人へ

1回のチャージ金額通常会員プライム会員
¥90,000〜2.0%2.5%
¥40,000〜1.5%2.0%
¥20,000〜1.0%1.5%
¥5,000〜0.5%1.0%

Amazonギフト券にチャージすることでお得にお買い物できる。通常のAmazon会員なら最大2.0%、プライム会員なら2.5%還元なのでバカにならない。

ゲットしたポイントは通常のAmazonでのお買い物に使えるからお得だ。一度チャージしてしまえば、好きなタイミングでお買いものできる。

なお、有効期限は10年だから安心だ。いつでも気軽にAmazonでお買い物できる。

Amazonチャージはここから出来るで

もっとお得なAmazon Prime会員はこちらから

30日間無料登録

執筆者も便利に使わせてもらってる

スポンサーリンク

  • この記事を書いた人

メガネ

独学でpythonを学び天文学系の大学院を修了。 ガジェット好きでMac×Android使い。色んなスマホやイヤホンを購入したいけどお金がなさすぎて困窮中。 元々、人見知りで根暗だったけど、人生楽しもうと思って良い方向に狂ったために今も人生めちゃくちゃ楽しい。 pythonとガジェットをメインにブログを書いていますので、興味を持たれましたらちょこちょこ訪問してくだされば幸いです🥰。 自己紹介→変わって楽しいの繰り返し

-Plotly全般
-, ,