Closing wxpython panel with Seaborn plot embedded - python

I have a seaborn plot embbeded in a wxPython panel, like this:
This is the code I made to accomplish this:
import numpy as np
import seaborn as sns
from matplotlib.backends.backend_wxagg import FigureCanvasWxAgg as FigureCanvas
from matplotlib.figure import Figure
import wx
class SimplePanel(wx.Panel):
def __init__(self, parent):
wx.Panel.__init__(self, parent)
sns.set(style="whitegrid", palette="pastel", color_codes=True)
self.figure = Figure()
self.ax = self.figure.add_subplot(111)
self.planets = sns.load_dataset("planets")
self.years = np.arange(2010, 2014)
sns.factorplot(x="year", ax= self.ax,data=self.planets, kind="count",palette="BuPu", size=6, aspect=1.5, order=self.years)
sns.despine(left=True)
self.canvas = FigureCanvas(self, -1, self.figure)
self.sizer = wx.BoxSizer(wx.VERTICAL)
self.sizer.Add(self.canvas, 1, wx.LEFT | wx.TOP | wx.GROW)
self.SetSizer(self.sizer)
self.Fit()
if __name__ == "__main__":
app = wx.App(False)
fr = wx.Frame(None, title='test', size=(800,600))
panel = SimplePanel(fr)
fr.Show()
app.MainLoop()
Problem: it works fine, except when I close the window the program doesn't terminate. I think it has to do with the seaborn plot because I've run the program without it and it closes normally. But I don't know how to fix it. I've tried also to had a close functionality to the window (below self.Fit() line) like this:
self.Bind(wx.EVT_CLOSE, self.OnCloseWindow)
def OnCloseWindow(self,event):
self.Destroy()
But it doesn't work either.
Any suggestions?

Erase the seaborn plot object before destroying the window.

Related

How can I maximize my figure on matplotlib python using macOS?

fig, ax = plt.subplots(figsize=(16,8),dpi=100,subplot_kwn {'projection':nccrs.PlateCarree()})
ax.set_global()
plt.subplots_adjust(left=0.04, bottom=0.02, right=0.96, top=0.96)
# set a figure window's title
fig2 = plt.gcf()
fig2.canvas.set_window_title('Metheoros 1.0')
mng = plt.get_current_fig_manager()
mng.Maximize(True)
I Tried this, but didn't work on mac
The first line should have an equals sign in place of the n in subplot_kwn:
fig, ax = plt.subplots(figsize=(16,8),dpi=100,subplot_kw= {'projection':ccrs.PlateCarree()})
You might want to check what you have imported cartopy.crs as, because that may cause problems as well.
EDIT:
So I did quite a bit of digging, and found that in mng has a method called 'full_screen_toggle', so in theory, you could call mng.full_screen_toggle() followed by mng.show(). I tried that, but that seemed to have no effect. I dug through the source code and found that the Mac OS X backend does not have a fullscreen function implemented (as far as I can tell).
That means that you'll have to use a different backend. You can change backends by calling plt.switch_backend('backend') where backend is your desired backend. This function accepts the following arguments:
'pdf', 'pgf', 'Qt4Agg', 'GTK', 'GTKAgg', 'ps', 'agg', 'cairo', 'MacOSX', 'GTKCairo', 'WXAgg', 'template', 'TkAgg', 'GTK3Cairo', 'GTK3Agg', 'svg', 'WebAgg', 'CocoaAgg', 'emf', 'gdk', 'WX'
# -*- coding: UTF-8 -*-
'''
Created on 4 de set de 2016
#author: VladimirCostadeAlencar
'''
from numpy import arange, sin, pi
import matplotlib
matplotlib.use('WXAgg')
from matplotlib.backends.backend_wxagg import FigureCanvasWxAgg as FigureCanvas
from matplotlib.backends.backend_wx import NavigationToolbar2Wx
from matplotlib.figure import Figure
import wx
class CanvasPanel(wx.Panel):
def __init__(self, parent):
wx.Panel.__init__(self, parent)
self.figure = Figure()
self.axes = self.figure.add_subplot(111)
self.canvas = FigureCanvas(self, 0, self.figure)
self.sizer = wx.BoxSizer(wx.VERTICAL)
self.sizer.Add(self.canvas, 1, wx.LEFT | wx.TOP | wx.GROW)
self.SetSizer(self.sizer)
self.Fit()
def draw(self):
from ler_csv import ler_csv
from plotar_csv04 import plotar_pontos
nomearq = 'gps01.csv'
print 'Reading points...'
coords = ler_csv(nomearq)
figure, ax = plotar_pontos(self, coords)
print 'Plotting on Wx...'
self.canvas = FigureCanvas(self, 0, figure)
if __name__ == "__main__":
app = wx.PySimpleApp()
fr = wx.Frame(None, title='Metheoros v1.0 - 2016')
panel = CanvasPanel(fr)
panel.draw()
fr.Maximize(True)
fr.Show()
app.MainLoop()

