This python program plots a figure in a wxpython window.
How can I change the program so that:
the figure resizes when I resize the window
the main window cannot be resized smaller than a particular dimension? (say, half the default size of the window)
.
# adapted from:
# http://wiki.wxpython.org/Getting%20Started
# http://www.cs.colorado.edu/~kena/classes/5448/s11/presentations/pearse.pdf
import wx
import pylab as pl
import matplotlib
from matplotlib.backends.backend_wxagg import FigureCanvasWxAgg as FigureCanvas
class GUIPanel(wx.Panel):
def __init__(self, parent):
wx.Panel.__init__(self, parent)
self.parent = parent
# create some sizers
sizer = wx.BoxSizer(wx.VERTICAL)
# A button
self.button =wx.Button(self, label="Tada!")
self.Bind(wx.EVT_BUTTON, self.OnClick,self.button)
# put up a figure
self.figure = pl.figure()
self.axes = self.drawplot(self.figure)
self.canvas = FigureCanvas(self, -1, self.figure)
sizer.Add(self.canvas, 0, wx.ALIGN_CENTER|wx.ALL)
sizer.Add(self.button, 0, wx.ALIGN_CENTER|wx.ALL)
self.SetSizerAndFit(sizer)
def log(self, fmt, *args):
print (fmt % args)
def OnClick(self,event):
self.log("button clicked, id#%d\n", event.GetId())
def drawplot(self, fig):
ax = fig.add_subplot(1,1,1)
t = pl.arange(0,1,0.001)
ax.plot(t,t*t)
ax.grid()
return ax
app = wx.App(False)
frame = wx.Frame(None)
panel = GUIPanel(frame)
frame.Fit()
frame.Center()
frame.Show()
app.MainLoop()
1) Modify the way you setup your sizer to this:
sizer.Add(self.canvas, 1, wx.EXPAND | wx.ALL)
Here is method reference. It is also helpful to install and use wxPython Demo. Sizers are covered nicely there. BTW: wx.ALL is useless unless you specify border.
2) And add this to your frame setup after frame.Show():
size = frame.GetSize()
frame.SetMinSize((size[0] / 2, size[1] / 2))
Related
I'm trying to show an image inside a panel of a wxpython notebook page using matplotlib. It works perfectly using wx.StaticBitmap but I need to use matplotlib for image manipulation. The code gives this result:
The right panel is a terminal and sizes correctly. The left panel is where the image is meant to fit. The result of wx.lib.inspection is showing that the wxFigureCanvas minsize is (640, 480) but I don't understand why and can't find how to change that. I suspect it's the problem. The code (sorry, short as I could make it):
import cv2 as cv
import matplotlib.pyplot as plt
from matplotlib.figure import Figure
from matplotlib.backends.backend_wxagg import FigureCanvasWxAgg as FigureCanvas
import wx
import wx.lib.agw.aui as aui
class TopRight(wx.Panel):
def __init__(self, parent):
super(TopRight, self).__init__(parent)
self.term = wx.TextCtrl(self,
size=(200, 100),
style=wx.TE_MULTILINE|wx.TE_READONLY|wx.TE_RICH
)
self.term.SetDefaultStyle(wx.TextAttr(wx.GREEN))
self.term.SetBackgroundColour(wx.BLACK)
self.sizer = wx.BoxSizer(wx.VERTICAL)
self.sizer.Add(self.term, 1, wx.EXPAND)
self.SetSizer(self.sizer)
self.term.WriteText("hello")
self.term.Update
class TopLeft(wx.Panel):
def __init__(self, parent):
super(TopLeft , self).__init__(parent)
img_pth = "av_cat.png"
# Intitialise the matplotlib figure
self.figure = Figure()
# Create an axes, turn off the labels and add them to the figure
self.axes = plt.Axes(self.figure,[0,0,1,1])
self.axes.set_axis_off()
self.figure.add_axes(self.axes)
# Add the figure to the wxFigureCanvas
self.canvas = FigureCanvas(self, -1, self.figure)
# Sizer to contain the canvas
self.sizer = wx.BoxSizer(wx.VERTICAL)
self.sizer.Add(self.canvas, 1, wx.EXPAND|wx.ALL)
self.SetSizer(self.sizer)
# If there is an initial image, display it on the figure
if img_pth is not None:
self.setImage(img_pth)
def setImage(self, pathToImage):
# Load the image into matplotlib
image = cv.imread(pathToImage)
# Add the image to the figure and redraw the canvas.
self.axes.imshow(image)
self.canvas.draw()
class explorer_panel(wx.Panel):
def __init__(self, parent):
#Constructor
wx.Panel.__init__(self, parent)
topSplitter = wx.SplitterWindow(self)
image_panel = TopLeft(topSplitter)
terminal_panel = TopRight(topSplitter)
topSplitter.SplitVertically(image_panel, terminal_panel)
topSplitter.SetSashGravity(0.5)
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(topSplitter, 1, wx.EXPAND)
self.SetSizer(sizer)
class Main(wx.Frame):
def __init__(self):
wx.Frame.__init__(
self,
parent = None,
title = "Borges Infinite Image",
size = (600,300)
)
panel = wx.Panel(self)
notebook = wx.Notebook(panel)
explorer = explorer_panel(notebook)
notebook.AddPage(explorer, 'Explorer')
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(notebook, 1, wx.ALL|wx.EXPAND, 5)
panel.SetSizer(sizer)
if __name__ == "__main__":
app = wx.App()
frame = Main()
frame.Show()
import wx.lib.inspection as wxli
wxli.InspectionTool().Show()
app.MainLoop()
I am trying to align checkboxes and matplotlib canvas in wx frame.
I need those check boxes to be aligned in left side and canvas on right side
below code not aligning them properly
can anybody suggest changes need to be done in the code ?
If I want to add one more canvas in the same frame how can i add it?
import wx
import matplotlib.pyplot as plt
from matplotlib.backends.backend_wxagg import FigureCanvasWxAgg as FigureCanvas
class canvasFrame(wx.Frame):
def __init__(self,parent,id):
wx.Frame.__init__(self,parent,id,"MyCanvas",size =(1200,400))
self.fig = plt.Figure(figsize=(15,5))
self.ax1 = self.fig.add_subplot(1,1,1)
self.canvas = FigureCanvas(self, -1, self.fig)
b1 = wx.CheckBox(self,-1,"Apples",(10,20),(160,-1))
b2 = wx.CheckBox(self,-1,"Mango",(10,40),(160,-1))
b1sizer = wx.BoxSizer(wx.VERTICAL)
b2sizer = wx.BoxSizer(wx.VERTICAL)
canvSizer = wx.BoxSizer(wx.HORIZONTAL)
b1sizer.Add(b1,0,wx.ALL,5)
b2sizer.Add(b2,0,wx.ALL,5)
canvSizer.Add(self.canvas,wx.TOP | wx.EXPAND,5)
if __name__ == "__main__":
app = wx.App()
frame = canvasFrame(parent=None,id = -1)
frame.Show()
app.MainLoop()
you are missing alot of parts of how sizers work ... I would really recommend revisiting the documentation and googling for tutorials
class canvasFrame(wx.Frame):
def __init__(self,parent,id):
wx.Frame.__init__(self,parent,id,"MyCanvas",size =(1200,400))
self.fig = plt.Figure(figsize=(15,5))
self.ax1 = self.fig.add_subplot(1,1,1)
self.canvas = FigureCanvas(self, -1, self.fig)
b1 = wx.CheckBox(self,-1,"Apples",(10,20),(160,-1))
b2 = wx.CheckBox(self,-1,"Mango",(10,40),(160,-1))
# first create your sizer for your checkboxes
checkbox_sizer = wx.BoxSizer(wx.VERTICAL)
checkbox_sizer.AddMany([b1,b2]) # add both buttons to the sizer
#next create the main sizer
main_sizer = wx.BoxSizer(wx.HORIZONTAL)
#add the checkbox sizer to the main_sizer
main_sizer.Add(checkbox_sizer)
# add the canvas to the main sizer
main_sizer.Add(self.canvas)
#Set the sizer as the sizer for the frame
self.SetSizer(main_sizer)
#call layout for the sizers to layout their elements
self.Layout()
#call fit to make the frame fit its contents
self.Fit()
You are using the sizers incorrectly, try the following:
import wx
import matplotlib.pyplot as plt
from matplotlib.backends.backend_wxagg import FigureCanvasWxAgg as FigureCanvas
class canvasFrame(wx.Frame):
def __init__(self,parent,id):
wx.Frame.__init__(self,parent,id,"MyCanvas",size =(1200,400))
self.fig = plt.Figure(figsize=(15,5))
self.ax1 = self.fig.add_subplot(1,1,1)
self.canvas = FigureCanvas(self, -1, self.fig)
b1 = wx.CheckBox(self,-1,"Apples", size=(160,-1))
b2 = wx.CheckBox(self,-1,"Mango", size=(160,-1))
mainsizer = wx.BoxSizer(wx.HORIZONTAL) # Main sizer
cbsizer = wx.BoxSizer(wx.VERTICAL) # Checkbox sizer
canvassizer = wx.BoxSizer(wx.VERTICAL) # Canvas sizer
# Add checkbox
cbsizer.Add(b1, 0, wx.ALIGN_LEFT|wx.ALL, 5)
cbsizer.Add(b2, 0, wx.ALIGN_LEFT|wx.ALL, 5)
# Add canvas
canvassizer.Add(self.canvas, 1, wx.EXPAND|wx.ALL, 1)
# Add "secondary" sizers to main sizer
mainsizer.Add(cbsizer, 1, wx.EXPAND|wx.ALL, 2)
mainsizer.Add(canvassizer, 3, wx.EXPAND|wx.ALL, 2)
self.SetSizer(mainsizer)
if __name__ == "__main__":
app = wx.App()
frame = canvasFrame(parent=None,id = -1)
frame.Show()
app.MainLoop()
Now, about the last question, i think that you would add a subplot (axes) instead other canvas, i.e.
self.fig = plt.Figure(figsize=(15,5))
self.ax1 = self.fig.add_subplot(1,2,1)
self.ax2 = self.fig.add_subplot(1,2,2)
self.canvas = FigureCanvas(self, -1, self.fig)
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()
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()
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.
This python program plots a figure in a wxpython window. It runs fine in Enthought Python Distribution 7.3-2, but when I close the figure, python keeps running and doesn't exit.
What am I missing? When I create wxpython GUIs that don't use matplotlib, they exit correctly after I close the window. I guess there must be something about message loops in wxpython that I don't understand.
# adapted from:
# http://wiki.wxpython.org/Getting%20Started
# http://www.cs.colorado.edu/~kena/classes/5448/s11/presentations/pearse.pdf
import wx
import pylab as pl
import matplotlib
from matplotlib.backends.backend_wxagg import FigureCanvasWxAgg as FigureCanvas
class GUIPanel(wx.Panel):
def __init__(self, parent):
wx.Panel.__init__(self, parent)
self.parent = parent
# create some sizers
sizer = wx.BoxSizer(wx.VERTICAL)
# A button
self.button =wx.Button(self, label="Tada!")
self.Bind(wx.EVT_BUTTON, self.OnClick,self.button)
# put up a figure
self.figure = pl.figure()
self.axes = self.drawplot(self.figure)
self.canvas = FigureCanvas(self, -1, self.figure)
sizer.Add(self.canvas, 0, wx.ALIGN_CENTER|wx.ALL)
sizer.Add(self.button, 0, wx.ALIGN_CENTER|wx.ALL)
self.SetSizerAndFit(sizer)
def log(self, fmt, *args):
print (fmt % args)
def OnClick(self,event):
self.log("button clicked, id#%d\n", event.GetId())
def drawplot(self, fig):
ax = fig.add_subplot(1,1,1)
t = pl.arange(0,1,0.001)
ax.plot(t,t*t)
ax.grid()
return ax
app = wx.App(False)
frame = wx.Frame(None)
panel = GUIPanel(frame)
frame.Fit()
frame.Center()
frame.Show()
app.MainLoop()
Huh. Per the discussion in comments from tcaswell, I changed
self.figure = pl.figure()
to
self.figure = matplotlib.figure.Figure()
and it fixed the problem. (python exits when I close the window)
I have a scatter plot with several thousand points. This post tells me how to label them:
Matplotlib: How to put individual tags for a scatter plot
But that will look like a disaster with so many points. What I would like instead is to have a "tool tip" type label that pops up when you mouseover a point. Is that possible using matplotlib?
Once you get the coords of the point you can show them or any object-linked info in a textctrl in the toolbar. For this you have to instantiate a toolbar (NavigationToolbar2Wx()) in your canvas and add the textcontrol there. This is not as nice as a popup but it does the job.
Here you have an example of customizing your toolbar (only showing the x coordinate in the txtctrl):
#!/usr/bin/env python
#-*- coding: utf-8 -*-
#
"""
jvisor_spectrum_panel (visor_07)
25 julio 2010
"""
#
import wx
from matplotlib.figure import Figure
from matplotlib.backends.backend_wxagg import FigureCanvasWxAgg as FigureCanvas
from matplotlib.backends.backend_wx import NavigationToolbar2Wx
#
#
class SpectrumPanel(wx.Panel):
def __init__(self, parent, xlabel='m/z', ylabel='Intensity'):
wx.Panel.__init__(self, parent)
#
self.parent = parent
self.xlabel = xlabel
self.ylabel = ylabel
self.SetBackgroundColour("white")
#
self.figure = Figure()
self.canvas = FigureCanvas(self, -1, self.figure)
#
self.add_toolbar()
#
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(self.canvas, 1, wx.LEFT | wx.TOP| wx.GROW| wx.EXPAND)
sizer.Add(self.toolbar, 0, wx.LEFT)
self.canvas.mpl_connect('motion_notify_event', self.on_motion)
self.SetSizer(sizer)
self.Fit()
self.clean()
#
def add_toolbar(self):
""
self.toolbar = NavigationToolbar2Wx(self.canvas)
mass_txt = wx.StaticText(self.toolbar, label='m/z', pos=(230, 7),
size=(25, 17))
mass_txt.SetBackgroundColour("light gray")
self.mass = wx.TextCtrl(self.toolbar, pos=(260,4), size=(50, 22),
style=wx.TE_READONLY)
#
self.toolbar.SetToolBitmapSize(wx.Size(24, 25))
self.toolbar.SetMinSize((1500, 31))
self.toolbar.Realize()
self.toolbar.Update()
#
def clean(self):
""
self.figure.clear()
self.axes = self.figure.add_subplot(111)
#
def dibuja(self):
"dibuja el canvas"
self.axes.set_xlabel(self.xlabel)
self.axes.set_ylabel(self.ylabel)
self.canvas.draw()
#
def on_motion(self, evt):
if evt.inaxes:
xpos = evt.xdata
self.mass.SetValue(' %0.1f' % (xpos))
if __name__ == '__main__':
""
class TestFrame(wx.Frame):
def __init__(self, *args, **kargs):
wx.Frame.__init__(self, *args, **kargs)
self.panel = SpectrumPanel(self)
self.Fit()
#
app = wx.PySimpleApp()
fr = TestFrame(None)
fr.Show()
app.MainLoop()
And here you can see the new control in the toolbar: