Mermaid.jsで快適な作図をお届け

この記事は[NTTコムウェア Advent Calendar 2024](https://qiita.com/advent-calendar/2024/nttcomware) 7日目の記事です。

こんにちは、NTTコムウェアの濱崎です。

オープンソーステクノロジセンタ なる組織に所属しており、
普段はPostgreSQLに関するノウハウ展開、支援に従事しています。
が、今回はPostgreSQLが主題ではありません。

# はじめに
いきなりですが、下の図を見てください。

<img width="500" alt="frustrating-work.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3946056/e168f3be-fd4c-a16d-57ac-14c5a8d3f52d.png">

何か感じましたか？私はイライラしました。

理由はコレです。
<img width="500" alt="frustrating-work2.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3946056/4cb1ae09-1b4a-060e-93b5-e6f47e05721f.png">

A型の人なら共感してもらえると思います。
誰もが1度は経験したことのある図形配置の煩わしさ。

シンプルな図形や配置なら多少の調整でよいですが、
何か情報を図示するダイアグラムの場合、煩雑になり、微調整で時間が溶けることも...

そこで使えるのが**Mermaid** です。

## Mermaidとは

JavaScript ベースのダイアグラム作成およびチャート作成ツールです。

MITライセンスのOSSとして公開されています。

https://github.com/mermaid-js/mermaid

https://github.com/mermaid-js/mermaid/blob/develop/LICENSE

Mermaidで書いたダイアグラムを見てみましょう。
```mermaid
flowchart LR
A(空腹) -->|食事| B(腹八分)
B -->  C{まだ食べる？}
C -->|Yes| D[血糖値スパイク]
C -->|No| E[おいしかった^^]
```

<details><summary>Mermaidテキスト</summary>

```
flowchart LR
A(空腹) -->|食事| B(腹八分)
B -->  C{まだ食べる？}
C -->|Yes| D[血糖値スパイク]
C -->|No| E[おいしかった^^]
```
</details>

どうでしょうか？
位置を調整することなく、簡単に整理されたダイアグラムが作成できました。
一番の長所は、ここから変更点がある場合に微調整する必要がない点です。

以下は2行だけ加えて分岐を増やしたものです。

```mermaid
flowchart LR
A(空腹) -->|食事| B(腹八分)
A(空腹) -->|食事| B'(満腹)
B -->  C{まだ食べる？}
C -->|Yes| D[血糖値スパイク]
C -->|No| E[おいしかった^^]
B' --> D[血糖値スパイク]
```

<details><summary>Mermaidテキスト</summary>

```
flowchart LR
A(空腹) -->|食事| B(腹八分)
A(空腹) -->|食事| B'(満腹)
B -->  C{まだ食べる？}
C -->|Yes| D[血糖値スパイク]
C -->|No| E[おいしかった^^]
B' --> D[血糖値スパイク]
```
</details>

なお、Mermaid記法を適用する場合はコードブロックを「```mermaid」と書く必要があります。

:::note info
QiitaはMermaidに対応しています。
上の図も直接Mermaid記法で書いています。
:::

# データベースからER図を作成する

MermaidはER図も簡単に作図することができます。

ER図も作成してみましょう。ただし、普通に作成すると面白くないので既存のデータベースから逆輸入してみます。

※ IntelliJ IDEA や DBeaver で簡単にER図を作成できるのはナイショ

https://dbeaver.com/docs/wiki/ER-Diagrams/

https://pleiades.io/help/idea/creating-diagrams.html

ということで、PostgreSQLとMermaidを使って、作成済みのデータベースからER図を作成してみます。


## データの用意

PostgreSQL 標準のベンチマークツールである pgbench を使ってデータベースを用意します。

事前に「pgbenchdb」という名前の適当なデータベースを作成しておき、pgbench を実行します。
pgbench コマンドのオプションについては以下を参照ください。

https://www.postgresql.jp/document/16/html/pgbench.html#PGBENCH-INIT-OPTIONS

性能測定は行わず、データベースの作成のみを実行します。

```
$ pgbench -i -I dtGvpf pgbenchdb
```

pgbench で作成されるテーブルは以下の４つです。

<details><summary>pgbenchで作成されたテーブル</summary>

```
pgbenchdb=# \d+ pgbench_accounts
                                   テーブル"public.pgbench_accounts"
    列    |    タイプ     | 照合順序 | Null 値を許容 | デフォルト | ストレージ | 圧縮 | 統計目標 | 説
 
----------+---------------+----------+---------------+------------+------------+------+----------+----
--
 aid      | integer       |          | not null      |            | plain      |      |          | 
 bid      | integer       |          |               |            | plain      |      |          | 
 abalance | integer       |          |               |            | plain      |      |          | 
 filler   | character(84) |          |               |            | extended   |      |          | 
インデックス:
    "pgbench_accounts_pkey" PRIMARY KEY, btree (aid)
外部キー制約:
    "pgbench_accounts_bid_fkey" FOREIGN KEY (bid) REFERENCES pgbench_branches(bid)
参照元:
    TABLE "pgbench_history" CONSTRAINT "pgbench_history_aid_fkey" FOREIGN KEY (aid) REFERENCES pgbench
_accounts(aid)
アクセスメソッド: heap
オプション: fillfactor=100

    
pgbenchdb=# \d+ pgbench_branches
                                   テーブル"public.pgbench_branches"
    列    |    タイプ     | 照合順序 | Null 値を許容 | デフォルト | ストレージ | 圧縮 | 統計目標 | 説
 
----------+---------------+----------+---------------+------------+------------+------+----------+----
--
 bid      | integer       |          | not null      |            | plain      |      |          | 
 bbalance | integer       |          |               |            | plain      |      |          | 
 filler   | character(88) |          |               |            | extended   |      |          | 
インデックス:
    "pgbench_branches_pkey" PRIMARY KEY, btree (bid)
参照元:
    TABLE "pgbench_accounts" CONSTRAINT "pgbench_accounts_bid_fkey" FOREIGN KEY (bid) REFERENCES pgben
ch_branches(bid)
    TABLE "pgbench_history" CONSTRAINT "pgbench_history_bid_fkey" FOREIGN KEY (bid) REFERENCES pgbench
_branches(bid)
    TABLE "pgbench_tellers" CONSTRAINT "pgbench_tellers_bid_fkey" FOREIGN KEY (bid) REFERENCES pgbench
_branches(bid)
アクセスメソッド: heap
オプション: fillfactor=100

pgbenchdb=# \d+ pgbench_history 
                                          テーブル"public.pgbench_history"
   列   |           タイプ            | 照合順序 | Null 値を許容 | デフォルト | ストレージ | 圧縮 | 統
計目標 | 説明 
--------+-----------------------------+----------+---------------+------------+------------+------+---
-------+------
 tid    | integer                     |          |               |            | plain      |      |   
       | 
 bid    | integer                     |          |               |            | plain      |      |   
       | 
 aid    | integer                     |          |               |            | plain      |      |   
       | 
 delta  | integer                     |          |               |            | plain      |      |   
       | 
 mtime  | timestamp without time zone |          |               |            | plain      |      |   
       | 
 filler | character(22)               |          |               |            | extended   |      |   
       | 
外部キー制約:
    "pgbench_history_aid_fkey" FOREIGN KEY (aid) REFERENCES pgbench_accounts(aid)
    "pgbench_history_bid_fkey" FOREIGN KEY (bid) REFERENCES pgbench_branches(bid)
    "pgbench_history_tid_fkey" FOREIGN KEY (tid) REFERENCES pgbench_tellers(tid)
アクセスメソッド: heap

      
pgbenchdb=# \d+ pgbench_tellers
                                    テーブル"public.pgbench_tellers"
    列    |    タイプ     | 照合順序 | Null 値を許容 | デフォルト | ストレージ | 圧縮 | 統計目標 | 説
 
----------+---------------+----------+---------------+------------+------------+------+----------+----
--
 tid      | integer       |          | not null      |            | plain      |      |          | 
 bid      | integer       |          |               |            | plain      |      |          | 
 tbalance | integer       |          |               |            | plain      |      |          | 
 filler   | character(84) |          |               |            | extended   |      |          | 
インデックス:
    "pgbench_tellers_pkey" PRIMARY KEY, btree (tid)
外部キー制約:
    "pgbench_tellers_bid_fkey" FOREIGN KEY (bid) REFERENCES pgbench_branches(bid)
参照元:
    TABLE "pgbench_history" CONSTRAINT "pgbench_history_tid_fkey" FOREIGN KEY (tid) REFERENCES pgbench
_tellers(tid)
アクセスメソッド: heap
オプション: fillfactor=100
```
</details>




## データベースをSQLファイルに変換する

ER図へ変換する前に、先ほど作成したデータベースをSQLファイルに変換します。
変換のためにpg_dumpを利用します。

pg_dumpはデータベースをバックアップすることのできるコマンドです。他のユーザによるデータベースへのアクセス（読み書き）をブロックしないため、データベースが運用中でもダンプしてスクリプトファイルやアーカイブファイルとして抽出することができます。

https://www.postgresql.jp/document/16/html/app-pgdump.html

データ定義情報のみ取り出したいので、-s (--schema-only) オプションを付ける必要があります。

```
$ pg_dump -s pgbenchdb > pgbenchdb-schema-only.sql
```

今回、データベース単位でスクリプトを作成しましたが、各テーブルごとにスクリプトを作成することもできます。

ここまでで、PostgreSQLでの操作は以上です。

## SQLからMermaid記法へ変換する

次に、SQLファイルをMermaid記法に変換させる必要があります。

自力で書いてもいいですが、LLMを使って変換させちゃいましょう。

以下のように1文だけ添えて、SQLの中身をコピペしました。
「以下のSQLをmermaid記法に変換してください。~~~」

~~出力過程は以下を参照ください。~~

:::note warn
ChatGPTのチャット履歴を共有していたのですが、「404 not found」になってしまったので、リンクを削除しました。
:::

SQLの中身をバッククォート3つ「```」で囲んで、「以下のSQLをMermaid記法に変換してください」と適当に指示するだけである程度出来のいいスクリプトが生成されます。

私の感覚ですが、Mermaid記法への変換精度は高いです。
今回のようなSQLからの変換に限らず、「Mermaid記法」という単語を使用することである程度完成度の高いソースコードが作成されます。

今回、変換前(SQLファイル)と変換後(Mermaidテキスト)を比較してどの程度の精度で変換できたのかは重要ではないので、ほとんど修正せずにそのまま使用します。

## 作成したMermaidテキストを画像、pdfへ出力する

Mermaidで図示する方法はいくつもあります。
とりあえず試してみたい方は Mermaid Live Editor がオススメです。

https://mermaid.live/edit#pako:eNpVjsFqw0AMRH9F6NRC_AM-FBq7zSWQQnOqnYOwZe-S7GqR14Rg-9-7TlpodZJm3gyasJGWMcfuItfGkEY4lrWHNK9VYdQO0dFwgix7mXccwYnn2wzbp53AYCQE6_vnB79dISim_YoxRGP9eXlYxT1_8DxDWe0pRAmnv87xKjO8VfbDpPr_jlFOqfeqo7yjrCGFgvSO4AYdqyPbpvenVakxGnZcY57WlvRcY-2XxNEY5fPmG8yjjrxBlbE3mAovQ7rG0FLk0lKv5H6RQP5LxP1AyzeJDl_b

今回、VSCodeの拡張機能のみを利用して画像、pdfへ出力します。
使用する拡張機能は以下です。

https://marketplace.visualstudio.com/items?itemName=bierner.markdown-mermaid

https://marketplace.visualstudio.com/items?itemName=bpruitt-goddard.mermaid-markdown-syntax-highlighting

https://marketplace.visualstudio.com/items?itemName=yzane.markdown-pdf

先ほど作成したMermaidテキストを「.md」拡張子で作成します。

その後、コマンドパレット(ctrl + shift + p)で`Markdown:Open Preview to the Side`を選択して
プレビューを確認できます。

<img width="700" alt="vscode_preview.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3946056/84e4b476-ff3f-8c2c-1d00-7635267316bb.png">

結果がいい感じなら。Markdown側で右クリックを押下し、好きな形式に変換します。

<img width="300" alt="menu.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3946056/e59d0da7-e984-89f0-c680-773d02535d94.png">

ただし、このままだと図を書き出せず、Markdownのままになってしまいます。

解決策は以下の記事を参考にさせていただきました。

https://qiita.com/fu-a-sak/items/632efae2cb6e953548f0

VSCodeの設定から`Markdown-pdf: Mermaid Server`で以下のように設定してください。
(2024/12/03 時点でも、10.5.0以降のバージョンを指定するとうまく変換してくれませんでした。)

<img width="700" alt="setting.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3946056/25479701-399b-582f-68e9-bda4c1148b5e.png">

これで図を書き出せるようになりました。

最終的に作成したER図はこちら。

```mermaid
erDiagram
    PG_BENCH_ACCOUNTS {
        INTEGER aid PK 
        INTEGER bid FK
        INTEGER abalance
        CHAR(84) filler
    }
    PG_BENCH_BRANCHES {
        INTEGER bid PK 
        INTEGER bbalance
        CHAR(88) filler
    }
    PG_BENCH_HISTORY {
        INTEGER tid FK 
        INTEGER bid FK 
        INTEGER aid FK 
        INTEGER delta
        TIMESTAMP mtime
        CHAR(22) filler
    }
    PG_BENCH_TELLERS {
        INTEGER tid PK 
        INTEGER bid FK 
        INTEGER tbalance
        CHAR(84) filler
    }

    %% Relationships
    PG_BENCH_ACCOUNTS ||--|| PG_BENCH_BRANCHES : "bid"
    PG_BENCH_HISTORY ||--|| PG_BENCH_ACCOUNTS : "aid"
    PG_BENCH_HISTORY ||--|| PG_BENCH_BRANCHES : "bid"
    PG_BENCH_HISTORY ||--|| PG_BENCH_TELLERS : "tid"
    PG_BENCH_TELLERS ||--|| PG_BENCH_BRANCHES : "bid"
```

<details><summary>Mermaidテキスト</summary>

```
erDiagram
    PG_BENCH_ACCOUNTS {
        INTEGER aid PK 
        INTEGER bid FK
        INTEGER abalance
        CHAR(84) filler
    }
    PG_BENCH_BRANCHES {
        INTEGER bid PK 
        INTEGER bbalance
        CHAR(88) filler
    }
    PG_BENCH_HISTORY {
        INTEGER tid FK 
        INTEGER bid FK 
        INTEGER aid FK 
        INTEGER delta
        TIMESTAMP mtime
        CHAR(22) filler
    }
    PG_BENCH_TELLERS {
        INTEGER tid PK 
        INTEGER bid FK 
        INTEGER tbalance
        CHAR(84) filler
    }

    %% Relationships
    PG_BENCH_ACCOUNTS ||--|| PG_BENCH_BRANCHES : "bid"
    PG_BENCH_HISTORY ||--|| PG_BENCH_ACCOUNTS : "aid"
    PG_BENCH_HISTORY ||--|| PG_BENCH_BRANCHES : "bid"
    PG_BENCH_HISTORY ||--|| PG_BENCH_TELLERS : "tid"
    PG_BENCH_TELLERS ||--|| PG_BENCH_BRANCHES : "bid"

```
</details>

# おわりに

PostgreSQLとMermaid を使って、データベースからER図を出力する方法を紹介しました。

結論、皆さんに伝えたいのが **Mermaidはいいぞ** ということです。

ただし、CSSでデザインを変更できるのですが、ピクセル単位での位置調整はできないことがたまにキズ。
利用ケースとしては、仕事の成果物として利用するのは難しくても、確認段階での利用に適しているのかなと思います。

記載されている会社名、製品名、サービス名は、各社の商標または登録商標です。