toolkit/javascript/d3/examples/force/force-cluster.html
changeset 47 c0b4a8b5a012
equal deleted inserted replaced
46:efd9c589177a 47:c0b4a8b5a012
       
     1 <!DOCTYPE html>
       
     2 <html>
       
     3   <head>
       
     4     <title>Clustered Network</title>
       
     5     <script type="text/javascript" src="../../d3.js"></script>
       
     6     <script type="text/javascript" src="../../d3.geom.js"></script>
       
     7     <script type="text/javascript" src="../../d3.layout.js"></script>
       
     8     <style type="text/css">
       
     9 svg {
       
    10   border: 1px solid #ccc;
       
    11 }
       
    12 body {
       
    13   font: 10px sans-serif;
       
    14 }
       
    15 circle.node {
       
    16   fill: lightsteelblue;
       
    17   stroke: #fff;
       
    18   stroke-width: 1.5px;
       
    19 }
       
    20 path.hull {
       
    21   fill: lightsteelblue;
       
    22   fill-opacity: 0.3;
       
    23 }
       
    24 line.link {
       
    25   stroke: #333;
       
    26   stroke-opacity: 0.5;
       
    27   pointer-events: none;
       
    28 }
       
    29     </style>
       
    30   </head>
       
    31   <body>
       
    32     <script type="text/javascript">
       
    33 var w = 960,     // svg width
       
    34     h = 600,     // svg height
       
    35     dr = 4,      // default point radius
       
    36     off = 15,    // cluster hull offset
       
    37     expand = {}, // expanded clusters
       
    38     data, net, force, hullg, hull, linkg, link, nodeg, node,
       
    39     curve = d3.svg.line().interpolate("cardinal-closed").tension(.85),
       
    40     fill = d3.scale.category20();
       
    41 
       
    42 function noop() { return false; }
       
    43 
       
    44 function nodeid(n) {
       
    45   return n.size ? "_g_"+n.group : n.name;
       
    46 }
       
    47 
       
    48 function linkid(l) {
       
    49   var u = nodeid(l.source),
       
    50       v = nodeid(l.target);
       
    51   return u<v ? u+"|"+v : v+"|"+u;
       
    52 }
       
    53 
       
    54 function getGroup(n) { return n.group; }
       
    55 
       
    56 // constructs the network to visualize
       
    57 function network(data, prev, index, expand) {
       
    58   expand = expand || {};
       
    59   var gm = {},    // group map
       
    60       nm = {},    // node map
       
    61       lm = {},    // link map
       
    62       gn = {},    // previous group nodes
       
    63       gc = {},    // previous group centroids
       
    64       nodes = [], // output nodes
       
    65       links = []; // output links
       
    66 
       
    67   // process previous nodes for reuse or centroid calculation
       
    68   if (prev) {
       
    69     prev.nodes.forEach(function(n) {
       
    70       var i = index(n), o;
       
    71       if (n.size > 0) {
       
    72         gn[i] = n;
       
    73         n.size = 0;
       
    74       } else {
       
    75         o = gc[i] || (gc[i] = {x:0,y:0,count:0});
       
    76         o.x += n.x;
       
    77         o.y += n.y;
       
    78         o.count += 1;
       
    79       }
       
    80     });
       
    81   }
       
    82 
       
    83   // determine nodes
       
    84   for (var k=0; k<data.nodes.length; ++k) {
       
    85     var n = data.nodes[k],
       
    86         i = index(n);
       
    87 
       
    88     if (expand[i]) {
       
    89       // the node should be directly visible
       
    90       nm[n.name] = nodes.length;
       
    91       nodes.push(n);
       
    92       if (gn[i]) {
       
    93         // place new nodes at cluster location (plus jitter)
       
    94         n.x = gn[i].x + Math.random();
       
    95         n.y = gn[i].y + Math.random();
       
    96       }
       
    97     } else {
       
    98       // the node is part of a collapsed cluster
       
    99       var l = gm[i] || (gm[i]=gn[i]) || (gm[i]={group:i, size:0, nodes:[]});
       
   100       if (l.size == 0) {
       
   101         // if new cluster, add to set and position at centroid of leaf nodes
       
   102         nm[i] = nodes.length;
       
   103         nodes.push(l);
       
   104         if (gc[i]) {
       
   105           l.x = gc[i].x / gc[i].count;
       
   106           l.y = gc[i].y / gc[i].count;
       
   107         }
       
   108       }
       
   109       l.size += 1;
       
   110       l.nodes.push(n);
       
   111     }
       
   112   }
       
   113 
       
   114   // determine links
       
   115   for (k=0; k<data.links.length; ++k) {
       
   116     var e = data.links[k],
       
   117         u = index(e.source),
       
   118         v = index(e.target);
       
   119     u = expand[u] ? nm[e.source.name] : nm[u];
       
   120     v = expand[v] ? nm[e.target.name] : nm[v];
       
   121     var i = (u<v ? u+"|"+v : v+"|"+u),
       
   122         l = lm[i] || (lm[i] = {source:u, target:v, size:0});
       
   123     l.size += 1;
       
   124   }
       
   125   for (i in lm) { links.push(lm[i]); }
       
   126 
       
   127   return {nodes: nodes, links: links};
       
   128 }
       
   129 
       
   130 function convexHulls(nodes, index, offset) {
       
   131   var h = {};
       
   132 
       
   133   // create point sets
       
   134   for (var k=0; k<nodes.length; ++k) {
       
   135     var n = nodes[k];
       
   136     if (n.size) continue;
       
   137     var i = index(n),
       
   138         l = h[i] || (h[i] = []);
       
   139     l.push([n.x-offset, n.y-offset]);
       
   140     l.push([n.x-offset, n.y+offset]);
       
   141     l.push([n.x+offset, n.y-offset]);
       
   142     l.push([n.x+offset, n.y+offset]);
       
   143   }
       
   144 
       
   145   // create convex hulls
       
   146   var hulls = [];
       
   147   for (i in h) {
       
   148     hulls.push({group: i, path: d3.geom.hull(h[i])});
       
   149   }
       
   150 
       
   151   return hulls;
       
   152 }
       
   153 
       
   154 function drawCluster(d) {
       
   155   return curve(d.path); // 0.8
       
   156 }
       
   157 
       
   158 // --------------------------------------------------------
       
   159 
       
   160 var body = d3.select("body");
       
   161 
       
   162 var vis = body.append("svg:svg")
       
   163    .attr("width", w)
       
   164    .attr("height", h);
       
   165 
       
   166 d3.json("miserables.json", function(json) {
       
   167   data = json;
       
   168   for (var i=0; i<data.links.length; ++i) {
       
   169     o = data.links[i];
       
   170     o.source = data.nodes[o.source];
       
   171     o.target = data.nodes[o.target];
       
   172   }
       
   173 
       
   174   hullg = vis.append("svg:g");
       
   175   linkg = vis.append("svg:g");
       
   176   nodeg = vis.append("svg:g");
       
   177 
       
   178   init();
       
   179 
       
   180   vis.attr("opacity", 1e-6)
       
   181     .transition()
       
   182       .duration(1000)
       
   183       .attr("opacity", 1);
       
   184 });
       
   185 
       
   186 function init() {
       
   187   if (force) force.stop();
       
   188 
       
   189   net = network(data, net, getGroup, expand);
       
   190 
       
   191   force = d3.layout.force()
       
   192       .nodes(net.nodes)
       
   193       .links(net.links)
       
   194       .size([w, h])
       
   195       .linkDistance(50)
       
   196       .start();
       
   197 
       
   198   hullg.selectAll("path.hull").remove();
       
   199   hull = hullg.selectAll("path.hull")
       
   200       .data(convexHulls(net.nodes, getGroup, off))
       
   201     .enter().append("svg:path")
       
   202       .attr("class", "hull")
       
   203       .attr("d", drawCluster)
       
   204       .style("fill", function(d) { return fill(d.group); })
       
   205       .on("dblclick", function(d) { expand[d.group] = false; init(); });
       
   206 
       
   207   link = linkg.selectAll("line.link").data(net.links, linkid);
       
   208   link.exit().remove();
       
   209   link.enter().append("svg:line")
       
   210       .attr("class", "link")
       
   211       .attr("x1", function(d) { return d.source.x; })
       
   212       .attr("y1", function(d) { return d.source.y; })
       
   213       .attr("x2", function(d) { return d.target.x; })
       
   214       .attr("y2", function(d) { return d.target.y; })
       
   215       .style("stroke-width", function(d) { return d.size || 1; });
       
   216 
       
   217   node = nodeg.selectAll("circle.node").data(net.nodes, nodeid);
       
   218   node.exit().remove();
       
   219   node.enter().append("svg:circle")
       
   220       .attr("class", function(d) { return "node" + (d.size?"":" leaf"); })
       
   221       .attr("r", function(d) { return d.size ? d.size + dr : dr+1; })
       
   222       .attr("cx", function(d) { return d.x; })
       
   223       .attr("cy", function(d) { return d.y; })
       
   224       .style("fill", function(d) { return fill(d.group); })
       
   225       .on("dblclick", function(d) {
       
   226         if (d.size) { expand[d.group] = true; init(); }
       
   227       });
       
   228 
       
   229   node.call(force.drag);
       
   230 
       
   231   force.on("tick", function() {
       
   232     if (!hull.empty()) {
       
   233       hull.data(convexHulls(net.nodes, getGroup, off))
       
   234           .attr("d", drawCluster);
       
   235     }
       
   236 
       
   237     link.attr("x1", function(d) { return d.source.x; })
       
   238         .attr("y1", function(d) { return d.source.y; })
       
   239         .attr("x2", function(d) { return d.target.x; })
       
   240         .attr("y2", function(d) { return d.target.y; });
       
   241 
       
   242     node.attr("cx", function(d) { return d.x; })
       
   243         .attr("cy", function(d) { return d.y; });
       
   244   });
       
   245 }
       
   246 
       
   247     </script>
       
   248   </body>
       
   249 </html>