事件处理和挑选

Matplotlib与许多用户界面工具包(wxpython、tkinter、qt4、gtk和macosx)配合使用,为了支持图形的交互式平移和缩放等功能,开发人员有一个通过按键和鼠标移动与图形交互的API,这对开发人员很有帮助,因此我们不必重复跨不同用户界面的代码OT。尽管事件处理API与GUI无关,但它基于GTK模型,这是支持的第一个用户界面matplotlib。与标准的GUI事件相比,触发的事件相对于Matplotlib也更丰富一些,包括以下信息: matplotlib.axes.Axes 事件发生在中。这些事件还了解matplotlib坐标系,并以像素和数据坐标报告事件位置。

事件连接

要接收事件,您需要编写一个回调函数,然后将您的函数连接到事件管理器,它是 FigureCanvasBase . 下面是一个简单的示例,打印鼠标单击的位置和按下的按钮:

fig, ax = plt.subplots()
ax.plot(np.random.rand(10))

def onclick(event):
    print('%s click: button=%d, x=%d, y=%d, xdata=%f, ydata=%f' %
          ('double' if event.dblclick else 'single', event.button,
           event.x, event.y, event.xdata, event.ydata))

cid = fig.canvas.mpl_connect('button_press_event', onclick)

这个 FigureCanvas 方法 mpl_connect() 返回仅为整数的连接ID。当您想断开回拨时,只需拨打:

fig.canvas.mpl_disconnect(cid)

注解

画布只保留对用作回调的实例方法的弱引用。因此,您需要保留对拥有此类方法的实例的引用。否则,实例将被垃圾收集,回调将消失。

这不会影响用作回调的自由函数。

以下是您可以连接到的事件、事件发生时发送回您的类实例以及事件描述:

事件名称 等级 描述
'button_press_event' MouseEvent 按下鼠标按钮
'button_release_event' MouseEvent 松开鼠标按钮
'close_event' CloseEvent 图形已关闭
'draw_event' DrawEvent 画布已绘制(但屏幕小部件尚未更新)
'key_press_event' KeyEvent 按键被按下
'key_release_event' KeyEvent 钥匙被释放
'motion_notify_event' MouseEvent 鼠标移动
'pick_event' PickEvent 画布中的艺术家被选中
'resize_event' ResizeEvent 调整图形画布的大小
'scroll_event' MouseEvent 鼠标滚轮滚动
'figure_enter_event' LocationEvent 鼠标进入新图形
'figure_leave_event' LocationEvent 老鼠留下一个身影
'axes_enter_event' LocationEvent 鼠标进入新轴
'axes_leave_event' LocationEvent 老鼠留下斧子

事件属性

所有Matplotlib事件都继承自基类 matplotlib.backend_bases.Event ,存储属性:

name
事件名称
canvas
生成事件的FigureCanvas实例
guiEvent
触发Matplotlib事件的GUI事件

事件处理最常见的事件是按键/释放事件和鼠标按下/释放和移动事件。这个 KeyEventMouseEvent 处理这些事件的类都是从具有以下属性的locationEvent派生的

x
X位置-画布左侧的像素
y
Y位置-画布底部的像素
inaxes
这个 Axes 鼠标位于轴上时的实例
xdata
数据坐标中鼠标的X坐标
ydata
数据坐标中鼠标的Y坐标

让我们看一个画布的简单示例,每次按下鼠标时都会创建一个简单的线段:

from matplotlib import pyplot as plt

class LineBuilder:
    def __init__(self, line):
        self.line = line
        self.xs = list(line.get_xdata())
        self.ys = list(line.get_ydata())
        self.cid = line.figure.canvas.mpl_connect('button_press_event', self)

    def __call__(self, event):
        print('click', event)
        if event.inaxes!=self.line.axes: return
        self.xs.append(event.xdata)
        self.ys.append(event.ydata)
        self.line.set_data(self.xs, self.ys)
        self.line.figure.canvas.draw()

fig = plt.figure()
ax = fig.add_subplot(111)
ax.set_title('click to build line segments')
line, = ax.plot([0], [0])  # empty line
linebuilder = LineBuilder(line)

plt.show()

这个 MouseEvent 我们刚才用的是 LocationEvent ,我们可以访问event.x和event.x data中的数据和像素坐标。除了 LocationEvent 属性,它有

