Is there a good example of text appended to the "line" or "edge" in a force directed graph?

152 views
Skip to first unread message

Guerino1

unread,
Jun 6, 2012, 10:21:39 PM6/6/12
to d3...@googlegroups.com
Hi,

Does anyone know where I can find a good example that shows how to add text to relationship lines in force directed graphs?

I'm building a "Force Directed Radial Graph" example to learn about force layouts.  I've been able to successfully and append text to the nodes but I can't seem to get it to work with lines.

When I look at the line in the debugger, it looks as follows...

<line class="link" style="stroke: #cccccc; " x1="482.6473837618528" y1="332.97976499758056" x2="289.4523443346812" y2="496.9334567006513">
  <g>
    <text font-family="Arial, Helvetica, sans-serif" x="15" dy=".35em">Relationship 1</text>
  </g>
</line>

Any examples or feedback is greatly appreciated.

Thanks,

Frank




Chris Michael

unread,
Jun 7, 2012, 12:03:01 PM6/7/12
to d3...@googlegroups.com
Hi Frank

I don't know if this is a good example or not, but it was something that I also needed to do, and I came up with the following:

var linkText = vis.selectAll(".gLink").append("svg:text")
.data(data.links).text(function(d) {return d.name}).attr("x", function(d) {return (d.target.x - d.source.x)})
.attr("y", function(d) {return (d.target.y - d.source.y)}).attr("stroke", "black")

....

oModules.ConsultationModule.force.on("tick", function () 
{
linkText.attr("x", function(d) 
{
{
tx=d.target.x; sx=d.source.x; x=sx+((tx-sx)/2); return x-35
}
else
{
if (d.selfrelationship==2) 
{
tx=d.target.x; sx=d.source.x; x=sx+((tx-sx)/2); return x+100
}
else
{
tx=d.target.x; sx=d.source.x; x=sx+((tx-sx)/2); return x-100
}
}
})
.attr("y", function(d) { ty=d.target.y; sy=d.source.y; y=sy+((ty-sy)/2); return y })
}

I sometimes have links that connect a node to itself do some of the code above will probably be superflous for you needs. 

I am sure there are better ways of doing it, but it did work.

Chris

Frank Guerino

unread,
Jun 7, 2012, 4:31:15 PM6/7/12
to d3...@googlegroups.com
Hi Chris,

Thanks for the reply.  I appreciate the help.

May I ask if you have a gist post that shows the working example?  Without it, I tried to follow your code to the best of my ability and applied it to my own "Force Directed Radial Graph" example.  However, I have the problem that the link text is not rendering.

When I look at the HTML code, it looks like I'm chaining elements together, properly, (i.e. <line><g><text></text></g></line>) but the x and y values always seem to evaluate to "0".  The elements look like:

<line class="link" style="stroke: #cccccc; " x1="470.1520753498232" y1="352.11662276345976" x2="480.50919512510313" y2="99.16735977544016">
  <g>
    <text font-family="Arial, Helvetica, sans-serif" x="0" y="0" stroke="black">Relationship 1</text>
  </g>
</line>

Any help is greatly appreciated!

BTW, I get the circular relationship concept.  I think your approach is very cool.  I'm not dealing w/ circular relationships just yet, but I will be soon enough.  So, thanks for the code.

My Best,

Frank

Chris Michael

unread,
Jun 7, 2012, 4:56:57 PM6/7/12
to d3...@googlegroups.com
Hi Frank

You cannot put a text tag inside a line tag. (Well you can, but it won't render as it isn't valid svg ;-)  )

My links look like this:

<g class="gLink"><polyline class="link" stroke="black" style="stroke-width: 2px; " fill="none" points="286,221 210,37 "></polyline><text x="212.59601757533875" y="128.94480166889105" stroke="black">confirm</text></g>

I use the polyline to allow for circular relationships, but it works fine for non circular relationships too.

If you want to send a snippet of code I am happy to have a quick look to see if I can spot the problem

I have just looked at my web app, and seen that I actually use two different methods in two different places.

The code above is how I first did it, then later, I did away with the <g> element and just had the texts as standalone elements.

The code I used in the latter case was even simpler:

