pathoren
pathoren

Reputation: 1666

Update wxpython figure canvas with new figure

In a wxPython application I have an embedded a FigureCanvas with a matplotlib figure. I want to be able to switch the figure by loading a new one. However, the figure is not being updated.

Answers to similar topics suggests that panel.canvas.draw() and panel.Refresh() should do the trick, but I've also tried panel.Update() and panel.canvas.Refresh(). I fear that this only works if you want to redraw the canvas with the same figure?

So my question is: how do you replace the figure inside a canvas and make it update?

Below is a small (non-working) example. First a figure is loaded with a single axis. If you from the embedded shell type panel.LoadFigure() a new figure with 2x2 subplots is created and put into the canvas. But the new figure is not shown.

import numpy as np
import wx
from wx.py.shell import Shell

from matplotlib.backends.backend_wxagg import FigureCanvasWxAgg as FigureCanvas
import matplotlib.pyplot as plt


class ShellPanel(wx.Panel):
    def __init__(self, parent):
        wx.Panel.__init__(self, parent)
        self.shell = Shell(self)
        self.sizer = wx.BoxSizer(wx.VERTICAL)
        self.sizer.Add(self.shell, 1, wx.GROW)
        self.SetSizer(self.sizer)
        self.Layout()
        self.Fit()


class FigurePanel(wx.Panel):
    def __init__(self, parent):

        wx.Panel.__init__(self, parent)
        self.parent = parent
        self.sizer = wx.BoxSizer(wx.VERTICAL)
        self.figure, ax = plt.subplots()
        self.canvas = FigureCanvas(self, -1, self.figure)
        self.shellpanel = ShellPanel(self)
        s1 = wx.BoxSizer(wx.VERTICAL)
        s1.Add(self.canvas, 0, wx.GROW)
        s1.Add(self.shellpanel, 1 , wx.EXPAND)
        self.sizer.Add(s1, 5, wx.GROW)

        self.SetSizer(self.sizer)
        self.Layout()
        self.Fit()

    def LoadFigure(self):
        self.figure, ax = plt.subplots(2, 2)
        self.canvas.draw()
        self.Refresh()


class FigureFrame(wx.Frame):
    def __init__(self, parent, id, title, size):
        wx.Frame.__init__(self, parent, id, title, size=size)

if __name__ == "__main__":
    app = wx.App(False)
    fr = FigureFrame(None, -1, title='Figure Loader', size=(wx.DisplaySize()[0]/2, wx.DisplaySize()[1]*3/4))
    panel = FigurePanel(fr)
    fr.Show()
    app.MainLoop()

Upvotes: 2

Views: 3082

Answers (2)

Yello Four
Yello Four

Reputation: 227

Try using the Figure itself to make your figure, not pyplot. It has usually worked for me because it gives you more room to improvise.

#!/usr/bin/python

# -*- coding: utf-8 -*-

import numpy as np
import wx
from wx.py.shell import Shell
from matplotlib.figure import Figure
from matplotlib.backends.backend_wxagg import FigureCanvasWxAgg as FigureCanvas
import matplotlib.pyplot as plt


class ShellPanel(wx.Panel):
    def __init__(self, parent):
        wx.Panel.__init__(self, parent)
        self.shell = Shell(self)
        self.sizer = wx.BoxSizer(wx.VERTICAL)
        self.sizer.Add(self.shell, 1, wx.GROW)
        self.SetSizer(self.sizer)
        self.Layout()
        self.Fit()


class FigurePanel(wx.Panel):
    def __init__(self, parent):

        wx.Panel.__init__(self, parent)
        self.parent = parent
        self.sizer = wx.BoxSizer(wx.VERTICAL)
        self.figure = Figure(figsize = (8,6.1), dpi =60)
        self.ax = self.figure.add_subplot(1,1,1)
        self.enlarged_figure = Figure(figsize = (8,6.1), dpi = 100)
        self.ax2 = self.enlarged_figure.add_subplot(2,1,2)
        self.canvas = FigureCanvas(self, -1, self.figure)
        self.canvas.Show()
        self.enlarged_canvas = FigureCanvas(self,-1,self.enlarged_figure)
        self.enlarged_canvas.Show()

        self.shellpanel = ShellPanel(self)
        s1 = wx.BoxSizer(wx.VERTICAL)
        s1.Add(self.canvas, 0, wx.GROW)
        s1.Add(self.shellpanel, 1 , wx.EXPAND)
        self.sizer.Add(s1, 5, wx.GROW)
        self.SetSizer(self.sizer)
        self.Layout()
        self.Fit()

    def LoadFigure(self):
        self.figure, ax = plt.subplots(2, 2)
        self.canvas.draw()
        self.Refresh()


class FigureFrame(wx.Frame):
    def __init__(self, parent, id, title, size):
        wx.Frame.__init__(self, parent, id, title, size=size)

if __name__ == "__main__":
    app = wx.App(False)
    fr = FigureFrame(None, -1, title='Figure Loader', size=(wx.DisplaySize()[0]/2, wx.DisplaySize()[1]*3/4))
    panel = FigurePanel(fr)
    fr.Show()
    app.MainLoop()

In the shell type in 'panel.LoadFigure()' and the new figure will show up very quickly and then disappear. Then type 'panel.canvas.draw()' and the new figure will be there. Then type 'panel.LoadFigure()' to load the old figure back. Repeat.

I don't know why the figure remains hidden after the MainLoop() continues but this is sort of a quick fix to your problem.

Upvotes: 0

Diziet Asahi
Diziet Asahi

Reputation: 40747

Is there any reason you are trying to create a new Figure and not simply new Axes on the already defined Figure?

I would write your code like so:

def LoadFigure(self):
    self.figure.clf()  # clear current figure
    # add arbitrary number of new Axes
    self.figure.add_subplot(221)
    self.figure.add_subplot(222)
    self.figure.add_subplot(223)
    self.figure.add_subplot(224)
    self.canvas.draw()  # refresh canvas

EDIT: following your comment, I think the problem is that you're creating a new figure, but your Canvas is still referencing the old one. I don't know if you can change that directly in the FigureCanvas properties, maybe someone with more experience can provide a better answer. For the moment, I would Destroy the previous canvas, and create a new FigureCanvas object with your new figure.

def LoadFigure(self):
    self.figure, ax = plt.subplots(2, 2)
    self.canvas.Destroy()
    self.canvas = FigureCanvas(self, -1, self.figure)
    self.canvas.draw()

Upvotes: 1

Related Questions