text doesn't append to node in force graph

Skip to first unread message

jsharp111

unread,
May 24, 2011, 6:51:17 AM5/24/11
to d3-js
I'm a nube at d3 - my whole site is protovis right now, but aside from
being easier to say, I'm impressed with the stability vs. protovis.

That said, having an issue appending a text label to a node in a force
graph. The title/tooltip is working just fine, so the problem isn't
with the original data - and in any case, when I use a test string it
still doesn't show up. Forum members, your help would be much
appreciated.

Here's the code that is working fine.

var node = vis.selectAll("circle.node")
.data(followers.nodes)
.enter().append("svg:circle")
.attr("class", "node")
.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; })
.attr("r", 7)
.style("fill", "#234B6F")
.call(force.drag);

node.append("svg:title")
.text(function(d) { return "@" + d.nodeName; });

No problem. But when I go to add a text label, doing what I assume is
the right thing by adding:

node.append("svg:title")
.text(function(d) { return "@" + d.nodeName; });

... nothing displays and the result starts looking weird.

Can anyone help?

Mike Bostock

unread,
May 24, 2011, 11:12:34 AM5/24/11
to d3...@googlegroups.com
> No problem.  But when I go to add a text label, doing what I assume is
> the right thing by adding:

Your two code examples look identical, so it's a bit difficult to say
precisely what the problem is. But, I suspect the confusion is related
to method chaining.

If you want to render an svg:circle and an svg:text for each node,
then you should map the nodes to svg:g elements. The `g` element is
SVG's container element, much like the `div` in HTML. So you do
something like this:

var node = vis.selectAll("g.node")
.data(followers.nodes)
.enter().append("svg:g")


.attr("class", "node")

.attr("transform", function(d) { return "translate(" + d.x + ","
+ d.y + ")"; });

OK, now your `node` refers to a container, to which you can add both
circles and text. But make sure you add the text to the g container,
not the circle. When you use append, it returns the selection you just
added! So, you need to keep a reference to the parent if you want to
create siblings rather than children. For example:

node.append("svg:circle")


.attr("r", 7)
.style("fill", "#234B6F")
.call(force.drag);

node.append("svg:text")
.style("pointer-events", "none")
.text(function(d) { return d.data.key; });

Mike

jsharp111

unread,
May 24, 2011, 11:29:07 PM5/24/11
to d3-js
You're right - I pasted the wrong code for the second example. My
apologies.

node.append("svg:text")
.attr("text-anchor", "middle")
.attr("dy", ".3em")
.text(function(d) { return d.nodeName; })

What I am trying to do is add a text label beside each node. My point
is including the title script is that I know the data is correct, so
the problem simply lies in how best to append the text to the node.

I tried your approach but still didn't get the result I need. FYI,
this is the entire script:

var w = 880,
h = 200,
fill = d3.scale.category20();

var vis = d3.select("#netviz")
.append("svg:svg")
.attr("width", w)
.attr("height", h);

var force = d3.layout.force()
.charge(-120)
.distance(30)
.nodes(followers.nodes)
.links(followers.links)
.size([w, h])
.start();

var link = vis.selectAll("line.link")
.data(followers.links)
.enter().append("svg:line")
.attr("class", "link")
.style("stroke-width", function(d) { return
Math.sqrt(d.value); })
.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; });

var node = vis.selectAll("circle.node")
.data(followers.nodes)
.enter().append("svg:circle")
.attr("class", "node")
.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; })
.attr("r", 7)
.style("fill", "#234B6F")
.call(force.drag);

node.append("svg:text")
.attr("text-anchor", "middle")
.attr("dy", ".3em")
.text(function(d) { return d.nodeName; });

node.append("svg:title")
.text(function(d) { return "@" + d.nodeName; });

vis.style("opacity", 1e-6)
.transition()
.duration(1000)
.style("opacity", 1);

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

});

jsharp111

unread,
May 25, 2011, 2:18:24 AM5/25/11
to d3-js
Okay, I got it working - almost!

Now, the only issue I am seeing is that when I drag a node, the mouse
position registers different from the node position, creating a
"dancing node" effect. Seeing this in both Safari and Firefox so am
assuming I am doing something fundamentally wrong... really enjoying
your D3 framework by the way. Seems to feel more robust than Protovis
- I'm looking forward to building the next visualization.

