Search code examples
animationmatplotlibwxpython

Updating matplotlib live graph in wxPython panel with scrolling x-axis


I am trying to animate a live graph in a wx.Panel. I would like to have the x-axis update like this example. Many of the examples I see are basic and don't take into consideration other controls and functions in the class. Others have so many extras that I get lost in the weeds. I can't get the animation command in the right place or update the x-axis. Here is the code:

import wx
import logging
import numpy as np
import matplotlib
import time
import matplotlib.animation as animation

matplotlib.use('WXAgg')

import matplotlib.pyplot as plt

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

fTemp = ""
x = 0

class TempClass(wx.Frame):
    def __init__(self):
        wx.Frame.__init__(self, None, -1, title="", size=(600,500))
        panel = wx.Panel(self)

        self.fig = Figure(figsize=(6,4), dpi=75, facecolor='lightskyblue', edgecolor='r')
        self.canvas = FigureCanvas(self, -1, self.fig)
        self.ax = self.fig.add_subplot(111)

        self.ax2 = self.ax.twinx()
        self.ax.set_ylim(60,90)
        self.ax.set_xlim(0,24)
        self.ax2.set_ylim(0,100)

        # major ticks every 5, minor ticks every 1                                      
        xmajor_ticks = np.arange(0, 24, 5)                                              
        xminor_ticks = np.arange(0, 24, 1)                                               

        self.ax.set_xticks(xmajor_ticks)                                                       
        self.ax.set_xticks(xminor_ticks, minor=True)                                           
        self.ax.grid()  

        self.ax.set_xlabel('Hour')
        self.ax.set_ylabel('Temp')
        self.ax2.set_ylabel('Humidity')
        self.ax.set_title('Temperature')

        # The graph does not show in the panel when this in uncommented
        #self.ani = animation.FuncAnimation(self.fig, self.onPlotTemp, interval=1000)

        self.fanSensorTimer = wx.Timer(self)
        self.Bind(wx.EVT_TIMER, self.onPlotTemp, self.fanSensorTimer)

        self.fanSensorBtn = wx.Button(self, -1, "Start Sensor")
        self.Bind(wx.EVT_BUTTON, self.onStartTempPlot, self.fanSensorBtn)

        font1 = wx.Font(18, wx.DEFAULT,wx.NORMAL,wx.BOLD)
        self.displayTemp = wx.StaticText(self, -1, "Current Tempurature")
        self.curTempTxt = wx.TextCtrl(self, -1, "0",size=(100,40), style=wx.TE_READONLY|wx.TE_CENTRE|wx.BORDER_NONE)
        self.curTempTxt.SetFont(font1)
        self.displayHum = wx.StaticText(self, -1, "Current Humidity")
        self.curHumTxt = wx.TextCtrl(self, -1,"0", size=(100,40), style=wx.TE_READONLY|wx.TE_CENTRE|wx.BORDER_NONE)
        self.curHumTxt.SetFont(font1)

        self.displayBox = wx.GridBagSizer(hgap=5,vgap=5)
        self.displayBox.Add(self.displayTemp, pos=(0,0), flag=wx.TOP|wx.LEFT, border=5)
        self.displayBox.Add(self.displayHum, pos=(0,1), flag=wx.TOP, border=5)
        self.displayBox.Add(self.curTempTxt, pos=(1,0), flag=wx.ALL, border=5)
        self.displayBox.Add(self.curHumTxt, pos=(1,1), flag=wx.ALL, border=5)

#---------
        self.vbox = wx.BoxSizer(wx.VERTICAL)
        self.vbox.Add(self.canvas, wx.ALIGN_CENTER|wx.ALL, 1)
        self.vbox.Add(self.fanSensorBtn)
        self.vbox.Add(self.displayBox, wx.ALIGN_CENTER|wx.ALL, 1)
        self.SetSizer(self.vbox)
        self.vbox.Fit(self)

    def start(self):
        # get temp/humidity reading from node
        pass


    def readTemp(self, data1, data2):
        "Populates Current Temp"
        global fTemp
        self.curTempTxt.Clear()
        a = format(data1, '08b')
        b = format(data2, '08b')
        x = a+b
        y = int(x, base=2)
        cTemp = ((175.72 * y)/65536)-46.85
        fTemp = cTemp *1.8+32
        cel = format(cTemp,'.1f')
        far = format(fTemp,'.1f')
        self.curTempTxt.WriteText(far + (u'\u00b0')+"F")

    def rh1(self, data1, data2):
        "Populates Current RH"
        global relhum
        self.curHumTxt.Clear()
        a = format(data1, '08b')
        b = format(data2, '08b')
        x = a+b
        y = int(x, base=2)
        rh = ((125 * y)/65536)-6
        relhum = format(rh,'.1f')
        self.curHumTxt.WriteText(relhum + " %")


    def onStartTempPlot(self,event):
        #set for a short time period for testing purposes
        self.fanSensorTimer.Start(5000)  
        print "Timer Started"

    def onPlotTemp(self,event):
        global fTemp, x, relhum
        x +=1
        y = int(fTemp)
        y2 = float(relhum)

        self.ax.plot(x,y,'r.')
        self.ax2.plot(x,y2,'k.')

        self.fig.canvas.draw()

        # send message to node for another reading of temp/humidity


