Question

I've been pulling out my hair trying to implement labels in a force-directed layout network visualization using d3js (I'm using Python and Flask to generate a JSON file from an API and present the viz in a webpage.

I've seen a bunch of examples, like...

http://bl.ocks.org/mbostock/2706022

http://bl.ocks.org/mbostock/1153292

http://bl.ocks.org/MoritzStefaner/1377729

... but I'm pretty new to Javascript, so I'm having a hard time reimplementing these pieces of code into my current code:

The JSON file I've been using is:

{"directed": true, "graph": [], "nodes": [{"id": "user1"}, {"id": "user2"}, {"id": "user3"}, {"id": "user4"}, {"id": "user5"}, {"id": "user6"}, {"id": "user7"}, {"id": "user8"}, {"id": "user9"}, {"id": "user10"}, {"id": "user11"}, {"id": "user12"}, {"id": "user13"}, {"id": "user14"}, {"id": "user15"}, {"id": "user16"}, {"id": "user17"}, {"id": "user18"}, {"id": "user19"}, {"id": "user20"}, {"id": "user21"}, {"id": "user22"}, {"id": "myaccount"}, {"id": "user23"}, {"id": "user24"}, {"id": "user25"}, {"id": "user26"}, {"id": "user27"}, {"id": "user28"}, {"id": "user29"}, {"id": "user30"}, {"id": "user31"}, {"id": "user32"}, {"id": "user33"}, {"id": "user34"}, {"id": "user35"}, {"id": "user36"}, {"id": "user37"}, {"id": "user38"}, {"id": "user39"}, {"id": "user40"}, {"id": "user41"}, {"id": "user42"}, {"id": "user43"}, {"id": "user44"}, {"id": "user45"}, {"id": "user46"}, {"id": "user47"}, {"id": "user48"}, {"id": "user49"}, {"id": "user50"}, {"id": "user51"}, {"id": "user52"}, {"id": "user53"}, {"id": "user54"}, {"id": "user55"}, {"id": "user56"}, {"id": "user57"}, {"id": "user58"}, {"id": "user59"}, {"id": "user60"}, {"id": "user61"}, {"id": "user62"}, {"id": "user63"}, {"id": "user64"}, {"id": "user65"}, {"id": "user66"}, {"id": "user67"}, {"id": "user68"}, {"id": "user69"}, {"id": "user70"}, {"id": "user71"}, {"id": "user72"}, {"id": "user73"}, {"id": "user74"}, {"id": "user75"}, {"id": "user76"}, {"id": "user77"}, {"id": "user78"}, {"id": "user79"}, {"id": "user80"}, {"id": "user81"}, {"id": "user82"}, {"id": "user83"}, {"id": "user84"}, {"id": "user85"}, {"id": "user86"}, {"id": "user87"}, {"id": "user88"}, {"id": "user89"}, {"id": "user90"}, {"id": "user91"}, {"id": "user92"}, {"id": "user93"}, {"id": "user94"}, {"id": "user95"}, {"id": "user96"}, {"id": "user97"}, {"id": "user98"}, {"id": "user99"}, {"id": "user100"}, {"id": "user101"}, {"id": "user102"}, {"id": "cuser103"}, {"id": "user104"}, {"id": "user105"}, {"id": "user106"}, {"id": "user107"}, {"id": "user108"}, {"id": "user109"}, {"id": "user110"}, {"id": "user111"}, {"id": "user112"}, {"id": "user113"}, {"id": "user114"}, {"id": "user115"}, {"id": "user116"}, {"id": "user117"}, {"id": "user118"}, {"id": "user119"}, {"id": "user120"}, {"id": "user121"}, {"id": "user122"}, {"id": "user123"}, {"id": "user124"}, {"id": "user125"}, {"id": "user126"}, {"id": "user127"}, {"id": "user128"}, {"id": "user129"}, {"id": "user130"}, {"id": "user131"}], "links": [{"source": 22, "target": 72, "weight": 1}, {"source": 22, "target": 65, "weight": 3}, {"source": 22, "target": 115, "weight": 1}, {"source": 22, "target": 56, "weight": 1}, {"source": 22, "target": 106, "weight": 1}, {"source": 22, "target": 124, "weight": 3}, {"source": 22, "target": 48, "weight": 1}, {"source": 22, "target": 101, "weight": 1}, {"source": 22, "target": 111, "weight": 1}, {"source": 22, "target": 64, "weight": 4}, {"source": 22, "target": 112, "weight": 1}, {"source": 22, "target": 71, "weight": 1}, {"source": 48, "target": 121, "weight": 1}, {"source": 48, "target": 114, "weight": 1}, {"source": 48, "target": 34, "weight": 1}, {"source": 48, "target": 28, "weight": 1}, {"source": 48, "target": 43, "weight": 1}, {"source": 48, "target": 52, "weight": 1}, {"source": 48, "target": 64, "weight": 1}, {"source": 56, "target": 101, "weight": 1}, {"source": 56, "target": 9, "weight": 1}, {"source": 56, "target": 50, "weight": 1}, {"source": 56, "target": 85, "weight": 1}, {"source": 64, "target": 51, "weight": 1}, {"source": 64, "target": 46, "weight": 1}, {"source": 64, "target": 104, "weight": 1}, {"source": 64, "target": 122, "weight": 1}, {"source": 64, "target": 20, "weight": 2}, {"source": 64, "target": 2, "weight": 1}, {"source": 64, "target": 23, "weight": 1}, {"source": 64, "target": 94, "weight": 1}, {"source": 64, "target": 60, "weight": 1}, {"source": 64, "target": 6, "weight": 1}, {"source": 64, "target": 89, "weight": 1}, {"source": 64, "target": 96, "weight": 1}, {"source": 64, "target": 131, "weight": 3}, {"source": 64, "target": 50, "weight": 1}, {"source": 65, "target": 73, "weight": 2}, {"source": 65, "target": 90, "weight": 1}, {"source": 65, "target": 11, "weight": 3}, {"source": 65, "target": 1, "weight": 3}, {"source": 65, "target": 29, "weight": 4}, {"source": 65, "target": 13, "weight": 1}, {"source": 65, "target": 83, "weight": 1}, {"source": 65, "target": 78, "weight": 1}, {"source": 65, "target": 88, "weight": 1}, {"source": 65, "target": 113, "weight": 2}, {"source": 65, "target": 63, "weight": 1}, {"source": 71, "target": 10, "weight": 1}, {"source": 72, "target": 98, "weight": 3}, {"source": 72, "target": 123, "weight": 1}, {"source": 72, "target": 91, "weight": 1}, {"source": 72, "target": 12, "weight": 1}, {"source": 72, "target": 107, "weight": 1}, {"source": 72, "target": 7, "weight": 2}, {"source": 72, "target": 36, "weight": 4}, {"source": 72, "target": 128, "weight": 4}, {"source": 72, "target": 129, "weight": 1}, {"source": 72, "target": 38, "weight": 2}, {"source": 101, "target": 69, "weight": 1}, {"source": 101, "target": 119, "weight": 1}, {"source": 101, "target": 82, "weight": 1}, {"source": 101, "target": 21, "weight": 1}, {"source": 101, "target": 116, "weight": 1}, {"source": 101, "target": 125, "weight": 1}, {"source": 101, "target": 4, "weight": 1}, {"source": 101, "target": 109, "weight": 1}, {"source": 101, "target": 64, "weight": 2}, {"source": 101, "target": 53, "weight": 1}, {"source": 101, "target": 130, "weight": 1}, {"source": 101, "target": 102, "weight": 1}, {"source": 101, "target": 32, "weight": 1}, {"source": 101, "target": 80, "weight": 1}, {"source": 101, "target": 62, "weight": 1}, {"source": 106, "target": 103, "weight": 1}, {"source": 106, "target": 84, "weight": 1}, {"source": 106, "target": 66, "weight": 1}, {"source": 106, "target": 67, "weight": 1}, {"source": 106, "target": 87, "weight": 1}, {"source": 106, "target": 15, "weight": 1}, {"source": 106, "target": 126, "weight": 2}, {"source": 106, "target": 74, "weight": 1}, {"source": 106, "target": 127, "weight": 1}, {"source": 106, "target": 42, "weight": 4}, {"source": 106, "target": 5, "weight": 1}, {"source": 106, "target": 77, "weight": 2}, {"source": 106, "target": 79, "weight": 1}, {"source": 106, "target": 118, "weight": 1}, {"source": 111, "target": 92, "weight": 1}, {"source": 111, "target": 54, "weight": 1}, {"source": 111, "target": 55, "weight": 1}, {"source": 111, "target": 95, "weight": 1}, {"source": 111, "target": 41, "weight": 2}, {"source": 111, "target": 3, "weight": 1}, {"source": 111, "target": 93, "weight": 3}, {"source": 111, "target": 76, "weight": 1}, {"source": 111, "target": 49, "weight": 1}, {"source": 111, "target": 99, "weight": 1}, {"source": 111, "target": 44, "weight": 1}, {"source": 111, "target": 61, "weight": 1}, {"source": 111, "target": 117, "weight": 2}, {"source": 111, "target": 81, "weight": 1}, {"source": 111, "target": 97, "weight": 1}, {"source": 112, "target": 26, "weight": 1}, {"source": 112, "target": 8, "weight": 1}, {"source": 112, "target": 39, "weight": 1}, {"source": 112, "target": 45, "weight": 1}, {"source": 112, "target": 57, "weight": 1}, {"source": 112, "target": 47, "weight": 1}, {"source": 112, "target": 108, "weight": 1}, {"source": 112, "target": 37, "weight": 5}, {"source": 112, "target": 17, "weight": 1}, {"source": 112, "target": 31, "weight": 1}, {"source": 112, "target": 131, "weight": 4}, {"source": 112, "target": 94, "weight": 1}, {"source": 115, "target": 18, "weight": 3}, {"source": 115, "target": 19, "weight": 1}, {"source": 115, "target": 33, "weight": 1}, {"source": 115, "target": 30, "weight": 1}, {"source": 115, "target": 110, "weight": 1}, {"source": 115, "target": 70, "weight": 1}, {"source": 124, "target": 120, "weight": 1}, {"source": 124, "target": 0, "weight": 1}, {"source": 124, "target": 40, "weight": 1}, {"source": 124, "target": 105, "weight": 1}, {"source": 124, "target": 35, "weight": 1}, {"source": 124, "target": 27, "weight": 1}, {"source": 124, "target": 86, "weight": 3}, {"source": 124, "target": 68, "weight": 1}, {"source": 124, "target": 14, "weight": 1}, {"source": 124, "target": 58, "weight": 1}, {"source": 124, "target": 24, "weight": 1}, {"source": 124, "target": 25, "weight": 1}, {"source": 124, "target": 100, "weight": 1}, {"source": 124, "target": 59, "weight": 1}, {"source": 124, "target": 75, "weight": 2}, {"source": 124, "target": 16, "weight": 2}], "multigraph": false}

