質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.35%
canvas

HTML5の<canvas>要素用のタグです。CanvasはHTML5から導入された、二次元の図形描写が可能な要素です。

Matplotlib

MatplotlibはPythonのおよび、NumPy用のグラフ描画ライブラリです。多くの場合、IPythonと連携して使われます。

Tkinter

Tkinterは、GUIツールキットである“Tk”をPythonから利用できるようにした標準ライブラリである。

Q&A

解決済

1回答

1996閲覧

Tkinterでデータを選択しMatplotlibでグラフ表示したり色を変えたりしたい

Q_1986-kt

総合スコア13

canvas

HTML5の<canvas>要素用のタグです。CanvasはHTML5から導入された、二次元の図形描写が可能な要素です。

Matplotlib

MatplotlibはPythonのおよび、NumPy用のグラフ描画ライブラリです。多くの場合、IPythonと連携して使われます。

Tkinter

Tkinterは、GUIツールキットである“Tk”をPythonから利用できるようにした標準ライブラリである。

0グッド

0クリップ

投稿2020/07/08 05:35

Tkinterにボタンやテキストボックスを配置しCSVファイルなどのデータを選択し選択したデータの
グラフをMatplotlibで表示させたり、色を変えたりできるコードを作っています。
基本的には作れていますが、1つ1つのボタンなどを記述して書いているとコードが長くなりすぎて
見づらくなってしまうので、for文を使ってTkinterのウイジェットを配置しようとコードを改造しています。
ウイジェットの配置はできるのですが、それぞれのボタンなどのコマンドを紐づけするのに苦労しています。

ベースとなるやりたいことができているコード

1、データ選択
(ボタンでデータを選択しテキストボックスにファイル名を表示、プロットに代入グラフ表示)
2、表示データの色の変更
(プロットカラーの初期設定はボタンの色、ボタンクリックでカラー選択、ボタンとプロットの色変更)
3、データの表示、非表示
(初期設定でプロット表示、チェックボックスをはずすとプロット非表示)

import os import csv import numpy as np import pandas as pd import tkinter import tkinter as tk from tkinter import font import tkinter.ttk as ttk import matplotlib.pyplot as plt import tkinter.filedialog as fl import tkinter.colorchooser from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg # CSV Data Graph Base fig = plt.Figure(dpi=100, figsize=(8,6)) ax = fig.add_subplot() ax.set_title("Test Graph", pad=25) ax.set_xlabel("X Axis", labelpad=10) ax.set_ylabel("Y Axis", labelpad=20) ax.grid(True) ax.set_xlim(0, 5) ax.set_xticks(np.arange(0, 6, 0.5)) ax.set_ylim(-0.5, 0.5) ax.set_yticks(np.arange(-0.5, 0.6, 0.1)) h1, = ax.plot([], [], color="blue", linewidth=1, linestyle="-") h2, = ax.plot([], [], color="red", linewidth=1, linestyle="-") class Application(tk.Tk): def __init__(self, *args, **kwargs): tk.Tk.__init__(self, *args, **kwargs) self.frame1 = ttk.Frame(self, relief="ridge", width=800, height=600) self.frame1.pack(anchor="w", side="left", fill="x") # Canvas Matplotlib PullDown Front self.canvas1 = FigureCanvasTkAgg(fig, self.frame1) self.canvas1.draw() self.canvas1.get_tk_widget().pack(side=tk.TOP, fill=tk.BOTH, expand=True) self.frame2 = ttk.Frame(self, relief="ridge", width=800, height=600) self.frame2.pack(anchor="n", side="left", fill="x") label = ttk.Label(self.frame2,text="CSV Data Select") label.grid(row=0, column=0, columnspan=5, padx=20, pady=10) # CSV Data 1 の選択 self.data1_bln = tk.BooleanVar() self.data1_bln.set(True) self.chk_data_1 = ttk.Checkbutton(self.frame2,variable=self.data1_bln,text="Data1 表示",command=self.data1_hidden) self.chk_data_1.grid(row=1, column=0, padx=10, pady=10) self.data1 = tkinter.StringVar() self.data1_Entry = tk.Entry(self.frame2,textvariable=self.data1,justify="center",width=10) self.data1_Entry.grid(row=1, column=1, sticky=tk.W, padx=10, pady=10) self.data1_Button = tk.Button(self.frame2,text="データ1 選択",width=12,command=self.select_data1) self.data1_Button.grid(row=1, column=3, padx=10, pady=10) self.color1_Button = tk.Button(self.frame2,text="データ1 色",width=10,background="blue",command=self.select_color1) self.color1_Button.grid(row=1, column=4, padx=10, pady=10) """ Data 1 データ選択 """ def select_data1(self): data_1 = os.path.abspath(os.path.dirname(__file__)) data_1_DirPath = fl.askopenfilename(filetypes=[("", "*.csv")], initialdir=data_1) data_1_file = os.path.splitext(os.path.basename(data_1_DirPath))[0] self.data1.set(data_1_file) df0 = pd.read_csv(data_1_DirPath, index_col=None) self.x1 = df0["X"] self.y1 = df0["Y"] h1.set_xdata(self.x1) h1.set_ydata(self.y1) self.canvas1.draw() """ Data 1 カラー """ def select_color1(self): button_color1 = tkinter.colorchooser.askcolor() self.color1_Button.configure(background=button_color1[1]) h1.set_color(button_color1[1]) self.canvas1.draw() """ Data 1 データ非表示 """ def data1_hidden(self): if self.data1_bln.get(): h1.set_xdata(self.x1) h1.set_ydata(self.y1) self.canvas1.draw() else: h1.set_xdata([]) h1.set_ydata([]) self.canvas1.draw() if __name__ == "__main__": app = Application() app.geometry("1200x600+10+10") app.title("テスト") app.mainloop()