var nodeText = vis.selectAll("text").data(data.nodes)
nodeText.enter().append("text")
.text(function(d) {return d.name})
.call(oModules.ConsultationModule.force.drag)

and


oModules.ConsultationModule.force.on("tick", function () 
{
link.attr("points", function (d) { return renderLinks(d)})
node.attr("x", function (d) { return d.x-25; }).attr("y", function (d) { return d.y-10; })
nodeText.attr("x", function (d) { return d.x-25; }).attr("y", function (d) { return d.y; })
})

in case you want the entire function it is below, although the lines above are the important bit.

function renderSSM(json)
{
try
{
data=json
oModules.ConsultationModule.force.nodes(data.nodes)            
oModules.ConsultationModule.force.links(data.links)            
oModules.ConsultationModule.force.start();
var vis = oModules.ConsultationModule.vis
var fill = d3.scale.category20(); //twenty colors

var link = vis.selectAll("polyline").data(data.links)
link.enter().append("polyline").attr("class", "link")
.attr("stroke", "black").style("stroke-width", "2").attr("fill","none").attr("points", function (d) { return renderLinks(d)})
var node = vis.selectAll("rect").data(data.nodes)
node.enter().append("rect")
.attr("width", function(d) {var w=120; if (d.name.indexOf("?")>-1) w=70; return w;}).attr("height", 20)
.attr("rx", 6).attr("ry", 6).attr("stroke","1")
.style("fill", function (d) { return fill(d.group); })
.call(oModules.ConsultationModule.force.drag) ;
var nodeText = vis.selectAll("text").data(data.nodes)
nodeText.enter().append("text")
.text(function(d) {return d.name})
.call(oModules.ConsultationModule.force.drag)
oModules.ConsultationModule.doResize()
oModules.ConsultationModule.force.on("tick", function () 
{
link.attr("points", function (d) { return renderLinks(d)})
node.attr("x", function (d) { return d.x-25; }).attr("y", function (d) { return d.y-10; })
nodeText.attr("x", function (d) { return d.x-25; }).attr("y", function (d) { return d.y; })
});
}

Chris Michael

unread,
Jun 7, 2012, 5:09:44 PM6/7/12
to d3...@googlegroups.com
Ooops, I just checked my app again, and spotted that at the moment, my second set of text on links are not working (the perils of iterative development), so for now, it might be worth looking at the first block of code I sent. That said, I think the second block is close so it might provide some hints. When I fix it, I shall put it on here.

On 7 June 2012 21:56, Chris Michael <mech...@googlemail.com> wrote:
Hi Franki

Frank Guerino

unread,
Jun 7, 2012, 11:04:21 PM6/7/12
to d3...@googlegroups.com
Hi Chris,

Thanks again for the help.

I applied your methodology to mine and (at least) I can see the link/relationship text on the screen.  I've now run into another problem that I can't seem to figure out.  The example and the code are located at "Force Directed Radial Graph" (or "http://bl.ocks.org/2879486").

Interestingly, when I try to calculate the X and Y position of the text, using the code:

.attr("x", function(d) {return (d.target.x - d.source.x); })
.attr("y", function(d) {return (d.target.y - d.source.y); })

I get calculated values of X = 0 and Y = 0.  In order to debug, I added the following code to see what d.target and d.source X and Y values were...

.attr("x2", function(d) { return d.target.x; })
.attr("y2", function(d) { return d.target.y; })
.attr("x1", function(d) { return d.source.x; })
.attr("y1", function(d) { return d.source.y; })

Interestingly, I found that d.target's X and Y have values that are equal to d.source's X and Y values.  The HTML code looks like:

  <g class="gLink">
    <line class="link" style="stroke: #cccccc; "
      x1="480.3358888120077" y1="356.5618368496099"
      x2="238.03250827086453" y2="283.17715204785117">
    </line>
    <text font-family="Arial, Helvetica, sans-serif"
      x="0" y="0"
      x2="143.49395625293255" y2="454.93095056153834"
      x1="143.49395625293255" y1="454.93095056153834" stroke="black" dy="3em">
        Relationship 1
    </text>
  </g>

For some reason, when I use .data(force.links()), the links don't seem to have the right X and Y data in them for the source and target nodes.

At the moment, I'm stumped.

Frank

Chris Michael

unread,
Jun 8, 2012, 3:08:35 AM6/8/12
to d3...@googlegroups.com
Hi Frank

This sounds like it a could be a problem with the data binding, I seem to recall I initially ran into something similar. If you want to post the code where you do the binding to the links and nodes, I may be able to spot something.
kr
Chris

Frank Guerino

unread,
Jun 8, 2012, 6:55:03 AM6/8/12
to d3...@googlegroups.com
Hi Chris,

The whole file is pretty short and I've pasted the code, below.  The interesting thing is that the binding seems to work fine for drawing the lines.  However, it's as if the source's and/or target's X and Y values somehow get corrupted after I draw the lines, or (and it's very possible) I'm incorrectly accessing the data.

Thx,

Frank

<!DOCTYPE html>
<html>
  <head>
    <title>My Force Directed Radial Graph</title>
    <script type="text/javascript" src="http://mbostock.github.com/d3/d3.js"></script>
  </head>
  <body>

    <div id="chart"></div>

    <script type="text/javascript">

      var focalNode = "N1";

      var nodeSet = [
        {id: "N1", name: "Node 1", type: "Type 1", hlink: "http://www.if4it.com"},
        {id: "N2", name: "Node 2", type: "Type 3", hlink: "http://www.if4it.com/glossary.html"},
        {id: "N3", name: "Node 3", type: "Type 4", hlink: "http://www.if4it.com/resources.html"},
        {id: "N4", name: "Node 4", type: "Type 5", hlink: "http://www.if4it.com/taxonomy.html"},
        {id: "N5", name: "Node 5", type: "Type 1", hlink: "http://www.if4it.com/disciplines.html"},
        {id: "N6", name: "Node 6", type: "Type 2", hlink: "http://www.if4it.com"},
        {id: "N7", name: "Node 7", type: "Type 3", hlink: "http://www.if4it.com/glossary.html"},
        {id: "N8", name: "Node 8", type: "Type 4", hlink: "http://www.if4it.com/resources.html"},
        {id: "N9", name: "Node 9", type: "Type 5", hlink: "http://www.if4it.com/taxonomy.html"},
        {id: "N10", name: "Node 10", type: "Type 1", hlink: "http://www.if4it.com/disciplines.html"},
        {id: "N11", name: "Node 11", type: "Type 2", hlink: "http://www.if4it.com"},
        {id: "N12", name: "Node 12", type: "Type 3", hlink: "http://www.if4it.com/glossary.html"},
        {id: "N13", name: "Node 13", type: "Type 4", hlink: "http://www.if4it.com/resources.html"},
        {id: "N14", name: "Node 14", type: "Type 5", hlink: "http://www.if4it.com/taxonomy.html"},
        {id: "N15", name: "Node 15", type: "Type 1", hlink: "http://www.if4it.com/disciplines.html"},
        {id: "N16", name: "Node 16", type: "Type 3", hlink: "http://www.if4it.com"},
        {id: "N17", name: "Node 17", type: "Type 1", hlink: "http://www.if4it.com/glossary.html"},
        {id: "N18", name: "Node 18", type: "Type 1", hlink: "http://www.if4it.com/glossary.html"},
        {id: "N19", name: "Node 19", type: "Type 4", hlink: "http://www.if4it.com/glossary.html"},
        {id: "N20", name: "Node 20", type: "Type 4", hlink: "http://www.if4it.com/glossary.html"},
        {id: "N21", name: "Node 21", type: "Type 4", hlink: "http://www.if4it.com/glossary.html"},
        {id: "N22", name: "Node 22", type: "Type 1", hlink: "http://www.if4it.com/glossary.html"},
        {id: "N23", name: "Node 23", type: "Type 5", hlink: "http://www.if4it.com/glossary.html"},
        {id: "N24", name: "Node 24", type: "Type 5", hlink: "http://www.if4it.com/glossary.html"}
      ];

      var linkSet = [
        {sourceId: "N1", linkName: "Relationship 1", targetId: "N2"},
        {sourceId: "N3", linkName: "Relationship 2", targetId: "N1"},
        {sourceId: "N4", linkName: "Relationship 3", targetId: "N1"},
        {sourceId: "N1", linkName: "Relationship 4", targetId: "N5"},
        {sourceId: "N6", linkName: "Relationship 5", targetId: "N1"},
        {sourceId: "N1", linkName: "Relationship 6", targetId: "N7"},
        {sourceId: "N1", linkName: "Relationship 7", targetId: "N8"},
        {sourceId: "N9", linkName: "Relationship 8", targetId: "N1"},
        {sourceId: "N1", linkName: "Relationship 9", targetId: "N10"},
        {sourceId: "N11", linkName: "Relationship 10", targetId: "N1"},
        {sourceId: "N1", linkName: "Relationship 11", targetId: "N12"},
        {sourceId: "N13", linkName: "Relationship 12", targetId: "N1"},
        {sourceId: "N1", linkName: "Relationship 13", targetId: "N14"},
        {sourceId: "N1", linkName: "Relationship 14", targetId: "N15"},
        {sourceId: "N16", linkName: "Relationship 15", targetId: "N1"},
        {sourceId: "N17", linkName: "Relationship 16", targetId: "N1"},
        {sourceId: "N18", linkName: "Relationship 17", targetId: "N1"},
        {sourceId: "N19", linkName: "Relationship 18", targetId: "N1"},
        {sourceId: "N20", linkName: "Relationship 19", targetId: "N1"},
        {sourceId: "N21", linkName: "Relationship 20", targetId: "N1"},
        {sourceId: "N22", linkName: "Relationship 21", targetId: "N1"},
        {sourceId: "N1", linkName: "Relationship 22", targetId: "N23"},
        {sourceId: "N1", linkName: "Relationship 23", targetId: "N24"}
      ];

    </script>

    <script type="text/javascript">

      var width = 960,
          height = 700,
centerNodeSize = 50;
          nodeSize = 10;
          colorScale = d3.scale.category20();

      var svgCanvas = d3.select("#chart").append("svg:svg")
          .attr("width", width)
          .attr("height", height);

      var node_hash = [];
      var type_hash = [];

      nodeSet.forEach(function(d, i) {
        node_hash[d.id] = d;
        type_hash[d.type] = d.type;
      });
      
      linkSet.forEach(function(d, i) {
        d.source = node_hash[d.sourceId];
        d.target = node_hash[d.targetId];
      });

      var nodes = self.nodes = nodeSet;
      var links = self.links = linkSet;

      var force = d3.layout.force()
          .charge(-1000)
          .nodes(nodes)
          .links(links)
          .size([width, height])
          .linkDistance( function(d) { if (width < height) { return width*1/3; } else { return height*1/3 } } ) // Controls edge length
          .on("tick", tick)
          .start();

      // Draw lines for Links between Nodes
      var link = svgCanvas.selectAll(".link")
          .data(force.links())
        .enter().append("g")
.attr("class", "gLink")
        .append("line")
          .attr("class", "link")
          .style("stroke", "#ccc")
          .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; });

      // Create Nodes
      var node = svgCanvas.selectAll(".node")
          .data(force.nodes())
        .enter().append("g")
          .attr("class", "node")
          .on("mouseover", mouseover)
          .on("mouseout", mouseout)
          .call(force.drag);

      // Append circles to Nodes
      node.append("circle")
          .attr("x", function(d) { return d.x; })
          .attr("y", function(d) { return d.y; })
          .attr("r", function(d) { if (d.id==focalNode) { return centerNodeSize; } else { return nodeSize; } } ) // Node radius
          .style("fill", "White") // Make the nodes hollow looking
          .style("stroke-width", 5) // Give the node strokes some thickness
          .style("stroke", function(d, i) { colorVal = colorScale(i); return colorVal; } ) // Node stroke colors
          .call(force.drag);

      // Append text to Nodes
      node.append("a")
          .attr("xlink:href", function(d) { return d.hlink; })
