tkinter GUI 中的多个 matplotlib 实例
Multiple matplotlib instances in tkinter GUI
我已经构建了简单的 tkinter GUI。
现在,我正在尝试可视化 3 个不同的图形(通过使用不同的变量调用相同的函数)并将它们放在 GUI 的 3 个不同行中。
当我这样做时,我遇到了 2 个问题:
- 每次我 运行 脚本 (interface.py) 我得到 2 windows - GUI 和外部图形的 window。如何去掉第二个?
- 我无法将所有 3 个图表都可视化。脚本在显示第一个后停止。我相信这是因为第一个图循环工作(遍历大量数据点)。有什么解决办法吗?
接口:
# -*- coding: utf-8 -*-
"""
Created on Tue Oct 6 10:24:35 2020
@author: Dar0
"""
from tkinter import * #import tkinter module
from visualizer import main #import module 'visualizer' that shows the graph in real time
class Application(Frame):
''' Interface for visualizing graphs, indicators and text-box. '''
def __init__(self, master):
super(Application, self).__init__(master)
self.grid()
self.create_widgets()
def create_widgets(self):
# Label of the 1st graph
Label(self,
text='Hook Load / Elevator Height / Depth vs Time'
).grid(row = 0, column = 0, sticky = W)
# Graph 1 - Hook Load / Elevator Height / Depth vs Time
# button that displays the plot
#plot_button = Button(self,2
# command = main,
# height = 2,
# width = 10,
# text = "Plot"
# ).grid(row = 1, column = 0, sticky = W)
self.graph_1 = main(root, 1, 0)
# place the button
# in main window
# Label of the 2nd graph
Label(self,
text = 'Hook Load / Elevator Height vs Time'
).grid(row = 3, column = 0, sticky = W)
# Graph 2 - Hook Load / Elevator Height vs Time
self.graph_2 = main(root, 4, 0)
#Label of the 3rd graph
Label(self,
text = 'Hook Load vs Time'
).grid(row = 6, column = 0, sticky = W)
#Graph 3 - Hook Load vs Time
#Label of the 1st indicator
Label(self,
text = '1st performance indicator'
).grid(row = 0, column = 1, sticky = W)
#1st performance indicator
#Label of 2nd performance indicator
Label(self,
text = '2nd performance indicator'
).grid(row = 3, column = 1, sticky = W)
#2nd performance indicator
#Label of 3rd performance indicator
Label(self,
text = '3rd performance indicator'
).grid(row = 6, column = 1, sticky = W)
#Text-box showing comments based on received data
self.text_box = Text(self, width = 50, height = 10, wrap = WORD)
self.text_box.grid(row = 9, column = 0, columnspan = 1)
self.text_box.delete(0.0, END)
self.text_box.insert(0.0, 'My message will be here.')
#Main part
root = Tk()
root.title('WiTSML Visualizer by Dar0')
app = Application(root)
root.mainloop()
可视化工具:
#WiTSML visualizer
#Created by Dariusz Krol
#import matplotlib
#matplotlib.use('TkAgg')
#from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2TkAgg
#from matplotlib.figure import Figure
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import random
class Visualizer(object):
""" Includes all the methods needed to show streamed data. """
def __init__(self):
self.file_path = 'C:/Anaconda/_my_files/witsml_reader/modified_witsml.csv' #Defines which file is streamed
self.datetime_mod = []
self.bpos_mod = []
self.woh_mod = []
self.torq_mod = []
self.spp_mod = []
self.depth_mod = []
self.flow_in_mod = []
self.rpm_mod = []
def open_file(self):
self.df = pd.read_csv(self.file_path, low_memory = False, nrows = 300000) #Opens the STREAMED file (already modified so that data convert is not required)
self.df = self.df.drop(0)
self.df = pd.DataFrame(self.df)
return self.df
def convert_dataframe(self):
self.df = self.df.values.T.tolist() #Do transposition of the dataframe and convert to list
#Columns are as following:
# - DATETIME
# - BPOS
# - WOH
# - TORQ
# - SPP
# - DEPTH
# - FLOW_IN
# - RPM
self.datetime_value = self.df[0]
self.bpos_value = self.df[1]
self.woh_value = self.df[2]
self.torq_value = self.df[3]
self.spp_value = self.df[4]
self.depth_value = self.df[5]
self.flow_in_value = self.df[5]
self.rpm_value = self.df[7]
return self.datetime_value, self.bpos_value, self.woh_value, self.torq_value, self.spp_value, self.depth_value, self.flow_in_value, self.rpm_value
#print(self.bpos_value)
def deliver_values(self, no_dp, columns):
''' Method gets no_dp amount of data points from the original file. '''
self.no_dp = no_dp #defines how many data points will be presented in the graph
val_dict = {
'datetime': [self.datetime_value, self.datetime_mod],
'bpos': [self.bpos_value, self.bpos_mod],
'woh': [self.woh_value, self.woh_mod],
'torq': [self.torq_value, self.torq_mod],
'spp': [self.spp_value, self.spp_mod],
'depth': [self.depth_value, self.depth_mod],
'flow_in': [self.flow_in_value, self.flow_in_mod],
'rpm': [self.rpm_value, self.rpm_mod]
}
for item in columns:
if self.no_dp > len(val_dict[item][0]):
dp_range = len(val_dict[item][0])
else:
dp_range = self.no_dp
for i in range(dp_range):
val_dict[item][1].append(val_dict[item][0][i])
return self.datetime_mod, self.bpos_mod, self.woh_mod, self.torq_mod, self.spp_mod, self.depth_mod, self.flow_in_mod, self.rpm_mod
def show_graph2(self, tr_val, row, column):
from pylive_mod import live_plotter, live_plotter2
self.open_file()
self.convert_dataframe()
self.deliver_values(no_dp = 100000, columns = ['datetime', 'depth', 'bpos', 'woh'])
fst_p = 0
size = 300 # density of points in the graph (100 by default)
x_vec = self.datetime_mod[fst_p:size]
y_vec = self.depth_mod[fst_p:size]
y2_vec = self.bpos_mod[fst_p:size]
y3_vec = self.woh_mod[fst_p:size]
line1 = []
line2 = []
line3 = []
for i in range(self.no_dp):
#print(self.datetime_mod[i:6+i])
#print('Ostatni element y_vec: ', y_vec[-1])
#print(x_vec)
x_vec[-1] = self.datetime_mod[size+i]
y_vec[-1] = self.depth_mod[size+i]
y2_vec[-1] = self.bpos_mod[size+i]
y3_vec[-1] = self.woh_mod[size+i]
line1, line2, line3 = live_plotter2(tr_val, row, column, x_vec, y_vec, y2_vec, y3_vec, line1, line2, line3)
x_vec = np.append(x_vec[1:], 0.0)
y_vec = np.append(y_vec[1:], 0.0)
y2_vec = np.append(y2_vec[1:], 0.0)
y3_vec = np.append(y3_vec[1:], 0.0)
def main(tr_val, row, column):
Graph = Visualizer()
Graph.open_file() #Opens the streamed file
Graph.convert_dataframe() #Converts dataframe to readable format
Graph.show_graph2(tr_val, row, column)
#Show us the graph
#main()
创建图形的函数:
def live_plotter2(tr_val, row, column, x_data, y1_data, y2_data, y3_data, line1, line2, line3, identifier='',pause_time=1):
if line1 == [] and line2 == [] and line3 == []:
# this is the call to matplotlib that allows dynamic plotting
plt.ion()
fig = plt.figure(figsize = (5, 4), dpi = 100)
fig.subplots_adjust(0.15)
# -------------------- FIRST GRAPH --------------------
host = fig.add_subplot()
ln1 = host
ln2 = host.twinx()
ln3 = host.twinx()
ln2.spines['right'].set_position(('axes', 1.))
ln3.spines['right'].set_position(('axes', 1.12))
make_patch_spines_invisible(ln2)
make_patch_spines_invisible(ln3)
ln2.spines['right'].set_visible(True)
ln3.spines['right'].set_visible(True)
ln1.set_xlabel('Date & Time') #main x axis
ln1.set_ylabel('Depth') #left y axis
ln2.set_ylabel('Elevator Height')
ln3.set_ylabel('Weight on Hook')
#
x_formatter = FixedFormatter([x_data])
x_locator = FixedLocator([x_data[5]])
#ln1.xaxis.set_major_formatter(x_formatter)
ln1.xaxis.set_major_locator(x_locator)
#
ln1.locator_params(nbins = 5, axis = 'y')
ln1.tick_params(axis='x', rotation=90) #rotates x ticks 90 degrees down
ln2.axes.set_ylim(0, 30)
ln3.axes.set_ylim(200, 250)
line1, = ln1.plot(x_data, y1_data, color = 'black', linestyle = 'solid', alpha=0.8, label = 'Depth')
line2, = ln2.plot(x_data, y2_data, color = 'blue', linestyle = 'dashed', alpha=0.8, label = 'Elevator Height')
line3, = ln3.plot(x_data, y3_data, color = 'red', linestyle = 'solid', alpha=0.8, label = 'Weight on Hook')
fig.tight_layout() #the graphs is not clipped on sides
plt.title('WiTSML Visualizer')
plt.grid(True)
#Shows legend
lines = [line1, line2, line3]
host.legend(lines, [l.get_label() for l in lines], loc = 'lower left')
#Shows the whole graph
#plt.show()
#-------------------- Embedding --------------------
canvas = FigureCanvasTkAgg(fig, master=tr_val)
canvas.draw()
canvas.get_tk_widget().grid(row=row, column=column, ipadx=40, ipady=20)
# navigation toolbar
toolbarFrame = tk.Frame(master=tr_val)
toolbarFrame.grid(row=row,column=column)
toolbar = NavigationToolbar2Tk(canvas, toolbarFrame)
# after the figure, axis, and line are created, we only need to update the y-data
mod_x_data = convert_x_data(x_data, 20)
line1.axes.set_xticklabels(mod_x_data)
line1.set_ydata(y1_data)
line2.set_ydata(y2_data)
line3.set_ydata(y3_data)
#Debugging
#rint('plt.lim: ', ln2.axes.get_ylim())
# adjust limits if new data goes beyond bounds
# limit for line 1
if np.min(y1_data)<=line1.axes.get_ylim()[0] or np.max(y1_data)>=line1.axes.get_ylim()[1]:
plt.ylim(0, 10)
line1.axes.set_ylim([np.min(y1_data)-np.std(y1_data),np.max(y1_data)+np.std(y1_data)])
# limit for line 2
if np.min(y2_data)<=line2.axes.get_ylim()[0] or np.max(y2_data)>=line2.axes.get_ylim()[1]:
plt.ylim([np.min(y2_data)-np.std(y2_data),np.max(y2_data)+np.std(y2_data)])
#plt.ylim(0, 25)
# limit for line 3
if np.min(y3_data)<=line3.axes.get_ylim()[0] or np.max(y3_data)>=line3.axes.get_ylim()[1]:
plt.ylim([np.min(y3_data)-np.std(y3_data),np.max(y3_data)+np.std(y3_data)])
#plt.ylim(0, 25)
# Adds lines to the legend
#host.legend(lines, [l.get_label() for l in lines])
# this pauses the data so the figure/axis can catch up - the amount of pause can be altered above
plt.pause(pause_time)
# return line so we can update it again in the next iteration
return line1, line2, line3
关键是当您想在 tkinter
内绘制时不要使用 pyplot
,如 official example. Use matplotlib.figure.Figure
instead (see this 所示以获取更多信息)。
下面是一个最小示例,它沿着我在您的代码中看到的 Text
小部件绘制了 3 个独立的图形:
import pandas as pd
import numpy as np
import tkinter as tk
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2Tk
from matplotlib.figure import Figure
class Graph(tk.Frame):
def __init__(self, master=None, title="", *args, **kwargs):
super().__init__(master, *args, **kwargs)
self.fig = Figure(figsize=(4, 3))
ax = self.fig.add_subplot(111)
df = pd.DataFrame({"values": np.random.randint(0, 50, 10)}) #dummy data
df.plot(ax=ax)
self.canvas = FigureCanvasTkAgg(self.fig, master=self)
self.canvas.draw()
tk.Label(self, text=f"Graph {title}").grid(row=0)
self.canvas.get_tk_widget().grid(row=1, sticky="nesw")
toolbar_frame = tk.Frame(self)
toolbar_frame.grid(row=2, sticky="ew")
NavigationToolbar2Tk(self.canvas, toolbar_frame)
root = tk.Tk()
for num, i in enumerate(list("ABC")):
Graph(root, title=i, width=200).grid(row=num//2, column=num%2)
text_box = tk.Text(root, width=50, height=10, wrap=tk.WORD)
text_box.grid(row=1, column=1, sticky="nesw")
text_box.delete(0.0, "end")
text_box.insert(0.0, 'My message will be here.')
root.mainloop()
结果:
我已经构建了简单的 tkinter GUI。 现在,我正在尝试可视化 3 个不同的图形(通过使用不同的变量调用相同的函数)并将它们放在 GUI 的 3 个不同行中。
当我这样做时,我遇到了 2 个问题:
- 每次我 运行 脚本 (interface.py) 我得到 2 windows - GUI 和外部图形的 window。如何去掉第二个?
- 我无法将所有 3 个图表都可视化。脚本在显示第一个后停止。我相信这是因为第一个图循环工作(遍历大量数据点)。有什么解决办法吗?
接口:
# -*- coding: utf-8 -*-
"""
Created on Tue Oct 6 10:24:35 2020
@author: Dar0
"""
from tkinter import * #import tkinter module
from visualizer import main #import module 'visualizer' that shows the graph in real time
class Application(Frame):
''' Interface for visualizing graphs, indicators and text-box. '''
def __init__(self, master):
super(Application, self).__init__(master)
self.grid()
self.create_widgets()
def create_widgets(self):
# Label of the 1st graph
Label(self,
text='Hook Load / Elevator Height / Depth vs Time'
).grid(row = 0, column = 0, sticky = W)
# Graph 1 - Hook Load / Elevator Height / Depth vs Time
# button that displays the plot
#plot_button = Button(self,2
# command = main,
# height = 2,
# width = 10,
# text = "Plot"
# ).grid(row = 1, column = 0, sticky = W)
self.graph_1 = main(root, 1, 0)
# place the button
# in main window
# Label of the 2nd graph
Label(self,
text = 'Hook Load / Elevator Height vs Time'
).grid(row = 3, column = 0, sticky = W)
# Graph 2 - Hook Load / Elevator Height vs Time
self.graph_2 = main(root, 4, 0)
#Label of the 3rd graph
Label(self,
text = 'Hook Load vs Time'
).grid(row = 6, column = 0, sticky = W)
#Graph 3 - Hook Load vs Time
#Label of the 1st indicator
Label(self,
text = '1st performance indicator'
).grid(row = 0, column = 1, sticky = W)
#1st performance indicator
#Label of 2nd performance indicator
Label(self,
text = '2nd performance indicator'
).grid(row = 3, column = 1, sticky = W)
#2nd performance indicator
#Label of 3rd performance indicator
Label(self,
text = '3rd performance indicator'
).grid(row = 6, column = 1, sticky = W)
#Text-box showing comments based on received data
self.text_box = Text(self, width = 50, height = 10, wrap = WORD)
self.text_box.grid(row = 9, column = 0, columnspan = 1)
self.text_box.delete(0.0, END)
self.text_box.insert(0.0, 'My message will be here.')
#Main part
root = Tk()
root.title('WiTSML Visualizer by Dar0')
app = Application(root)
root.mainloop()
可视化工具:
#WiTSML visualizer
#Created by Dariusz Krol
#import matplotlib
#matplotlib.use('TkAgg')
#from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2TkAgg
#from matplotlib.figure import Figure
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import random
class Visualizer(object):
""" Includes all the methods needed to show streamed data. """
def __init__(self):
self.file_path = 'C:/Anaconda/_my_files/witsml_reader/modified_witsml.csv' #Defines which file is streamed
self.datetime_mod = []
self.bpos_mod = []
self.woh_mod = []
self.torq_mod = []
self.spp_mod = []
self.depth_mod = []
self.flow_in_mod = []
self.rpm_mod = []
def open_file(self):
self.df = pd.read_csv(self.file_path, low_memory = False, nrows = 300000) #Opens the STREAMED file (already modified so that data convert is not required)
self.df = self.df.drop(0)
self.df = pd.DataFrame(self.df)
return self.df
def convert_dataframe(self):
self.df = self.df.values.T.tolist() #Do transposition of the dataframe and convert to list
#Columns are as following:
# - DATETIME
# - BPOS
# - WOH
# - TORQ
# - SPP
# - DEPTH
# - FLOW_IN
# - RPM
self.datetime_value = self.df[0]
self.bpos_value = self.df[1]
self.woh_value = self.df[2]
self.torq_value = self.df[3]
self.spp_value = self.df[4]
self.depth_value = self.df[5]
self.flow_in_value = self.df[5]
self.rpm_value = self.df[7]
return self.datetime_value, self.bpos_value, self.woh_value, self.torq_value, self.spp_value, self.depth_value, self.flow_in_value, self.rpm_value
#print(self.bpos_value)
def deliver_values(self, no_dp, columns):
''' Method gets no_dp amount of data points from the original file. '''
self.no_dp = no_dp #defines how many data points will be presented in the graph
val_dict = {
'datetime': [self.datetime_value, self.datetime_mod],
'bpos': [self.bpos_value, self.bpos_mod],
'woh': [self.woh_value, self.woh_mod],
'torq': [self.torq_value, self.torq_mod],
'spp': [self.spp_value, self.spp_mod],
'depth': [self.depth_value, self.depth_mod],
'flow_in': [self.flow_in_value, self.flow_in_mod],
'rpm': [self.rpm_value, self.rpm_mod]
}
for item in columns:
if self.no_dp > len(val_dict[item][0]):
dp_range = len(val_dict[item][0])
else:
dp_range = self.no_dp
for i in range(dp_range):
val_dict[item][1].append(val_dict[item][0][i])
return self.datetime_mod, self.bpos_mod, self.woh_mod, self.torq_mod, self.spp_mod, self.depth_mod, self.flow_in_mod, self.rpm_mod
def show_graph2(self, tr_val, row, column):
from pylive_mod import live_plotter, live_plotter2
self.open_file()
self.convert_dataframe()
self.deliver_values(no_dp = 100000, columns = ['datetime', 'depth', 'bpos', 'woh'])
fst_p = 0
size = 300 # density of points in the graph (100 by default)
x_vec = self.datetime_mod[fst_p:size]
y_vec = self.depth_mod[fst_p:size]
y2_vec = self.bpos_mod[fst_p:size]
y3_vec = self.woh_mod[fst_p:size]
line1 = []
line2 = []
line3 = []
for i in range(self.no_dp):
#print(self.datetime_mod[i:6+i])
#print('Ostatni element y_vec: ', y_vec[-1])
#print(x_vec)
x_vec[-1] = self.datetime_mod[size+i]
y_vec[-1] = self.depth_mod[size+i]
y2_vec[-1] = self.bpos_mod[size+i]
y3_vec[-1] = self.woh_mod[size+i]
line1, line2, line3 = live_plotter2(tr_val, row, column, x_vec, y_vec, y2_vec, y3_vec, line1, line2, line3)
x_vec = np.append(x_vec[1:], 0.0)
y_vec = np.append(y_vec[1:], 0.0)
y2_vec = np.append(y2_vec[1:], 0.0)
y3_vec = np.append(y3_vec[1:], 0.0)
def main(tr_val, row, column):
Graph = Visualizer()
Graph.open_file() #Opens the streamed file
Graph.convert_dataframe() #Converts dataframe to readable format
Graph.show_graph2(tr_val, row, column)
#Show us the graph
#main()
创建图形的函数:
def live_plotter2(tr_val, row, column, x_data, y1_data, y2_data, y3_data, line1, line2, line3, identifier='',pause_time=1):
if line1 == [] and line2 == [] and line3 == []:
# this is the call to matplotlib that allows dynamic plotting
plt.ion()
fig = plt.figure(figsize = (5, 4), dpi = 100)
fig.subplots_adjust(0.15)
# -------------------- FIRST GRAPH --------------------
host = fig.add_subplot()
ln1 = host
ln2 = host.twinx()
ln3 = host.twinx()
ln2.spines['right'].set_position(('axes', 1.))
ln3.spines['right'].set_position(('axes', 1.12))
make_patch_spines_invisible(ln2)
make_patch_spines_invisible(ln3)
ln2.spines['right'].set_visible(True)
ln3.spines['right'].set_visible(True)
ln1.set_xlabel('Date & Time') #main x axis
ln1.set_ylabel('Depth') #left y axis
ln2.set_ylabel('Elevator Height')
ln3.set_ylabel('Weight on Hook')
#
x_formatter = FixedFormatter([x_data])
x_locator = FixedLocator([x_data[5]])
#ln1.xaxis.set_major_formatter(x_formatter)
ln1.xaxis.set_major_locator(x_locator)
#
ln1.locator_params(nbins = 5, axis = 'y')
ln1.tick_params(axis='x', rotation=90) #rotates x ticks 90 degrees down
ln2.axes.set_ylim(0, 30)
ln3.axes.set_ylim(200, 250)
line1, = ln1.plot(x_data, y1_data, color = 'black', linestyle = 'solid', alpha=0.8, label = 'Depth')
line2, = ln2.plot(x_data, y2_data, color = 'blue', linestyle = 'dashed', alpha=0.8, label = 'Elevator Height')
line3, = ln3.plot(x_data, y3_data, color = 'red', linestyle = 'solid', alpha=0.8, label = 'Weight on Hook')
fig.tight_layout() #the graphs is not clipped on sides
plt.title('WiTSML Visualizer')
plt.grid(True)
#Shows legend
lines = [line1, line2, line3]
host.legend(lines, [l.get_label() for l in lines], loc = 'lower left')
#Shows the whole graph
#plt.show()
#-------------------- Embedding --------------------
canvas = FigureCanvasTkAgg(fig, master=tr_val)
canvas.draw()
canvas.get_tk_widget().grid(row=row, column=column, ipadx=40, ipady=20)
# navigation toolbar
toolbarFrame = tk.Frame(master=tr_val)
toolbarFrame.grid(row=row,column=column)
toolbar = NavigationToolbar2Tk(canvas, toolbarFrame)
# after the figure, axis, and line are created, we only need to update the y-data
mod_x_data = convert_x_data(x_data, 20)
line1.axes.set_xticklabels(mod_x_data)
line1.set_ydata(y1_data)
line2.set_ydata(y2_data)
line3.set_ydata(y3_data)
#Debugging
#rint('plt.lim: ', ln2.axes.get_ylim())
# adjust limits if new data goes beyond bounds
# limit for line 1
if np.min(y1_data)<=line1.axes.get_ylim()[0] or np.max(y1_data)>=line1.axes.get_ylim()[1]:
plt.ylim(0, 10)
line1.axes.set_ylim([np.min(y1_data)-np.std(y1_data),np.max(y1_data)+np.std(y1_data)])
# limit for line 2
if np.min(y2_data)<=line2.axes.get_ylim()[0] or np.max(y2_data)>=line2.axes.get_ylim()[1]:
plt.ylim([np.min(y2_data)-np.std(y2_data),np.max(y2_data)+np.std(y2_data)])
#plt.ylim(0, 25)
# limit for line 3
if np.min(y3_data)<=line3.axes.get_ylim()[0] or np.max(y3_data)>=line3.axes.get_ylim()[1]:
plt.ylim([np.min(y3_data)-np.std(y3_data),np.max(y3_data)+np.std(y3_data)])
#plt.ylim(0, 25)
# Adds lines to the legend
#host.legend(lines, [l.get_label() for l in lines])
# this pauses the data so the figure/axis can catch up - the amount of pause can be altered above
plt.pause(pause_time)
# return line so we can update it again in the next iteration
return line1, line2, line3
关键是当您想在 tkinter
内绘制时不要使用 pyplot
,如 official example. Use matplotlib.figure.Figure
instead (see this 所示以获取更多信息)。
下面是一个最小示例,它沿着我在您的代码中看到的 Text
小部件绘制了 3 个独立的图形:
import pandas as pd
import numpy as np
import tkinter as tk
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2Tk
from matplotlib.figure import Figure
class Graph(tk.Frame):
def __init__(self, master=None, title="", *args, **kwargs):
super().__init__(master, *args, **kwargs)
self.fig = Figure(figsize=(4, 3))
ax = self.fig.add_subplot(111)
df = pd.DataFrame({"values": np.random.randint(0, 50, 10)}) #dummy data
df.plot(ax=ax)
self.canvas = FigureCanvasTkAgg(self.fig, master=self)
self.canvas.draw()
tk.Label(self, text=f"Graph {title}").grid(row=0)
self.canvas.get_tk_widget().grid(row=1, sticky="nesw")
toolbar_frame = tk.Frame(self)
toolbar_frame.grid(row=2, sticky="ew")
NavigationToolbar2Tk(self.canvas, toolbar_frame)
root = tk.Tk()
for num, i in enumerate(list("ABC")):
Graph(root, title=i, width=200).grid(row=num//2, column=num%2)
text_box = tk.Text(root, width=50, height=10, wrap=tk.WORD)
text_box.grid(row=1, column=1, sticky="nesw")
text_box.delete(0.0, "end")
text_box.insert(0.0, 'My message will be here.')
root.mainloop()
结果: