Re: Plotting to a matplotlib / pyplot window does not work
Matthias Brennwald <[email protected]>
| Newsgroups | gmane.comp.python.wxpython |
|---|---|
| Message-ID | <[email protected]> |
On Mo, Jul 20, 2020 at 23:02, Andrea Gavana <[email protected]> wrote: > A relatively straightforward solution is to embed your plot in a > wxPython window and update the plot from a thread when new data > becomes available. Ok, I revised my code accordingly (see attached example), and it works a lot better. However, if the plot is updated every time after adding a new data value, the GUI locks up if the rate of incoming data becomes too high. I worked around this by setting a flag that is TRUE while the plot is being updated, and the code calls the updating of the plot only if this flag is FALSE (this is also in the attached example). This works ok, but my gut feeling is that there is a better / cleaner / more elegant way of avoiding the GUI lockup. Thoughts, ideas, suggestions? -- You received this message because you are subscribed to the Google Groups "wxPython-users" group. To unsubscribe from this group and stop receiving emails from it, send an email to [email protected]. To view this discussion on the web visit https://groups.google.com/d/msgid/wxpython-users/LZ1XDQ.HRWQ1OF4INFR%40gmail.com.
wx_pyplot_minimal_example_2
(text/x-python3, 4 KB)
#!/usr/bin/env python3
# Example showing how I cannot make pyplot work to plot data in a pyplot window
# (1) Show GUI with a button to start running a measurement instrument
# (2) Once the button is pressed, run the measurement instrument:
# - Get a new data value every second
# - After getting a new value, plot all data in a pyplot window
# - The GUI locks up if the data rate is too high, and the data plotting is called too often (idle_time less than 0.1 in the measurement_thread)
#
# The problem is that the pyplot window does not seem to show up on the screen.
import time
import random
from threading import Thread
import wx
from matplotlib.backends.backend_wxagg import FigureCanvasWxAgg as FigureCanvas
from matplotlib.figure import Figure
# measurement instrument that takes measurement data and plots data in a pyplot window
class instrument:
def __init__(self):
self.values = [] # container list of the measurement values
self.plot_frame = instrument_plot_frame() # frame for plotting measurement data recorded with the instrument
self.plotting_in_progress = False
def read_value(self):
# get a (fake) measurement value
print('Getting a new data value...')
val = random.random() # determine a (fake) data value
self.values.append(val) # append the value to list of previous values
return val
def plot_data(self):
print('Plotting the data (' + str(len(self.values)) + ' data points)...')
self.plotting_in_progress = True
self.plot_frame.Show(True)
self.plot_frame.axes.cla() # clear plot data
self.plot_frame.axes.plot(self.values) # plot all data
self.plot_frame.canvas.draw()
self.plot_frame.Refresh()
self.plotting_in_progress = False
# plot window (part of the instrument class)
class instrument_plot_frame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, -1, 'Instrument data')
self.figure = Figure() # create matplotlib figure
self.axes = self.figure.add_subplot(111) # create figure axes
self.canvas = FigureCanvas(self, -1, self.figure) # FigureCanvasWxAgg
self.sizer = wx.BoxSizer(wx.VERTICAL)
self.sizer.Add(self.canvas, 1, wx.LEFT | wx.TOP | wx.EXPAND)
self.SetSizer(self.sizer)
self.Fit()
# Thread that gets measurement data from the instrument, with logging to pyplot window
class measurement_thread(Thread):
def __init__(self, main_window):
Thread.__init__(self)
self.main_window = main_window # for communication with the main_window
self.idle_time = 0.01 # idle time between instrument readings
def run(self):
# get data from instrument and plot the data in pyplot window
while True:
self.main_window.instrument.read_value() # get a measurement value
if not self.main_window.instrument.plotting_in_progress:
wx.CallAfter( self.main_window.instrument.plot_data ) # update the pyplot figure <-- *** THIS NEEDS TO BE IN wx.CallAfter TO AVOID CRASHING ***
time.sleep(self.idle_time) # wait a bit for the next measurement
# main window:
class MyFrame(wx.Frame):
def __init__(self):
# define GUI:
wx.Frame.__init__(self, None, title="Instrument Control", size=(300,150))
panel = wx.Panel(self)
button = wx.Button(panel, label='Start measurements')
button.Bind(wx.EVT_BUTTON, self.on_button_press)
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(button, 1, wx.ALL | wx.CENTER, 25)
panel.SetSizer(sizer)
# measurement instrument, and thread for measurements:
self.instrument = instrument()
self.measurement_thread = measurement_thread(self)
# show the window:
self.Show()
def on_button_press(self, event):
print('Starting the measurement thread...')
self.measurement_thread.start() # start the measurement thread
# main:
if __name__ == '__main__':
app = wx.App()
frame = MyFrame()
app.MainLoop()