読み込むサンプルデータファイル

data1.csv        data2.csv
X Y X Y
0 0 0 0
0.1 -0.098 0.1 0
0.2 -0.2206504 0.2 0.103
0.3 -0.298896862 0.3 0.2306504
0.4 -0.304096311 0.4 0.308896862
0.5 -0.249144635 0.5 0.314096311
0.6 -0.174277652 0.6 0.259144635
0.7 -0.121153529 0.7 0.184277652
0.8 -0.111721969 0.8 0.131153529
0.9 -0.141430161 0.9 0.121721969
1 -0.187121213 1 0.151430161
1.1 -0.222559123 1.1 0.197121213
1.2 -0.232199955 1.2 0.232559123
1.3 -0.216695681 1.3 0.242199955
1.4 -0.189132519 1.4 0.226695681
1.5 -0.165850128 1.5 0.199132519
1.6 -0.157581613 1.6 0.175850128
1.7 -0.165276309 1.7 0.167581613
1.8 -0.181700559 1.8 0.175276309
1.9 -0.19679042 1.9 0.191700559
2 -0.203284028 2 0.20679042

問題のコード、変更点と問題点

1、クリックしたボタンの背景色を変更、これは問題なし
2、ボタンでファイルダイアログを開くまではできるが、対になるテキストボックスにファイル名を
入れることができない。どうやってテキストボックスを選択するのか??
3、同じようにイベントの発生したウイジェットと、チェックボックス、グラフ・プロットとの関連付けをどうするのか

