Org-babelでMermaid.js

文書中に図を挿入したい!

WordからMarkdownなりOrg-modeなりに移行すると、「今、図をここに書きたい」ということがある。

こういうとき、「やっぱりWordじゃないとな」とか思ってしまう。

ただ、方法を知り、慣れると、実は楽に図を書くことができる方法がある。

それが、Mermaid.jsGraphvizといった作図ツールだ。 これらは、マウスではなくテキストで指示して作図する。慣れればそれなりの図を簡単に書ける。

例えば、こんな書き方でちょっとした図を作ることができる。

flowchart LR
問題意識 -.-> 資料 -.-> 主張

/images/mermaidsample.png

こうしたちょっとした図というかチャートを書きたいときには、一瞬でしかもキーボードから手を離さずに書けるのでとても楽だ。

ちなみに僕はWordに図を挿入するときには、PowerPointで図を作成して貼り込んでいるのだが、それよりも簡単だ。

org-modeでの設定

設定方法は、公式サイトを参照して設定すると簡単だ。

なお、サイトには、mermaid.cliを使えと書いてあるが、おそらく間違いだ。mermaidのコマンドラインインターフェイスにはmermaid.cliとmermaid-cliがある。 前者はもう古いようだ。

後者を使う。

Windows用のmmdc.cmd、壊れてない?

何が問題なのかよくわからんけど、emacsから呼び出すとエラーが出てしまう。

仕方がないので、mmdc.cmdのindex.bundle.jsを呼び出す部分をフルパスでハードコードしてしまう。

いいのかわからないけど、これで取りあえず動く。

@IF EXIST "%~dp0\node.exe" (
  "%~dp0\node.exe"  "c:\Users\<username>\node_modules\@mermaid-js\mermaid-cli\index.bundle.js" %*
) ELSE (
  @SETLOCAL
  @SET PATHEXT=%PATHEXT:;.JS;=;%
  node  "c:\Users\<username>\node_modules\@mermaid-js\mermaid-cli\index.bundle.js" %*
)

ターミナルから直接mmdc.cmdを呼び出すと問題なく動くので、emacsの設定になにか変なものが残っていてそれが悪影響を与えている気がする。

使用方法

以下のように書いて、begin-endの間でC-cC-cすると、画像が生成される。 画像は指定した場所に保存される。

resultsの下には画像へのリンクが表示される。

#+begin_src mermaid :file ../../static/images/mermaidsample.png :exports none
flowchart LR
問題意識 -.-> 資料 -.-> 主張
#+end_src

#+results:
<画像ファイル>

活用メモ

最初は、「おー!画像が生成されたぞ!」と喜ぶのだが、実際に使い始めるとだんだん不満というか細かく調整したくなってくる。

そのたびにググるのもどうかと思うので、以下にメモを追加していく。

タイトルを付けたい

画像に図番号とタイトル(キャプション)をつける。

こうしたくなるのだが、これは間違い。

#+caption: abc
#+begin_src mermaid :file ../../static/images/mermaidsample.png :exports none
flowchart LR
問題意識 -.-> 資料 -.-> 主張
#+end_src

#+results:
<画像ファイル>

画像へのリンクはresultsに生成されるのでそのすぐ上で指定する。

#+begin_src mermaid :file ../../static/images/mermaidsample.png :exports none
flowchart LR
問題意識 -.-> 資料 -.-> 主張
#+end_src

#+caption: abc
#+results:
<画像ファイル>

PDFを作成する際に、サイズを指定したい

例えば、図を画面の半分程度にときはcaptionとresultsの間で指定する。

#+begin_src mermaid :file ../../static/images/mermaidsample.png :exports none
flowchart LR
問題意識 -.-> 資料 -.-> 主張
#+end_src

#+caption: abc
#+attr_latex: :width 0.5\linewidth
#+results:
<画像ファイル>

図の位置を指定したい

図を右寄せにしたり、画像のまわりにテキストを回り込ませることができる。 オプションの細かい指定方法は、org-babelのドキュメントどおり。

org-modeのコードブロック(Babel)の使い方(Misohena Blog)には日本語での説明(ドキュメントの和訳?)がある。

では、オプションをどこに指定するのか?というのがわからなかった。 オプションはcaptionとresultsの間に置く。

#+begin_src mermaid :file ../../static/images/mermaidsample.png :exports none
flowchart LR
問題意識 -.-> 資料 -.-> 主張
#+end_src

#+caption: abc
#+attr_latex: :width 0.38\textwidth :float wrap :placement {r}{0.4\textwidth}
#+results:
<画像ファイル>

hugoで作成した画像を表示したい

