MPLCanvas and Figure.canvas.draw()

691 views
Skip to first unread message

Jeff Price

unread,
Jan 14, 2014, 2:06:41 PM1/14/14
to en...@googlegroups.com
I'm updating an existing enaml application I have written against 0.6.8. I'm using an `MPLCanvas` whose figure is bound to a `matplotlib.figure.Figure` in my controller. The data being displayed is image data and I'm using `imshow`. When I change the backing data in the controller, I've been creating a new `Figure()` object and refreshing the view as follows:

        self.figure = Figure()
        ax = self.figure.add_subplot(111)
        ax.imshow(self.img)
        self.figure.canvas.draw() # this fails w 0.8.9 because self.figure.canvas is None

I found that I needed to call `self.figure.canvas.draw()` else the view wouldn't reflect the newly loaded figure/image. After updating to enaml 0.8.9, this no longer works as `self.figure.canvas` is `None`, even though the initially loaded image is displaying fine. The `MPLCanvas` is never redrawn for me.


1. Why is `self.figure.canvas` now `None`?
2. How can I force the MPLCanvas to be redrawn when the controller changes the backing data?
--or--
3. What am I doing wrong in general and what should I be doing instead?

Thanks!

Chris Colbert

unread,
Jan 14, 2014, 2:12:43 PM1/14/14
to en...@googlegroups.com
Take a look at this example for how to update a figure (you basically just assign a new Figure to the 'figure' attribute):

Marty Lichtman

unread,
Jan 14, 2014, 3:42:05 PM1/14/14
to en...@googlegroups.com
I have a solution that I used in 0.6.8, and should work in 0.8.9, which you might like.  This is my first post so I hope it makes some sense.  I wanted to be able to update the canvas without the overhead of creating a new Figure.  I edited the qt_mpl_canvas.py file to allow me to refresh the canvas at will.  Here are the necessary pieces:

In <home>\AppData\Local\Enthought\Canopy\User\Lib\site-packages\enaml\qt\qt_mpl_canvas.py add this function to the QtMPLCanvas class:

    def on_action_set_refresh(self, content):
        self.refresh_mpl_widget()

Now in your .enaml file, create this class definition:

class RefreshableMPLCanvas(MPLCanvas):

    #: Toggle this to refresh the canvas
    refresh = Bool(False)

    def snapshot(self):
        """ Get the snapshot dict for the MPLCanvas."""
        snap = super(RefreshableMPLCanvas, self).snapshot()
        snap['refresh'] = self.refresh
        return snap

    def bind(self):
        """ Bind the change handlers for the MPLCanvas"""
        super(RefreshableMPLCanvas, self).bind()
        self.publish_attributes('refresh')

Now when you create your MPL canvas in the .enaml file, link to a Figure as usual (in this case waveform.Figure), but also subscribe to a boolean (in this case waveform.refresh):

            RefreshableMPLCanvas: canvas:
                figure = waveform.figure
                refresh<<waveform.refresh

When you want to refresh the canvas now (where you were doing self.figure.canvas.draw() before) just do:

    refresh=not refresh

This will trigger a change event on the boolean and update the canvas.

I suspect there is probably a cleaner way to do this using explicit events, rather than flipping a boolean like I am, but this works for me.

Chris Colbert

unread,
Jan 14, 2014, 4:20:16 PM1/14/14
to en...@googlegroups.com
feel free to open a feature request on Github for a manual refresh method on MPLCanvas.

Jeff Price

unread,
Jan 14, 2014, 7:31:46 PM1/14/14
to en...@googlegroups.com
Chris,
Confused a bit by the suggestion - I am setting the figure attribute to a new Figure() each time. That's the first line that's repeated on each update to the backing data. Do I need to create the new Figure() without assigning it to the attribute, do all the drawing, then set `self.figure`? (I'm dying to check right now but won't have access to the data I need again until tomorrow morning.)

Chris Colbert

unread,
Jan 14, 2014, 8:32:50 PM1/14/14
to en...@googlegroups.com
Jeff,

Could you please post a small but complete example of what you think should be working, but isn't?

