source: Dev/trunk/d3/examples/force/force-cluster.html @ 76

Last change on this file since 76 was 76, checked in by fpvanagthoven, 14 years ago

d3

File size: 6.4 KB
Line 
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">
9svg {
10  border: 1px solid #ccc;
11}
12body {
13  font: 10px sans-serif;
14}
15circle.node {
16  fill: lightsteelblue;
17  stroke: #fff;
18  stroke-width: 1.5px;
19}
20path.hull {
21  fill: lightsteelblue;
22  fill-opacity: 0.3;
23}
24line.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">
33var 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
42function noop() { return false; }
43
44function nodeid(n) {
45  return n.size ? "_g_"+n.group : n.name;
46}
47
48function linkid(l) {
49  var u = nodeid(l.source),
50      v = nodeid(l.target);
51  return u<v ? u+"|"+v : v+"|"+u;
52}
53
54function getGroup(n) { return n.group; }
55
56// constructs the network to visualize
57function 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
130function 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
154function drawCluster(d) {
155  return curve(d.path); // 0.8
156}
157
158// --------------------------------------------------------
159
160var body = d3.select("body");
161
162var vis = body.append("svg:svg")
163   .attr("width", w)
164   .attr("height", h);
165
166d3.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
186function 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  link = linkg.selectAll("line.link");
217
218  node = nodeg.selectAll("circle.node").data(net.nodes, nodeid);
219  node.exit().remove();
220  node.enter().append("svg:circle")
221      .attr("class", function(d) { return "node" + (d.size?"":" leaf"); })
222      .attr("r", function(d) { return d.size ? d.size + dr : dr+1; })
223      .attr("cx", function(d) { return d.x; })
224      .attr("cy", function(d) { return d.y; })
225      .style("fill", function(d) { return fill(d.group); })
226      .on("dblclick", function(d) {
227        if (d.size) { expand[d.group] = true; init(); }
228      });
229
230  node = nodeg.selectAll("circle.node")
231      .call(force.drag);
232
233  force.on("tick", function() {
234    if (!hull.empty()) {
235      hull.data(convexHulls(net.nodes, getGroup, off))
236          .attr("d", drawCluster);
237    }
238
239    link.attr("x1", function(d) { return d.source.x; })
240        .attr("y1", function(d) { return d.source.y; })
241        .attr("x2", function(d) { return d.target.x; })
242        .attr("y2", function(d) { return d.target.y; });
243
244    node.attr("cx", function(d) { return d.x; })
245        .attr("cy", function(d) { return d.y; });
246  });
247}
248
249    </script>
250  </body>
251</html>
Note: See TracBrowser for help on using the repository browser.