イベント処理とピッキング#

Matplotlib は、多くのユーザー インターフェイス ツールキット (wxpython、tkinter、qt、gtk、および macosx) と連携し、Figure のインタラクティブなパンやズームなどの機能をサポートするために、開発者が Figure と対話するための API を用意すると便利です。 「GUI ニュートラル」であるキーの押下とマウスの動きを介して、さまざまなユーザー インターフェイスで多くのコードを繰り返す必要がありません。イベント処理 API は GUI に依存しませんが、Matplotlib が最初にサポートしたユーザー インターフェイスである GTK モデルに基づいています。トリガーされるイベントは、イベントが発生した場所などの情報を含む、標準の GUI イベントよりも Matplotlib に対して少し豊富 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)

このFigureCanvasBase.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

キーが離される

「モーション通知イベント」

MouseEvent

マウスの動き

'pick_event'

PickEvent

キャンバス内のアーティストが選択されています

'resize_event'

ResizeEvent

フィギュアキャンバスのサイズ変更

「スクロールイベント」

MouseEvent

マウスのスクロール ホイールが回転している

'figure_enter_event'

LocationEvent

マウスは新しい図に入ります

'figure_leave_event'

LocationEvent

マウスは図を残します

'axes_enter_event'

LocationEvent

マウスは新しい軸に入ります

「axes_leave_event」

LocationEvent

マウスが軸を離れる

ノート

'key_press_event' および 'key_release_event' イベントに接続すると、Matplotlib が動作するさまざまなユーザー インターフェイス ツールキット間で不整合が発生する場合があります。これは、ユーザー インターフェイス ツールキットの不一致/制限によるものです。次の表は、さまざまなユーザー インターフェイス ツールキットからキーとして (QWERTY キーボード レイアウトを使用して) 受け取ると予想されるものの基本的な例を示しています。コンマはさまざまなキーを区切ります。

押されたキー

WxPython

Qt

WebAgg

Gtk

トキンター

macosx

Shift+2

シフト、シフト+2

シフト、 @

シフト、 @

シフト、 @

シフト、 @

シフト、 @

Shift+F1

シフト、シフト+f1

シフト、シフト+f1

シフト、シフト+f1

シフト、シフト+f1

シフト、シフト+f1

シフト、シフト+f1

シフト

シフト

シフト

シフト

シフト

シフト

シフト

コントロール

コントロール

コントロール

コントロール

コントロール

コントロール

コントロール

代替

代替

代替

代替

代替

代替

代替

Alt Gr

何もない

何もない

代替

iso_level3_shift

iso_level3_shift

キャップスロック

キャップスロック

キャップスロック

キャップスロック

キャップスロック

キャップスロック

キャップスロック

CapsLock+a

caps_lock、

caps_lock、

caps_lock、A

caps_lock、A

caps_lock、A

caps_lock、

a

a

a

a

a

a

a

Shift+a

シフト、A

シフト、A

シフト、A

シフト、A

シフト、A

シフト、A

CapsLock+Shift+a

caps_lock、シフト、A

caps_lock、シフト、A

caps_lock、シフト、a

caps_lock、シフト、a

caps_lock、シフト、a

caps_lock、シフト、A

Ctrl+Shift+Alt

コントロール、ctrl+shift、ctrl+alt

コントロール、ctrl+shift、ctrl+meta

コントロール、ctrl+shift、ctrl+meta

コントロール、ctrl+shift、ctrl+meta

コントロール、ctrl+shift、ctrl+meta

コントロール、Ctrl+Shift、Ctrl+Alt+Shift

Ctrl+Shift+a

コントロール、ctrl+shift、ctrl+A

コントロール、ctrl+shift、ctrl+A

コントロール、ctrl+shift、ctrl+A

コントロール、ctrl+shift、ctrl+A

コントロール、ctrl+shift、ctrl+a

コントロール、ctrl+shift、ctrl+A

F1

f1

f1

f1

f1

f1

f1

Ctrl+F1

コントロール、ctrl+f1

コントロール、ctrl+f1

コントロール、ctrl+f1

コントロール、ctrl+f1

コントロール、ctrl+f1

コントロール、なし

Matplotlib は、対話性のためにデフォルトでいくつかのキープレス コールバックをアタッチします。これらは、ナビゲーションのキーボード ショートカットセクションに記載されています。

イベント属性#

すべての Matplotlib イベント matplotlib.backend_bases.Eventは、属性を格納する基本クラスから継承します。

name

イベント名

canvas

イベントを生成する FigureCanvas インスタンス

guiEvent

Matplotlib イベントをトリガーした GUI イベント

イベント処理の基本となる最も一般的なイベントは、キーのプレス/リリース イベントと、マウスのプレス/リリースおよび移動イベントです。これらのイベントを処理するKeyEventおよびMouseEventクラスは、次の属性を持つ LocationEvent から派生しています。

xy

キャンバスの左と下からのマウスの x と y の位置 (ピクセル単位)

inaxes

Axesマウスがある場合、そのインスタンス。そうでなければなし

xdataydata

マウスが座標軸上にある場合、データ座標におけるマウスの x および 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, ax = plt.subplots()
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.y)(event.xdata, event.ydata)LocationEvent

button

押されたボタン: None、MouseButton、'up'、または 'down' (上下はスクロール イベントに使用されます)

