【Python】【matplotlib】マウスのドラッグでグラフをパンする
はじめに
前のエントリでは、マウスのホイールを使用してグラフを拡大縮小する方法について説明し、実装しました。このエントリでは、さらにグラフをドラッグすることでパン(平行移動)する方法について説明します。
やりたいこと
- マウスのグラフをドラッグするとグラフを平行移動する
- カーソルキーで押した方向にグラフを平行移動させる
ドラッグ操作・キー操作の受け取り方法
ドラッグ動作の受け取り
ドラッグ操作を受け取るには、'motion_notify_event'
を使います。これは、グラフ内でカーソルが移動したときに発生するイベントですが、マウスのボタンの状態も受け取ることができます。これを利用して、左ボタンが押されている状態のときに、ドラッグしていると判定します。
実装例
import matplotlib.pyplot as plt
def on_move(event):
if event.button == 1:
print("dragging")
fig, ax = plt.subplots()
fig.canvas.mpl_connect('motion_notify_event', on_move)
event.button
が押されているマウスのボタンを表します。1
が左ボタンです。
この例では、マウスの左ボタンを押しながらドラッグした場合に、コンソールに"dragging"と出力します。
キー操作の受け取り
前のエントリでCtrlキーの受け取りを実装しました。同様に'key_press_event'
を使います。
実装例
import matplotlib.pyplot as plt
def on_key_press(event):
print(event.key)
fig, ax = plt.subplots()
fig.canvas.mpl_connect('key_press_event', on_key_press)
上記の例では、押したキーに対応した文字列をコンソールに出力します。キーを押し続けると、一定間隔で文字列が表示され続けます。
パン操作の実装
上記を応用して、実際に実装してみます。前のエントリで作成したMplZoomHelper
クラスに追加で実装し、拡張します。また、おまけ機能として'h'
キーを押すことで、最初の位置に戻れるようにします。
import matplotlib.pyplot as plt
class MplZoomHelper:
def __init__(self, ax):
self.ax = ax
self.ctrl_flag = False
self.zoom_rate = 0.9
self.move_rate = 0.05
self.x_on_click = 0
self.y_on_click = 0
self.xlim_on_click = ax.get_xlim()
self.ylim_on_click = ax.get_ylim()
self.initial_xlim = ax.get_xlim()
self.initial_ylim = ax.get_ylim()
ax.figure.canvas.mpl_connect('key_press_event', self.on_key_press)
ax.figure.canvas.mpl_connect('key_release_event', self.on_key_release)
ax.figure.canvas.mpl_connect('scroll_event', self.on_scroll)
ax.figure.canvas.mpl_connect(
'button_press_event', self.on_button_press)
ax.figure.canvas.mpl_connect('motion_notify_event', self.on_move)
def on_key_press(self, event):
if event.inaxes is not self.ax:
return
if event.key == 'control':
self.ctrl_flag = True
elif event.key == 'h':
self.on_home(event)
elif event.key == 'right':
self.on_move_x(event, 1)
elif event.key == 'left':
self.on_move_x(event, -1)
elif event.key == 'up':
self.on_move_y(event, 1)
elif event.key == 'down':
self.on_move_y(event, -1)
def on_key_release(self, event):
if event.inaxes is not self.ax:
return
if event.key == 'control':
self.ctrl_flag = False
def on_home(self, event):
self.ax.set_xlim(self.initial_xlim)
self.ax.set_ylim(self.initial_ylim)
self.ax.figure.canvas.draw()
def on_scroll(self, event):
if event.inaxes is not self.ax:
return
if self.ctrl_flag:
self.on_scroll_y(event)
else:
self.on_scroll_x(event)
self.ax.figure.canvas.draw()
def on_scroll_x(self, event):
x_pos = event.xdata
min_value, max_value = self.ax.get_xlim()
if event.button == 'up':
new_min_value = x_pos - (x_pos - min_value) * self.zoom_rate
new_max_value = (max_value - x_pos) * self.zoom_rate + x_pos
elif event.button == 'down':
new_min_value = x_pos - (x_pos - min_value) / self.zoom_rate
new_max_value = (max_value - x_pos) / self.zoom_rate + x_pos
self.ax.set_xlim(new_min_value, new_max_value)
def on_scroll_y(self, event):
y_pos = event.ydata
min_value, max_value = self.ax.get_ylim()
if event.button == 'up':
new_min_value = y_pos - (y_pos - min_value) * self.zoom_rate
new_max_value = (max_value - y_pos) * self.zoom_rate + y_pos
elif event.button == 'down':
new_min_value = y_pos - (y_pos - min_value) / self.zoom_rate
new_max_value = (max_value - y_pos) / self.zoom_rate + y_pos
self.ax.set_ylim(new_min_value, new_max_value)
def on_button_press(self, event):
if event.inaxes is not self.ax:
return
if event.button != 1:
return
self.x_on_click = event.xdata
self.y_on_click = event.ydata
self.xlim_on_click = self.ax.get_xlim()
self.ylim_on_click = self.ax.get_ylim()
def on_move(self, event):
if event.inaxes is not self.ax:
return
if event.button != 1:
return
dx = event.xdata - self.x_on_click
dy = event.ydata - self.y_on_click
self.xlim_on_click -= dx
self.ylim_on_click -= dy
self.ax.set_xlim(self.xlim_on_click)
self.ax.set_ylim(self.ylim_on_click)
self.ax.figure.canvas.draw()
def on_move_x(self, event, dir):
min_value, max_value = self.ax.get_xlim()
width = max_value - min_value
dx = width * self.move_rate * dir
self.ax.set_xlim(min_value + dx, max_value + dx)
self.ax.figure.canvas.draw()
def on_move_y(self, event, dir):
min_value, max_value = self.ax.get_ylim()
width = max_value - min_value
dy = width * self.move_rate * dir
self.ax.set_ylim(min_value + dy, max_value + dy)
self.ax.figure.canvas.draw()
if __name__ == "__main__":
fig, axes = plt.subplots(2, 2)
axes[0, 0].plot([-1, 2, 3], [2, 4, 3])
axes[1, 1].plot([0, 5, 9], [-2, 1, 10])
z1 = MplZoomHelper(axes[0, 0])
z2 = MplZoomHelper(axes[1, 1])
plt.show()
コンストラクタ
コンストラクタで、追加の変数の初期化を実施します。また、ホームに戻る機能のために、初期表示時の表示範囲を保持しておきます。
on_key_press()
カーソルキーと'h'
キーが押されたときの動作を追加しています。カーソルキーは'right'
, 'left'
, 'up'
, 'down'
でそれぞれ受け取ることができます。各キーが押されたときの処理に飛びます。
on_home()
'h'
キーを押したときの動作です。'h'
キーが押されたら、コンストラクタで保持しておいた表示範囲(self.initial_xlim
, self.initail_ylim
)に戻します。
on_button_press()
マウスのボタンが押されたときの動作です。この時、event.inaxes
を見て、登録したグラフ(self.ax
)上で発生したイベントかどうか調べます。
ドラッグ開始に備えて、クリックしたときのグラフ座標(event.xdata
, event.ydata
)と、表示されているx軸、y軸の範囲を記録しておきます。
on_move()
マウスのカーソルが動いているときの動作です。この時、event.inaxes
を見て、登録したグラフ(self.ax
)上で発生したイベントかどうか調べます。また、event.button
を見て、1
ならば左ボタンによるドラッグ中と判定しています。
グラフ座標(event.xdata
, event.ydata
)を取得し、クリック開始時からの差分を計算します。その分だけ表示範囲をずらします。
パンした結果は、再描画しないとグラフに反映されません。そのために、self.ax.figure.canvas.draw()
を実施します。
on_move_x()
, on_move_y()
カーソルキーを押したときの動作です。カーソルキーが押された方向に、表示幅のself.move_rate
分だけ平行に移動します。移動後再描画するために、self.ax.figure.canvas.draw()
を実施します。
まとめ
matplotlibで表示されたグラフに対して、マウスのドラッグでパン操作を実施する方法です。
mpl_connect()
を使って、'button_press_event'
を受け取るコールバック関数を登録します。mpl_connect()
を使って、'motion_notify_event'
を受け取るコールバック関数を登録します。button_press_event
のコールバック関数でマウスのクリックイベントを捕まえ、その時のグラフ上の座標を記録します。motion_notify_event
のコールバック関数でマウスのボタンの押下状態を取得します。押下状態にあれば、ドラッグ中と判断します。- クリック開始時に記録した座標からの差分を計算し、グラフの両端の値を変更します。
- 変更した結果を画面に反映するため、再描画します。
ディスカッション
コメント一覧
まだ、コメントがありません