Pythonで〇×ゲームのAIを一から作成する　その119　matplotlib での文字の表示の性質と部分木の表示サイズを変更する FloatSlider の実装

# 目次と前回の記事

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

https://qiita.com/ysgeso/items/0f83bcdf76bac5b0175f

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

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

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

AI の一覧とこれまでに作成したデータファイルについては、下記の記事を参照して下さい。

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

# Marubatsu_GUI クラスの問題点と改良

〇×ゲームの GUI の下に GUI の部分木を Marubatsu_GUI クラスを使って表示するようにしたことで、Marubatsu_GUI クラスにいくつかの問題点や改良したほうが良い点が生じるようになりました。どのような問題点や改良の余地があるかについて少し考えてみて下さい。

# 評価値の文字の表示に関する問題の修正

[前回の記事](https://qiita.com/ysgeso/items/0f83bcdf76bac5b0175f#%E8%A9%95%E4%BE%A1%E5%80%A4%E3%81%AE%E8%A1%A8%E7%A4%BA)の修正によって、下記のプログラムのように __`gui_play()`__ を実行した際に、実行結果のように GUI の部分木の __局面の上に評価値を表示__ するようにしましたが、下図からわかるように __評価値の文字の上端__ が上にある __ゲーム盤に重なって表示される__ という問題が発生しています。なお、`gui_play()` を実行して最初に表示される部分木の 0 という評価値は重なって表示されていることがわかりづらいので、下図では評価値として 1 が表示される局面を選択した場合の図にしました。

```python
from util import gui_play

gui_play()
```

実行結果

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/64be540b-2912-6745-52da-c9b3890e430d.png" width="500">

ただし、下記のプログラムで __Mbtree_GUI クラスのインスタンスを直接作成__ してゲーム木の部分木を表示した場合は、実行結果のように評価値が上にある __ゲーム盤に重なって表示されることはありません__。

```python
from tree import Mbtree, Mbtree_GUI

mbtree = Mbtree.load("../data/aidata")
Mbtree_GUI(mbtree)
```

実行結果

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/e3d28bb4-3f28-b37f-bf2b-ac73f5147a6b.png" width="500">

## 評価値が重なって表示される問題の原因

上記の 2 つの違いが生じる理由は、matplotlib の Axes の __`text` メソッドで文字を描画する際__ に、表示される __文字の大きさ__ が Figure の大きさや、Axes の表示範囲に影響されず、__常に同じ大きさで表示されるようになっている__ ことが原因です。

### matplotlib での文字の表示の性質

具体例を挙げて説明します。

下記のプログラムは、matplotlib で以下のような図形と文字を 4 ~ 7、9 ~ 12、14 ~ 19 行目で 3 回描画するプログラムです。__図形を描画する `plot`__ メソッドと __文字を描画する `text`__ メソッドの処理は __3 回の描画で全く同じ__ ですが、__Figure の大きさ__ と __Axes の表示範囲__ は下記の表のように __それぞれ異なります__。なお、実際には 3 つの Figure が縦に並べて表示されますが、実行結果ではわかりやすさを重視して横に並べて表示しました。

* `plot` メソッドで (0, 0)、(1, 0)、(0.5, 1) を頂点とする三角形を描画する
* `text` メソッドで (0.5, 0) の座標に `fontsize` が 20 で A という文字を描画する

| Figure の大きさ | Axes の x 座標の表示範囲 | Axes の y 座標の表示範囲 |
|:-:|:-:|:-:|
| (1, 1)  | 0 ~ 1  | 0 ~ 1  |
| (2, 2)  | 0 ~ 1  | 0 ~ 1  |
| (2, 2)  | -1 ~ 2  | -1 ~ 2  |

```python
 1  import matplotlib.pyplot as plt
 2  import japanize_matplotlib
 3
 4  fig, ax = plt.subplots(figsize=(1, 1))
 5  ax.plot([0, 1, 0.5, 0], [0, 0, 1, 0])
 6  ax.text(0.5, 0, "A", fontsize=20)
 7  plt.show()
 8
 9  fig, ax = plt.subplots(figsize=(2, 2))
10  ax.plot([0, 1, 0.5, 0], [0, 0, 1, 0])
11  ax.text(0.5, 0, "A", fontsize=20)
12  plt.show()
13
14  fig, ax = plt.subplots(figsize=(2, 2))
15  ax.set_xlim(-1, 2)
16  ax.set_ylim(-1, 2)
17  ax.plot([0, 1, 0.5, 0], [0, 0, 1, 0])
18  ax.text(0.5, 0, "A", fontsize=20)
19  plt.show()
```

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

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

fig, ax = plt.subplots(figsize=(1, 1))
ax.plot([0, 1, 0.5, 0], [0, 0, 1, 0])
ax.text(0.5, 0, "A", fontsize=20)
plt.show()

fig, ax = plt.subplots(figsize=(2, 2))
ax.plot([0, 1, 0.5, 0], [0, 0, 1, 0])
ax.text(0.5, 0, "A", fontsize=20)
plt.show()

fig, ax = plt.subplots(figsize=(2, 2))
ax.set_xlim(-1, 2)
ax.set_ylim(-1, 2)
ax.plot([0, 1, 0.5, 0], [0, 0, 1, 0])
ax.text(0.5, 0, "A", fontsize=20)
plt.show()
```
</details>

実行結果

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/c8e1902e-9ec5-b65a-ac6d-2c015387a149.png" width="100">　<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/aba8e03b-0c32-42a0-e57a-be2464af4ffc.png" width="200">　<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/0bc21854-9134-6cba-35ac-50f9464800af.png" width="200">

実行結果からわかるように __三角形の図形__ は Figure の大きさや Axes の表示範囲が変化すると __見た目の大きさが変わります__ が、`text` メソッドで表示した __A という文字は常に同じ大きさで表示__ されます。

:::note warn
matplotlib では、Axes の __`text` メソッドで表示される文字列__ は、図形の描画とは異なり Figure の大きさや Axes の表示範囲に影響されず、キーワード引数 __`fontsize` で設定された値が同じ__ であれば、__常に同じ大きさで表示される__。
:::

__`gui_play()`__ で表示される GUI の部分木は、Marubatsu_GUI クラスの `__init__` メソッドの中の下記のプログラムのように、部分木を表示する __Figure の大きさを表すキーワード引数に `size=0.1`__ を記述して Mbtree_GUI クラスのインスタンスを作成することで表示されます。

```python
self.mbtree_gui = Mbtree_GUI(Marubatsu_GUI.mbtree, size=0.1)
```

一方、`Mbtree_GUI(mbtree)` のように、Mbtree_GUI クラスのインスタンスを、__キーワード引数 `size` を記述せずに作成__ した場合は、下記の Mbtree_GUI クラスの `__init__` メソッドの定義から、仮引数 __`size`__ にはデフォルト値である __0.15 が代入__ されます。

```python
def __init__(self, mbtree, size=0.15):
```

そのため、__`gui_play()`__ で表示される下図左の GUI の部分木を表示する Figure と、下図右の __`Mbtree_GUI(mbtree)`__ で表示される部分木を表示する __Figure の大きさの比率__ は __0.1 対 0.15 = 1 対 1.5__ になるので、右図では左図の __1.5 倍の大きさで部分木が表示__ されます。

一方で、__評価値の文字__ はどちらも __全く同じ大きさで表示__ されますが、左図ではゲーム盤と __ゲーム盤の表示の間隔__ が __右図よりも狭いため__、評価値の文字の上部がゲーム盤に重なって表示されるようになります。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/d597d5a1-0342-c672-8e15-2c659c81558f.png" width="500">

__評価値の文字の大きさが同じ__ であることは、下図のようにそれぞれのゲーム盤と評価値の表示を横に並べ、評価値の文字の上と下に水平に線を引くことで確認することができます。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/a90eaf03-cb79-62fd-7f21-5797b082d6f9.png" width="200">

文字の大きさが変わらないことは、下記のプログラムのように __キーワード引数に `size=0.5`__ を記述して Mbtree_GUI クラスのインスタンスを作成することではっきりと確認することができます。実行結果のようにゲーム盤が大きく表示されますが、__評価値の文字の大きさは明らかに変わっていません__。

```python
Mbtree_GUI(mbtree, size=0.5)
```

実行結果

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/58372a84-d586-bc87-4a72-03af03effd7e.png" width="300">

## 問題の修正方法

この問題は、Node クラスの __`draw_node`__ で __評価値を表示する際__ に、ゲーム盤の表示の大きさを表す __仮引数 `size`__ の値に __比例する大きさ__ で評価値の文字列を表示するようにすることで解決することができます。

`size` の値に比例する `fontsize` の値を計算する式は、__`size` に `1` が代入されている場合の `fontsize`__ の値を計算し、その値に __`size` を掛け算する__ という式で表すことができます。

これまでのプログラムでは、`Mbtree_GUI(mbtree)` を実行した際に仮引数 __`size`__ にはデフォルト値である __`0.15` が代入__ され、__評価値の文字__ は `draw_node` 内の下記のプログラムでフォントサイズを表すキーワード引数 __`fontsize` に `10` を指定して表示__ しています。

```python
ax.text(dx, y - 0.1, self.score, fontsize=10)
```

### `draw_node` の修正

従って、__`size` が `1` の場合の `fontsize`__ は __10 ÷ 0.15 = 66.666・・・__ になり、`fontsize` の値は __`66.666 * size` という式で計算__ することができます。ただし、66.666 という数字はきりが良くないので、下記のプログラムの 6 行目のように少し大きめのきりの良い __`70 * size`__ という式で `fontsize` を計算するように `draw_node` を修正することにします。

```python
1  from tree import Node, Rect
2  from marubatsu import Marubatsu_GUI
3
4  def draw_node(self, ax=None, maxdepth=None, emphasize=False, darkness=0, size=0.25, lw=0.8, dx=0, dy=0):  
元と同じなので省略
5      if hasattr(self, "score"):
6          ax.text(dx, y - 0.1, self.score, fontsize=70*size)
元と同じなので省略
7
8  Node.draw_node = draw_node
```

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

```python
from tree import Node, Rect
from marubatsu import Marubatsu_GUI

def draw_node(self, ax=None, maxdepth=None, emphasize=False, darkness=0, size=0.25, lw=0.8, dx=0, dy=0):  
    width = 8
    if ax is None:
        height = len(self.children) * 4
        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")
        for childnode in self.children:
            childnode.height = 4
        self.height = height         
        
    # 自分自身のノードを真ん中の位置になるように (dx, dy) からずらして描画する
    y = dy + (self.height - 3) / 2
    bc = "red" if emphasize else None
    Marubatsu_GUI.draw_board(ax, self.mb, show_result=True, 
                            score=getattr(self, "score", None), bc=bc, darkness=darkness, lw=lw, dx=dx, dy=y)
    if hasattr(self, "score"):
        ax.text(dx, y - 0.1, self.score, fontsize=70*size)
    rect = Rect(dx, y, 3, 3)
    # 子ノードが存在する場合に、エッジの線と子ノードを描画する
    if len(self.children) > 0:
        if maxdepth != self.depth:   
            ax.plot([dx + 3.5, dx + 4], [y + 1.5, y + 1.5], c="k", lw=lw)
            prevy = None
            for childnode in self.children:
                childnodey = dy + (childnode.height - 3) / 2
                if maxdepth is None:
                    Marubatsu_GUI.draw_board(ax, childnode.mb, show_result=True,
                                            score=getattr(childnode, "score", None), dx=dx+5, dy=childnodey, lw=lw)
                edgey = childnodey + 1.5
                ax.plot([dx + 4 , dx + 4.5], [edgey, edgey], c="k", lw=lw)
                if prevy is not None:
                    ax.plot([dx + 4 , dx + 4], [prevy, edgey], c="k", lw=lw)
                prevy = edgey
                dy += childnode.height
        else:
            ax.plot([dx + 3.5, dx + 4.5], [y + 1.5, y + 1.5], c="k", lw=lw)
            
    return rect

Node.draw_node = draw_node
```
</details>

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

```diff_python
from tree import Node, Rect
from marubatsu import Marubatsu_GUI

def draw_node(self, ax=None, maxdepth=None, emphasize=False, darkness=0, size=0.25, lw=0.8, dx=0, dy=0):  
元と同じなので省略
    if hasattr(self, "score"):
-       ax.text(dx, y - 0.1, self.score, fontsize=10)
+       ax.text(dx, y - 0.1, self.score, fontsize=70*size)
元と同じなので省略

Node.draw_node = draw_node
```
</details>

上記の修正後に、下記のプログラムを実行すると修正前と異なり、実行結果のように __評価値の文字が上のゲーム盤に重なって表示されてしまう__ という問題が発生することがわかります。その原因について少し考えてみて下さい。

```python
Mbtree_GUI(mbtree)
```

実行結果

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/c02e530b-c2fe-856e-2553-9b2ccd07e4e1.png" width="200">

### 問題の検証

原因として、__部分木の大きさを表す__ `Mbtree_GUI` クラスの `__init__` メソッドの __仮引数 `size`  の値__ が __評価値の文字の大きさに正しく反映されていない可能性がある__ ので、下記のプログラムのようにキーワード引数に `size=0.5` を記述して実行してみると、実行結果のように __評価値の文字の大きさが変化しない__ ことから __その可能性が高い__ ことがわかります。


```python
Mbtree_GUI(mbtree, size=0.5)
```

実行結果

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/70905b27-b62c-bb89-5ac8-22d17a92ea0e.png" width="300">

そこで、Mbtree_GUI クラスの `__init__` メソッドの __仮引数 `size` の値が__  Node クラスの `draw_node` メソッドで __評価値を表示する際にどのように利用されるかを検証__ します。

Mbtree_GUI クラスでは、__`update_gui`__ メソッドによって __ゲーム木の表示が更新される__ ので __その処理を辿っていく__ と、下記の手順で Node クラスの `draw_node` が呼び出されて評価値の文字が表示されることが確認できます。

* Mbtree_GUI クラスの `__init__` メソッドで __仮引数 `size`__ の値が __`size` 属性に代入__ される
* Mbtree_GUI クラスの __`update_gui`__ メソッドで __Mbtree クラスの `draw_subtree`__ メソッドが __呼び出される__
* Mbtree クラスの `draw_subtree` メソッドで __Node クラスの `draw_node`__ メソッドが __呼び出される__

まず `update_gui` を調べると、Mbtree クラスの __`draw_subtree` メソッドを呼び出す__ 下記のプログラムには __キーワード引数 `size` が記述されていない__ ことがわかります。

```python
self.mbtree.draw_subtree(centernode=centernode, selectednode=self.selectednode,
                         show_bestmove=True, ax=self.ax, maxdepth=maxdepth)
```

従って、上記のプログラムが実行されると、下記の `draw_subtree` の定義の 2 行目から、__`draw_subtree` の仮引数 `size`__ にはデフォルト値である __0.25 が代入__ されることになります。

```python
1  def draw_subtree(self, centernode=None, selectednode=None, ax=None, anim_frame=None,
2                  isscore=False, show_bestmove=False, size=0.25, lw=0.8, maxdepth=2):
略
```

また、`draw_subtree` 内では Node クラスの `draw_node` メソッドが 5 箇所で呼ばれていますが、__いずれも__ 下記のプログラムの 2 行目のように __キーワード引数 `size=size` を記述__ して呼び出しているので、__`draw_node` の仮引数 `size`__ には __0.25 が代入__ されることになります。 

```python
1  rect = node.draw_node(ax=ax, maxdepth=maxdepth, emphasize=emphasize,
2                        darkness=darkness, size=size, lw=lw, dx=dx, dy=dy)
```

`draw_node` で __評価値を表示するプログラム__ は下記のように記述されているので、評価値の文字の大きさを表す __`fontsize`__ には 70 * 0.25 = __17.5__ という、今回の記事で __修正する前__ の `fontsize` の値である __10 よりも大きな値が計算__ されます。これが、`Mbtree_GUI(mbtree)` を実行した際に評価値の文字が修正前よりも大きく表示されてしまう原因です。

```python
ax.text(dx, y - 0.1, self.score, fontsize=70*size)
```

### 問題の修正

この問題は、__`update_gui`__ メソッドで __`draw_subtree` を呼び出す際__ に、__キーワード引数 `size` を記述していないことが原因__ なので、下記のプログラムの 3 行目のようにキーワード引数 `size=self.size` を記述することで修正することができます。

```python
1  def update_gui(self):
元と同じなので省略
2      self.mbtree.draw_subtree(centernode=centernode, selectednode=self.selectednode,
3                       show_bestmove=True, ax=self.ax, maxdepth=maxdepth, size=self.size)
元と同じなので省略
4
5  Mbtree_GUI.update_gui = update_gui
```

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

```python
def update_gui(self):
    self.ax.clear()
    self.ax.set_xlim(-1, self.width - 1)
    self.ax.set_ylim(-1, self.height - 1)   
    self.ax.invert_yaxis()
    self.ax.axis("off")   
    
    if self.selectednode.depth <= 4:
        maxdepth = self.selectednode.depth + 1
    elif self.selectednode.depth == 5:
        maxdepth = 7
    else:
        maxdepth = 9
    centernode = self.selectednode
    while centernode.depth > 6:
        centernode = centernode.parent
    self.mbtree.draw_subtree(centernode=centernode, selectednode=self.selectednode,
                     show_bestmove=True, ax=self.ax, maxdepth=maxdepth, size=self.size)
    
    disabled = self.selectednode.parent is None
    self.set_button_status(self.left_button, disabled=disabled)
    disabled = self.selectednode.depth >= 6 or len(self.selectednode.children) == 0
    self.set_button_status(self.right_button, disabled=disabled)
    disabled = self.selectednode.parent is None or self.selectednode.parent.children.index(self.selectednode) == 0
    self.set_button_status(self.up_button, disabled=disabled)
    disabled = self.selectednode.parent is None or self.selectednode.parent.children[-1] is self.selectednode
    self.set_button_status(self.down_button, disabled=disabled)

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

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

```diff_python
def update_gui(self):
元と同じなので省略
    self.mbtree.draw_subtree(centernode=centernode, selectednode=self.selectednode,
-                    show_bestmove=True, ax=self.ax, maxdepth=maxdepth)
+                    show_bestmove=True, ax=self.ax, maxdepth=maxdepth, size=self.size)
元と同じなので省略

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

上記の修正後に下記のプログラムを実行することで、実行結果のように評価値の文字が正しい大きさで表示されるようになることが確認できます。

```python
Mbtree_GUI(mbtree)
```

実行結果

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/c60c58e2-4692-b08c-e741-c63a643b6329.png" width="300">

また、下記のプログラムのようにキーワード引数 `size=0.5` を記述して実行すると、実行結果のように評価値がゲーム盤の大きさに合わせた大きさで表示されることが確認できます。

```python
Mbtree_GUI(mbtree, size=0.5)
```

実行結果

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/7abea1ef-08ee-1ca7-c66d-07d559e74493.png" width="300">

また、下記のプログラムのように `gui_play()` を実行した際でも評価値の大きさがゲーム盤の大きさに合わせた大きさで表示されるようになり、上のゲーム盤に重ならなくなったことが確認できます。

```python
gui_play()
```

実行結果

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/ae591688-c76e-af35-ff80-7877350da511.png" width="300">

以上で、評価値の文字の大きさの問題の修正は完了です。

# FloatSlider ウィジェットによる部分木の表示の大きさの変更

Marubatsu_GUI クラスによって表示される部分木の表示の大きさは、インスタンスを作成した際にキーワード引数 `size` を記述することで設定できますが、__後から部分木の表示の大きさを調整できるほうが便利__ だと思いましたので、そのように改良することにします。

__部分木の表示の大きさ__ は Marubatsu_GUI クラスの __`size` 属性__ に __float 型のデータで代入される__ ので、その値を ipywidgets の __float 型のデータを扱う FloatSlider__ というウィジェットを使って変更できるようにすることにします。FloatSlider は float 型のデータを扱えるという性質を除くと、[以前の記事](https://qiita.com/ysgeso/items/17c74178449148ad3ef7#intslider-%E3%81%AE%E5%B1%9E%E6%80%A7%E3%81%A8%E8%A1%A8%E7%A4%BA)で紹介した整数を扱う IntSlider と同じ性質を持ちます。

下記は FloatSlider の主な属性です。

| 属性 | 意味 | デフォルト値 |
|:--|:--|--:|
| __`value`__  | 現在の値  | `0` |
| __`min`__  | 最小値  | `0` |
| __`max`__  | 最大値  | `100` |
| __`step`__ | 選択できる数値の間隔 | `0.1` | 
| __`description`__  | 左に表示される説明  | `""` |
| __`disabled`__ | `True` の場合に操作できなくなる | `False` |

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

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

https://ipywidgets.readthedocs.io/en/stable/reference/ipywidgets.html#ipywidgets.widgets.widget_float.FloatSlider

## `create_widgets` の修正

まず、下記のプログラムのように `create_widgets` の中で FloatSlider を作成します。下記のプログラムでは FloatSlider の最小値を 0.05、最大値を 0.25、数値の間隔を 0.01、初期値を `self.size`、説明文を size としましたが、別の値に設定したい方は自由に変更して下さい。

* __5、6 行目__：上記の設定の FloatSlider を作成し、`size_slider` 属性に代入する

```python
1  import ipywidgets as widgets
2
3  def create_widgets(self):
元と同じなので省略
4      self.down_button = self.create_button("↓", 50)
5      self.size_slider = widgets.FloatSlider(min=0.05, max=0.25, step=0.01, 
6                                             description="size", value=self.size)
7      self.help_button = self.create_button("？", 50)
元と同じなので省略
8
9  Mbtree_GUI.create_widgets = create_widgets 
```

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

```python
import ipywidgets as widgets

def create_widgets(self):
    self.output = widgets.Output()  
    self.print_helpmessage()
    self.output.layout.display = "none"
    self.left_button = self.create_button("←", 50)
    self.up_button = self.create_button("↑", 50)
    self.right_button = self.create_button("→", 50)
    self.down_button = self.create_button("↓", 50)
    self.size_slider = widgets.FloatSlider(min=0.05, max=0.25, step=0.01, 
                                           description="size", value=self.size)
    self.help_button = self.create_button("？", 50)
    self.label = widgets.Label(value="", layout=widgets.Layout(width=f"50px"))
    
    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
import ipywidgets as widgets

def create_widgets(self):
元と同じなので省略
    self.down_button = self.create_button("↓", 50)
+   self.size_slider = widgets.FloatSlider(min=0.05, max=0.25, step=0.01, 
+                                          description="size", value=self.size)
    self.help_button = self.create_button("？", 50)
元と同じなので省略

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

:::note
FloatSlider の値の範囲が 0.05 ~ 0.25 ではわかりづらいと思った方は、例えば 1 ~ 20 などの範囲の IntSlider に変更するという方法があります。その場合は、この後で修正する `display_widgets` で Figure のサイズを計算する式を、変更した値に応じた式に修正して下さい。
:::

## `display_widgets` の修正

次に、FloatSlider を __どこに表示するかを決める__ 必要あります。本記事では下記のプログラムのように、2 行目の → ボタンと ？ ボタンの間に配置することにしました。なお、→ ボタンと ？ ボタンの間を空けるために配置した空白のウィジェットである `self.label` を間に入れると間が空きすぎるので入れないようにしました。

* __4 行目__：→ ボタンと ？ ボタンのウィジェットの間に、FloatSlider のウィジェットを配置する。その際に間にあった `self.label` は削除した

```python
1  def display_widgets(self):    
2      hbox1 = widgets.HBox([self.label, self.up_button, self.label])
3      hbox2 = widgets.HBox([self.left_button, self.label, self.right_button,
4                            self.size_slider, self.help_button])
5      hbox3 = widgets.HBox([self.label, self.down_button, self.label])
6      self.vbox = widgets.VBox([self.output, hbox1, hbox2, hbox3, self.fig.canvas])
7      display(self.vbox)  
8    
9  Mbtree_GUI.display_widgets = display_widgets
```

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

```python
def display_widgets(self):    
    hbox1 = widgets.HBox([self.label, self.up_button, self.label])
    hbox2 = widgets.HBox([self.left_button, self.label, self.right_button,
                          self.size_slider, self.help_button])
    hbox3 = widgets.HBox([self.label, self.down_button, self.label])
    self.vbox = widgets.VBox([self.output, hbox1, hbox2, hbox3, self.fig.canvas])
    display(self.vbox)  
    
Mbtree_GUI.display_widgets = display_widgets
```
</details>

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

```diff_python
def display_widgets(self):    
    hbox1 = widgets.HBox([self.label, self.up_button, self.label])
    hbox2 = widgets.HBox([self.left_button, self.label, self.right_button,
-                         self.label, self.help_button])
+                         self.size_slider, self.help_button])
    hbox3 = widgets.HBox([self.label, self.down_button, self.label])
    self.vbox = widgets.VBox([self.output, hbox1, hbox2, hbox3, self.fig.canvas])
    display(self.vbox)  
    
Mbtree_GUI.display_widgets = display_widgets
```
</details>

上記の修正後に下記のプログラムを実行すると、実行結果のように → ボタンと ？ ボタンの間に FloatSlider が表示されるようになります。

```python
Mbtree_GUI(mbtree)
```

実行結果

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/22af334b-4e83-0810-f239-21eca4a6be2e.png" width="500">

## `create_event_handler` の修正

次に、FloatSlider の値を変更した際の __イベントハンドラを定義する必要__ があります。そのイベントハンドラでどのような処理を行えばよいかについて少し考えてみて下さい。

FloatSlider のイベントハンドラには下記の処理を記述する必要があります。

* `self.size` の値を FloatSlider の更新された値に変更する
* Figure のサイズを `self.size` の値に応じたサイズに変更する
* 変更したサイズで __評価値の文字を表示し直す__ ために、`update_gui` を呼び出して __部分木の表示を更新する__
 
Figure のサイズの変更は、Figure の __`set_figwidth`__ と __`set_figheight`__ メソッドで行うことができます。詳しくは下記のリンク先を参照して下さい。

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

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

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

* __2 ~ 6 行目__：FloatSlider の値が変更された際に呼び出されるイベントハンドラを定義する
* __3 行目__：`size` 属性に変更後の FloatSlider の値を表す [`changed["new"]`](https://qiita.com/ysgeso/items/17c74178449148ad3ef7#%E3%82%A4%E3%83%99%E3%83%B3%E3%83%88%E3%83%8F%E3%83%B3%E3%83%89%E3%83%A9%E3%81%AE%E4%BB%AE%E5%BC%95%E6%95%B0%E3%81%AE%E5%80%A4%E3%81%AE%E6%84%8F%E5%91%B3) を代入する
* __4、5 行目__：Figure のサイズを `size` 属性の値を使って計算した値に変更する
* __6 行目__：部分木の表示を更新する
* __15 行目__：イベントハンドラと FloatSlider を結び付ける

```python
 1  def create_event_handler(self):
元と同じなので省略
 2      def on_size_slider_changed(changed):
 3          self.size = changed["new"]
 4          self.fig.set_figwidth(self.width * self.size)
 5          self.fig.set_figheight(self.height * self.size)
 6          self.update_gui()
 7               
 8      def on_help_button_clicked(b=None):
 9          self.output.layout.display = "none" if self.output.layout.display is None else None
10                        
11      self.left_button.on_click(on_left_button_clicked)
12      self.right_button.on_click(on_right_button_clicked)
13      self.up_button.on_click(on_up_button_clicked)
14      self.down_button.on_click(on_down_button_clicked)
15      self.size_slider.observe(on_size_slider_changed, names="value")
16      self.help_button.on_click(on_help_button_clicked)
元と同じなので省略
17    
18  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.selectednode.parent is not None:
            self.selectednode = self.selectednode.parent
            self.update_gui()
            
    def on_right_button_clicked(b=None):
        if len(self.selectednode.children) > 0:
            self.selectednode = self.selectednode.children[0]
            self.update_gui()

    def on_up_button_clicked(b=None):
        if self.selectednode.parent is not None:
            index = self.selectednode.parent.children.index(self.selectednode)
            if index > 0:
                self.selectednode = self.selectednode.parent.children[index - 1]
                self.update_gui()
            
    def on_down_button_clicked(b=None):
        if self.selectednode.parent is not None:
            index = self.selectednode.parent.children.index(self.selectednode)
            if self.selectednode.parent.children[-1] is not self.selectednode:
                self.selectednode = self.selectednode.parent.children[index + 1]
                self.update_gui()            
                
    def on_size_slider_changed(changed):
        self.size = changed["new"]
        self.fig.set_figwidth(self.width * self.size)
        self.fig.set_figheight(self.height * self.size)
        self.update_gui()
                
    def on_help_button_clicked(b=None):
        self.output.layout.display = "none" if self.output.layout.display is None else None
                        
    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)
    self.size_slider.observe(on_size_slider_changed, names="value")
    self.help_button.on_click(on_help_button_clicked)

    def on_key_press(event):
        keymap = {
            "left": on_left_button_clicked,
            "0": 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]()
        else:
            try:
                num = int(event.key) - 1
                x = num % 3
                y = 2 - (num // 3)
                move = (x, y)
                if move in self.selectednode.children_by_move:
                    self.selectednode = self.selectednode.children_by_move[move]
                    self.update_gui()
            except:
                pass            
            
    def on_mouse_down(event):
        for rect, node in self.mbtree.nodes_by_rect.items():
            if rect.is_inside(event.xdata, event.ydata):
                self.selectednode = node
                self.update_gui()
                break               
            
    # fig の画像イベントハンドラを結び付ける
    self.fig.canvas.mpl_connect("key_press_event", on_key_press)
    self.fig.canvas.mpl_connect("button_press_event", on_mouse_down)
    
Mbtree_GUI.create_event_handler = create_event_handler
```
</details>

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

```diff_python
def create_event_handler(self):
元と同じなので省略
+   def on_size_slider_changed(changed):
+       self.size = changed["new"]
+       self.fig.set_figwidth(self.width * self.size)
+       self.fig.set_figheight(self.height * self.size)
+       self.update_gui()
                
    def on_help_button_clicked(b=None):
        self.output.layout.display = "none" if self.output.layout.display is None else None
                        
    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)
+   self.size_slider.observe(on_size_slider_changed, names="value")
    self.help_button.on_click(on_help_button_clicked)
元と同じなので省略
    
Mbtree_GUI.create_event_handler = create_event_handler
```
</details>

実行結果は省略しますが、上記の修正後に下記のプログラムを実行し、FloatSlider の値をドラッグして変更すると部分木の表示サイズが変更されることを確認して下さい。

```python
Mbtree_GUI(mbtree)
```

# 評価値の表示の有無の切り替え

評価値の表示は、それぞれのノードの具体的な評価値を確認したい場合には便利ですが、〇の必勝の局面、引き分けの局面、×の必勝の局面を区別するだけであれば、ゲーム盤の色で区別できるので評価値を表示する必要はありません。__必要のない情報を表示__ すると、__表示が複雑になってわかりづらくなる__ ので、__必要に応じて評価値の表示の有無を切り替えることができる__ ように改良することにします。

## Node クラスの `draw_node` の修正

評価値の表示の有無を切り替えることができるようにするためには、評価値を表示する処理を行う Node クラスの __`draw_node`__ メソッドに、__評価値を表示するかどうかを表す仮引数を追加する必要__ があります。本記事では、その仮引数の名前を __`show_score`__ とし、下記のプログラムのように `draw_node` を修正することにします。
 
* __3 行目__：デフォルト値を `True` とする仮引数 `show_score` を追加する
* __4 行目__：評価値を表示する条件に、`show_score` が `True` であることを追加する

```python
1  import matplotlib.patches as patches
2
3  def draw_node(self, ax=None, maxdepth=None, emphasize=False, darkness=0, show_score=True, size=0.25, lw=0.8, dx=0, dy=0):
元と同じなので省略
4      if hasattr(self, "score") and show_score:
5          ax.text(dx, y - 0.1, self.score, fontsize=70*size)
元と同じなので省略
6
7  Node.draw_node = draw_node
```

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

```python
import matplotlib.patches as patches

def draw_node(self, ax=None, maxdepth=None, emphasize=False, darkness=0, show_score=True, size=0.25, lw=0.8, dx=0, dy=0):
    width = 8
    if ax is None:
        height = len(self.children) * 4
        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")
        for childnode in self.children:
            childnode.height = 4
        self.height = height         
        
    # 自分自身のノードを真ん中の位置になるように (dx, dy) からずらして描画する
    y = dy + (self.height - 3) / 2
    bc = "red" if emphasize else None
    Marubatsu_GUI.draw_board(ax, self.mb, show_result=True, 
                            score=getattr(self, "score", None), bc=bc, darkness=darkness, lw=lw, dx=dx, dy=y)
    if hasattr(self, "score") and show_score:
        ax.text(dx, y - 0.1, self.score, fontsize=70*size)
    rect = Rect(dx, y, 3, 3)
    # 子ノードが存在する場合に、エッジの線と子ノードを描画する
    if len(self.children) > 0:
        if maxdepth != self.depth:   
            ax.plot([dx + 3.5, dx + 4], [y + 1.5, y + 1.5], c="k", lw=lw)
            prevy = None
            for childnode in self.children:
                childnodey = dy + (childnode.height - 3) / 2
                if maxdepth is None:
                    Marubatsu_GUI.draw_board(ax, childnode.mb, show_result=True,
                                            score=getattr(childnode, "score", None), dx=dx+5, dy=childnodey, lw=lw)
                edgey = childnodey + 1.5
                ax.plot([dx + 4 , dx + 4.5], [edgey, edgey], c="k", lw=lw)
                if prevy is not None:
                    ax.plot([dx + 4 , dx + 4], [prevy, edgey], c="k", lw=lw)
                prevy = edgey
                dy += childnode.height
        else:
            ax.plot([dx + 3.5, dx + 4.5], [y + 1.5, y + 1.5], c="k", lw=lw)
            
    return rect

Node.draw_node = draw_node
```
</details>

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

```diff_python
import matplotlib.patches as patches

-def draw_node(self, ax=None, maxdepth=None, emphasize=False, darkness=0, size=0.25, lw=0.8, dx=0, dy=0):
+def draw_node(self, ax=None, maxdepth=None, emphasize=False, darkness=0, show_score=True, size=0.25, lw=0.8, dx=0, dy=0):
元と同じなので省略
-   if hasattr(self, "score"):
+   if hasattr(self, "score") and show_score:
        ax.text(dx, y - 0.1, self.score, fontsize=70*size)
元と同じなので省略

Node.draw_node = draw_node
```
</details>

## Mbtree クラスの `draw_subtree` メソッドの修正

次に、`draw_node` を呼び出す Mbtree クラスの `draw_subtree` メソッドを下記のプログラムのように修正します。

* __2 行目__：デフォルト値を `True` とする仮引数 `show_score` を追加する
* __4、6、8、10、12 行目__：5 箇所ある `draw_node` の呼び出しすべてにキーワード引数 `show_score` を追加する

```python
 1  def draw_subtree(self, centernode=None, selectednode=None, ax=None, anim_frame=None, isscore=False,
 2                   show_bestmove=False, show_score=True, size=0.25, lw=0.8, maxdepth=2):
元と同じなので省略
 3                  rect = node.draw_node(ax=ax, maxdepth=maxdepth, emphasize=emphasize, darkness=darkness,
 4                                        show_score=show_score, size=size, lw=lw, dx=dx, dy=dy)
元と同じなので省略
 5                          rect = bestnode.draw_node(ax=ax, maxdepth=bestnode.depth, emphasize=emphasize,
 6                                                    show_score=show_score, size=size, lw=lw, dx=dx, dy=dy)
元と同じなので省略
 7                  rect = sibling.draw_node(ax, maxdepth=sibling.depth, size=size, darkness=darkness,
 8                                           show_score=show_score, lw=lw, dx=dx, dy=dy)
元と同じなので省略
 9          rect = parent.draw_node(ax, maxdepth=maxdepth, darkness=darkness, 
10                                  show_score=show_score, size=size, lw=lw, dx=dx, dy=0)
元と同じなので省略
11              rect = node.draw_node(ax, maxdepth=node.depth, darkness=darkness,
12                                    show_score=show_score, size=size, lw=lw, dx=dx, dy=0)
13              self.nodes_by_rect[rect] = node
14            
15  Mbtree.draw_subtree = draw_subtree
```

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

```python
def draw_subtree(self, centernode=None, selectednode=None, ax=None, anim_frame=None, isscore=False,
                 show_bestmove=False, show_score=True, size=0.25, lw=0.8, maxdepth=2):
    def calc_darkness(node):
        """ノードを表示する暗さを計算して返す."""
        
        if show_bestmove:
            if node.parent is None:
                return 0
            elif node.mb.last_move in node.parent.bestmoves:
                return 0
            else:
                return 0.2
            
        if anim_frame is None:
            return 0
        index = node.score_index if isscore else node.id
        return 0.5 if index > anim_frame else 0
    
    self.nodes_by_rect = {}

    if centernode is None:
        centernode = self.root
    self.calc_node_height(N=centernode, maxdepth=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")        
    
    if show_bestmove:
        bestx = 5 * maxdepth + 4
        bestwidth = 50 - bestx
        ax.add_artist(patches.Rectangle(xy=(bestx, -1), width=bestwidth,
                                        height=height + 1, fc="lightgray"))
    
    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
                emphasize = node is selectednode
                darkness = calc_darkness(node)
                rect = node.draw_node(ax=ax, maxdepth=maxdepth, emphasize=emphasize, darkness=darkness,
                                      show_score=show_score, size=size, lw=lw, dx=dx, dy=dy)
                self.nodes_by_rect[rect] = node
                if show_bestmove and depth == maxdepth:
                    bestnode = node
                    while len(bestnode.bestmoves) > 0:
                        bestmove = bestnode.bestmoves[0]
                        bestnode = bestnode.children_by_move[bestmove]
                        dx = 5 * bestnode.depth
                        bestnode.height = 4
                        rect = bestnode.draw_node(ax=ax, maxdepth=bestnode.depth, emphasize=emphasize,
                                                  show_score=show_score, size=size, lw=lw, dx=dx, dy=dy)
                        self.nodes_by_rect[rect] = bestnode                                          
                    
                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
                darkness = calc_darkness(sibling)
                rect = sibling.draw_node(ax, maxdepth=sibling.depth, size=size, darkness=darkness,
                                         show_score=show_score, lw=lw, dx=dx, dy=dy)
                self.nodes_by_rect[rect] = sibling
            dy += sibling.height
        dx = 5 * parent.depth
        darkness = calc_darkness(parent)
        rect = parent.draw_node(ax, maxdepth=maxdepth, darkness=darkness, 
                                show_score=show_score, size=size, lw=lw, dx=dx, dy=0)
        self.nodes_by_rect[rect] = parent
    
        node = parent
        while node.parent is not None:
            node = node.parent
            node.height = height
            dx = 5 * node.depth
            darkness = calc_darkness(node)
            rect = node.draw_node(ax, maxdepth=node.depth, darkness=darkness,
                                  show_score=show_score, size=size, lw=lw, dx=dx, dy=0)
            self.nodes_by_rect[rect] = node
            
Mbtree.draw_subtree = draw_subtree
```
</details>

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

```diff_python
def draw_subtree(self, centernode=None, selectednode=None, ax=None, anim_frame=None, isscore=False,
-                show_bestmove=False, size=0.25, lw=0.8, maxdepth=2):
+                show_bestmove=False, show_score=True, size=0.25, lw=0.8, maxdepth=2):
元と同じなので省略
                rect = node.draw_node(ax=ax, maxdepth=maxdepth, emphasize=emphasize, darkness=darkness,
-                                     size=size, lw=lw, dx=dx, dy=dy)
+                                     show_score=show_score, size=size, lw=lw, dx=dx, dy=dy)
元と同じなので省略
                        rect = bestnode.draw_node(ax=ax, maxdepth=bestnode.depth, emphasize=emphasize,
-                                                 size=size, lw=lw, dx=dx, dy=dy)
+                                                 show_score=show_score, size=size, lw=lw, dx=dx, dy=dy)
元と同じなので省略
                rect = sibling.draw_node(ax, maxdepth=sibling.depth, size=size, darkness=darkness,
-                                        size=size, lw=lw, dx=dx, dy=dy)
+                                        show_score=show_score, size=size, lw=lw, dx=dx, dy=dy)
元と同じなので省略
        rect = parent.draw_node(ax, maxdepth=maxdepth, darkness=darkness, 
-                               size=size, lw=lw, dx=dx, dy=dy)
+                               show_score=show_score, size=size, lw=lw, dx=dx, dy=dy)
元と同じなので省略
            rect = node.draw_node(ax, maxdepth=node.depth, darkness=darkness,
-                                 size=size, lw=lw, dx=dx, dy=dy)
+                                 show_score=show_score, size=size, lw=lw, dx=dx, dy=dy)
            self.nodes_by_rect[rect] = node
            
Mbtree.draw_subtree = draw_subtree
```
</details>

## Mbtree_GUI クラスの `__init__` メソッドの修正

次に、下記のプログラムのように、Mbtree_GUI クラスの __インスタンスの作成時__ に、__キーワード引数 `show_score`__ によって __評価値の表示の有無を設定__ できるように `__init__` メソッドを修正します。

* __1 行目__：デフォルト値を `True` とする仮引数 `show_score` を追加する
* __3 行目__：`show_score` を同名の属性に代入する

```python
 1  def __init__(self, mbtree, show_score=True, size=0.15):
 2      self.mbtree = mbtree
 3      self.show_score = show_score
 4      self.size = size
 5      self.width = 50
 6      self.height = 65
 7      self.selectednode = self.mbtree.root
 8      super(Mbtree_GUI, self).__init__()
 9   
10  Mbtree_GUI.__init__ = __init__
```

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

```python
def __init__(self, mbtree, show_score=True, size=0.15):
    self.mbtree = mbtree
    self.show_score = show_score
    self.size = size
    self.width = 50
    self.height = 65
    self.selectednode = self.mbtree.root
    super(Mbtree_GUI, self).__init__()
    
Mbtree_GUI.__init__ = __init__
```
</details>

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

```diff_python
-def __init__(self, mbtree, size=0.15):
+def __init__(self, mbtree, show_score=True, size=0.15):
    self.mbtree = mbtree
+   self.show_score = show_score
    self.size = size
    self.width = 50
    self.height = 65
    self.selectednode = self.mbtree.root
    super(Mbtree_GUI, self).__init__()
    
Mbtree_GUI.__init__ = __init__
```
</details>

## Mbtree_GUI クラスの `update_gui` メソッドの修正

最後に、下記のプログラムの 3 行目のように `update_gui` クラス内で `draw_subtree` を呼び出す際に、キーワード引数 `show_score=self.show_score` を記述するように修正します。

```python
1  def update_gui(self):
元と同じなので省略
2      self.mbtree.draw_subtree(centernode=centernode, selectednode=self.selectednode,
3                               show_bestmove=True, show_score=self.show_score,
4                               ax=self.ax, maxdepth=maxdepth, size=self.size)
元と同じなので省略
5
6  Mbtree_GUI.update_gui = update_gui
```

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

```python
def update_gui(self):
    self.ax.clear()
    self.ax.set_xlim(-1, self.width - 1)
    self.ax.set_ylim(-1, self.height - 1)   
    self.ax.invert_yaxis()
    self.ax.axis("off")   
    
    if self.selectednode.depth <= 4:
        maxdepth = self.selectednode.depth + 1
    elif self.selectednode.depth == 5:
        maxdepth = 7
    else:
        maxdepth = 9
    centernode = self.selectednode
    while centernode.depth > 6:
        centernode = centernode.parent
    self.mbtree.draw_subtree(centernode=centernode, selectednode=self.selectednode,
                             show_bestmove=True, show_score=self.show_score,
                             ax=self.ax, maxdepth=maxdepth, size=self.size)
    
    disabled = self.selectednode.parent is None
    self.set_button_status(self.left_button, disabled=disabled)
    disabled = self.selectednode.depth >= 6 or len(self.selectednode.children) == 0
    self.set_button_status(self.right_button, disabled=disabled)
    disabled = self.selectednode.parent is None or self.selectednode.parent.children.index(self.selectednode) == 0
    self.set_button_status(self.up_button, disabled=disabled)
    disabled = self.selectednode.parent is None or self.selectednode.parent.children[-1] is self.selectednode
    self.set_button_status(self.down_button, disabled=disabled)

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

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

```diff_python
def update_gui(self):
元と同じなので省略
    self.mbtree.draw_subtree(centernode=centernode, selectednode=self.selectednode,
-                            show_bestmove=True,
+                            show_bestmove=True, show_score=self.show_score,
                             ax=self.ax, maxdepth=maxdepth, size=self.size)
元と同じなので省略

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

実行結果は同じなので省略しますが、上記の修正後に下記のプログラムを実行すると実行結果からこれまでと同じようにゲーム木の部分木に評価値が表示されることが確認できます。

```python
Mbtree_GUI(mbtree)
```

また、下記のプログラムのようにキーワード引数に `show_score=False` を記述すると、実行結果のように評価値が表示されなくなることが確認できます。

```python
Mbtree_GUI(mbtree, show_score=False)
```

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/a5d5c79b-39ec-53d4-c2c9-c9a6f83815c2.png" width="300">

# ボタンによる評価値の表示の有無の切り替え

上記のプログラムでは、Mbtree_GUI クラスのインスタンスを作成する際に評価値の表示の有無をキーワード引数で指定していますが、[以前の記事](https://qiita.com/ysgeso/items/b5e388878fef195d8f2c#gui-%E3%81%AE%E9%83%A8%E5%88%86%E6%9C%A8%E3%81%AE%E8%A1%A8%E7%A4%BA%E3%81%AE%E5%88%87%E3%82%8A%E6%9B%BF%E3%81%88%E3%81%A8%E8%A1%A8%E7%A4%BA%E3%81%AE%E3%83%AA%E3%82%BB%E3%83%83%E3%83%88)でヘルプの表示の有無を ？ ボタンで切り替えるように修正したように、__評価値の表示の有無を切り替えるボタンを用意したほうが便利__ でしょう。

## `create_widgets` の修正

まず、`create_widgets` を下記のプログラムのように修正します。

* __3 行目__：評価値の表示の有無を設定するボタンを作成する

```python
1  def create_widgets(self):
元と同じなので省略
2      self.down_button = self.create_button("↓", 50)
3      self.score_button = self.create_button("評価値の表示", 100)
元と同じなので省略
4    
5  Mbtree_GUI.create_widgets = create_widgets 
```

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

```python
def create_widgets(self):
    self.output = widgets.Output()  
    self.print_helpmessage()
    self.output.layout.display = "none"
    self.left_button = self.create_button("←", 50)
    self.up_button = self.create_button("↑", 50)
    self.right_button = self.create_button("→", 50)
    self.down_button = self.create_button("↓", 50)
    self.score_button = self.score_button = self.create_button("評価値の表示", 100)
    self.size_slider = widgets.FloatSlider(min=0.05, max=0.25, step=0.01, description="size", value=self.size)
    self.help_button = self.create_button("？", 50)
    self.label = widgets.Label(value="", layout=widgets.Layout(width=f"50px"))
    
    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):
元と同じなので省略
    self.down_button = self.create_button("↓", 50)
+   self.score_button = self.score_button = self.create_button("評価値の表示", 100)
元と同じなので省略
    
Mbtree_GUI.create_widgets = create_widgets 
```
</details>

## `display_widgets` の修正

次に、このボタンを __どこに表示するかを決める必要__ あります。本記事では下記のプログラムのように、1 行目の ↑ ボタンの右、2 行目の FloatSlider の上に配置することにしました。なお、FloatSlider の上に配置するために、↑ ボタンの右に self.label を 2 つ配置して表示位置を調整しました。

* __2 行目__：↑ ボタンの右に、`self.label` を 2 つはさんでボタンを配置する

```python
1  def display_widgets(self):    
2      hbox1 = widgets.HBox([self.label, self.up_button, self.label, self.label, self.score_button])
3      hbox2 = widgets.HBox([self.left_button, self.label, self.right_button,
4                            self.size_slider, self.help_button])
5      hbox3 = widgets.HBox([self.label, self.down_button, self.label])
6      self.vbox = widgets.VBox([self.output, hbox1, hbox2, hbox3, self.fig.canvas])
7      display(self.vbox)  
    
Mbtree_GUI.display_widgets = display_widgets
```

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

```python
def display_widgets(self):    
    hbox1 = widgets.HBox([self.label, self.up_button, self.label, self.label, self.score_button])
    hbox2 = widgets.HBox([self.left_button, self.label, self.right_button,
                          self.size_slider, self.help_button])
    hbox3 = widgets.HBox([self.label, self.down_button, self.label])
    self.vbox = widgets.VBox([self.output, hbox1, hbox2, hbox3, self.fig.canvas])
    display(self.vbox)  
    
Mbtree_GUI.display_widgets = display_widgets
```
</details>

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

```diff_python
def display_widgets(self):    
-   hbox1 = widgets.HBox([self.label, self.up_button])
+   hbox1 = widgets.HBox([self.label, self.up_button, self.label, self.label, self.score_button])
    hbox2 = widgets.HBox([self.left_button, self.label, self.right_button,
                          self.size_slider, self.help_button])
    hbox3 = widgets.HBox([self.label, self.down_button, self.label])
    self.vbox = widgets.VBox([self.output, hbox1, hbox2, hbox3, self.fig.canvas])
    display(self.vbox)  
    
Mbtree_GUI.display_widgets = display_widgets
```
</details>

上記の修正後に、下記のプログラムを実行すると、実行結果のように ↑ ボタンの右に評価値の表示の有無を切り替えるボタンが表示されるようになります。

```python
Mbtree_GUI(mbtree)
```

実行結果

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/8b754b1b-7513-61bd-9f1e-2c305e7a1d73.png" width="350">

## `create_event_handler` の修正

次に、下記のプログラムのように、このボタンをクリックした際に実行する __イベントハンドラを `create_event_handler` の中で定義__ します。

* __2 ~ 4 行目__：評価値の表示の有無を変更するボタンがクリックされた際のイベントハンドラを定義する。行う処理は、not 演算子で `show_score` 属性の値の `True` と `False` を反転し、`update_gui` メソッドを呼び出して表示を更新する処理である
* __19 行目__：ボタンとイベントハンドラを結び付ける

```python
 1  def create_event_handler(self):
元と同じなので省略              
 2      def on_score_button_clicked(b=None):
 3          self.show_score = not self.show_score
 4          self.update_gui()
 5                  
 6      def on_size_slider_changed(changed):
 7          self.size = changed["new"]
 8          self.fig.set_figwidth(self.width * self.size)
 9          self.fig.set_figheight(self.height * self.size)
10          self.update_gui()
11                
12      def on_help_button_clicked(b=None):
13          self.output.layout.display = "none" if self.output.layout.display is None else None
14                        
15      self.left_button.on_click(on_left_button_clicked)
16      self.right_button.on_click(on_right_button_clicked)
17      self.up_button.on_click(on_up_button_clicked)
18      self.down_button.on_click(on_down_button_clicked)
19      self.score_button.on_click(on_score_button_clicked)
元と同じなので省略
20    
21  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.selectednode.parent is not None:
            self.selectednode = self.selectednode.parent
            self.update_gui()
            
    def on_right_button_clicked(b=None):
        if len(self.selectednode.children) > 0:
            self.selectednode = self.selectednode.children[0]
            self.update_gui()

    def on_up_button_clicked(b=None):
        if self.selectednode.parent is not None:
            index = self.selectednode.parent.children.index(self.selectednode)
            if index > 0:
                self.selectednode = self.selectednode.parent.children[index - 1]
                self.update_gui()
            
    def on_down_button_clicked(b=None):
        if self.selectednode.parent is not None:
            index = self.selectednode.parent.children.index(self.selectednode)
            if self.selectednode.parent.children[-1] is not self.selectednode:
                self.selectednode = self.selectednode.parent.children[index + 1]
                self.update_gui()            
                
    def on_score_button_clicked(b=None):
        self.show_score = not self.show_score
        self.update_gui()
                
    def on_size_slider_changed(changed):
        self.size = changed["new"]
        self.fig.set_figwidth(self.width * self.size)
        self.fig.set_figheight(self.height * self.size)
        self.update_gui()
                
    def on_help_button_clicked(b=None):
        self.output.layout.display = "none" if self.output.layout.display is None else None
                        
    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)
    self.score_button.on_click(on_score_button_clicked)
    self.size_slider.observe(on_size_slider_changed, names="value")
    self.help_button.on_click(on_help_button_clicked)

    def on_key_press(event):
        keymap = {
            "left": on_left_button_clicked,
            "0": 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]()
        else:
            try:
                num = int(event.key) - 1
                x = num % 3
                y = 2 - (num // 3)
                move = (x, y)
                if move in self.selectednode.children_by_move:
                    self.selectednode = self.selectednode.children_by_move[move]
                    self.update_gui()
            except:
                pass            
            
    def on_mouse_down(event):
        for rect, node in self.mbtree.nodes_by_rect.items():
            if rect.is_inside(event.xdata, event.ydata):
                self.selectednode = node
                self.update_gui()
                break               
            
    # fig の画像イベントハンドラを結び付ける
    self.fig.canvas.mpl_connect("key_press_event", on_key_press)
    self.fig.canvas.mpl_connect("button_press_event", on_mouse_down)
    
Mbtree_GUI.create_event_handler = create_event_handler
```
</details>

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

```diff_python
def create_event_handler(self):
元と同じなので省略              
+   def on_score_button_clicked(b=None):
+       self.show_score = not self.show_score
+       self.update_gui()
                
    def on_size_slider_changed(changed):
        self.size = changed["new"]
        self.fig.set_figwidth(self.width * self.size)
        self.fig.set_figheight(self.height * self.size)
        self.update_gui()
                
    def on_help_button_clicked(b=None):
        self.output.layout.display = "none" if self.output.layout.display is None else None
                        
    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)
+   self.score_button.on_click(on_score_button_clicked)
元と同じなので省略
    
Mbtree_GUI.create_event_handler = create_event_handler
```
</details>

実行結果は省略しますが、下記のプログラムを実行し、「評価値の表示」ボタンをクリックすることで、評価値の表示の有無が切り替わることを確認して下さい。

```python
Mbtree_GUI(mbtree)
```

# 評価値の表示の有無によるボタンの表示の色の変更

現状では、「__評価値の表示__」ボタンをクリックしても __ボタンの色が変わらない点がわかりづらい__ という問題があります。そこで、__評価値を表示するかどうか__ で「評価値の表示」__ボタンの色を下記の表のように変更する__ ように改良します。下記のようにボタンの表示の色を変更することで、評価値の表示の有無のような、__ON/OFF の状態を色で区別できるボタン__ を作成することができるようになります。

なお、下記の表とは別の色を設定したい人は自由に変更して下さい。

| 状態 | ボタンの色 |
|:-:|:-:|
| __評価値が表示されている__  |  薄い緑（lightgreen） |
| __評価値が表示されていない__  | 濃い灰色（darkgray）  |

## GUI クラスの `set_button_color` メソッドの定義

ボタンの表示の色の変更を行うメソッドとして、[以前の記事](https://qiita.com/ysgeso/items/94fba56cb97dc4787ca8#%E3%83%9C%E3%82%BF%E3%83%B3%E3%81%AE%E7%8A%B6%E6%85%8B%E3%82%92%E5%A4%89%E6%9B%B4%E3%81%99%E3%82%8B%E9%96%A2%E6%95%B0%E3%81%AE%E5%AE%9A%E7%BE%A9)で GUI クラスの `set_button_status` メソッドで定義しましたが、このメソッドは __ボタンが操作できない状態（disabled）であるか__ どうかで __ボタンの色を変更__ する処理を行うため、__ボタンを操作できる状態で色を変更する__ という、上記の用途で __利用することはできません__。そこで、下記のプログラムのように、__ボタンの状態__ を表す仮引数 __`value`__ の値によってボタンの色を変更する __`set_button_color`__ メソッドを GUI クラスに定義する事にします。

* __3 行目__：`set_button_status` と同様に静的メソッドとして定義する
* __4、5 行目__：仮引数 `value` に代入された値によって、仮引数 `button` に代入されたボタンの表示の色を上記の表のように変更する

```python
1  from gui import GUI
2
3  @staticmethod
4  def set_button_color(button, value):      
5      button.style.button_color = "lightgreen" if value else "darkgray"
6        
7  GUI.set_button_color = set_button_color   
```

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

```python
from gui import GUI

@staticmethod
def set_button_color(button, value):      
    button.style.button_color = "lightgreen" if value else "darkgray"
        
GUI.set_button_color = set_button_color   

```
</details>

## Mbtree_GUI クラスの `update_gui` メソッドの修正

次に、Mbtree_GUI クラスの `update_gui` メソッドを下記のプログラムのように修正します。

__3 行目__：`set_button_color` メソッドを呼び出すことで、`show_score` 属性の値によって、「評価値の表示」ボタンの色を変更するようにする

```python
1  def update_gui(self):
元と同じなので省略
2      self.set_button_status(self.down_button, disabled=disabled)
3      self.set_button_color(self.score_button, value=self.show_score)
4
5  Mbtree_GUI.update_gui = update_gui
```

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

```python
def update_gui(self):
    self.ax.clear()
    self.ax.set_xlim(-1, self.width - 1)
    self.ax.set_ylim(-1, self.height - 1)   
    self.ax.invert_yaxis()
    self.ax.axis("off")   
    
    if self.selectednode.depth <= 4:
        maxdepth = self.selectednode.depth + 1
    elif self.selectednode.depth == 5:
        maxdepth = 7
    else:
        maxdepth = 9
    centernode = self.selectednode
    while centernode.depth > 6:
        centernode = centernode.parent
    self.mbtree.draw_subtree(centernode=centernode, selectednode=self.selectednode,
                             show_bestmove=True, show_score=self.show_score,
                             ax=self.ax, maxdepth=maxdepth, size=self.size)
    
    disabled = self.selectednode.parent is None
    self.set_button_status(self.left_button, disabled=disabled)
    disabled = self.selectednode.depth >= 6 or len(self.selectednode.children) == 0
    self.set_button_status(self.right_button, disabled=disabled)
    disabled = self.selectednode.parent is None or self.selectednode.parent.children.index(self.selectednode) == 0
    self.set_button_status(self.up_button, disabled=disabled)
    disabled = self.selectednode.parent is None or self.selectednode.parent.children[-1] is self.selectednode
    self.set_button_status(self.down_button, disabled=disabled)
    self.set_button_color(self.score_button, value=self.show_score)

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

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

```diff_python
def update_gui(self):
元と同じなので省略
    self.set_button_status(self.down_button, disabled=disabled)
+   self.set_button_color(self.score_button, value=self.show_score)

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

上記の修正後に下記のプログラムを実行し、「評価値の変更」ボタンをクリックすると、実行結果のようにボタンの色が灰色になり評価値が表示されなくなることを確認して下さい。

```python
Mbtree_GUI(mbtree)
```

実行結果

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/430887c5-2012-9657-7d8e-5c220f5c7831.png" width="300">

また、実行結果は省略しますが、下記のプログラムで `gui_play()` を実行した場合に GUI の部分木の FloatSlider や「評価値の表示」ボタンによる操作が行えることを確認して下さい。

```python
gui_play()
```

# ボタンの表示に関する補足

？ ボタンと「評価値の表示」ボタンに関するいくつかの補足説明を行います。

## ？ ボタンに関する補足

？ ボタンもヘルプの表示の有無を変更するボタンなので、「評価値の表示」ボタンと同様に、ON/OFF で色を変更するようにしてみたのですが、__ヘルプを表示していない場合に ？ ボタンを暗い灰色で表示__ すると __？ ボタンが目立たなくなるのがあまりよくない__ 気がしましたので ？ ボタンは __常に緑色で表示することにしました__。

？ ボタンも「評価値の表示」ボタンと同様に色を変更する場合は、`update_gui` の最後に下記のプログラムを追加して下さい。

* __1 行目__：ヘルプが表示されているかどうかを表す `output.layout.display` の値によって、？ ボタンの ON/OFF を表す値を計算する

```python
1  value = self.output.layout.display is None
2  self.set_button_color(self.help_button, value=value)
```

また、上記の変更だけでは、？ ボタンをクリックした際にボタンの色の表示が変更されないので、下記のプログラム 4 行目のように `create_event_handler` の中の `on_help_button_clicked` の最後に表示の更新を行う処理を追加して下さい。
```python
1  def create_event_handler(self):
元と同じなので省略
2      def on_help_button_clicked(b=None):
3          self.output.layout.display = "none" if self.output.layout.display is None else None
4          self.update_gui()
元と同じなので省略 
5    
6  Mbtree_GUI.create_event_handler = create_event_handler
```

## ToggleButton に関する補足

ipywidgets にある程度詳しい人は、__ON/OFF の区別__ をつけることができる __ToggleButton__ というウィジェットを使えばもっと簡単にプログラムを記述できるのではないかと思った人がいるかもしれません。筆者も最初は ToggleButton を使って「評価値の表示」ボタンを作成してみたのですが、__ToggleButton__ で表示される __ボタンの色__ は、__特定の配色でしか表示できず__、その __配色があまりしっくりこなかった__ ので、本記事では ToggleButton を利用しないことにしました。参考までに ToggleButton の簡単な使い方についてこの後で紹介しますので、興味がある方は「評価値の表示」ボタンを ToggleButton で作成してみて下さい。

また、他にも __CheckBox__ というウィジェットを利用するという方法もありますが、CheckBox は __表示の幅がボタンよりも広くなってしまう__ という問題があるので本記事では採用しませんでした。CheckBox を使ってみたい方は、下記のリンク先を見て下さい。

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

### ToggleButton の性質

ToggleButton は以下のような性質を持つボタンです。

* `True` と `False` のいずれかの値を持つ
* クリックすることで `True` と `False` が入れ替わる
* 設定されている値によって __自動的に表示の色が変わる__ ので、ボタンの色を変化させる処理を記述しなくても、どちらの値が設定されているかが見た目からわかる

下記は ToggleButton の主な属性です。

| 属性 | 意味 | デフォルト値 |
|:--|:--|--:|
| __`value`__  | 現在の値（`True` または `False`）  | `False` |
| __`description`__  | ToggleButton に表示される文字列  | `""` |
| __`disabled`__ | `True` の場合に操作できなくなる | `False` |

### ToggleButton の作成と表示

下記は ToggleButton を作成して表示するプログラムです。実行結果の左図は ToggleButton に `True` が設定されている場合、右図は `False` が設定されている場合で、ToggleButton の色からどちらが設定されているかが一目でわかるようになっています。表示されたボタンを実際にクリックして確かめてみてください。

```python
tb = widgets.ToggleButton(description="評価値の表示")
display(tb)
```

実行結果

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/e8ba71d1-734b-e672-07cb-52386c8cb5da.png" width="150">　<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/747c5b61-f3ff-bbab-c6fc-972f8c703da1.png" width="150">

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

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

https://ipywidgets.readthedocs.io/en/stable/reference/ipywidgets.html#ipywidgets.widgets.widget_bool.ToggleButton

### ToggleButton の色の変更

ToggleButton は、__初期設定__ では __濃い灰色が `True`__、__薄い灰色が `False`__ を表します。本記事でこれまで作成した GUI では、__操作できない状態のボタンを薄い灰色__ で、__操作できるボタンを薄い緑色で表示__ していたので、__初期設定の ToggleButton の配色__ では、__操作できない状態のボタン__ と __`False` が設定された ToggleButton__ の __区別がつけづらい__ という問題が発生します。また、__ToggleButton__ は通常のボタンと異なり `style.button_color` 属性によって __表示の色を変更することができません__。これが ToggleButton を採用しなかった理由です。

なお、下記のプログラムのように、ToggleButton の __`button_style`__ 属性を変更することでボタンの色を __特定のスタイルの色に変更__ することができます。

```python
tb = widgets.ToggleButton(description="評価値の表示", button_style="success")
display(tb)
```

実行結果

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/4cce03df-6269-fa40-20c0-613e2fdd5e92.png" width="150">

残念ながら `button_style` 属性に設定できるスタイルの種類は、下記のリンク先が示すように 5 種類しかないようです。いずれのスタイルの配色も 〇×ゲームの GUI の配色としては筆者はピンとこなかったので採用しないことにしました。興味がある方はそれぞれの配色で ToggleButton を実際に表示してみて下さい。

https://ipywidgets.readthedocs.io/en/stable/examples/Widget%20Styling.html#predefined-styles

# 今回の記事のまとめ

今回の記事では以下の説明を行いました。

* matplotlib での文字の表示の性質
* FloatSlider を用いた、部分木の表示の変更機能の実装
* 評価値の表示の有無を変更するボタンの実装

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

| リンク | 説明 |
|:--|:--|
| [marubatsu.ipynb](https://github.com/ysgeso/marubatsu/blob/master/119/marubatsu.ipynb)  | 本記事で入力して実行した JupyterLab のファイル |
| [tree_new.py](https://github.com/ysgeso/marubatsu/blob/master/119/tree_new.py)  | 今回の記事で更新した tree.py |
| [gui_new.py](https://github.com/ysgeso/marubatsu/blob/master/119/gui_new.py)  | 今回の記事で更新した gui.py |

# 次回の記事

https://qiita.com/ysgeso/items/900d36483538c2745d9a