ネットワーク可視化ツール「Cytoscape」の拡張GUIを、htmlファイル1つでお手軽に作る話 ―XMLHttpRequestでcyRESTを叩く―

さて、Cytoscape関係の4本目です。 1〜3本目はこちら↓

2本目の記事で、CytoscapeのAPIを Powershellで叩く話を書きましたが、セキュリティ設定なんかの関係で、たとえば他の人に使ってもらいたい時には不便です。

あと、GUIが欲しいって時、PowershellでもGUIを作ることはできるようですが、そもそもPowershell自体使い慣れていないので、少し敷居が高いです。

sutefu7.hatenablog.com

そこで、面倒な環境構築が不要で、プラットフォームも気にせず使えるhtmlとjavascriptを使って、簡単にCytoscapeの拡張GUIを作る方法を思いついたので、ここで紹介します。

f:id:at_you_key:20200120234556p:plain

javascriptからCyREST APIを叩く方法

さて、javascriptからHTTPリクエストを送るには、XMLHttpRequestという関数を使います。

この関数を使えば、クロスドメインで非同期通信ができるので、今回のようにローカルで開いたhtml ファイルから、Webサーバー上のCyREST-APIを叩くような使い方もできます。

developer.mozilla.org

さらに、CytoscapeからはJson形式の返事がくるので、Javascriptならそのままオプジェクトとして扱えます。楽ちんです。

さっそく試してみる

例として、サンプルセッションの「Yeast Perturbation.cys」を使って、「選択中のエッジのEdgeBetweennessの合計値をリアルタイムで表示する」というGUIを作ってみましょう。

非同期通信なので、1秒間隔でポーリングして、「ほぼリアルタイム」を目指します(Cytoscapeがツラそうだったら、もう少し頻度を下げます)。

ソースコード

解説はコードのコメントに書いてみました。 タイトルの通り、ひとつのhtmlファイルにまとめてあるので、テキストエディタに張り付け、UTF-8のhtmlとして保存すれば動くはずです。

お作法とかエラー処理とかは特に気にせず、とりあえず動くレベル。

<!DOCTYPE html>
<html>

<head>
   <meta charset="UTF-8">
   <style>
        #result {
            font-size: 25pt;
        }
    </style>
</head>

<body>
    <div id="result">ここに結果が入ります。</div>
    <script>
       // 計算結果を表示する用のdivを取得
       const result = document.getElementById("result");

       // たまにメモリー解放してあげないと暴走するので、メモリ解放するコマンドを用意
       const free_unused_memory = () => {
           const xhr = new XMLHttpRequest();
           xhr.open('GET', 'http://localhost:1234/v1/gc');
           xhr.send(null);
       }

       // とりあえずHTTPリクエストのとこ全部囲む
       const update = () => {

           // APIのリクエストボディを、Json文字列として用意する(書き方はCytoscapeのヘルプを参照)
           const request_body = JSON.stringify({
               "columnList": "EdgeBetweenness",
               "edgeList": "selected",
               "network": "current"
           });

           // xhrを使う準備
           const xhr = new XMLHttpRequest();

           // Edgeのアトリビュートを取得するAPI
           xhr.open('POST', 'http://localhost:1234/v1/commands/edge/get attribute');

           // リクエストはJson形式で送る
           xhr.setRequestHeader('Content-Type', 'application/json');

           // 返事はJson形式で解釈する
           xhr.responseType = 'json';

           // HTTPリクエストに返事が来たときの処理
           xhr.onload = () => {

               // 返事の中身を取り出して
               const response_body = xhr.response.data;

               // reduce関数でEdgeBetweennessを積算してtoFixedで小数点1桁表示
               // 0を足しているのはnull値対策
               const total_Betweenness = (response_body.reduce((acc, x) => acc + x.EdgeBetweenness, 0) + 0).toFixed(1);

               // テンプレートリテラルで結果のhtml文字列を作る
               const html = `選択中のEdgeは${response_body.length}本です。<br>
                               EdgeBetweennessの合計は${total_Betweenness}です。`;

               // 結果をhtmlに書き込む
               result.innerHTML = html;
           }

           // HTTPリクエストを送信
           xhr.send(request_body);

           // 10回毎にメモリ開放
           if (update_count > 10) {
               free_unused_memory();
               update_count = 0;
           } else { update_count++ }
       }

       // メモリ解放するタイミング用のカウンタ
       let update_count = 0;
       
           // 最初の1回はすぐに実行
       update();

       // あとは1秒間隔でアップデート
       setInterval(update, 1000);
   </script>
</body>

</html>

動いた!

htmlファイルをブラウザで開いてから、Cytoscapeでエッジを選択すると、すぐに集計結果が反映されます。

f:id:at_you_key:20200120202613g:plain

1秒間隔でも十分なレスポンスです、呼び出し頻度的にも特に問題は無さそう。

おまけ:ネイティブプラグイン風に表示する

CytoscapeにはWebブラウザが内蔵されているので、そこでhtmlファイルを開いて、あたかも本体に組み込まれた機能のような見た目にしてみましょう。

手で開くなら「Tools」→「Cytoscape Browser」で、file://を付けたフルパスを入れ、「Open in results panel?」にチェックを入れます。

それか、APIからの操作で開く場合はこう。

POST http://localhost:1234/v1/commands/cybrowser/show

{
    "id": "適当なID",
    "title": "タブに表示したいタイトル",
    "url": "file://htmlファイルのフルパス"
}

するとこうなります、ちゃんとCytoscapeのウィンドウ内で動くので、まるでネイティブなプラグインのようです。

まとめ

今回は文字列を作って表示しただけですが、データさえ取れてしまえば、あとはもうjavascriptでやりたい放題です。

集計結果をテーブル表示したり、chart.jsのような可視化ライブラリを使ってグラフを描いたりと、夢が広がりますね!

misc.0o0o.org

それじゃ、今日はここまで。