tkinter 子Window ← Matplotlib埋め込み
構想:ボタンクリックでグラフwindowを表示
ボタンがいくつか(Bar、Scatter、Hist、Heatmap、、、など)あり、各種グラフを表示してくれるようなイメージ。この基本として、ボタンクリック → グラフ表示、を作成。
子WindowにはMatplotlibの標準アイコン付き
コード全体は末尾(こちら)
必要なもの
以下の要素を併せていく
- fig : Matplotlib.pyplotで作成するグラフ描画のオブジェクト
- MainWindow : 親window。ボタン配置し、クリックで子Window(グラフwindow)表示
- childWindow : 子Window。ここにcanvas設置(FigureCanvasTkAgg)、canvas上にグラフを載せる
- toolbar : NavigateToolbar2Tkでcanvas上にデフォルトのツールバーを表示。カスタマイズも可能
fig部:抜粋
単純にfigを作るだけ。
fig,axes = plt.subplots(n, m)
してaxes.scatter(x,y)
などで描画し、このfigを使うだけ。
class CreateFigure:
def __init__(self, n, m, data_x, data_y, labelList):
self.fig, self.axes = plt.subplots(n, m)
self.data_x = data_x
self.data_y = data_y
self.labelList = labelList
self.plot()
def plot(self):
for ax, x, y, label in zip(self.axes.flatten(),self.data_x, self.data_y, self.labelList):
ax.scatter(x, y, label=label)
# 必要に応じてその他装飾
ax.set_title(f"for {label}")
MainWindow部:抜粋
通常のtkinter
でのWindow作成、ボタン配置。
ボタンには子Window作成の関数を指定する
細かな点では以下がある。
- Windowクローズ時の処理を忘れないこと
- childWindowを複数作る場合、childWindows[]配列を作ってそこにchildWindowオブジェクトを入れると管理できる
class MainWindow:
# 初期化時にframe作成、frame上にボタン配置
def __init__(self,windowWidth, windowHeight):
self.root = tk.Tk()
self.root.geometry(f"{windowWidth}x{windowHeight}")
self.frame = tk.Frame(self.root, relief="raised", borderwidth=5 , bg="lightgray")
self.DrawFigButton = tk.Button(self.frame, text="Draw Fig.", command=self.DrawFig)
self.frame.pack(fill=tk.BOTH, expand=True)
self.DrawFigButton.pack()
self.childWindows=[]
# ここでグラフ描画してfigを生成、addNewWindowを呼び出して子Window生成
def DrawFig(self):
data_len = 20
n = 2
m = 3
data_x = np.random.random(size=(data_len, n,m))*10
data_y = np.random.random(size=(data_len,n,m))*20
print(data_x)
labelList = [f"name_{i}" for i in range(n*m)]
figBase = CreateFigure(n ,m, data_x, data_y, labelList)
self.childWindow = self.addNewWindow(figBase.fig)
self.childWindow.transient(self.root) # 常にMainWindowの手前に表示
# self.childWindow.grab_set() # 「常に手前に表示」で検索するとこれがヒットするが、子がモーダルになるので却下
self.childWindows.append(self.childWindow)
# 子Windowの追加。ChildWindowオブジェクト(後述)のインスタンシングにて
def addNewWindow(self,fig):
childWindow = ChildWindow(fig, self.root,len(self.childWindows),self)
return childWindow
# 子Windowを閉じたときに、子Window側から呼び出される。子Windowリストの要素削除
def deleteChildWindow(self, childWindow):
if childWindow in self.childWindows:
self.childWindows.remove(childWindow)
# クローズ時の終了処理
def onClose(self):
for wnd in self.childWindows:
wnd.quit()
wnd.destroy()
self.root.quit()
self.root.destroy()
# mainプロシージャで呼ばれてmainloop()する
def run(self):
self.root.protocol("WM_DELETE_WINDOW", self.onClose)
self.root.mainloop()
# mainプロシージャでMainWindowオブジェクト作成、mainloop()。
def main():
oGUI = MainWindow(200,100)
oGUI.run()
if __name__=="__main__":
main()
ChildWindow部:抜粋
MainWindow
から作成された子Window。
figオブジェクトを引数取得している。
2ステップに分かれている。
tk.Toplevelをベースに子Windowが作成される。
これにself.cavas = FigureCanvasTkAgg(fig, self)
することでfigが載る。
(以下の図ではちょっと先回りして「toolbar」も載っているが容赦願いたい)
ここのコード抜粋
class ChildWindow(tk.Toplevel):
def __init__(self,fig,root,windowNum, parent): # parent means "MainWindow object". to delete childWindow from childWindows[]
super().__init__(root)
self.parent = parent
self.fig = fig
self.title(f"child:{windowNum}")
###### ここが主なポイント ######
self.canvas = FigureCanvasTkAgg(fig, self)
self.canvas.draw()
self.canvas.get_tk_widget().pack(side=tk.TOP, fill=tk.BOTH, expand=True)
# おまけ:子Window閉じたときの解法処理
self.protocol("WM_DELETE_WINDOW", self.onClosing)
def onClosing(self):
self.parent.deleteChildWindow(self)
self.destroy()
Toolbar部:抜粋
Windowのself.canvas
に配置する
self.canvas = FigureCanvasTkAgg(fig, self)
したすぐ後に記述する
class ChildWindow(tk.Toplevel):
def __init__(self,fig,root,windowNum, parent): # parent means "MainWindow object". to delete childWindow from childWindows[]
super().__init__(root)
self.parent = parent
self.fig = fig
self.title(f"child:{windowNum}")
self.canvas = FigureCanvasTkAgg(fig, self)
self.canvas.draw()
###### ここでToolbar作成 ######
self.toolbar = NavigationToolbar2Tk(self.canvas, self)
self.toolbar.update()
self.toolbar.pack(side=tk.TOP, fill=tk.X, expand=False)
self.canvas.get_tk_widget().pack(side=tk.TOP, fill=tk.BOTH, expand=True)
コード全体
import matplotlib.pyplot as plt
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2Tk
import tkinter as tk
import numpy as np
class MainWindow:
def __init__(self,windowWidth, windowHeight):
self.root = tk.Tk()
self.root.geometry(f"{windowWidth}x{windowHeight}")
self.frame = tk.Frame(self.root, relief="raised", borderwidth=5 , bg="lightgray")
self.DrawFigButton = tk.Button(self.frame, text="Draw Fig.", command=self.DrawFig)
self.frame.pack(fill=tk.BOTH, expand=True)
self.DrawFigButton.pack()
self.childWindows=[]
def DrawFig(self):
data_len = 20
n = 2
m = 3
data_x = np.random.random(size=(data_len, n,m))*10
data_y = np.random.random(size=(data_len,n,m))*20
print(data_x)
labelList = [f"name_{i}" for i in range(n*m)]
figBase = CreateFigure(n ,m, data_x, data_y, labelList)
self.childWindow = self.addNewWindow(figBase.fig)
self.childWindow.transient(self.root) # 常にMainWindowの手前に表示
# self.childWindow.grab_set() # 「常に手前に表示」で検索するとこれがヒットするが、子がモーダルになるので却下
self.childWindows.append(self.childWindow)
def addNewWindow(self,fig):
childWindow = ChildWindow(fig, self.root,len(self.childWindows))
return childWindow
def onClose(self):
for wnd in self.childWindows:
wnd.quit()
wnd.destroy()
self.root.quit()
self.root.destroy()
def run(self):
self.root.protocol("WM_DELETE_WINDOW", self.onClose)
self.root.mainloop()
class ChildWindow(tk.Toplevel):
def __init__(self,fig,root,windowNum):
super().__init__(root)
self.fig = fig
self.title(f"child:{windowNum}")
self.canvas = FigureCanvasTkAgg(fig, self)
self.canvas.draw()
# addToolbar
self.toolbar = NavigationToolbar2Tk(self.canvas, self)
self.toolbar.update()
self.toolbar.pack(side=tk.TOP, fill=tk.X, expand=False)
self.canvas.get_tk_widget().pack(side=tk.TOP, fill=tk.BOTH, expand=True)
self.protocol("WM_DELETE_WINDOW", self.onClosing)
def onClosing(self):
self.destroy()
class CreateFigure:
def __init__(self, n, m, data_x, data_y, labelList):
self.fig, self.axes = plt.subplots(n, m)
self.data_x = data_x
self.data_y = data_y
self.labelList = labelList
self.plot()
def plot(self):
for ax, x, y, label in zip(self.axes.flatten(),self.data_x, self.data_y, self.labelList):
ax.scatter(x, y, label=label)
# 必要に応じてその他装飾
ax.set_title(f"for {label}")
def main():
oGUI = MainWindow(200,100)
oGUI.run()
if __name__=="__main__":
main()