matplotlib的简单使用
- 创建画布
- 准备x,y轴数据
- 绘制图像
- 显示图像
matplotlib.pyplot的简单画图
使用matplotlib中的pyplot包做画图示例。
- 其中创建画布的figsize(10,10)设置的是像素大小,1代表100像素。此处1010图像大小就为10001000。
import matplotlib.pyplot as plt
# 1.创建画布
plt.figure(figsize=(10, 10), dpi=100)
# 2.绘制折线图
plt.plot([1, 2, 3, 4, 5, 6 ,7], [17,17,18,15,11,11,13])
# 3.显示图像
plt.show()
图片显示结果:
matplotlib.pyplot画图的其他设置
可以看到上面的绘图中没有标题,同时x,y轴也不是我们想要的,x,y轴的上下限是图中数据的上下限。可能我们画图想要的上下限并不是这样
import matplotlib.pyplot as plt
import numpy as np
# 1.创建画布
plt.figure(figsize=(10, 10), dpi=100)
# 2.绘制折线图
plt.plot([1, 2, 3, 4, 5, 6 ,7], [17,17,18,15,11,11,13])
# 2.1设置图的标题
plt.title("测试绘图")
# 2.2设置x、y轴的刻度
plt.xticks(np.arange(1,26,1.5))
plt.yticks(np.arange(1,30,1))
# 2.3设置x轴y轴信息
plt.xlabel("test")
plt.ylabel("ytest")
# 3.显示图像
plt.show()
上图显示可以看到中文出现乱码,此处需要添加一些额外信息,如下。
import matplotlib as mpl
mpl.rcParams['font.sans-serif']=['SimHei'] #指定默认字体 SimHei为黑体
mpl.rcParams['axes.unicode_minus']=False #用来正常显示负号
matplotlib另一种画布创建以及画图
在matplotlib中不仅可以通过matplotlib.pyplot来创建画布,还可以通过matplotlib.figure中的Figure类来常见画布。
其实这两者之间具体有什么区别,官方api文档也没有具体说明。
import matplotlib.pyplot as plt
import numpy as np
from matplotlib.figure import Figure
# 1.创建画布
figure = Figure(figsize=(9, 4), dpi=100)
# 2.绘制折线图
plt.plot([1, 2, 3, 4, 5, 6 ,7], [17,17,18,15,11,11,13])
plt.xticks(np.arange(1,26,1.5))
plt.yticks(np.arange(1,30,1))
# 3.显示图像
plt.show()
此处有问题出现需要注意,下图。
图中右上角显示图形像素为6404800,但是代码中明明通过Figure设置像素大小为900400。说明通过matplotlib.plot绘图和Figure创建的画布并没有直接关系。所以Figure画布并不能通过plt.show()的方式展示出来。
Figure的绘图
- 关于Figure如何将画布展示出来,我也没有找到用什么方法可以展示,但是Figure可以调用清空函数,对于重复画图可以解决内存溢出的问题。
- 类似下图,就可以将绘制出一个折线图,但是没有展示。
- 但是我的目的最后是要在gui编程上的ui界面显示出绘图。
此处在Figure上创建子图绘制中,最操蛋的是,pycharm对子图所有方法都没有代码提示,也不熟悉这类方法。
import matplotlib.pyplot as plt
import numpy as np
from matplotlib.figure import Figure
# 1.创建画布
figure = Figure(figsize=(9, 4), dpi=100)
# 1.创建一个子图
subplot = figure.add_subplot(111)
# 2.绘制折线图
subplot.plot([1, 2, 3, 4, 5, 6 ,7], [17,17,18,15,11,11,13])
# 2.1 设置x y 轴上下限,以及标题
subplot.set(xlim=[0, 26], ylim=[0, 250], title=filename)
# 2.2 设置x轴刻度
subplot.xaxis.set_ticks(np.arange(0,26,1))
# 3.显示图像
tkinter的简单使用
- 创建应用窗口
import tkinter
root = Tk()
#设置标题
root.title("画图程序")
#设置窗口大小
root.geometry("1500x960")
- 设置按钮
# 1 设置文本标签
# 1.1 声明一个文本标签
label = tkinter.Label(root, text='选择目录:', font=('华文彩云', 15))
# 1.2 设置文本标签在root窗口何处
label.place(x=50, y=10)
- 设置输入框
# 输入框控件
# 1.1 创建一个字符串变量,StringVar变量最主要两个方法set,get
entry_text = StringVar()
# 1.2 创建一个只读输入框,输入框中内容就是字符串变量内容。
entry = Entry(root, textvariable=entry_text, font=('FangSong', 10), width=30, state='readonly')
# 1.3 设置输入框位置
entry.place(x=150, y=10)
- 设置按钮
按钮将要绑定方法,通过command指定定义好的方法
# 选择路径的按钮
tkinter_button = Button(root, text="目录选择", command=get_path)
tkinter_button.place(x=400, y=5)
- 将matplotlib绘制的图像展示在tkinter的UI界面上
首先创建Figure画布,绘制图像。然后将Figure画布和tkinter组件做个映射,之后把Figure上的绘图的图draw到中间件上。最后将这个绘画从中间件上提出来pack到UI界面上(其实就是把中间件上的图映射到UI组件上)。
from matplotlib.figure import Figure
from matplotlib.backends.backend_tkagg import (FigureCanvasTkAgg,
NavigationToolbar2Tk)
f = Figure(figsize = (4,3), dpi = 100)
a = f.add_subplot(111)
a.plot([1,2,4,3,5,7,6,7,8,8,9,6,7,8,7,5,6,4,3,4,3,2,1])
root = Tk()
# 中间件映射
canvas = FigureCanvasTkAgg(f, root)
# 中间件draw到Figure上的图
canvas.draw()
# 获取到中间件上的图,并且pack到UI组件root上。
canvas.get_tk_widget().pack()
root.mainloop()
内存溢出问题
在实际使用过程中,可能会一直在一个GUI上相同位置频繁绘图。
有下面几种情况都会出现内存溢出的问题
- 在matplot层面上,每一次绘图都重新生成Figure,subplot。
- 在GUI层面上,每次重新生成一个UI组件去接收plot的绘图,再放到GUI界面上。经过实验此处就算对组件使用destroy()方法销毁组件也不会回收掉内存。
- 在matplot和GUI中间的映射层面上,每次重新绘图都用同一个figure,subplot,以及UI组件,但是有个中间件FigureCanvasTkAgg在其中频繁创建并绘图到GUI上。
内存溢出问题具体案例
类似于这篇文章的改进代码(代码如下),虽然可以清除UI组件-canvas画图内容,但是内存还是在一直叠加没有回收。
from tkinter import *
import matplotlib.pyplot as plt
from matplotlib.figure import Figure
from matplotlib.backends.backend_tkagg import (FigureCanvasTkAgg,
NavigationToolbar2Tk)
def plot():
global output, fig
fig = Figure(figsize=(5, 5), dpi=100)
y = [i ** 2 for i in range(101)]
# adding the subplot
plot1 = fig.add_subplot(111)
# plotting the graph
plot1.plot(y)
# creating the Tkinter canvas
# containing the Matplotlib figure
output = FigureCanvasTkAgg(fig, master=canvas)
print(FigureCanvasTkAgg)
output.draw()
# placing the canvas on the Tkinter window
output.get_tk_widget().pack()
def clear_plot():
global output
if output:
# or for child in canvas.winfo_children():
# child.destroy()
# or just use canvas.winfo_children()[0].destroy()
output.get_tk_widget().destroy()
output = None
# the main Tkinter window
window = Tk()
output = None
fig = None
# setting the title
window.title('Plotting in Tkinter')
# dimensions of the main window
window.geometry("700x700")
canvas = Canvas(window, width=500, height=500, bg='white')
canvas.pack()
# button that displays the plot
plot_button = Button(master=window, command=plot, height=2, width=10, text="Plot")
clear_button = Button(master=window, command=clear_plot, height=2, width=10, text="clear", background="yellow")
# place the button
plot_button.pack()
clear_button.pack()
# run the gui
window.mainloop()
- 代码分析:首先在主程序中创建一个主窗口UI,并创建了一个画布UI-canvas来接收绘图。并且两个按钮并分别绑定方法做绘制和清除图像。在绘制中:内存溢出有Figure画布频繁创建,中间件FigureCanvasTkAgg频繁创建。在清除中:直接销毁掉了中间件在canvas画布上放置的内容。
- 内存分析
-
首次执行代码,程序情况与内存情况如图:
首次运行,还未绘图,内存大小为57M
-
绘图后,程序情况与内存大小
绘图后,内存大小变成62M
-
之后,销毁绘图,并重新绘图,重复此操作,如下
内存以及达到122M。
-
综合以上实验可以得出结论,销毁UI组件上的内容,并不能释放掉内存,重复的绘图只能增加内存使用情况。
内存溢出改进方案
Figure画布只创建一次,用此画布频繁画图,以上案例中UI组件的canvas也只创建一次。最后是中间件的处理。
值得注意的是:绘图已经在canvas上pack出来,要清除要么和上文代码一样,用销毁的方式(直接把中间件绘图到UI组件上的映射关系给销毁),要么pack_forget()方式取消展示。
为了减少不必要的内存开销,我将Figure,subplot,canvas,中间件FigureCanvasTkAgg的创建都声明在主程序中,不会在绑定方法中重复声明增加内存开销。
继而,上文中将清除方法用直接销毁映射关系【output.get_tk_widget().destroy()】就行不通(上文代码可以使用这种方式是因为中间件在重复申请,销毁了映射并没有关系,下一次绘图已经是一个新的中间件),因为销毁了映射关系,重新画图时,新图将不能展示在UI上。所以使用了pack_forget()方式取消展示,并且用Figure的clear()方法,清空了Figure画布上的内容。
然后在绘图方法中重新把Figure上的图绘制到中间件上,同时再pack()展示到UI界面上(因为在清除方法中使用pack_forget取消了展示)。
- 为什么使用Figure,不适用plot.figure?
- 因为Figure有clear方法,这个是Figure特有的。
- plot也有clf(),cla()方法,也可以清空画布为什么不用
- 实践出真知,在我的需求中我用了没效果,不知道为啥。
from tkinter import *
import matplotlib.pyplot as plt
from matplotlib.figure import Figure
from matplotlib.backends.backend_tkagg import (FigureCanvasTkAgg,
NavigationToolbar2Tk)
def plot():
global output, plot1, fig
y = [i ** 2 for i in range(101)]
# plotting the graph
plot1.plot(y)
output.draw()
output.get_tk_widget().pack()
# placing the canvas on the Tkinter window
# output.get_tk_widget().pack()
def clear_plot():
global output, fig, plot1
# if output:
# for child in canvas.winfo_children():
# child.destroy()
# # or just use canvas.winfo_children()[0].destroy()
# output = None
output.get_tk_widget().pack_forget()
plot1.clear()
# the main Tkinter window
window = Tk()
output = None
fig = None
# setting the title
window.title('Plotting in Tkinter')
# dimensions of the main window
window.geometry("700x700")
fig = Figure(figsize=(5, 5), dpi=100)
plot1 = fig.add_subplot(111)
plot1.plot()
canvas = Canvas(window, width=500, height=500, bg='white')
output = FigureCanvasTkAgg(fig, master=canvas)
output.draw()
output.get_tk_widget().pack()
canvas.pack()
# button that displays the plot
plot_button = Button(master=window, command=plot, height=2, width=10, text="Plot")
clear_button = Button(master=window, command=clear_plot, height=2, width=10, text="clear", background="yellow")
# place the button
plot_button.pack()
clear_button.pack()
# run the gui
window.mainloop()
总结
将matplot的绘图放到tkinter的UI上时内存的开销问题,主要是要理解到中间件的3个必要操作:
canvas_tk_agg = FigureCanvasTkAgg(figure, master=canvas)
canvas_tk_agg.draw()
canvas_tk_agg.get_tk_widget().pack()
第一个创建中间件
第二个将Figure画布上的内容draw到中间件上
第三个将中间件上的内容pack()到UI组件上(我理解为将中间件上的内容映射到UI组件上)
第三个语句其实是就是一个映射关系,只要有这个关系在,中间件上的内容将会一直在映射到UI组件上并显示。所以要重复展示新图而又不增加内存开销,只需要更换中间件上的内容-即重新在画布上绘图,再draw()到中间件上。
附录
我的实际需求中改进了内存溢出的代码。
import os
import tkinter
from tkinter import filedialog
from tkinter.filedialog import askdirectory
from tkinter import Listbox
from os import listdir
import main
import os
import matplotlib.pyplot as plt
import numpy as np
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
import json
from matplotlib.axes import Axes
from matplotlib.figure import Figure
import gc
import matplotlib as mpl
mpl.rcParams['font.sans-serif']=['SimHei'] #指定默认字体 SimHei为黑体
mpl.rcParams['axes.unicode_minus']=False #用来正常显示负号
def get_path():
global listbox
path = filedialog.askdirectory(title='请选择文件')
entry_text.set(path)
fileName_col = listdir(path)
tuple_fileName = tuple(fileName_col)
var_fileName = tkinter.StringVar()
var_fileName.set(tuple_fileName)
listbox = tkinter.Listbox(root, width=60, height=100, listvariable=var_fileName)
listbox.place(x=0, y=100)
# 对存放文件名的UI组件做事件绑定
listbox.bind("<Button-1>", get_oneOfFilename)
def get_oneOfFilename(event):
listbox_curselection = listbox.curselection()
filename = listbox.get(listbox_curselection[0])
full_filename = entry_text.get() + '/' + filename
tubiao(full_filename)
def tubiao(filename):
global figure, canvas_tk_agg, num, subplot, canvas, widget
print("准备绘图的文件名字:",filename)
num = num +1
print("第",num,"次绘图")
with open(filename,'r') as fp:
data = json.load(fp)
Categories = data['Data'][0]["Categories"]
SeriesData = data['Data'][0]["SeriesData"]
# 清空Figure画布上内容,方便重新画图
subplot.clear()
subplot.set(xlim=[0, 26], ylim=[0, 250], title=filename)
subplot.xaxis.set_ticks(np.arange(0,26,1))
subplot.bar(Categories, SeriesData, width=0.08, color='red', linewidth=1)
print("开始在两个画布上的映射绘图")
print(widget)
print(canvas_tk_agg)
#在中间件上重新绘图,不用get_tk_widget().pack()。主程序中已经将中间件到UI组件的映射关系声明好了。
canvas_tk_agg.draw()
# widget.pack()
plt.show()
if __name__ == '__main__':
num = 0
#生成主窗口
root = tkinter.Tk()
root.title("画图程序")
root.geometry("1500x960")
# 选择路径的文本标签
label = tkinter.Label(root, text='选择目录:', font=('华文彩云', 15))
label.place(x=50, y=10)
# 选择路径的输入框控件
entry_text = tkinter.StringVar()
entry = tkinter.Entry(root, textvariable=entry_text, font=('FangSong', 10), width=30, state='readonly')
entry.place(x=150, y=10)
# 选择路径的按钮
tkinter_button = tkinter.Button(root, text="目录选择", command=get_path)
tkinter_button.place(x=400, y=5)
#声明存放文件名的ui组件
listbox = None
# 声明plot画布
figure = Figure(figsize=(9, 4), dpi=100)
subplot = figure.add_subplot(1, 1, 1)
subplot.set(xlim=[0, 26], ylim=[0, 250],title="初试状态图")
subplot.xaxis.set_ticks(np.arange(0, 26, 1))
# 声明canvas画布
canvas = tkinter.Canvas(root)
canvas_tk_agg = FigureCanvasTkAgg(figure, master=canvas)
canvas_tk_agg.draw()
widget = canvas_tk_agg.get_tk_widget()
widget.pack()
canvas.place(x=450, y=100)
root.mainloop()
参考资料
https://www.cnpython.com/qa/725169
https://qa.1r1g.com/sf/ask/848704531/
https://www.5axxw.com/questions/content/gpjdq3
https://cloud.tencent.com/developer/ask/sof/1139788
https://cloud.tencent.com/developer/ask/sof/316513
https://www.cnpython.com/qa/329692