Skip to content

Instantly share code, notes, and snippets.

@mattnworb
Last active December 10, 2015 21:38
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mattnworb/4496542 to your computer and use it in GitHub Desktop.
Save mattnworb/4496542 to your computer and use it in GitHub Desktop.
<!DOCTYPE html>
<html>
<head>
<title>Service Dependencies</title>
<meta http-equiv="content-type" content="text/html;charset=utf-8">
<script type="text/javascript" src="http://d3js.org/d3.v3.min.js"></script>
<script type="text/javascript" src="http://underscorejs.org/underscore-min.js"></script>
<style type="text/css">
circle.node {
stroke: #fff;
stroke-width: 1.5px;
}
line.link {
stroke: #999;
stroke-opacity: .6;
}
</style>
</head>
<body>
<div class="body">
<div class="content">
<p id="info"></p>
<div id="chart">
</div>
</div>
</div>
<script type="text/javascript">
var data = {
"nodes": [
{ "name": "account", "group": "service"},
{ "name": "audio", "group": "service"},
{ "name": "authentication", "group": "service"},
{ "name": "comm-service", "group": "service"},
{ "name": "commerce", "group": "product"},
{ "name": "desktopnotification", "group": "service"},
{ "name": "ext-admin", "group": "product"},
{ "name": "g2wattendee", "group": "product"},
{ "name": "g2wbroker", "group": "product"},
{ "name": "internal-admin", "group": "service"},
{ "name": "meeting", "group": "product"},
{ "name": "queue", "group": "service"},
{ "name": "reporting-dispatch", "group": "service"},
{ "name": "screensharing", "group": "service"},
{ "name": "usage", "group": "service"}
],
"connections": [
{"source": "commerce", "target": "usage", "type":"REST"},
{"source": "ext-admin", "target": "reporting-dispatch", "type":"SQL"},
{"source": "ext-admin", "target": "meeting", "type":"REST"},
{"source": "g2wattendee", "target": "g2wbroker", "type":"REST"},
{"source": "g2wbroker", "target": "comm-service", "type":"REST"},
{"source": "g2wbroker", "target": "authentication", "type":"REST"},
{"source": "g2wbroker", "target": "account", "type":"REST"},
{"source": "g2wbroker", "target": "queue", "type":"REST"},
{"source": "g2wbroker", "target": "audio", "type":"REST"},
{"source": "g2wbroker", "target": "comm-service", "type":"HttpInvoker"},
{"source": "reporting-dispatch", "target": "queue", "type":"REST"},
{"source": "meeting", "target": "screensharing", "type":"REST"},
{"source": "meeting", "target": "account", "type":"REST"},
{"source": "meeting", "target": "audio", "type":"REST"},
{"source": "screensharing", "target": "queue", "type":"REST"},
{"source": "screensharing", "target": "comm-service", "type":"REST"},
{"source": "screensharing", "target": "desktopnotification", "type":"REST"},
{"source": "screensharing", "target": "account", "type":"REST"},
{"source": "screensharing", "target": "audio", "type":"REST"},
{"source": "screensharing", "target": "authentication", "type":"REST"},
{"source": "meeting", "target": "queue", "type":"REST"},
{"source": "usage", "target": "queue", "type":"REST"},
{"source": "reporting-dispatch", "target": "queue", "type":"REST"},
{"source": "internal-admin", "target": "g2wbroker", "type":"REST"},
{"source": "internal-admin", "target": "screensharing", "type":"REST"}
]
};
var width = 960,
height = 500;
var color = d3.scale.category10();
var svg = d3.select("#chart").append("svg")
.attr("width", width)
.attr("height", height);
//build nodes and links structure
//nodemap is <nodename, list of connections>
var nodeMap = {};
data.connections.forEach(function(connection) {
var item = {target: connection.target, type: connection.type};
if (connection.source in nodeMap) {
nodeMap[connection.source].push(item);
}
else {
nodeMap[connection.source] = [item];
}
if (!(connection.target in nodeMap)) {
nodeMap[connection.target] = [];
}
});
var groupNames = {};
data.nodes.forEach(function(node) {
groupNames[node.name] = node.group;
});
var nodeNames = _.keys(nodeMap);
var nodes = _.map(nodeNames, function(name) {
var group = "";
if (name in groupNames) {
group = groupNames[name];
}
return { name: name, group: group};
});
// index of element position in nodes to name
var nameIndex = {};
_.each(nodeNames, function(name, index) {
nameIndex[name] = index;
});
//build links
var links = [];
_.each(nodeMap, function(conns, nodename) {
var sourceIx = nameIndex[nodename];
_.each(conns, function(conn) {
var targetIx = nameIndex[conn.target];
links.push({source: sourceIx, target: targetIx, type: conn.type});
});
});
var defaultInfo,
formatNumber = d3.format(",d");
var info = d3.select("#info")
.text(defaultInfo = "Showing " + formatNumber(links.length) + " dependencies among " + formatNumber(nodes.length) + " services.");
var force = d3.layout.force()
.nodes(nodes)
.links(links)
.size([width, height])
.linkDistance([250])
.charge([-500])
.start();
//draws the links
var link = svg.selectAll("line.link")
.data(links)
.enter().append("line")
.attr("class", "link")
.style("stroke-width", function(d) { return Math.sqrt(d.value); })
.on("mouseover", linkMouseover)
.on("mouseout", mouseout);
var node = svg.selectAll("circle.node")
.data(nodes)
.enter().append("circle")
.attr("class", "node")
.attr("r", 25)
.style("fill", function(d) { return color(d.group); })
.call(force.drag)
.on("mouseover", nodeMouseover)
.on("mouseout", mouseout);
node.append("title")
.text(function(d) { return d.name; });
var nodelabel = svg.selectAll("text.nodelabel")
.data(nodes)
.enter()
.append("text")
.text(function(d) { return d.name; });
var linklabel = svg.selectAll("test.linklabel")
.data(links)
.enter()
.append("text")
.text(function(d) { return d.type })
.attr("font-size", "11px");
//animates the nodes on each "tick" of the force layout animation
force.on("tick", function() {
link.attr("x1", function(d) { return d.source.x; })
.attr("y1", function(d) { return d.source.y; })
.attr("x2", function(d) { return d.target.x; })
.attr("y2", function(d) { return d.target.y; });
node.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; });
nodelabel.attr("x", function(d) { return d.x; })
.attr("y", function(d) { return d.y; });
linklabel.attr("x", function(d) { return (d.target.x + d.source.x) / 2; })
.attr("y", function(d) { return (d.target.y + d.source.y) / 2; });
});
// Highlight the link and connected nodes on mouseover.
function linkMouseover(d) {
svg.selectAll(".link").classed("active", function(p) { return p === d; });
svg.selectAll(".node circle").classed("active", function(p) { return p === d.source || p === d.target; });
info.text(d.source.name + " → " + d.target.name);
}
function nodeMouseover(d) {
svg.selectAll(".link").classed("active", function(p) { return p.source === d || p.target === d; });
d3.select(this).classed("active", true);
info.text(d.name);
}
// Clear any highlighted nodes or links.
function mouseout() {
svg.selectAll(".active").classed("active", false);
info.text(defaultInfo);
}
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment