HTMLとjavascript(vis.js)でインタラクティブなネットワーク図を色々カスタマイズ

HTMLとjavascriptでインタラクティブなネットワーク図を作成する方法の紹介です。vis.jsというライブラリを使います。
今回は例として、漫画の推薦帯(あの作品の〇〇先生推薦!ってやつ)ネットワークを作ってみようと思います。


vis.jsとは

vis.jsはjavascriptでネットワーク図等を描画するためのライブラリです。
以下のタグを追加することで使用できるようになります。

<script type="text/javascript" src="https://unpkg.com/vis-network@9.0.1/dist/vis-network.min.js"></script>

vis.js全体のホームページはこちら。

vis.jsのネットワーク部分についてのドキュメントはこちら。ドキュメントが充実してるので工夫次第で色々とできそうです。

基本部分

まず、HTML部分にネットワークを描画するdiv要素を用意します。

<div id="network"></div>

ここからはJavascriptのコード紹介。
簡単な説明をコメントアウトでつけています。詳細は公式ドキュメントを参照してください。

//ノードの情報
var nodes = new vis.DataSet([
  { id: "呪術廻戦", label: "呪術廻戦", shape: 'image',image:"https://www.books.or.jp/img/books_icon/9784088815169.jpg"},
  { id: "鬼滅の刃", label: "鬼滅の刃", shape: 'image',image:"https://www.books.or.jp/img/books_icon/9784088807232.jpg"},
  { id: "ワートリ", label: "ワートリ", shape: 'image', image:"https://www.books.or.jp/img/books_icon/9784088708096.jpg"},
  { id: "マリッジトキシン", label: "マリッジトキシン", shape: 'image', image:"https://www.books.or.jp/img/books_icon/9784088832135.jpg"},
  { id: "サカモトデイズ", label: "サカモトデイズ", shape: 'image', image:"https://www.books.or.jp/img/books_icon/9784088826578.jpg"},
  { id: "ヒロアカ", label: "ヒロアカ", shape: 'image',image:"https://www.books.or.jp/img/books_icon/9784088802640.jpg"},
]);
//エッジの情報
var edges = new vis.DataSet([
  { from: "呪術廻戦", to: "サカモトデイズ"},
  { from: "鬼滅の刃", to: "マリッジトキシン" },
  { from: "ワートリ", to: "呪術廻戦"},
  { from: "ヒロアカ", to: "呪術廻戦"},
  { from: "呪術廻戦", to: "ヒロアカ"},
  { from: "ヒロアカ", to: "ワートリ"},
  { from: "呪術廻戦", to: "マリッジトキシン"},
]);
//ネットワークのデータを作成
var data = {nodes: nodes, edges: edges};

//ネットワークのオプションを設定
var options = {
  nodes: {
    borderWidth: 2,
    shape: 'box',
    shapeProperties: {useBorderWithImage: true},
    color: {border: '#ffffff', highlight:'#3498db'},
  },
  edges: {
    color: {color: '#999999', highlight:'#3498db'},
    arrows: {
      to: {
        enabled: true
      },
    },
  },
};

//ネットワークを表示する場所を指定
var container = document.getElementById("network");

//ネットワークを描画
var network = new vis.Network(container, data, options);

最低限のcssをつけておきます。

#network{
  height: 500px;
  border: 1px solid #000;
  margin: 5px;
}

ここまでの途中経過を表示しておきます(完成版はページの最後に操作できる形で置いてます)
各作品がノードで、推薦関係がエッジで表現されています。クリックで選択したノードとそれに結合しているエッジが青色で強調表示されます。

基本オブ基本

基本的な骨格とカスタマイズはこれで以上です。
ここから必要に応じて更にカスタマイズして行けばいいと思います。
ここからはそういったカスタマイズ例の紹介。

カスタマイズ例1:選択したノードに移動する

ノードを選択したときに、そのノードが画面の中心になるようにネットワークを移動します。

//ノードを選択したときに実行
network.on("selectNode", function(params) {
  //選択したノードのIDを取得
  let selectedNodeId = params.nodes[0];

  //選択したノードの位置を取得
  let positions = network.getPositions([selectedNodeId]);
  let position = positions[selectedNodeId];
  let options = {position: position, animation: {duration: 500, easingFunction:'linear'}}; 
  //選択したノードの位置に移動
  network.moveTo(options);
});
ワートリの選択と移動

カスタマイズ例2:選択したノードと連結したノードを強調表示