Seaborn disable plot

I have a seaborn barplot embedded in a WxPython panel, like this:
The bar plot is drawn when the (big) button is clicked. This is what I made to accomplish it:
class SamplePanel(wx.Panel):
def __init__(self, parent):
wx.Panel.__init__(self, parent)
self.figure = Figure()
self.ax = self.figure.add_subplot(111)
self.x = np.array(list('XYZV'))
self.y = np.array([200,400,300,20])
self.ax.set_ylabel("Sample numbers")
self.canvas = FigureCanvas(self, -1, self.figure)
self.button = wx.Button(self, label="Plot data", pos=(100,15))
self.button.Bind(wx.EVT_BUTTON, self.OnButtonClick)
self.sizer = wx.BoxSizer(wx.VERTICAL)
self.sizer.Add(self.canvas, 1, wx.LEFT | wx.TOP | wx.GROW)
self.sizer.Add(self.button, 1, wx.LEFT | wx.TOP | wx.GROW)
self.SetSizer(self.sizer)
self.Fit()
def OnButtonClick(self,event):
sns.barplot(self.x, self.y, palette="BuGn_d", ax=self.ax)
if __name__ == "__main__":
app = wx.PySimpleApp()
frame = wx.Frame(None, title='Sample bar plot')
panel = SamplePanel(frame)
frame.Show()
app.MainLoop()
I have two questions:
How can I disable/draw the plot when I click the button? That is, if I click the button then the plot appears. If I click again the plot disappears and I get back to an empty original view, like this:
Also, the plot only changes when I maximize the window, how can I change it to be immediate, as soon as I click the button?
Any suggestions? Thanks in advance
It would have helped a lot if you had posted a small runnable example. Fortunately, Google helped me figure out what all was needed. Basically you need to set some kind of variable to keep track of if you've clicked the button or not. Or you could use a wx.ToggleButton instead of a regular wx.Button.
To get the graph to display without resizing the frame, you just need to call self.Layout().
To clear the figure, you'll need to do something like self.ax.cla() or self.ax.clear(). Here's a full example that worked for me:
import numpy as np
import seaborn as sns
import wx
from matplotlib.backends.backend_wxagg import FigureCanvasWxAgg as FigureCanvas
from matplotlib.figure import Figure
class SamplePanel(wx.Panel):
def __init__(self, parent):
wx.Panel.__init__(self, parent)
self.toggled = False
self.figure = Figure()
self.ax = self.figure.add_subplot(111)
self.x = np.array(list('XYZV'))
self.y = np.array([200,400,300,20])
self.ax.set_ylabel("Sample numbers")
self.canvas = FigureCanvas(self, -1, self.figure)
self.button = wx.Button(self, label="Plot data", pos=(100,15))
self.button.Bind(wx.EVT_BUTTON, self.OnButtonClick)
self.sizer = wx.BoxSizer(wx.VERTICAL)
self.sizer.Add(self.canvas, 1, wx.LEFT | wx.TOP | wx.GROW)
self.sizer.Add(self.button, 1, wx.LEFT | wx.TOP | wx.GROW)
self.SetSizer(self.sizer)
self.Fit()
def OnButtonClick(self, event):
if not self.toggled:
sns.barplot(self.x, self.y, palette="BuGn_d", ax=self.ax)
self.toggled = True
else:
self.ax.cla()
self.toggled = False
self.Layout()
if __name__ == "__main__":
app = wx.App(False)
frame = wx.Frame(None, title='Sample bar plot', size=(800,600))
panel = SamplePanel(frame)
frame.Show()
app.MainLoop()
Also note that wx.PySimpleApp is deprecated. I swapped it out for the recommended method of creating an app object.

embedding matplotlib figure and change its size

