matplotlib与tkinter的简单使用,以及内存溢出问题。

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与tkinter的简单使用,以及内存溢出问题。

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

matplotlib与tkinter的简单使用,以及内存溢出问题。

上图显示可以看到中文出现乱码,此处需要添加一些额外信息,如下。

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

此处有 问题出现需要注意,下图。

matplotlib与tkinter的简单使用,以及内存溢出问题。
图中右上角显示图形像素为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()

matplotlib与tkinter的简单使用,以及内存溢出问题。

内存溢出问题

在实际使用过程中,可能会一直在一个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

    matplotlib与tkinter的简单使用,以及内存溢出问题。
    matplotlib与tkinter的简单使用,以及内存溢出问题。
  • 绘图后,程序情况与内存大小

    绘图后,内存大小变成62M

    matplotlib与tkinter的简单使用,以及内存溢出问题。
    matplotlib与tkinter的简单使用,以及内存溢出问题。
  • 之后,销毁绘图,并重新绘图,重复此操作,如下

    内存以及达到122M。

    matplotlib与tkinter的简单使用,以及内存溢出问题。

综合以上实验可以得出结论,销毁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/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

  • golang 笔记

    for循环,一个key在一个map中,则一直迭代 go总是使用值传递,但是有些数据类型是引用类型,比如map, pointer, channel, slice是部分引用类型 在给函…

    Python 2023年6月12日
    071
  • YOLO系列 — YOLOV7算法(六):YOLO V7算法onnx模型部署

    有很多人来问我,基于YOLO v7算法训练出来一个权重文件,如何进行部署。所以特地写一篇部署的blog~一般,我们基于pytorch深度学习框架训练出来的权重文件是pt格式的,我们…

    Python 2023年8月2日
    056
  • itemexporters-scrapy框架8-python

    文章目录 1 前言 2 item exporters * 2.1 Item Exporters 2.2 BaseItemExporter 2.3 实例化 – 2.3.1…

    Python 2023年10月6日
    049
  • 可靠的自托管「GitHub 热点速览 v.22.37」

    自托管(Self-Hosted) 是很多开源项目主打的亮点:数据在手,安全我有。本周 GitHub 热点榜单上有多款自托管的项目,当中自然不能少了之前 HG 小伙伴 @makes …

    Python 2023年10月22日
    069
  • 【小记】go如何判断key是否在map中

    go如何判断key是否在map中 判断key是否存在, 判断方式为value,ok := map[key], ok为true则存在 if _, ok := map[key], ok…

    Python 2023年6月9日
    058
  • [Pandas] 数据迭代

    df 1.迭代Series Series本身是一个可迭代的对象,可直接对Series使用for语句来遍历它的值 import pandas as pd df = pd.DataFr…

    Python 2023年8月7日
    038
  • Python Flask 请求GET、 POST 、PUT、 DELETE

    1,示例一个简单的请求 from flask import Flask # 导入Flask类 app = Flask(__name__) # 实例化flask @app.route…

    Python 2023年8月9日
    089
  • 1.python中使用easygui出现的AttributeError错误的一种原因

    错误概述: 今天学习easygui的用法的时候,发现用vscode老是出现AttributeError提示,我一开始以为是vscode的错误,可是我检查了很多次都没发现错误,期间找…

    Python 2023年6月15日
    077
  • VScode开发STM32/GD32单片机-环境搭建

    1、软件下载 1.1、安装VSCode 1.2、下载安装VisualGDB 1.3、下载安装mingwin64 1.4、下载安装OpenOCD 1.5、下载GNU Arm Embe…

    Python 2023年10月19日
    034
  • Pandas基础命令速查

    Pandas基础命令速查 缩写解释 & 库的导入df — 任意的pandas DataFrame(数据框)对象s — 任意的pandas Series(数组)对象panda…

    Python 2023年8月20日
    031
  • 实验三 pandas的基本使用

    实验三 pandas的基本使用 1. 实验内容 (1)pandas的导入。(2)创建Series对象和DataFrame数据表。(3)DataFrame数据表索引与切片。(4)Da…

    Python 2023年8月20日
    047
  • Pytest学习-日志模块

    Pytest学习-日志模块 原创 我的事说来话长2022-08-07 19:41:52博主文章分类:Pytest ©著作权 文章标签 pytest 文章分类 Python 后端开发…

    Python 2023年5月24日
    082
  • 2020年1月-*CTF比赛Web部分题解

    文章目录 1 Web * 1.1 oh-my-note 1.2 oh-my-socket 2 总结 3 附录 1 Web 由于期末考试和各种课设,有一段时间没有比赛了,做题时没有思…

    Python 2023年8月15日
    064
  • pytest(一)框架的使用

    一、pytest特点1.入门简单,文档丰富2.支持部分单元测试,和复杂的功能测试3.支持参数化4.执行测试用例过程中支持跳过操作5.pytest支持重复执行失败的case,unit…

    Python 2023年9月14日
    043
  • Python多进程处理(读、写)numpy矩阵

    前言 由于需要使用python处理一个380*380的numpy矩阵,经过计算后对其中的每个元素进行赋值,单进程处理大约需要4小时,要处理几百个矩阵,时间上有些耗不起,研究了一下p…

    Python 2023年8月27日
    079
  • MTCTF-easypiclke复现

    给出了题目源码: import base64 import pickle from flask import Flask, session import os import ran…

    Python 2023年8月10日
    049
亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球