And the code that currently generates the visualization without labels is:

var width = 600,
    height = 500;

var color = d3.scale.category20();

var force = d3.layout.force()
    .charge(-120)
    .linkDistance(30)
    .size([width, height]);

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

d3.json("static/reblognetwork_{{ session_username }}.json", function(error, graph) {
  force
      .nodes(graph.nodes)
      .links(graph.links)
      .start();

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

  var node = svg.selectAll(".node")
      .data(graph.nodes)
    .enter().append("circle")
      .attr("class", "node")
      .attr("r", 5)
      .style("fill", function(d) { return color(d.group); })
      .call(force.drag);

  node.append("text")
    // .attr("x", 12)
    // .attr("dy", ".35em")
    .text(function(d) { return d.id; });

  // var text = svg.append("g").selectAll("text")
  //   .data(graph.nodes)
  // .enter().append("text")
  //   // .attr("x", ".5em")
  //   // .attr("y", ".31em")
  //   .text(function(d) { return d.id; });

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

  });
});

You can see some of the (commented out) code that I tried without success in there as well, based on previous questions like D3.js, force-graph, cannot display text/label of nodes

Any help appreciated!


SOLUTION: The final code below worked.

<script src="http://d3js.org/d3.v3.min.js"></script>

<script>
var width = 500, height = 450;

