inet-henge で、好みのネットワーク図を描くヒント
2020-02-25追記: SVG DOM が変更になったため、この記事のCSS ではスタイルが壊れるかもしれません。こちら も参考にしてください。
「構成管理DB から自動でネットワーク図を描く」というコンセプトで、inet-henge というライブラリを開発しています。
ネットワークは日々変化しますが、ネットワーク図の更新が面倒です。「このデバイスはあのデバイスとつながっている」くらいの接続情報の集まりから、オートレイアウトでネットワーク図を描きたい。これがモチベーションです。
ここでは、最近入った機能を使って 好みのネットワーク図を描くためのヒントを紹介します。
計算結果のキャッシュ
描画のたびに計算結果である位置情報をキャッシュし、それをヒントにすることで2回目以降の表示が高速になりました。
- キャッシュはブラウザに保存される
- 入力データが変わった場合はキャッシュを使わない
キャッシュを参照せず常に計算を行うには、次のように positionCache
オプションを false
にします。
<script> var diagram = new Diagram('#diagram', 'bar.json', {positionCache: false}); diagram.init('bandwidth', 'interface'); </script>
計算ステップ数を調整する
2回目以降はキャッシュを使えるという前提のもと、計算ステップを増やすことで よりよい配置を得つつ (2回目以降の) 表示速度を落とさない、ということが可能になりました。
このオプションを理解するために、内部で利用している D3.js のforce layout について説明します。
force layout はノード間に引力や斥力が働く場を想定し、初期配置から小刻みに時間ステップを進めながらノード配置を計算して収束させるD3.js モジュールです。ノード数やリンク数が増えるにつれ、一般的には収束までに必要な時間ステップ数が増加します。よりよいネットワーク図を得る代わりに表示時間が長くなるトレードオフがあったわけです。
これについて、過去のinet-henge では 最大時間ステップ数 = 1000
を決め打っていました 💦
表示のたびに再計算する仕様 & スピード優先 でした。
ここが可変になり、ネットワーク規模に合わせたステップ数を設定できます。デフォルトは今まで通りの 1000
です。
<script> var diagram = new Diagram('#diagram', 'bar.json', { ticks: 2000 // 2000 ステップまで計算する }); diagram.init('bandwidth', 'interface'); </script>
CSS スタイリング
次のようなJSONデータを
{ "nodes": [ { "name": "POP1-A" }, { "name": "POP1-B" }, { "name": "POP2-A", "icon": "https://inet-henge.herokuapp.com/images/router.png" }, { "name": "POP2-B", "icon": "https://inet-henge.herokuapp.com/images/router.png" }, { "name": "POP3-A", "icon": "https://inet-henge.herokuapp.com/images/router.png" } ], "links": [ { "source": "POP1-A", "target": "POP1-B", "meta": { "interface": { "source": "ge-0/{0,1}/0", "target": "Te0/{0,1}/0/0" }, "bandwidth": "20G" } }, { "source": "POP2-A", "target": "POP2-B", "meta": { "interface": { "source": "ge-0/{0,1}/0", "target": "Te0/0/{0,1}/0" }, "bandwidth": "20G" } }, { "source": "POP1-A", "target": "POP2-A", "meta": { "interface": { "source": "ge-0/0/1", "target": "Te0/0/0/1" }, "bandwidth": "10G" } }, { "source": "POP1-B", "target": "POP2-B", "meta": { "interface": { "source": "ge-0/0/1", "target": "Te0/0/0/1" }, "bandwidth": "10G" } }, { "source": "POP3-A", "target": "POP2-B"} ] }
次のHTMLのように描画するとします。
<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.17/d3.js"></script> <script src="https://inet-henge.herokuapp.com/js/cola.min.js"></script> <script src="https://inet-henge.herokuapp.com/js/inet-henge.js"></script> </head> <body> <div id="diagram"></div> </body> <script> var diagram = new Diagram('#diagram', 'bar.json', { pop: /^([^\s-]+)-/ }); diagram.init('bandwidth', 'interface'); </script> </html>
デフォルトで左のような図になりますが、右のようにCSS で要素ごとにスタイルを変えることが可能です。
- グループ、ノード、リンク全体にスタイルを当てる
- 特定グループにスタイルを当てる (
POP1
) - 特定ノードにスタイルを当てる (
POP1-A
) - 特定リンクにスタイルを当てる (
POP1-A
-POP1-B
間)
下のCSSは、右の図を出力するサンプルです。
配色を変えているのに加え、リンクにマウスオーバーすると強調表示するようになっています。
(リンクラベルが重なって読めませんが、後ほど調節します)
.group rect { opacity: 0.5; } .node, .group rect { cursor: move; } /* 特定 node, link のみスタイルを当てる */ .group.pop1 rect { fill: #4ecdc4 !important; } .node.pop1-a rect { fill: #c7f464 !important; } .link.pop1-a-pop1-b { stroke: #c44d58; } /* リンクにマウスオーバーすると、リンクを太く & ラベル表示 */ .link:hover { stroke-width: 5px; } .link:hover ~ .path-label { font-weight: bold; visibility: visible !important; }
リンク長さの調節
先ほどの例で、マウスオーバーしたときのリンクラベルが重なっていました。
これは、次のようにリンク長さを調節することで解決できます。
<script> var diagram = new Diagram('#diagram', 'bar.json', { pop: /^([^\s-]+)-/, distance: function(force) { force.jaccardLinkLengths(100, 2); } }); diagram.init('bandwidth', 'interface'); </script>
- ジャッカード距離を計算に使う
- 共通ノードを持たないノード間の、ベースとなる長さを
100
- ジャッカード距離に対する係数を
2
としています。
描画したい ノード数、リンク数 によっていい感じのパラメーターが変わってきますので、「見づらいな」と感じたら時々調節してみることをオススメします。
(調節はコンセプトに反するので、ラベルの重なり検知は課題のひとつ💪)
ネットワークステータスの反映
下のように、障害のあるルーターやリンクを強調表示できます。
データ:
{ "nodes": [ { "name": "POP1-A" }, { "name": "POP1-B" }, { "name": "POP2-A", "icon": "https://inet-henge.herokuapp.com/images/router.png" }, { "name": "POP2-B", "icon": "https://inet-henge.herokuapp.com/images/faulty_router.png" }, // アイコン差し替え { "name": "POP3-A", "icon": "https://inet-henge.herokuapp.com/images/router.png" } ], "links": [ { "source": "POP1-A", "target": "POP1-B", "meta": { "interface": { "source": "ge-0/{0,1}/0", "target": "Te0/{0,1}/0/0" }, "bandwidth": "20G" } }, { "source": "POP2-A", "target": "POP2-B", "meta": { "interface": { "source": "ge-0/{0,1}/0", "target": "Te0/0/{0,1}/0" }, "bandwidth": "20G" } }, { "source": "POP1-A", "target": "POP2-A", "meta": { "interface": { "source": "ge-0/0/1", "target": "Te0/0/0/1" }, "bandwidth": "10G" } }, { "source": "POP1-B", "target": "POP2-B", "meta": { "interface": { "source": "ge-0/0/1", "target": "Te0/0/0/1" }, "bandwidth": "10G" } }, { "source": "POP3-A", "target": "POP2-B", "class": "fault" } // クラス指定 ] }
CSS:
.link.fault { stroke: #c44d58; stroke-dasharray: 5, 5; }
出力:
ノードはアイコンの差し替えを、リンクは class
属性の追加によりスタイルを操作します。
補足: ほかのCSS ヒント
CSS スタイリングについて、別の記事も書きました。こちらも参考にしてみてください。
補足: JANOG41 で登壇しました
ずいぶん経ってしまいましたが、JANOG41 でinet-henge について話してきました。
LT で、事前投票 上位のトークが採択されるシステムでしたが…なんと一位 🎉🎉🎉
会期中やその後に「使ってみました」「ここ、こうなりませんか?」などなど多数のフィードバックを頂きまして、めちゃくちゃ有意義なミーティングでした。ありがとうございました!
今回紹介したヒントのほとんどが、そのときの話を参考に実装したものです。
まだまだフィードバックお待ちしてます!