今さら森博嗣「笑わない数学者」のビリヤードの問題を解く(12)～作図のヒミツ

# 0. はじめに

今回の記事の執筆にあたり，ビリヤードの玉の配置図を大量に描くことになったのだが，わざわざビットマップイメージを作成して貼り付けるのも面倒である。ということで Qiita のサポートする二大作図言語の Mermaid と PlantUML で作図に挑戦した。

https://qiita.com/TETSURO1999/items/fa1db203d48858c607f1

# 1. Mermaid の場合

頑張って描くと以下のようになるが，ＣとＤの線を繋げられない。これではネックレスにならない！

```mermaid
---
title: 図１－１　頑張って描いた図
---
graph TD
A((Ａ))
B((Ｂ))
C((Ｃ))
D((Ｄ))
E((Ｅ))
A --- B --- C
A --- E --- D
style A fill:#FF0000,stroke:#000000,scale:1.5
style B fill:#00FF00,stroke:#000000,scale:1.5
style C fill:#FFFF00,stroke:#000000,scale:1.5
style D fill:#00FFFF,stroke:#000000,scale:1.5
style E fill:#FF00FF,stroke:#000000,scale:1.5
```

Mermaid のスクリプトは以下のようになる。

````
```mermaid
---
title: 図１－１　頑張って描いた図
---
graph TD
A((Ａ))
B((Ｂ))
C((Ｃ))
D((Ｄ))
E((Ｅ))
A --- B --- C
A --- E --- D
style A fill:#FF0000,stroke:#000000,scale:1.5
style B fill:#00FF00,stroke:#000000,scale:1.5
style C fill:#FFFF00,stroke:#000000,scale:1.5
style D fill:#00FFFF,stroke:#000000,scale:1.5
style E fill:#FF00FF,stroke:#000000,scale:1.5
```
````

ＣとＤを繋げると以下のようにＣとＤの縦の位置を揃えられず，どちらか一方が下になる。もともとフローチャートを描くものなので，これは仕方ないところではある。

```mermaid
---
title: 図１－２　ＣとＤを繋げた場合
---
graph TD
A((Ａ))
B((Ｂ))
C((Ｃ))
D((Ｄ))
E((Ｅ))
A --- B --- C
A --- E --- D
D --- C
style A fill:#FF0000,stroke:#000000,scale:1.5
style B fill:#00FF00,stroke:#000000,scale:1.5
style C fill:#FFFF00,stroke:#000000,scale:1.5
style D fill:#00FFFF,stroke:#000000,scale:1.5
style E fill:#FF00FF,stroke:#000000,scale:1.5
```

ボールが６個になると，それなりにいい感じにはなる。

```mermaid
---
title: 図１－３ ボール６個の場合
---
graph TD
A((Ａ))
B((Ｂ))
C((Ｃ))
D((Ｄ))
E((Ｅ))
F((Ｆ))
A --- B --- C
A --- E --- D
C --- F
D --- F
style A fill:#FF0000,stroke:#000000,scale:1.5
style B fill:#00FF00,stroke:#000000,scale:1.5
style C fill:#FFFF00,stroke:#000000,scale:1.5
style D fill:#00FFFF,stroke:#000000,scale:1.5
style E fill:#FF00FF,stroke:#000000,scale:1.5
style F fill:#0000FF,stroke:#000000,scale:1.5
```

# 2. PlantUML の場合
一方，PlantUML のほうは図形をループ状に繋げられる。ただし PlantUML で図形を所望の場所に配置するのは難しく，中央にダミー図形を配置して三角形を結ぶようにするとうまくいく。こうして正五角形っぽいＡタイプと，省スペース型のＢタイプの二種類を作ってみた。

```plantuml
@startuml
<style>
root {
  margin 0
  padding 0
}
storage {
  fontSize 16
}
footer {
  fontColor black
  fontSize 16
}
</style>
storage #FF0000 " A " as A
storage #00FF00 " B " as B
storage #FFFF00 " C " as C
storage #00FFFF " D " as D
storage #FF00FF " E " as E
circle #white;line:black " " as dummy1
circle #white;line:black " " as dummy2
A -- B
A -- E
B -- C
E -- D
C -right- D : "　　　　"
B -[dotted]right- dummy1
dummy2 -[dotted]right- E 
dummy1 -[dotted]- C
dummy2 -[dotted]- D
footer 図２－１　Ａタイプ
@enduml
```

