252 строки
6.4 KiB
HTML
252 строки
6.4 KiB
HTML
<!DOCTYPE html>
|
|
<html>
|
|
<head>
|
|
<title>Clustered Network</title>
|
|
<script type="text/javascript" src="../../d3.js"></script>
|
|
<script type="text/javascript" src="../../d3.geom.js"></script>
|
|
<script type="text/javascript" src="../../d3.layout.js"></script>
|
|
<style type="text/css">
|
|
svg {
|
|
border: 1px solid #ccc;
|
|
}
|
|
body {
|
|
font: 10px sans-serif;
|
|
}
|
|
circle.node {
|
|
fill: lightsteelblue;
|
|
stroke: #fff;
|
|
stroke-width: 1.5px;
|
|
}
|
|
path.hull {
|
|
fill: lightsteelblue;
|
|
fill-opacity: 0.3;
|
|
}
|
|
line.link {
|
|
stroke: #333;
|
|
stroke-opacity: 0.5;
|
|
pointer-events: none;
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<script type="text/javascript">
|
|
var w = 960, // svg width
|
|
h = 600, // svg height
|
|
dr = 4, // default point radius
|
|
off = 15, // cluster hull offset
|
|
expand = {}, // expanded clusters
|
|
data, net, force, hullg, hull, linkg, link, nodeg, node,
|
|
curve = d3.svg.line().interpolate("cardinal-closed").tension(.85),
|
|
fill = d3.scale.category20();
|
|
|
|
function noop() { return false; }
|
|
|
|
function nodeid(n) {
|
|
return n.size ? "_g_"+n.group : n.name;
|
|
}
|
|
|
|
function linkid(l) {
|
|
var u = nodeid(l.source),
|
|
v = nodeid(l.target);
|
|
return u<v ? u+"|"+v : v+"|"+u;
|
|
}
|
|
|
|
function getGroup(n) { return n.group; }
|
|
|
|
// constructs the network to visualize
|
|
function network(data, prev, index, expand) {
|
|
expand = expand || {};
|
|
var gm = {}, // group map
|
|
nm = {}, // node map
|
|
lm = {}, // link map
|
|
gn = {}, // previous group nodes
|
|
gc = {}, // previous group centroids
|
|
nodes = [], // output nodes
|
|
links = []; // output links
|
|
|
|
// process previous nodes for reuse or centroid calculation
|
|
if (prev) {
|
|
prev.nodes.forEach(function(n) {
|
|
var i = index(n), o;
|
|
if (n.size > 0) {
|
|
gn[i] = n;
|
|
n.size = 0;
|
|
} else {
|
|
o = gc[i] || (gc[i] = {x:0,y:0,count:0});
|
|
o.x += n.x;
|
|
o.y += n.y;
|
|
o.count += 1;
|
|
}
|
|
});
|
|
}
|
|
|
|
// determine nodes
|
|
for (var k=0; k<data.nodes.length; ++k) {
|
|
var n = data.nodes[k],
|
|
i = index(n);
|
|
|
|
if (expand[i]) {
|
|
// the node should be directly visible
|
|
nm[n.name] = nodes.length;
|
|
nodes.push(n);
|
|
if (gn[i]) {
|
|
// place new nodes at cluster location (plus jitter)
|
|
n.x = gn[i].x + Math.random();
|
|
n.y = gn[i].y + Math.random();
|
|
}
|
|
} else {
|
|
// the node is part of a collapsed cluster
|
|
var l = gm[i] || (gm[i]=gn[i]) || (gm[i]={group:i, size:0, nodes:[]});
|
|
if (l.size == 0) {
|
|
// if new cluster, add to set and position at centroid of leaf nodes
|
|
nm[i] = nodes.length;
|
|
nodes.push(l);
|
|
if (gc[i]) {
|
|
l.x = gc[i].x / gc[i].count;
|
|
l.y = gc[i].y / gc[i].count;
|
|
}
|
|
}
|
|
l.size += 1;
|
|
l.nodes.push(n);
|
|
}
|
|
}
|
|
|
|
// determine links
|
|
for (k=0; k<data.links.length; ++k) {
|
|
var e = data.links[k],
|
|
u = index(e.source),
|
|
v = index(e.target);
|
|
u = expand[u] ? nm[e.source.name] : nm[u];
|
|
v = expand[v] ? nm[e.target.name] : nm[v];
|
|
var i = (u<v ? u+"|"+v : v+"|"+u),
|
|
l = lm[i] || (lm[i] = {source:u, target:v, size:0});
|
|
l.size += 1;
|
|
}
|
|
for (i in lm) { links.push(lm[i]); }
|
|
|
|
return {nodes: nodes, links: links};
|
|
}
|
|
|
|
function convexHulls(nodes, index, offset) {
|
|
var h = {};
|
|
|
|
// create point sets
|
|
for (var k=0; k<nodes.length; ++k) {
|
|
var n = nodes[k];
|
|
if (n.size) continue;
|
|
var i = index(n),
|
|
l = h[i] || (h[i] = []);
|
|
l.push([n.x-offset, n.y-offset]);
|
|
l.push([n.x-offset, n.y+offset]);
|
|
l.push([n.x+offset, n.y-offset]);
|
|
l.push([n.x+offset, n.y+offset]);
|
|
}
|
|
|
|
// create convex hulls
|
|
var hulls = [];
|
|
for (i in h) {
|
|
hulls.push({group: i, path: d3.geom.hull(h[i])});
|
|
}
|
|
|
|
return hulls;
|
|
}
|
|
|
|
function drawCluster(d) {
|
|
return curve(d.path); // 0.8
|
|
}
|
|
|
|
// --------------------------------------------------------
|
|
|
|
var body = d3.select("body");
|
|
|
|
var vis = body.append("svg:svg")
|
|
.attr("width", w)
|
|
.attr("height", h);
|
|
|
|
d3.json("miserables.json", function(json) {
|
|
data = json;
|
|
for (var i=0; i<data.links.length; ++i) {
|
|
o = data.links[i];
|
|
o.source = data.nodes[o.source];
|
|
o.target = data.nodes[o.target];
|
|
}
|
|
|
|
hullg = vis.append("svg:g");
|
|
linkg = vis.append("svg:g");
|
|
nodeg = vis.append("svg:g");
|
|
|
|
init();
|
|
|
|
vis.attr("opacity", 1e-6)
|
|
.transition()
|
|
.duration(1000)
|
|
.attr("opacity", 1);
|
|
});
|
|
|
|
function init() {
|
|
if (force) force.stop();
|
|
|
|
net = network(data, net, getGroup, expand);
|
|
|
|
force = d3.layout.force()
|
|
.nodes(net.nodes)
|
|
.links(net.links)
|
|
.size([w, h])
|
|
.linkDistance(50)
|
|
.start();
|
|
|
|
hullg.selectAll("path.hull").remove();
|
|
hull = hullg.selectAll("path.hull")
|
|
.data(convexHulls(net.nodes, getGroup, off))
|
|
.enter().append("svg:path")
|
|
.attr("class", "hull")
|
|
.attr("d", drawCluster)
|
|
.style("fill", function(d) { return fill(d.group); })
|
|
.on("dblclick", function(d) { expand[d.group] = false; init(); });
|
|
|
|
link = linkg.selectAll("line.link").data(net.links, linkid);
|
|
link.exit().remove();
|
|
link.enter().append("svg:line")
|
|
.attr("class", "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; })
|
|
.style("stroke-width", function(d) { return d.size || 1; });
|
|
link = linkg.selectAll("line.link");
|
|
|
|
node = nodeg.selectAll("circle.node").data(net.nodes, nodeid);
|
|
node.exit().remove();
|
|
node.enter().append("svg:circle")
|
|
.attr("class", function(d) { return "node" + (d.size?"":" leaf"); })
|
|
.attr("r", function(d) { return d.size ? d.size + dr : dr+1; })
|
|
.attr("cx", function(d) { return d.x; })
|
|
.attr("cy", function(d) { return d.y; })
|
|
.style("fill", function(d) { return fill(d.group); })
|
|
.on("dblclick", function(d) {
|
|
if (d.size) { expand[d.group] = true; init(); }
|
|
});
|
|
|
|
node = nodeg.selectAll("circle.node")
|
|
.call(force.drag);
|
|
|
|
force.on("tick", function() {
|
|
if (!hull.empty()) {
|
|
hull.data(convexHulls(net.nodes, getGroup, off))
|
|
.attr("d", drawCluster);
|
|
}
|
|
|
|
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; });
|
|
});
|
|
}
|
|
|
|
</script>
|
|
</body>
|
|
</html>
|