The example I linked you to above is evidence that simply changing the Figure is sufficient to have the canvas update.

Cheers,

Chris

Jeff Price

unread,
Jan 15, 2014, 5:49:55 PM1/15/14
to en...@googlegroups.com
Chris-

I've attached a few files and demonstrated to myself that setting the figure property before drawing on it prevents the MPLCanvas from updating. If, however, I create a local Figure(), draw on it, then set the figure property, the MPLCanvas updates as expected. I've attached sample code that demonstrates this point. In particular in my `Controller` `update_figure` method.

test.py
view.enaml
1.jpg
2.jpg

Chris Colbert

unread,
Jan 15, 2014, 8:06:09 PM1/15/14
to en...@googlegroups.com
Jeff, what you posted is expected behavior according to the current public API. 

Enaml has no way of knowing that you've updated the figure in-place after you assign it to the 'figure' attribute. So, you can either do all the work to the new Figure first, then assign it to the 'figure' attribute, or wait for me to implement the feature improvement requested here, which will allow you to manually request a redraw.

Cheers,

Chris

Jeff Price

unread,
Jan 16, 2014, 7:54:59 AM1/16/14
to en...@googlegroups.com
Chris-

Thanks a bunch for the clarification. I have no problem doing all the drawing first then setting the figure attribute (makes more sense anyway). I'm guessing it was like that with 0.6.8 but I was hiding that fact from myself by calling `figure.canvas.draw()` which (for some reason I still don't understand) is no longer available.

-Jeff

Chris Colbert

unread,
Jan 17, 2014, 9:37:42 AM1/17/14
to en...@googlegroups.com
Not sure why it would be different either. The code for between 0.6.8 and 0.8.9 concerning creating the canvas is nearly identical.

Jeff Price

unread,
Jan 20, 2014, 1:09:57 PM1/20/14
to en...@googlegroups.com
I think I've discovered (okay, maybe guessing a bit) that it's a threading issue with `figure.canvas.draw()`.

It's only the very first time I call `canvas.draw()` that `canvas` is `None`. Subsequently, it's valid. I think I am asking for the `canvas` in my controller before the run-loop has a chance to set it. If I just verify `canvas is not None` then I can force `MPLCanvas` redraws using `figure.canvas.draw()`. I'm not saying it's appropriate, but for me it's better than creating an entirely new figure just to change the axis limits.

Chris Colbert

unread,
Jan 20, 2014, 1:19:14 PM1/20/14
to en...@googlegroups.com
are you using threads in your app? If not, it's not a threading issue.

It may, however, be a lifecycle issue. The backend will not be setup until you call .show() on the main window. In that case, you will indeed find a bunch of backend pieces uninitialized.

Jeff Price

unread,
Jan 20, 2014, 1:45:04 PM1/20/14
to en...@googlegroups.com
Not explicitly using any threads, I was just mistakenly thinking about implicit UI-based run loop stuff (from all of my Cocoa bugs by accidentally manipulating UI elements off main).

.show() is definitely being called on the main window then .start() on the QtApplication() instance. Maybe it is still a lifecycle setup/speed thing?

Chris Colbert

unread,
Jan 20, 2014, 1:48:04 PM1/20/14
to en...@googlegroups.com
If you aren't explicitly starting a thread yourself, then there is only one thread in the process, the main thread, and everything runs on that thread. So it has nothing to do with setup "speed", since nothing is executing in parallel.

Please post a *complete* example of code which doesn't work.

Marty Lichtman

unread,
Jan 21, 2014, 1:54:54 PM1/21/14
to en...@googlegroups.com
I have actually been working on an MPL update problem in code that needs to use threads.  I need separate threads because I spawn some minutes-long activities during which I need the GUI to stay responsive.  The GUI is launched from the main thread, but then I spawn these other activities from the GUI (via a menu action).  The other activity occasionally needs to update an MPL figure, but the actual redrawing needs to be done from within the main/GUI thread, otherwise Qt gets angry.  The solution that I had posted above previously turns out does not work with 0.8.9, but here is what I came up with that does.

