singmotor
singmotor

Reputation: 4180

Animating data motion between nodes in d3

I have a d3v4 force-directed graph. For simplicity's sake, let's assume it's like this.

The graph shows flows (like a powergrid). I'd love to be able to animate each link to show flow from the target to the source (with moving dotted lines, bubbles, something).

Does anyone have an idea on how to do that?

Upvotes: 4

Views: 1800

Answers (1)

Xavier Guihot
Xavier Guihot

Reputation: 61686

Here is a slightly modified version of Mike's "Les Misérables" force directed graph in which lines are composed of dashes for which the offset is periodically incremented (inspired by this post):

var svg = d3.select("svg"),
    width = +svg.attr("width"),
    height = +svg.attr("height");

var color = d3.scaleOrdinal(d3.schemeCategory20);

var simulation = d3.forceSimulation()
    .force("link", d3.forceLink().id(function(d) { return d.id; }))
    .force("charge", d3.forceManyBody())
    .force("center", d3.forceCenter(width / 2, height / 2));

d3.json("https://gist.githubusercontent.com/mbostock/4062045/raw/5916d145c8c048a6e3086915a6be464467391c62/miserables.json", function(error, graph) {
  if (error) throw error;

  var link = svg.append("g")
      .attr("class", "links")
    .selectAll("line")
    .data(graph.links)
    .enter().append("line")
      .attr("stroke-width", function(d) { return Math.sqrt(d.value); });

  var node = svg.append("g")
      .attr("class", "nodes")
    .selectAll("circle")
    .data(graph.nodes)
    .enter().append("circle")
      .attr("r", 5)
      .attr("fill", function(d) { return color(d.group); })
      .call(d3.drag()
          .on("start", dragstarted)
          .on("drag", dragged)
          .on("end", dragended));

  node.append("title")
      .text(function(d) { return d.id; });

  simulation
      .nodes(graph.nodes)
      .on("tick", ticked);

  simulation.force("link")
      .links(graph.links);

  var lines = d3.selectAll('line');

  var offset = 1; 
  setInterval(function() {
    lines.style('stroke-dashoffset', offset);
    offset += 1; 
  }, 50);

  function ticked() {
    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 dragstarted(d) {
  if (!d3.event.active) simulation.alphaTarget(0.3).restart();
  d.fx = d.x;
  d.fy = d.y;
}

function dragged(d) {
  d.fx = d3.event.x;
  d.fy = d3.event.y;
}

function dragended(d) {
  if (!d3.event.active) simulation.alphaTarget(0);
  d.fx = null;
  d.fy = null;
}
.links line {
  stroke: #999;
  stroke-opacity: 0.6;
}

.nodes circle {
  stroke: #fff;
  stroke-width: 1.5px;
}

line {
  opacity: 0.5;
  stroke-dasharray: 10, 4;
}
<svg width="960" height="600"></svg>
<script src="https://d3js.org/d3.v4.min.js"></script>

Once the graph is created, we can start a periodic update of the dash offset:

var lines = d3.selectAll('line');

// Updates the offset of dashes every 50ms:
var offset = 1;
setInterval( function() {
  lines.style('stroke-dashoffset', offset);
  offset += 1; 
}, 50);

And here is the css associated to the dashed line:

line {
  opacity: 0.5;
  stroke-dasharray: 10, 4;
}

Upvotes: 3

Related Questions