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

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

### 参考文献
[Event handling and picking](https://matplotlib.org/3.1.0/users/event_handling.html)

### 動作確認
```
matplotib : '2.2.2'
backend : MacOSX or Qt5Agg
```
# イベントとコールバック関数
こちらが用意されてるイベントの一覧です。
![Screen Shot 2019-07-05 at 18.41.51.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/35426/2cc40940-f306-fb05-4e0b-64aa9f9adc19.png)

イベントにコールバック関数を紐付けるだけでインタラクティブなプロットができます。例えば、マウスをクリックしたときに`onclick`関数を呼ぶには下記のように書きます。

```py
def onclick(event):
  pass

fig = plt.figure()
fig.canvas.mpl_connect('button_press_event', onclick)
```

ちなみにこれでもいけます。

```py
def onclick(event):
   pass

plt.figure()
plt.connect('button_press_event', onclick)
```
# マウスカーソルの位置に点を描画

`event.xdata`でマウスカーソルのx軸の値を、`event.ydata`でマウスカーソルのy軸の値をとってこれます。マウスカーソルの位置に点を描画するコードが下記になります。

```py
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](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/35426/8f2d87ec-2078-b730-1bb3-b764300abe9e.gif)

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

```py
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の値だけ更新できます。

```py
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](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/35426/a28ae4e6-d9b2-8810-e719-cc90402045a2.gif)

# subplotでもインタラクティブ・プロット
`event.inaxes`でマウスカーソルがのっている軸の`Axes`インスタンスがとってこれるので, `subplot`の軸毎に別々の処理をさせたい時は下記のように書きます。

```py
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](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/35426/42652d54-c4ff-33d9-a6f4-c50ba4190611.gif)

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

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

`event.artist`が`pick_event`を発生させた`Artist`になります。

```py
def onpick(event):
    plt.title(event.artist)

plt.connect('pick_event', onpick)
```

プロットした点を動かすコードです。

```py
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](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/35426/b559e771-8699-f48a-c33e-194621dc7d11.gif)

# 左クリック、右クリック、ダブルクリック (追記) 
`button_press_event`だと、左クリックをしたときは`event.button`が1に, 右クリックをしたときは`event.button`が3になっています。またダブルクリックしたときは`event.dblclick`が1になっています。

```py
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](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/35426/8e9e4b69-27a3-9962-d414-423c18651a80.gif)

# ドラッグ中にだけ更新 (追記) 
`motion_notify_event`の場合では、左クリックしながら動かす(つまりドラッグする)と、`event.button`が1なっているので、ドラッグ中にだけプロットしたいときは下記のように書きます。

```py
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](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/35426/d0ffdaea-a887-f0cf-f69a-e42dcefd27ca.gif)

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


<!---
# キー入力 (追記) 
キー入力も`key_press_event`と`event.key`だけで簡単にとってこれます。

```py
import matplotlib.pyplot as plt 

def onkey(event):
    plt.title(event.key)
    plt.draw()
    
plt.figure()
plt.connect('key_press_event', onkey)
plt.show()
```

手元の環境ではbackendが`MacOSX`だと、キー入力も動かなかったので`Qt5Agg`に変えています。
-->