Question

I have an issue with graphing data within wxPython - this code below works but it's not exactly right: For now I plot random numbers which are multiples of an entry box, this is done every 100ms.

My issue is that the entire history of numbers is shown as a pose is a (say) running window of 25 samples. I initially tried redrawing the graph on each collection of 100 samples, something like, if( length(data)%100 ): drawGraph but again this didn't look correct.

Thoughts and suggestions welcome.

My code:

print( "\n- Please Wait -- Importing Matplotlib and Related Modules...\n" )
import random
import matplotlib
import numpy
import wx
import u3
import numpy as np
matplotlib.use('WXAgg')

from matplotlib.backends.backend_wxagg import FigureCanvasWxAgg as FigureCanvas
from matplotlib.backends.backend_wx import NavigationToolbar2Wx
from matplotlib.figure import Figure


class TemperaturePanel( wx.Panel ) :
    def __init__( self, parent, position ) :
        wx.Panel.__init__( self, parent, pos=position, size=(800,320) )
        # initialize matplotlib 
        self.figure = matplotlib.figure.Figure( None, facecolor="white" )
        self.canvas = matplotlib.backends.backend_wxagg.FigureCanvasWxAgg( self, -1, self.figure )
        self.axes = self.figure.add_subplot(111)
        self.axes.grid(True, color="gray")
        self.axes.set_xbound( (0,5) )
        self.axes.set_ybound( (3,80) )
        self.axes.set_xlabel( "Minutes" )
        self.axes.set_ylabel( "Temperature ($^\circ$C)" )
        self.axes = self.figure.add_subplot(111)
        self.axes.grid(True, color="gray")
        self._SetSize()
        self.Bind( wx.EVT_SIZE, self._SetSize )
        self.TemperatureData   = []

    def updateTemperature(self, value):
        self.TemperatureData.append( value )
        length = len(self.TemperatureData)
        x = np.arange( length )
        y = np.array(self.TemperatureData)

        yMin = round(min(y)) - 2
        yMax = round(max(y)) + 2            
        self.axes.plot(x,y, "-k")
        self.axes.set_ybound( (yMin,yMax) )
        self.canvas = FigureCanvas(self, -1, self.figure)

    #-----------------------------------------------------------------------------------
    def _SetSize( self, event=None ):
        pixels = self.GetSize()
        self.SetSize( pixels )
        self.canvas.SetSize( pixels )

        dpi = self.figure.get_dpi()
        self.figure.set_size_inches( float( pixels[0] ) / dpi,float( pixels[1] ) / dpi )
    #------------------------------------------------------------------------------------




class MainWindow(wx.Frame):
    def __init__(self, parent):
        #wx.Frame.__init__(self, *args, **kwargs)
        wx.Frame.__init__(self, parent, title="Graph Issue", size=(1000,600))
        self.panel = wx.Panel(self)
        self.spin = wx.SpinCtrl(self.panel)
        self.button = wx.Button(self.panel, label="Update")
        self.stop   = wx.Button(self.panel, label="Stop")

        self.sizer = wx.BoxSizer()
        self.sizer.Add(self.spin)
        self.sizer.Add(self.button)
        self.sizer.Add(self.stop)

        self.TemperatureGraph = TemperaturePanel( self, position=(20, 50) )
        self.panel.SetSizerAndFit(self.sizer)
        self.Show()

        # Use EVT_CHAR_HOOK on Frame insted of wx.EVT_KEY_UP on SpinCtrl
        # to disable "on Enter go to next widget" functionality
        self.Bind(wx.EVT_CHAR_HOOK, self.OnKey) 
        self.button.Bind(wx.EVT_BUTTON, self.OnUpdate)
        self.stop.Bind(wx.EVT_BUTTON, self.OnStop)

        self.timer = wx.Timer(self)
        self.Bind(wx.EVT_TIMER, self.OnTimer, self.timer)
        self.Bind(wx.EVT_TIMER, self.updateTemperature, self.timer)
        self.timer.Start(100)
        self.value = 0

    def OnKey(self, e):
        if e.GetKeyCode() == wx.WXK_RETURN:   # Is the key ENTER?
            self.value = self.spin.GetValue() # Read SpinCtrl and set internal value
        else:                                 # Else let the event out of the handler
            e.Skip()

    def OnUpdate(self, e):
        self.value = self.spin.GetValue() # Read SpinCtrl and set internal value

    def OnTimer(self, e):
        # Show internal value
        print(self.value)

    def updateTemperature(self, e):
        Temperature       = self.value*random.uniform(-1,1)                # obtain currnt temperature
        self.TemperatureGraph.updateTemperature(Temperature)               # add temperature to graph   

    def OnStop(self, e):
        self.timer.Stop()
        self.Destroy()


app = wx.App(False)
win = MainWindow(None)
app.MainLoop()
Was it helpful?

Solution

If I understood the question correctly, you need 25 last temperature values only to be shown in your graph instead of all history of values. If that's what you want, then in the updateTemperature subroutine only the last 25 values should be plotted:

if length < 25:
    x = np.arange(length)
    y = np.array(self.TemperatureData)
else:
    x = np.arange(length-25, length)
    y = np.array(self.TemperatureData)[-25:]

To make the plot look better, x axis can be adjusted the same way you do it with y axis:

xMin = 0 if length < 25 else length-25
xMax = 25 if length < 25 else length
self.axes.set_xbound( (xMin,xMax) )

If the plot looks OK to you, and the issue is about the memory leak that causes the graph to freeze after ~200 iterations, that's due to the creation of FigureCanvas on every temperature update. Instead, you can re-use your existing FigureCanvas, changing the last line of updateTemperature to

    self.canvas.draw()
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top