0

Good Day,

So I am fighting a bit against refreshing a Matplotlib inside a Tkinter. Overall the code works fine but once you press the "up" key the programm just crashes at figure.canvas.draw() - I also run this code on a rasberry and there it just says memory access failure.

I know you can animate via FuncAnimation but there I had a big problem that it just increased memory over time until crash so I dont want to use it (see Python3 - Matplotlib FuncAnimation continuous memoryleak)

So I thought I could just redraw it the old fashioned way but that doesnt seem to work aswell.

Minimal Code:

import tkinter as tk

from threading import Thread
from numpy import sin, cos, pi
from pynput.keyboard import Listener
import keyboard



reDraw = False


U1ausg = U2ausg = U3ausg = Counter_3 = 50
Counter_1 = 120
Counter_2 = 240


        

#--Mainwindow--
class Drehstromdemonstrator(tk.Tk):

    def __init__(self):
        #Predefine

        tk.Tk.__init__(self)
        tk.Tk.wm_title(self, "Minimal")
        
        container = tk.Frame(self)
        container.pack(side="top", fill="both", expand=True)
        container.grid_rowconfigure(0, weight=1)
        container.grid_columnconfigure(0, weight=1)  

        self.frame = plotSine(self,(tk.Tk.winfo_screenwidth(self)*0.7,tk.Tk.winfo_screenheight(self)*0.7))

    def reDrawFrame(self):
        self.frame.drawStuff()

      
import numpy,matplotlib
matplotlib.use('TkAgg')
from numpy import sin, cos, pi, deg2rad, linspace, arange
import matplotlib.pyplot as plt
import matplotlib.animation as animation
import matplotlib.ticker as tck
import time
import math


from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from matplotlib.figure import Figure
from math import ceil

#global U1ausg, U2ausg, U3ausg, Counter_1, Counter_2, Counter_3


class plotSine:

    def __init__(self,masterframe,size):
        self._running = True

        global U1ausg
        global U2ausg
        global U3ausg

        (w,h) = size
        inchsize = (w/25.5, h/25.4)
        fig = self.figure = Figure(inchsize)

        self.axes = fig.add_subplot(111)

        self.axes.xaxis.set_ticks(arange(0,390,30))
        self.axes.margins(x=0)
        self.axes.yaxis.set_ticks(arange(-120,130,20))
        self.axes.set_ylim(-120,120)

        #create canvas as matplotlib drawing area
        self.canvas = FigureCanvasTkAgg(fig, master=masterframe)
        self.canvas.get_tk_widget().pack()
        #self.canvas.mpl_connect('key_press_event',on_press)
        
        self.x = linspace(0,360,1000)
        self.axes.grid()

        self.ysin = int(ceil(U1ausg))*sin(50/Counter_3 * deg2rad(self.x))


        self.lineU1, = self.axes.plot(self.x, self.ysin, "-r",label="U1")
        
        #self.drawStuff()

    #Draw the plot
    def drawStuff(self,*args):

        ax = self.axes
        #ax.clear()
        ax.legend(["U1"])

        #Changed everything to degree instead of PI, better looking
        self.lineU1.set_ydata(int(ceil(U1ausg))*sin(50/Counter_3 * deg2rad(self.x)))

        print("Redrawing!")
#---------------------------------------------------------------------
#---------------------------------------------------------------------
#---------------------------------------------------------------------
        self.figure.canvas.draw()  #First run is fine, crashes here afterwards

        print("Flush!")
        self.figure.canvas.flush_events()
        
        print("ReDraw Done!")

#--Run Mainprog
app = Drehstromdemonstrator()


def on_press(event):
    global Counter_3
    Counter_3 = Counter_3 + 5
    print("up")
    app.reDrawFrame()
    
try:    
    keyboard.on_press_key("up", on_press)
    app.mainloop()

# Aufraeumarbeiten nachdem das Programm beendet wurde
except UnicodeDecodeError:
    print("Interrupt aswell!")
    lampensteuerung.terminate()

except KeyboardInterrupt:
    print("Interrupt")
    lampensteuerung.terminate()

except Exception as e:
    print(e)   

This code just puts a simple Sine Wave (matplotlib) into a Tkinter Overlay (There have to be one) - On the Button Press "UP" you can increase the frequenzy by +5.

I am using keyboard.on_press_key("up", on_press) because I have to simulate it as close as possible on a rasberry where I use GPIO.add_event_detect(channel, GPIO.RISING) (With fig.canvas.mpl_connect('key_press_event', on_press) it works fine but I cant do that on the PI)

Thanks and Greetings!

Daniel Do
  • 51
  • 7

1 Answers1

0

So I got it myself - The solution : change draw() to draw_idle()

For anyone who needs a changeable Matplotlib inside a Tinker that reacts to Keyinputs / or GPIO.INPUTS here is the Code:

# coding=utf-8
import tkinter as tk
import math as m
from numpy import sin

#-----------------------------------Ausgabe:-----------------------------------------
#Predefine
app = None

freq = 50

# Create Main Window
class Application(tk.Tk):

    def __init__(self):
        #Predefine
        tk.Tk.__init__(self)
        tk.Tk.wm_title(self, "Title")

        container = tk.Frame(self)
        container.pack(side="top", fill="both", expand=True)
        container.grid_rowconfigure(0, weight=1)
        container.grid_columnconfigure(0, weight=1)

        #Add the MatPlotLib 
        self.frame = plotSine(self,(tk.Tk.winfo_screenwidth(self)*0.7,tk.Tk.winfo_screenheight(self)*0.7))

    def reDrawFrame(self):
        self.frame.reDraw()
        self.frame.reDraw()  ## For whatever reason it can happen that the Draw Function has to be called twice for a good refresh?

#----------------------------------------------------------------------------------
#----------------------------------------------------------------------------------
#----------------------------------------------------------------------------------


import numpy,matplotlib
matplotlib.use('TkAgg')

from numpy import sin, deg2rad, linspace
import math

from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from matplotlib.figure import Figure
from math import ceil

import keyboard

class plotSine:

    def __init__(self,masterframe,size):
        self._running = True

        (w,h) = size
        inchsize = (w/25.5, h/25.4)
        fig = self.figure = Figure(inchsize)

        self.axes = fig.add_subplot(111)
        
        #create canvas as matplotlib drawing area
        self.canvas = FigureCanvasTkAgg(fig, master=masterframe)
        self.canvas.draw_idle()
        self.canvas.get_tk_widget().pack()

        self.x = linspace(0,360,1000)
        self.axes.grid()

        self.ysin = int(ceil(120))*sin(50/freq * deg2rad(self.x))

        self.lineU1, = self.axes.plot(self.x, self.ysin, "-r",label="U1")

    #Draw the plot
    def reDraw(self,*args):
        ax = self.axes

        self.ysin = int(ceil(120))*sin(50/freq * deg2rad(self.x))

        self.lineU1.set_ydata(self.ysin)

        self.figure.canvas.draw_idle()
        self.figure.canvas.flush_events()


#------------------------------------------------------------------------------------
#Keyboard Click - Changeable to any other event i.e. GPIO event detect
def up_press(event):
    global freq
    freq = freq + 5
    app.reDrawFrame()

def down_press(event):
    global freq
    if(freq > 5):
        freq = freq - 5
    app.reDrawFrame()
#-------------------------------------------------------------------------------------
# Start Tkinter
try:
    app = Application()
    keyboard.on_press_key("up", up_press)
    keyboard.on_press_key("down", down_press)
    app.mainloop()

except Exception as e:
    print(e)
Daniel Do
  • 51
  • 7