matplotlib的简单使用
- 创建画布
- 准备x,y轴数据
- 绘制图像
- 显示图像
matplotlib.pyplot的简单画图
使用matplotlib中的pyplot包做画图示例。
- 其中创建画布的figsize(10,10)设置的是像素大小,1代表100像素。此处10 _10图像大小就为1000_1000。
import matplotlib.pyplot as plt
plt.figure(figsize=(10, 10), dpi=100)
plt.plot([1, 2, 3, 4, 5, 6 ,7], [17,17,18,15,11,11,13])
plt.show()
matplotlib.pyplot画图的其他设置
可以看到上面的绘图中没有标题,同时x,y轴也不是我们想要的,x,y轴的上下限是图中数据的上下限。可能我们画图想要的上下限并不是这样
import matplotlib.pyplot as plt
import numpy as np
plt.figure(figsize=(10, 10), dpi=100)
plt.plot([1, 2, 3, 4, 5, 6 ,7], [17,17,18,15,11,11,13])
plt.title("测试绘图")
plt.xticks(np.arange(1,26,1.5))
plt.yticks(np.arange(1,30,1))
plt.xlabel("test")
plt.ylabel("ytest")
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()
此处有 问题出现需要注意,下图。
图中右上角显示图形像素为640 _4800,但是代码中明明通过Figure设置像素大小为900_400。说明通过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
figure = Figure(figsize=(9, 4), dpi=100)
subplot = figure.add_subplot(111)
subplot.plot([1, 2, 3, 4, 5, 6 ,7], [17,17,18,15,11,11,13])
subplot.set(xlim=[0, 26], ylim=[0, 250], title=filename)
subplot.xaxis.set_ticks(np.arange(0,26,1))
tkinter的简单使用
- *创建应用窗口
import tkinter
root = Tk()
root.title("画图程序")
root.geometry("1500x960")
- *设置按钮
label = tkinter.Label(root, text='选择目录:', font=('华文彩云', 15))
label.place(x=50, y=10)
- 设置输入框
entry_text = StringVar()
entry = Entry(root, textvariable=entry_text, font=('FangSong', 10), width=30, state='readonly')
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)
canvas.draw()
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)]
plot1 = fig.add_subplot(111)
plot1.plot(y)
output = FigureCanvasTkAgg(fig, master=canvas)
print(FigureCanvasTkAgg)
output.draw()
output.get_tk_widget().pack()
def clear_plot():
global output
if output:
output.get_tk_widget().destroy()
output = None
window = Tk()
output = None
fig = None
window.title('Plotting in Tkinter')
window.geometry("700x700")
canvas = Canvas(window, width=500, height=500, bg='white')
canvas.pack()
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")
plot_button.pack()
clear_button.pack()
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)]
plot1.plot(y)
output.draw()
output.get_tk_widget().pack()
def clear_plot():
global output, fig, plot1
output.get_tk_widget().pack_forget()
plot1.clear()
window = Tk()
output = None
fig = None
window.title('Plotting in Tkinter')
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()
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")
plot_button.pack()
clear_button.pack()
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']
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)
listbox.bind("", 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"]
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)
canvas_tk_agg.draw()
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)
listbox = None
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 = 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
Original: https://blog.csdn.net/qq_44817900/article/details/124302515
Author: Rui@
Title: matplotlib与tkinter的简单使用,以及内存溢出问题。
原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/764262/
转载文章受原作者版权保护。转载请注明原作者出处!