button
按下按钮无,1,2,3,'向上','向下'(向上和向下用于滚动事件)
key
按键:无、任何字符、“shift”、“win”或“control”

可拖动矩形练习

写入用初始化的可拖动矩形类 Rectangle 但拖动时会移动其X、Y位置。提示:您需要存储原始文件 xy 存储为rect.xy并连接到按下、移动和释放鼠标事件的矩形的位置。当按下鼠标时,检查单击是否发生在矩形上(请参见 matplotlib.patches.Rectangle.contains() )如果是这样,则将矩形xy和鼠标单击的位置存储在数据坐标中。在motion事件回调中,计算鼠标移动的deltax和deltay,并将这些delta添加到存储的矩形的原点。重新绘制图形。在按钮释放事件中,只需重置您存储为“无”的所有按钮按下数据。

解决方法如下:

import numpy as np
import matplotlib.pyplot as plt

class DraggableRectangle:
    def __init__(self, rect):
        self.rect = rect
        self.press = None

    def connect(self):
        'connect to all the events we need'
        self.cidpress = self.rect.figure.canvas.mpl_connect(
            'button_press_event', self.on_press)
        self.cidrelease = self.rect.figure.canvas.mpl_connect(
            'button_release_event', self.on_release)
        self.cidmotion = self.rect.figure.canvas.mpl_connect(
            'motion_notify_event', self.on_motion)

    def on_press(self, event):
        'on button press we will see if the mouse is over us and store some data'
        if event.inaxes != self.rect.axes: return

        contains, attrd = self.rect.contains(event)
        if not contains: return
        print('event contains', self.rect.xy)
        x0, y0 = self.rect.xy
        self.press = x0, y0, event.xdata, event.ydata

    def on_motion(self, event):
        'on motion we will move the rect if the mouse is over us'
        if self.press is None: return
        if event.inaxes != self.rect.axes: return
        x0, y0, xpress, ypress = self.press
        dx = event.xdata - xpress
        dy = event.ydata - ypress
        #print('x0=%f, xpress=%f, event.xdata=%f, dx=%f, x0+dx=%f' %
        #      (x0, xpress, event.xdata, dx, x0+dx))
        self.rect.set_x(x0+dx)
        self.rect.set_y(y0+dy)

        self.rect.figure.canvas.draw()


    def on_release(self, event):
        'on release we reset the press data'
        self.press = None
        self.rect.figure.canvas.draw()

    def disconnect(self):
        'disconnect all the stored connection ids'
        self.rect.figure.canvas.mpl_disconnect(self.cidpress)
        self.rect.figure.canvas.mpl_disconnect(self.cidrelease)
        self.rect.figure.canvas.mpl_disconnect(self.cidmotion)

fig = plt.figure()
ax = fig.add_subplot(111)
rects = ax.bar(range(10), 20*np.random.rand(10))
drs = []
for rect in rects:
    dr = DraggableRectangle(rect)
    dr.connect()
    drs.append(dr)

plt.show()

额外信贷 :使用bliting使动画绘制更快更平滑。

额外信贷解决方案:

# Draggable rectangle with blitting.
import numpy as np
import matplotlib.pyplot as plt

