matplotlibでインタラクティブにプロットしたいんじゃ


はじめに

Pythonのmatplotlibを使うと簡単にインタラクティブなプロットができます。特に、グラフの座標の値を容易にとってこれるので、実験やシミュレーション結果の簡易ビューワの作成に適しています。


参考文献

Event handling and picking


動作確認

matplotib : '2.2.2'

backend : MacOSX or Qt5Agg


イベントとコールバック関数

こちらが用意されてるイベントの一覧です。

Screen Shot 2019-07-05 at 18.41.51.png

イベントにコールバック関数を紐付けるだけでインタラクティブなプロットができます。例えば、マウスをクリックしたときに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()

Jul-05-2019 19-00-18.gif

軸の外にマウスカーソルがあるとevent.xdataevent.ydataにはNoneが入るので,

def motion(event):  

if (event.xdata is None) or (event.ydata is None):
return

などと書いて除きます。


マウスカーソルの位置に縦線や横線を引く

axvlineaxhlineで縦線や横線が引けます。戻り値はリストではなくmatplotlib.lines.Line2Dオブジェクトです(plt.plotの戻り値はリスト)。set_xdataset_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()

Jul-05-2019 19-19-22.gif


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()

Jul-05-2019 19-35-22.gif


プロットした点を動かしたい

pick_eventを使うとプロットしたオブジェクトをとってこれます。 このときpickerプロパティに許容する範囲を指定する必要があります。

plt.plot(0, 0, "o", picker=15)

event.artistpick_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()

Jul-05-2019 19-54-27.gif


左クリック、右クリック、ダブルクリック (追記)

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()

Jul-06-2019 22-48-34.gif


ドラッグ中にだけ更新 (追記)

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()

Jul-06-2019 23-05-58.gif

手元の環境では、matplotlibのbackendがMacOSXの場合、上記のように書かなくてもドラッグ中にしかプロットされませんでした。