最近可能會接到一個需要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檔就可以像這樣寫
一個簡單的例子,跑起來像這樣
從此之後在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/