静的ブログ作成ツールのHugoを使っている。 せっかくなので、mermaidで作成した画像をHugoでも使いたい。 しかし、残念ながら、いい方法が見つからない。

普通にやると、以下のように出力する画像は指定した場所に保存されて、resultsではこの画像へのパスを自動で指定してくれる。

#+begin_src mermaid :file ../../static/images/mermaidsample.png :exports none
flowchart LR
問題意識 -.-> 資料 -.-> 主張
#+end_src

#+caption: abc
#+attr_latex: :width 0.38\textwidth :float wrap :placement {r}{0.4\textwidth}
#+results:
<画像ファイル>

しかし、このパスはhugoで生成される個別postで指定すべき場所ではない。 エラーが出て表示されない。

Hugoで表示させるためには、別の場所(/images/以下)を指定しなければならない。

/images/file.png

まず、org-babelでの出力を:exports noneとして、ブログ原稿には画像もソースも表示しないように指定する。

C-cC-cすると画像は生成され、そのパスがresultsに記入される。

resultsの次の行に[二つでくくられたパスがある。これがorg-modeで画像をインラインで表示させる書式だ。

#+resutls:
[[../../static/images/file.png]]

出力されたものを手動で下にコピーしてパスを書き換えておく。

[[/images/file.png]]

とりあえず、めんどうだけどこれで表示は可能だ。

だが、画像を生成するたびに手動でパスをコピーして書き換えなければならない。こういう定形作業こそPCの仕事のはずだ!

別解:Pythonの置換機能を使う

org-babelはあるブロックの処理結果を別のブロックに渡して処理できる。 そこで、Mermaidでの画像生成→ファイル名→Pythonブロックで整形→文書に埋め込みという一連の処理を実装してみる。

/images/babelWorkflow.png

以下ではPythonの置換機能を使った。

もちろん、emacs-lispでもできるが、僕がぱぱっと使えるのはPythonなので、Pythonでの実装を紹介する。

#+name: fnametest
#+begin_src mermaid :file ../../static/images/mmds.png :post fout(fname=*this*)  :results raw :exports results
flowchart LR
問題意識 -.-> 資料 -.-> 主張
#+end_src

#+RESULTS:
[[file:/images/mmds.png]]


#+name: fout
#+BEGIN_SRC python :var fname="abc"  :results raw :exports code
  return f"{fname.replace('../../static', '')}"
#+END_SRC

#+RESULTS:
abc

org-babelの機能を使ってみた。

本来ならば、../../static/images/mmds.pngとなるはずのファイル名が、#+results: fnametestのところでは、以下のように置き換わっている。

#+RESULTS:
[[file:/images/mmds.png]]

ちょっと面倒かもしれないけれど、関数(fout)を一度定義しておけば、画像を出力したい場所でいつでも使える。

要素にclassやidを付け加える(邪魔な要素を消す)

だんだんやりたいことができるようになってきた。

CSSで作成したClassを指定できるようになるとデザインがかなり自由になる。

この手のツールでデザインを自由にしたいばあい、custom.cssなどの名前でカスタマイズ用のcssファイルを作るのが常套手段だ。 hugoでもオリジナルのCSSを読み込む方法が使えるので、以下のサイトを参考に「hidecode」というclassを作った。

customCSS という設定項目を読み込む (出所:「Hugo でカスタム CSS を適用して画像の配置をイジる · Yutaka Kato」、(https://mikan.github.io/2017/11/03/centering-figures-in-hugo/

hugoのorg-mode対応はgo用のOrg modeパーサを使っているようで、exportsの設定に関わらずコードとか計算結果が表示されてしまうようだ。 そこで、表示したくないコードなどを非表示にする方法が必要になる。

ここでは、hidecode クラスを作成して、custom.cssにも追加した。custom.cssを読み込む設定は上記のリンクを参考にした。

.hidecode{
 display:none;
}

本文中では、まずC-cC-cで結果を生成してから、srcとresultsの両方の上にattr_htmlの行を記入する。 これで表示してほしくないコードやリンク切れの画像などを隠すことができる。

ただしこれでは残念ながら以下のリンク切れの画像は非表示にできない。 もう、resultsを手動で消すほうがよい。

#+attr_html: :class hidecode
#+begin_src mermaid :file ../../static/images/mmds2.png  :results raw :exports code
flowchart LR
問題意識 -.-> 資料 -.-> 主張
#+end_src

#+caption: 表示したくない画像
#+attr_html: :class hidecode
#+RESULTS:
[[file:../../static/images/mmds2.png]]

なお、いちいちattrなんとかって書くのめんどくせーって思う人は、yasnippetを使うといい。 僕はahhC-cC-yと入力すると、この一行が入力されるようにした。

Hugo で構築されています。
テーマ StackJimmy によって設計されています。