var svg = d3.select("div").append("svg");

var color = d3.scale.category20();

var force = d3.layout.force()
    .charge(-120)
    .linkDistance(30)
    .size([width, height]);

svg.attr("width", width)
    .attr("height", height);

d3.json("static/reblognetwork_{{ session_username }}.json", function(error, graph) {
  force
      .nodes(graph.nodes)
      .links(graph.links)
      .start();

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

  var node = svg.selectAll("g.node")
      .data(graph.nodes);
  var nodeEnter = node.enter()
      .append("g")
      .attr("class", "node")
      .call(force.drag);
  nodeEnter.append("circle")
      .attr("r", 5)
      .style("fill", function(d) { return color(d.group); })
      .call(force.drag);

  nodeEnter.append("text")
     .attr("dx", 5)
     .style("text-anchor", "start")
     .text(function(d) { return d.id; });

  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("transform", function(d) {
      return "translate(" + [d.x, d.y] + ")";
    });
  });
};


</script>

<div id="networkgraph"></div>
Was it helpful?

Solution 2

When adding multiple elements for a node, it is usually better to have a g element whose position you set and which contains all the other elements, just like in the first example you've linked to. In your case, this could look like the following.

var node = svg.selectAll("g.node")
  .data(graph.nodes);
var nodeEnter = node.enter()
  .append("g")
  .attr("class", "node")
  .call(force.drag);
nodeEnter.append("circle")
  .attr("r", 5)
  .style("fill", function(d) { return color(d.group); })
  .call(force.drag);
nodeEnter.append("text")
 .attr("dx", 5)
 .style("text-anchor", "start")
 .text(function(d) { return d.id; });

Complete demo here.

OTHER TIPS

I'm not great at troubleshooting, but I plugged your code into a JSFiddle, and the nodes appear to render (not the links though).

Maybe it has to do with the data you are passing in? In this case, I plugged in the Les Miserables data to test it out (so the only change I made was stripping out the d3.json call and sizing down the node text).

http://jsfiddle.net/JA4kD/1/

var width = 500,
    height = 450;

var svg = d3.select("#networkgraph").append("svg");

var color = d3.scale.category20();

var force = d3.layout.force()
    .charge(-120)
    .linkDistance(30)
    .size([width, height]);

svg.attr("width", width)
    .attr("height", height);

force.nodes(graph.nodes)
    .links(graph.links)
    .start();

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

var node = svg.selectAll("g.node")
    .data(graph.nodes);

var nodeEnter = node.enter()
    .append("g")
    .attr("class", "node")
    .call(force.drag);

nodeEnter.append("circle")
    .attr("r", 5)
    .style("fill", function (d) { return color(d.group); })
    .call(force.drag);

nodeEnter.append("text")
    .attr("dx", 5)
    .style("text-anchor", "start")
    .style("font-size", "8px")
    .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("transform", function (d) { return "translate(" + [d.x, d.y] + ")"; });
});
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top