Introduction
wxPython comes with a simple plotting library called PyPlot which is an improvement over wxPlotCanvas, which is now deprecated. PyPlot features include zooming, legends and a grid. Possible graphs: scatter, line, and bar graphs.
So why do we worry to embed matplotlib if wxPython has a plotting by defualt? The simple answer is because of the rich features available in matplotlib library.
I will demostrate a minimal approach to embed MatPlotLib figure into a GUI based on wxPython classic (for Python 2.7.12). This article could also be used for Python 3 on wxPython Phoenix with little modifications. The final result is shown below:-
MatPlotLib figure is a GUI with little controls/widgets, so python programmers often embed the figure into a richer GUI libarary such as wxPython, TkInter, PyQt/PySide etc in order to obtain the most advantage of a GUI application. In this article, am going to use the wxPython GUI libarary.
The Problem
First of all, if you go to the MatPlotLib example webpage (under user_interfaces Examples), you will find many good examples on embedding MatPlotLib figure in wxPython GUI. But those examples are quite complex and difficult for a beginner to understand the work flow.
The Solution
First, lets make the GUI's Frame in wxPython. The code below comprises the simplest wxPython application ever concieved. It creates our "Frame", (also referred to as a window) that will contain all of the widgets and tools that we will define later in the program.
import wx class MainFrame(wx.Frame): """docstring for MainFrame""" def __init__(self, parent): wx.Frame.__init__(self, parent, title=">>>> by www.UmarYusuf.com", size=(800, 580)) app = wx.App() frame = MainFrame(None).Show() app.MainLoop()
We are going to define two classes, the first class named "MainFrame" has been defined above and it is going to be the main GUI definition. The second class will be named "MatplotPanel" and it is the panel where the matplotlib figure will be displayed.
Now that we've created our frame as seen above, we need to customize it by adding some control/widgets! There are a plethora of control/widgets that can be added to the frame. For simplicity's sake I am going to emphasize one (SplitterWindow) that is commonly used in many wxPython GUI applications.
Lets add a SplitterWindow panels and divide/split it horizontally. Our matplotlib figure will be displayed on the top SplitterWindow while some controls/widgets will be displayed at the bottom SplitterWindow.
import wx class MainFrame(wx.Frame): """docstring for MainFrame""" def __init__(self, parent): wx.Frame.__init__(self, parent, title=">>>> by www.UmarYusuf.com", size=(800, 580)) # Add SplitterWindow panels self.split_win = wx.SplitterWindow(self) self.top_split = wx.Panel(self.split_win, style=wx.SUNKEN_BORDER) self.bottom_split = wx.Panel(self.split_win, style=wx.SUNKEN_BORDER) self.split_win.SplitHorizontally(self.top_split, self.bottom_split, 480) app = wx.App() frame = MainFrame(None).Show() app.MainLoop()
Lets add StaticText and Buttons to the bottom_split window. The "StaticText" will be added to show that such is possible, then the "Buttons" will be added to plot graph figure from matplotlib onto the top_split window.
I will add five (5) buttons and bind them to five (5) diffrent plot functions. So when you press button1 or button2 or button3 etc, it will call plot1 or plot2 or plot3 etc functions respectively and then display its graph on the top_split window.
import wx class MainFrame(wx.Frame): """docstring for MainFrame""" def __init__(self, parent): wx.Frame.__init__(self, parent, title=">>>> by www.UmarYusuf.com", size=(800, 580)) # Add SplitterWindow panels self.split_win = wx.SplitterWindow(self) self.top_split = wx.Panel(self.split_win, style=wx.SUNKEN_BORDER) self.bottom_split = wx.Panel(self.split_win, style=wx.SUNKEN_BORDER) self.split_win.SplitHorizontally(self.top_split, self.bottom_split, 480) # Add some contrls/widgets (StaticText and Buttons) # Add Text control to the bottom_split window self.text1 = wx.StaticText(self.bottom_split, -1, u"You can also plot from file", size=(250, 30), pos=(510, 10), style=wx.ALIGN_CENTER) self.text1.SetBackgroundColour('Gray') font = wx.Font(15, wx.SWISS, wx.NORMAL, wx.NORMAL) self.text1.SetFont(font) # Add Buttons to the bottom_split window and bind them to plot functions self.Button1 = wx.Button(self.bottom_split, -1, "Plot1", size=(80, 40), pos=(10, 10)) self.Button1.Bind(wx.EVT_BUTTON, self.plot1) self.Button2 = wx.Button(self.bottom_split, -1, "Plot2", size=(80, 40), pos=(110, 10)) self.Button2.Bind(wx.EVT_BUTTON, self.plot2) self.Button3 = wx.Button(self.bottom_split, -1, "Plot3", size=(80, 40), pos=(210, 10)) self.Button3.Bind(wx.EVT_BUTTON, self.plot3) def plot1(self, event): pass def plot2(self, event): pass def plot3(self, event): pass def plot4(self, event): pass def plot5(self, event): pass app = wx.App() frame = MainFrame(None).Show() app.MainLoop()
Now, lets create the second class "MatplotPanel" that will hold the matplotlib figure. In order for the two classes (MatplotPanel and MainFrame) to communicate with each other, we have to edit "self.top_split" in the MainFrame class to reference the MatplotPanel instead of a generic panel.
So, this line of code in the MainFrame class
will change to
So, this line of code in the MainFrame class
self.top_split = wx.Panel(self.split_win, style=wx.SUNKEN_BORDER)
will change to
self.top_split = MatplotPanel(self.split_win)
To use matplotlib, we first import its packages. Them we can define the MatplotPanel class to display a very simple plot using the matplotlib "plot()" function.
import matplotlib from matplotlib.figure import Figure from matplotlib.backends.backend_wxagg import FigureCanvasWxAgg as FigureCanvas class MatplotPanel(wx.Panel): def __init__(self, parent): wx.Panel.__init__(self, parent,-1,size=(50,50)) self.figure = Figure() self.axes = self.figure.add_subplot(111) t = [ 0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0] s = [0.0, 1.0, 0.0, 1.0, 0.0, 2.0, 1.0, 2.0, 1.0, 0.0] self.axes.plot(t, s) self.canvas = FigureCanvas(self, -1, self.figure)
The next task is to define the five (5) functions we defined above within the MainFrame class. They are going to be some common plot figures in matplotlib. To make things easy, we will declare two variables (a and b) to be used within the functions. These variables can be declared within MainFrame class (self.a and self.b) or outside the MainFrame class (a and b). Am going to use the later (declared within MainFrame class) in this article.
That is it. The completed source code can be downloaded here from github.
# plotting variables declared outside MainFrame class # a = [ 0.25, 0.97, 0.59, 0.84, 0.93] # b = [ 0.52, 0.83, 0.98, 0.28, 0.31, 0.013, 0.29, 0.49, 0.85, 0.80] class MainFrame(wx.Frame): """docstring for MainFrame""" def __init__(self, parent): wx.Frame.__init__(self, parent, title=">>>> by www.UmarYusuf.com", size=(800, 580)) . . . # plotting variables declared within MainFrame class self.a = [ 0.25, 0.97, 0.59, 0.84, 0.93, 0.83, 0.98, 0.28, 0.31, 0.67] self.b = [ 0.52, 0.83, 0.98, 0.28, 0.31, 0.03, 0.29, 0.49, 0.85, 0.80] . . . def plot1(self, event): self.fig1 = Figure() self.ax1f1 = self.fig1.add_subplot(111) self.ax1f1.plot(self.a) self.ax1f1.set_title("Simple Plot by Button One") self.canvas = FigureCanvas(self, -1, self.fig1) def plot2(self, event): self.fig2 = Figure() self.ax1f2 = self.fig2.add_subplot(121) self.ax1f2.plot(self.a) self.ax1f2.set_title("First Plot by Button Two") self.ax2f2 = self.fig2.add_subplot(122) self.ax2f2.plot(self.b) self.ax2f2.set_title("Second Plot by Button Two") self.canvas = FigureCanvas(self, -1, self.fig2) def plot3(self, event): # Pcolormesh for a 10x10 matrix/array import numpy as np matrix = np.random.rand(10, 10) self.fig3 = Figure() self.ax1f3 = self.fig3.add_subplot(111) self.ax1f3.pcolormesh(matrix) self.ax1f3.set_title("Pcolormesh Plot by Button Three") self.canvas = FigureCanvas(self, -1, self.fig3) def plot4(self, event): self.fig4 = Figure() self.ax1f4 = self.fig4.add_subplot(111) self.ax1f4.bar(self.a, self.b, 0.05) self.ax1f4.set_title("Bar Plot by Button Four") self.canvas = FigureCanvas(self, -1, self.fig4) def plot5(self, event): self.fig5 = Figure() self.ax1f5 = self.fig5.add_subplot(111) self.ax1f5.scatter(self.a, self.b, 300) self.ax1f5.set_title("Scatter Plot by Button Five") self.canvas = FigureCanvas(self, -1, self.fig5)
That is it. The completed source code can be downloaded here from github.
If you have any question or comment, be free to fill the comment box below.
Thanks for reading.
No comments:
Post a Comment