inet-henge.js generates d3.js based Auto Layout Network Diagram from JSON data.
inet-henge helps you draw it by calculating coordinates automatically, placing nodes and links in SVG format.
Each object is draggable and zoomable.
All you have to do are:
- Define nodes identified by name
- Define links by specifying both end nodes
- Show in a browser. That's it.
JSON example:
{
"nodes": [
{ "name": "A" },
{ "name": "B" }
],
"links": [
{ "source": "A", "target": "B" }
]
}
npm install inet-henge
# or
git clone https://github.com/codeout/inet-henge.git
Then host the root directory in your favorite web server.
ruby -run -e httpd . -p 8000
Now you can see http://localhost:8000/example
.
python -m SimpleHTTPServer # python2
python -m http.server # python3
or
php -S 127.0.0.1:8000
are also available to start a web server.
In example here, load related assets at first:
- d3.js v3
- cola.js
⚠️ It doesn't support d3.js v4⚠️
- inet-henge.js
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<!-- You can customize a style of network diagram by CSS -->
<link href="style.css" rel="stylesheet" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.17/d3.js"></script>
<script src="../vendor/cola.min.js"></script>
<script src="../inet-henge.js"></script>
</head>
define a blank container:
<body>
<div id="diagram"></div>
</body>
and render your network diagram:
<script>
new Diagram("#diagram", "shownet.json").init("interface");
</script>
</html>
Object is also acceptable:
<script>
const data = {
"nodes": [{ "name": "A" }, { "name": "B" }],
"links": [{ "source": "A", "target": "B" }]
};
new Diagram("#diagram", data).init("interface");
</script>
</html>
inet-henge.js renders your network diagram as SVG within <div id="diagram"></div>
. In the example above the diagram also displays metadata labelled "interface"
which defined in JSON data.
Minimal json looks like:
{
"nodes": [
{ "name": "A" },
{ "name": "B" }
],
"links": [
{ "source": "A", "target": "B" }
]
}
You can specify node icon by URL:
"nodes": [
{ "name": "dceast-ne40e", "icon": "./images/router.png" }
]
Metadata to display on network diagrams:
new Diagram("#diagram", "index.json").init("interface");
"links": [
{
"source": "noc-asr9904", "target": "noc-ax8616r",
"meta": {
"interface": { "source": "0-0-0-2", "target": "1-1" }
}
}
]
☝️ This will render metadata on both ends of links.
Nodes get rendered in groups when you specify which node belongs to which group by regular expression.
When the first three characters describe POP name, you can group nodes by doing this:
const diagram = new Diagram("#diagram", "data.json", { pop: /^.{3}/ });
When init()
API is called with arguments, inet-henge finds corresponding metadata and show them as labels.
To place a loopback address on nodes:
new Diagram("#diagram", "index.json").init("loopback");
{
"nodes": [
{ "name": "Node 1", "meta": { "loopback": "10.0.0.1" } },
...
],
...
}
To place link and interface names:
new Diagram("#diagram", "index.json").init("bandwidth", "intf-name");
{
...
"links": [
{
"source": "Node 1", "target": "Node 2",
"meta": {
"bandwidth": "10G",
"intf-name": { "source": "interface A", "target": "interface B" }
}
},
...
]
You can change node width and height:
const diagram = new Diagram("#diagram", "data.json", { nodeWidth: 120, nodeHeight: 30 });
Width 60
and heigh 40
(px) by default.
You can use linkWidth()
API to customize link widths. The argument should be a function which calculates metadata and returns value for stroke-width
of SVG.
const diagram = new Diagram("#diagram", "index.json");
diagram.linkWidth(function (link) {
if (!link)
return 1; // px
else if (link.bandwidth === "100G")
return 10; // px
else if (link.bandwidth === "10G")
return 3; // px
else if (link.bandwidth === "1G")
return 1; // px
});
diagram.init("bandwidth");
"links": [
{ "source": "Node 1", "target": "Node 2", "meta": { "bandwidth": "1G" }},
{ "source": "Node 1", "target": "Node 3", "meta": { "bandwidth": "10G" }},
{ "source": "Node 2", "target": "Node 3", "meta": { "bandwidth": "100G" }}
]
You can specify padding to increase the size of groups (default: 1):
const diagram = new Diagram("#diagram", "data.json", { groupPadding: 30 });
💡 Position calculation sometimes gets stuck when increasing groupPadding
. initialTicks may help in such cases.
You can specify the number of steps (called as ticks) to calculate with d3-force layout. Bigger ticks typically converge on a better layout, but it will take much longer until settlement. The default value is 1000.
const diagram = new Diagram("#diagram", "data.json", { ticks: 3000 });
For large scale network diagrams, you can also specify the number of initial unconstrained ticks.
const diagram = new Diagram("#diagram", "data.json", { initialTicks: 100, ticks: 100 });
inet-henge calculates the layout in two iteration phases:
- Initial iteration with no constraints. ( default: 0 tick )
- The main iteration with constraints that apply groups as bounding boxes, prevent nodes and groups from overlapping with each other, and so on. ( default: 1000 ticks )
If you increase initialTicks
, inet-henge calculates faster in exchange for network diagram precision so that you can decrease ticks
which is the number of main iteration steps.
20 ~ 100 initialTicks
and 70 ~ 100 ticks
should be good start for 800 nodes with 950 links for example. It takes 20 ~ 30 seconds to render in the benchmark environment.
inet-henge caches a calculated position of nodes, groups, and links for the next rendering. If you load the same JSON data, the cache will be used as a position hint. You can disable this behavior with positionCache
option.
const diagram = new Diagram("#diagram", "data.json", { positionCache: false });
You can change svg's viewport size:
const diagram = new Diagram("#diagram", "data.json", { width: 1200, height: 600 });
This will generate:
<svg width="1200" height="600">
inet-henge generates an SVG image, so you can customize the style by using CSS.
You can display multiple links between nodes by setting bundle: true
in the constructor like:
<script>
const diagram = new Diagram("#diagram", "index.json", { pop: /^([^\s-]+)-/, bundle: true });
diagram.init("loopback", "interface");
</script>
Nodes are connected to each other with a single link by default.
You can save positions of all nodes in browser even after dragging them by setting positionCache: "fixed"
in the constructor like:
<script>
const diagram = new Diagram("#diagram", "index.json", { pop: /^([^\s-]+)-/, positionCache: "fixed" });
diagram.init("loopback", "interface");
</script>
You can provide the coordinates of nodes as position hints to place them where you want. inet-henge calculates the layout by considering them. It always refers to position cache over hint when there is a cache.
💡 They are "hints". Nodes won't be strictly placed there.
const diagram = new Diagram("#diagram", "index.json", {
pop: /^([^\s-]+)-/,
bundle: true,
positionHint: {
nodeCallback: (node) => {
const [_, pop] = node.name.match(/^([^\s-]+)-/);
// specify the position of nodes in POP01
if (pop === "POP01") {
return { x: 600, y: 330 };
}
// unspecified
return null;
},
},
});
diagram.init("loopback", "interface", "description", "type");
- Use
nodeCallback
option to specify per node.- The
Node
object is passed as an argument. - Return value should be an object like
{x: 600, y: 330}
. - If the callback returns
null
, this means that the node position is unspecified.
- The
The position hints are initial positions technically.
- inet-henge places nodes according to the hints.
- When no hint is specified, the node will be placed in the center of the diagram.
- Then, it starts the ticks calculation.
You can display node metadata in the tooltip, instead of always showing as node text, by setting tooltip: "click"
in the constructor like:
<script>
const diagram = new Diagram("#diagram", "index.json", { pop: /^([^\s-]+)-/, tooltip: "click" });
diagram.init("description", "type");
</script>
In the example above, description
and type
will be displayed.
"nodes": [
{ "name": "POP01-bb01", "meta": {"description": "This is a router", "type": "Backbone"}, "icon": "./images/router.png" },
{ "name": "POP01-bb02", "meta": {"description": "This is a router", "type": "Backbone"}, "icon": "./images/router.png" },
{ "name": "POP01-ag01", "meta": {"description": "This is a router", "type": "Aggregation"}, "icon": "./images/switch.png" },
{ "name": "POP01-ag02", "meta": {"description": "This is a router", "type": "Aggregation"}, "icon": "./images/switch.png" },
💡 tooltip: "hover"
is also available.
You can show <a href="...">...</a>
in node metadata tooltips.
<script>
const diagram = new Diagram("#diagram", "index.json", {
pop: /^([^\s-]+)-/,
bundle: true,
tooltip: "click",
href: (t, type) => {
switch (type) {
case "node":
// {
// "name": "POP01-bb01",
// "meta": { "description": "This is a router", "type": "Backbone" },
// "icon": "./images/router.png"
// }
return `https://example.com/node/${tooltip.node.meta?.type}/${tooltip.node.name}`;
case "link":
// {
// "source": "POP03-bb01",
// "target": "POP03-bb02",
// "meta": { "interface": { "source": "ge-0/0/0", "target": "Te0/0/0/0" } }
// }
return `https://example.com/link/${tooltip.link.source.name}-${tooltip.link.target.name}`;
}
}
});
diagram.init("interface", "description", "type");
</script>
This example above will generate <a href="https://example.com/node/Backbone/POP01-bb02">...</a>
.
💡 Use tooltip: "click"
to make tooltips sticky.
You can specify initial position and scale of diagram.
<script>
const diagram = new Diagram("#diagram", "index.json");
diagram.init();
// move 100px in both x and y, and show in 1/2 scale
diagram.attr("transform", "translate(100, 100) scale(0.5)");
</script>
You can apply x-axis or y-axis based position constraints to nodes.
Here is an example.
<script>
const diagram = new Diagram("#diagram", "clos.json", {
positionHint: {
nodeCallback: (node) => {
switch (true) {
// spines, leaves, and servers from the top
case /^spine/.test(node.name):
return { x: 300, y: 100 };
case /^leaf/.test(node.name):
return { x: 300, y: 200 };
case /^server/.test(node.name):
return { x: 300, y: 300 };
}
}
},
positionConstraints: [{
axis: "y",
nodesCallback: (nodes) => [
// all spines should be in line in y-axis. leaves and servers as well.
nodes.filter((n) => /^spine/.test(n.name)),
nodes.filter((n) => /^leaf/.test(n.name)),
nodes.filter((n) => /^server/.test(n.name)),
]
}]
});
diagram.init();
</script>
- Use
nodesCallback
option to create node groups.- The Node object is passed as an argument.
- Individual constraints will be applied to each group.
You may want to define Position hinting besides constraints. Please note that hint is just a hint and nodes won't be strictly placed there, while constraint is always satisfied.
You can display node type based groups in POP-based Node group by group
definition in each node.
"nodes": [
{ "name": "POP01-bb01", "group": "core", "icon": "./images/router.png" },
{ "name": "POP01-bb02", "group": "core", "icon": "./images/router.png" },
...
You can show a "tie" over bundled links by bundle:
definition in each link.
"links": [
{ "source": "POP01-bb01", "target": "POP01-bb02", "bundle": "lag 1" },
{ "source": "POP01-bb01", "target": "POP01-bb02", "bundle": "lag 1" },
{ "source": "POP01-bb01", "target": "POP01-bb02"}
]
- Define bundle name as
bundle:
to specify which link belongs to the bundle ( integer or string value ) - Set
bundle: true
when initializingDiagram
. See Display bundles section for details. - inet-henge draws a "tie" when there are multiple links in bundle among the same node pair. It doesn't draw the mark between different node pairs, even when bundle names are the same.
Name | Note |
---|---|
Removable Node Plugin | Hide and show nodes by key inputs |
Arrows Link Plugin | Make links bidirectional arrows |
Please report issues or enhancement requests to GitHub issues. For questions or feedbacks write to my twitter @codeout.
Or send a pull request to fix.
Copyright (c) 2016-2024 Shintaro Kojima. Code released under the MIT license.