最近可能會接到一個需要GUI上畫不少圖表的案子,於是就來研究了一下wxPython + matplotlib,前陣子買了wxPython in action,雖然一本1500元大洋讓我心有點淌血,不過這本書真的寫得很棒,讓我對wxPython有更深入的瞭解,如果案子接下來買n本都可以,我個人認為買書來讀是一種投資,所以基本上看上眼的書買起來也不會手軟,接著因為除了畫圖形,也需要用到不少GUI,我把wxPython官方的demo抓下來,裡面每個範例都跑了一次,對於wxPython能做到什麼地步有了更深入的瞭解,越來越覺得wxPython真是酷斃了,很好玩的GUI framework
在這之後,雖然案子還在歸劃的初期,不過我不是很喜歡空等而沒有程式可以寫的感覺,其實在接洽的這段期間就能先完成一些將會用到的東西,像是widget之類的,或寫寫原形來驗證確實可行之類的都很好,所以我就寫了一個用Matplotlib畫圖表的Control : FigurePanel,因為往後在寫GUI都會用到,如果每次都要特地寫來處理這些我會發瘋,當然最好的方法就是包成Control,如此一來所有圖表都可以直接使用,加上可以有統一的行為,例如另存新檔、列印等等,相當方便
什麼是Matplotlib? Matplotlib是一套強大的Python畫圖表用的函式庫,如果不知道他到底可以畫到什麼地步,就可以看他們官網的畫廊,相信看完後就知道它有多強大,幾乎你想得到的圖、想不到的圖,沒看過的圖,他都可以畫,當然,如果裡面沒有你要的圖,或著你要自創圖,當然也可以自己擴增,有了這套函式庫,原本我可能得先花個一個星期還多少時間來建構一套畫圖表的基礎,如今這些時間完全省了下來,這就是使用Open source的好處,沒有這些東西真的不知道要寫到民國幾年
切入正題,我們來看一下FigurePanel的原始碼
import wx from wx import xrc import matplotlib matplotlib.use('WXAgg') from matplotlib.backends.backend_wxagg import FigureCanvasWxAgg from matplotlib.backends.backend_wx import NavigationToolbar2Wx from matplotlib.figure import Figure __all__ = ["HAVE_TOOL_BAR", "FigurePanel", "FigurePanelXmlHandler"] # this flag indicate that there is toolbar in figure panel HAVE_TOOL_BAR = 1 class FigurePanel(wx.PyPanel): def __init__(self, figSize=None, figDpi=None, *args, **kwargs): wx.Panel.__init__(self, *args, **kwargs) self.figSize = figSize self.figDpi = figDpi self.SetBackgroundStyle(wx.BG_STYLE_CUSTOM) self.createFigure(figSize, figDpi) def createFigure(self, size, dpi): """Create figure canvas """ self.fig = Figure(size, dpi) self.canvas = FigureCanvasWxAgg(self, wx.ID_ANY, self.fig) self.toolbar = None # Now put all into a sizer sizer = wx.BoxSizer(wx.VERTICAL) # This way of adding to sizer allows resizing sizer.Add(self.canvas, 1, wx.LEFT|wx.TOP|wx.GROW) # add tool bar if self.GetWindowStyle() & HAVE_TOOL_BAR: self.toolbar = self.createToolbar() # On Windows, default frame size behaviour is incorrect # you don't need this under Linux tw, th = self.toolbar.GetSizeTuple() fw, fh = self.canvas.GetSizeTuple() self.toolbar.SetSize(wx.Size(fw, th)) # Best to allow the toolbar to resize! sizer.Add(self.toolbar, 0, wx.GROW) self.SetSizer(sizer) self.Fit() def createToolbar(self): """Create toolbar for controlling figure panel, override this method to provide custom toolbar """ toolbar = NavigationToolbar2Wx(self.canvas) toolbar.Realize() return toolbar def GetToolBar(self): # You will need to override GetToolBar if you are using an # unmanaged toolbar in your frame return self.toolbar def onEraseBackground(self, evt): # this is supposed to prevent redraw flicker on some X servers... pass def getFigure(self): """Get figure of this panel """ return self.fig class FigurePanelXmlHandler(xrc.XmlResourceHandler): def __init__(self): xrc.XmlResourceHandler.__init__(self) # Specify the styles recognized by objects of this type self.AddStyle("wxHAVE_TOOL_BAR", HAVE_TOOL_BAR) self.AddWindowStyles() # This method and the next one are required for XmlResourceHandlers def CanHandle(self, node): return self.IsOfClass(node, "FigurePanel") def DoCreateResource(self): assert self.GetInstance() is None figSize = self.GetParamValue("figsize") if not len(figSize): figSize = None else: figSize = map(int, figSize.split(',')) figDpi = self.GetParamValue("figdpi") if not len(figDpi): figDpi = None else: figDpi = int(figDpi) # Now create the object panel = FigurePanel( figSize, figDpi, self.GetParentAsWindow(), self.GetID(), self.GetPosition(), self.GetSize(), self.GetStyle("style", 0), self.GetName() ) return panel if __name__ == '__main__': app = wx.PySimpleApp() class Frame(wx.Frame): def __init__(self, *args, **kwargs): wx.Frame.__init__(self, *args, **kwargs) self.figPanel = FigurePanel(parent=self, figSize=(5, 5), figDpi=75) self.draw() sizer = wx.BoxSizer() sizer.Add(self.figPanel, 1, wx.GROW) self.SetSizer(sizer) self.Fit() def draw(self): fig = self.figPanel.getFigure() plot = fig.add_subplot(111) plot.plot([1,2,3]) plot.set_ylabel('some numbers') frame = Frame(parent=None, title='Figure panel demo') frame.Show() app.MainLoop()
其實程式不難,很簡單,就只有這樣而已,配合XmlResource的InsertHandler就可以寫在xrc檔裡
from figure_panel import * def getHandlers(): """Get xrc handlers """ from wx import xrc for key, value in globals().iteritems(): try: if issubclass(value, xrc.XmlResourceHandler): yield value except TypeError: pass def installHandlers(res): """Insert xml handlers into xml resource object @param res: resources object to insert """ for handler in getHandlers(): res.InsertHandler(handler())
有了xml handler,wxPython在讀你的xrc檔時就可以認得並且正確地建構出FigurePanel,有其它自訂的Control其實也都可以用這樣的方式來擴增到xrc檔中,xrc檔就可以像這樣寫
<object class="wxPanel" name="m_panel2"> <style>wxTAB_TRAVERSAL</style> <object class="wxBoxSizer"> <orient>wxVERTICAL</orient> <object class="sizeritem"> <option>1</option> <flag>wxALL|wxEXPAND</flag> <border>5</border> <object class="FigurePanel" name="figurePanel"> <style>wxHAVE_TOOL_BAR</style> </object> </object> </object> </object>
一個簡單的例子,跑起來像這樣
從此之後在wxPython中畫圖表就非常輕鬆了



