Pythonで〇×ゲームのAIを一から作成する　その68　ipywidgets と ipympl の使い方とイベント駆動型プログラミング

# 目次と前回の記事

https://qiita.com/ysgeso/items/2381dd4e3283cbed49a0

https://qiita.com/ysgeso/items/8ac83f1b50b2706b8319

## これまでに作成したモジュール

以下のリンクから、これまでに作成したモジュールを見ることができます。

https://github.com/ysgeso/marubatsu/blob/master/068/marubatsu.py

https://github.com/ysgeso/marubatsu/blob/master/068/ai.py

## ルールベースの AI の一覧

ルールベースの AI の一覧については、下記の記事を参照して下さい。

https://qiita.com/ysgeso/items/10d1d01192c014173e4b

# 〇×ゲームの GUI の入力機能

前回の記事では、〇×ゲームの __ゲーム盤__ を __画像で表示__ するという、__GUI__ の __出力機能__ を __実装__ しました。今回の記事では、__GUI__ の __入力機能__ を実装しますが、そのためには、〇×ゲームの __具体的__ な __入力機能__ について __検討__ する __必要__ が __あります__。まず、どのような入力機能が必要になるかについて少し考えてみて下さい。

## 〇×ゲームの GUI の入力機能の検討

〇×ゲームの __入力機能__ について __最低限必要__ になるのは、__以下の機能__ です。

* __着手__ を行う __座標を入力__ する __機能__

他にも、必須ではありませんが、__下記__ のような __機能__ があると __便利__ でしょう。

* __ゲーム__ を最初から __やり直す__ 機能
* __直前の着手__ を __取り消す__、__待った__ を行う機能
* 決着がついたゲームの __対戦経過__ を __表示__ する __リプレイ__ の機能

__本記事__ では、__上記の機能__ を __実装__ することにしますが、他にも、あると良さそうだと思った機能があれば列挙して実装してみて下さい。また、本記事でも上記以外の機能を思いついた場合は、実装することにします。

## 具体的な入力機能の検討

次に、__それぞれ__ の __機能__ について、__具体的__ にどのような方法で入力を行うについて __検討__ を行う __必要__ が __あります__。それぞれについて、少し考えてみて下さい。

### 着手を行う座標を入力する機能

__CUI__ では、__着手__ を行う __座標__ を、__テキストボックス__ に座標を表す __文字列で入力__ しました。__GUI__ では、__ゲーム盤__ が __画像で表示__ されるので、__着手__ を行いたい __マス__ を __直接マウス__ で __クリック__ するのが最も __直観的__ で __わかりやすく__、__入力の手間__ も __簡単__ だと思います。

:::note
他にも、__メニュー__ から __座標を選択__ するなどの __方法__ も __考えられます__。本記事よりも良い方法が思いついた方は、その方法で実装してみると良いでしょう。
:::

__この機能__ を __実装__ するためには、__下記__ のようなプログラムを __記述__ する __必要__ が __あります__。

* __ゲーム盤__ の __画像の上__ で __クリック__ した時に、__以下の処理__ を __行う__
    * __クリック__ した __ゲーム盤__ の __マスの座標__ を __知る__
    * __その座標__ に __着手__ を __行う__
* __ゲーム盤__ の __画像__ を __更新__ する