class DraggableRectangle:
    lock = None  # only one can be animated at a time
    def __init__(self, rect):
        self.rect = rect
        self.press = None
        self.background = None

    def connect(self):
        'connect to all the events we need'
        self.cidpress = self.rect.figure.canvas.mpl_connect(
            'button_press_event', self.on_press)
        self.cidrelease = self.rect.figure.canvas.mpl_connect(
            'button_release_event', self.on_release)
        self.cidmotion = self.rect.figure.canvas.mpl_connect(
            'motion_notify_event', self.on_motion)

    def on_press(self, event):
        'on button press we will see if the mouse is over us and store some data'
        if event.inaxes != self.rect.axes: return
        if DraggableRectangle.lock is not None: return
        contains, attrd = self.rect.contains(event)
        if not contains: return
        print('event contains', self.rect.xy)
        x0, y0 = self.rect.xy
        self.press = x0, y0, event.xdata, event.ydata
        DraggableRectangle.lock = self

        # draw everything but the selected rectangle and store the pixel buffer
        canvas = self.rect.figure.canvas
        axes = self.rect.axes
        self.rect.set_animated(True)
        canvas.draw()
        self.background = canvas.copy_from_bbox(self.rect.axes.bbox)

        # now redraw just the rectangle
        axes.draw_artist(self.rect)

        # and blit just the redrawn area
        canvas.blit(axes.bbox)

    def on_motion(self, event):
        'on motion we will move the rect if the mouse is over us'
        if DraggableRectangle.lock is not self:
            return
        if event.inaxes != self.rect.axes: return
        x0, y0, xpress, ypress = self.press
        dx = event.xdata - xpress
        dy = event.ydata - ypress
        self.rect.set_x(x0+dx)
        self.rect.set_y(y0+dy)

        canvas = self.rect.figure.canvas
        axes = self.rect.axes
        # restore the background region
        canvas.restore_region(self.background)

        # redraw just the current rectangle
        axes.draw_artist(self.rect)

        # blit just the redrawn area
        canvas.blit(axes.bbox)

    def on_release(self, event):
        'on release we reset the press data'
        if DraggableRectangle.lock is not self:
            return

        self.press = None
        DraggableRectangle.lock = None

        # turn off the rect animation property and reset the background
        self.rect.set_animated(False)
        self.background = None

        # redraw the full figure
        self.rect.figure.canvas.draw()

    def disconnect(self):
        'disconnect all the stored connection ids'
        self.rect.figure.canvas.mpl_disconnect(self.cidpress)
        self.rect.figure.canvas.mpl_disconnect(self.cidrelease)
        self.rect.figure.canvas.mpl_disconnect(self.cidmotion)

fig = plt.figure()
ax = fig.add_subplot(111)
rects = ax.bar(range(10), 20*np.random.rand(10))
drs = []
for rect in rects:
    dr = DraggableRectangle(rect)
    dr.connect()
    drs.append(dr)

plt.show()

鼠标输入和离开

如果要在鼠标进入或离开图形或轴时得到通知,可以连接到图形/轴进入/离开事件。下面是一个简单的例子,可以更改鼠标所在轴和图形背景的颜色:

"""
Illustrate the figure and axes enter and leave events by changing the
frame colors on enter and leave
"""
import matplotlib.pyplot as plt

def enter_axes(event):
    print('enter_axes', event.inaxes)
    event.inaxes.patch.set_facecolor('yellow')
    event.canvas.draw()

def leave_axes(event):
    print('leave_axes', event.inaxes)
    event.inaxes.patch.set_facecolor('white')
    event.canvas.draw()

def enter_figure(event):
    print('enter_figure', event.canvas.figure)
    event.canvas.figure.patch.set_facecolor('red')
    event.canvas.draw()

def leave_figure(event):
    print('leave_figure', event.canvas.figure)
    event.canvas.figure.patch.set_facecolor('grey')
    event.canvas.draw()

fig1 = plt.figure()
fig1.suptitle('mouse hover over figure or axes to trigger events')
ax1 = fig1.add_subplot(211)
ax2 = fig1.add_subplot(212)

fig1.canvas.mpl_connect('figure_enter_event', enter_figure)
fig1.canvas.mpl_connect('figure_leave_event', leave_figure)
fig1.canvas.mpl_connect('axes_enter_event', enter_axes)
fig1.canvas.mpl_connect('axes_leave_event', leave_axes)

fig2 = plt.figure()
fig2.suptitle('mouse hover over figure or axes to trigger events')
ax1 = fig2.add_subplot(211)
ax2 = fig2.add_subplot(212)

fig2.canvas.mpl_connect('figure_enter_event', enter_figure)
fig2.canvas.mpl_connect('figure_leave_event', leave_figure)
fig2.canvas.mpl_connect('axes_enter_event', enter_axes)
fig2.canvas.mpl_connect('axes_leave_event', leave_axes)

plt.show()

对象拾取

