mkdocsにbokehを埋め込む

静的サイトジェネレータの mkdocs に bokeh のグラフを埋め込む方法を調べた。 端的にいうと、bokeh の html ファイルの script タグなどを、Markdown ファイルに直書きすることで埋め込める。 他の静的サイトジェネレータの場合は html ファイルのまま include する手段があるようだけど、mkdocs の場合はなさそうだったので、タグを直書きする方法を用いた。

bokeh のグラフを script タグなどのパーツとして取り出す方法はいくつかあるようだけど、以下、2つの方法についてメモしておく。

方法1:bokeh.embed.components を使う方法

この方法だと、script タグと、canvas が埋め込まれる div タグとに分けて取り出すことができる。

以下のコードでは、script タグについてはスクリプトの部分だけを取り出すために、wrap_script という引数の値を False にしている。

from bokeh.plotting import figure
from bokeh.embed import components

# プロット
p = figure()
p.circle([1, 2, 3, 4, 5], [6, 7, 2, 4, 5], size=20, color='navy', alpha=0.5)

# パーツの生成
script, div = components(p, wrap_script=False)

# ファイルとして出力
with open('sample_1.js', mode='w') as f:
    f.write(script)

with open('sample_1.html', mode='w') as f:
    f.write(div)

div 要素が書き込まれる sample_1.html の中身は、以下のようになる。

<div class="bk-root" id="5896505b-7646-4192-b221-4d0ba6db2f5d" data-root-id="1001"></div>

一方、script 要素が書き込まれる sample_1.js の中には、グラフのデータと js のコードが入る。 この js ファイルを呼び出す script タグを、下記のように自分で書いておく。

<script src="sample_1.js"></script>

以上のタグと、bokeh のライブラリを CDN から読み込む script タグとを合わせて、下記のような Markdown ファイルを作成する。

## Bokeh test

<script src="https://cdn.bokeh.org/bokeh/release/bokeh-2.1.1.min.js" crossorigin="anonymous"></script>
<div class="bk-root" id="5896505b-7646-4192-b221-4d0ba6db2f5d" data-root-id="1001"></div>
<script src="sample_1.js"></script>

これを mkdocs でビルドすると、グラフがちゃんと埋め込まれる。もちろん、インタラクティブに動かすこともできる。

f:id:bitbot:20200628213102p:plain
index.md
bokeh のライブラリをロードする部分は、mkdocs.yml の extra_javascript の項に書くこともできる。

site_name: My Docs

extra_javascript:
  - https://cdn.bokeh.org/bokeh/release/bokeh-2.1.1.min.js

方法2:bokeh.embed.autoload_static を使う方法

次に、autoload_static を使う方法。この方法を使うと、div と script タグをひとつにまとめて取り出すことができる。 さらに、CDN から bokeh のライブラリをロードする処理を含めることもできる。

from bokeh.plotting import figure
from bokeh.resources import Resources
from bokeh.embed import autoload_static

# プロット
p = figure()
p.circle([1, 2, 3, 4, 5], [6, 7, 2, 4, 5], size=20, color='navy', alpha=0.5)

# パーツの生成
resources = Resources(components=['bokeh'])
js, tag = autoload_static(p, resources, 'sample_2.js')

# ファイルとして出力
with open('sample_2.js', mode='w', encoding='utf-8') as f:
    f.write(js)

with open('sample_2.html', mode='w') as f:
    f.write(tag)

components という引数の値を、空のリスト [] にすると、CDN からライブラリをロードする処理は行われない。 デフォルトの値は None であるが、その場合、ウィジット等を含む4種類のライブラリすべてがロードされてしまうので、ここでは ['bokeh'] という値のみを設定している。*1

sample_2.html の中身は、以下のようになっている。

<script src="sample_2.js" id="2cb55733-863c-467c-a61b-8da7e404f74d"></script>

これを Markdown ファイルに書き込んで、sample_2.js を適切な場所に配置すれば、ひとつ目の方法同様に bokeh のグラフが mkdocs に埋め込まれる。

以上おわり。

感想

mkdocs も bokeh も最近使い始めたので、本当はもっとスマートな方法があるのかもしれない。 というかそれ以前に、autoload_static とか他の関数とかも、イレギュラーな使い方をしているのかもしれない。

資料

*1:他の3つは、'bokeh-widgets', 'bokeh-tables', 'bokeh-gl' という値をとる