🧜‍♀️

mermaid でマーメイド描いてみた

2024/05/13に公開

結果

はじめに

本来の使い方とはかけ離れていますが、mermaid の自由度を試す意味で限界に挑戦してみました。(なんか面白そうだったので思い付きでやってみました)

方針検討

絵を描くためには、

  • 描きたい位置に
  • 描きたい形のものを
  • 描きたい色で

描けることが必要です。
3番目はおおよそ問題無いのですが、2番目についてせいぜい四角とか丸と線ぐらいしか描けません。
また、 mermaid のシーケンス図やフロー図ではよしなに位置調整をしてくれる以上、1番目がとても自由にできるとは言えません。また、そこでまず思いついたのが ピクセルアート です!


画像は MS の copilot に描かせました。(人魚に船ってセットなんだろうか...)

これはあくまで理想形ですが、なんとなくイメージはできてきました。mermaid のフロー図のノードをたくさん並べればなんかいけそうな気がします。

とりあえず適当に半角スペースを使って 3x3 ドットを描いてみた図

着色可能な格子状のマスが用意できるかどうか

先ほどの例では、四角と四角の間が空いていてとてもドット絵ができそうな感じではありません。
というわけで3つの対策をとりました。

  1. 1ノードの数を増やして、相対的に隙間を小さくして目立たないようにする
  2. 1ノードの個々の大きさを大きくし、相対的に隙間を小さくして目立たないようにする
  3. ノード間の距離をなるべく狭める
  4. ノード間のリンクを消す

1. ノード数を増やす

まず、ノードの数については mermaid のデフォルトの制約として 500ノードしか使えません。
(適当に 100x100 ノード作ってみようとしたら途中で弾かれました)

mermaid.js を使って静的サイトを自作すれば https://mermaid.js.org/config/schema-docs/config.html#maxedges のデフォルト値を上書きしたりできそうですが、(クソめんどくさいので) 400ノード作ってみた時点で結構描画重かった(5秒ぐらいかかる)ので 500がブラウザの限界かもしれません。ひとまず今回は 20x20 で頑張ることにしました。

ちなみに、各ノード定義とリンクを記述するのが果てしなく面倒だったのでさすがにそこはスクリプトを書きました(Python)。

# main.py
datalist = []
sample = "    1-1[ ] --- 2-1[ ]"


for i in range(1, 20):
    for j in range(1, 21):
        if i != 1:
            datalist.append(f"    {i}-{j} --- {i+1}-{j}[ ]\n")
        else:
            datalist.append(f"    {i}-{j}[ ] --- {i+1}-{j}[ ]\n")

f = open('file.txt', 'w', encoding='UTF-8')
f.writelines(datalist)

f.close()

2. ノードの大きさを大きくする

ノードの大きさはノード内の文字列の長さに依存するので、特に大きさを指定するような機構はありませんでした。
とはいえ適当な文字列を描いて色を背景やノードに合わせるのは面倒だったので、適当な数・種類の空白で埋めました。後述のノード間の距離も含めていくつか試行錯誤した結果、以下のように各ノードを埋めています。普通にスペースを置いても2個以上は無視されてしまうので、文字コード指定で記述していきます。
参考: Markdownを使う際に知っておくと便利なhtml

# (全角)スペースx3, 少し小さい半角スペース, 改行, (全角)スペースx3, 改行, (全角)スペースx3
# つまり横はスペース3.8つぐらいで高さが3行
&emsp;&emsp;&emsp;&emsp;&ensp;<br>&emsp;<br>&emsp;

ほぼただの試行錯誤で正方形っぽいのを探しただけですが、このようにしたノードを開発者ツールで見るとちょうど正方形になっていました。

3. ノード間の距離を狭める

参考: ノード・グループ間の距離

nodeSpacing, rankSpacing を指定してノード間の距離を調整できます。一つ注意としては、0を指定してもデフォルト値になってしまうので今回は先ほどの図のように 1 を指定します。

