Tkinterにmatplotlibグラフを埋め込む


1. はじめに

最近、プログラミングに関して、次のハードルがありました。

(1) PythonでGUIプログラムを作りたい。→ Tkinterを利用でOK
(2) 何等かの計算結果をグラフに表示したい。→ matplotlibを利用でOK
(3) (2)のmatplotlibで書いたグラフを (1)のTkinterに埋め込みたい。 → NG 

何とか例題コードを弄ることで乗り越えようとしましたが、うまくいきませんでした。

今回の記事は、(3)の課題がどう解決できたか、その内容を紹介するものです。

2. matplotlibのオブジェクト指向設計

まず、matplotlibのオブジェクト指向について理解する必要がることが分かりました。
matplotlib.pyplotで作成されるグラフは、次のようにFigureクラスのオブジェクト、実際のグラフを描画するAxesクラスのオブジェクト、そしてx軸、y軸を管理する Axisクラスのオフジェクト(以下、インスタンス)の三つで構成されています。

(注意:英語のAxesとは軸の意味ですが、matplotlibでは、軸ではなくグラフを書く部分を意味しています。)

ここで、matplotlibのオブジェクト指向設計を利用して、簡単なグラフを描いてみます。

(1)一つのFigureインスタンスであるfigを生成します。

fig = plt.figure()

(2) 実際のグラフを描画するAxesインスタスを四つ生成します。

ax1, ax2, ax3, ax4

(3) 四つのAxesインスタスをfigインスタス上に(2X2)マトリクス状で配置し、描画します。

ax1 = fig.add_subplot(221)
ax2 = fig.add_subplot(222)
ax3 = fig.add_subplot(223)
ax4 = fig.add_subplot(224)
plt.show()

次の図に描画結果を表示します。このWindowの丸いアイコン(左上)で、Matplotlibであることが分かります。

matplotlibのオブジェクト指向設計のプログラムコードを掲載します。

import numpy as np
import matplotlib.pyplot as plt

#Prepare Data
x1 = np.linspace(0.0, 5.0)
y1 = np.cos(2 * np.pi * x1) * np.exp(-x1)
x2 = np.linspace(0.0, 3.0)
y2 = np.cos(2 * np.pi * x2) * np.exp(-x1)


# Figure instance
fig = plt.figure()
print(type(fig))

#ax1 instance
ax1 = fig.add_subplot(221)
ax1.plot(x1, y1)
ax1.set_title('line plot')
ax1.set_ylabel('Damped oscillation')
print(type(ax1))

#ax2 instance
ax2 = fig.add_subplot(222)
ax2.scatter(x1, y1, marker='o')
ax2.set_title('Scatter plot')

#ax3 instance
ax3 = fig.add_subplot(223)
ax3.plot(x2,y2)
ax3.set_ylabel('Damped oscillation')
ax3.set_xlabel('time (s)')

#ax4 instance
ax4 = fig.add_subplot(224)
ax4.scatter(x2, y2, marker='o')
ax4.set_xlabel('time (s)')

#Drawing
plt.show()

3. Tkinterにmatplotlibを埋め込む

次はTkinterにmatploblibのグラフを埋め込む方法です。
下記の図を用いながら、手順を説明します。

(1) TkinterのWindowを用意します。そのインスタンスの名前をrootとします。
(2) rootの上に、canvasインスタンスを配置します。
(3) 前章と同じ方法で、グラフが描かれたfigインスタンスを用意します。
(4) figインスタンスをcanvasインスタンスに配置し、描画します。

インスタンスが三つも出るから、複雑ですね。でも、この概念さえ押さえておけば、次回からのGUIプログラミングは楽になります。

上記の(2)と(4)の手順を実行するのが、次のコードになります。FigureCanvasTkAggの使い方には気を付けましょう。

canvas = FigureCanvasTkAgg(fig, master=root)  # Generate canvas instance, Embedding fig in root
canvas.draw()
canvas.get_tk_widget().pack()

次の図に描画結果を表示します。上手にGUI上にmatplotグラフが描画できました。
このWindowの青い羽根アイコン(左上)から、Tkinterであることが分かります。

Tkinterにmatplotlibのグラフを埋め込むプログラムコードを掲載します。
前章のプログラムコードを維持しつつ、後半にTkinterの部分を拡張した内容となります。

import numpy as np
import matplotlib.pyplot as plt
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
import tkinter as tk

# Prepare Data
x1 = np.linspace(0.0, 5.0)
y1 = np.cos(2 * np.pi * x1) * np.exp(-x1)
x2 = np.linspace(0.0, 3.0)
y2 = np.cos(2 * np.pi * x2) * np.exp(-x1)

# Figure instance
fig = plt.Figure()

# ax1
ax1 = fig.add_subplot(221)
ax1.plot(x1, y1)
ax1.set_title('line plot')
ax1.set_ylabel('Damped oscillation')

# ax2
ax2 = fig.add_subplot(222)
ax2.scatter(x1, y1, marker='o')
ax2.set_title('Scatter plot')

# ax3
ax3 = fig.add_subplot(223)
ax3.plot(x2, y2)
ax3.set_ylabel('Damped oscillation')
ax3.set_xlabel('time (s)')

# ax4
ax4 = fig.add_subplot(224)
ax4.scatter(x2, y2, marker='o')
ax4.set_xlabel('time (s)')


# When windows is closed.

def _destroyWindow():
    root.quit()
    root.destroy()


# Tkinter Class

root = tk.Tk()
root.withdraw()
root.protocol('WM_DELETE_WINDOW', _destroyWindow)  # When you close the tkinter window.

# Canvas
canvas = FigureCanvasTkAgg(fig, master=root)  # Generate canvas instance, Embedding fig in root
canvas.draw()
canvas.get_tk_widget().pack()
#canvas._tkcanvas.pack()

# root
root.update()
root.deiconify()
root.mainloop()

4.まとめ

(1) matplotlibで書いたグラフをTkinterに埋め込みたいけどうまく行きませんでした。 
(2) まず、オブジェクト指向設計でmatplotlibグラブを描く必要があります。
(3) そして、Tkinterのcanvasインスタンスを用意し、matplotlibグラフのインスタンスをそれの上に乗せます。

5.参考資料

  1. Matplotlib Usage https://matplotlib.org/1.5.1/faq/usage_faq.html
  2. 早く知っておきたかったmatplotlibの基礎知識 https://qiita.com/skotaro/items/08dc0b8c5704c94eafb9
  3. Embedding in Tk https://matplotlib.org/3.1.1/gallery/user_interfaces/embedding_in_tk_sgskip.html
  4. Tkinter イベントのバインディング https://cassiopeia.hatenadiary.org/entry/20070822/1187712650