Issue
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!
Solution
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)
Answered By - Daniel Do
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.