I had the same problem like here.
It works, but this figure shall not cover the whole frame; so I changed size and position of his panel. Problem now is, that my figure still has the same size of my frame and therefore I only changed the panelsize, my figure gets "cut". How can I solve this?
Example for better understanding: My Frame is 800x600 and my figure shall only have the size 400x300 inside this frame.
edit:
import wx
from numpy import arange, sin, pi
import matplotlib
#matplotlib.use('WXAgg')
from matplotlib.backends.backend_wxagg import FigureCanvasWxAgg as FigureCanvas
from matplotlib.backends.backend_wx import NavigationToolbar2Wx
from matplotlib.figure import Figure
class MainFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, wx.ID_ANY,
"Pyramid App",size=(800,600),pos=((wx.DisplaySize()[0]-800)/2,(wx.DisplaySize()[1]-600)/2),style= wx.SYSTEM_MENU | wx.CAPTION | wx.MINIMIZE_BOX | wx.CLOSE_BOX)
self.PageThree = pageThree(self)
class pageThree(wx.Panel):
def __init__(self, parent):
wx.Panel.__init__(self, parent=parent,size=(800,525))
self.myparent=parent
self.pageThree=wx.Panel(self,size=(800,525)) # put some elements at this panel
self.pylabfigure = wx.Panel(self,size=(440,420),pos=(350,105)) # this is the panel for my figure
self.drawPylabFigure()
self.draw()
def drawPylabFigure(self):
self.figure = Figure()
self.axes = self.figure.add_subplot(111)
self.canvas = FigureCanvas(self.pylabfigure, -1, self.figure)
self.sizer = wx.BoxSizer(wx.VERTICAL)
self.sizer.Add(self.canvas, 1, wx.LEFT | wx.TOP | wx.GROW)
self.SetSizer(self.sizer)
self.Fit()
def draw(self):
#example drawing
t = arange(0.0, 3.0, 0.01)
s = sin(2 * pi * t)
self.axes.plot(t, s)
if __name__ == "__main__":
app = wx.App()
MainFrame().Show()
app.MainLoop()
This will give you an idea of how to do it.
Main thing to have into account it to put your panels/canvas organized by one sizer.
Also, if the canvas has to fill his side there is not need to put it in a panel.
import wx
from numpy import arange, sin, pi
import matplotlib
from matplotlib.backends.backend_wxagg import FigureCanvasWxAgg as FCW
from matplotlib.figure import Figure
class MainFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, wx.ID_ANY,
"Pyramid App",size=(800,600),
pos=((wx.DisplaySize()[0]-800)/2,(wx.DisplaySize()[1]-600)/2),
style=wx.SYSTEM_MENU | wx.CAPTION | wx.MINIMIZE_BOX | wx.CLOSE_BOX)
self.PageThree = pageThree(self)
class pageThree(wx.Panel):
def __init__(self, parent):
wx.Panel.__init__(self, parent=parent,size=(800,525))
self.myparent=parent
self.pageThree=wx.Panel(self, size=(500,525))
self.drawPyFigure()
self.draw()
def drawPyFigure(self):
self.figure = Figure()
self.axes = self.figure.add_subplot(111)
self.canvas = FCW(self, -1, self.figure)
sizer = wx.BoxSizer(wx.HORIZONTAL)
sizer.Add(self.pageThree, 0)
sizer.Add(self.canvas, 1, wx.EXPAND)
self.SetSizer(sizer)
self.Fit()
def draw(self):
t = arange(0.0, 3.0, 0.01)
s = sin(2 * pi * t)
self.axes.plot(t, s)
if __name__ == "__main__":
app = wx.App()
MainFrame().Show()
app.MainLoop()
In a nutshell, you want self.figure = Figure(figsize=(400 / 80.0, 300 / 80.0)).
You need to specify the size of the figure (by default 8 inches wide by 6 inches tall) and/or the dpi of the figure (by default, 80 for on-screen display).
However, be aware that the size of the figure on-screen depends purely on the total number of pixels (in other words, width_inches*dpi x height_inches*dpi). For that reason, if we want a figure to be a specific number of pixels, we need to convert that to "inches" (which have nothing to do with the figure's display size if it's displayed on-screen) by dividing the number of pixels by the figure's dpi.

How to redraw a mathplotlib figure in a wxpython panel?