.append("text")

      //node.append("text")
          .attr("x", function(d) { if (d.id==focalNode) { return 0; } else {return 20;} } )
          .attr("y", function(d) { if (d.id==focalNode) { return 0; } else {return -10;} } )
.attr("text-anchor", function(d) { if (d.id==focalNode) {return "middle";} else {return "start";} })
.attr("font-family", "Arial, Helvetica, sans-serif")
          .attr("fill", "Blue")
          .attr("dy", ".35em")
          .text(function(d) { return d.name; });

      // Append text to Link edges
      var linkText = svgCanvas.selectAll(".gLink")
          .data(force.links())
        .append("svg:text")
.attr("font-family", "Arial, Helvetica, sans-serif")
          .attr("x", function(d) {return (d.target.x - d.source.x); })
.attr("y", function(d) {return (d.target.y - d.source.y); })
.attr("x2", function(d) { return d.target.x; })
.attr("y2", function(d) { return d.target.y; })
.attr("x1", function(d) { return d.source.x; })
.attr("y1", function(d) { return d.source.y; })
.attr("stroke", "black")
          //.attr("dy", ".35em")
          .attr("dy", "3em")
          .text(function(d) { return d.linkName; });

      function tick() {
        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 mouseover() {
        d3.select(this).select("circle").transition()
            .duration(250)
.attr("r", function(d,i) { if(d.id==focalNode) {return 75;} else {return 15;} } );
d3.select(this).select("text").transition()
            .duration(250)
.style("font", "bold 1.25em Arial")
.attr("fill", "Blue");
      }

      function mouseout() {
        d3.select(this).select("circle").transition()
            .duration(250)
.attr("r", function(d,i) { if(d.id==focalNode) {return centerNodeSize;} else {return nodeSize;} } );
d3.select(this).select("text").transition()
            .duration(250)
.style("font", "normal 1em Arial")
.attr("fill", "Blue");
      }


    </script>

  </body>

</html>

Tore

unread,
Jun 8, 2012, 10:44:27 AM6/8/12
to d3...@googlegroups.com
Hi Frank,

I think the reason you don't see the x and y values you were expecting is that the linkText position is not being updated in the tick function. Therefore, your text remains stuck in the initial position. Only the link and node selections atributes are being updated. In your code, the link selection refers to the selection of line elements. The lineText elements are not related to those. 

Yes, you did wrap the line and text together in a g element. But updating the line element's coordinates in the tick function will not change the position of the text element.

So either update the g element, like you do for the nodes, or keep the text elements completely separate and update it's own coordinates.


As a side note; there are a few parts in your code a bit unclear:

var link = svgCanvas.selectAll(".link")
    .data(force.links())
  .enter().append("g")
    .attr("class", "gLink")
  .append("line")
    .attr("class", "link")
    .style("stroke", "#ccc")

I guess you meant selectAll(".gLink")?

So after this the data will be bound to the g elements. The append("line") will make the line element a child of the g element. The append method will propagate data from parent to child; this means that the data is also bound to the line elements. Just to be clear, the link variable now refers to the selection of line elements, and not the g elements.

Later you select the g elements and do another data join. However, this is the same data as before so this isn't necessary:

var linkText = svgCanvas.selectAll(".gLink")
    .data(force.links())
  .append("svg:text")    

I would say the way you handle node looks cleaner. The node variable refers to the selection of g elements. Then you append a circle to each g element and an anchor element to each g element.

Tore

Frank Guerino

unread,
Jun 9, 2012, 10:27:54 AM6/9/12
to d3...@googlegroups.com
Hi Tore,

You were right and it now works (see "Force Directed Radial Graph").

Aside from the Chrome mouseover issue, it seems to be working.

Does anyone have any ideas on how to possibly get the relationship text (i.e. "linkText") to follow the angle of the line?

Thanks, again, for all the help.

Frank

Chris Michael

unread,
Jun 9, 2012, 10:39:56 AM6/9/12
to d3...@googlegroups.com
Hi Frank

I want to do that too, but haven't got round to it yet.

This is where I am looking at so far. I think you will want to use a text path.

http://www.w3schools.com/svg/svg_text.asp 

kr
Chris

Tore

unread,
Jun 9, 2012, 10:57:20 AM6/9/12
to d3...@googlegroups.com
The font-size interpolation issue in Chrome doesn't seem to occur when the size is specified in px instead of em.

Frank Guerino

unread,
Jun 9, 2012, 3:59:30 PM6/9/12
to d3...@googlegroups.com
Thanks Tore!  You're right.  I changed it to px and it seems to work in all browsers, now.

Frank
Reply all
Reply to author
Forward
0 new messages