カテゴリー

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

plt

【plt&mpld3】pltで作ったグラフを後から編集できるようにhtmlで保存

2021年8月7日

こんな人にオススメ

matplotlib.pyplotpltでグラフを保存。それはいいんだけど、グラフを保存しちゃうと静止画だから後で細かい部分を確認できないじゃん。

グラフを保存した後でも動かせる方法ってない?

っということで、今回はpltを使って保存した後でも動かせるグラフを作成する。どういうことかというと、pltでグラフを保存すると静止画だから動かせない。しかし、本記事の方法を使用すると、保存した後でもグラフを動かすことができる。

グラフを動かすってのはplotlyが得意な部分ではあるけど、pltとはコードの書き方が結構違うからとっつきにくい。そんな人にはpltのまま少しコードを咥えたりするだけでできるmpld3がおすすめ。

python環境は以下。

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

運営者のメガネです。YouTubeTwitterInstagram、自己紹介はこちら、お問い合わせはこちらから。

運営者メガネ

下準備

import sys
import numpy as np
import matplotlib.pyplot as plt
import mpld3
import plotly.graph_objects as go
import plotly.io as pio
sys.path.append('../../')
import plotly_layout_template as template

x = np.linspace(0, 2 * np.pi)
y1 = np.sin(x)
y2 = np.cos(x)

まずはグラフ作成用のimport関連とデータの作成。最後にplotlyを使用したグラフも作成するのでplotlyimportplotly_layout_templateは自作のplotlyテンプレート。以下参照。

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

続きを見る

通常のpltだとグラフ保存後に動かせない

plt。それはpythonでグラフを作成する人誰もが通るだろうライブラリ。簡単にグラフを作成できるしカスタムも豊富。さらにユーザー数も半端じゃない数いるので困った時も解決策がサイトにゴロゴロ。

しかし、pltの個人的最大の弱点が、グラフを保存した後に動かせないということ。当たり前だけど静止画で保存すると画像として保存されるので動かせない。

動かせないから執筆者は動かすことに特化しているplotlyに電撃移籍したわけだが、本記事ではなんとpltでも動かせるようにした。

グラフを眺めるだけなら保存でいい

def plt_sin_cos():
    fig, ax = plt.subplots(1, 1)
    ax.set_title('title')
    ax.set_xlabel('x')
    ax.set_ylabel('y')
    ax.plot(x, y1, 'o-', ms=4, label='sin')
    ax.plot(x, y2, '^-', ms=4, label='cos')
    ax.legend()  # 凡例を追加

    ax.grid(which='major', ls='-')
    ax.grid(which='minor', ls='--', alpha=0.5)

    fig.savefig('plt_sin_cos.png')
    fig.show()

plt_sin_cos()

本章の始めに描いたグラフは上のコードで作成したものだが、静止画としてpngを使用しているので、グラフを保存した後は細かい部分の拡大やグラフの移動ができない。

ならplotlyみたいに.html形式で保存すればいいじゃないかということで、試してみた。ダメです。

fig.savefig('plt_sin_cos.html')
# ValueError: Format 'html' is not supported (supported formats: eps, jpeg, jpg, pdf, pgf, png, ps, raw, rgba, svg, svgz, tif, tiff)

要するに画像として保存しろってことだろう。拡張子について詳しくないけどこれを見る限りそうだと思う。

保存後も動かしたい

一応、pltでもグラフを動かすことができる。グラフを作成した後だ。上の画像の左下のようなマークでグラフを動かしたり拡大したり元に戻したりできる。

しかし、これはグラフを作成したその時だけに効くもので、閉じると終わり。だから、動かしたいならまたコードを回してグラフを作成しないといけない。面倒。

mpld3pltを動かせるグラフにする

さっきと同じグラフに見えるかもしれないが、一応今回の主役mpld3で作成したグラフだ。コードは以下。