I want to draw a new figure on a each draw() operation. I pieced together code for drawing a static figure which is never updated after the object is created. But I want to be able to redraw when presented with new data.
How do I structure my code to do redrawable figures?
Here is the code in question, that draws exactly once:
from numpy import arange, sin, pi
import matplotlib
matplotlib.use('WXAgg')
from matplotlib.backends.backend_wxagg import FigureCanvasWxAgg as FigureCanvas
from matplotlib.backends.backend_wx import NavigationToolbar2Wx
class CanvasPanel(wx.Panel):
def __init__(self, parent):
wx.Panel.__init__(self, parent)
#self.size = (800, 50)
self.figure = Figure()
self.figure.set_size_inches( (8,1) )
self.figure.set_dpi(80)
#self.axes = self.figure.add_subplot(111)
self.canvas = FigureCanvas(self, -1, self.figure )
self.sizer = wx.BoxSizer(wx.VERTICAL)
self.sizer.Add(self.canvas, 1, wx.LEFT | wx.TOP | wx.GROW)
self.SetSizer(self.sizer)
self.Fit()
def draw(self):
self.axes = self.figure.add_subplot(111)
t = arange(0.0, 3.0, 0.01)
s = sin(2 * pi * t)
self.axes.plot(t, s)
#time.sleep(5)
#self.figure.clear()
As #acattle suggested in his comment, all you have to do is add these lines to your drawing subroutine, after you update your plot:
self.canvas.draw()
self.canvas.Refresh()

Python/Matplotlib - Quickly Updating Text on Axes

