天天看點

Matplotlib中的事件處理詳解與實戰前言事件連接配接事件屬性實戰:直方圖中矩形的拖拽

前言

Matplotlib 作為 Python 的繪圖庫,能否直接進行事件處理呢?答案是肯定的,本文将介紹在 Matplotlib 中事件的處理。

Matplotlib 與許多使用者界面工具包配合使用,包括 wxpython、tkinter、qt 以及gtk 等,是以不必針對不同的使用者界面編寫功能重複的代碼。Matplotlib 與标準 GUI 事件相比,觸發的事件更豐富,其包括事件發生在哪個軸等資訊。

事件連接配接

要使 Matplotlib 可以接收事件,需要編寫一個回調函數(在事件發生時會進行調用),然後将函數連接配接到事件管理器。

以滑鼠事件為例,我們通過以下程式實作列印滑鼠點選的位置和按下了哪個按鈕的功能:

from matplotlib import pyplot as plt
import numpy as np
fig, ax = plt.subplots()
ax.scatter(np.random.rand(100), np.random.rand(100))
# 編寫回調函數
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)
plt.show()      

FigureCanvasBase.mpl_connect 方法傳回該事件的連接配接 id,此 id 可用于斷開回調:  

fig.canvas.mpl_disconnect(cid)      

可以使用 Matplotlib 連接配接更多事件,具體參見

官方文檔

,在此不在一一列舉。需要注意的是,連接配接到“key_press_event”和“key_release_event”事件時,Matplotlib 使用不同使用者界面工具包之間可能會出現不一緻。可以通過檢視

鍵盤快捷鍵

,可以看到 Matplotlib 預設附加了一些按鍵回調。

事件屬性

所有 Matplotlib 事件都繼承自基類 matplotlib.backend_bases.Event,它包含以下屬性:

屬性名 含義
name 事件名
canvas FigureCanvas 執行個體生成事件
guiEvent 觸發 Matplotlib 事件的GUI事件

我們以事件進行中最常見的事件按鍵按下/釋放事件和滑鼠按下/釋放/移動事件為例,利用事件屬性。 處理這些事件的 KeyEvent 和 MouseEvent 類都是從 LocationEvent 派生的,它具有以下屬性:

x,y 滑鼠距畫布左下角的距離(以像素為機關)
inaxes 滑鼠所在的 Axes 執行個體(如果有)
xdata, ydata 滑鼠在資料坐标中的位置

為了對比屬性 x、y 和 xdata、ydata 的差別,使用以下程式進行說明,此程式會在滑鼠單擊時,在畫布上顯示滑鼠事件的 x、y、xdata、和 ydata 屬性:

from matplotlib import pyplot as plt
class TextBuilder:
    def __init__(self, line):
        self.text = text
        self.content = self.text.get_text()
        self.cid = text.figure.canvas.mpl_connect('button_press_event', self)
    def __call__(self, event):
        print('click', event)
        if event.inaxes!=self.text.axes: return
        self.text.set_x(event.xdata)
        self.text.set_y(event.ydata)
        self.text.set_text('x:{},y:{},xdata:{:.2f},ydata:{:.2f}'.format(event.x,event.y,event.xdata,event.ydata))
        self.text.figure.canvas.draw()
fig, ax = plt.subplots()
ax.set_title('click to show event attribute')
text = plt.text([0], [0], '')
textbuilder = TextBuilder(text)
plt.show()      

運作程式後,當在畫布上單擊時,會在滑鼠點選處,繪制出事件的 x、y、xdata、和 ydata 屬性值:

Matplotlib中的事件處理詳解與實戰前言事件連接配接事件屬性實戰:直方圖中矩形的拖拽

接下來,我們編寫另一個示例程式,此程式會在每次按下滑鼠時繪制一條線段:  

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, ax = plt.subplots()
ax.set_title('click to build line segments')
line, = ax.plot([0], [0])  # empty line
linebuilder = LineBuilder(line)
plt.show()      

運作程式後,當在畫布上單擊時,會在每次按下滑鼠時繪制一條線段:

Matplotlib中的事件處理詳解與實戰前言事件連接配接事件屬性實戰:直方圖中矩形的拖拽

實戰:直方圖中矩形的拖拽

接下來,為了更好的了解 Matplotlib 中的事件,編寫使用 Rectangle 執行個體初始化的可拖動矩形類,在拖動時同時移動矩形位置。

在拖拽前,需要存儲矩形的原始位置,該位置存儲可以使用 rect.xy 獲得,然後需要連接配接按下(press)、運動(motion)和釋放(release)滑鼠事件。 當按下滑鼠時,檢查單擊是否發生在矩形上,如果是,則将矩形位置和滑鼠單擊的位置存儲在資料坐标中。 在運動事件回調中,計算滑鼠移動的距離 deltax 和 deltay,并利用這些值計算矩形新的位置,設定矩形透明度,最後重繪圖。在滑鼠釋放事件中,需要将存儲的資料重置為 None,并恢複矩形透明度:

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):
        """Check whether mouse is over us; if so, 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)
        self.press = self.rect.xy, (event.xdata, event.ydata)
    def on_motion(self, event):
        """Move the rectangle if the mouse is over us."""
        if self.press is None or 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)
        self.rect.set_alpha(0.5)
        self.rect.figure.canvas.draw()
    def on_release(self, event):
        """Clear button press information."""
        self.press = None
        self.rect.set_alpha(1.)
        self.rect.figure.canvas.draw()
    def disconnect(self):
        """Disconnect all callbacks."""
        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, ax = plt.subplots()
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()      

運作程式後,可以在畫布上使用滑鼠拖拽矩形,并且在拖拽過程中矩形透明度會降低以顯式顯示正在拖拽的矩形:

Matplotlib中的事件處理詳解與實戰前言事件連接配接事件屬性實戰:直方圖中矩形的拖拽