イベント処理とピッキング#
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' |
マウスボタンが押された |
|
'button_release_event' |
マウスボタンが離される |
|
'close_event' |
図は閉じています |
|
'draw_event' |
キャンバスが描画されました (ただし、画面ウィジェットはまだ更新されていません) |
|
'key_press_event' |
キーが押された |
|
'key_release_event' |
キーが離される |
|
「モーション通知イベント」 |
マウスの動き |
|
'pick_event' |
キャンバス内のアーティストが選択されています |
|
'resize_event' |
フィギュアキャンバスのサイズ変更 |
|
「スクロールイベント」 |
マウスのスクロール ホイールが回転している |
|
'figure_enter_event' |
マウスは新しい図に入ります |
|
'figure_leave_event' |
マウスは図を残します |
|
'axes_enter_event' |
マウスは新しい軸に入ります |
|
「axes_leave_event」 |
マウスが軸を離れる |
ノート
'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 から派生しています。
x
、y
キャンバスの左と下からのマウスの x と y の位置 (ピクセル単位)
inaxes
Axes
マウスがある場合、そのインスタンス。そうでなければなしxdata
、ydata
マウスが座標軸上にある場合、データ座標におけるマウスの 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
位置が移動する、ドラッグ可能な四角形クラスを記述し
ます。ヒント:
xy
rect.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
( Line2D
、Text
、Patch
、など)Polygon
を設定することで、ピッキングを有効にすることができます。AxesImage
プロパティは、picker
さまざまなタイプを使用して設定できます。
None
このアーティストのピッキングは無効になっています (デフォルト)。
boolean
True の場合、ピッキングが有効になり、マウス イベントがアーティストの上にある場合、アーティストは pick イベントを発生させます。
callable
picker が callable の場合、アーティストがマウス イベントにヒットしたかどうかを判断するユーザー提供の関数です。署名は、ヒット テストを決定することです。マウス イベントがアーティストの上にある場合は、;を返します。の追加属性となるプロパティのディクショナリです。
hit, props = picker(artist, mouseevent)
hit = True
props
PickEvent
さらに、アーティストのpickradius
プロパティをポイント単位 (1 インチあたり 72 ポイント) の許容値に設定することもできます。これにより、マウスがどれだけ離れていてもマウス イベントをトリガーできるかが決まります。
プロパティを設定してアーティストのピッキングを有効にした後picker
、ハンドラーを Figure キャンバス pick_event に接続して、マウス プレス イベントでピッキング コールバックを取得する必要があります。ハンドラーは通常次のようになります
def pick_handler(event):
mouseevent = event.mouseevent
artist = event.artist
# now do something with this...
コールバックにPickEvent
渡される には、常に次の属性があります。
mouseevent
MouseEvent
pick イベントを生成する。 マウス イベントの有用な属性のリストについては、イベント属性を参照してください。artist
Artist
pick イベントを生成した。
さらに、特定のアーティストは、ピッカー基準を満たすデータのインデックスなどの追加のメタデータを好み、添付する場合が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()