Pythonで〇×ゲームのAIを一から作成する　その94　ゲーム木を視覚化する GUI の作成

# 目次と前回の記事

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

https://qiita.com/ysgeso/items/2acd8c5ceb8e9cda7233

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

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

| リンク | 説明 |
|:--|:--|
| [marubatsu.py](https://github.com/ysgeso/marubatsu/blob/master/094/marubatsu.py)  | Marubatsu、Marubatsu_GUI クラスの定義|
| [ai.py](https://github.com/ysgeso/marubatsu/blob/master/094/ai.py)  |  AI に関する関数 |
|  [util.py](https://github.com/ysgeso/marubatsu/blob/master/094/util.py) |  ユーティリティ関数の定義。現在は `gui_play` のみ定義されている |
| [tree.py](https://github.com/ysgeso/marubatsu/blob/master/094/tree.py)  | ゲーム木に関する Node、Mbtree クラスの定義  |
| [gui.py](https://github.com/ysgeso/marubatsu/blob/master/094/gui.py)  |  GUI に関する処理を行う基底クラスとなる GUI クラスの定義  |

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

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

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

# ゲーム木を視覚化する Mbtree_GUI クラスの作成

前回の記事で、GUI に関する処理を行う[基底クラス](https://qiita.com/ysgeso/items/2acd8c5ceb8e9cda7233#%E7%B6%99%E6%89%BFinheritance)となる GUI クラスを作成しましたので、今回の記事ではそれを継承した、ゲーム木の視覚化を行う Mbtree_GUI クラスを定義します。ゲーム木の視覚化する GUI の仕様については、[前回の記事](https://qiita.com/ysgeso/items/2acd8c5ceb8e9cda7233#%E3%82%B2%E3%83%BC%E3%83%A0%E6%9C%A8%E3%82%92%E8%A6%96%E8%A6%9A%E5%8C%96%E3%81%99%E3%82%8B-gui-%E3%81%AE%E4%BB%95%E6%A7%98)を参照して下さい。

## `draw_tree` に関する修正

Mbtree_GUI を定義する前に、`draw_tree` に関する修正を行うことにします。

`draw_tree` は最初は `startnode` で指定したノードからはじまり（start）、指定した深さまでのノードの部分木を描画していたので、仮引数の名前を `startnode` にしましたが、その後の修正によって、__`startnode` の親ノードも描画する__ ようになったため、`startnode` という名前を変えたほうが良いでしょう。現状では、`draw_tree` は、__`startnode` を中心（center）とした部分木を描画__ するので、`startnode` を __`centernode`__ という名前に修正することにします。

また、`draw_tree` が行う処理は、ゲーム木の __部分木（subtree）の描画__ ですが、`draw_tree` という名前では、ゲーム木全体を描画するように勘違いされる可能性が高いでしょう。そこで、メソッドの名前を `draw_subtree` に修正することにします。

また、以後は `draw_tree` が行う処理を、「__`centernode` を中心とする部分木を描画する__」のように表記することにします。

なお、一般的には仮引数の名前やメソッドの名前を変更すると、そのメソッドを利用する他の関数やメソッドを修正する必要が生じますが、幸いなことに、現時点では他の関数やメソッドから `draw_tree` を呼び出す処理をどこにも記述していないので、上記の修正を行っても問題は発生しません。

下記は、そのように `draw_tree` を修正したプログラムです。`startnode` を `centernode` に修正する際は、[以前の記事](https://qiita.com/ysgeso/items/c2d569b1be171fbe4b31#vscode-%E3%81%AE%E3%82%B7%E3%83%B3%E3%83%9C%E3%83%AB%E5%90%8D%E3%81%AE%E5%A4%89%E6%9B%B4%E3%81%AE%E4%BD%BF%E3%81%84%E6%96%B9)で説明した、VSCode の __シンボル名の変更の機能を使うと簡単に行える__ ので、修正した場所を行番号で示す説明は省略します。

<details><summary><strong>修正したプログラム</strong></summary>

```python
from tree import Mbtree
import matplotlib.pyplot as plt

def draw_subtree(self, centernode=None, size=0.25, lw=0.8, maxdepth=2):
    if centernode is None:
        centernode = self.root
    self.calc_node_height(maxdepth)
    width = 5 * (maxdepth + 1)
    height = centernode.height
    parent = centernode.parent
    if parent is not None:
        height += (len(parent.children) - 1) * 4
        parent.height = height
    fig, ax = plt.subplots(figsize=(width * size, height * size))
    ax.set_xlim(0, width)
    ax.set_ylim(0, height)   
    ax.invert_yaxis()
    ax.axis("off")        
    
    nodelist = [centernode]
    depth = centernode.depth
    while len(nodelist) > 0 and depth <= maxdepth:        
        dy = 0
        if parent is not None:
            dy = parent.children.index(centernode) * 4
        childnodelist = []
        for node in nodelist:
            if node is None:
                dy += 4
                childnodelist.append(None)
            else:
                dx = 5 * node.depth
                node.draw_node(ax=ax, maxdepth=maxdepth, size=size, lw=lw, dx=dx, dy=dy)
                dy += node.height
                if len(node.children) > 0:  
                    childnodelist += node.children
                else:
                    childnodelist.append(None)
        depth += 1
        nodelist = childnodelist
        
    if parent is not None:
        dy = 0
        for sibling in parent.children:
            if sibling is not centernode:
                sibling.height = 4
                dx = 5 * sibling.depth
                sibling.draw_node(ax, maxdepth=sibling.depth, size=size, lw=lw, dx=dx, dy=dy)
            dy += sibling.height
        dx = 5 * parent.depth
        parent.draw_node(ax, maxdepth=maxdepth, size=size, lw=lw, dx=dx, dy=0)
    
        node = parent
        while node.parent is not None:
            node = node.parent
            node.height = height
            dx = 5 * node.depth
            node.draw_node(ax, maxdepth=node.depth, size=size, lw=lw, dx=dx, dy=0)
            
Mbtree.draw_subtree = draw_subtree
```
</details>

<details><summary><strong>修正箇所</strong></summary>

```diff_python
from tree import Mbtree
import matplotlib.pyplot as plt

-def draw_tree(self, startnode=None, size=0.25, lw=0.8, maxdepth=2):
+def draw_subtree(self, centernode=None, size=0.25, lw=0.8, maxdepth=2):
-   if startnode is None:
+   if centernode is None:
-       startnode = self.root
+       centernode = self.root
    self.calc_node_height(maxdepth)
    width = 5 * (maxdepth + 1)
-   height = startnode.height
+   height = centernode.height
-   parent = startnode.parent
+   parent = centernode.parent
    if parent is not None:
        height += (len(parent.children) - 1) * 4
        parent.height = height
    fig, ax = plt.subplots(figsize=(width * size, height * size))
    ax.set_xlim(0, width)
    ax.set_ylim(0, height)   
    ax.invert_yaxis()
    ax.axis("off")        
    
-   nodelist = [startnode]
+   nodelist = [centernode]
-   depth = startnode.depth
+   depth = centernode.depth
    while len(nodelist) > 0 and depth <= maxdepth:        
        dy = 0
        if parent is not None:
-           dy = parent.children.index(startnode) * 4
+           dy = parent.children.index(centernode) * 4
        childnodelist = []
        for node in nodelist:
            if node is None:
                dy += 4
                childnodelist.append(None)
            else:
                dx = 5 * node.depth
                node.draw_node(ax=ax, maxdepth=maxdepth, size=size, lw=lw, dx=dx, dy=dy)
                dy += node.height
                if len(node.children) > 0:  
                    childnodelist += node.children
                else:
                    childnodelist.append(None)
        depth += 1
        nodelist = childnodelist
        
    if parent is not None:
        dy = 0
        for sibling in parent.children:
-           if sibling is not startnode:
+           if sibling is not centernode:
                sibling.height = 4
                dx = 5 * sibling.depth
                sibling.draw_node(ax, maxdepth=sibling.depth, size=size, lw=lw, dx=dx, dy=dy)
            dy += sibling.height
        dx = 5 * parent.depth
        parent.draw_node(ax, maxdepth=maxdepth, size=size, lw=lw, dx=dx, dy=0)
    
        node = parent
        while node.parent is not None:
            node = node.parent
            node.height = height
            dx = 5 * node.depth
            node.draw_node(ax, maxdepth=node.depth, size=size, lw=lw, dx=dx, dy=0)
            
Mbtree.draw_subtree = draw_subtree
```
</details>

上記の修正後に、下記のプログラムでルートノードを中心とする、深さ 2 までの部分木を描画することで、実行結果のように `draw_subtree` が正しく動作することが確認できます。

```python
mbtree = Mbtree()
mbtree.draw_subtree(mbtree.root, maxdepth=2)
```

<details><summary><strong>実行結果（縦に長いのでクリックして表示して下さい）</strong></summary>

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/2ac39a33-6dec-38a8-483f-36f1f371e59c.png" width="150">

</details>

:::note
実引数を省略した場合は、ルートノードを中心とする深さ 2 までの部分木を描画するように `draw_subtree` を定義したので、上記のプログラムは下記のプログラムのように記述できますが、そのことを忘れている人がいるかもしれないので、上記では実引数を記述しました。なお、実行結果は上記と同じなので省略します。

```python
mbtree.draw_subtree()
```
:::

## GUI クラスを継承した Mbtree_GUI クラスの定義

GUI クラスを継承した Mbtree_GUI クラスは、下記のプログラムのように定義します。

* __3 行目__：GUI クラスを基底クラスとする、Mbtree_GUI クラスを定義する
* __4 行目__：Mbtree_GUI クラスでは、__〇×ゲームのゲーム木に対する処理を行う必要がある__ ので、〇×ゲームのゲーム木を表す __Mbtree クラスのインスタンスの情報が必要__ になる。そこで、その情報を代入する __`mbtree`__ という仮引数を `__init__` メソッドに追加する。また、Mbtree_GUI で表示する部分木の画像を表す __Figure のサイズを調整__ できるように、__`size`__ という仮引数を追加することにする
* __5、6 行目__：`mbtree` と `size` を、同じ名前の属性に代入する
* __7 行目__：基底クラスである GUI クラスの `__init__` メソッドの処理を __[`super`](https://qiita.com/ysgeso/items/2acd8c5ceb8e9cda7233#%E3%82%AA%E3%83%BC%E3%83%90%E3%83%BC%E3%83%A9%E3%82%A4%E3%83%89%E3%81%AE%E7%9B%AE%E7%9A%84%E3%81%A8%E5%9F%BA%E5%BA%95%E3%82%AF%E3%83%A9%E3%82%B9%E3%81%AE%E3%83%A1%E3%82%BD%E3%83%83%E3%83%89%E3%81%AE%E5%91%BC%E3%81%B3%E5%87%BA%E3%81%97)__ を使って呼び出すことで、__GUI に共通する初期化処理__ を行う
* __9 ~ 22 行目__：`create_widgets` などの、GUI クラスの __[抽象メソッド](https://qiita.com/ysgeso/items/2acd8c5ceb8e9cda7233#%E6%8A%BD%E8%B1%A1%E3%83%A1%E3%82%BD%E3%83%83%E3%83%89%E3%81%A8%E6%8A%BD%E8%B1%A1%E3%82%AF%E3%83%A9%E3%82%B9)__ と同じ名前のメソッドを定義して __[オーバーライド](https://qiita.com/ysgeso/items/2acd8c5ceb8e9cda7233#%E3%83%A1%E3%82%BD%E3%83%83%E3%83%89%E3%81%AE%E3%82%AA%E3%83%BC%E3%83%90%E3%83%BC%E3%83%A9%E3%82%A4%E3%83%89override)__ する。なお、これらのメソッドの処理はこの後で記述する

```python
 1  from gui import GUI
 2
 3  class Mbtree_GUI(GUI):
 4      def __init__(self, mbtree, size=1):
 5          self.mbtree = mbtree
 6          self.size = size
 7          super().__init__()
 8       
 9      def create_widgets(self):
10          pass
11     
12      def create_event_handler(self):
13          pass
14    
15      def display_widgets(self):
16          pass
17
18      def update_gui(self):
19          pass
20
21      def update_widgets_status(self):
22          pass
```

<details><summary><strong>行番号のないプログラム</strong></summary>

```python
from gui import GUI

class Mbtree_GUI(GUI):
    def __init__(self, mbtree, size=1):
        self.mbtree = mbtree
        self.size = size
        super().__init__()
        
    def create_widgets(self):
        pass
    
    def create_event_handler(self):
        pass
    
    def display_widgets(self):
        pass

    def update_gui(self):
        pass

    def update_widgets_status(self):
        pass
```
</details>

:::note
`super().__init__()` を `__init__` メソッドのどこで呼び出すかは、状況によって異なります。Mbtree_GUI クラスの場合は、`self.mbtree` や `self.size` などの情報を、`super().__init__()` の中から呼び出されるメソッドで利用する必要がある（例えば、Figure を作成する際に `self.size` の情報が必要になります）ので、`self.size = size` などの処理を、`super().__init__()` の前に記述する必要があります。

逆に、`super().__init__()` の中で行った処理を利用するプログラムを `__init__` メソッド内に記述する必要がある場合は、それらの処理は `super().__init__()` の後に記述する必要があります。
:::

## `create_widgets` メソッドの定義

次に、GUI クラスの抽象メソッドをオーバーライドする、`create_widgets` などのメソッドが行う処理を具体的に記述する必要があります。

ウィジェットがなければ `Mbtree_GUI` の処理は行えないので、最初にGUI に表示する __ウィジェットを作成する `create_widgets` メソッドを定義__ します。

[前回の記事](https://qiita.com/ysgeso/items/2acd8c5ceb8e9cda7233#%E3%82%B2%E3%83%BC%E3%83%A0%E6%9C%A8%E3%82%92%E8%A6%96%E8%A6%9A%E5%8C%96%E3%81%99%E3%82%8B-gui-%E3%81%AE%E4%BB%95%E6%A7%98)で、Mbtree_GUI では、下記の 4 つのボタンを利用することに決めたので、その __4 つのボタンのウィジェットを作成する必要__ があります。また、ゲーム木は matplotlib の Figure に描画するので、__Figure を作成する必要__ もあります。

|  | ボタンの表示と対応するカーソルキー |
|:--|:-:|
| __親ノードへ移動__  | ←  |
| __一つ前の兄弟ノードへ移動__  | ↑  |
| __一つ後の兄弟ノードへ移動__  | ↓  |
| __子孫ノードの中の先頭のノードへ移動__  | →  |で

### ボタンのウィジェットの作成

まず、4 つのボタンのウィジェットを作成することにします。そのためには、それぞれのボタンのウィジェットを代入する Mbtree_GUI クラスの __属性の名前を決める必要__ があり、本記事ではボタンに表示する矢印の方向から、下記のように命名しました。

| ボタンの表示 | 属性の名前 |
|:--|:--|
| __←__  | `left_button`  |
| __↑__  | `up_button`  |
| __→__  | `right_button`  |
| __↓__  | `down_button`  |

ボタンのウィジェットは、基底クラスである __[GUI クラス](https://qiita.com/ysgeso/items/2acd8c5ceb8e9cda7233#gui-%E3%82%AF%E3%83%A9%E3%82%B9%E3%81%AE%E5%AE%9A%E7%BE%A9)__ の __`create_button`__ メソッドを使って作成できるので、`create_widgets` は下記のプログラムのように記述できます。なお、本記事ではボタンの幅を 100 ピクセルに設定しましたが、自由に変更してもかまいません。

```python
def create_widgets(self):
    self.left_button = self.create_button("←", 100)
    self.up_button = self.create_button("↑", 100)
    self.right_button = self.create_button("→", 100)
    self.botton_button = self.create_button("↓", 100)
    
Mbtree_GUI.create_widgets = create_widgets
```

### Figure の大きさの計算

部分木を描画する __Figure を作成するため__ には、__Figure の大きさを決める必要__ があります。どのようにしてその大きさを決めることができるかについて少し考えてみて下さい。

Mbtree_GUI では、`draw_subtree` を使って、下図のような部分木を描画します。赤丸が、`draw_subtree` で描画を行う際に仮引数 `centernode` に代入したノードです。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/4f335afc-b034-069b-a995-83158e558021.png" width="350">

`draw_subtree` が描画する __部分木__ は、`draw_subtree` の仮引数 __`centernode` と `maxdepth` に代入される値によって大きく変化__ します。例えば、上図の部分木と、先程描画したルートノードを中心とする深さ 2 までの部分木では、__縦幅も横幅も大きく異なります__。

__部分木の描画__ は、Figure に登録された __Axes に対して行います__。そのため、__Axes の表示範囲__ を __`centernode` と `maxdepth` にどのような値が代入された場合__ でも、`draw_subtree` が描画する __部分木がすべて収まるだけの大きさに設定する__ 必要があります。そのためには、__部分木の縦幅と横幅の最大値__ を調べる必要があります。

Axes の表示範囲が決まれば、__Figure の大きさ__ を Axes の表示範囲の __縦幅と横幅と同じ比率で設定すればよい__ ことがわかる（そうしなければ、縦と横の縮尺が異なる、ゆがんだ部分木が描画されてしまいます）ので、Axes の表示範囲を考えることにします。

:::note
`draw_subtree` が描画する部分木の大きさに合わせて、Figure のサイズを変更するという方法も考えられますが、Figure のサイズを変化させると、JupyterLab のセルの大きさが変化するため、見た目があまりよくないと思いましたので、本記事では Figure のサイズを Figure の作成後に変更する方法は採用しません。
:::

まず、部分木の __横幅の最大値__ を考えることにします。`draw_subtree` では、深さ 0 のノードから、最大で深さ 9 までのノードを描画するので、__最大で横に深さが 0 から 9 までの 10 個のゲーム盤が描画される__ ことになります。`draw_subtree` では、ゲーム盤を __横方向に間を 2 だけ開けて描画する__ ので、__最大で 5 * 10 = 50 の幅が必要__ になることが分かります。

次に、部分木の __縦幅の最大値__ を考えることにします。上図では、赤丸の `centernode` から深さ 9 までの部分木を描画していますが、__`centernode` の深さが浅い場合に、深いノードまでの部分木を描画__ した場合は、縦方向に描画しなければならない __ノードの数が多くなりすぎる__ ことになります。例えば、[以前の記事](https://qiita.com/ysgeso/items/949d18e60939d510c931#%E7%89%B9%E5%AE%9A%E3%81%AE%E6%B7%B1%E3%81%95%E3%81%BE%E3%81%A7%E3%81%AE%E3%83%8E%E3%83%BC%E3%83%89%E3%81%AE%E9%AB%98%E3%81%95%E3%82%92%E8%A8%88%E7%AE%97%E3%81%99%E3%82%8B%E5%87%A6%E7%90%86%E3%81%AE%E5%AE%9F%E8%A3%85)で説明したように、深さ 0 から深さ 9 までの __ゲーム木全体を描画__ すると、__ゲーム木の高さが 100 万を超える__ ことになります。

__部分木の高さ__ は、`draw_subtree` で描画する __部分木の深さを浅くすることで減らす__ ことができます。`centernode` の子ノードまでの部分木が、最も浅い部分木になるので、Mbtree_GUI では下図のように、__`centernode` の子ノードまでの部分木を描画__ することにします。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/670a9389-c02e-be31-daab-9b71efe12c5e.png" width="300">

次に、`centernode` の子ノードまでを描画した場合の部分木の高さの最大値を考えることにします。どのように計算するかがわからない場合は、__具体例を使って部分木の高さを計算する__ と良いでしょう。そこで、最初に上図の部分木の高さの計算方法について考えることにします。どのように計算すればよいかについて少し考えてみて下さい。

まず、`centernode` とその子ノードを表す、下図の赤枠の中に、ゲーム盤が最大でいくつ縦に並ぶかを計算します。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/f08cac7a-5d0e-0b26-bddc-0d482acfa7e8.png" width="300">

その数は、__`centernode` の子ノードの数によって変化__ します。__`centernode` の深さ__（depth）を __d__ とすると、深さ d のノードは d 手目の局面なので、__合法手の数__ は __9 - d__ になります。ただし、決着がついた局面には合法手が存在しないので、その数は下記のようになります。

* `centernode` の局面が、__決着がついた局面__ の場合は __子ノードは存在しない__ ので、赤枠の中には __`centernode` が 1 つだけ描画__ される
* __決着がついていない__ 場合は、赤枠の中に __子ノードが 9 - d 個縦に並んで描画__ される

〇×ゲームでは、__深さが 9 の局面__ は必ず決着がついた局面になります。そのため、上記から、__赤枠の中に並ぶゲーム盤の最大値__ は以下のようになります。

* __深さ d が 9__ の場合は __1__
* それ以外の場合は __9 - d__

次に下図の緑枠の中に、ゲーム盤が最大でいくつ縦に並ぶかを計算します。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/f209660f-ce5a-b6fb-42bd-458a7a5000d1.png" width="300">

図から、__緑枠の高さ__ は、__赤枠の高さ__ に、`centernode` の __[兄弟ノード](https://qiita.com/ysgeso/items/0bad81b51c0ccbdbc3c7#%E5%85%84%E5%BC%9F%E3%83%8E%E3%83%BC%E3%83%89%E3%81%AE%E6%8F%8F%E7%94%BB)の高さを加えたもの__ であることがわかります。ただし、__深さが 0__ のルートノードだけは __親ノードを持たない__ ので、兄弟ノードは存在せず、__緑枠の高さは赤枠の高さと同じになる__ 点に注意して下さい。

d が 0 以外の場合に、深さ d のノードの __親ノードの深さは d - 1__ なので、`centernode` の親ノードは 9 - (d - 1) = __10 - d 個__ の子ノードを持つことになります。`centernode` は __その中の 1 つ__ なので、`centernode` の __兄弟ノードの数__ は 10 - d - 1 = __9 - d__ で計算できます。

従って、上図の緑枠の中に縦に並ぶゲーム盤の数の最大値は、__赤枠の中の最大値 + 兄弟ノードの数__ になるので、以下のようになります。

| d | 赤枠の最大値 | 兄弟ノードの数 | 最大値 |
|:-:|:-:|:-:|:-:|
| 0  | 9 - 0 = 9 | 0 | 9 |
| 1 ~ 8  | 9 - d | 9 - d | 18 - 2 * d  |
| 9  | 1 | 9 - 9 = 0 | 1 | 



`0` から `9` までのそれぞれの最大値を計算すると以下のようになるので、部分木では、__最大で縦に 16 個__ のゲーム盤が並べて描画されることが分かります。

| `d` | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|
| 最大値 | 9 | 16 | 14  | 12  | 10  | 8  | 6  | 4  | 2 | 1 |

`draw_subtree` では、ゲーム盤を横方向に間を 1 だけ開けて描画するので、__最大で 4 * 16 = 64 の高さが必要__ になることが分かります。

従って、__Axes の表示範囲__ の x、y 座標をそれぞれ __0 ~ 50__、と __0 ~ 64__ に設定し、Figure の大きさを __横 50、縦 64 の比率で設定すればよい__ ことがわかりました。

この 50 と 64 という数値は、Figure の作成と Axes の表示範囲を設定する際に必要となるので、下記のプログラムの 5、6 行目のように、`__init__` メソッド内で、__`width`__ と __`height`__ という __属性に代入する__ ことにします。

なお、前回の記事で説明したように、__[`super`](https://qiita.com/ysgeso/items/2acd8c5ceb8e9cda7233#%E3%82%AA%E3%83%BC%E3%83%90%E3%83%BC%E3%83%A9%E3%82%A4%E3%83%89%E3%81%AE%E7%9B%AE%E7%9A%84%E3%81%A8%E5%9F%BA%E5%BA%95%E3%82%AF%E3%83%A9%E3%82%B9%E3%81%AE%E3%83%A1%E3%82%BD%E3%83%83%E3%83%89%E3%81%AE%E5%91%BC%E3%81%B3%E5%87%BA%E3%81%97) はクラスの定義の中で記述する必要がある__ ので、これまでのように、通常の関数として `__init__` を定義し、`Mbtree_GUI.__init__ = __init__` を実行して `__init__` を __定義する事はできない点に注意__ して下さい。

__Mbtree_GUI__ というクラスを __定義し直した__ ので、先程修正した __`create_widgets` の定義__ は、下記のプログラムの 9、10 行目の __`create_widgets` の定義で上書き__ されて __無効になる__ 点にも注意して下さい。ただし、__`create_widgets`__ は、この __すぐ後で定義し直す__ ので、下記では何も行わない関数として `create_widgets` を定義しました。

```python
 1  class Mbtree_GUI(GUI):
 2      def __init__(self, mbtree, size=1):
 3          self.mbtree = mbtree
 4          self.size = size
 5          self.width = 50
 6          self.height = 64
 7          super().__init__()
 8       
 9      def create_widgets(self):
10          pass
元と同じなので省略
```

<details><summary><strong>行番号のないプログラム</strong></summary>

```python
class Mbtree_GUI(GUI):
    def __init__(self, mbtree, size=1):
        self.mbtree = mbtree
        self.size = size
        self.width = 50
        self.height = 64
        super().__init__()
        
    def create_widgets(self):
        pass
    
    def create_event_handler(self):
        pass
    
    def display_widgets(self):
        pass

    def update_gui(self):
        pass

    def update_widgets_status(self):
        pass
```
</details>

<details><summary><strong>修正箇所</strong></summary>

```diff_python
class Mbtree_GUI(GUI):
    def __init__(self, mbtree, size=1):
        self.mbtree = mbtree
        self.size = size
+       self.width = 50
+       self.height = 64
        super().__init__()
元と同じなので省略
```
</details>

下記は、__Figure を作成する処理を `create_widgets` に加えた__ プログラムです。なお、Marubatsu_GUI では、`create_widgets` の記述が長くなってしまうため、`create_figure` というメソッドを別途定義して Figure を作成しましたが、Mbtree_GUI クラスの `create_widgets` は記述が短いので `create_widgets` の中にその処理を記述しました。

* __7 行目__：[以前の記事](https://qiita.com/ysgeso/items/5941cc59c3e0e173c5cf#figure-%E3%81%8C-2-%E5%9B%9E%E6%8F%8F%E7%94%BB%E3%81%95%E3%82%8C%E3%82%8B%E5%95%8F%E9%A1%8C%E3%81%AE%E4%BF%AE%E6%AD%A3%E6%96%B9%E6%B3%95)で説明したように、`%matplotlib widget` を実行した場合に `display` で Figure を描画する場合は、Figure が 2 回表示されないようにするために、matplotlib のインタラクティブモードを OFF にした状態で Figure を作成する必要があり、`with plt.ioff():` のブロック内でで Figure を作成することでそれを行っている
* __8、9 行目__：`self.width` と `self.height` に、Figure の大きさを調整するための `self.size` を乗算した大きさの Figure を作成する
* __10 ~ 13 行目__：[以前の記事](https://qiita.com/ysgeso/items/1920c68f5a1e8a998796#%E3%82%BF%E3%82%A4%E3%83%88%E3%83%AB%E3%82%84%E3%83%84%E3%83%BC%E3%83%AB%E3%83%90%E3%83%BC%E3%81%AA%E3%81%A9%E3%81%AB%E9%96%A2%E3%81%99%E3%82%8B%E8%A8%AD%E5%AE%9A)で説明した方法で、Figure に対して、タイトルやツールバーなどの表示が行われないようにする

```python
 1  def create_widgets(self):
 2      self.left_button = self.create_button("←", 100)
 3      self.up_button = self.create_button("↑", 100)
 4      self.right_button = self.create_button("→", 100)
 5      self.down_button = self.create_button("↓", 100)
 6   
 7      with plt.ioff():
 8          self.fig, self.ax = plt.subplots(figsize=[self.width * self.size,
 9                                                    self.height * self.size])
10      self.fig.canvas.toolbar_visible = False
11      self.fig.canvas.header_visible = False
12      self.fig.canvas.footer_visible = False
13      self.fig.canvas.resizable = False       
14    
15  Mbtree_GUI.create_widgets = create_widgets
```

<details><summary><strong>行番号のないプログラム</strong></summary>

```python
def create_widgets(self):
    self.left_button = self.create_button("←", 100)
    self.up_button = self.create_button("↑", 100)
    self.right_button = self.create_button("→", 100)
    self.down_button = self.create_button("↓", 100)
    
    with plt.ioff():
        self.fig, self.ax = plt.subplots(figsize=[self.width * self.size,
                                                  self.height * self.size])
    self.fig.canvas.toolbar_visible = False
    self.fig.canvas.header_visible = False
    self.fig.canvas.footer_visible = False
    self.fig.canvas.resizable = False       
    
Mbtree_GUI.create_widgets = create_widgets
```
</details>

<details><summary><strong>修正箇所</strong></summary>

```diff_python
def create_widgets(self):
    self.left_button = self.create_button("←", 100)
    self.up_button = self.create_button("↑", 100)
    self.right_button = self.create_button("→", 100)
    self.down_button = self.create_button("↓", 100)
    
+   with plt.ioff():
+       self.fig, self.ax = plt.subplots(figsize=[self.width * self.size,
+                                                 self.height * self.size])
+   self.fig.canvas.toolbar_visible = False
+   self.fig.canvas.header_visible = False
+   self.fig.canvas.footer_visible = False
+   self.fig.canvas.resizable = False       
    
Mbtree_GUI.create_widgets = create_widgets
```
</details>

## `display_widgets` メソッドの定義

次に、`create_widgets` で作成した __ウィジェットを配置して表示__ する __`display_widgets`__ メソッドを定義します。そのためには、4 つのボタンをどのように配置するかを決める必要があります。4 つのボタンをゲーム機のコントローラーの十字キーのように、上下左右の位置に配置することは可能ですが、一般的に、ボタンなどの __GUI のレイアウト__ は、GUI を実装する際に __最初に厳密に決めなければならないものではありません__。その理由は、一生懸命厳密なレイアウトを決めたとしても、後から GUI にボタンなどの機能を追加したりした場合に、せっかく厳密に決めたレイアウトを一から設計し直す必要が生じるからです。そのため、__一般的には、厳密な GUI のレイアウトは、GUI の機能をすべて実装してから行うと良い__ でしょう。本記事では、とりあえず ←、→、↑、↓ の順で横に並べて配置することにします。また、ゲーム木を描画する Figure はその下に配置することにします。

下記は、そのように `display_widgets` を記述したプログラムです。

* __4 行目__：4 つのボタンを横に並べて配置した Hbox を作成する
* __5 行目__：上記の Hbox と Figure を縦に並べた VBox を作成し、`display` で表示する

```python
1  import ipywidgets as widgets
2
3  def display_widgets(self):
4      hbox = widgets.HBox([self.left_button, self.right_button, self.up_button, self.down_button])
5      display(widgets.VBox([hbox, self.fig.canvas]))    
6    
7  Mbtree_GUI.display_widgets = display_widgets
```

<details><summary><strong>行番号のないプログラム</strong></summary>

```python
import ipywidgets as widgets

def display_widgets(self):
    hbox = widgets.HBox([self.left_button, self.right_button, self.up_button, self.down_button])
    display(widgets.VBox([hbox, self.fig.canvas]))    
    
Mbtree_GUI.display_widgets = display_widgets
```
</details>

上記の修正後に、下記のプログラムを実行して、Mbtree_GUI クラスのインスタンスを作成すると、実行結果のように、上部に 4 つのボタンが表示され、その下に 横 50 縦 64 の非常に大きな Figure されるようになります。Figure が大きすぎるので、下記の実行結果には 4 つのボタンの部分だけを表記します。なお、作成したインスタンスは今回の記事では特に利用しませんが、`mbtree_gui` という変数に代入することにします。

```python
mbtree_gui = Mbtree_GUI(mbtree)
```

実行結果

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/fda23504-a615-84b1-b400-30e57ff9d265.png" width="300">

Figure の大きさを調整するために、キーワード引数 `size` に様々な値を記述して上記のプログラムを実行した所、下記のように、実引数に `size=0.15` を指定して Mbtree_GUI のインスタンスを作成すると、ちょうど良い大きさの Figure が表示されるようになることがわかりました。そこで、後で `__init__` メソッドの仮引数 `size` をデフォルト値が `0.15` のデフォルト引数に修正することにします。他の値が良いと思った人は自由に変更して下さい。

```python
mbtree_gui = Mbtree_GUI(mbtree, size=0.15)
```

実行結果

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/b805ee56-6b27-91e4-3acc-f6c79553d5e2.png" width="300">

## `update_gui` メソッドの定義

次に、__GUI の表示__ を行う __`update_gui`__ を定義して、Figure に __`draw_subtree` を使って部分木を描画する__ ようにします。そのためには、部分木の描画を行う __`draw_subtree` を修正する必要があります__。何を修正する必要があるかについて少し考えてみて下さい。

### `draw_subtree` の修正

Mbtree クラスの `draw_subtree` メソッドは、下記のプログラムのように、その中で部分木を描画する __Figure と Axes を作成__ していますが、Mbtree_GUI では、部分木を __`create_widgets` で作成した Figure の Axes に対して描画を行う__ 必要があります。

```python
def draw_subtree(self, centernode=None, size=0.25, lw=0.8, maxdepth=2):
略
    fig, ax = plt.subplots(figsize=(width * size, height * size))
略
```

そこで、`draw_subtree` に、デフォルト値を `None` とする仮引数 `ax` を追加し、__`ax` が `None` の場合のみ `draw_subtree` 内で Figure を作成する__ ように修正することにします。なお、この修正は、[以前の記事](https://qiita.com/ysgeso/items/0bb9d7fd972636d470d5#draw_node-%E3%81%A7%E3%81%AE-figure-%E3%81%AE%E4%BD%9C%E6%88%90%E5%87%A6%E7%90%86%E3%81%AB%E9%96%A2%E3%81%99%E3%82%8B%E4%BF%AE%E6%AD%A3)で `draw_node` に対して行ったものと同じものです。

下記は、そのように `draw_subtree` を修正したプログラムです。

* __1 行目__：デフォルト値を `None` とするデフォルト引数 `ax` を追加する
* __5 ~ 10 行目__：`ax` が `None` の場合に、Figure の作成などの処理を行うようにする

```python
 1  def draw_subtree(self, centernode=None, ax=None, size=0.25, lw=0.8, maxdepth=2):
元と同じなので省略
 2      if parent is not None:
 3          height += (len(parent.children) - 1) * 4
 4          parent.height = height
 5      if ax is None:
 6          fig, ax = plt.subplots(figsize=(width * size, height * size))
 7          ax.set_xlim(0, width)
 8          ax.set_ylim(0, height)   
 9          ax.invert_yaxis()
10          ax.axis("off")        
元と同じなので省略
11            
12  Mbtree.draw_subtree = draw_subtree
```

<details><summary><strong>行番号のないプログラム</strong></summary>

```python
def draw_subtree(self, centernode=None, ax=None, size=0.25, lw=0.8, maxdepth=2):
    if centernode is None:
        centernode = self.root
    self.calc_node_height(maxdepth)
    width = 5 * (maxdepth + 1)
    height = centernode.height
    parent = centernode.parent
    if parent is not None:
        height += (len(parent.children) - 1) * 4
        parent.height = height
    if ax is None:
        fig, ax = plt.subplots(figsize=(width * size, height * size))
        ax.set_xlim(0, width)
        ax.set_ylim(0, height)   
        ax.invert_yaxis()
        ax.axis("off")        
    
    nodelist = [centernode]
    depth = centernode.depth
    while len(nodelist) > 0 and depth <= maxdepth:        
        dy = 0
        if parent is not None:
            dy = parent.children.index(centernode) * 4
        childnodelist = []
        for node in nodelist:
            if node is None:
                dy += 4
                childnodelist.append(None)
            else:
                dx = 5 * node.depth
                node.draw_node(ax=ax, maxdepth=maxdepth, size=size, lw=lw, dx=dx, dy=dy)
                dy += node.height
                if len(node.children) > 0:  
                    childnodelist += node.children
                else:
                    childnodelist.append(None)
        depth += 1
        nodelist = childnodelist
        
    if parent is not None:
        dy = 0
        for sibling in parent.children:
            if sibling is not centernode:
                sibling.height = 4
                dx = 5 * sibling.depth
                sibling.draw_node(ax, maxdepth=sibling.depth, size=size, lw=lw, dx=dx, dy=dy)
            dy += sibling.height
        dx = 5 * parent.depth
        parent.draw_node(ax, maxdepth=maxdepth, size=size, lw=lw, dx=dx, dy=0)
    
        node = parent
        while node.parent is not None:
            node = node.parent
            node.height = height
            dx = 5 * node.depth
            node.draw_node(ax, maxdepth=node.depth, size=size, lw=lw, dx=dx, dy=0)
            
Mbtree.draw_subtree = draw_subtree
```
</details>

<details><summary><strong>修正箇所</strong></summary>

```diff_python
-def draw_subtree(self, centernode=None, size=0.25, lw=0.8, maxdepth=2):
+def draw_subtree(self, centernode=None, ax=None, size=0.25, lw=0.8, maxdepth=2):
元と同じなので省略
    if parent is not None:
        height += (len(parent.children) - 1) * 4
        parent.height = height
-   fig, ax = plt.subplots(figsize=(width * size, height * size))
-   ax.set_xlim(0, width)
-   ax.set_ylim(0, height)   
-   ax.invert_yaxis()
-   ax.axis("off")        
+   if ax is None:
+       fig, ax = plt.subplots(figsize=(width * size, height * size))
+       ax.set_xlim(0, width)
+       ax.set_ylim(0, height)   
+       ax.invert_yaxis()
+       ax.axis("off")        
元と同じなので省略
            
Mbtree.draw_subtree = draw_subtree
```
</details>

### `update_gui` の定義

最終的には Figure に描画する部分木を、ボタンで移動できるようにする必要がありますが、ボタンの __イベントハンドラをまだ記述していない__ ので、__現時点__ では __常にルートノードを中心とした深さ 1 までの部分木を描画__ することにします。下記は、そのように `update_gui` を記述したプログラムです。

* __2 行目__：それまでに Axes に描画されていた内容をクリアする。__[ax.clear()](https://qiita.com/ysgeso/items/c91d5373753253d98f23#clear-%E3%83%A1%E3%82%BD%E3%83%83%E3%83%89%E3%81%8C%E8%A1%8C%E3%81%86%E5%87%A6%E7%90%86)__ で、Axes の描画範囲や、上下の反転などの設定がリセットされるので、この後で設定し直す必要がある
* __3、4 行目__：Axes の描画範囲を、先程計算した範囲に設定する
* __5、6 行目__：Axes の y 軸の上下を反転させ、目盛りを表示しないようにする
* __7 行目__：`draw_subtree` で、ルートノードを中心とする、深さ 1 までの部分木を描画する

```python
1  def update_gui(self):
2      self.ax.clear()
3      self.ax.set_xlim(0, self.width)
4      self.ax.set_ylim(0, self.height)   
5      self.ax.invert_yaxis()
6      self.ax.axis("off")   
7      self.mbtree.draw_subtree(self.mbtree.root, ax=self.ax, maxdepth=1)
8    
9  Mbtree_GUI.update_gui = update_gui
```

<details><summary><strong>行番号のないプログラム</strong></summary>

```python
def update_gui(self):
    self.ax.clear()
    self.ax.set_xlim(0, self.width)
    self.ax.set_ylim(0, self.height)   
    self.ax.invert_yaxis()
    self.ax.axis("off")   
    self.mbtree.draw_subtree(self.mbtree.root, ax=self.ax, maxdepth=1)
    
Mbtree_GUI.update_gui = update_gui
```
</details>

上記の修正後に、下記のプログラムを実行すると、実行結果のように部分木は描画されません。その理由を少し考えてみて下さい。

```python
mbtree_gui = Mbtree_GUI(mbtree, size=0.15)
```

実行結果

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/b805ee56-6b27-91e4-3acc-f6c79553d5e2.png" width="300">

### 部分木が描画されない理由と修正

部分木が描画されない理由は、Mbtree_GUI クラスの __インスタンスを作成した際に実行__ される `__init__` メソッドの中で、__`draw_gui` を呼び出す処理を記述していない__ からです。

:::note
以前の記事で Marubatsu_GUI のインスタンスを作成した場合はゲーム盤が表示されるのに、Mbtree_GUI では描画されないのはおかしいと思った方がいるかもしれません。Marubatsu_GUI の場合は、以下のような理由でゲーム盤が描画されます。

* Marubatsu_GUI クラスのインスタンスは、Marubatsu クラスの `play` メソッドの中の下記の 4 行目部分で作成される。

```python
1  def play(self, 略):
略
2      # gui が True の場合に、GUI の処理を行う Marubatsu_GUI のインスタンスを作成する
3      if gui:
4          mb_gui = Marubatsu_GUI(self, ai_dict=ai_dict, seed=seed, size=size)  
5      else:
6          mb_gui = None
7            
8      self.restart()
9      return self.play_loop(mb_gui)
```

* その後で、上記の 9 行目で `play_loop` メソッドが呼び出される
* `play_loop` の中の下記のプログラムの 6 行目で、`update_gui` が呼び出される

```python
1  def play_loop(self, mb_gui):
略
2          if verbose:
3              if gui:
4                  # AI どうしの対戦の場合は画面を描画しない
5                  if ai[0] is None or ai[1] is None:
6                      mb_gui.update_gui()
略
```

つまり、Marubatsu_GUI の場合は、Marubatsu_GUI のインスタンスを作成した際にゲーム盤が描画されているわけではなく、作成後に Marubatsu クラスの `play_loop` 内で `update_gui` が呼び出されてゲーム盤が描画されているということです。
:::

そこで、下記のプログラムの 9 行目のように、GUI クラスの `__init__` メソッドを、`draw_widgets` を実行してウィジェットを配置した後で `update_gui` を呼び出すようにすることで、GUI の表示を更新する処理を行うように修正することにします。

```python
 1  def __init__(self):
 2      # %matplotlib widget のマジックコマンドを実行する
 3      get_ipython().run_line_magic('matplotlib', 'widget')
 4   
 5      self.disable_shortcutkeys()
 6      self.create_widgets()
 7      self.create_event_handler()
 8      self.display_widgets() 
 9      self.update_gui()
10    
11  GUI.__init__ = __init__
```

<details><summary><strong>行番号のないプログラム</strong></summary>

```python
def __init__(self):
    # %matplotlib widget のマジックコマンドを実行する
    get_ipython().run_line_magic('matplotlib', 'widget')
    
    self.disable_shortcutkeys()
    self.create_widgets()
    self.create_event_handler()
    self.display_widgets() 
    self.update_gui()
    
GUI.__init__ = __init__
```
</details>

<details><summary><strong>修正箇所</strong></summary>

```diff_python
def __init__(self):
    # %matplotlib widget のマジックコマンドを実行する
    get_ipython().run_line_magic('matplotlib', 'widget')
    
    self.disable_shortcutkeys()
    self.create_widgets()
    self.create_event_handler()
    self.display_widgets() 
+   self.update_gui()
    
GUI.__init__ = __init__
```
</details>

上記の修正後に、下記のプログラムを実行すると、実行結果のようにルートノードを中心とした部分木が描画されるようになります。

```python
mbtree_gui = Mbtree_GUI(mbtree, size=0.15)
```

実行結果
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/d2ce89cc-a90c-99dc-1c9d-86dec7ffec72.png" width="300">

## Axes の配置位置の調整

ところで、上記の実行結果で __部分木の描画位置__ が、4 つのボタンより __かなり下に表示__ されている点が気になる人はいないでしょうか。これは、Figure の中に作成された __Axes の配置の位置が原因__ ですが、上図では、Figure、Axes の背景色が白色なので、Figure と Axes の範囲が見た目からはわかりません。

### Axes の配置位置の確認

そこで、Figure の中で、__Axes がどのような位置に配置されているかを確認する__ ために、下記のプログラムのように Figure の背景色を灰色にし、Axes の軸の目盛りを表示するように `update_gui` を修正してみることにします。なお、Figure の背景色の変更は、2 行目のように、`set_facecolor` というメソッドで行うことができます。

* __2 行目__：Figure の背景色を薄い灰色（lightgray）にする
* __7 行目__：軸の目盛りを表示しないようにする処理をコメントにして実行しないようにする。この部分は一時的に実行しないようにするだけなので、コメントにした

```python
 1  def update_gui(self):
 2      self.fig.set_facecolor("lightgray")
 3      self.ax.clear()
 4      self.ax.set_xlim(0, self.width)
 5      self.ax.set_ylim(0, self.height)   
 6      self.ax.invert_yaxis()
 7  #   self.ax.axis("off")   
 8
 9      self.mbtree.draw_subtree(self.mbtree.root, ax=self.ax, maxdepth=1)
10    
11  Mbtree_GUI.update_gui = update_gui
```

<details><summary><strong>行番号のないプログラム</strong></summary>

```python
def update_gui(self):
    self.fig.set_facecolor("lightgray")
    self.ax.clear()
    self.ax.set_xlim(0, self.width)
    self.ax.set_ylim(0, self.height)   
    self.ax.invert_yaxis()
#   self.ax.axis("off")   
    self.mbtree.draw_subtree(self.mbtree.root, ax=self.ax, maxdepth=1)
    
Mbtree_GUI.update_gui = update_gui
```
</details>

<details><summary><strong>修正箇所</strong></summary>

```diff_python
def update_gui(self):
+   self.fig.set_facecolor("lightgray")
    self.ax.clear()
    self.ax.set_xlim(0, self.width)
    self.ax.set_ylim(0, self.height)   
    self.ax.invert_yaxis()
-   self.ax.axis("off")   
+#  self.ax.axis("off")   
    self.mbtree.draw_subtree(self.mbtree.root, ax=self.ax, maxdepth=1)
    
Mbtree_GUI.update_gui = update_gui
```
</details>

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

https://matplotlib.org/stable/api/_as_gen/matplotlib.figure.Figure.set_facecolor.html

上記の修正後に、下記のプログラムを実行すると、実行結果のような画像が描画されます。

```python
mbtree_gui = Mbtree_GUI(mbtree, size=0.15)
```

実行結果

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/53bc5745-b2b7-7bf2-9239-9bd2f716e9d3.png" width="300">

上図の __灰色の部分が Figure__ を、内部の __枠の中の白い部分が Axes__ を表しており、Figure は 4 つのボタンのすぐ下に配置されていることが分かります。従って、部分木が 4 つのボタンよりかなり下に描画されるのは、__Axes が Figure のかなり内側に配置されている__ ことが原因であることがわかります。

### `%matplotlib widget` による Axes の配置位置の変化

筆者が試行錯誤して調べてみた所、__`%matplotlib widget` を実行するか__ どうかで `subplots` メソッドで作成した __Axes が Figure の内部のどこに配置されるかが若干異なる__ ことがわかりました。`%matplotlib inline` を実行すると、`%matplotlib widget` を実行する前の状態に戻すことができるので、そのことは下記の 2 つのプログラムで確認することができます。

下記は、`%matplotlib widget` を実行しない状態で、`subplots` メソッドで Figure と Axes を作成して表示するプログラムです。

```python
%matplotlib inline
plt.close("all")
fig, ax = plt.subplots(facecolor="lightgray")
```

実行結果

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/a49a95f7-d63f-4872-dec6-4915671ae4e1.png" width="300">

上記の 2 行目の意味は説明しないとわからないと思いますので説明します。下記に関する内容は[以前の記事](https://qiita.com/ysgeso/items/1920c68f5a1e8a998796#ipympl-%E3%81%AE%E5%88%A9%E7%94%A8%E6%96%B9%E6%B3%95)でも説明したので、忘れた方はそちらも見て下さい。

__`%matplotlib widget` を実行しない__ 場合は、JupyterLab のセルを実行すると、そのセルで作成された Figure は __自動的に描画された後で閉じられて利用できなくなります__。

一方、__`%matplotlib widget` を実行した後__ では、JupyterLab のセルを実行しても、作成した Figure は __自動的に閉じられず、残り続ける__ ことになります。これは、Figure を閉じてしまうと、後から Figure の内容を変更できなくなるからです。

今回の記事では、Mbtree_GUI クラスを作成する際に、__`%matplotlib widget`__ が実行され、多くの Figure を描画しました。そのため、それらの Figure が残り続けています。その後で、上記のプログラムのように、`%matplotlib inline` を実行して JupyterLab のセルを実行すると、それまでに作成されたFigure が全て描画されてしまいます。2 行目の __`plt.close("all")` は、それまでに作成した Figure を全て閉じるという処理を行います__。そうすることで、セルの実行後にこれまでに作成した __余計な Figure が描画されなくなります__。

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

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

下記は、`%matplotlib widget` を実行した状態で、`subplots` メソッドで Figure と Axes を作成した場合のプログラムです。

```python
%matplotlib widget
fig, ax = plt.subplots(facecolor="lightgray")
```

実行結果

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/776315ad-655a-5cd1-29eb-d7b09af37983.png" width="300">

下記は、`%matplotlib widget` を実行しない場合と、実行した場合で表示される Figure を並べたものです。比べてみるとわかるように、__`%matplotlib widget` を実行した場合__ のほうが、__Axes が Figure のより内側に作成される__ ようです。このようなことが起きる原因については筆者の調べた限りではよくわかりませんでした[^1]が、この問題は、__Axes__ を `plt.subplots` ではない、__別の方法で作成する__ ことで解決することができます。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/a49a95f7-d63f-4872-dec6-4915671ae4e1.png" width="300">　<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/776315ad-655a-5cd1-29eb-d7b09af37983.png" width="300">

### `add_axes` メソッドによる Axes の配置位置の設定

`plt.subplots` は 1 つの Axes が配置された Figure を作成する場合だけでなく、（本記事ではまだそのような処理を行ったことはありませんが）__複数の Axes[^2] が規則正しく並べて配置される__ ような Figure を作成する際で便利なので良く使われますが、__Figure の中での Axes の配置__ をどのような大きさで、どのような位置に行うかを __厳密に指定して作成することはできません__。そのような場合は、Figure の __`add_axes`__ というメソッドを利用する必要があります。`add_axes` の __実引数__ には、Figure の中に配置する Axes の __位置と大きさ__ を __`(left, bottom, width, height)`__ という __4 つの要素を持つ tuple で記述__ します。

ぞれぞれの要素の意味は以下の通りです。

|  | 意味 |
|:--|:--|
| `left`  | Figure の左端を 0、右端を 1 とする座標 |
| `bottom`  | Figure の下端を 0、上端を 1 とする座標 |
| `width`  | Figure の幅を 1 とした場合の Axes の幅  |
| `height`  | Figure の高さを 1 とした場合の Axes の高さ  |

:::note
`plt.axes((left, bottom, width, height))` で、[current figure](https://qiita.com/ysgeso/items/ab2770693c947c108dd0#fn-6) に対して同様の方法で Axes を追加することができます。
:::

__`add_axes` は、Figure のメソッド__ なので、利用する場合は、下記のプログラムのように、__`plt.figure()` で Figure だけを作成した後__ で、その Figure に対して `add_axes` を呼び出します。下記は、Figure の下半分と、右上に Axes を作成するプログラムです。

```python
fig = plt.figure()
ax1 = fig.add_axes([0.1, 0.1, 0.8, 0.4]) # 左下が (0.1, 0.1)、幅が 0.8、高さが 0.4 の Axes
ax2 = fig.add_axes([0.6, 0.6, 0.3, 0.3]) # 左下が (0.6, 0.3)、幅が 0.3、高さが 0.3 の Axes
```

実行結果
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/c582b849-a558-dfd1-f821-21797ed3a54c.png" width="300">

:::note
上記のプログラムでは行っていませんが、`ax1` と `ax2` を使って、2 つの Axes にそれぞれ別の画像を描画することができます。
:::

Figure の `add_axes` メソッドの詳細については、下記のリンク先を参照して下さい。

https://matplotlib.org/stable/api/_as_gen/matplotlib.figure.Figure.add_axes.html#matplotlib.figure.Figure.add_axes

`plt.axes()` の詳細については、下記のリンク先を参照して下さい。

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

### `create_widgets` の修正

下記は、Axes を Figure 全体に配置するように `create_widgets` を修正するプログラムです。

* __5 行目__：Figure の左下の (0, 0) を基準に、幅 1、高さ 1 の範囲を表すように `add_axes` を記述することで、Figure 全体に配置するように Axes を作成する

```python
1  def create_widgets(self):
元と同じなので省略
2      with plt.ioff():
3          self.fig = plt.figure(figsize=[self.width * self.size,
4                                         self.height * self.size])
5          self.ax = self.fig.add_axes([0, 0, 1, 1])
元と同じなので省略
6    
7  Mbtree_GUI.create_widgets = create_widgets
```

<details><summary><strong>行番号のないプログラム</strong></summary>

```python
def create_widgets(self):
    self.left_button = self.create_button("←", 100)
    self.up_button = self.create_button("↑", 100)
    self.right_button = self.create_button("→", 100)
    self.down_button = self.create_button("↓", 100)
    
    with plt.ioff():
        self.fig = plt.figure(figsize=[self.width * self.size,
                                       self.height * self.size])
        self.ax = self.fig.add_axes([0, 0, 1, 1])   
    self.fig.canvas.toolbar_visible = False
    self.fig.canvas.header_visible = False
    self.fig.canvas.footer_visible = False
    self.fig.canvas.resizable = False       
    
Mbtree_GUI.create_widgets = create_widgets
```
</details>

<details><summary><strong>修正箇所</strong></summary>

```diff_python
def create_widgets(self):
元と同じなので省略
    with plt.ioff():
        self.fig = plt.figure(figsize=[self.width * self.size,
                                       self.height * self.size])
+       self.ax = self.fig.add_axes([0, 0, 1, 1])
元と同じなので省略
    
Mbtree_GUI.create_widgets = create_widgets
```
</details>

また、下記のプログラムのように、`update_gui` を元に戻します。

```python
def update_gui(self):
    self.ax.clear()
    self.ax.set_xlim(0, self.width)
    self.ax.set_ylim(0, self.height)   
    self.ax.invert_yaxis()
    self.ax.axis("off")   

    self.mbtree.draw_subtree(self.mbtree.root, ax=self.ax, maxdepth=1)
    
Mbtree_GUI.update_gui = update_gui
```

<details><summary><strong>修正箇所</strong></summary>

```diff_python
def update_gui(self):
-   self.fig.set_facecolor("lightgray")
    self.ax.clear()
    self.ax.set_xlim(0, self.width)
    self.ax.set_ylim(0, self.height)   
    self.ax.invert_yaxis()
-#  self.ax.axis("off")   
+   self.ax.axis("off")   

    self.mbtree.draw_subtree(self.mbtree.root, ax=self.ax, maxdepth=1)
    
Mbtree_GUI.update_gui = update_gui
```
</details>

上記の修正後に、下記のプログラムを実行すると、実行結果のように部分木が 4 つのボタンのすぐ下に描画されるようになります。

```python
mbtree_gui = Mbtree_GUI(mbtree, size=0.15)
```

実行結果

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/ed937292-3e54-54a2-bb07-9fc60813b336.png" width="300">

:::note
実は、Marubatsu_GUI クラスが作成する __ゲーム盤を表示する Figure__ も、__Axes が Figure のかなり内側に配置__ されています。ただし、Marubatsu_GUI の場合は、__ゲーム盤の上と下にメッセージを表示する必要がある__ ので、Axes が Figure のかなり内側に描画されても問題はありません。ただし、Marubatsu_GUI を定義した際に Axes が Figure のかなり内側に描画されることを筆者は知らなかったので、それは筆者が意図したものではなく、偶然そのようになっていたに過ぎません。
:::

## 選択中のノードを表す属性の追加と `update_gui` の修正

Mbtree_GUI では、ボタンによって描画する部分木の __中心となるノードを変更__ しますが、そのためには、__どのノードが中心となるノードであるか__ を表す情報を __記録する必要__ があります。そこで、本記事では __`centernode` という属性にその情報を代入する__ ことにします。

次に、__`centernode` 属性の初期化の処理__ を `__init__` メソッド内で記述する必要があります。__初期設定__ では `centernode` に __ルートノードを設定するのが自然__ だと思いますので、本記事では下記のプログラムのように `__init__` メソッドを修正することにします。なお、ついでに __仮引数 `size`__ を __デフォルト値を 0.15 とするデフォルト引数に変更__ しました。

また、__`update_gui`__ を、__`centernode` 属性を中心__ とし、`centernode` の __次の深さまで__ を描画するように修正しました。なお、__ゲーム木の深さは 9 までしかない__ ので、__`maxdepth` が 9 を超えないように__ しないとエラーが発生してしまう可能性が生じる点に注意して下さい。

なお、`super` が記述された `__init__` メソッドを変更するので、下記のプログラムのように、Mbtree_GUI クラスを定義しなおしました。

* __7 行目__：`centernode` 属性にルートノードを代入して初期化する
* __18 行目__：`centernode` の深さ + 1 を計算して `maxdepth` に代入する。ただし、`max` を使って深さが 9 を超えないようにする
* __19 行目__：`centernode` を中心とする、深さ `maxdepth` までの部分木を描画する

```python
 1  class Mbtree_GUI(GUI):
 2      def __init__(self, mbtree, size=0.15):
 3          self.mbtree = mbtree
 4          self.size = size
 5          self.width = 50
 6          self.height = 64
 7          self.centernode = self.mbtree.root
 8          super().__init__()
 9
元と同じなので省略
10
11      def update_gui(self):
12          self.ax.clear()
13          self.ax.set_xlim(0, self.width)
14          self.ax.set_ylim(0, self.height)   
15          self.ax.invert_yaxis()
16          self.ax.axis("off")   
17        
18          maxdepth = min(self.centernode.depth + 1, 9)
19          self.mbtree.draw_subtree(self.centernode, ax=self.ax, maxdepth=maxdepth)
20        
元と同じなので省略
```

<details><summary><strong>行番号のないプログラム</strong></summary>

```python
class Mbtree_GUI(GUI):
    def __init__(self, mbtree, size=0.15):
        self.mbtree = mbtree
        self.size = size
        self.width = 50
        self.height = 64
        self.centernode = self.mbtree.root
        super().__init__()
        
    def create_widgets(self):
        self.left_button = self.create_button("←", 100)
        self.up_button = self.create_button("↑", 100)
        self.right_button = self.create_button("→", 100)
        self.down_button = self.create_button("↓", 100)
        
        with plt.ioff():
            self.fig = plt.figure(figsize=[self.width * self.size,
                                           self.height * self.size])
            self.ax = self.fig.add_axes([0, 0, 1, 1])
        self.fig.canvas.toolbar_visible = False
        self.fig.canvas.header_visible = False
        self.fig.canvas.footer_visible = False
        self.fig.canvas.resizable = False       
        
    def create_event_handler(self):
        pass
    
    def display_widgets(self):
        hbox = widgets.HBox([self.left_button, self.right_button, self.up_button, self.down_button])
        display(widgets.VBox([hbox, self.fig.canvas]))  

    def update_gui(self):
        self.ax.clear()
        self.ax.set_xlim(0, self.width)
        self.ax.set_ylim(0, self.height)   
        self.ax.invert_yaxis()
        self.ax.axis("off")   
        
        maxdepth = min(self.centernode.depth + 1, 9)
        self.mbtree.draw_subtree(self.centernode, ax=self.ax, maxdepth=maxdepth)

    def update_widgets_status(self):
        pass
```
</details>

<details><summary><strong>修正箇所</strong></summary>

```diff_python
class Mbtree_GUI(GUI):
    def __init__(self, mbtree, size=0.15):
        self.mbtree = mbtree
        self.size = size
        self.width = 50
        self.height = 64
+       self.centernode = self.mbtree.root
        super().__init__()

元と同じなので省略

    def update_gui(self):
        self.ax.clear()
        self.ax.set_xlim(0, self.width)
        self.ax.set_ylim(0, self.height)   
        self.ax.invert_yaxis()
        self.ax.axis("off")   
        
+       maxdepth = min(self.centernode.depth + 1, 9)
-       self.mbtree.draw_subtree(self.mbtree.root, ax=self.ax, maxdepth=1)
+       self.mbtree.draw_subtree(self.centernode, ax=self.ax, maxdepth=maxdepth)
        
元と同じなので省略
```
</details>

実行結果は先ほどと同じなので省略しますが、上記の修正後に、下記のプログラムを実行すると、ルートノードを中心とした、深さ 1 までの部分木が表示されます。

```python
mbtree_gui = Mbtree_GUI(mbtree)
```

## `create_event_handler` の定義

`create_event_handler` には、以下の内容を記述します。下の 2 つは、Marubatsu_GUI の `create_event_handler` と同様の方法で記述できます。

* 4 つのボタンをクリックした際に呼び出されるイベントハンドラの定義
* 4 つのボタンとイベントハンドラの結び付け
* 4 つのボタンに対応するキーを押した時に対応するイベントハンドラを呼び出す処理

4 つのボタンに対する処理を、順番に記述する事にします。

### 親ノードへの移動

← ボタンを押した時に行う、部分木の中心となる `centernode` 属性のノードを __親のノードへ移動__ する処理は、以下の手順で行うことができます。最後の __Figure の描画の更新の処理の記述を忘れないように注意__ して下さい。

* `centernode` に親ノードが存在する場合は、親ノードを `centernode` に代入する
* 親ノードが存在しない場合は何もしない
* `update_gui` を呼び出して、Figure の描画を更新する

従って、このイベントハンドラは下記のプログラムで記述できます。

イベントハンドラの名前は、Marubatsu_GUI のボタンのイベントハンドラと同様に、ボタンのウィジェットを代入する属性の名前の前後に `on_` と `_clicked` を加えたものにしました。仮引数 `b=None` は、イベントループから呼び出される場合と、キー入力で呼び出される場合の両方に対応できるようにするためのものです。詳細は[以前の記事](https://qiita.com/ysgeso/items/17c74178449148ad3ef7#-%E3%82%AD%E3%83%BC%E3%81%AB%E5%AF%BE%E5%BF%9C%E3%81%99%E3%82%8B%E5%87%A6%E7%90%86%E3%81%AE%E8%A8%98%E8%BF%B0%E6%96%B9%E6%B3%95)を参照して下さい。

```python
def on_left_button_clicked(b=None):
    if self.centernode.parent is not None:
        self.centernode = self.centernode.parent
        self.update_gui()
```

### 子ノードへの移動

→ ボタンを押した時に行う子のノードへの移動は、以下の手順で行うことができます。

* `centernode` に子ノードが存在する場合は、最初の子ノードを `centernode` に代入する
* 子ノードが存在しない場合は何もしない
* `update_gui` を呼び出して、Figure の描画を更新する

従って、このイベントハンドラは下記のプログラムで記述できます。__子ノードが存在するか__ どうかは、__親ノードの `children` 属性の要素の数が 0 より大きいか__ どうかで判定できます。

```python
def on_right_button_clicked(b=None):
    if len(self.centernode.children) > 0:
        self.centernode = self.centernode.children[0]
        self.update_gui()
```

### 親ノードと子ノードへの移動に対応する `create_event_handler` の定義

まだ、兄弟ノードへの移動に対応するイベントハンドラを定義していませんが、親ノードと子ノードへの移動ができれば、Mbtree_GUI の部分木の表示を変更できるようになるので、親ノードと子ノードへの移動に対応する `create_event_handler` を、下記のプログラムのように定義する事にします。

:::note
プログラムは、新しい機能を実装した際に、なるべくその都度正しく実装できているかどうかをチェックするべきです。そうしないと、バグが発生した場合に、どこでバグが発生したかを見つけることが困難になるからです。
:::

* __2 ~ 10 行目__：上記のイベントハンドラを定義する
* __12、13 行目__：イベントハンドラとボタンを結び付ける
* __15 ~ 24 行目__：キー入力とイベントハンドラを結び付ける。詳細は[以前の記事](https://qiita.com/ysgeso/items/17c74178449148ad3ef7#%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%A0%E3%81%AE%E6%94%B9%E8%89%AF)を参照

```python
 1  def create_event_handler(self):
 2      def on_left_button_clicked(b=None):
 3          if self.centernode.parent is not None:
 4              self.centernode = self.centernode.parent
 5              self.update_gui()
 6              
 7      def on_right_button_clicked(b=None):
 8          if len(self.centernode.children) > 0:
 9              self.centernode = self.centernode.children[0]
10              self.update_gui()
11            
12      self.left_button.on_click(on_left_button_clicked)
13      self.right_button.on_click(on_right_button_clicked)
14
15      def on_key_press(event):
16          keymap = {
17              "left": on_left_button_clicked,
18              "right": on_right_button_clicked,
19          }
20          if event.key in keymap:
21              keymap[event.key]()
22            
23      # fig の画像イベントハンドラを結び付ける
24      self.fig.canvas.mpl_connect("key_press_event", on_key_press)        
25
26  Mbtree_GUI.create_event_handler = create_event_handler
```

<details><summary><strong>行番号のないプログラム</strong></summary>

```python
def create_event_handler(self):
    def on_left_button_clicked(b=None):
        if self.centernode.parent is not None:
            self.centernode = self.centernode.parent
            self.update_gui()
            
    def on_right_button_clicked(b=None):
        if len(self.centernode.children) > 0:
            self.centernode = self.centernode.children[0]
            self.update_gui()
            
    self.left_button.on_click(on_left_button_clicked)
    self.right_button.on_click(on_right_button_clicked)

    def on_key_press(event):
        keymap = {
            "left": on_left_button_clicked,
            "right": on_right_button_clicked,
        }
        if event.key in keymap:
            keymap[event.key]()
            
    # fig の画像イベントハンドラを結び付ける
    self.fig.canvas.mpl_connect("key_press_event", on_key_press)        

Mbtree_GUI.create_event_handler = create_event_handler
```
</details>

上記の修正後に、下記のプログラムを実行し、← と → ボタンまたは、左右のカーソルキーを押すと、部分木の中心となるノードが変化するようになることを確認して下さい。

```python
mbtree_gui = Mbtree_GUI(mbtree)
```

なお、→ ボタンを何度も押すと、下記の画面が表示され、それ以上 → ボタンを押しても表示が変化しないようになります。その理由について少し考えてみて下さい。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/32be0c84-0ee4-6241-ece5-184c8682ffe8.png" width="300">

その理由は、上図では、右上の 〇 が勝利した局面を中心とする部分木が描画されているからです。決着がついた局面には子ノードは存在しないので、右上のゲーム盤の右には何も表示されず、→ ボタンをクリックしても子ノードには移動しません。

現状の Mbtree_GUI には、このように、__中心となるノードが何であるかがわかりづらいという欠点__ があります。その欠点については次回の記事で修正します。

### 兄弟ノードへの移動に対応する `create_event_handler` の定義

↑ ボタンを押した時に行う、一つ前の兄弟ノードへの移動の処理は、親ノードや子ノードへの移動のように簡単ではありません。その方法について少し考えてみて下さい。

一つ前の兄弟ノードへの移動の処理は、以下の手順で行うことができます。

1. `centernode` に親ノードが存在しない場合は、兄弟ノードは存在しないので何もしない
2. 親ノードの子ノードの一覧を表す `centernode.parent.childnode` 属性に代入された list の中で、`centernode` が何番のインデックスに代入されているかを調べる
3. そのインデックスが 0 の場合は、一つ前の兄弟ノードは存在しないので何もしない
4. 0 以外の場合は、一つ前のインデックスの要素を `childnode` に代入する
5. `update_gui` を呼び出して、Figure の描画を更新する

上記の処理を行うイベントハンドラは、下記のプログラムで記述できます。

* __2 行目__：親ノードが存在する場合のみ処理を行うようにする
* __3 行目__：親ノードの子ノードの list の中で、`centernode` が代入されている要素のインデックスを、[以前の記事](https://qiita.com/ysgeso/items/0bad81b51c0ccbdbc3c7#startnode-%E3%81%AE%E6%8F%8F%E7%94%BB%E4%BD%8D%E7%BD%AE%E3%81%AE%E3%83%90%E3%82%B0%E3%81%AE%E6%A4%9C%E8%A8%BC%E3%81%A8%E4%BF%AE%E6%AD%A3)で説明した `index` メソッドを使って計算する
* __4、5 行目__：`index` が 0 以上の場合は、一つ前のインデックスを `centernode` とする

```python
1  def on_up_button_clicked(b=None):
2      if self.centernode.parent is not None:
3          index = self.centernode.parent.children.index(self.centernode)
4          if index > 0:
5              self.centernode = self.centernode.parent.children[index - 1]
6              self.update_gui()
```

同様に、一つ後の兄弟ノードへの移動は、上記の手順 3、4 を下記のように修正することで行うことができます。

3\. そのインデックスが最後のインデックスの場合は、一つ後の兄弟ノードは存在しないので何もしない
4\. それ以外の場合は、一つ後のインデックスの要素を `childnode` に代入する

上記の処理を行うイベントハンドラは、下記のプログラムで記述できます。

* __4 行目__：`centernode` が、親ノードの子ノードの最後の要素でないことは、__list 最後の要素を表す [-1]__ と `is not` 演算子を利用することで判定できる

```python
1  def on_down_button_clicked(b=None):
2      if self.centernode.parent is not None:
3          index = self.centernode.parent.children.index(self.centernode)
4          if self.centernode.parent.children[-1] is not self.centernode:
5              self.centernode = self.centernode.parent.children[index + 1]
6              self.update_gui()
```

:::note
最後の要素に代入されているということは、その要素のインデックスが __list の要素の数 - 1__ であるということです。従って、上記の 4 行目は、下記のように親ノードの子ノードの数を使った式で記述することもできます。

```python
if index != len(self.centernode.parent.children) - 1:
```
:::

下記は、上記のイベントハンドラを組み込んだ `create_event_handler` のプログラムです。先程とほぼ同様なので、説明は省略します。

```python
def create_event_handler(self):
    def on_left_button_clicked(b=None):
        if self.centernode.parent is not None:
            self.centernode = self.centernode.parent
            self.update_gui()
            
    def on_right_button_clicked(b=None):
        if len(self.centernode.children) > 0:
            self.centernode = self.centernode.children[0]
            self.update_gui()

    def on_up_button_clicked(b=None):
        if self.centernode.parent is not None:
            index = self.centernode.parent.children.index(self.centernode)
            if index > 0:
                self.centernode = self.centernode.parent.children[index - 1]
                self.update_gui()
            
    def on_down_button_clicked(b=None):
        if self.centernode.parent is not None:
            index = self.centernode.parent.children.index(self.centernode)
            if self.centernode.parent.children[-1] is not self.centernode:
                self.centernode = self.centernode.parent.children[index + 1]
                self.update_gui()            
            
    self.left_button.on_click(on_left_button_clicked)
    self.right_button.on_click(on_right_button_clicked)
    self.up_button.on_click(on_up_button_clicked)
    self.down_button.on_click(on_down_button_clicked)

    def on_key_press(event):
        keymap = {
            "left": on_left_button_clicked,
            "right": on_right_button_clicked,
            "up": on_up_button_clicked,
            "down": on_down_button_clicked,
        }
        if event.key in keymap:
            keymap[event.key]()
            
    # fig の画像イベントハンドラを結び付ける
    self.fig.canvas.mpl_connect("key_press_event", on_key_press)        

Mbtree_GUI.create_event_handler = create_event_handler
```

上記の修正後に、下記のプログラムを実行し、←、→、↑、↓ ボタンまたは、カーソルキーを押すと、部分木の中心となるノードが変化するようになることを確認して下さい。

```python
mbtree_gui = Mbtree_GUI(mbtree)
```

また、下図のように、最も縦幅が必要になる、深さ 1 のノードを中心とする部分木が、Figure の中にすべて表示されることも確認して下さい。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/8a42a731-a204-785c-6514-52d74a496982.png" width="200">

# 今回の記事のまとめ

今回の記事では、ゲーム木を表示する GUI を作成し、4 つのボタンで描画する部分木の中心となるノードを移動できるようにしました。ただし、現状では中心となるノードが何であるかが分かりにくいなどの問題があります。次回の記事ではそれらの問題を修正するので、どのような問題があるかについて考えておいてください。

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

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

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

以下のリンクは、今回の記事で更新した tree.py です。

https://github.com/ysgeso/marubatsu/blob/master/094/tree_new.py

以下のリンクは、今回の記事で更新した gui.py です。

https://github.com/ysgeso/marubatsu/blob/master/094/gui_new.py

# 次回の記事

https://qiita.com/ysgeso/items/17b0438676f4a41a906c

[^1]:ご存じの方がいらっしゃれば、コメントに書いていただければ嬉しいです
[^2]:メソッドの名前が `subplots` のように、複数形になっているのはそのためです