```plantuml
@startuml
<style>
root {
  margin 0
  padding 0
}
storage {
  fontSize 16
}
footer {
  fontColor black
  fontSize 16
}
</style>
storage #FF0000 " A " as A
storage #00FF00 " B " as B
storage #FFFF00 " C " as C
storage #00FFFF " D " as D
storage #FF00FF " E " as E
circle #white;line:black " " as dummy
A -- B
A -- E
B -- C
E -- D
C -right- D : " "
B -[dotted]right- dummy
dummy -[dotted]right- E 
dummy -[dotted]- C
dummy -[dotted]- D
footer 図２－２　Ｂタイプ
@enduml
```

実際の図ではダミーの境界線を black から white とし，また配線の [dotted] を [hidden] として見えないようにする。本当は真円（circle）を描きたかったのだが，PlantUML の circle は図形の外に文字が描かれるため，やむを得ず楕円（storage）で代用した。少しでも真円っぽく見せるために文字の左右に空白を入れて横幅を微調整している。また C と D の間の配線にラベルをつけて空白で間隔を微調整している。

````markdown
```plantuml:Ａタイプ
@startuml
storage #FF0000 " A " as A
storage #00FF00 " B " as B
storage #FFFF00 " C " as C
storage #00FFFF " D " as D
storage #FF00FF " E " as E
circle #white;line:black " " as dummy1
circle #white;line:black " " as dummy2
A -- B
A -- E
B -- C
E -- D
C -right- D : "　　　　"
B -[dotted]right- dummy1
dummy2 -[dotted]right- E 
dummy1 -[dotted]- C
dummy2 -[dotted]- D
footer 図２－１　Ａタイプ
@enduml
```
````

````markdown
```plantuml:Ｂタイプ
@startuml
storage #FF0000 " A " as A
storage #00FF00 " B " as B
storage #FFFF00 " C " as C
storage #00FFFF " D " as D
storage #FF00FF " E " as E
circle #white;line:black " " as dummy
A -- B
A -- E
B -- C
E -- D
C -right- D : " "
B -[dotted]right- dummy
dummy -[dotted]right- E 
dummy -[dotted]- C
dummy -[dotted]- D
footer 図２－２　Ｂタイプ
@enduml
```
````

さらに以下のように重複している箇所を cloud で囲んで分かり易く図示したかったのだが，囲んでしまうとレイアウトが崩れてしまうので断念した。

```plantuml
@startuml
<style>
root {
  margin 0
  padding 0
}
storage {
  fontSize 16
}
footer {
  fontColor black
  fontSize 16
}
</style>
storage #FF0000 " 1 " as A
storage #00FF00 " 2 " as B
cloud {
storage #FFFF00 " 4 " as C
storage #00FFFF " 5 " as D
}
storage #FF00FF " 9 " as E
circle #white;line:white " " as dummy
A -- B
A -- E
B -- C
E -- D
C -right- D : " "
B -[hidden]right- dummy
dummy -[hidden]right- E 
dummy -[hidden]- C
dummy -[hidden]- D
footer 図２－３　成功例
@enduml
```

```plantuml
@startuml
<style>
root {
  margin 0
  padding 0
}
storage {
  fontSize 16
}
footer {
  fontColor black
  fontSize 16
}
</style>
storage #FF0000 " 1 " as A
storage #00FF00 " 2 " as B
storage #FF00FF " 9 " as C
cloud {
storage #00FFFF " 5 " as D
storage #FFFF00 " 4 " as E
}
circle #white;line:white " " as dummy
A -- B
A -- E
B -- C
E -- D
C -right- D : " "
B -[hidden]right- dummy
dummy -[hidden]right- E 
dummy -[hidden]- C
dummy -[hidden]- D
footer 図２－４　失敗例
@enduml
```

# 3. PlantUML の裏技！インライン SVG スプライトを使う

いや，もう直接 SVG 使って描きたいと思って PlantUML のマニュアルを調べていたところ，ようやく見つけた！PlantUML にはインライン SVG スプライトという機能がある。これはスプライト（絵文字，アイコンのようなもの）を SVG を使って定義し，後で再利用しようというものだ。ただ，サポートしている SVG の機能は極めて限定的である。

こうして超頑張って描いたものが以下の図になる。

