Question

I'm adding nodes to a force layout graph like this:

var node = vis.selectAll("circle.node")
    .data(nodes)
    .enter()
    .append("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 fill(d.group); })
    .call(force.drag);

Is there a way to add compound SVG elements as nodes? I.e. I want to add a hyperlink for each circle, so I'd need something like this:

<a href="whatever.com"><circle ...></circle></a>

Was it helpful?

Solution

Creating a "compound" element is as simple as appending one or more children to another element. In your example, you want to bind your data to a selection of <a> elements, and give each <a> a single <circle> child.

First of all, you need to select "a.node" instead of "circle.node". This is because your hyperlinks are going to be the parent elements. If there isn't an obvious parent element, and you just want to add multiple elements for each datum, use <g>, SVG's group element.

Then, you want to append one <a> element to each node in the entering selection. This creates your hyperlinks. After setting each hyperlink's attributes, you want to give it a <circle> child. Simple: just call .append("circle").

var node = vis.selectAll("a.node")
    .data(nodes);

// The entering selection: create the new <a> elements here.
// These elements are automatically part of the update selection in "node".
var nodeEnter = node.enter().append("a")
    .attr("class", "node")
    .attr("xlink:href", "http://whatever.com")
    .call(force.drag);

// Appends a new <circle> element to each element in nodeEnter.
nodeEnter.append("circle")
    .attr("r", 5)
    .style("fill", function(d) { return fill(d.group); })

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

Remember that D3 primarily operates on selections of nodes. So calling .append() on the entering selection means that each node in the selection gets a new child. Powerful stuff!

One more thing: SVG has its own <a> element, which is what I was referring to above. This is different from the HTML one! Typically, you only use SVG elements with SVG, and HTML with HTML.

Thanks to @mbostock for suggesting that I clarify the variable naming.

OTHER TIPS

Reply to Jason Davies (since stackoverflow limits the length of reply comments…): Excellent answer. Be careful with the method chaining, though; typically you want node to refer to the outer anchor element rather than the inner circle element. So I'd recommend a small variation:

var node = vis.selectAll("a.node")
    .data(nodes)
  .enter().append("a")
    .attr("class", "node")
    .attr("xlink:href", "http://whatever.com")
    .attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; })
    .call(force.drag);

node.append("circle")
    .attr("r", 5)
    .style("fill", function(d) { return fill(d.group); });

I've also replaced the circle's cx and cy attributes with a transform on the containing anchor element; either one will work. You can treat svg:a elements as svg:g (both are containers), which is nice if you want to add labels later.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top