matplotlib畫出來的圖(這裡我用的是"plot"來畫 (x,y)), 放在wxPython的frame與dialog, 在圖畫的"移動"與"放大"會有不太一樣的反應; 在dialog上的圖"移動"的反應比較滿一些.
不太懂@@,不太一樣的反應? 比較滿? 我這裡的是用Panel做的,放在Frame裡,所以如果放在Dialog裡反應會不一樣?
例如"移動", plot的圖會隨著滑鼠拖曳, 很平順的移動, 然而在dialog中, 圖與滑鼠的連動有些延遲.像"跳格"般移動.
後來我做了一個上下圖的x軸連動, 當上圖x軸limit改變時, 下圖也會跟著變. 這種情況下, 即使放在frame中也會有些不平順的表現.
可以這麼說, matplotlib在圖的"重繪"上有些效能的問題.
這我倒是沒有很強烈的感覺,我的應用的圖幾乎都是靜態的,有也應該是有變時才重畫,matplotlib在重畫上的確不夠快,每次都是整張重畫,我不知道是不是Backend的問題,我在想如果把Backend改成GDI+之類原生的畫圖方式,或是關掉反鋸齒之類的話,速度會不會改善
hello 小弟我最近也再這套軟體開發一些TOOL,遇到最大的問題感覺就是開圖不像matlab這麼快,感覺每開一次圖就會頓一下..因為我處理可能到上百張圖我現在作法是那它自動存檔plt.savefig plt.close() 不讓他秀出來 可是在開檔存檔感覺還是頗佔資源 在畫圖時CPU都幾乎是100% 到大概70張左右就會當機 出現記憶體錯誤.. 不知道版主有沒有好的解決方法….目前我適用批次畫不超過60為原則..不知有沒有語法可以清理記憶體的
此文章以转载
谢谢博主分享
http://blog.163.com/jackylau_v/blog/static/175754040201191510513497/