# CSV Data Graph Base fig = plt.Figure(dpi=100, figsize=(8,6)) ax = fig.add_subplot() ax.set_title("Test Graph", pad=25) ax.set_xlabel("X Axis", labelpad=10) ax.set_ylabel("Y Axis", labelpad=20) ax.grid(True) ax.set_xlim(0, 5) ax.set_xticks(np.arange(0, 6, 0.5)) ax.set_ylim(-0.5, 0.5) ax.set_yticks(np.arange(-0.5, 0.6, 0.1)) h1, = ax.plot([], [], color="blue", linewidth=1, linestyle="-") h2, = ax.plot([], [], color="red", linewidth=1, linestyle="-") # データファイル index_no = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] datatext = ["Data 1", "Data 2", "Data 3", "Data 4", "Data 5", "Data 6", "Data 7", "Data 8"] datacolor = ["blue", "red", "green", "magenta", "cyan", "yellow", "purple", "lightgreen"] class Application(tk.Tk): def __init__(self, *args, **kwargs): tk.Tk.__init__(self, *args, **kwargs) self.frame1 = ttk.Frame(self, relief="ridge", width=1200, height=600) self.frame1.pack(anchor="w", side="left", fill="x") # Canvas Matplotlib self.canvas1 = FigureCanvasTkAgg(fig, self.frame1) self.canvas1.draw() self.canvas1.get_tk_widget().pack(side=tk.TOP, fill=tk.BOTH, expand=True) self.frame2 = ttk.Frame(self, relief="ridge", width=1200, height=600) self.frame2.pack(anchor="n", side="left", fill="x") label = ttk.Label(self.frame2, text="This is the Page 1") label.grid(row=0, column=0, columnspan=5, padx=40, pady=10) # データ表示ウイジェットの作成 self.string_vars = [] # enumerateを使わないで:zip_longestを使うのは、 # 条件などにより様々なリスト要素の組み合わせを作れるようにするため for j, name, code in zip_longest(index_no, datatext, datacolor): if j >= 5: break i = len(self.string_vars) self.string_vars.append(tkinter.StringVar()) pfbln = tk.BooleanVar() pfbln.set(True) chk_pfbln = ttk.Checkbutton(self.frame2, variable=pfbln, text=name + "表示") chk_pfbln.grid(row=j + 1, column=0, padx=10, pady=10) pfe = tk.Entry(self.frame2, textvariable=self.string_vars[i], justify="center", width=10) pfe.grid(row=j + 1, column=1, padx=10, pady=10) pfb = tk.Button(self.frame2, text=name + " 選択") pfb.bind("<Button-1>", self.select_data) pfb.grid(row=j + 1, column=2, padx=10, pady=10) pfbc = tk.Button(self.frame2, text="カラー選択", bg=code) pfbc.bind("<Button-1>", self.select_color) pfbc.grid(row=j + 1, column=3, padx=10, pady=10) def select_color(self, event): color1 = tkinter.colorchooser.askcolor() event.widget["bg"] = color1[1] return "break" def select_data(self, event): data = os.path.abspath(os.path.dirname(__file__)) self.data_DirPath = fl.askopenfilename(filetypes=[("", "*.csv")], initialdir=data) data_name = os.path.splitext(os.path.basename(self.data_DirPath))[0] return "break" if __name__ == "__main__": app = Application() app.geometry("1200x600+10+10") app.title("テスト") app.mainloop()

ボタンの数が多くなる予定なのでできれば、for文を使って効率よくコードを書きたいと思っています。
一番の問題、わからないことは、イベントの発生したウイジェットとその他のウイジェットを関連付けることです。

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答1

0

ベストアンサー

一番の問題、わからないことは、イベントの発生したウイジェットとその他のウイジェットを関連付けることです。

functools.partial でイベントハンドラで呼び出す関数の引数に
関連付けるのオブジェクトを束縛できます。

簡易的には lambda を使っても同様の事は可能ですが、
for文内で扱う場合は変数スコープの問題があるので partial推奨

python

1 2import tkinter as tk 3from tkinter import ttk 4from functools import partial 5 6 7def main(): 8 9 # functools.partial で引数を束縛する 10 def onclick(var, text, event): 11 var.set(text) 12 13 root = tk.Tk() 14 text = tk.StringVar() 15 ttk.Label(root, textvar=text).pack() 16 17 button1 = ttk.Button(root, text="button1") 18 button1.bind("<Button-1>", partial(onclick, text, "button1 is clicked")) 19 button1.pack() 20 21 button2 = ttk.Button(root, text="button2") 22 button2.bind("<Button-1>", partial(onclick, text, "button2 is clicked")) 23 button2.pack() 24 25 root.mainloop() 26

ボタンの数が多くなる予定なのでできれば、for文を使って効率よくコードを書きたいと思っています。

他の解決策:
一連のウィジェット/オブジェクトをひとつのクラスに纏めると、
インスタンス変数で関連付けられたウィジェットにアクセスできます。

python