```plantuml
@startuml
<style>
root {
  margin 0
  paddig 0
}
caption {
  fontSize 16
}
</style>
sprite billiard <svg width="250" height="250" viewbox="0 0 250 250">
<circle cx="125" cy="125" r="100" stroke="#000000" fill="none"/>
<circle cx="125"   cy="25"    r="20" fill="#FF0000"/>
<circle cx="125"   cy="25"    r="20" stroke="#000000" fill="none"/>
<circle cx="29.9"  cy="94.1"  r="20" fill="#00FF00"/>
<circle cx="29.9"  cy="94.1"  r="20" stroke="#000000" fill="none"/>
<circle cx="66.2"  cy="205.9" r="20" fill="#FFFF00"/>
<circle cx="66.2"  cy="205.9" r="20" stroke="#000000" fill="none"/>
<circle cx="183.8" cy="205.9" r="20" fill="#00FFFF"/>
<circle cx="183.8" cy="205.9" r="20" stroke="#000000" fill="none"/>
<circle cx="220.1" cy="94.1"  r="20" fill="#FF00FF"/>
<circle cx="220.1" cy="94.1"  r="20" stroke="#000000" fill="none"/>
<text x="120"   y="31"    fill="black" font-size="16">A</text>
<text x="24.9"  y="100.1" fill="black" font-size="16">B</text>
<text x="61.2"  y="211.9" fill="black" font-size="16">C</text>
<text x="178.8" y="211.9" fill="black" font-size="16">D</text>
<text x="215.1" y="100.1" fill="black" font-size="16">E</text>
</svg>
title <$billiard>
caption 図３－１　超頑張って描いた図
@enduml
```

円の塗り潰しと境界線描画を同時には行えないので，五つのボールは先に塗り潰しを行い，後から境界線のみ描くという二度手間をかけている。また直線を描くということが難しいので，五つのボールを円で結んでいる。まあ，もともと「ビリヤードの玉を真珠のネックレスのようにリングに繋げる」のだからコチラが正しいのだが。


````markdown
```plantuml
@startuml
sprite billiard <svg width="250" height="250" viewbox="0 0 250 250">
<circle cx="125" cy="125" r="100" stroke="#000000" fill="none"/>
<circle cx="125"   cy="25"    r="20" fill="#FF0000"/>
<circle cx="125"   cy="25"    r="20" stroke="#000000" fill="none"/>
<circle cx="29.9"  cy="94.1"  r="20" fill="#00FF00"/>
<circle cx="29.9"  cy="94.1"  r="20" stroke="#000000" fill="none"/>
<circle cx="66.2"  cy="205.9" r="20" fill="#FFFF00"/>
<circle cx="66.2"  cy="205.9" r="20" stroke="#000000" fill="none"/>
<circle cx="183.8" cy="205.9" r="20" fill="#00FFFF"/>
<circle cx="183.8" cy="205.9" r="20" stroke="#000000" fill="none"/>
<circle cx="220.1" cy="94.1"  r="20" fill="#FF00FF"/>
<circle cx="220.1" cy="94.1"  r="20" stroke="#000000" fill="none"/>
<text x="120"   y="31"    fill="black" font-size="16">A</text>
<text x="24.9"  y="100.1" fill="black" font-size="16">B</text>
<text x="61.2"  y="211.9" fill="black" font-size="16">C</text>
<text x="178.8" y="211.9" fill="black" font-size="16">D</text>
<text x="215.1" y="100.1" fill="black" font-size="16">E</text>
</svg>
title <$billiard>
caption 図３－１　超頑張って描いた図
@enduml
```
````

重複するボールの配置も以下のように矩形塗り潰しで示すことができる。

