はじめに
Pythonのmatplotlibを使うと簡単にインタラクティブなプロットができます。特に、グラフの座標の値を容易にとってこれるので、実験やシミュレーション結果の簡易ビューワの作成に適しています。
参考文献
動作確認
matplotib : '2.2.2'
backend : MacOSX or Qt5Agg
イベントとコールバック関数
イベントにコールバック関数を紐付けるだけでインタラクティブなプロットができます。例えば、マウスをクリックしたときにonclick
関数を呼ぶには下記のように書きます。
def onclick(event):
pass
fig = plt.figure()
fig.canvas.mpl_connect('button_press_event', onclick)
ちなみにこれでもいけます。
def onclick(event):
pass
plt.figure()
plt.connect('button_press_event', onclick)
マウスカーソルの位置に点を描画
event.xdata
でマウスカーソルのx軸の値を、event.ydata
でマウスカーソルのy軸の値をとってこれます。マウスカーソルの位置に点を描画するコードが下記になります。
import matplotlib.pyplot as plt
def motion(event):
x = event.xdata
y = event.ydata
ln.set_data(x,y)
plt.draw()
plt.figure()
ln, = plt.plot([],[],'x')
plt.connect('motion_notify_event', motion)
plt.show()
軸の外にマウスカーソルがあるとevent.xdata
やevent.ydata
にはNone
が入るので,
def motion(event):
if (event.xdata is None) or (event.ydata is None):
return
などと書いて除きます。
マウスカーソルの位置に縦線や横線を引く
axvline
やaxhline
で縦線や横線が引けます。戻り値はリストではなくmatplotlib.lines.Line2D
オブジェクトです(plt.plot
の戻り値はリスト)。set_xdata
やset_ydata
とすることで、xやyの値だけ更新できます。
import matplotlib.pyplot as plt
def motion(event):
x = event.xdata
y = event.ydata
ln_v.set_xdata(x)
ln_h.set_ydata(y)
plt.draw()
plt.figure()
ln_v = plt.axvline(0)
ln_h = plt.axhline(0)
plt.connect('motion_notify_event', motion)
plt.show()
subplotでもインタラクティブ・プロット
event.inaxes
でマウスカーソルがのっている軸のAxes
インスタンスがとってこれるので, subplot
の軸毎に別々の処理をさせたい時は下記のように書きます。
import matplotlib.pyplot as plt
def motion(event):
x = event.xdata
y = event.ydata
if event.inaxes == ax1:
ln_1.set_data(x,y)
if event.inaxes == ax2:
ln_2.set_data(x,y)
plt.draw()
plt.figure()
ax1 = plt.subplot(1,2,1)
ln_1, = plt.plot([],[],'o')
ax2 = plt.subplot(1,2,2)
ln_2, = plt.plot([],[],'x')
plt.connect('motion_notify_event', motion)
plt.show()
プロットした点を動かしたい
pick_event
を使うとプロットしたオブジェクトをとってこれます。 このときpicker
プロパティに許容する範囲を指定する必要があります。
plt.plot(0, 0, "o", picker=15)
event.artist
がpick_event
を発生させたArtist
になります。
def onpick(event):
plt.title(event.artist)
plt.connect('pick_event', onpick)
プロットした点を動かすコードです。
import matplotlib.pyplot as plt
def motion(event):
global gco
if gco is None:
return
x = event.xdata
y = event.ydata
gco.set_data(x,y)
plt.draw()
def onpick(event):
global gco
gco = event.artist
plt.title(gco)
def release(event):
global gco
gco = None
gco = None
plt.figure()
plt.plot(0,0,"o",picker=15)
plt.plot(1,0,"o",picker=15)
plt.connect('motion_notify_event', motion)
plt.connect('pick_event', onpick)
plt.connect('button_release_event', release)
plt.show()
左クリック、右クリック、ダブルクリック (追記)
button_press_event
だと、左クリックをしたときはevent.button
が1に, 右クリックをしたときはevent.button
が3になっています。またダブルクリックしたときはevent.dblclick
が1になっています。
import matplotlib.pyplot as plt
def motion(event):
if event.dblclick == 1:
plt.title("double click")
elif event.button == 1:
plt.title("left click")
elif event.button == 3:
plt.title("right click")
plt.draw()
plt.figure()
plt.connect('button_press_event', motion)
plt.show()
ドラッグ中にだけ更新 (追記)
motion_notify_event
の場合では、左クリックしながら動かす(つまりドラッグする)と、event.button
が1なっているので、ドラッグ中にだけプロットしたいときは下記のように書きます。
import matplotlib.pyplot as plt
def motion(event):
if event.button == 1:
x = event.xdata
y = event.ydata
ln.set_data(x,y)
plt.draw()
plt.figure()
ln, = plt.plot([],[],'x')
plt.connect('motion_notify_event', motion)
plt.show()
手元の環境では、matplotlibのbackendがMacOSX
の場合、上記のように書かなくてもドラッグ中にしかプロットされませんでした。