var w = 880,
h = 200;

var vis = d3.select("#netviz")
.append("svg:svg")
.attr("width", w)
.attr("height", h);

var force = d3.layout.force()
.charge(-120)
.distance(40)
.nodes(followers.nodes)
.links(followers.links)
.size([w, h])
.start();

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

var node = vis.selectAll("g.node")
.data(followers.nodes)
.enter().append("svg:g")
.attr("class", "node");

node.append("svg:circle")
.attr("r", 5)
.style("fill", "#234B6F")
.call(force.drag);

node.append("svg:text")
.style("pointer-events", "none")
.attr("fill", "#555")
.attr("font-size", "11px")
.attr("dx", "8")
.attr("dy", ".35em")
.text(function(d) { return "@" + d.nodeName; });

node.append("svg:title")
.style("pointer-events", "none")
.text(function(d) { return "@" + d.nodeName; });

vis.style("opacity", 1e-6)
.transition()
.duration(1000)
.style("opacity", 1);

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

});

Mike Bostock

unread,
May 25, 2011, 2:36:41 AM5/25/11
to d3...@googlegroups.com
> Now, the only issue I am seeing is that when I drag a node, the mouse
> position registers different from the node position, creating a
> "dancing node" effect.

You need to bind the .call(force.drag) to the svg:g element (that you
are transforming), rather than the child circle. At least, that's my
guess. Alternatively, what version of D3 are you using? I fixed a bug
related to this fairly recently. Working example here:

http://bl.ocks.org/950642

Glad you're enjoying D3!

Mike

jsharp111

unread,
May 25, 2011, 12:21:55 PM5/25/11
to d3-js
It's working great - thanks Mike, you're a rock star.

Any plans to create a "bounding box" for this kind of chart? It;d be
nice if my "loose" nodes (i.e. those not connected to a link) hit the
"ceiling" and bounced back, rather than disappeared... that would be
cool.

Mike Bostock

unread,
May 25, 2011, 1:01:08 PM5/25/11
to d3...@googlegroups.com
> Any plans to create a "bounding box" for this kind of chart?

There's a soft bounding box currently, by way of a "gravity" force
that pulls each node towards the center of the layout. It's actually
more like an invisible spring that connects each node to the center,
such that the center pull gets stronger the farther away the node is
(and has little effect in the middle, so as to avoid distortion).
Depending on the size and connectivity of your graph, you may need to
play with the different parameters so that the graph stays
approximately within its bounds; two things to try are decreasing the
charge force and increasing the gravity force. You can do this from
the developer console to see how it affects your graph interactively!

If you want to implement a proper hard bounding box, you can do that
in the on("tick") callback; just set the `x` and `y` attributes of
your nodes so they're within the bounds. D3 uses position Verlet
integration, which means the simulation automatically adjusts to
repositioning of nodes.

Mike

neotrex

unread,
Jul 5, 2011, 5:05:28 AM7/5/11
to d3...@googlegroups.com
Hello all,
A similar piece of code always gives the error: 
node.exit is not a function
   var node = vis.selectAll("g.node")
        .data(nodes, function(d) { return d.id; })
      .enter().append("svg:g")
        .attr("class", "node")
        .call(force.drag)

   node.append("svg:circle")
      .attr("class", "node")
      .attr("cx", function(d) { return d.x; })
      .attr("cy", function(d) { return d.y; })
      .attr("r", 5)
      .style("fill", function(d) { return d._children ? "lightsteelblue" : "#fff"; })
      .call(force.drag)

    node.append("svg:text")
      .attr("class", "nodetext")
      .attr("dx", function(d) { return d.x; })
      .attr("dy", function(d) { return d.y; })
      .text(function(d) { return d.name })
      .call(force.drag)
		
	node.exit().remove();
I guess this is because I couldn't select the nodes directly when they are
displayed on a container g.
So how do we select the nodes or the texts displayed on a container g,
so that we can remove them both... (so that I could call node.exit().remove())?
Best regards,
neotrex

Jason Davies