I have 2 Figures, and I swap back and forth to trigger the redraw.  (I use a blank and a real figure, and swap twice.  It would perhaps be even more clever to have two real figures, and so only swap once per redraw.)  But although this is commanded from the new thread, it needs to happen in the main/GUI thread, and I use signaling to achieve that.

Some understand of how to work with threads taken from here and Steve Silvester's example here.

I have attached a working simplified example.  "python main.py" to run.
main.py
model.py
view.enaml

Chris Colbert

unread,
Jan 22, 2014, 6:38:49 PM1/22/14
to en...@googlegroups.com
There's no need to use QThread. There's also no need to overcomplicate things. There is 1 rule which you must follow: do not touch the UI from anything but the UI thread. This includes updating models which are bound to the UI, since operators and observers run on the thread from which they are triggered.

Enaml provides three thread-safe facilities to schedule work on the ui thread: deferred_call, timed_call, and schedule. For simple message posting, deferred_call is all you need. Here's an example of how simple it can be.


Don't make your life unnecessarily complex:
  1. Determine what work can be done simply and with minimal immutable dependencies.
  2. Create a function which takes the dependency as arguments along with the output model.
  3. Spawn a daemon thread to do the work.
  4. Compute the output value in the daemon thread.
  5. Use deferred call to post the results to the model on the UI thread.
  6. ???
  7. Profit.
When it comes to threads, sharing state is the devil. The functional programming crowd got at least one thing right...

Marty Lichtman

unread,
Jan 23, 2014, 12:25:49 PM1/23/14
to en...@googlegroups.com
Hi Chris, most of my code functionality takes place in a non-GUI thread, so if I understand you correctly, I should be doing every single update to the model using a deferred_call.  (Up until now I was only doing MPLCanvas updates in the GUI thread, because they are the only ones that complained.)  This feels somewhat unnatural.  It makes sense to me to have model-controller interactions do their thing, regardless of whether or not the GUI even exists.  But since the model is Enaml-bound to the view, any update to the model would need to be done using deferred_call to make sure it happens in the GUI thread.  That makes the rest of the code very dependent on Enaml.

There is also the possibility of code being extended, and the coder not being aware that a model element is Enaml-bound, and so mistakenly does an update from a non-GUI thread.

Perhaps I could make all the model Members to be Properties, so that any update to their state makes a deferred_call in the set method.

What about changing Enaml so that all updates to Enaml bindings automatically get made in the GUI thread?  This would simplify usage of Enaml: If there is only one thread you are already in that thread, but if there are multiple threads then you wouldn't need to change the model-controller interaction to accommodate the GUI.

(I don't mean to sound demanding, this is just how it seems it would work best for my usage case.)

Chris Colbert

unread,
Jan 25, 2014, 2:13:19 PM1/25/14
to en...@googlegroups.com
Marty, 

This is the nature of GUI programming when using threads, it is necessarily more complicated than a single threaded application. If you find yourself having to do many deferred calls to update your UI models, then your code is not factored well. Your worker threads should compute all needed data at once, and then post a large update to the UI thread. This can be as simple as collecting all update values into a dict, then applying those values to the UI model from the GUI thread. There is no requirement that your UI model be the same object as your data model...

Updating the Enaml bindings to handle this automatically is a non-starter for several reasons:
  1. It incurs additional overhead for all users, if when most use-cases (single threaded) don't need it.
  2. Serially updates vs deferred updates have different behavior with respect to the call-site. Enaml should not transparently hide this from the developer.
  3. Posting a single large update is more efficient than several small posts. Enaml would be forced to do the latter, since developer understanding of the problem semantics is required for the former.
There is no free lunch. Writing threaded UI apps is an advanced topic; you have to do more work, and you have to factor your code correctly.

Marty Lichtman

unread,
Jan 26, 2014, 3:07:45 PM1/26/14
to en...@googlegroups.com
Thanks for the tips Chris.  I have been considering splitting making separate UI and backend models, and may go that route.
Reply all
Reply to author
Forward
0 new messages