0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Matplotlib を tkinterの子Windowに描画する

Posted at

tkinter 子Window ← Matplotlib埋め込み

構想:ボタンクリックでグラフwindowを表示

ボタンがいくつか(Bar、Scatter、Hist、Heatmap、、、など)あり、各種グラフを表示してくれるようなイメージ。この基本として、ボタンクリック → グラフ表示、を作成。
子WindowにはMatplotlibの標準アイコン付き

{E1FDA9E3-6693-4914-A49B-1D4AC9083196}.png

コード全体は末尾(こちら

必要なもの

以下の要素を併せていく

  • 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部:抜粋

image.png

通常の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が作成される。

{F8902D3C-C2C7-477A-B9A9-17686389A70B}.png

これにself.cavas = FigureCanvasTkAgg(fig, self) することでfigが載る。
(以下の図ではちょっと先回りして「toolbar」も載っているが容赦願いたい)

{E5117DDD-7442-46AA-8E42-AE4C4EB03B84}.png

ここのコード抜粋

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()
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?