def sin_cos():
    fig, ax = plt.subplots(1, 1)
    ax.set_title('title')
    ax.set_xlabel('x')
    ax.set_ylabel('y')
    ax.plot(x, y1, 'o-', ms=4, label='sin')
    ax.plot(x, y2, '^-', ms=4, label='cos')
    ax.legend()  # 凡例を追加

    ax.grid(which='major', ls='-')
    # 副目盛は表示されないっぽい
    ax.grid(which='minor', ls='--', alpha=0.5)

    name = 'sin_cos'
    fig.savefig(f"{name}.png")
    mpld3.save_html(fig, f"{name}.html")  # HTML形式で保存
    mpld3.show()  # HTMl形式でブラウザで表示

sin_cos()

ほとんど通常のpltと変わらない。変わる点としてはhtmlで出力して表示するという点。このコードを動かすと自動でブラウザが起動してそこにグラフが作成される。plotlyと同じ。

デメリットはグラフを描くたびに一回中止とかしないといけないということ。どういうことかというと、グラフを表示するとその処理で止まってしまい、後の処理に進まないということ。

これのダルいところがVScodeの拡張機能Code Runnerだと一回グラフを作成したら進めないという点。一生そのグラフを表示している処理で止まる。VScodeの拡張機能については以下。

一応、回避策としてVScodeの設定で「Code-runner: Run In Terminal」をオンにしたらctrl cで処理を強制終了して進めるが、オフにしていると先に進めない。オフにしているとコンソールに色がついて見やすくなるから取捨選択。

多少使い勝手は悪いが動かせる

グラフを作成してブラウザで表示すると上の画像のような感じでグラフが表示される。で、左下に元の状態に戻すボタンと移動ボタン、拡大ボタンが出てくる。

どうやらpltのグラフショートカットは使用できないようなので、拡大したい時はいちいちこのボタンを押さないといけない。面倒。

本当は動かせるグラフを埋め込みたかったけどやり方がわからなかったのでスルー。plotlyの時はやり方があったけど、それとはまた別なので個人で色々試してほしい。

ズーム時の挙動がヌルヌル

mpld3で作成したグラフを拡大・縮小するとやたらとヌルッと動く。謎だけどヌルヌル。上のgifだとあまりスムーズになってる感はないけど、実際には結構ヌルヌル。

凡例とホバーを改造

とりあえずpltのグラフをhtmlで作成できた。そして拡大とかできるようにした。しかし、これではなかなかに貧相。ということで、ここでは凡例とホバーを作成する。

凡例で上のgifで言えばグラフ右側にsinとcosがどの色かを示しているけど、これにカーソルを合わせると濃くなるし、クリックすればそのグラフが薄くなる。

また、プロット線やプロット点にカーソルを合わせるとその情報が表示される。マウスオーバーとかホバーとか言う。これらはplotlyでも作成可能。

考え方はプラグイン