1#!/usr/bin/env python3.8 2 3import os 4import tkinter as tk 5from tkinter import ttk 6from tkinter.filedialog import askopenfilename 7from tkinter.colorchooser import askcolor 8 9 10class FileInputField(ttk.Labelframe): 11 def __init__(self, master, dataName, color, *args, **kw): 12 super().__init__(master, *args, **kw) 13 14 visibleVar = self._visible = tk.BooleanVar() 15 filepathVar = self._filepath = tk.StringVar() 16 17 self["text"] = dataName 18 filepathVar.trace("w", self.onFilepathChanged) 19 20 check = tk.Checkbutton(self, variable=visibleVar) 21 entry = tk.Entry(self, textvar=filepathVar) 22 button1 = tk.Button(self, text="Select", command=self.selectFile) 23 button2 = tk.Button(self, text="Change Color", bg=color) 24 button2.bind("<Button-1>", self.selectColor) 25 26 for w in [check, entry, button1, button2]: 27 w.pack(side=tk.LEFT) 28 29 @property 30 def filepath(self): 31 return self._filepath.get() 32 33 def onFilepathChanged(self, *args): 34 #filepath = self._filepath.get() 35 #dataName = os.path.splitext(os.path.basename(filepath))[0] 36 #self.config(text=dataName) 37 self.event_generate("<<FileInputField_filepathChanged>>") 38 39 def selectFile(self): 40 filepath = askopenfilename(filetypes=[("", "*.csv")]) 41 if filepath and os.path.isfile(filepath): 42 self._filepath.set(filepath) 43 44 def selectColor(self, event): 45 _, color = askcolor() 46 if color: 47 event.widget.config(bg=color) 48 49 50def changed(event): 51 if isinstance(event.widget, FileInputField): 52 print(event.widget.filepath) 53 54 55def main(): 56 root = tk.Tk() 57 for dataName, color in zip(["Data1", "Data2", "Data3"], ["red", "green", "blue"]): 58 field = FileInputField(root, dataName, color) 59 field.bind("<<FileInputField_filepathChanged>>", changed) 60 field.pack() 61 root.mainloop() 62 63 64if __name__ == '__main__': 65 main() 66

投稿2020/07/08 18:04

編集2020/07/08 23:43
teamikl

総合スコア8760

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

Q_1986-kt

2020/07/09 02:27

ありがとうございます。 イメージ通りにウィジェットを配置してテキストボックスに書き込むことができました。 しかし、グラフのプロットに反映させて読み込んだデータを表示したり色を変えたりすることができません。 selectFile関数の後にファイルを読み込むことはできました。 def selectFile(self): filepath = askopenfilename(filetypes=[("", "*.csv")]) if filepath and os.path.isfile(filepath): self._filepath.set(filepath) df0 = pd.read_csv(filepath, index_col=None) print(df0) このデータをグラフに反映させるにはどうすればよいのでしょうか
teamikl

2020/07/09 03:21 編集

## クラス内で処理する場合 グラフへの反映に必要な変数を __init__ の引数に渡して __init__内で、インスタンス変数に格納します。 selectFile 関数内で、インスタンス経由で参照できます。 ## クラス外で処理する方法 event_generate と bind を使って、外部の関数を呼び出します。 selectFile 内では read_csv は呼び出さず、 プロットや canvas の変数のある場所から、関数をbind 選択されたファイル名を参照し、読み込むようにします。 # ※ 注意: コメント内なので全角スペース利用 def loadFile(plot, canvas, event):   filepath = event.widget.filepath   df0 = pd.read_csv(filepath)   plot.set_xdata(df0["X"])   plot.set_ydata(df0["Y"])   canvas.draw() field.bind("<<FileInputField_filepathChanged>>", partial(loadFile, h1, canvas)) 回答に書いたコードクラス内のevent_generate 辺りを参考に 任意の場所で、任意のイベントを発生させます。(但し、引数は渡せません) << >> で囲まれた文字は自由に名前を付けられます。
Q_1986-kt

2020/07/10 03:55

丁寧な説明、ありがとうございます。 まだ、確認はできていないのですが、イメージとしては、何とか理解できているのかな?と思います。 週末に色々と自分なりに試してみます。
teamikl

2020/07/10 04:48

少し説明捕捉で 回答にサンプルで書いたFileInputField_filepathChangedイベントは ファイルパスを1文字手入力で編集した際にもイベントが発生するという問題があるので、 この辺りは用途に合わせて調整してください。 クラスは、matplotlibに依存しないようにする為にも、 プロットやキャンバスといった要素はクラスの外部に出す、 後者の設計にするのが良さそうです。 ---- 概要のみですが実装例: クラス内では selectFileとselectColorメソッド内で fileChanged と colorChanged の仮想イベントを発生させる。 クラス外(利用側)では、bindでcsv読込・色替えと対応した関数に結び付ける。 プロットやキャンバスといったクラス外の要素は bind 時に partial により引数で渡す。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.35%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問