I have a matplotlib figure/canvas in a wxpython window. I want to update some information on the plot as the mouse moves around. I've connected to 'motion_notify_event' to get this information.
In the code below, a lot of random data is plotted and then the x,y location of the cursor is displayed in the statusbar of the window. This is very smooth and works well. However, I really want to display this information at the top of the plot. The behavior I want is shown if you uncomment the last two lines of cbUpdateCursor. However, when this is done, the response time to moving the cursor is terribly slow (because draw gets called and there is a lot of data, but draw must be called or the text doesn't get updated).
How can I speed this up so the cursor position can be displayed on the plot, but not slow it down so much? I think I might need to do something with bbox?
Code:
import wx
import numpy as np
import matplotlib
matplotlib.use('WXAgg')
from matplotlib.figure import Figure
from matplotlib.widgets import Cursor
from matplotlib.backends.backend_wxagg import \
FigureCanvasWxAgg as FigCanvas, \
NavigationToolbar2WxAgg as NavigationToolbar
class wxPlotting(wx.Frame):
title = 'Test'
def __init__(self):
wx.Frame.__init__(self, None, -1, self.title)
self.time = np.arange(10000)
self.data = np.random.random(10000)
self.sb = self.CreateStatusBar()
self.create_main_panel()
self.axes.plot(self.time, self.data)
self.canvas.draw()
def create_main_panel(self):
self.panel = wx.Panel(self)
self.fig = Figure((5.0, 4.0), dpi=100)
self.canvas = FigCanvas(self.panel, -1, self.fig)
self.axes = self.fig.add_subplot(111)
self.text = self.axes.text(0., 1.005, '', transform = self.axes.transAxes)
self.cursor = Cursor(self.axes, useblit=True, color='red')
self.canvas.mpl_connect('motion_notify_event', self.cbUpdateCursor)
self.vbox = wx.BoxSizer(wx.VERTICAL)
self.vbox.Add(self.canvas, 1, wx.LEFT | wx.TOP | wx.GROW)
self.panel.SetSizer(self.vbox)
self.vbox.Fit(self)
def cbUpdateCursor(self, event):
if event.inaxes:
text = 'x = %5.4f, y = %5.4f' % (event.xdata, event.ydata)
self.sb.SetStatusText(text)
#self.text.set_text(text)
#self.canvas.draw()
if __name__ == '__main__':
app = wx.PySimpleApp()
app.frame = wxPlotting()
app.frame.Show()
app.MainLoop()
Basically I want something similar to the text that gets displayed using pyplot, i.e. the bottom right corner when the code below is run:
Code:
import matplotlib.pyplot as plt
plt.plot(range(10000), range(10000))
plt.show()
EDIT:
In my actual program, I want the static text to be within the matplotlib axes, not really above it. So I don't think I can just use a wxpython statictext to display it.
You could use blitting, similar to the animation examples here.
This make a very large performance difference in this case, as only a small portion of the window needs to be redrawn.
Unfortunately, I can't figure out how to get a gray background behind the text when it's redrawn, to match the default figure background behind it... The performance is excellent, though.
As a stand-alone example based on your code above:
import wx
import numpy as np
import matplotlib
matplotlib.use('WXAgg')
from matplotlib.figure import Figure
from matplotlib.widgets import Cursor
from matplotlib.backends.backend_wxagg import \
FigureCanvasWxAgg as FigCanvas, \
NavigationToolbar2WxAgg as NavigationToolbar
class wxPlotting(wx.Frame):
title = 'Test'
def __init__(self):
wx.Frame.__init__(self, None, -1, self.title)
self.time = np.arange(10000)
self.data = np.random.random(10000)
self.sb = self.CreateStatusBar()
self.create_main_panel()
self.axes.plot(self.time, self.data)
self.background = self.canvas.copy_from_bbox(self.fig.bbox)
self.canvas.draw()
def create_main_panel(self):
self.panel = wx.Panel(self)
self.fig = Figure((5.0, 4.0), dpi=100)
self.canvas = FigCanvas(self.panel, -1, self.fig)
self.axes = self.fig.add_subplot(111)
self.text = self.axes.text(0., 1.005, '', transform = self.axes.transAxes, animated=True)
self.cursor = Cursor(self.axes, useblit=True, color='red')
self.canvas.mpl_connect('motion_notify_event', self.cbUpdateCursor)
self.vbox = wx.BoxSizer(wx.VERTICAL)
self.vbox.Add(self.canvas, 1, wx.LEFT | wx.TOP | wx.GROW)
self.panel.SetSizer(self.vbox)
self.vbox.Fit(self)
def cbUpdateCursor(self, event):
if event.inaxes:
text = 'x = %5.4f, y = %5.4f' % (event.xdata, event.ydata)
self.sb.SetStatusText(text)
self.canvas.restore_region(self.background)
self.text.set_text(text)
self.axes.draw_artist(self.text)
self.canvas.blit(self.text.get_window_extent())
if __name__ == '__main__':
app = wx.PySimpleApp()
app.frame = wxPlotting()
app.frame.Show()
app.MainLoop()
You could add a static text box on top, and just update it's label:
import wx
import numpy as np
import matplotlib
matplotlib.use('WXAgg')
from matplotlib.figure import Figure
from matplotlib.widgets import Cursor
from matplotlib.backends.backend_wxagg import \
FigureCanvasWxAgg as FigCanvas, \
NavigationToolbar2WxAgg as NavigationToolbar
class wxPlotting(wx.Frame):
title = 'Test'
def __init__(self):
wx.Frame.__init__(self, None, -1, self.title)
self.time = np.arange(10000)
self.data = np.random.random(10000)
self.sb = self.CreateStatusBar()
self.create_main_panel()
self.axes.plot(self.time, self.data)
self.canvas.draw()
def create_main_panel(self):
self.panel = wx.Panel(self)
self.fig = Figure((5.0, 4.0), dpi=100)
self.canvas = FigCanvas(self.panel, -1, self.fig)
self.axes = self.fig.add_subplot(111)
self.text = self.axes.text(0., 1.005, '', transform = self.axes.transAxes)
self.cursor = Cursor(self.axes, useblit=True, color='red')
self.canvas.mpl_connect('motion_notify_event', self.cbUpdateCursor)
self.vbox = wx.BoxSizer(wx.VERTICAL)
self.cursor_pos = wx.StaticText(self.panel,-1, label="")
self.vbox.Add(self.cursor_pos, 0, wx.LEFT | wx.TOP | wx.GROW)
self.vbox.Add(self.canvas, 1, wx.LEFT | wx.TOP | wx.GROW)
self.panel.SetSizer(self.vbox)
self.vbox.Fit(self)
def cbUpdateCursor(self, event):
if event.inaxes:
text = 'x = %5.4f, y = %5.4f' % (event.xdata, event.ydata)
self.sb.SetStatusText(text)
self.cursor_pos.SetLabel(text)
#self.text.set_text(text)
#self.canvas.draw()
if __name__ == '__main__':
app = wx.PySimpleApp()
app.frame = wxPlotting()
app.frame.Show()
app.MainLoop()

Categories