score:2

Accepted answer

Group circle and text inside a group element. You can use marker-end property to add end markers to the line.

There are marker-mid and marker-start configs too.

var graph = JSON.parse('{"nodes":[{"id":"0","color":"#4444E5"},{"id":"1","color":"#4444E5"},{"id":"2","color":"#CCCCCC"},{"id":"3","color":"#CCCCCC"},{"id":"4","color":"#CCCCCC"},{"id":"5","color":"#CCCCCC"},{"id":"6","color":"#CCCCCC"},{"id":"7","color":"#CCCCCC"},{"id":"8","color":"#CCCCCC"},{"id":"9","color":"#CCCCCC"},{"id":"10","color":"#cc0000"},{"id":"11","color":"#CCCCCC"},{"id":"12","color":"#CCCCCC"},{"id":"13","color":"#CCCCCC"},{"id":"14","color":"#CCCCCC"},{"id":"15","color":"#cc0000"}],"links":[{"source":1,"target":15,"weight":-0.7582412523901727},{"source":0,"target":6,"weight":-0.08179822732917646},{"source":6,"target":10,"weight":0.0939757437427291},{"source":3,"target":4,"weight":0.436380368086641},{"source":4,"target":5,"weight":-0.5385567058738547},{"source":1,"target":2,"weight":-0.277729004201837},{"source":2,"target":3,"weight":0.7675128737907505},{"source":13,"target":14,"weight":0.5519067984674436},{"source":14,"target":15,"weight":0.832660697502495},{"source":5,"target":7,"weight":0.8607887414383458},{"source":7,"target":8,"weight":-0.8965760877371078},{"source":8,"target":9,"weight":-0.2488791800975232},{"source":9,"target":10,"weight":-0.646075500567235},{"source":12,"target":13,"weight":0.40622804770087395},{"source":0,"target":11,"weight":0.24806004723386413},{"source":11,"target":12,"weight":0.8035303834469385}]}');

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

var defs = svg.append("defs")

var marker = defs

  .append("svg:marker")
  .attr("id", "arrow")
  .attr("viewBox", "0 -5 10 10")
  .attr("refX", 15)
  .attr("refY", -1.5)
  .attr("markerWidth", 6)
  .attr("markerHeight", 6)
  .attr("orient", "auto")

marker.append("path")
  .attr("d", "M0,-5L10,0L0,5")
  .attr("class", "arrowHead");

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));


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.weight);
  }).attr("marker-end", "url(#arrow)")

var node = svg.append("g")
  .attr("class", "nodes")
  .selectAll("circle")
  .data(graph.nodes)
  .enter().append("g");

node.append("circle")
  .attr("r", 5)
  .attr("fill", function(d) {
    return d3.color(d.color);
  })
  .call(d3.drag()
    .on("start", dragstarted)
    .on("drag", dragged)
    .on("end", dragended));

var label = node.append("text")
  .attr("class", "label")
  .text("test")

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

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

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

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("transform", function(d) {
      return "translate(" + d.x + "," + 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;
}
line {
  stroke: black;
}
<script src="https://d3js.org/d3.v4.min.js"></script>
<svg width="960" height="600"></svg>

score:1

Your texts are not part of the simulation. Besides that, when you do...

var label = svg.selectAll(".node")
    .data(graph.nodes)
    .enter().append("text")

... your "enter" selection will have one element less, because there is already a <g> element with class node in that SVG.

Solution: append both circles and texts to groups:

var node = svg.append("g")
    .attr("class", "nodes")
    .selectAll("foo")
    .data(graph.nodes)
    .enter().append("g");

var circles = node.append("circle")
    .attr("r", 5)
    .attr("fill", function(d) {
        return d3.color(d.color);
    })
    .call(d3.drag()
        .on("start", dragstarted)
        .on("drag", dragged)
        .on("end", dragended));

var label = node.append("text")
    .attr("class", "label")
    .text("test");

And use transform in the ticked function:

node.attr("transform", function(d) {
    return "translate(" + d.x + "," + d.y + ")";
})

Here is your updated code:

var graph = JSON.parse('{"nodes":[{"id":"0","color":"#4444E5"},{"id":"1","color":"#4444E5"},{"id":"2","color":"#CCCCCC"},{"id":"3","color":"#CCCCCC"},{"id":"4","color":"#CCCCCC"},{"id":"5","color":"#CCCCCC"},{"id":"6","color":"#CCCCCC"},{"id":"7","color":"#CCCCCC"},{"id":"8","color":"#CCCCCC"},{"id":"9","color":"#CCCCCC"},{"id":"10","color":"#cc0000"},{"id":"11","color":"#CCCCCC"},{"id":"12","color":"#CCCCCC"},{"id":"13","color":"#CCCCCC"},{"id":"14","color":"#CCCCCC"},{"id":"15","color":"#cc0000"}],"links":[{"source":1,"target":15,"weight":-0.7582412523901727},{"source":0,"target":6,"weight":-0.08179822732917646},{"source":6,"target":10,"weight":0.0939757437427291},{"source":3,"target":4,"weight":0.436380368086641},{"source":4,"target":5,"weight":-0.5385567058738547},{"source":1,"target":2,"weight":-0.277729004201837},{"source":2,"target":3,"weight":0.7675128737907505},{"source":13,"target":14,"weight":0.5519067984674436},{"source":14,"target":15,"weight":0.832660697502495},{"source":5,"target":7,"weight":0.8607887414383458},{"source":7,"target":8,"weight":-0.8965760877371078},{"source":8,"target":9,"weight":-0.2488791800975232},{"source":9,"target":10,"weight":-0.646075500567235},{"source":12,"target":13,"weight":0.40622804770087395},{"source":0,"target":11,"weight":0.24806004723386413},{"source":11,"target":12,"weight":0.8035303834469385}]}');

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

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));


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.weight);
  })

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

var label = node.append("text")
  .attr("class", "label")
  .text("test");

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

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

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

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("transform", function(d) {
      return "translate(" + d.x + "," + 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;
}
<script src="https://d3js.org/d3.v4.min.js"></script>
<svg width="500" height="400"></svg>


Related Query

More Query from same tag