```plantuml
@startuml
<style>
root {
  margin 0
  paddig 0
}
caption {
  fontSize 16
}
</style>
sprite billiard <svg width="250" height="250" viewBox="0 0 250 250">
<path d="M101 1 h48 v165.6 h-48" fill="#FFFFC0" transform="rotate(0,125,25)"/>
<path d="M40.2 179.9 L209.8 179.9 L209.8 231.9 L40.2 231.9" fill="#FFFFC0" />
<circle cx="125" cy="125" r="100" stroke="#000000" fill="none"/>
<circle cx="125"   cy="25"    r="20" fill="#FF0000"/>
<circle cx="125"   cy="25"    r="20" stroke="#000000" fill="none"/>
<circle cx="29.9"  cy="94.1"  r="20" fill="#00FF00"/>
<circle cx="29.9"  cy="94.1"  r="20" stroke="#000000" fill="none"/>
<circle cx="66.2"  cy="205.9" r="20" fill="#FFFF00"/>
<circle cx="66.2"  cy="205.9" r="20" stroke="#000000" fill="none"/>
<circle cx="183.8" cy="205.9" r="20" fill="#00FFFF"/>
<circle cx="183.8" cy="205.9" r="20" stroke="#000000" fill="none"/>
<circle cx="220.1" cy="94.1"  r="20" fill="#FF00FF"/>
<circle cx="220.1" cy="94.1"  r="20" stroke="#000000" fill="none"/>
<text x="120"   y="31"    fill="black" font-size="16">A</text>
<text x="24.9"  y="100.1" fill="black" font-size="16">B</text>
<text x="61.2"  y="211.9" fill="black" font-size="16">C</text>
<text x="178.8" y="211.9" fill="black" font-size="16">D</text>
<text x="215.1" y="100.1" fill="black" font-size="16">E</text>
</svg>
title <$billiard>
caption 図３ー２　重複配置（回転前）
@enduml
```

```plantuml
@startuml
<style>
root {
  margin 0
  paddig 0
}
caption {
  fontSize 16
}
</style>
sprite billiard <svg width="250" height="250" viewBox="0 0 250 250">
<path d="M101 1 h48 v165.6 h-48" fill="#FFFFC0" transform="rotate(54,125,25)"/>
<path d="M40.2 179.9 L209.8 179.9 L209.8 231.9 L40.2 231.9" fill="#FFFFC0" />
<circle cx="125" cy="125" r="100" stroke="#000000" fill="none"/>
<circle cx="125"   cy="25"    r="20" fill="#FF0000"/>
<circle cx="125"   cy="25"    r="20" stroke="#000000" fill="none"/>
<circle cx="29.9"  cy="94.1"  r="20" fill="#00FF00"/>
<circle cx="29.9"  cy="94.1"  r="20" stroke="#000000" fill="none"/>
<circle cx="66.2"  cy="205.9" r="20" fill="#FFFF00"/>
<circle cx="66.2"  cy="205.9" r="20" stroke="#000000" fill="none"/>
<circle cx="183.8" cy="205.9" r="20" fill="#00FFFF"/>
<circle cx="183.8" cy="205.9" r="20" stroke="#000000" fill="none"/>
<circle cx="220.1" cy="94.1"  r="20" fill="#FF00FF"/>
<circle cx="220.1" cy="94.1"  r="20" stroke="#000000" fill="none"/>
<text x="120"   y="31"    fill="black" font-size="16">A</text>
<text x="24.9"  y="100.1" fill="black" font-size="16">B</text>
<text x="61.2"  y="211.9" fill="black" font-size="16">C</text>
<text x="178.8" y="211.9" fill="black" font-size="16">D</text>
<text x="215.1" y="100.1" fill="black" font-size="16">E</text>
</svg>
title <$billiard>
caption 図３ー３　重複配置（回転後）
@enduml
```
傾いた矩形はまず &lt;path&gt; を用いて水平・垂直な矩形を描き，transform オプションを使って回転させると座標計算が楽になる。回転の基準座標を指定できるので，１の玉の位置 (125, 25) を中心にして 54 度回転させた。

```markdown:回転前
<path d="M101 1 h48 v165.6 h-48" fill="#FFFFC0" transform="rotate(0,125,25)"/>
```

```markdown:回転後
<path d="M101 1 h48 v165.6 h-48" fill="#FFFFC0" transform="rotate(54,125,25)"/>
```

# 4. 結論

Mermaid の場合，図のタイトルが上端固定なのも融通が利かないところで残念だ。ただし PlantUML の場合，図形の外枠線が描かれてしまうし，図形内のテキストもコピーできないのに対し，Mermaid の図のほうは外枠線がなくて本文と一体化しており，テキストのコピーも可能であるなど利点も多い。Mermaid 陣営にはもうちっと頑張って欲しいところだ。

PlantUML のインライン SVG スプライトは座標計算がひたすら面倒なのでお勧めしない。テキストの起点が文字（グリフ）の左下端になるので，本図のように中央揃えで表示しようとすると文字列の横幅や高さを計測して微調整しなくてはならない。ただし，非常に強力な作図手段ではある。

PlantUML のインライン SVG スプライト機能については別記事でまとめることにしよう。

https://qiita.com/TETSURO1999/items/870962714724dde98651