Matplotlib でお絵かきアプリっぽいものを作ります。
1. matplotlib のバージョン
GUIのAPIの仕様は変わる可能性が高いため、matplotlib のバージョンを記載しておきます。
バージョン 2.2.2 で動作確認済みです。以下のようにするとバージョンの確認ができます。
import matplotlib print(matplotlib.__version__) # Out: '2.2.2'
2. 仕様
アプリの仕様は以下とします。
No | 状態 | 動作 |
---|---|---|
1 | マウス左ボタン プレス | ペンを下ろす |
2 | マウス左ボタン リリース | ペンを上げて、軌跡を消去 |
3 | マウス左ボタンをプレス中にマウスを動かす | ペンで軌跡を描画 |
4 | マウス左ボタンをリリース中にマウスを動かす | なにもしない |
簡単にいってしまうと、マウス左ボタンを押している間だけマウスの軌跡を描画するアプリです。ボタンを離すと軌跡は消えます。一筆書きをしている間は軌跡をみることができます。
3. コード
import numpy as np import matplotlib.pyplot as plt from matplotlib.patches import Circle, Arrow fig, ax = plt.subplots() being_pressed = False pre_x = None pre_y = None def on_motion(event): global pre_x, pre_y if None in (event.xdata, event.ydata): # マウスが画面の外にいるときは何もしない #print('x=%d, y=%d, xdata=%f, ydata=%f' %(event.x, event.y, event.xdata, event.ydata)) return if being_pressed: ax.add_patch(Circle((event.xdata, event.ydata), 0.01, color='r')) if None not in (pre_x, pre_y): # pre_x と pre_y が存在するとき矢印を描画 ax.add_patch( Arrow(pre_x, pre_y, event.xdata-pre_x, event.ydata-pre_y, linestyle='solid', color='blue', width=0.02)) pre_x = event.xdata pre_y = event.ydata fig.canvas.draw() # 必須! def on_button_press(event): print('button pressed') global being_pressed being_pressed = True def on_button_release(event): print('button released') global being_pressed, pre_x, pre_y being_pressed = False pre_x = None pre_y = None # reset patches for patch in reversed(ax.patches): patch.remove() fig.canvas.draw() # 必須! cid_1 = fig.canvas.mpl_connect('motion_notify_event', on_motion) cid_2 = fig.canvas.mpl_connect('button_press_event', on_button_press) cid_3 = fig.canvas.mpl_connect('button_release_event', on_button_release) plt.show()
4. コードの説明
マウスの左ボタンを押しているかどうかを being_pressed 変数で管理することとしています。pre_x, pre_y はボタンが押されているときの、マウスを動かす前のマウスの座標を保存するために定義しています。(pre_x, pre_y はボタンが押されてないときは存在しないので None で初期化します)
ボタンが押されたときに独自関数(on_button_press関数)を呼んでほしいため
fig.canvas.mpl_connect('button_press_event', on_button_press)
としています。これにより canvas で button_press_event が発生すると on_button_press 関数が呼ばれるようになります。同様に、 ボタンが離されたとき(button_release_event イベントが発生したとき)には on_button_release、 マウスが動いたとき(motion_notify_event イベントが発生したとき)には on_motion 関数が呼ばれるように設定します。
話はそれますが、matplotlib で他にどのようなイベントが提供されているか興味のある方は、以下を参照してください。
https://matplotlib.org/users/event_handling.html より抜粋
Event name | Class and description |
---|---|
'button_press_event' | MouseEvent - mouse button is pressed |
'button_release_event' | MouseEvent - mouse button is released |
'draw_event' | DrawEvent - canvas draw (but before screen update) |
'key_press_event' | KeyEvent - key is pressed |
'key_release_event' | KeyEvent - key is released |
'motion_notify_event' | MouseEvent - mouse motion |
'pick_event' | PickEvent - an object in the canvas is selected |
'resize_event' | ResizeEvent - figure canvas is resized |
'scroll_event' | MouseEvent - mouse scroll wheel is rolled |
'figure_enter_event' | LocationEvent - mouse enters a new figure |
'figure_leave_event' | LocationEvent - mouse leaves a figure |
'axes_enter_event' | LocationEvent - mouse enters a new axes |
'axes_leave_event' | LocationEvent - mouse leaves an axes |
話を戻して、以下のコードで現在のマウスのx, y座標に丸を描画します。0.01 は 丸の大きさです。何度か試して 0.01 が良さそうだったのでそうしています。
ax.add_patch(Circle((event.xdata, event.ydata), 0.01, color='r'))
以下のコードで、前のマウスのx, y座標と現在の座標に矢印を描画します。0.02 は線の太さです。これも試した結果これくらいがよさそうだったのでそうしています。
ax.add_patch( Arrow(pre_x, pre_y, event.xdata-pre_x, event.ydata-pre_y, linestyle='solid', color='blue', width=0.02))
これら 0.01 や 0.02 を計算でだせるようにするのは私の宿題としておきます。
5. 実行結果
6. まとめ・考察
matploblib でお絵かきアプリっぽいものを作りました。
マウスがクリックされたら〜するとか、マウスが動いたら〜するなどいろいろ応用できるのではないかと思います。筆跡を時系列データとして機械学習して分類するのもおもしろそうです、夏休みの自由研究にもできそうではないでしょうか。
7. 今後の予定
個人的にはこれを使って軌跡に囲まれた領域のデータの点だけを取得して、それらのデータの可視化したいです(実はもうしたけどそれは次回以降、書きます)。
8. 宿題
matplotlib の Arrow の太さとか Circle の大きさを計算できるように仕様を把握する
それでは、おやすみなさい。