__matplotlib__ は、__上記__ のような __処理__ を __行うための機能__ を __備えています__ が、残念ながら、__JupyterLab__ 上で __matplotlib__ を使って __画像を描画__ した場合は、そのままでは __その機能__ を __利用__ することは __できません__。__具体的__ には、__JupyterLab__ で __matplotlib__ の __`plt.show()`__  で __描画した画像__ は、__後から__ その __描画内容__ を __変更__ したり、__クリック__ などの __操作__ によって、__特定の処理__ を行うプログラムを __記述__ することは __できません__。実際に、[前回の記事](https://qiita.com/ysgeso/items/8ac83f1b50b2706b8319#play-%E3%83%A1%E3%82%BD%E3%83%83%E3%83%89%E3%81%AE%E4%BF%AE%E6%AD%A3)で __`play`__ メソッドに〇×ゲームの __画像を描画__ する __機能を実装__ しましたが、その際には、__着手__ を __行うたび__ に、__新しいゲーム盤の画像__ を __描画__ しました。

__上記__ のような __処理を行う__ ためには、今回の記事で紹介する、__ipywidgets__ と __ipympl__ などの __モジュール__ を __インストール__ して __利用__ する __必要__ が __あります__。

:::note
本記事では取り扱いませんが、__JupyterLab 以外__ の、__Python__ の __プログラムを実行__ する __環境__ で __matplotlib__ を __実行__ した場合や、__JupyterLab__ で上記の __ipywidgets__ と __ipympl 以外__ の __モジュールを利用__ した場合は、__本記事__ で紹介するプログラムとは __異なる記述__ を __行う必要__ が __あります__。
:::

### その他の機能

「__ゲームを最初からやり直す__」、「__待ったを行う__」、「__リプレイを表示する__」などの __機能__ は、__画面上__ にそれらの __機能を呼び出す__ ための __ボタン__ などを __描画__ し、マウスで __ボタンをクリック__ した際に __その機能を実行__ するという __方法__ で __実装__ するのが __一般的__ です。

Python には、そういった __GUI__ の __アプリケーション__ を __作成__ するための __様々なモジュール__ が __あります__ が、本記事では、__JupyterLab 上__ で、ボタンやテキストボックスなどの __GUI__ による __入力処理__ を行うことができる __ipywidgets__ という __モジュール__ を __利用__ することにします。

# ipywidgets による GUI

__ipywidgets__ は、__IPython 上__ で __widgets__ を __利用__ できるようにするための __モジュール__ です。

__IPython__ とは、__Python__ の __プログラム__ を、__対話型に実行__ するための __シェル__（プログラム）のことで、__JupyterLab__ は __ウェブブラウザで動作__ するように __IPython を拡張__ したものです。

なお、__Python__ のプログラムを __実行__ するための __シェル__ は、__IPython 以外__ にも __あります__ が、__IPython__ は __それらの中__ でも、__便利な機能__ が __豊富に用意__ されているため、 __良く使われます__。

:::note
__IPython__ のように、__ユーザ__ と、__コンピュータ__ の __OS__ や __Python__ などの __プログラム言語__ を __つなぐ役割__ を持つ __プログラム__ の事を __シェル__ と呼びます。

__Windows__ の場合は、__コマンドプロンプト__ や、__Windows Power Shell__ が __ユーザー__ と __Windows の OS__ を __つなぐ役割__ を持つ __シェル__ です。

__JupyterLab__ は、__セル__ に __入力__ した __プログラム__ を __Python に伝え__、その __実行結果__ を __表示__ するという形で __ユーザ__ と __Python__ を __つなぐ処理__ を __行います__。
:::

__ウィジェット__（widget）[^1]とは、__ボタン__ や __テキストボックス__ などの、__GUI__ を __構成する部品__ のことを __表す用語__ です。__ipywidgets__ は、その名前が示すように、__IPython__ 上で、__ウィジェット__ を __扱う__ ことが __できるようにする__ ための __モジュール__ です。

ipywidget の詳細については、下記のリンク先を参照して下さい。

https://ipywidgets.readthedocs.io/en/stable/index.html

なお、__ipywidget__ は非常に __多彩な機能__ を持つ __モジュール__ ですが、__本記事__ ではその中で、__〇×ゲームの GUI__ を __作るために必要な機能__ を __主に紹介__ します。__ipywidget__ は __有名なモジュール__ で、__使い方__ を __説明__ する __ウェブページ__ は __多数ある__ ので、他の機能について知りたい方は調べてみると良いでしょう。

## ipywidgets のインストール

__ipywidgets__ は、__matplotlib と同様__ の、__下記の手順__ で、__Anaconda Navigator__ から __インストール__ することが __できます__。

1. __Anaconda Navigator__ を立ち上げる
2. 左の __Environments__ を __クリック__ して表示される __仮想環境の一覧__ から __marubatsu__ を __クリック__ すると、右に __marubatsu__ の __仮想環境__ に __インストール__ されている __モジュールの一覧__ が __表示__ される
3. __上のメニュー__ から「__Not installed__」を __選択__ し、その右の「__Search packages__」と表示された __テキストボックス__ に「__ipywidget__」を __入力__ する
4. 下に __ipywidget が表示__ されるので、その __左のチェックボックス__ を __チェック__ し、下の「__Apply__」__ボタン__ を __クリック__ する。    
5. 「__Install packages__」という __パネルが表示__ され、しばらく待つと「__Apply__ 」__ボタン__ がクリックできるようになるので __クリック__ する。 

## ipywidgets のインポート

__ipywidgets__ を __利用__ するためには、__インポート__ を __行う必要__ が __あります__。本記事では、下記のプログラムのように、__`widgets`__ という __名前__ で __インポート__ することにします。

```python
import ipywidgets as widgets
```

## モジュールのバージョン

__本記事__ では __ipywidgets__ の __バージョン 8.1.2__ を __インストール__ して __利用__ します。__モジュール__ の __バージョンが異なる__ と、__モジュールの機能__ が __若干異なる__ 場合があります。__本記事__ の __プログラムを実行__ した際に、__エラーが発生__ したり、__表示が異なる__ 場合は、__ipywidgets__ の __バージョンを確認__ して下さい。例えば、__7 以前__ の __バージョン__ では __利用できない機能__ を使ってプログラムを __記述する可能性__ が __あります__。

:::note
__8.1.2 以降__ のバージョンであれば、__おそらく問題なく動作する__ と思います。
:::

### モジュールのバージョンの確認方法

__モジュール__ の __バージョン__ を __確認する方法__ をいくつか __紹介__ します。

__ほとんどのモジュール__ では、__`__version__`__ という __名前__ の __特殊属性__ に、__モジュール__ の __バージョン__ を __表す文字列__ が __代入__ されています。従って、__下記__ のプログラムのように、モジュールの __`__version__` 属性__ を __表示__ することで __バージョン__ を __確認__ することが __できます__。

```python
print(widgets.__version__)
```

実行結果

```
8.1.2
```

__Anaconda Navigator__ から、__下記の手順__ で、__仮想環境__ に __インストール__ された __モジュール__ の __バージョン__ を __確認__ することが __できます__。

1. __Anaconda Navigator__ を立ち上げる
2. 左の __Environments__ を __クリック__ して表示される __仮想環境の一覧__ から __marubatsu__ を __クリック__ すると、右に __marubatsu__ の __仮想環境__ に __インストール__ されている __モジュールの一覧__ が __表示__ される
3. __上のメニュー__ から「__Installed__」を __選択__ し、右の「__Search packages__」と表示された __テキストボックス__ にモジュール名を __入力__ すると、下図のように __バージョンが表示__ される

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/289e2f8a-2d99-6a0f-72e9-2db5b2477a7f.png" width="500">

### モジュールのバージョンの変更方法

何らかの理由で、インストールされている __モジュール__ の __バージョン__ を __変更__ したい場合は、__Anaconda Navigator__ を使って __下記の手順__ で __変更__ することが __できます__。

1\. 上記の画面で、左にある __緑色__ の __チェックマーク__ を __クリック__ すると、__下図__ のような __メニューが表示__ される

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/c84c7d88-59e6-81fb-ada8-7579e9817adc.png" width="300">

2\. 「__Mark for specific version installation__」を __クリック__ すると、__下図__ のような、__バージョン__ を __選択__ する __メニューが表示__ される

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/423dc393-5824-2e94-18d2-ba589563b378.png" width="300">

3\. __バージョンを選択__ し、下記の __Apply ボタン__ を __クリック__ する。「__Install packages__」という __パネルが表示__ され、しばらく待つと「__Apply__ 」__ボタン__ がクリックできるようになるので __クリック__ する

:::note
本記事では説明しませんが、__conda__ や __pip__ などの __コマンド__ を使って、__異なるバージョン__ を __インストールし直す__ ことが __できます__。
:::

:::note
現在よりも __新しいバージョン__ を __インストール__ することを「__バージョンアップ__」、__古いバージョン__ を __インストール__ することを「__バージョンダウン__」と呼びます。
:::

## イベント駆動型プログラミング

__これまで__ に __本記事__ で __記述__ してきた __プログラム__ は、__実行__ すると __記述__ した __プログラムの処理__ が __順番に行われ__、__すべての処理__ が __終了__ した時点で、__プログラムの実行__ も __終了__ します。そのような __プログラミングの手法__ の事を、__処理の流れ__（flow）を __記述__ して __実行__ することから、__フロー駆動型プログラミング__ と呼びます。

それに対して、__ボタン__ や __メニュー__ などによって __操作__ を行う __GUI__ の __アプリケーション__ では、__一般的__ に __プログラムの実行後__ に、__ボタン__ などの __GUI を操作するまで__ の間は __待機し__、__ボタン__ などの __GUI__ に対するの __操作__ が __行われた時__ に __処理が行われる__ ようになっています。そのようなプログラムは、__フロー駆動型プログラミング__ の __手法で記述__ することは __困難__ なので、__GUI__ の __アプリケーション__ は、__イベント駆動型プログラミング__ という __手法__ が __良く使われます__。また、__本記事__ で __利用__ する、__ipywidgets__ は __その手法__ を __用います__。

### イベント駆動型プログラミングの用語

__イベント駆動型プログラミング__ で使われる __主な用語__ について最初に __説明__ します。

| 用語 | 意味 |
|:--|:--|
| __ウィジェット__ | ボタンやテキストボックスなどの、__GUI__ を __構成する部品__<br>__コントロール__ などと __呼ぶ場合__ もある |
| __イベント__  |  __ウィジェットに__ 対して行われる __操作__ |
| __イベントの発生__   | __ウィジェットに__ 対して __操作が行われたこと__ を表す  |
| __イベントハンドラ__  | __イベントの発生__ に __応じた処理__ を行う __プログラム__  |
| __イベントループ__  | __イベントの発生__ に __応じて__、__適切__ な __イベントハンドラ__ を __実行する処理__ を __行うプログラム__  |

### イベント駆動型プログラミングによる GUI アプリの記述方法

__イベント駆動型プログラミング__ の __手法__ を __利用__ した __GUI__ の __アプリケーション__ は、__下記__ のような __手順__ で __プログラムを記述__ します。

1. __画面内__ の適切な位置に __ウィジェットを配置__ する __処理を記述__ する
2. __それぞれ__ の __ウィジェット__ に対して、__下記の処理__ を __記述__ する 
    1. __ウィジェット__ を __操作__ した際に __行う処理__ を __記述__ する、__イベントハンドラ__ というプログラムを __記述__ する
    2. __ウィジェット__ に __イベントハンドラ__ を __結び付ける__

__具体例__ として、__ipywidgets__ で、__ボタンをクリック__ すると、__画面__ に __メッセージを表示__ する __GUI__ のプログラムを __記述__ する __方法__ を __紹介__ します。

#### ボタンのウィジェットを作成して配置する

__ipywidgets__ には __様々な種類__ の __ウィジェット__ を __作成__ し、__JupterLab 上__ に __表示__ する __機能__ があり、__ウィジェット__ の __種類ごと__ に、__ウィジェットを作成__ する __関数が用意__ されています。例えば、__`Button`__ という __関数__ によって、__ボタン__ の __ウィジェット__ を __作成__ することが __できます__。__`Button`__ の __返り値__ は、__作成__ された __ウィジェット__ を表す __オブジェクト__ です。

__作成__ した __ウィジェット__ は、__`display`__ という __関数__ を使って __JupyterLab__ に __描画__ することが __できます__。__これまでに利用__ してきた __`print`__ は、__文字列を表示__ する __機能__ を持ちますが、__`display`__ は、__画像__ や __ウィジェットなど__ を __JupyterLab__ に __描画__ する __機能__ を持つ __関数__ です。

__下記__ は、__ボタン__ の __ウィジェット__ を __作成__ して __描画__ するプログラムです。なお、__キーワード引数 `description`__ は、__ボタン__ に __表示__ する __文字列__ を __設定__ する __実引数__ です。

```python
button = widgets.Button(description='Click me')
display(button)
```

実行結果（下図は、画像なので操作することはできません）

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/7644037a-84de-2b1b-4543-ffd9ad87a726.png" width="150">

:::note warn
__Quiita__ の __記事内__ で __Python__ のプログラムを __実行__ することは __できない__ ので、__本記事内__ の __実行結果__ には、__ウィジェット__ の __画像__ を表示します。__実際のウィジェット__ は、__JupyterLab__ 上で __確認して下さい__。
:::

:::note
__`display`__ は、__IPython__ という __モジュール__ で __定義__ された __関数__ ですが、__VSCode__ の __JupyterLab__ では、__IPython モジュール__ を __インポートしなくても利用できる__ ようです。なお、__`display` を実行__ して __エラーが発生__ する場合は、__下記__ のプログラムを __記述__ して __`display`__ を __インポート__ して下さい。

```python
from IPython.display import display
```
:::

__表示__ された __ボタン__ は、__一般的__ なアプリケーションの __ボタンと同様__ に、__マウス__ で __クリック__ することで __ボタンの色__ が __一時的に変化__ しますが、__ボタン__ を __クリックした時__ の __処理__ をまだ __記述していない__ ので、__クリック__ しても __色が変わるだけ__ で __他の処理__ はまだ __行われません__。

__`Button`__ の __詳細__ については、下記のリンク先を参照して下さい。

https://ipywidgets.readthedocs.io/en/stable/examples/Widget%20List.html#button

__ipywidgets__ の __ウィジェットの種類__ については、下記のリンク先を参照して下さい。

https://ipywidgets.readthedocs.io/en/stable/examples/Widget%20List.html

#### イベントハンドラの定義

__ipywidgets__ では、__イベントハンドラ__ は __イベント__（event）を __処理__ する（handle）__機能を持つ関数__ として __定義__ します。今回のプログラムでは、__ボタン__ を __クリック__ するという __操作__ を __行った時__ に __メッセージを表示__ するという __処理を行う__ ので、__その処理__ を __行う__ ための __イベントハンドラ__ を __定義__ する __必要__ が __あります__。

__ipywidgets__ では、__ウィジェットの種類__ によって、__イベントハンドラ__ の __定義の方法__ が __若干異なります__。__ボタン__ の __ウィジェット__ に対する __イベントハンドラ__ は、__仮引数__ を __1 つ持つ関数__ として __定義__ します。__その仮引数__ には、__イベントハンドラ__ が __呼び出された際__ に、__ボタン__ の __ウィジェット__ を表す __オブジェクトが代入__ されます。

:::note
この後で紹介する例を含め、__ボタン__ に対する __イベントハンドラ__ の __仮引数の情報__ が __必要になる__ ことは __あまりない__ かもしれませんが、__仮引数__ を __記述せず__ に __イベントハンドラを定義__ すると __エラーが発生__ するので、__必ず記述__ する __必要__ が __あります__。
:::

:::note
__ボタン以外__ の __ウィジェット__ の __イベントハンドラ__ の __仮引数__ には、__別の情報__ が __代入__ されます。具体例は今後の記事で紹介する予定です。
:::

__下記__ は、__画面__ に __"ボタンがおされたよ！"__ という __メッセージを表示__ する __イベントハンドラ__ の __定義__ の一例です。__イベントハンドラ__ の __関数__ の __名前__ は、__ボタン__（button）が __クリックされた時__（on clicked）に __行う処理__ なので __`on_button_clicked`__ に、__仮引数__ の __名前__ は、__ボタン__ の __ウィジェットの情報__ が __代入__ されるので　__button__ の __頭文字__ をとって __`b`__ としました。なお、__これらの名前__ に __決まりはない__ ので __好きな名前__ に __変更__ しても __かまいません__。

```python
def on_button_clicked(b):
    print("ボタンがおされたよ！")
```

#### ウィジェットへのイベントハンドラの結び付け

__イベントハンドラ__ を __定義しただけ__ では、__ボタン__ を __クリック__ しても __何も起きません__。__ボタン__ を __クリック__ した際に __イベントハンドラを実行する__ ようにするためには、__ウィジェット__ と __イベントハンドラ__ を __結び付ける__ という __処理を記述__ する __必要__ が __あります__。

__ボタン__ の __ウィジェット__ を表す __オブジェクト__ には、__`on_click`__ という、__ウィジェット__ と __イベントハンドラ__ を __結び付ける機能__ を持つ __メソッド__ が用意されており、__下記__ のプログラムのように、__実引数__ に __イベントハンドラ__ を __記述__ して呼び出すことで、__ボタン__ を __クリックした際__ に、__そのイベントハンドラ__ が __呼び出される__ ようになります。

```python
button.on_click(on_button_clicked)
```

__上記__ のプログラムを __実行後__ に、__先程表示__ した __ボタン__ を __クリック__ すると、__クリックするたび__ に、__ボタンの下__ に __下図__ のように __メッセージが表示__ されるようになります。なお、__下図__ は __3 回クリック__ を行った __場合の図__ です。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/ef3fc75d-a088-1a05-e70f-29b54070a599.png" width="150">

:::note
__`on_click`__ メソッドは、__ボタン__ の __ウィジェットのみ__ で __利用__ できます。__他のウィジェット__ に __イベントハンドラ__ を __結び付ける__ 場合は __`observe`__ という __メソッド__ を __利用__ します。具体的な方法については、今後の記事で紹介する予定です。
:::

:::note
__上記__ のプログラムでは __ボタンの下__ に __メッセージが表示__ されますが、__メッセージ__ を __別の場所__ に __表示__ することも __できます__。具体的な方法は今後の記事で紹介します。
:::

__ipywidgets__ の __ボタン__ の __widgets__ を __利用__ することで、〇×ゲームの __リセットなど__ の __機能__ を __実装__ することが __できます__。具体的な実装は次回の記事で行います。

### イベント駆動型プログラミングの仕組み

下記は、__これまで__ の、__フロー駆動型プログラミング__ と、__イベント駆動型プログラミング__ の __違い__ を表す表です。__フロー駆動型__ の場合は、__プログラムの実行__ が __終了した時点__ で、__プログラムの処理__ が __すべて完了__ します。ここでいう __プログラム__ の __実行の終了__ とは、JupyterLab の __セルに入力__ した __プログラムの実行__ が __終了__ し、__次のプログラム__ を __セルに入力__ して __実行できる状態になる__ という __意味__ です。

| 駆動型 | 性質 |
|:--|:--|
| __フロー__  | __プログラムの実行__ が __終了した時点__ で、__プログラムの処理__ が __完了する__  |
|__イベント__   | __プログラムの実行__ が __終了した後__ でも、__プログラムの処理__ は __完了しない__  |

__イベント駆動型__ の場合は、__プログラムの実行__ が __終了した後__ も、この後で説明する、__イベントループ__ という __処理__ が __引き続き実行__ されるため、__プログラムの処理__ は __完了しません__。

__イベントループ__ は、__下記__ のような __アルゴリズム__ で __処理__ を行う __プログラム__ のことです。__イベントループ__ は、「__イベント__ が __発生するまで待ち__、__発生したイベント__ に __対する処理__ を __行う__」という処理を __無限に繰り返す__（__ループ__ する）ことが、__名前の由来__ になっています。

1. __ipywidgets__ に __登録__ された __イベントハンドラ__ に __対応するイベント__ が __発生しているか__ どうかを __調べる__
2. __イベント__ が __発生していれば__、__対応__ する __イベントハンドラ__ を __呼び出す__。__発生していなけれ__ ば __何もしない__
3. __手順 1__ へ __戻る__

__ipywidgets__ を __インポート__ すると、__自動的__ に __上記__ の __イベントループ__ の __処理__ を行うプログラムが __実行__ されます。__その際__ に、__JupyterLab__ の __セル__ に __プログラム__ を __入力__ して __実行できる__ ので、__イベントループ__ の __処理__ が __行われている__ ことが __実感できない__ かもしれませんが、__実際__ に、__見えない所__ で __イベントループの処理__ が __行われています__。

__最初__ は、__ipywidgets__ に __イベントハンドラ__ は一つも __登録されていない__ ので、__イベントループ__ は、__実質的__ に __何の処理__ も __行いません__。

先程のように、ボタンなどの __ウィジェットを作成__ し、__イベントハンドラ__ と __結びつける__ ことで、__ipywidgets__ に __イベントハンドラが登録__ され、__以後__ は、__イベントハンドラ__ に __対応するイベント__ が __発生するたび__ に、__イベントループの処理__ によって、__イベントハンドラが実行される__ ようになります。また、__対応するイベント__ が __発生しない間__ は、__イベントループ__ は __実質的__ に __何の処理__ も __行わない__ ので、__イベント__ が __発生するまでの間__ は __待機状態__ になります。

これが、__イベント駆動型プログラミング__ の __大まかな仕組み__ です。

:::note
__実際__ の __イベントループ__ は、__上記以外__ の __処理__ も __行っています__ が、__イベント駆動型プログラミング__ の __おおまかな仕組み__ を __把握するだけ__ であれば、__上記の処理__ を __理解__ するだけで __十分__ でしょう。
:::

### イベントループと `play` メソッドのループの類似点

__イベントループ__ が __行う処理__ が __ピンとこない人__ が __多いのではないか__ と思いますので、__`Marubatsu`__ クラスの __`play`__ メソッドで行う __処理__ が、__イベントループ__ で行われる __処理__ と __似ている__ ことを __説明__ します。

__イベントループ__ を、__Python 風__ の __プログラム__ で __記述__ すると __以下__ のようになります。

```python
while True
    if ipywidgets に登録されたイベントハンドラに対応するイベントが発生している？:
        対応するイベントハンドラを呼び出す
```

__下記__ は __`play`__ メソッドの中で、__人間どうし__ が __対戦__ を行った際に、__イベントループ__ に __似た処理__ を行う __部分__ を __抜粋__ したプログラムです。

```python
# ゲームの決着がつくまで無限に繰り返す
while self.status == Marubatsu.PLAYING:
   # キーボードからの座標の入力
   coord = input("x,y の形式で座標を入力して下さい。exit を入力すると終了します")
   # この後で、coord に着手を行うプログラムが記述されている
```

__上記__ のプログラムは、__下記__ の点で __イベントループ__ と __似ています__。

* __`input`__ という __組み込み関数__ が __行う処理__ は、__テキストボックス__ への __文字列の入力__ という __イベントが発生__ するまで __待機__ する
* __文字列__ が __入力された場合__ は、入力された __文字列__ に __対応する処理__ を __行う__
* __処理__ の __完了後__ は、再び __次の入力を待つ__ という __処理__ を __繰り返す__

ただし、__下記__ の点では、__イベントループ__ とは __異なります__。

* __`play`__ メソッドの場合は、ゲームの __決着がついた場合__ は __繰り返しを終了__ するので、イベントループと異なり、__無限ループではない__
* __イベントループ__ では、__複数__ の __イベントハンドラ__ を __登録__ することで、__複数__ の __イベント__ に __対応する処理を行う__ ことが __できるが__、__`play`__ メソッドの場合は、__テキストボックス__ への __文字列の入力__ という、__一つのイベント__ にしか __対応できない__

このように、__`play`__ メソッドが __行う処理__ は、イベントループが行う処理と __異なる部分もありますが__、「__イベント__ が __発生するまで待ち__、__発生した場合__ に __対応する処理を行う__」という __点__ では、__同様の処理__ を __行っています__。

:::note
__初心者の方__ には __理解しづらい__ かもしれないので、__上記の違い__ と、__下記の説明__ については、__現時点__ では __よくわからなくても構わない__ と思います。

__`play`__ メソッドが、__イベントループ__ と __異なり__、__一つのイベント__ にしか __対応できない理由__ は、__`input`__ を __実行__ すると、__テキストボックス__ に __文字列__ が __入力されるまで__ は、__他の処理__ を __一切行わない__ からです。このように、__実行__ すると、__特定の条件__ が __満たされるまで__ 処理が __終了しない__ ことを、__処理__ が __ブロックされる__ と呼びます。

__イベントループ__ では、__特定のイベント__ が __発生__ するまで、__処理__ を __ブロックする__ といことは __行っていない__ ため、__複数の種類__ の __イベント__ に __対応__ することが __できます__。

なお、__`play` メソッド__ は、そもそも __文字列を入力__ する __以外__ のイベントに __対応する必要はない__ ので、__そのまま__ でも __問題はありません__。
:::

# ipympl

__matplotlib__ には、__描画__ した __画像__ に対して、下記のような __インタラクティブ__[^2]（interactive）な __処理を行う__ プログラムを __記述するため__ の __機能__ があります。しかし、__JupyterLab__ では、そのままでは __matplotlib__ の __下記の機能__ を __利用__ することは __できません__。

* __描画した画像__ を、__後から更新__ する
* __描画した画像__ に対する、__マウスを押す__ などの __操作__ に __対応する処理__ を表す __イベントハンドラー__ を定義し、__結び付ける__ ことが __できる__

__ipympl__[^3] は、__JupyterLab__ 上で、__matplotlib__ の __インタラクティブな機能__ を __利用できるよう__ にする __モジュール__ です。

__ipympl__ の詳細については、下記のリンク先を参照して下さい。

https://matplotlib.org/ipympl/index.html

## ipympl のインストール

__ipympl__ も、__Anaconda Navigator__ を使って、__下記__ の方法で __インストール__ できます。

1. __Anaconda Navigator__ を立ち上げる
2. 左の __Environments__ を __クリック__ して表示される __仮想環境の一覧__ から __marubatsu__ を __クリック__ すると、右に __marubatsu__ の __仮想環境__ に __インストール__ されている __モジュールの一覧__ が __表示__ される
3. __上のメニュー__ から「__Not installed__」を __選択__ し、その右の「__Search packages__」と表示された __テキストボックス__ に「__ipympl__」を __入力__ する
4. 下に __ipympl が表示__ されるので、その __左のチェックボックス__ を __チェック__ し、下の「__Apply__」__ボタン__ を __クリック__ する。    
5. 「__Install packages__」という __パネルが表示__ され、しばらく待つと「__Apply__ 」__ボタン__ がクリックできるようになるので __クリック__ する。 

## ipympl のバージョン

__ipympl__ を __利用する際__ は、__matplotlib__ の __バージョン__ の __組み合わせの制限__ があり、__制限を満たさない__ 場合は __エラーが発生__ したりしてプログラムが __うまく動作しない__ 場合が __あります__。

__具体的__ な __組み合わせ__ については、__下記のリンク先__ を __参照__ して下さい。

https://matplotlib.org/ipympl/installing.html#compatibility-table

__筆者の PC__ では、__下記のバージョン__ で __動作を確認__ しています。プログラムが __うまく動作しない場合__ は、__先程説明__ した方法で __モジュール__ の __バージョン__ を __確認__ し、__必要__ であれば __バージョン__ を __あわせてみて__ 下さい。

| モジュール | バージョン |
|:-:|:-:|
| __ipympl__ | 0.9.3 |
| __matplotlib__  |  3.8.0 |

__上記__ の __リンク先__ の __jupyter-matplotlib__ と __JupyterLab__ の __バージョン__ についてですが、__VSCode__ の __JupyterLab__ を __利用__ する場合は、__VSCode__ の __拡張機能__ の __Jupyter__ が関係します。__下図__ は、__筆者__ の __VSCode__ の __拡張機能__ の __Jupyter の表示__ です。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/e7f7791c-43cb-aab3-792a-d21084779187.png" width="350">

実はこの記事を最初に執筆した時に、__ipympl__ を __利用__ すると、__matplotlib__ の __画像__ が __うまく表示できない__ という __問題__ が __発生__ しました。試行錯誤した結果、__筆者__ の __VSCode__ の __Jupyter__ が __下図__ のように、__上部__ に __pre-release__ が __表示__ される __プレリリースバージョン__ になっており、__下図__ の「__リリースバージョンへの切り替え__」をクリックして、__VSCode__ を __再起動__ した結果、__画像__ が __うまく表示される__ ようになりました。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/662ee296-c3fe-2d56-90d9-02df2abe58f1.png" width="350">

ただし、__筆者__ の __PC__ で __うまく画像__ が __描画できなかった理由__ が、__プレリリースバージョン__ の __せいか__ どうかは __よくわかりません__。もしかすると、単にVSCode にインストールされていた Jupyter の拡張機能のバージョンが古く、リリースバージョンへの切り替えをクリックしたことでバージョンアップされたことで問題が解決できただけかもしれません。いずれにせよ、この後のプログラムを実行した際に、__matplotlib__ の __画像__ が __うまく描画できない__ 場合は、__VSCode__ の __拡張機能__ の __Jupyter__ をみて、__バージョンアップできる__ のであれば、__しておくこと__ を __お勧めします__。__うまくいかない方__ や、__この件__ について __ご存じの方__ は __コメント__ に __書いていただけると嬉しい__ です。

## ipympl の利用方法

__ipympl__ を __利用する際__ は、これまでのように __モジュール__ を __インポート__ するの __ではなく__、下記の __マジックコマンド__ を __記述__ して __実行する必要__ があります。

```python
%matplotlib widget
```

:::note warn
上記の __%__ で __始まるもの__ は、__JupyterLab__ の __機能を呼び出す__、__マジックコマンド__ と呼ばれるもので、__Python__ の __プログラム__ では __ありません__。__マジックコマンド__ は、__JupyterLab__ の __セルの中__ で __のみ実行__ できるため、__`marubatsu.py`__ などの、__モジュールの中__ に __記述__ すると __エラーが発生__ する点に __注意__ して下さい。
:::

本記事では、他のマジックコマンドについては、必要に応じて紹介する予定ですが、興味がある方は、「Jupyter マジックコマンド」をキーワードに検索してみると良いでしょう。

## `%matplotlib widget` の効果

__`%matplotlib widget`__ を __実行__ することで、__matplotlib__ に __対する処理__ が、__大きく変化__ します。その __変化の内容__ について __説明__ します。

### インタラクティブモード

__matplotlib__ には、__matplotlib__ の __インタラクティブな機能__ を __利用できる__「__インタラクティブモード__（interactive mode）」と、__利用できない__「__非インタラクティブモード__（non interactive mode）」があります。__JupyterLab__ では、__matplotlib__ は __初期設定__ では、__非インタラクティブモード__ になっていますが、__`%matplotlib widget`__ を __実行__ することで、__matplotlib__ が __インタラクティブモード__ に __なります__。

__インタラクティブモード__ では、__matplotlib__ での __画像の描画__ の __処理__ が、__以下__ のように、これまでの __非インタラクティブモード__ と __大きく異なる処理__ が行われるようになります。

* 下記のような、__Figure__ に __対する変更__ が __行われるたび__ に、__画像の描画__ が __更新__ される
    * __Figure の作成__
    * __`plot`__ メソッドなどによる __Artist の登録__
    * __`set_xlim`__ メソッドなどによる __Artist__ の __設定の変更__

### それ以外の効果

__`%matplotlib widget`__ を __実行__ すると、__上記に加え__、__下記__ のような __matplotlib__ で __描画した画像__ を __インタラクティブ__ にする __効果__ が __得られます__。

* __描画__ した __画像の付近__ に、__Figure__ の __タイトル__、__マウスの座標__、__画像に対する操作__ を行うための __ツールバー__ などが表示されるようになる
* __描画__ した __画像__ に __対する__ マウスのクリックなどの __イベント__ と、__イベントハンドラ__ を __結び付ける__ ことが __できる__ ようになる
* __JupyterLab__ の __セルを実行__ しても、__`plt.show()`__ が自動的に __実行されなくなる__
* __`plt.show()`__ を __実行__ しても、__pyplot__ が __管理__ する __Figure__ が __削除されない__

:::note
__上記__ は、__matplotlib__ の __インタラクティブモード__ とは __関係__ は __ありません__。

例えば、__pyplot__ には、__インタラクティブモード__ を __有効__ にする __`ion`__ というメソッドがありますが、__`plt.ion()`__ を __実行__ して __インタラクティブモード__ にしても、__JupyterLab__ で __matplotlib__ を __利用する場合__ は、__それだけ__ では __matplotlib__ の __インタラクティブな機能__ を __利用__ することは __できません__。
:::

__上記の変化__ について、__具体例__ をいくつか挙げて __説明__ します。

## 画像の描画

__`%matplotlib widgets`__ を __実行後__ は、__下記__ のプログラムのように、__Figure__ を __作成するだけ__ で、__画像が描画される__ ようになります。

```python
import matplotlib.pyplot as plt
import japanize_matplotlib

fig = plt.figure(figsize=[3, 3], facecolor="lightblue")
```

実行結果（下図は、画像なので操作することはできません）

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/a45ae197-a44f-f75e-a450-1aabfc52ec53.png" width="200">

:::note
[以前の記事](https://qiita.com/ysgeso/items/ab2770693c947c108dd0#axes)で説明したように、__`%matplotlib widgets`__ を __実行しない__ 場合は、__上記__ のプログラムを __実行__ しても __画像__ は __描画されません__。
:::

また、__描画される画像__ は、__`%matplotlib widgets`__ を __実行しない__ 場合と __比べて__、__下記__ のような __違い__ があります。それぞれについて説明します。

* __画像の上部__ に、__Figure__ の __タイトル__ が表示される
* __画像の下部__ に、__Figure__ に __登録__ された __Axes__ に対する __マウスポインタ__ の __座標が表示__ される（__上記__ の例では __Axes__ は __登録されていない__ ので __表示されない__） 
* __画像の右下__ に、__画像__ の __大きさ__ を __ドラッグ__ して __変更できる__ ことを表す __三角形のマーク__ が __表示__ される
* __画像の上__ に __マウスを移動__ すると、__画像__ を __操作__ するための __ツールバー__ が __表示__ される

### Figure のタイトルとマウスの座標

先程の __画像の上部__ に __表示__ される __Figure 1__ は、__Figure__ の __タイトル__ です。__Figure__ の __タイトル__ は、__Figure__ を __作成した順番__ で、__Figure 1__ のように __自動的__ に __付けられます__。

__Figure__ に __Axes__ が __登録されている__ 場合は、__マウス__ を __Axes__ の __plot の上__ に __移動__ することで、__画像の下部__ に __Axes__ に対する __マウスポインタ__ の __座標が表示__ されるようになります。なお、__Figure の外__ や、Figure の中で、__Axes の範囲外__ に __マウスを移動__ した場合は、__何も表示されなくなります__。

下記は、__`subplots`__ メソッドで、__Axes__ が __登録__ された __Figure__ を __作成__ するプログラムです。__先程の図__ と __区別__ できるように、Figure の __背景色__ を __黄色__ にしました。__実行結果__ は、この __画像の上__ に __マウスを移動__ した場合の図で、__下部の表示__ は __Axes__ の __(0.490, 0.371)<sub>A</sub>__ の __座標の上__ に __マウスポインタ__ が __存在する__ ことを __表します__。なお、__2 つ目__ に __作成__ した __画像__ なので、__Figure__ の __タイトル__ は __Figure 2__ になります。

```python
fig2, ax2 = plt.subplots(figsize=[3, 3], facecolor="yellow")
```

実行結果（下図は、画像なので操作することはできません）

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/feda4928-b357-e4a8-c163-905f1221b4fd.png" width="200">

なお、__画像の左__ に __表示__ される __ボタン__ は、この後で説明する __ツールバー__ です。

### Figure のタイトルの設定方法

__Figure__ の __タイトル__ を __自分で設定__ したい場合は、__下記__ のプログラムのように、__`figure`__ メソッドや、__`subplots`__ メソッドで __Figure を作成__ する際に、__キーワード引数 `num`__ に __タイトルを表す文字列__ を __代入__ して __呼び出す__ ことで行えます。__実行結果__ から、__画像の上部__ に __〇×ゲーム__ が __表示__ されることが __確認__ できます。

```python
fig3, ax3 = plt.subplots(num="〇×ゲーム", figsize=[3, 3], facecolor="lightgreen")
```

実行結果（下図は、画像なので操作することはできません）

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/fe7506f8-4c42-5fce-b5b3-b83fb7792edd.png" width="200">

:::note
__`figure`__ メソッドや、__`subplots`__ メソッドの __キーワード引数 `num`__ は、画像の上部のタイトル以外にも、作成した __Figure__ の __識別子__ としても __利用__ されます。

詳細は長くなるので、下記のリンク先を参照して下さい。
:::

https://matplotlib.org/stable/api/_as_gen/matplotlib.pyplot.figure.html

### 画像の大きさの変更と、ツールバー

__画像__ の __右下__ の __三角形のマーク__ を __マウスでドラッグ__ することで、__画像の大きさ__ を __変更__ することが __できます__。また、__画像の上__ に __マウスを移動__ することで、__下図__（先ほどの __`fig2`__ の画像です）のような __ツールバー__ が __画像の内部__ に表示されます。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/feda4928-b357-e4a8-c163-905f1221b4fd.png" width="200">

この __ツールバー__ は、__画像__ の __表示範囲の変更__ や、__ファイルへの保存__ などを行うことができる __ボタン__ ですが、__〇×ゲーム__ では __利用しない__ ので本記事では __詳細__ は __説明しません__。興味がある方は、下記のリンク先を参照して下さい。

https://matplotlib.org/stable/users/explain/figure/interactive.html#interactive-navigation

### タイトルやツールバーなどに関する設定

__Figure__ には、__JupyterLab__ 上に __描画__ される __画像__ を表す __オブジェクト__ が __代入__ されている __`canvas` 属性__ があり、__`canvas` 属性__ に __代入__ された __オブジェクト__ には、__画像の描画__ の __設定__ に関する __下記__ のような __属性が存在__ します。__これらの属性の値__ を __変更__ することで、__タイトル__ や __ツールバー__ の __表示の有無__ などの __設定__ を __変更__ することが __できます__。

| 属性名 | 意味 |
|:--|:--|
| __`header_visible`__[^4]  | __`True`__ の場合に上部（header）の __タイトルを表示__ する|
| __`footer_visible`__[^5]  | __`True`__ の場合に下部（footer）の __マウスの座標を表示__ する|
| __`toolbar_visible`__  | __`True`__ の場合に __ツールバーを表示__ する|
| __`resizable`__[^6]  | __`True`__ の場合に、__画像のサイズ__ を __変形できる__  |

__下記__ は、先程 __最後に作成__ した __`fig3`__ に対して、__上記__ の __すべて__ を __`False`__ に __設定__ するプログラムです。__下記__ のプログラムを実行すると、__実行結果__ のように、__先程作成__ した __画像__ の __タイトルなど__ が __表示されなく__ なります。また、__マウスのドラッグ__ による __画像__ の __サイズの変更__ を行うことも __できなくなります__。__JupterLab__ 上で、__実際に確認__ してみて下さい。

```python
fig3.canvas.toolbar_visible = False
fig3.canvas.footer_visible = False
fig3.canvas.header_visible = False
fig3.canvas.resizable = False
```

実行結果（下図は、画像なので操作することはできません）

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/c443826d-3fd5-93be-a2d7-30afa2cad614.png" width="200">

## `plt.show()` に関する注意点

:::note warn
__`%matplotlib widget`__ を __実行__ した場合は、__以下__ のような理由で、__`plt.show()`__ は基本的に __実行しません__。__2 つ目__ の __現象__ に関しては、__この後__ で __説明__ します。

* __`plt.show()`__ を __実行しなくても__、__Figure__ を __作成__ したり、__Figure__ の __画像の内容__ が __変化__ するような __処理を行う__ と、__自動的__ に __画像の描画__ が __更新される__
* __`plt.show()`__ を __実行__ すると、__同じ画像__ が __同時__ に __複数描画__ されてしまう
:::

## `plt.show()` の処理の変化

上記で説明したように、__`%matplotlib widget`__ を __実行__ した場合は、基本的に __`plt.show()`__ を __実行しない__ ので、__下記の説明__ は、__現時点__ では __理解する必要__ は __ありません__。意味が分からない方や、あまり興味がない方は、この後の __画像の描画の更新__ に __進んで下さい__。

__`%matplotlib widget`__ を __実行__ している __場合__ と、__そうでない場合__ は、__`plt.show()`__ メソッドを __実行__ した際の __処理__ が __下記__ のように __変化します__。

| `%matplotlib widget` | __`plt.show()`__ に関する処理 |
|:--|:--|
| __実行していない__  |  __pyplot__ が __管理__ する __すべての Figure__ を __描画__ する<br>__pyplot__ が __管理__ する __Figure__ をすべて __削除__ する<br>__JupyterLab__ の __セルを実行__ すると、__`plt.show()`__ が __実行される__ |
| __実行した__  | __current Figure のみ__ を __新しく描画__ する<br>__pyplot__ が __管理__ する __Figure__ は __変化しない__<br>__JupyterLab__ の __セルを実行__ しても、__`plt.show()`__ は __実行されない__  |

### pyplot が管理する Figure と current Figure

__上記__ のようなことが起きることを __確認するため__ には、__pyplot__ が __Figure__ を __管理する仕組み__ について __理解__ する __必要__ が __あります__。

__pyplot__ は、__`figure`__ メソッドや __`subplots`__ メソッドで __作成__ した __Figure__ を __管理__ する __機能__ を持っています。__pyplot__ が __管理__ している __Figure__ の __一覧__ は、__下記__ のプログラムのように、__pyplot__ の __`get_figures`__ メソッドで __知る__ ことが __できます__。__このメソッド__ の __返り値__ は、__Figure__ に __割り当てられた ID__ の __番号__ を __要素__ とする __list__ で、先程 __3 つ__ の __Figure を作成__ したので、__実行結果__ には __3 つ__ の __Figure__ に __対応__ する __ID が表示__ されます。

```python
plt.get_fignums()
```

実行結果

```
[1, 2, 3]
```

__pyplot__ が、__管理__ する __Figure__ の中で、__pyplot__ が __現在__（current）__処理の対象__ とする __Figure__ を __current Figure__ と呼びます[^7]。__pyplot__ では、__新しい Figure__ を __作成__ すると、__その Figure__ が __current Figure__ になります。

:::note
本記事では利用しませんが、__pyplot__ には __current Figure__ と __current Axes__ が __何であるか__ を __調べたり__、__別のもの__ に __変更__ する __メソッド__ があります。
:::

#### __`%matplotlib widget`__ を実行した場合の `plt.show()` の処理の確認

__下記__ のプログラムを実行すると、__実行__ した __セルの下__ に、__current Figure__ である、先程作成した、__3 つ目__ の __画像のみ__ が __描画__ されます。

```python 
plt.show()
```

実行結果（下図は、画像なので操作することはできません）

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/c443826d-3fd5-93be-a2d7-30afa2cad614.png" width="200">

また、__下記__ のプログラムを実行することで、__`plt.show()`__ を __実行__ しても、__pyplot__ が __管理__ する __Figure__ が __変化していない__ ことが __確認__ できます。

```python
plt.get_fignums()
```

実行結果

```
[1, 2, 3]
```

__上記__ のプログラムを __実行__ した際に、__実行結果__ に __画像が描画されない点__ に __注目__ して下さい。もし、__JupyterLab__ の __セルを実行__ した際に、__自動的__ に __`plt.show()` が実行される__ のであれば、先程 __`plt.show()`__ を __実行した場合__ と __同様__ に、__セルの下__ に __画像が描画__ されるはずです。また、__下記__ のプログラムのように、__もう一度 `plt.show()`__ を __実行__ した場合は、__`fig3` の 画像__ が __新しく描画__ されます。

このことから、__`%matplotlib widget`__ を __実行__ している __場合__ は、__セルを実行__ しても __自動的__ に __`plt.show()`__ が __実行されなくなっている__ ことが __確認__ できました。

```python 
plt.show()
```

実行結果（下図は、画像なので操作することはできません）

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/c443826d-3fd5-93be-a2d7-30afa2cad614.png" width="200">

#### __`%matplotlib widget`__ を実行していない場合の `plt.show()` の処理の確認

次に、__下記__ のプログラムを __実行__ して下さい。__`%matplotlib inline`__ は、__`%matplotlib widget`__ を __実行する前__ の __状態に戻すマジックコマンド__ です。

```python
%matplotlib inline
```

__上記を実行__ した __後__ で、__下記__ のプログラムのように、__`plt.show()`__ を __実行__ すると、前回までの記事のように、__pyplot__ が __管理__ する __3 つの Figure__ が __すべて__ セルの下に __描画されます__。また、__描画__ される __画像__ は、__下記の点__ が __先程__ と __異なります__。

* __1 つ目__ の __Figure__ には、__Axes__ が __登録されていない__ ので、__画像__ は __描画されない__
* __いずれの画像__ にも、__タイトル__ や __ツールバー__ は __表示されず__、__変形もできない__

```python 
plt.show()
```

実行結果

```
<Figure size 450x450 with 0 Axes>
```

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/37bb4ccb-d92c-a582-2e87-8b2b80596bea.png" width="200"><br>
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/f44f63f6-a4c2-04be-1301-e38ed17021c6.png" width="200">


また、__下記__ のプログラムを実行することで、__`plt.show()`__ によって、__pyplot__ が __管理__ する __Figure__ が __削除__ されたことが __確認__ できます。

```python
plt.get_fignums()
```

実行結果

```
[]
```

## 画像の描画の更新

__この前__ の「__`plt.show()` の処理の変化__」で __記述__ されている __プログラム__ を __実行した人__ と、この前の説明を __読み飛ばして来た人__ では、__JupyterLab__ の __状態が異なる__ ので、__JupyterLab__ を __再起動__ してから、__下記__ のプログラムを __実行__ して __新しい Figure__ を __作成__ して下さい。

```python
%matplotlib widget
import matplotlib.pyplot as plt
import japanize_matplotlib

fig, ax = plt.subplots(figsize=[3, 3])
```

実行結果（下図は、画像なので操作することはできません）

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/fa9b3444-7301-fdff-30cb-9a016def1355.png" width="200">

下記は、__上記__ で __描画__ した __Figure__ の __Axes__ に対して、__`plot`__ メソッドで __線を描画__ するプログラムです。__下記__ を __実行__ すると、__これまでと異なり__、__下記__ のプログラムの __セルの直後__ には __画像__ が __描画されず__、__上記__ で __描画__ した __画像__ が __更新されて__、__直線が描画されます__。

```python
ax.plot([1, 2], [1, 2])
```

実行結果（先程描画した画像が下記のように更新されます）

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/64598fca-f439-f1de-5c3f-b8ae47a33eee.png" width="200">

このように、__`%matplotlib widget`__ を __実行__ した場合に、__既に作成した画像__ に対して __描画などの処理__ を行うと、__新しい画像が作成__ されるの __ではなく__、__既に描画した画像__ が __更新される__ ことになります。また、先程 __`fig3`__ の __タイトル__ や __ツールバー__ の __表示の設定__ を __修正__ した際に、__既に描画__ した __`fig3`__ の __画像__ の __表示の設定__ が __変化__ したのは、__そのため__ です。

:::note warn
__本記事__ の __最初で決めた__、__〇×ゲーム__ の __以下の処理__ は、__`%matplotlib widget`__ を __実行__ することで __記述できる__ ようになります。

* __着手を行う__ ことで、__ゲーム盤__ の __画像の内容__ を __更新__ する
:::


### `plot.show()` を実行すべきでない理由

先程、__`%matplotlib widget`__ を __実行__ した場合は、__基本的__ に __`plt.show()`__ を __実行しない__ と説明しましたが、その __理由の一つ__ を __紹介__ します。

__下記__ は、__`plt.show()`__ を __実行__ し、__セルの下__ に __先程の画像__ を __もう一つ描画__ するプログラムです。__実行結果__ から、__先程と同じ画像__ が __描画__ されることが __確認__ できます。

実行結果（下図は、画像なので操作することはできません）

```python
plt.show()
```

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/64598fca-f439-f1de-5c3f-b8ae47a33eee.png" width="200">

__続けて__、__下記__ のプログラムを __実行__ して、__画像__ に __もう一本の線__ を __描画__ すると、先程描画した __2 つの画像__ の __両方__ に __線が描画__ されることになります。

```python
ax.plot([1, 2], [2, 1])
```

実行結果（下図は、画像なので操作することはできません）

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/16e3e514-cece-8969-5101-97f6b37b4b69.png" width="200">

__画像の内容__ を __更新__ する際に、このように __同じ画像__ を __2 つ描画__ して __同時に更新__ する __必要__ は __一般的__ には __ありません__。これが、__`plt.show()`__ を __実行しない理由__ の一つです。

# matplotlib の Figure に対するイベントハンドラの登録

__今回の記事__ の __最初__ で、__〇×ゲーム__ の __着手を入力__ する __GUI__ を __実装__ するためには、__下記__ のようなプログラムを __記述__ する __必要がある__ と説明しました。

* __ゲーム盤__ の __画像の上__ で __クリック__ した時に、__以下の処理__ を行う
    * __クリック__ した __ゲーム盤__ の __マスの座標__ を __知る__
    * __その座標__ に __着手を行う__

__上記の処理__ を __実現__ するためには、__matplotlib__ で __描画__ した __画像の上__ で __マウスがクリック__ された際に __実行__ される、__イベントハンドラ__ を __定義__ し、その __イベントハンドラ__ を、__画像__ に __結び付ける必要__ が __あります__。

## `mpl_connect` メソッド

先程、__Figure__ には、__JupyterLab__ 上に __描画__ される __画像__ を表す __オブジェクトが代入__ されている __`canvas` 属性__ があると説明し、__そのオブジェクト__ の __いくつかの属性__ を __紹介__ しました。

__`canvas` 属性__ に代入された __オブジェクト__ には、__`mpl_connect`__ という、__JupyterLab__ 上に __描画__ された __matplotlib__ の __画像__ に対する __イベント__ と __イベントハンドラ__ を __結び付け__（connect）、__ipywidgets__ に __登録__ することで、__イベントループ__ で __処理を行う__ ことができるようする __メソッド__ があります。__`mpl_connect`__ を __実行する際__ には、__イベントの種類__ を表す __文字列__ と、__イベントハンドラ__ の __2 つ__ の __実引数__ を __記述__ します。

### イベントハンドラの定義

__`mpl_connect`__ で結びつける __イベントハンドラ__ は、__仮引数__ を __1 つ持つ関数__ として __定義__ する __必要があります__。また、__その仮引数__ には、__発生__ した __イベントの情報__ を表す __オブジェクトが代入__ されます。なお、__仮引数__ の __名前__ は __どのような名前__ を付けても __構いません__ が、__一般的__ には、__`event`__、__`evt`__、__`e`__ などが __良く使われます__。

下記は、__仮引数__ に __代入__ された __オブジェクト__ の __主な属性__ の __一覧__ です。

| 属性名 | 意味 |
|:--|:--|
| __`inaxes`__ | __マウス__ が __Axes の上にあるか__ どうかを表す __bool 型__ のデータ |
|  __`xdata`__、__`ydata`__ | __マウス__ が __Axes の上__ に __ある場合__ は、その __Axes 上__ の __x 座標__ と __y 座標__<br>__マウス__ が __Axes の上__ に __ない場合__ はどちらも __`None` が代入__ される|
| __`x`__、__`y`__ | __Figure__ の __左上の座標__ を __原点__ とした場合の __マウス__ の __x 座標__ と __y 座標__ |
| __`button`__  | __マウス__ の __ボタン__ が __押された場合__ の、__ボタンの種類__  |
| __`key`__  | __キー__ が __押された場合__ の、__キーの種類__  |

イベントの属性の詳細は、下記のリンク先を参照して下さい。

https://matplotlib.org/stable/users/explain/figure/event_handling.html#event-attributes

__下記__ は、__イベント__ が __発生した際__ の、__Axes 上__ の __マウスの座標__ を __表示__ する __イベントハンドラ__ の __定義__ を行うプログラムです。マウス（mouse）が押された（down）際（on）の __イベントハンドラ__ を __想定__ して、__名前__ は __`on_mouse_down`__ としました。

```python
def on_mouse_down(event):
    print(event.xdata, event.ydata)
```

### `mpl_connect` の記述方法

__matplotlib__ の __画像__ には、__マウスを押す__、__キーを押す__ などの、__様々な種類__ の __イベント__ に対して、__それぞれ個別__ に __イベントハンドラ__ を __定義__ して __結びつける__ ことが __できます__。

:::note
先程紹介した __ipywidgets__ の __ボタン__ などの __ウィジェット__ に対して __イベントハンドラ__ を __結び付ける方法__  は、__matplotlib__ の __画像__ に対して __イベントハンドラ__ を __結び付ける方法__ とは __異なる__ ので、__混同しない__ ように __注意__ して下さい。
:::

__`mpl_connect`__ は、__実引数__ に、__イベントの種類__ と、__イベントハンドラ__ を __記述__ することで、__指定__ した __イベントが発生__ した際に、__指定__ した __イベントハンドラ__ が __呼び出され__ て __実行される__ ようになります。__主なイベントの種類__ には、__以下__ のようなものがあります。

| イベントを表す文字列 | イベントの意味 |
|:--|:--|
| __`"button_press_event"`__  |  __マウス__ の __ボタン__ を __押した__ |
| __`"button_release_event"`__  | __マウス__ の __ボタン__ を __離した__  |
| __`"key_press_event"`__  | __キー__ を __押した__  |
| __`"key_release_event"`__ | __キー__ を __離した__  |
| __`"figure_enter_event"`__  |  __Figure の中__ に __マウスが移動__ した |
| __`"figure_leave_event"`__  |  __Figure の外__ に __マウスが移動__ した |

イベントの種類の一覧と詳細については、下記のリンク先を参照して下さい。

https://matplotlib.org/stable/users/explain/figure/event_handling.html#event-connections

:::note
__上記の表から__ わかるように、__マウス__ の __クリックに対応__ する __イベント__ は __存在しません__。__マウス__ の __クリック__ は、__マウス__ の __ボタン__ を __押して__、__同じ場所__ で __離す__ という __操作__ なので、__マウス__ の __クリック__ に __対応する処理__ を __記述__ したい場合は、__以下__ のようなプログラムを __記述する必要__ が __あります__。

* __`"button_press_event"`__ に対する __イベントハンドラ__ で、__マウス__ が __押された座標__ を __記録__ しておく
* __`"button_release_event"`__ に対する __イベントハンドラ__ で、__上記__ で __記録した座標__ と、__マウス__ が __離された座標__ を __比較__ し、__一致していた場合__ に __クリック__ に対応する __処理を行う__

__上記の処理__ の __記述__ は __面倒__ なので、先程はマウスをクリックした際に着手を行うと記述しましたが、__マウス__ が __押された時__ に __着手__ を __行う__ ことにします。
:::

__下記__ のプログラムは、__マウスが押された__ という __イベント__ に、__`on_mouse_down`__ という __イベントハンドラ__ を __結び付ける__ プログラムです。

```python
fig.canvas.mpl_connect("button_press_event", on_mouse_down)
```

__上記__ のプログラムを __実行後__ に、先程作成した __画像__ の __Axes__ の __範囲内__ で __マウス__ を __押すたび__ に、下記の __実行結果__ のように、__画像の下__ に __クリック__ した __Axes の座標__ が __表示__ されます。また、__3 行目__ のように、__Axes__ の __範囲の外__ で __マウスを押す__ と __`None`__ が __表示__ されます。

実行結果（下図は、画像なので操作することはできません）

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/62e09bde-e44b-c90b-a3e9-443e4f0802e7.png" width="200">

なお、先程 __`plt.show()`__ を __実行__ するとで、__2 つの画像__ が __描画__ されていますが、__どちらの画像__ の上で __マウスを押しても__、__`on_mouse_down`__ が __呼び出され__ て __Axes の座標__ が __表示__ されます。ただし、__表示__ されるのは、__最初の画像__ の __下だけ__ で、__2 つ目__ の __画像の下__ には __表示されません__。これは、__`print`__ で行われる __文字列の表示__ が、__matplotlib__ の __画像__ とは __関係のない場所__ に __行われる__ からです。

__上記の方法__ を __利用__ することで、__〇×ゲーム__ の __ゲーム盤の画像__ の上で __マウスを押した__ 際の、__ゲーム盤の座標__ を __知ること__ が __できます__。

# 今回の記事のまとめ

__今回の記事__ では、__ipywidgets__ をインポートし、__ボタン__ などの __ウィジェット__ を __配置する方法__ と、__ウィジェット__ を __操作__ した際に __実行__ する __イベントハンドラの定義__ の __方法__ を説明しました。また、__ipywidgets__ の __仕組__ みとして、__イベントループ__ について __説明__ しました。

次に、__matplotlib__ の __インタラクティブな機能__ を __利用__ するために __必要__ な __ipympl__ の __使い方__ を __説明__ し、__実際__ に __matplotlib__ で __描画__ した __画像の更新__ や、__その上__ で __マウス__ を __押した際__ に処理を行う __イベントハンドラ__ の __定義の方法__ を __説明__ しました。

__次回の記事__ では、__これらの機能__ を使って、__〇×ゲーム__ を __GUI で遊べる__ ようにします。

# 本記事で入力したプログラム

以下のリンクから、本記事で入力して実行した JupyterLab のファイルを見ることができます。

なお、下記のリンクをクリックして、__github__ 上の __JupyterLab__ の __ファイル__ を __見た場合__ は、__ボタンなど__ の __ウィジェット__ は __表示されません__。また、__`%matplotlib widget`__ を __実行した後__ の __セル__ の __matplotlib__ の __画像__ は、__インタラクティブな画像__ には __なりません__。__実際__ の __プログラムの挙動__ を __確認したい人__ は、__下記のファイル__ を __ダウンロード後__ に、__自分のパソコン__ で __実行する必要__ が __あります__。

https://github.com/ysgeso/marubatsu/blob/master/068/marubatsu.ipynb

今回の記事では marubatsu.py は変更していません。

# 次回の記事

https://qiita.com/ysgeso/items/975078999ad86bd38876

[^1]:__widget__ は、__window__ と、__小道具__ を表す __gadget__ を __合成__ してできた __用語__ だと __言われています__
[^2]:__インタラクティブ__（interactive）とは、__双方向の__ という意味を表す英語です
[^3]:__ipympl__ の __mpl__ は __matplotlib__ の __略__ を表します
[^4]:__ヘッダ__（header）とは、__先頭__ の __データ__ のことで、__人間__ を __上から見た__ 時に、__頭__（head）が __最初__ にあることが __由来__ です。__visible__ は、__目に見える__ という意味を表す英語です
[^5]:__フッタ__（footer）とは、__末尾__ の __データ__ のことで、__人間__ を __上から見た__ 時に、__足__（foot）が __最後__ にあることが __由来__ です。なお、__ヘッダ__ と __フッタ__ の __間__ にある、__本体のデータ__ の事を、__体__ を表す __ボディ__ （body）と呼びます
[^6]:__resize__ とは __大きさ__（size）を __変更__ する（re）という意味の英語で、__可能__ を表す __able__ をつけた __resizable__ は、__大きさを変更可能__ という意味を表します
[^7]:__同様__ に、__Axes__ に対しても、__pyplot__ が __現在処理の対象__ とする __current Axes__ があります