if __name__ == "__main__":
    app = wx.App(False)
    frame = TempClass()
    frame.Show()
    frame.start()
    logging.basicConfig(level=logging.DEBUG)
    app.MainLoop()

I would like to see the x axis increment as the data is plotted beyond the 24 hour point on the graph; when data for point 25 appears, the first point is dropped and the x axis shows '25'. The animation is commented out because it causes the graph to disappear until a point is plotted.

Here is a runnable example of what I am trying to achieve with the x axis:

    import numpy
    from matplotlib.pylab import *
    from mpl_toolkits.axes_grid1 import host_subplot
    import matplotlib.animation as animation


    # Sent for figure
    font = {'size'   : 9}
    matplotlib.rc('font', **font)

    # Setup figure and subplots
    f0 = figure(num = 0, figsize = (6, 4))#, dpi = 100)
    f0.suptitle("Oscillation decay", fontsize=12)
    ax01 = subplot2grid((2, 2), (0, 0))

    # Set titles of subplots
    ax01.set_title('Position vs Time')

    # set y-limits
    ax01.set_ylim(0,2)

    # sex x-limits
    ax01.set_xlim(0,1)

    # Turn on grids
    ax01.grid(True)

    # set label names
    ax01.set_xlabel("x")
    ax01.set_ylabel("py")

    # Data Placeholders
    yp1=zeros(0)
    yv1=zeros(0)
    yp2=zeros(0)
    yv2=zeros(0)
    t=zeros(0)

    # set plots
    p011, = ax01.plot(t,yp1,'b-', label="yp1")
    p012, = ax01.plot(t,yp2,'g-', label="yp2")

    # set lagends
    ax01.legend([p011,p012], [p011.get_label(),p012.get_label()])


    # Data Update
    xmin = 0
    xmax = 24
    x = 0

    def updateData(self):
            global x
            global yp1
            global yv1
            global yp2
            global yv2
            global t

            tmpp1 = 1 + exp(-x) *sin(2 * pi * x)
            tmpv1 = - exp(-x) * sin(2 * pi * x) + exp(-x) * cos(2 * pi * x) * 2 * pi
            yp1=append(yp1,tmpp1)
            yv1=append(yv1,tmpv1)
            yp2=append(yp2,0.5*tmpp1)
            yv2=append(yv2,0.5*tmpv1)
            t=append(t,x)

            x += 1

            p011.set_data(t,yp1)
            p012.set_data(t,yp2)

            if x >= xmax-1:
                    p011.axes.set_xlim(x-xmax+1,x+1)

            return p011 

    # interval: draw new frame every 'interval' ms
    # frames: number of frames to draw
    simulation = animation.FuncAnimation(f0, updateData, blit=False, frames=200, interval=20, repeat=False)


    plt.show()

Solution

  • You are not incrementing the X axis limit or the ticks.

    def onPlotTemp(self,event):
        global fTemp, x, relhum
        x +=1
        y = int(fTemp)
        y2 = float(relhum)
        if x >= 24-1:
                self.ax.set_xlim(x-24+1,x+1)
                xmajor_ticks = np.arange(x-24+1,x+5, 5)
                xminor_ticks = np.arange(x-24+1, x+1,1)
                self.ax.set_xticks(xmajor_ticks)
                self.ax.set_xticks(xminor_ticks, minor=True)
    
        self.ax.plot(x,y,'r.')
        self.ax2.plot(x,y2,'k.')
    
        self.fig.canvas.draw()
    

    I'm not sure if the above resets the ticks the way you want them but you get the idea. Obviously I have hard-coded 24 as your limit, you may want to create a variable to sort that out.