center point of a path

1,179 views
Skip to first unread message

TommyG

unread,
Jun 24, 2014, 4:22:37 PM6/24/14
to d3...@googlegroups.com
I'm creating a graph with a tree layout and a diagonal projection.  I've got an initial graph displaying properly.  I have this crazy requirement, however, to display a text node of the midpoint of the path linking each leaf.  It is my understanding the path is a bezier curve. Is it possible to put a text node over the midpoint of each path? I'm a bit lost on how to do this. The images below show the positioning of the text nodes as numbers. Any help is greatly appreciated.


nick

unread,
Jun 25, 2014, 9:56:31 AM6/25/14
to d3...@googlegroups.com
There is almost certainly a better option than this, but: to fake it, you can use <element>.getBBox() to get that element's size offset from its parent.

Here is a drop-in you can run against http://bl.ocks.org/mbostock/raw/4339083 and hopefully adapt to your purposes until something better comes along.

var renderLabels = function(){
  var labelPositions = [];

  d3.selectAll("path.link")
    .each(function(d, i){
      var bounds = this.getBBox();
      labelPositions[i] = {
        x: bounds.x + bounds.width / 2,
        y: bounds.y + bounds.height / 2
      };
    });
   
  var label = d3.select("svg g")
    .selectAll("text.linklabel")
    .data(labelPositions);

  label.exit().remove();
 
  label.enter()
    .append("text")
    .classed("linklabel", true)
    .style({fill: "red"});
 
  label.text(function(d, i){ return i; })
    .attr({
      x: function(d){return d.x;},
      y: function(d){return d.y;}
    });
};

renderLabels();

Cheers!

Frank Guerino

unread,
Jun 25, 2014, 2:07:35 PM6/25/14
to d3...@googlegroups.com
Hi Nick,

I believe your code is applicable to a visualization I'm working on.  I'm trying to build a D3 Tree that has link text, as well as node text, and I need to position the link text at the center of each link path, regardless of whether that path is straight or curved.  The code is at: http://bl.ocks.org/Guerino1/raw/ed80661daf8e5fa89b85/

I don't understand transitions and transforms enough to be clear about how I would strap in your code.  Where would I call it from?

Thanks,

Frank




--
You received this message because you are subscribed to the Google Groups "d3-js" group.
To unsubscribe from this group and stop receiving emails from it, send an email to d3-js+un...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Adam

unread,
Jun 26, 2014, 12:08:03 AM6/26/14
to d3...@googlegroups.com
Try using getTotalLength and getPointAtLength

Dario Villanueva

unread,
Jun 26, 2014, 5:35:36 AM6/26/14
to d3...@googlegroups.com
+1 to Adam's suggestion.

nick

unread,
Jun 26, 2014, 1:04:26 PM6/26/14
to d3...@googlegroups.com
Ooh, that's gorgeous. +1 indeed!

@guerino1: it looks like you had the stuff already in place to do the positions with transition. However, you will need to run getTotalLength and getPointAtLength at each "tick" of the transition, for each label, which will have to have a pointer to its path. For this, you'll need to create an attrTween. In the example adam posted, almost exactly this is being done, but with the global t rather than the i that you need.

function centerLabel(d, i, a) {
  var path = linkPaths[0][i];
  return function(t) {
    var p = path.getPointAtLength(.5 * path.getTotalLength());
    return "translate(" + p.x + "," + p.y + ")";
  };
}

// Transition nodes to their new position.
var linkTextUpdate = linkTextItems.transition()
  .duration(duration)
  .attrTween("transform", centerLabel);

Also: rather than foreignObject for said labels, you may want to use https://github.com/vijithassar/d3textwrap
 
For grins, here is my code with getPointAtLength approach: the result is much more satisfying that than getBBox.
var renderLabels = function(){
  var labelPositions = [];

  d3.selectAll("path.link")
    .each(function(d, i){
      var p = this.getPointAtLength(.5 * this.getTotalLength());
     
      labelPositions[i] = p;

    });
  
  var label = d3.select("svg g")
    .selectAll("text.linklabel")
    .data(labelPositions);

  label.exit().remove();
 
  label.enter()
    .append("text")
    .classed("linklabel", true)
    .style({fill: "red"});
 
  label.text(function(d, i){ return i; })
    .attr({
      x: function(d){return d.x;},
      y: function(d){return d.y;},
      dy: ".35em",
      "text-anchor": "middle"
    });
};

renderLabels();

TommyG

unread,
Jun 27, 2014, 10:11:04 AM6/27/14
to d3...@googlegroups.com

Thanks everybody. Your comments have been most helpful.

TommyG

unread,
Jun 27, 2014, 10:13:33 AM6/27/14
to d3...@googlegroups.com
I think this may be my problem.  I've got the text label centered for new paths, but the old paths don't get updated until the next time that I provide new data.  I noticed that if I put a long setTimeOut around my renderLabel() method, that my label does move to the middle of the path.  

Harry Voorhees

unread,
Jul 21, 2014, 12:35:52 PM7/21/14
to d3...@googlegroups.com
I had a similar problem, which I only solved after finding Nick's advice to use attrTween() to update the position. Simple demo here:

Harry

On Thursday, June 26, 2014 1:04:26 PM UTC-4, nick wrote:
Reply all
Reply to author
Forward
0 new messages