UNPKG

d3

Version:

A small, free JavaScript library for manipulating documents based on data.

252 lines (218 loc) 6.6 kB
<!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>