Dynamic plots with matplotlib and wx on OS X

Hi,

I'm having difficulty producing dynamic plots on OS X using matplotlib
0.73.1 and wxPython 2.5.3.1 on OS X 10.3 with Python 2.3.5

The code below works with the same configuration on Fedora Core 3,
producing a graph with a line that pivots at the origin. On OS X,
however, the frame remains blank and grey until the final plot is drawn,
when the plot takes on the correct final state.

I've tried similar examples with bar graphs and scatter plots, and the
same results are produced. I don't know if this is a problem with
matplotlib or wxPython, and any help would be gratefully appreciated.

If you need any more info to help, let me know.

The example code is:

···

----------------

import matplotlib
matplotlib.use("WXAgg")
matplotlib.interactive(True)
from matplotlib.backends.backend_wx import FigureCanvasWx
from matplotlib.figure import Figure
from matplotlib.axes import Subplot

from Numeric import pi, sin, arange

import wx

class PlotFigure(wx.Frame):
    def __init__(self):
        wx.Frame.__init__(self, None, -1, "Test embedded wxFigure")

        self.fig = Figure((5,4), 75)
        self.canvas = FigureCanvasWx(self, -1, self.fig)

        sizer = wx.BoxSizer(wx.VERTICAL)
        sizer.Add(self.canvas, 1, wx.LEFT|wx.TOP|wx.GROW)
        self.SetSizer(sizer)
        self.Fit()

    def init_plot_data(self):
        self.a = self.fig.add_subplot(111)
        x = arange(0,2*pi,0.01) # x-array
        self.a.set_xlim((0., 2*pi))
        self.a.set_ylim((-1., 1.))
        self.line, = self.a.plot(x,x)

    def update_data(self, i):
        self.line.set_ydata(self.line.get_xdata()*0.01*i)
        self.canvas.draw()

if __name__ == '__main__':
    app = wx.PySimpleApp(0)
    frame = PlotFigure()
    frame.Show(True)
    frame.init_plot_data()
    for i in arange(1,200):
        frame.update_data(i)
    app.MainLoop()

--
Dr Leighton Pritchard AMRSC
D131, Plant-Pathogen Interactions, Scottish Crop Research Institute
Invergowrie, Dundee, Scotland, DD2 5DA, UK
T: +44 (0)1382 562731 x2405 F: +44 (0)1382 568578
E: lpritc@...539... W: http://bioinf.scri.sari.ac.uk/lp
GPG/PGP: FEFC205C E58BA41B http://www.keyserver.net
(If the signature does not verify, please remove the SCRI disclaimer)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

DISCLAIMER:

This email is from the Scottish Crop Research Institute, but the views
expressed by the sender are not necessarily the views of SCRI and its
subsidiaries. This email and any files transmitted with it are confidential
to the intended recipient at the e-mail address to which it has been
addressed. It may not be disclosed or used by any other than that addressee.
If you are not the intended recipient you are requested to preserve this
confidentiality and you must not use, disclose, copy, print or rely on this
e-mail in any way. Please notify postmaster@...539... quoting the
name of the sender and delete the email from your system.

Although SCRI has taken reasonable precautions to ensure no viruses are
present in this email, neither the Institute nor the sender accepts any
responsibility for any viruses, and it is your responsibility to scan the email
and the attachments (if any).

Leighton,

This also does not work Win XP.... The issue is that you're
trying to update the data *before* running the MainLoop(). The
puzzle should be why it works on Linux / GTK ;).

There are a few approaches to the better solution, all of which
lead to slightly more complex than your example. One such
solution is below (and attached), using Start and Stop buttons,
and a timer to periodically update the plot. I tried to add as
little code to yours as possible, but also changed from
backend_wx to backend_wxagg, because it's significantly faster
and seemed to be your intent. I'm not sure the logic in
update_data is really what you want, BTW.

You might find the example in MPlot-0.7/examples/test.py at
   http://cars9.uchicago.edu/~newville/Python/MPlot

helpful. This is a more complete example of dynamically
updating a WXAgg canvas. It needs better documentation, but
seems pretty close to what you're trying to do.

Hope that helps,

--Matt Newville

import matplotlib
matplotlib.use("WXAgg")
matplotlib.interactive(True)
from matplotlib.backends.backend_wxagg import FigureCanvasWxAgg
from matplotlib.figure import Figure
from matplotlib.axes import Subplot

from Numeric import pi, sin, arange

import wx

class PlotFigure(wx.Frame):
    def __init__(self):
        wx.Frame.__init__(self, None, -1, "Test embedded wxFigure")

        self.fig = Figure((5,4), 75)
        self.canvas = FigureCanvasWxAgg(self, -1, self.fig)

        sizer = wx.BoxSizer(wx.VERTICAL)
        sizer.Add(self.canvas, 1, wx.TOP)

        # create start/stop buttons
        self.button_start = wx.Button(self,-1, " Start ", size=(-1,-1))
        self.button_stop = wx.Button(self,-1, " Stop ", size=(-1,-1))

        # bind actions to the buttons
        self.button_start.Bind(wx.EVT_BUTTON,self.OnStart)
        self.button_stop.Bind(wx.EVT_BUTTON, self.OnStop)

        # pack the buttons in the Sizer
        btnsizer = wx.BoxSizer(wx.HORIZONTAL)
        btnsizer.Add(self.button_start, 1, wx.LEFT)
        btnsizer.Add(self.button_stop, 1, wx.LEFT)

        sizer.Add(btnsizer, 0, wx.TOP)
        self.SetSizer(sizer)
        self.Fit()

        # initialize the data here
        self.init_plot_data()

        # create a timer, used in OnStart / OnStop
        ID_TIMER = wx.NewId()
        wx.EVT_TIMER(self, ID_TIMER, self.onTimer)
        self.timer = wx.Timer(self, ID_TIMER)

    def init_plot_data(self):
        self.a = self.fig.add_subplot(111)
        x = arange(0,2*pi,0.01) # x-array
        self.a.set_xlim((0., 2*pi))
        self.a.set_ylim((-1., 1.))
        self.line, = self.a.plot(x,x)
        self.npts = len(x)
        
    def update_data(self,i):
        self.line.set_ydata(self.line.get_xdata()*0.01*i)
        self.canvas.draw()

    def onTimer(self, event):
        self.count += 1
        if self.count > self.npts:
            self.OnStop()
        else:
            self.update_data(self.count)
            
    def OnStop(self,event=None):
        self.timer.Stop()
        
    def OnStart(self,event=None):
        self.count = 0
        self.timer.Start(20) # delay in milliseconds

if __name__ == '__main__':
    app = wx.PySimpleApp(0)
    frame = PlotFigure()
    frame.Show(True)
    app.MainLoop()

mpl_wx01.py (2.32 KB)