--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/toolkit/javascript/d3/examples/force/force-cluster.html Thu Apr 10 14:20:23 2014 +0200
@@ -0,0 +1,249 @@
+<!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; });
+
+ 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.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>