選択したノードとエッジで繋がったノードを取得し、そのノードの色を変更することで強調表示します。
ノードの選択を解除したときに色を戻す処理を忘れずに。

//ノードを選択したときに実行
network.on("selectNode", function(params) {
  //選択したノードのIDを取得
  let selectedNodeId = params.nodes[0];

  //選択したノードと連結したノードのIDを取得
  let connectedNodes = network.getConnectedNodes(selectedNodeId);
  
  //連結ノードの枠線の色を変える
  var updatedNodes = [];
  connectedNodes.forEach(function(nodeId) {
    updatedNodes.push({id: nodeId, color: {border:'#3498db'}}); 
  });
  nodes.update(updatedNodes);
});

//ノードの選択を解除したときに実行
network.on('deselectNode', function() {
  //全てのノードの色を元に戻す
  var resetNodes = nodes.map(function(node) {
    return {id: node.id, color: {border:'#ffffff', highlight:'#3498db'}};
  });
  nodes.update(resetNodes);
});
「呪術廻戦」を選択中の表示

カスタマイズ例3:ノードの情報を表示

選択したノードおよび結合したノードを別画面に表示します。
表示用のdiv要素をhtmlに追加。

<div id="information">
  <h2>選択作品名</h2>
  推薦: <br>
  非推薦: 
</div>

javascriptはこんな感じ。選択したノードが始点(推薦側)の場合と終点(被推薦)の場合は分けて処理してます。

//ノードを選択したときに実行
network.on("selectNode", function(params) {
  //選択したノードのIDを取得
  let selectedNodeId = params.nodes[0];

  //選択したノードを始点とするエッジを取得
  var outgoingEdges = edges.get({filter: function (edge) {return edge.from === selectedNodeId;}});   
  //選択したノードを始点とするエッジの終点を取得
  var endNodes = outgoingEdges.map(function(edge) {
    var endNode = edge.to;
    return endNode;
  });
  
  //選択したノードを終点とするエッジを取得
  var incomingEdges = edges.get({filter: function (edge) {return edge.to === selectedNodeId;}});
  //選択したノードを終点とするエッジの始点を取得
  var startNodes = incomingEdges.map(function(edge) {
    var startNode = edge.from;
    return startNode;
  });

  //取得した情報を表示
  let information_div = document.getElementById('information');
  information_div.innerHTML = "<h2>"+selectedNodeId+"</h2><br>推薦: "+endNodes+"<br>被推薦: "+startNodes;
});
ワートリの情報

ノードのIDをkeyにしたデータを作っておけば、他にも色んなデータを紐付けて表示することができそうです。

カスタマイズ例4:ノードの検索機能

今回くらいのノード数だと見やすいネットワークになりますが、ノードの数が増えるとネットワークが複雑になり注目したいノードを見つけにくくなります。そこで検索機能を実装します。

まずはHTML部分。検索ボックスです。
検索ボックスの内容が変化した時に関数を呼びます。

 <input type="text" placeholder="ノード検索" onChange=search_node(this.value)>

呼び出される関数がこれ。入力されたノードが存在する場合は選択し、存在しない場合は視点がリセットされます。

function search_node(value){
  try{
    //検索されたノードを選択
    network.selectNodes([value]);
  }catch (error) {
    //存在しないノードを選択した場合は視点をリセット
    network.fit();
  }
}
鬼滅の刃を検索

ここで注意が必要なのは、ここではノードが選択されるだけで、ノードが選択されたときの処理(カスタマイズ1~3など)は行われないことです。
直接ノードをクリックした場合と検索した場合でどちらも同じ処理を行いたいときは、以下のようなコードにして共通の関数を呼び出すと良いと思います。

//入力した文字を取得
<input type="text" placeholder="ノード検索" onChange=select_node(this.value)>
network.on("selectNode", function(params) {
  //選択したノードのIDを取得
  let selectedNodeId = params.nodes[0];
    //共通の関数select_nodeを呼び出す
  select_node(selectedNodeId);
}

//共通の関数
function select_node(value){
  try{
        network.selectNodes([value]);
    〜〜色んな処理〜〜
  }catch (error) {
    network.fit();
  }
}

完成イメージ

カスタマイズを全部載せたやつの完成イメージです。
PCだと0.25倍を選択すると見やすいかも。

ニッチな話題になってしまうので、今回は汎用性が比較的ありそうなカスタマイズ例のみを紹介しました。
もしニーズがあるようなら他のカスタマイズやtipsも追加するかも。

いいなと思ったら応援しよう!