def sin_cos_hover_legend():
    fig, ax = plt.subplots(1, 1)
    ax.set_title('title')
    ax.set_xlabel('x')
    ax.set_ylabel('y')
    plot1 = ax.plot(x, y1, 'o-', ms=4, label='sin')
    plot2 = ax.plot(x, y2, '^-', ms=4, label='cos')

    ax.grid(which='major', ls='-')
    ax.grid(which='minor', ls='--', alpha=0.5)

    # 凡例のマウスオーバーを作成
    handles, labels = ax.get_legend_handles_labels()  # 凡例情報を取得
    # 凡例のマウスオーバーを設定
    interactive_legend = mpld3.plugins.InteractiveLegendPlugin(
        handles, labels,
        alpha_unsel=0.2,  # 凡例をクリックして非表示にした時の透明度(1以上は線の太さ)
        alpha_over=2,  # 凡例にマウスオーバーした時の透明度(1以上は線の太さ)
    )
    mpld3.plugins.connect(fig, interactive_legend)  # 作成したホバーをグラフに追加

    # プロット点のマウスオーバーを作成
    # PointLabelTooltip と似ているがこちらはHTML記法が使えないっぽい
    text1 = [f"x: {i:.2}<br>y: {j:.2}" for i, j in zip(x, y1)]  # y1
    tooltip1 = mpld3.plugins.PointHTMLTooltip(
        plot1[0], labels=text1,
        voffset=30, hoffset=20,  # カーソルからホバー情報までの垂直・平行距離
    )

    text2 = [f"x: {i:.2}<br>y: {j:.2}" for i, j in zip(x, y2)]  # y2
    tooltip2 = mpld3.plugins.PointHTMLTooltip(
        plot2[0], labels=text2,
        voffset=30, hoffset=20,  # カーソルからホバー情報までの垂直・平行距離
    )

    # プロット線上でマウスオーバーした時の出力
    tooltip3 = mpld3.plugins.LineLabelTooltip(plot1[0], label='sin')
    tooltip4 = mpld3.plugins.LineLabelTooltip(plot2[0], label='cos')

    tooltip = [
        tooltip1,
        tooltip2,
        tooltip3,
        tooltip4,
    ]

    # 作成したホバーをグラフに追加
    mpld3.plugins.connect(fig, *tooltip)

    name = 'sin_cos_hover_legend'
    fig.savefig(f"{name}.png")  # mpld3で画像の保存は簡単にはできないっぽい
    mpld3.save_html(fig, f"{name}.html")  # HTML形式で保存

    mpld3.show()  # HTMl形式でブラウザで表示

sin_cos_hover_legend()

上のコードが凡例とホバーを改造した後のグラフのコードだけど長い。で、mpld3では基本的にプラグイン(plugins)を使って機能を追加していく。

今回だと以下の3種類のプラグインで機能を追加。

  • 凡例
  • プロット点
  • プロット線

プラグインについてはGitHubのこのページに書いてある。全てのプラグインがあるかはわからないけど、大体のことはできそう。

凡例をクリックで表示・非表示を切り替え

# 凡例のマウスオーバーを作成
handles, labels = ax.get_legend_handles_labels()  # 凡例情報を取得
# 凡例のマウスオーバーを設定
interactive_legend = mpld3.plugins.InteractiveLegendPlugin(
    handles, labels,
    alpha_unsel=0.2,  # 凡例をクリックして非表示にした時の透明度(1以上は線の太さ)
    alpha_over=2,  # 凡例にマウスオーバーした時の透明度(1以上は線の太さ)
)
mpld3.plugins.connect(fig, interactive_legend)  # 作成したホバーをグラフに追加

凡例についてはこの部分だけど、まずはax.get_legend_handles_labels()で凡例の情報を取得。そしてmpld3の凡例用のプラグインInteractiveLegendPluginを使ってクリックした時とホバー時の状態を設定。

最後にmpld3.plugins.connectでグラフにプラグインをくっつけると完成。毎回くっつけないといけないのは少し面倒。

プロット点をホバーした時に値を表示

# プロット点のマウスオーバーを作成
# PointLabelTooltip と似ているがこちらはHTML記法が使えないっぽい
text1 = [f"x: {i:.2}<br>y: {j:.2}" for i, j in zip(x, y1)]  # y1
tooltip1 = mpld3.plugins.PointHTMLTooltip(
    plot1[0], labels=text1,
    voffset=30, hoffset=20,  # カーソルからホバー情報までの垂直・平行距離
)

text2 = [f"x: {i:.2}<br>y: {j:.2}" for i, j in zip(x, y2)]  # y2
tooltip2 = mpld3.plugins.PointHTMLTooltip(
    plot2[0], labels=text2,
    voffset=30, hoffset=20,  # カーソルからホバー情報までの垂直・平行距離
)

プロット点をホバーした時にその点の値を表示するには上のようなコードを作成する。PointHTMLTooltipは代わりにLineLabelTooltipでもいいが、PointHTMLTooltipの方がHTMLのタグが使えるので汎用性は高い。

