Dynamic plots with matplotlib and wx on OS X

Hi,

Many thanks (again) to Matt for his suggestions - I had a "D'oh" moment
when it was pointed out where I was calling the update from.

Matt's solution works on OS X, but I'm afraid I didn't specify what my
goal was particularly well. I'm aiming to dynamically update a
matplotlib plot embedded within a wx.Panel (or wx.Frame) from another
object, whose internal state is changing so that the calling object's
internal state is reflected in the matplotlib plot. The wx.Timer
solution doesn't seem appropriate for that (though if I'm wrong, I'm
happy to take advice).

After some reading around, I worked up two alternatives for updating the
plot from another object.

1) wx.Timer, as per Matt
2) Loop, and pass the value of the loop counter
3) A custom wx event, bound to the update method

The code at http://widdowquinn.pwp.blueyonder.co.uk/wxtest.py implements
all three methods, and how effective they are appears to be OS-
dependent:

The wx.Timer method works on Windows (XP SP2), Linux (FC3) and OS X
(10.3)

The Loop method and custom event methods work fine on Windows. On
Linux, they dynamically update the plot, but not the button label. On
OS X, they do nothing, and only appear to update the plot after the
loops end.

Does anyone here have any ideas on how I could progress further on
getting this to run, and update correctly under OS X? Or should I go
and bug the wxPython-Users list about it? :wink:

For info, all three systems are using Python 2.3.5., matplotlib 0.73.1,
and wxPython 2.5.3.1.

Cheers,

···

--
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).

Hi Leighton,

The Loop method and custom event methods work fine on Windows.
On Linux, they dynamically update the plot, but not the button
label. On OS X, they do nothing, and only appear to update
the plot after the loops end.

I'm no expert in wxPython or matplotlib, but there are a few
possible problems with your script:
- For the timing method, I think the issue is simply how fast
   you can send Timer events. You're doing them every 20ms,
   which is pretty fast (I'm willing to take the blame for
   sending you an example that goes that fast!). If you change
   that to 200ms, I'll bet you will see more updates.

- The Loop method runs a for-loop which may not allow other wx
   events to be processed, such as changing a Button label.

- The Custom Event uses a python for-loop, so I think it's not
   very different from the Loop method in that it's liable to
   drop events.

For all these, I would think that adding
    self.Update()

should help. This is supposed to repaint the whole wxWidget.
That will take time, but should really update all widgets. But
it seems that this is not foolproof, and that it can lose
events: I don't know why this is.

Matt's solution works on OS X, but I'm afraid I didn't specify
what my goal was particularly well. I'm aiming to dynamically
update a matplotlib plot embedded within a wx.Panel (or
wx.Frame) from another object, whose internal state is
changing so that the calling object's internal state is
reflected in the matplotlib plot. The wx.Timer solution
doesn't seem appropriate for that (though if I'm wrong, I'm
happy to take advice).

After some reading around, I worked up two alternatives for
updating the plot from another object.

1) wx.Timer, as per Matt
2) Loop, and pass the value of the loop counter
3) A custom wx event, bound to the update method

I think you want to avoid the Loop method, as it can block other
widget events. I believe that what you want is to replot the
figure when (a) the data has changed and (b) some minimum time
has elapsed since the last drawing. One approach is to use a
timer to periodically check (this sets the minimun time) for new
data, and redraw the plot when there's new data.

Another approach is to trigger on changes in the data, possibly
saving the timestamp when a redraw is done so as to not draw too
often. Which method to use probably depends on how easily it is
to detect and act upon changes to the data. Finally, you may
also consider putting the data-intensive code in a separate
thread from the GUI.

But I think the main issue is to not try to redraw the plot 50
times per second. ;).

Hope that helps,

--Matt

Hi Matt,

I'm no expert in wxPython or matplotlib, but there are a few
possible problems with your script:
- For the timing method, I think the issue is simply how fast
   you can send Timer events. You're doing them every 20ms,
   which is pretty fast (I'm willing to take the blame for
   sending you an example that goes that fast!). If you change
   that to 200ms, I'll bet you will see more updates.

The timer was working pretty well on all three systems at 20ms intervals
- no blame to apportion there! :wink:

- The Loop method [...]
- The Custom Event [...]
For all these, I would think that adding
    self.Update()
should help.

I added self.Update() to each frame on the update event, and that fixed
my problem on OS X. I'm guessing that the wx.Timer events force an
update?

I think you want to avoid the Loop method, as it can block other
widget events. I believe that what you want is to replot the
figure when (a) the data has changed and (b) some minimum time
has elapsed since the last drawing. One approach is to use a
timer to periodically check (this sets the minimun time) for new
data, and redraw the plot when there's new data.

I'm less bothered about the minimum time, though I do like the idea of
setting up a timer in the monitor frame, and polling for a changed state
in the object it's monitoring, and might change part of my design to
implement that in the 'real' code.

Another approach is to trigger on changes in the data, possibly
saving the timestamp when a redraw is done so as to not draw too
often. Which method to use probably depends on how easily it is
to detect and act upon changes to the data.

This is what I'm doing in the full application (bar the timestamp) at
the moment - it's under a 'data-push' model at the moment.

Finally, you may also consider putting the data-intensive code in a separate
thread from the GUI.

Ah, threads... I'll stick to the self.Update() for now :wink:

But I think the main issue is to not try to redraw the plot 50
times per second. ;).

<grin> My algorithm coding isn't good enough for that to be an issue,
anyway. But I think the root of the cross-platform problem might lie
elsewhere, and the different steps I had to take on each OSs are
puzzling me. On Windows I didn't need to add self.Update() calls, as
everything worked first time. On Linux, the plot updated without resort
to self.Update(), but the buttons didn't - and adding the self.Update()
calls only caused the buttons to appear blank, rather than not
change...

On OS X, however, adding the self.Update() calls made it run exactly how
I wanted (and expected). I'm guessing that the wx.Timer events are
propagating Update() calls to both frames within wx, while the other two
methods aren't. Except in Windows. Hmm... more reading ahead for me.

Still, most importantly (for me) my problem is solved, and I've learnt
things about wx events, and cross-platform issues. Many thanks again
for your help, Matt - I'd have been floundering much longer without it.
Now I'll try to solve some of the non-matplotlib-related issues :wink:

Cheers,

···

On Thu, 2005-03-24 at 22:48 -0600, Matt Newville wrote:

--
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).