unread,
Jul 5, 2011, 5:57:37 AM7/5/11
to d3...@googlegroups.com
On Tue, Jul 05, 2011 at 02:05:28AM -0700, neotrex wrote:
> A similar piece of code always gives the error:
>
> *node.exit is not a function*

>
> var node = vis.selectAll("g.node")
> .data(nodes, function(d) { return d.id; })
> .enter().append("svg:g")
> .attr("class", "node")
> .call(force.drag)
>
> node.append("svg:circle")
> .attr("class", "node")
> .attr("cx", function(d) { return d.x; })
> .attr("cy", function(d) { return d.y; })
> .attr("r", 5)
> .style("fill", function(d) { return d._children ? "lightsteelblue" : "#fff"; })
> .call(force.drag)
>
> node.append("svg:text")
> .attr("class", "nodetext")
> .attr("dx", function(d) { return d.x; })
> .attr("dy", function(d) { return d.y; })
> .text(function(d) { return d.name })
> .call(force.drag)
>
> node.exit().remove();

This is a fairly common mistake I think. Note that your variable "node"
above is actually the result of calling .enter(). So you can't call
.exit() on it. The fix is:

var node = vis.selectAll("g.node")

.data(...);

var nodeEnter = node.enter().append("svg:g")
.attr(...)
.call(...);

nodeEnter.append(...);

node.exit().remove();

Note that you don't currently have any code to deal with the update
selection. So you would need to add something like:

// Update existing nodes
node.select("circle")
.attr("cx", ...);

We may get a change soon to make this simpler:
<https://github.com/mbostock/d3/pull/184> but it's not been released
yet. This change would make it easier to update both the enter and
update selection in one go, because the update selection would be
automatically updated with anything appended to the enter selection.

Hope that makes sense,
--
Jason Davies, http://www.jasondavies.com/

neotrex

unread,
Jul 5, 2011, 7:04:24 AM7/5/11
to d3...@googlegroups.com
Hello all,

Thanks for quick reply Jason.
At least the error dissapeared.

I also added as you suggested some code to update the circles, texts,...

However the text is still not displayed where it should have been. 
And the clicks don't work, even tough with previous wrong code worked ...

  var node = vis.selectAll("g.node")
        .data(nodes, function(d) { return d.id; })
.on("click", click)
  
  var nodeEnter = node.enter().append("svg:g")
        .attr("class", "node")
.attr("cx", function(d) { return d.x; })
        .attr("cy", function(d) { return d.y; })
        .call(force.drag)
  nodeEnter.append("svg:circle")
      .attr("class", "node")
      .attr("cx", function(d) { return d.x; })
      .attr("cy", function(d) { return d.y; })
      .attr("r", 10)
      .style("fill", function(d) { return d._children ? "lightsteelblue" : "#fff"; })
       .call(force.drag)
    nodeEnter.append("svg:text")
        .attr("class", "nodetext")
        .attr("dx", function(d) { return d.x; })
        .attr("dy", function(d) { return d.y; })
.style('text-anchor', 'middle')
    //.style('alignment-baseline', 'middle')
        .text(function(d) { return d.name })
.call(force.drag)
node.exit().remove();
node.select("circle")
.attr("cx", function(d) { return d; })
                .attr("cy", function(d) { return d; })

node.select("text")
.attr("cx", function(d) { return d; })
                .attr("cy", function(d) { return d; }

After the new release with <https://github.com/mbostock/d3/pull/184 
please add a simple tutorial on how to work with update, 
append, enter, exit with containers.... 

So that we have a standard way to do it.... (one that will work in most cases,
not universal...) 

Best regards,
neotrex

Jason Davies

unread,
Jul 5, 2011, 7:13:02 AM7/5/11
to d3...@googlegroups.com
On Tue, Jul 05, 2011 at 04:04:24AM -0700, neotrex wrote:
> However the text is still not displayed where it should have been.
> And the clicks don't work, even tough with previous wrong code worked ...
>
> var node = vis.selectAll("g.node")
> .data(nodes, function(d) { return d.id; })
> .on("click", click)

Here you are binding the click handler only to the update selection,
which explains why it's not working. I think you want to bind to the
enter selection instead.

Why are you setting the "cx" and "cy" attributes for your text node?
You probably want "dx" and "dy". Also you're missing other attributes
that you were setting for your enter selection, such as "fill", as well
as calling .text(), assuming you want to update those too.

neotrex

unread,
Jul 5, 2011, 8:35:36 AM7/5/11
to d3...@googlegroups.com
Hello all,
Hello and Thanks Jason :)