key

押されたキー: なし、任意の文字、'shift'、'win'、または 'control'

ドラッグ可能な長方形の演習#

Rectangleインスタンスで初期化されるが、ドラッグするとそのxy 位置が移動する、ドラッグ可能な四角形クラスを記述し ます。ヒント: xyrect.xy として格納されている四角形の元の位置を格納し、プレス、モーション、リリース マウス イベントに接続する必要があります。マウスが押されたときに、クリックが長方形の上で発生するかどうかを確認し (「 」を参照 Rectangle.contains)、発生した場合は、長方形の xy とマウス クリックの位置をデータ座標に保存します。モーション イベント コールバックで、マウスの動きの 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
        # print(f'x0={x0}, xpress={xpress}, event.xdata={event.xdata}, '
        #       f'dx={dx}, x0+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):
        """Clear button press information."""
        self.press = None
        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()

追加クレジット: ブリッティングを使用して、アニメーションの描画をより高速かつスムーズにします。

エクストラ クレジット ソリューション:

# 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):
        """Check whether mouse is over us; if so, store some data."""
        if (event.inaxes != self.rect.axes
                or DraggableRectangle.lock is not None):
            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)
        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):
        """Move the rectangle if the mouse is over us."""
        if (event.inaxes != self.rect.axes
                or DraggableRectangle.lock is not self):
            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):
        """Clear button press information."""
        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 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()

マウスの出入り#

マウスが Figure または Axes に出入りしたときに通知を受け取りたい場合は、Figure/Axes の出入りイベントに接続できます。マウスが置かれている軸と図の背景の色を変更する簡単な例を次に示します。

"""
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, axs = plt.subplots(2)
fig1.suptitle('mouse hover over figure or axes to trigger events')

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, axs = plt.subplots(2)
fig2.suptitle('mouse hover over figure or axes to trigger events')

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のプロパティArtist( Line2DTextPatch、など)Polygonを設定することで、ピッキングを有効にすることができます。AxesImage

プロパティは、pickerさまざまなタイプを使用して設定できます。

None

このアーティストのピッキングは無効になっています (デフォルト)。

boolean

True の場合、ピッキングが有効になり、マウス イベントがアーティストの上にある場合、アーティストは pick イベントを発生させます。

callable

picker が callable の場合、アーティストがマウス イベントにヒットしたかどうかを判断するユーザー提供の関数です。署名は、ヒット テストを決定することです。マウス イベントがアーティストの上にある場合は、;を返します。の追加属性となるプロパティのディクショナリです。hit, props = picker(artist, mouseevent)hit = TruepropsPickEvent

さらに、アーティストのpickradiusプロパティをポイント単位 (1 インチあたり 72 ポイント) の許容値に設定することもできます。これにより、マウスがどれだけ離れていてもマウス イベントをトリガーできるかが決まります。

プロパティを設定してアーティストのピッキングを有効にした後picker 、ハンドラーを Figure キャンバス pick_event に接続して、マウス プレス イベントでピッキング コールバックを取得する必要があります。ハンドラーは通常次のようになります

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

コールバックにPickEvent渡される には、常に次の属性があります。

mouseevent

MouseEventpick イベントを生成する。 マウス イベントの有用な属性のリストについては、イベント属性を参照してください。

artist

Artistpick イベントを生成した。

さらに、特定のアーティストは、ピッカー基準を満たすデータのインデックスなどの追加のメタデータを好み、添付する場合がLine2Dあります (たとえば、指定された許容範囲PatchCollection内にあるライン内のすべてのポイント)。pickradius

簡単なピッキングの例#

以下の例では、ライン上でのピッキングを有効にし、ピッキング半径の許容範囲をポイント単位で設定しています。コールバック関数はonpick 、ピック イベントがラインから許容距離内にある場合に呼び出され、ピック距離の許容範囲内にあるデータ頂点のインデックスを持っています。コールonpick バック関数は、ピック位置の下にあるデータを出力するだけです。異なる Matplotlib アーティストは、異なるデータを PickEvent にアタッチできます。たとえばLine2D、ピック ポイントの下のライン データへのインデックスである ind プロパティをアタッチします。ラインのプロパティのLine2D.pick詳細については、 を参照してください。 PickEvent

import numpy as np
import matplotlib.pyplot as plt

fig, ax = plt.subplots()
ax.set_title('click on points')

line, = ax.plot(np.random.rand(100), 'o',
                picker=True, pickradius=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 (mean, stddev) points, plot the raw dataset
that generated that point.
"""

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, ax = plt.subplots()
ax.set_title('click on point to plot time series')
line, = ax.plot(xs, ys, 'o', picker=True, pickradius=5)  # 5 points tolerance


def onpick(event):
    if event.artist != line:
        return
    n = len(event.ind)
    if not n:
        return
    fig, axs = plt.subplots(n, squeeze=False)
    for dataind, ax in zip(event.ind, axs.flat):
        ax.plot(X[dataind])
        ax.text(0.05, 0.9,
                f"$\\mu$={xs[dataind]:1.3f}\n$\\sigma$={ys[dataind]:1.3f}",
                transform=ax.transAxes, verticalalignment='top')
        ax.set_ylim(-0.5, 1.5)
    fig.show()
    return True


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