1
votes

I want to make a force graph with permanently visible text on the nodes and text on the links.

This snippet provides labels on the nodes.

The 2nd snippet from an answer to Show tool-tip on links of force directed graph in d3js provides mouseover labels on links and nodes.

I'm currently trying to extend the second to make the mouseover node labels permanent.

var width = 400;
var height = 125;
var margin = 20;
var pad = margin / 2;

var graph = {  "nodes":[ { "name": "A"}, { "name": "B"}] };
drawGraph(graph);

function drawGraph(graph) {
  var svg = d3.select("#force").append("svg")
    .attr("width", width)
    .attr("height", height);

  // create an area within svg for plotting graph
  var plot = svg.append("g")
    .attr("id", "plot")
    .attr("transform", "translate(" + pad + ", " + pad + ")");

  var layout = d3.layout.force()
    .size([width - margin, height - margin])
    .charge(-120)
    .nodes(graph.nodes)
    .start();

  drawNodes(graph.nodes);

  // add ability to drag and update layout
  d3.selectAll(".node").call(layout.drag);

  layout.on("tick", function() {
    d3.selectAll(".node")
      .attr("cx", d =>  { return d.x; })
      .attr("cy", d =>  { return d.y; });
      
  });
}

// Draws nodes on plot
function drawNodes(nodes) {

  d3.select("#plot").selectAll(".node")
    .data(nodes)
    .enter()
    .append("circle")
    .attr("class", "node")
    .attr("id", (d, i) => { return d.name;  })
    .attr("cx", (d, i) => { return d.x;  })
    .attr("cy", (d, i) => { return d.y;  })
    .attr("r",  4)
    .style("fill", "#EE77b4")
    .on("mouseover", function(d, i) {

      var x = d3.mouse(this)[0];
      var y = d3.mouse(this)[1];
      var tooltip = d3.select("#plot")
        .append("text")
        .text(d.name)
        .attr("x", x)
        .attr("y", y)
        .attr("id", "tooltip");

    })
    .on("mouseout", function(d, i) {
      d3.select("#tooltip").remove();
    });
}
body {
  font-family: 'Source Sans Pro', sans-serif;
  font-weight: 300;
}
b {
  font-weight: 900;
}
.outline {
  fill: none;
  stroke: #888888;
  stroke-width: 1px;
}
#tooltip {
  font-size: 10pt;
  font-weight: 900;
  fill: #000000;
  stroke: #ffffff;
  stroke-width: 0.25px;
}
.node {
  stroke: #ffffff;
  stroke-weight: 1px;
}
.highlight {
  stroke: red;
  stroke-weight: 4px;
  stroke-opacity: 1.0;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<div align="center" id="force"></div>

I've tried replacing the mouseover function with:

.each( function(d, i) {
      var x = d.x; //d3.mouse(this)[0];
      var y = d.y; //d3.mouse(this)[1];
      var tooltip = d3.select("#plot")
        .append("text")
        .text(d.name)
        .attr("x", x)
    .attr("y", y)
    .attr("id", "tooltip");

    })

but now the labels don't move so I added

d3.selectAll("text").attr( "x", d => { return d.x; })
                    .attr( "y", d => { return d.y; });

in layout.on("tick", function() ...

But now it's all in one place doesn't move and I get TypeError: d is undefined

1

1 Answers

2
votes

Rewrite your code this way (pay attention on the comments):

  layout.on("tick", function() {
    tooltips // here we set new position for tooltips on every tick
      .attr("x", (d, i) => { return d.x;  })
      .attr("y", (d, i) => { return d.y;  });

    d3.selectAll(".node")
      .attr("cx", d =>  { return d.x; })
      .attr("cy", d =>  { return d.y; });

  });

...

function drawNodes(nodes) {
  tooltips = d3.select("#plot").selectAll(".node")
    .data(nodes)
    .enter()
    .append("circle")
    .attr("class", "node")
    .attr("id", (d, i) => { return d.name;  })
    .attr("cx", (d, i) => { return d.x;  })
    .attr("cy", (d, i) => { return d.y;  })
    .attr("r",  4)
    .style("fill", "#EE77b4")
    .select(function() { return this.parentNode }) // returns to parent node
    .append('text') // append svg-text elements for tooltip
    .data(nodes)
    .text(function(d) { return d.name; }) // set text
    .attr("x", (d, i) => { return d.x;  }) // set initial x position
    .attr("y", (d, i) => { return d.y;  }) // set initial y position
    .attr("id", function(d,i) { return "tooltip-" + i; }) // set unique id
    .attr("class", "d3-tooltip");
}

Working demo:

var width = 400;
var height = 125;
var margin = 20;
var pad = margin / 2;
var tooltips = null;

var graph = {  "nodes":[ { "name": "A"}, { "name": "B"}] };
drawGraph(graph);

function drawGraph(graph) {
  var svg = d3.select("#force").append("svg")
    .attr("width", width)
    .attr("height", height);

  // create an area within svg for plotting graph
  var plot = svg.append("g")
    .attr("id", "plot")
    .attr("transform", "translate(" + pad + ", " + pad + ")");

  var layout = d3.layout.force()
    .size([width - margin, height - margin])
    .charge(-120)
    .nodes(graph.nodes)
    .start();

  drawNodes(graph.nodes);

  // add ability to drag and update layout
  d3.selectAll(".node").call(layout.drag);

  layout.on("tick", function() {
    tooltips
      .attr("x", (d, i) => { return d.x;  })
      .attr("y", (d, i) => { return d.y;  });
    
    d3.selectAll(".node")
      .attr("cx", d =>  { return d.x; })
      .attr("cy", d =>  { return d.y; });
      
  });
}

// Draws nodes on plot
function drawNodes(nodes) {
  tooltips = d3.select("#plot").selectAll(".node")
    .data(nodes)
    .enter()
    .append("circle")
    .attr("class", "node")
    .attr("id", (d, i) => { return d.name;  })
    .attr("cx", (d, i) => { return d.x;  })
    .attr("cy", (d, i) => { return d.y;  })
    .attr("r",  4)
    .style("fill", "#EE77b4")
    .select(function() { return this.parentNode })
    .append('text')
    .data(nodes)
    .text(function(d) { return d.name; })
    .attr("x", (d, i) => { return d.x;  })
    .attr("y", (d, i) => { return d.y;  })
    .attr("class", "d3-tooltip")
    .attr("id", function(d,i) { return "tooltip-" + i; });
}
body {
  font-family: 'Source Sans Pro', sans-serif;
  font-weight: 300;
}
b {
  font-weight: 900;
}
.outline {
  fill: none;
  stroke: #888888;
  stroke-width: 1px;
}
.d3-tooltip {
  font-size: 20pt;
  font-family: 'Comic Sans MS';
  font-weight: 900;
  fill: #000000;
  stroke: #ffffff;
  stroke-width: 0.25px;
}
.node {
  stroke: #ffffff;
  stroke-weight: 1px;
}
.highlight {
  stroke: red;
  stroke-weight: 4px;
  stroke-opacity: 1.0;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<div align="center" id="force"></div>