您可以通过设置 picker AN的性质 Artist (例如,Matplotlib Line2DTextPatchPolygonAxesImage

有各种各样的含义 picker 财产:

None
已为此艺术家禁用领料(默认)
boolean
如果为真,则将启用拾取,并且如果鼠标事件位于艺术家上方,则艺术家将激发拾取事件。
float
如果选取器是一个数字,它被解释为点中的epsilon公差,并且如果艺术家的数据在鼠标事件的epsilon范围内,他将触发一个事件。对于一些艺术家(如线条和补丁集合),艺术家可以为生成的拾取事件提供额外的数据,例如,拾取事件epsilon内的数据索引。
function
如果选取器是可调用的,则它是用户提供的函数,用于确定艺术家是否被鼠标事件击中。签名是 hit, props = picker(artist, mouseevent) 以确定命中测试。如果鼠标事件在艺术家上方,则返回 hit=True props是要添加到 PickEvent 属性

当您通过设置 picker 属性,您需要连接到图形画布pick_事件以获取鼠标按下事件的pick回调。例如。::

def pick_handler(event):
    mouseevent = event.mouseevent
    artist = event.artist
    # now do something with this...

这个 PickEvent 传递给回调的总是用两个属性激发:

mouseevent 生成pick事件的鼠标事件。这个
鼠标事件的属性依次为 xy (显示空间中的坐标,例如从左到下的像素)和扩展数据、Ydata(数据空间中的坐标)。此外,您还可以获得有关按下了哪些按钮、按下了哪些键以及 Axes 鼠标已结束等。请参见 matplotlib.backend_bases.MouseEvent 有关详细信息。
artist
这个 Artist 生成了pick事件。

此外,某些艺术家喜欢 Line2DPatchCollection 可以将其他元数据(如索引)附加到符合选取器标准的数据中(例如,行中所有在指定epsilon公差范围内的点)。

简单选取示例

在下面的示例中,我们将线条选取器属性设置为一个标量,因此它表示以点为单位的公差(每英寸72点)。当pick事件发生时,将调用onpick回调函数,该函数位于与直线的公差距离内,并且具有pick距离公差内的数据顶点的索引。我们的onpick回调函数只打印在pick位置下的数据。不同的Matplotlib艺术家可以将不同的数据附加到PickEvent。例如, Line2D 附加ind属性,该属性是拾取点下的行数据的索引。见 pick() 有关 PickEvent 行的属性。代码如下:

import numpy as np
import matplotlib.pyplot as plt

fig = plt.figure()
ax = fig.add_subplot(111)
ax.set_title('click on points')

line, = ax.plot(np.random.rand(100), 'o', picker=5)  # 5 points tolerance

def onpick(event):
    thisline = event.artist
    xdata = thisline.get_xdata()
    ydata = thisline.get_ydata()
    ind = event.ind
    points = tuple(zip(xdata[ind], ydata[ind]))
    print('onpick points:', points)

fig.canvas.mpl_connect('pick_event', onpick)

plt.show()

挑选练习

创建一个由1000个高斯随机数组成的100个数组的数据集,计算每个数组的样本均值和标准差(提示:NumPy数组有一个均值和标准差方法),并绘制100个均值与100个标准差的xy标记图。将plot命令创建的线连接到pick事件,并绘制生成点击点的数据的原始时间序列。如果有多个点在单击点的公差范围内,则可以使用多个子点绘制多个时间序列。

运动解决方案:

"""
compute the mean and stddev of 100 data sets and plot mean vs. stddev.
When you click on one of the mu, sigma points, plot the raw data from
the dataset that generated the mean and stddev
"""
import numpy as np
import matplotlib.pyplot as plt

X = np.random.rand(100, 1000)
xs = np.mean(X, axis=1)
ys = np.std(X, axis=1)

fig = plt.figure()
ax = fig.add_subplot(111)
ax.set_title('click on point to plot time series')
line, = ax.plot(xs, ys, 'o', picker=5)  # 5 points tolerance


def onpick(event):

    if event.artist!=line: return True

    N = len(event.ind)
    if not N: return True


    figi = plt.figure()
    for subplotnum, dataind in enumerate(event.ind):
        ax = figi.add_subplot(N,1,subplotnum+1)
        ax.plot(X[dataind])
        ax.text(0.05, 0.9, 'mu=%1.3f\nsigma=%1.3f'%(xs[dataind], ys[dataind]),
                transform=ax.transAxes, va='top')
        ax.set_ylim(-0.5, 1.5)
    figi.show()
    return True

fig.canvas.mpl_connect('pick_event', onpick)

plt.show()