morikomorou’s blog

自分が学んだことなどの備忘録的なやつ

【python】matplotlibのグラフ上の点をドラッグで動かす

はじめに

これまでの記事で、matplotlibのグラフにマウスをクリックするイベント、マウスを動かすイベント、要素を選択するイベントを紐づける方法について紹介してきました。

マウスクリック、マウスモーションイベント
【python】matplotlibでマウスクリックイベントを紐づける - morikomorou’s blog

ピックイベント
【python】クリックでmatplotlibのグラフの色や透明度を変える - morikomorou’s blog


今回は、新しくマウスを離すイベント(マウスリリースイベント)を使ってみます。前述のイベントと組み合わせることでドラッグして要素を動かしたりする事が出来るようになります。


目標として以下を実装していきたいと思います。

  • ドラッグでmatplotlibのグラフ上の点を動かす

こんなのなんの役に立つんだ…って感じですが。
お絵描きしたり、文字通り手動で外れ値を丸め込むとかですかね 笑

マウスリリースイベント

過去記事でmatplotlibで使えるイベントについてまとめましたが、
【python】matplotlibでマウスクリックイベントを紐づける - morikomorou’s blog

今回は「マウスを離す」という'button_release_event'をグラフに紐づけます。
使い方はマウスをクリックするという'button_press_event'と同じです。

さっそくコード実装に移ります。



ドラッグでグラフ上の点を動かす

import matplotlib.pyplot as plt
import numpy as np
from matplotlib.patches import Rectangle

def motion(event):
    global gco, xdata, ydata, ind
    if gco is None:
        return
    x = event.xdata
    y = event.ydata
    if x == None or y == None:
        return
    xdata[ind] = x
    ydata[ind] = y
    gco.set_data(xdata,ydata)
    plt.draw()

def onpick(event):
    global gco, xdata, ydata, ind
    gco = event.artist
    xdata = gco.get_xdata()
    ydata = gco.get_ydata()
    ind = event.ind[0]

def release(event):
    global gco
    gco = None

gco = None     # ピックした要素が含まれる直線(Line2Dクラス)
ind = None     # ピックした直線のインデックス
xdata = None     # ドラッグするまえの直線のxデータを入れておく
ydata = None     # ドラッグするまえの直線のyデータを入れておく
fig, ax = plt.subplots()
ax.plot(np.random.rand(10),"o-",picker=15)

fig.canvas.mpl_connect('pick_event', onpick)
fig.canvas.mpl_connect('motion_notify_event', motion)
fig.canvas.mpl_connect('button_release_event', release)
plt.show()

結果が以下です。

ポイント、解説

fig.canvas.mpl_connect('pick_event', onpick)
fig.canvas.mpl_connect('motion_notify_event', motion)
fig.canvas.mpl_connect('button_release_event', release)

まず、点をドラッグするためにfigureに要素ピックのイベントそしてマウスのモーションイベント、マウスのリリースイベントを紐づけます。
要素をピックしてからマウスを離すまでの間のみ、マウスのモーションイベントが有効になるようにグローバル変数を使って条件分岐しています。
これでドラッグが可能になります。
それぞれの関数の処理について順番に見ていきましょう。

onpick関数

要素をクリックした瞬間に呼ばれる関数です。

def onpick(event):
    global gco, xdata, ydata, ind
    gco = event.artist
    xdata = gco.get_xdata()
    ydata = gco.get_ydata()
    ind = event.ind[0]

要素がピックされたらgcoにその要素を含む直線クラスを代入します。
次にピックした要素以外を更新しないようにするため、ピックした瞬間の全x,yデータをxdata, ydataに格納しておきます。
event.ind変数にはピックした要素のインデックスのリストが入ります。
要素が重なっていたり、近いときは複数の点が同時に選択されてしまいます。
それを避けるためにevent.ind[0]として選択された要素のうち一番インデックスが若いもの1つを選択するようにしています。

motion関数

マウスを動かしている間ずっと呼ばれる関数です。

def motion(event):
    global gco, xdata, ydata, ind
    if gco is None:
        return
    x = event.xdata      # データ軸内のマウスのx座標
    y = event.ydata      # データ軸内のマウスのy座標
    if x == None or y == None:
        return

グローバル変数gcoを参照して、要素がピックされてなければ何もしないようにしています。
ここでドラッグ中、データ軸の外に出てしまった場合、データをNoneで上書きしてしまわないように、returnで終了します。
※データをNoneで上書きしてしまうとデータエリア上からその要素がなくなってしまうため、再度選択することができなくなります。

def motion(event):
    # 略
    xdata[ind] = x
    ydata[ind] = y
    gco.set_data(xdata,ydata)
    plt.draw()

ドラッグで動かしたいのはピックした点のみなので、onpick関数実行時に取得しておいたインデックス(ind)を使ってピックした要素のx, y座標のみを更新し、グラフを再描画しています。

release関数

マウスを離した時に呼ばれる関数です。

def release(event):
    global gco
    gco = None

グローバル変数gcoを空にして、motion関数が何もしないようにします。

おわりに

今回はマウスリリースイベントを使って、グラフ要素のドラッグ移動を実装しました。
遊ぶ以外の何か良い使い道が見つかったら、記事にしたいと思います。