Jupyter上にGraphvizの図をSVGで描画する
Jupyter上にGraphvizの図をSVGで描画する
Jupyter上にGraphviz(.dot)の図をSVGで描画することに成功したのでメモとして残す。(※拡張機能としてはまだ作れていない)
Viz.jsのおかげでJavaのインストールすら不要になった..!
dotファイルのコードを書いてあげればこうなる👇
gist上では表示されないがnbviewerを通せば図も表示される。
https://nbviewer.org/gist/Cartman0/5a7cfadc2f367869f2df506577ace525
また、IPythonのカーネル周りの挙動もドキュメントがなく分かりづらいのメモとして残す。
試した環境
DockerDesktopはインストール済みを想定
- Windows10 64bit
- DockerDesktop 4.4
- scipy-notebook Image
- python 3.8.4
- scipy-notebook Image
- DockerDesktop 4.4
how to use
gist:
- 最初のセルのコードをノートブックの一番上などで事前に実行しておく(CDNのjsファイルを読み込むので最初はオンライン環境でないといけない)
drawDot
関数にdotファイル中身の文字列を渡して実行
動作の仕組み
Viz.jsやIPython.notebook.kernel
の仕様がわかりにくいのでメモ。
IPython.notebook.kernel
に至っては詳しいドキュメントがなくjupyterのショートカットのコードあたりを読まないと挙動がわからん感じになってる。
今回はほぼJavaScriptで動作しており出力セルへの表示だけpythonが行っている。
- RequireJSでViz.jsとそれ用のrender.jsを読み込む(Jupyterは外部スクリプトを
script
タグだけで読み込めないらしいので) - Vizインスタンスの作成(Graphvizのdotコードを画像に変換してくれる)
Viz.renderSVGElement()
関数で.dot
コードをsvgに変換- このsvgはjsのSVGElementオブジェクトなのでこれをpythonに渡せるようにシリアライズして文字列(改行あり)に変換
IPython.notebook.kernel.excute
にpyrhon側のSVG
関数を渡して出力セルに表示させる。cell.output_area.append_output({})
関数を使って出力セルに表示させるとpythonコードを実行したときのようにノートブックファイル(.ipynb)にsvgコードが保存される。
Viz.jsとは
GraphvizをJavascript(EcmaScript)で書いたもの。NodeJS用かと思ったらCDNがありブラウザ上でも動作する模様。
ブラウザ上で動かしている例:<初心者>Vue.jsとviz.jsでgraphvizのオンラインエディタを作ってみた。<Vue.js練習> - Qiita
- オンラインエディタで動くこれが凄い: Viz.js
- Viz.jsそのものの使い方は公式wikiにある:Usage - mdaines/viz.js Wiki
- github: GitHub - mdaines/viz.js: A hack to put Graphviz on the web.
恐らく2系より仕様が変わりrenderと分かれるようになり、dotコードを変換するにはVizインスタンスを作成してそこから関数を呼ぶ必要がある。
iPythonとJavaScriptの連携
この辺、情報が少ないのでメモ
iPython上でJSを動かす方法
まず iPython上でもJSは動く。 主に2種類ある。
-
iPython.display.HTML
かマジックコマンド%%HTML
を使って<script>
タグ内にJSコードを書く%%HTML <script> ..// your js code </script>
-
iPython.display.Javascript
かマジックコマンド%%javascript
を使うfrom iPython.display import Javascript Javascript(''' ..// your js code ''')
この2つに大きな違いはないが、this
の参照先が変わるので注意。
iPython上のJSからpythonの実行
iPython上のJSのメリットとしてJSからpythonを実行できる。 これにより、
などが可能になる。
例:numpyの計算結果をJSのalert
で表示している:
JS側で使えるAPIとしてiPython.notebook.kernel
がある。
このiPython.notebook.kernel.execute()
関数にpythonコードの文字列を渡すとバックグラウンド上で実行される。
サンプルコード例1:
from IPython.display import Javascript jscode = ''' var kernerl=IPython.notebook.kernel; var jsobj = "hello world"; var pycode = `"""${jsobj}""`; kernel.execute(pycode); ''' Javascript(jscode)
注意1:python側で文字列リテラルとして認識させるためにダブルクオーテーションやシングルクォーテーション等つけてサニタイズする必要がある。
JS側でpythonの計算結果を受け取るにはコールバックを渡す. ブラウザ上のコンソールに表示するだけならコールバックの結果を表示すればいい。
サンプルコード例2:
from IPython.display import Javascript jscode = ''' var kernel = IPython.notebook.kernel; var callback = function(output) { console.log(output); var res = output.content.data['text/plain']; console.log("o:", res); return res; }; var x = 0; var pycode = `"""hello world """ + str(${x}+1)`; kernel.execute(pycode, {'iopub': {"output": callback}}, {'silent':false}); ''' Javascript(jscode)
ブラウザからコンソールを開く。ちなみにpython側でエラーが出るとコールバック引数output
にTracebackが保存される。
コンソール :
{header: {…}, msg_id: '66a569b4-e88df9f760b38eff2762703f_219', msg_type: 'execute_result', parent_header: {…}, metadata: {…}, …} o: 'hello world 1'
上記の例だと、セルのOut
には何も結果が表示されない。
表示させるには、セルオブジェクトを取得してcell.output_area.append_output()
関数が使える。
サンプル3:
from IPython.display import Javascript jscode = ''' var kernel = IPython.notebook.kernel; function get_exec_cell(this_of_call){ var output_area = this_of_call; var cell_element = output_area.element.parents('.cell'); var cell_idx = Jupyter.notebook.get_cell_elements().index(cell_element); var cell = Jupyter.notebook.get_cell(cell_idx); return cell; } var callback = function(output) { console.log(output) var res = output.content.data['text/plain']; console.log("o:", res); var cell = get_exec_cell($this); cell.output_area.clear_output(); cell.output_area.append_output({ output_type: "stream", name: "o", text: String(res) }); return res; }; var x = 0; var pycode = `"""hello world """+str(${x}+1)`; $this=this kernel.execute(pycode, {'iopub': {"output": callback}}, {'silent':false}); ''' Javascript(jscode)
関数に渡すにはオブジェクトにする必要がある。必要なプロパティが決まっている。プロパティを満たさないとnotebookファイルのjsonがvalidにならない。
まず output_type
を指定する必要がある。
テキスト出力の場合: output_type: "stream"
、name
:(名前)、text
: 出力したい文字列、の3つが必要
//テキストの出力 cell.output_area.append_output({ output_type: "stream", name: "o", text: String(res) });
画像出力の場合:output_type: "display_data"
、data
の2つが必要。
data
にMIMEタイプを指定して画像データを渡す。
//画像の出力 cell.output_area.append_output({ output_type: "display_data", data: {"image/svg+xml": out.content.data["image/svg+xml"]} });
※注意:実行中のセルの取得にJupyter.notebook.get_selected_cell();
は使えないので注意。SHIFT+ENTERでセル実行後の次のセルが選択されてしまう。参考:
ipython - How to select current cell with JavaScript in Jupyter? - Stack Overflow
参考リンク
IPython.notebook.kernel
周り:
- iPython APIを呼んだときに発行されるevent。なのでこれに関係した関数が定義されているはずだがサンプルコードの情報が少ない:JavaScript Events — Jupyter Documentation 4.1.1 alpha documentation
IPython.display
のサンプルコード集:Python Examples of IPython.display.Javascript- JS→Pythonへのコールバック関数のPromise化:jupyter notebookでpythonとJavaScriptの連携 - Qiita