はじめに
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の場合、上記のように書かなくてもドラッグ中にしかプロットされませんでした。