tooltip1とかtooltip2っていうのは後でまとめてグラフにコネクトするために一時的に変数としておいてある。毎回コネクトしてもいいけど、面倒なので一括コネクト。後で行う。

plot1[0]とか[0]とするのはよくわからんけど、色んなサイトでそうしているので合わせている。理由はどこかに書いていたけど忘れた。執筆者はmpld3は使わないからとりあえず放置。

プロット線をホバーした時はグラフ名を表示

# プロット線上でマウスオーバーした時の出力
tooltip3 = mpld3.plugins.LineLabelTooltip(plot1[0], label='sin')
tooltip4 = mpld3.plugins.LineLabelTooltip(plot2[0], label='cos')

tooltip = [
    tooltip1,
    tooltip2,
    tooltip3,
    tooltip4,
]

# 作成したホバーをグラフに追加
mpld3.plugins.connect(fig, *tooltip)

プロット線上でもホバーできるようにするにはLineLabelTooltipを使用する。基本的な使い方はPointHTMLTooltipと同じだが、LineLabelTooltipでは1つのlabelを使用する。

プロット線のホバー設定が終われば、tooltip1からtooltip4を一括でtooltipに入れて一括でコネクト。コネクト時には展開しているので、シンプルにtooltip1, tooltip2のように並べて書いてもいい。

plotlyでも描いてみる


最後に、凡例とホバーの設定をしたsin_cos_hover_legendをplotlyでも書いてみた。プロット線のホバーについては適用していないが、基本的には同じようなグラフが作成可能。

慣れの問題もあるけど、執筆者的にはplotly推し。他にもボタンを追加したりスライダーを追加したりするのが簡単なのも背中を押す1つの要因。mpld3はまだまだ発展途上のようで、できることが少ないっぽい。

def plotly_sin_cos_hover_legend():
    plot = []

    for name, y in zip(('sin', 'cos'), (y1, y2)):
        d = go.Scatter(
            mode='lines+markers',
            x=x, y=y,
            name=name,
            hovertemplate=f"{name}<br>"
            + 'x: %{x} <br>'
            + 'y: %{y} <br>'
            + "<extra></extra>",
        )
        plot.append(d)

    layout = go.Layout(
        template=template.plotly_layout(),
        title=dict(text='',),
        xaxis=dict(title='',),
        yaxis=dict(title='',),
    )

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

    name = 'plotly_sin_cos_hover_legend'
    pio.orca.config.executable = '/Applications/orca.app/Contents/MacOS/orca'
    pio.write_html(
        fig, f"{name}.html", config=template.plotly_config()
    )
    pio.write_image(fig, f"{name}.png")

plotly_sin_cos_hover_legend()

使い勝手はplotlyの方がダントツ良し

今回はpltのグラフをmpld3を使用してhtml出力する方法について解説した。こうすることで、グラフを作成した後でも拡大とかができるので結構便利。

しかし、使い勝手的にはダントツでplotlyが良いと思う。それに本家のpltまではいかないにもかなり色んな人がグラフの作成例とかを出してくれているのでplotlyで良いような気がする。

まあ、知っていても損はないから知ってる程度にする。これからもplotlyを使い続ける。ちなみにmpld3公式のグラフ例はここから見ることができる。

関連記事


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

続きを見る


【plotly&px.scatter】pxでの散布図の描き方

続きを見る


【plotly&size, width】Scatterのサイズやlineの太さ一覧表を作成

続きを見る


【python&csv読み込み】pythonを使ってcsvを読み込み

続きを見る


【plotly&legendまとめ】凡例の引数一覧

続きを見る


【plotly&legendまとめ】凡例の引数一覧

続きを見る


【plotly&fig作成と更新】add_traceやupdate_layoutの使い方

続きを見る


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

続きを見る


(9 < x y2)の条件に合った塗りつぶし
【plt&fill_between】matplotlibで領域を塗りつぶし

続きを見る

ガジェット

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

-plt
-, ,