1

Im have a force directed graph with a bunch of various nodes. Each node has either a single word, or double word label.

I have the svg circle and text label inside a group element. I plan on styling the label so it overlaps the node. My issue is that the majority of the time, the text will overflow the node.

Is there anyway to change the radius of the node based on its label size?

Oliver Evans
  • 960
  • 1
  • 20
  • 43
  • 1
    It's possible to measure the size of the label – once it's rendered – and then use that to set the radius. You would measure using `this.getBBox()`. – meetamit Feb 23 '15 at 03:14
  • As @meetamit says, I could use the getBBox function to do this. I found an example as an answer to a question asking a different question. http://stackoverflow.com/a/27650414/1787122 I've decided to take a differnet route into displaying nodes, so hopefully someone can try this example out :) – Oliver Evans Feb 23 '15 at 18:23

2 Answers2

1

Let's say that nodes contains your g groups, that you've already bound data to them, and that the label text string is in a property name. You probably used this string when you told d3 to add the text elements to the g groups. You can use the same bound data to configure the circles when you add them. Something like this should work:

nodes.append("circle")
     .attr("r", function(d) {return d.name.length * 2.5;})
     ... more stuff here.

The second line is the key. I'm just setting the circle radius based on the length of the label text. I used 2.5 as the multiplier based on trial and error with the default san-serif in 10pt type.

In theory, it would be nice to have some systematic method for determining how much each character takes up, on average, and use that to determine the multiplier in the second line. (Even with fixed-width fonts, there's a lot of variation in how much space is used for different fonts with the same point size.) If it were me, that would be more work than it was worth. I would probably just set a variable containing the multiplier near the top of the script and try to remember to change it when I changed fonts.

EDIT: It might be possible to use one of the functions getBBox() or getBoundingClientRect() on the text object (probably referencing it as this) to figure out the size of the text.

Mars
  • 8,689
  • 2
  • 42
  • 70
  • Thanks for your answer. I guesses this might be the only option. I have actually found an answer using the getBBox function, but havent got round to testing it: http://stackoverflow.com/a/27650414/1787122. Using your method I actually got it to work, but in some instances where there is more than one word in the label, the nodes are huge. I resorted to wrapping the text, and the calculating the radius of the node based off the longest word on the string, but then if the longest word was at the top of the node then it would still overflow slightly.... – Oliver Evans Feb 23 '15 at 18:27
  • ...I think this is just issue after issue, so i'm abandoning it for the time being and just displaying the label below the node for now. Thanks for your help. – Oliver Evans Feb 23 '15 at 18:28
1

Try using getComputedTextLength().

From Mike Bostock: http://bl.ocks.org/mbostock/1846692

node.append("text")
  .text(function(d) { return d.name; })
  .style("font-size", function(d) { return Math.min(2 * d.r, (2 * d.r - 8) / this.getComputedTextLength() * 24) + "px"; })

This allows you to fill up the circle with text without overflowing. I'm not totally sure where the numbers come from there—perhaps someone can better explain.

Alternatively, you could use getBBox() as in this example (and the other answer by Mars), though you'd need to also do some calculations for the circle. You can do that using .attr("text-anchor", "middle") and some geometry.

Hope this helps.

Lucas
  • 1,359
  • 7
  • 16