39

I've created the following document:

<g>
    <path class=​"line" name=​"gene_1" stroke=​"#aec7e8" d=​"M10.47...">​</path>​
    <path class=​"line" name=​"gene_2" stroke=​"#aec7e8" d=​"M10.47...">​</path>​
    <path class=​"line" name=​"gene_3" stroke=​"#aec7e8" d=​"M10.47...">​</path>​
    ...
</g>

When I mouse over each path I want to append it last inside the svg:g so it appears on top of the other lines, but I don't know how to properly select the parentNode:

function onmouseover(d, i){
  var current_gene_name = d3.select(this).attr("name"),
      current_gene_pcp = d3.select(".line[name=" + current_gene_name + "]");

  p1 = this.parentNode 

  p2 = current_gene_pcp[0].parentNode

  p3 = current_gene_pcp[0][0].parentNode

  //p.appendChild(this);
}

p1 works, but I wanted to make sure that this is a .line, so I preferred to use current_gene_pcp, but p2 returns <html></html> as the parent, even though p3 returns the proper <g></g>. This last version seems like a bug waiting to happen. Is there a better way?

altocumulus
  • 21,179
  • 13
  • 61
  • 84
nachocab
  • 13,328
  • 21
  • 91
  • 149

6 Answers6

49

A D3 selection is just a double-array wrapped around the element(s) selected. As you found with p3, you can dereference the arrays to find your first node, if you want. However, a better method does exist:

From the docs for selection.node():

Returns the first non-null element in the current selection. If the selection is empty, returns null.

In your case:

var dad = current_gene_pcp.node().parentNode;

However, if you don't need the line other than the DOM handle, you might as well just get that directly:

// Search document-wide...
var dad = document.querySelector(".line[name=" + current_gene_name + "]");

// ...or only as a child of the current DOM element
var dad = this.querySelector(".line[name=" + current_gene_name + "]");
Phrogz
  • 296,393
  • 112
  • 651
  • 745
36

Here's a quick way to move the mouseover element to the front:

selection.on("mouseover", function() { this.parentNode.appendChild(this); });

See also a related thread in the d3-js group.

mbostock
  • 51,423
  • 13
  • 175
  • 129
  • This behavior is unstable. When I try this in Chrome in combination with `:hover` styles, the styles are correctly applied. When I try this in Firefox (v20), the styles are not applied at all. When I remove this `mouseover` code, the styles are correctly applied in Firefox. I guess this is caused by an element's mouseover` trying to remove itself before it's reattached as the last child of its DOM. – John Slegers Mar 31 '14 at 19:35
5

There are two ways to access it.
Either use the third variable like this: someNode.attr("someAttrib", function(d, i, j) { console.log(d, i, j); });. The j contains the data you supplied to the parent node.
or use d3.select(this.parentNode).data()[0].id;.

An example:

<!DOCTYPE html>
<meta charset="utf-8">
<body>
<script src="http://d3js.org/d3.v3.min.js"></script>
<div id="area", width="1000px" height="1000px"></div>
<script>
var GAP = 10, NODE_RADIUS = 14;

  var nodes = [
    {id: 1, prop: [{id: 1, something: 1}], abc: 1},
    {id: 2, prop: [{id: 1, something: 1}, {id: 2, something: 1}, {id: 3, something: 1}], abc: 2},
    {id: 3, prop: [{id: 1, something: 1}, {id: 2, something: 1}], abc: 3},
    {id: 4, prop: [{id: 1, something: 1}], abc: 4},
    {id: 5, prop: [{id: 1, something: 1}], abc: 5},
    {id: 6, prop: [], abc: 6}
  ];

var nodeLayer = d3.select("#area").selectAll(".node").data(nodes, function(d) {return d.id; });
var iNodes = nodeLayer.enter().append("svg").attr("class", "node");
    //append the black circle
    iNodes.append("circle")
                .style("fill", "black")
                .attr("r", NODE_RADIUS)
                .attr("cx", function(d) {return 10 + d.id * 10;})
                .attr("cy", 100);

    iNodes.append("g").selectAll(".prop").data(function(d) {return d.prop;}).enter()
            .append("text")
                .text(function(d,i,j){console.log("parent id="+j); return d3.select(this.parentNode).data()[0].id;})
                .attr("dx", 50)
                .attr("dy", 50)
                .style("font-size", "8px")
                .style("fill", d3.rgb(180,0,0))
                .style("text-anchor", "middle");

</script>
</body>
Community
  • 1
  • 1
Nav
  • 19,885
  • 27
  • 92
  • 135
0

For some reason, I had to go up 2x levels: console.log(">>"+my_chart.node().parentNode.parentNode.id+"<<")

Jason
  • 719
  • 9
  • 19
0

One way I have done is:

bars.on('mouseover', d => {
let parentNode = d3.select(this.parentNode);
let tooltip = parentNode.append('rect)

from this key word, you can access the current node, then touch its parentNode.

Ping Woo
  • 1,423
  • 15
  • 21
0

With the release of D3 v4 this has become an XY-problem of sorts. There is no longer the need to get to the parentNode to re-insert elements as the last child of its parent. Because from that release on D3 offers the selection.raise() method:

selection.raise()

Re-inserts each selected element, in order, as the last child of its parent. Equivalent to:

selection.each(function() {
  this.parentNode.appendChild(this);
});

Using D3 v6 where the event is passed as the first parameter to the event listener (see changelog) your code can be as simple as this:

function onmouseover(event) {
  d3.select(event.target).raise();
}

Have a look at the following demo:

d3.select("g")
  .on("mouseover", event => d3.select(event.target).raise())
<script src="https://d3js.org/d3.v6.js"></script>

<svg width="400" height="400">
  <g>
    <rect x="100" y="50" width="100" height="100" fill="red"></rect>
    <rect x="150" y="50" width="100" height="100" fill="blue"></rect>
  </g>
</svg>
altocumulus
  • 21,179
  • 13
  • 61
  • 84