You are right about dx and dy.
Now the only thing that doesn't work is the fact that the text is not following the nodes.
which is the reason why I had to put a container in the first place
(by putting the onclick to the enter selection I did obtained the effect I was looking for:
clik on node or text opens or deletes nodes...- does work only when painting the nodes however...)

It is painted in the corect position after removing or adding nodes...but it stays there....
no movement for the text if I move the node.... 

What I am missing here? 
Do I need to add something for the text update part?

Best regards,

Mike Bostock

unread,
Jul 5, 2011, 10:50:53 AM7/5/11
to d3...@googlegroups.com
Here's an example of a force-directed layout with images and text
labels (previously posted):

http://bl.ocks.org/950642

If you are going to put the circle and text inside a g element, it's
easier to move the g element using the "transform" attribute rather
than moving the circle and text individually.

Also, the definition of `node` in your code is the updating selection,
so if you say node.select("circle").attr(…) in your tick handler,
you're only touching the updating nodes. So probably you need to
re-select to combine enter and update at the end of your
initialization:

// reselect
node = vis.selectAll("g.node");

(Or, call vis.selectAll("g.node") in your tick handler.)

Also, thanks Jason for fielding questions. :)

Mike

neotrex

unread,
Jul 5, 2011, 11:49:18 AM7/5/11
to d3...@googlegroups.com
Hello all,

I agree that the transition would be easier. I've read that example before.
I initially tried something similar. 
And also reselect doesn't work.
Maybe because everything is in the update() method?

So i'm still left clueless about why the text isn't following the nodes and remains static.
Why don't we simply set a bound_text_to_node variable to true?
This way at least we make sure the text will always follow the nodes
and we do not duplicate code.

Other than that, the text is there and if I click it I can remove nodes....
which is what I wanted....:)

In few months a lot of person will be able to field questions after the basics are clear....
and they have tried their hand at several complex visualizations.

Thanks,
neotrex


Mike Bostock

unread,
Jul 5, 2011, 12:28:51 PM7/5/11
to d3...@googlegroups.com
> So i'm still left clueless about why the text isn't following the nodes and
> remains static.

You could debug this using your browser's developer console. The
questions to ask:

1. Is your code being run?

Try setting a breakpoint or using console.log. This will verify that
the "tick" event listener is being called at the expected times.
Setting a breakpoint is a bit more reliable, but you can also call a
method that doesn't exist (e.g., `fail()`) to cause an error that
opens the debugger.

2. Does your selection contain the expected elements?

If you console.log the selection, you'll be able to open the contained
arrays and see what elements are selected. For example:

console.log(node);

If the selection doesn't contain the expected elements, perhaps you
need to reselect or there's a typo in the selector.

3. Do your attributes have the expected values?

Using the element inspector, you can look at individual text, circle
and g elements to see what their attributes are. You can even edit the
attributes directly from the inspector to see if changing the values
affects the elements as you expect. If the attributes look correct in
the element inspector, but don't look right in the visualization, then
consult the SVG specification or other examples for guidance.

Mike

neotrex

unread,
Jul 5, 2011, 1:38:40 PM7/5/11
to d3...@googlegroups.com
Hello,
 
Yes I do debug. I have debugged d3 before. It's nice to inspect the elements...

1)the "tick event" listener is working - i've put an alert...and it appeared 

2) console.log(node) - returns only 26 null elements....

however, console.log(nodeEnter) does return the 26 elements I expect to see there...
(g elements with a node and a text)

you really think it's a typo? the nodes do work fine ....
just that the text is not bond to them....

3) attributes do have expected values, I can modify them ok directly from inspector

I have to admit I only checked 1) and 3) when writing previous mails.... 
I tought it's enough if I see the elements in there...

Best regards,
neotrex
Reply all
Reply to author
Forward
0 new messages