var width = 960, height = 500; var color = d3.scale.category20(); var force = d3.layout.force() .charge(-120) .linkDistance(30) .size([width, height]); var svg = d3.select("#chart").append("svg") .attr("width", width) .attr("height", height); d3.json("miserables.json", function(json) { var t = Date.now(); // set start time to moment when data has actually loaded, so animation doesn't start as if already running for a while force .nodes(json.nodes) .links(json.links) .start(); var link = svg.selectAll("line.link") .data(json.links) .enter().append("line") .attr("class", "link") .attr("opacity",1e-6) .style("stroke-width", function(d) {return Math.sqrt(d.value);}) .each(function(d, i, j) { //d.pending = 1; // add 'pending' member to each enter()ed link object }); //.each(appear()); var node = svg.selectAll("circle.node") .data(json.nodes); var nodeEnter = node.enter().append("circle") .attr("class", "node") .attr("r", 5) .attr("opacity",1e-6) .attr("id",function(d,i){return "c_"+i.toString()}) .style("fill", function(d) {return color(d.group);}) .each(appear()) .call(force.drag); node.append("title") .text(function(d) {return d.name;}); force.on("tick", function() { 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;}); }); function appear() { return function(d, i, j) { d.pending = 1; // add 'pending' member to each enter()ed node object t += 200; d3.select(this).transition() .duration(1000) .delay(t - Date.now()) .attr("opacity", 1) .each("end", function(d, idx) { //console.log('end: ', d, idx, this, t); d.pending = 0; // select the links for this node: var nodes = json.nodes; var links = json.links; var neighbors; var i, j, n = nodes.length, m = links.length; var linkchain = []; //console.log('nodes: ', n, m); // we know that the FORCE LAYOUT adds the field 'index' to each node. // We use that 'internals' knowledge here to match links with node d. link.each(function(lnk, a, b) { //console.log('link check: ', this, lnk, a, b); // only show those links which have both endpoints (nodes) already showing. // // Note: this condition is sufficient to ensure all links are shown // once all the nodes are shown. if ((lnk.source == d || lnk.target == d) && !lnk.source.pending && !lnk.target.pending) { linkchain.push(d3.select(this)); } }); appear_link(linkchain); }); }; } function appear_link(chain) { if (chain.length == 0) return; chain.pop().transition() .duration(1000) .attr("opacity", 1) .each("end", function() { appear_link(chain); }); } });