ちなみに、ここで1を指定した場合でも、ノード内に半角スペース1つ分しか文字列がないと横方向が埋まらないようです。

4. ノード間のリンクを消す

ノード間の距離を 1 にするとノードを透明にした時にわずかにリンクが描画されてしまうことが分かったのでリンクを消すようにしました。コード中に default 指定で全てのリンクに一括で style 適用できます。

linkStyle default opacity:0;

ちなみに、そもそもリンクを繋がなければいいのでは?と思われるかもしれませんが、
リンクを繋がずノード定義すると全てのノードがただ一直線に並ぶだけなので方眼状に配置することができないです。

実際に作っていく

方針検討の段階でドット絵の基盤はできたので後は色を付けていきます。
※先ほどのスクリプトはノードにスペースを含むよう修正しました
現状は以下のようになっているので、後は各々のノードを塗っていきましょう。

この際、モデルのマーメイド画像としては copilot に描かせたものだと解像度が高すぎて 20x20 で描くのは到底無理なので かわいいドット絵フリー素材サイト 様の画像をお借りしました。

https://dot-illust.net/character_ningyo_02_blue_pink/

髪飾り無しverもありましたが筆者の好みで髪飾り有りを選択。

着色

ノードの色指定は単純に以下のようにすれば良いですが、問題はそれを全てのドットに応じて指定しなければいけないとうことです。正直ここは画像を見ながら塗っていくしかないので、気合でどうにかしました。
参考: Styling a node

classDef で色指定を定義してその class を定義するノードを列挙します。

# 黄色の例
# ノード名はカンマ繋ぎで複数指定できる(スペースNG)
    %% 黄色
    classDef yellow fill:#FFEE58,stroke-opacity:0;
    class 2-13,3-13,,4-11,4-12,4-13,4-14,4-15,,5-12,5-13,5-14,,6-11,6-12,6-14,6-15 yellow

ちなみに塗りつぶし色もそうですが、枠線も色指定することができます(今回は邪魔なので stroke-opacity:0 で消します)。

画像によると思いますが今回筆者が使用した画像が(色が付いている部分だけ数えると)縦19マス横16マスだったので、左右端2マスと下端1マスを空白で埋めて調整しています。

表示結果

残念ながら Zenn のコードブロックの文字数制限に引っかかってしまうので記事中に記載はできませんが、 mermaid の live Editor で描画したものが記事冒頭の画像になります。
ついでに Github の記載限界にはなんとか収まるようなので Wiki に描いてみました。

おわりに

着色がえげつないくらい時間かかるのでどうにか自動化したいです。有識者求ム。
また、馬鹿げた作業ではありましたが「技術検証の結果を見つつ折衷案を探してなるべく達成する要件を考える」,「try & error を繰り返し実装していく」一連の作業は普段の開発に活きるものがあるのではと思いました。

おまけ

完全に蛇足ですが、文字コード指定でスペースを書く部分に関しては同様の方法でフロー図内に絵文字を記載することもできるようです。
https://note.com/nerunoi/n/n80624d81cc91

付録

実際に20x20マス分の定義を作成したスクリプト

datalist = []


for i in range(1, 20):
    for j in range(1, 21):
        if i != 1:
            datalist.append(f"    {i}-{j} --- {i+1}-{j}[&emsp;&emsp;&emsp;&emsp;&ensp;<br>&emsp;<br>&emsp;]\n")
        else:
            datalist.append(f"    {i}-{j}[&emsp;&emsp;&emsp;&emsp;&ensp;<br>&emsp;<br>&emsp;] --- {i+1}-{j}[&emsp;&emsp;&emsp;&emsp;&ensp;<br>&emsp;<br>&emsp;]\n")


f = open('file.txt', 'w', encoding='UTF-8')
f.writelines(datalist)

f.close()

Discussion