【发布时间】:2018-08-15 10:03:13
【问题描述】:
我正在使用 matplotlib 库进行绘图并在我的 wxPython GUI 中显示它。我正在绘制来自激光雷达仪器的大量数据点。问题是,我想在这个图中绘制矩形来指示有趣的区域。但是当我在与绘图相同的轴上绘制一个矩形时,整个绘图会被重新绘制,这需要很多时间。这是因为 self.canvas.draw(),一个重新绘制所有内容的函数。
代码在 GUI 中显示如下:
这是该问题的一个最小工作示例。你可以通过按住鼠标右键来绘制矩形。使用左侧的按钮绘制 NetCDF 数据后,矩形的绘制变得非常缓慢。我使用 ImportanceOfBeingErnest 提供的示例尝试了一些有关 blitting 的操作,但经过多次尝试,我仍然无法使其正常工作。
要使最小的工作示例正常工作,您必须在 plot_Data() 函数下指定 NetCDF 文件的路径。我提供了要在此处下载的 NetCDF 文件:
如何在 onselect 函数中将 self.square 粘贴到 self.canvas?
import netCDF4 as nc
import matplotlib
matplotlib.use('WXAgg')
from matplotlib.figure import Figure
from matplotlib.backends.backend_wxagg import FigureCanvasWxAgg as FigureCanvas
import matplotlib.pyplot as plt
import matplotlib.colors as colors
import matplotlib.widgets
import time
import wx
class rightPanel(wx.Panel):
def __init__(self, parent):
wx.Panel.__init__(self, parent, style=wx.SUNKEN_BORDER)
self.initiate_Matplotlib_Plot_Canvas()
self.add_Matplotlib_Widgets()
def initiate_Matplotlib_Plot_Canvas(self):
self.figure = Figure()
self.axes = self.figure.add_subplot(111)
self.colorbar = None
self.canvas = FigureCanvas(self, -1, self.figure)
self.sizer = wx.BoxSizer(wx.VERTICAL)
self.sizer.Add(self.canvas, proportion=1, flag=wx.ALL | wx.GROW)
self.SetSizer(self.sizer)
self.Fit()
self.canvas.draw()
def add_Matplotlib_Widgets(self):
self.rectangleSelector = matplotlib.widgets.RectangleSelector(self.axes, self.onselect,
drawtype="box", useblit=True,
button=[3], interactive=False
)
def onselect(self, eclick, erelease):
tstart = time.time()
x1, y1 = eclick.xdata, eclick.ydata
x2, y2 = erelease.xdata, erelease.ydata
height = y2-y1
width = x2-x1
self.square = matplotlib.patches.Rectangle((x1,y1), width,
height, angle=0.0, edgecolor='red',
fill=False
#blit=True gives Unknown property blit
)
self.axes.add_patch(self.square)
self.canvas.draw()
# =============================================================================
# self.background = self.canvas.copy_from_bbox(self.axes.bbox)
#
#
# self.canvas.restore_region(self.background)
#
# self.axes.draw_artist(self.square)
#
# self.canvas.blit(self.axes.bbox)
# =============================================================================
tend = time.time()
print("Took " + str(tend-tstart) + " sec")
def plot_Data(self):
"""This function gets called by the leftPanel onUpdatePlot. This updates
the plot to the set variables from the widgets"""
path = "C:\\Users\\TEST_DATA\\cesar_uvlidar_backscatter_la1_t30s_v1.0_20100501.nc"
nc_data = self.NetCDF_READ(path)
print("plotting......")
vmin_value = 10**2
vmax_value = 10**-5
combo_value = nc_data['perp_beta']
self.axes.clear()
plot_object = self.axes.pcolormesh(combo_value.T, cmap='rainbow',
norm=colors.LogNorm(vmin=vmin_value, vmax=vmax_value))
self.axes.set_title("Insert title here")
if self.colorbar is None:
self.colorbar = self.figure.colorbar(plot_object)
else:
self.colorbar.update_normal(plot_object)
self.colorbar.update_normal(plot_object)
print('canvas draw..............')
self.canvas.draw()
print("plotting succesfull")
###############################################################################
###############################################################################
"""BELOW HERE IS JUST DATA MANAGEMENT AND FRAME/PANEL INIT"""
###############################################################################
###############################################################################
def NetCDF_READ(self, path):
in_nc = nc.Dataset(path)
list_of_keys = in_nc.variables.keys()
nc_data = {} #Create an empty dictionary to store NetCDF variables
for item in list_of_keys:
variable_shape = in_nc.variables[item].shape
variable_dimensions = len(variable_shape)
if variable_dimensions > 1:
nc_data[item] = in_nc.variables[item][...] #Adding netCDF variables to dictonary
return nc_data
class leftPanel(wx.Panel):
def __init__(self, parent, mainPanel):
wx.Panel.__init__(self, parent)
button = wx.Button(self, -1, label="PRESS TO PLOT")
button.Bind(wx.EVT_BUTTON, self.onButton)
self.mainPanel = mainPanel
def onButton(self, event):
self.mainPanel.rightPanel.plot_Data()
class MainPanel(wx.Panel):
def __init__(self, parent):
"""Initializing the mainPanel. This class is called by the frame."""
wx.Panel.__init__(self, parent)
self.SetBackgroundColour('red')
"""Acquire the width and height of the monitor"""
width, height = wx.GetDisplaySize()
"""Split mainpanel into two sections"""
self.vSplitter = wx.SplitterWindow(self, size=(width,(height-100)))
self.leftPanel = leftPanel(self.vSplitter, self)
self.rightPanel = rightPanel(self.vSplitter)
self.vSplitter.SplitVertically(self.leftPanel, self.rightPanel,102)
class UV_Lidar(wx.Frame):
"""Uppermost class. This class contains everything and calls everything.
It is the container around the mainClass, which on its turn is the container around
the leftPanel class and the rightPanel class. This class generates the menubar, menu items,
toolbar and toolbar items"""
def __init__(self, parent, id):
print("UV-lidar> Initializing GUI...")
wx.Frame.__init__(self, parent, id, 'UV-lidar application')
self.Bind(wx.EVT_CLOSE, self.OnCloseWindow)
self.mainPanel = MainPanel(self)
def OnCloseWindow(self, event):
self.Destroy()
if __name__ == '__main__':
app = wx.App()
frame = UV_Lidar(parent=None, id=-1)
frame.Show()
print("UV-lidar> ")
print("UV-lidar> Initializing GUI OK")
app.MainLoop()
【问题讨论】:
-
如果在绘图之前绘制 Recangle,之后不需要重新绘制。另外,看看 blitting 技术。 SO上有很多问题。
-
我必须在绘图时在绘图上绘制矩形。否则我无法标记有趣的区域。我在绘图上添加了另一个带有一些矩形的 GUI 打印屏幕。
-
然后你可以blit矩形。请参阅this question 或this one。
-
对于 rectangleSelector,我使用 blit。但是,我希望在释放鼠标按钮后矩形保持绘制状态。我添加了将矩形绘制到问题中的代码。
-
是的,你需要恢复画布而不是
self.canvas.draw()并 blitself.square。
标签: python-3.x matplotlib wxpython matplotlib-basemap matplotlib-widget