PurplePanda
PurplePanda

Reputation: 673

How to select a specific d3 node group element

I have a D3 v4 force simulation with several nodes. Each node has a group. When I mouse over one of the elements of that group(an invisible circle) I want one of the other elements (the red circle on that specific node only which I gave an id of "backcircle") to do something. Currently this is what I have, but it does it to all nodes not just the one I'm hovering over's element.

this.node = this.d3Graph.selectAll(null)
.data(this.props.nodes)
.enter()
.append("g")
.attr("class", "nodes");

this.node.append("circle")
.attr("id", "backCircle")
.attr("r", 60)
.attr("fill", "red")


this.node.append("svg:image")
        .attr("xlink:href", function(d) { return d.img })
        .attr("height", 60)
        .attr("width", 60)
        .attr("x", -30)
        .attr("y", -30)


          this.node.append("circle")
            .attr("r", 60)
            .attr("fill", "transparent")
            .on( 'mouseenter', function(d) {
              d.r = 65;
              this.node.select("#backCircle")
              .transition()
              .attr("r", 80);

            }.bind(this))

Upvotes: 2

Views: 4412

Answers (2)

Gerardo Furtado
Gerardo Furtado

Reputation: 102174

Before anything else, two important tips:

  1. Do not use "transparent" in an SVG.
  2. IDs are unique. So, use classes instead (or select by the tag name)

Back to your question:

There are several ways of selecting the circle element based on a sibling circle element. The first one is going up the DOM and down again, using this.parentNode. The second one, if you know exactly the sequence of the siblings, is using previousSibling.

In the following demos, I have 3 elements per group: a circle, a text and a rectangle. Hovering over the rectangle will select the circle.

First, the option with this.parentNode. in your case:

d3.select(this.parentNode).select(".backCircle")

Hover over the squares:

var svg = d3.select("svg");
var data = [50, 150, 250];
var g = svg.selectAll(null)
  .data(data)
  .enter()
  .append("g")
  .attr("transform", function(d) {
    return "translate(" + d + ",75)"
  });

g.append("circle")
  .attr("class", "backCircle")
  .attr("r", 40)
  .attr("fill", "teal")

g.append("text")
  .attr("font-size", 20)
  .attr("text-anchor", "middle")
  .text("FOO");

g.append("rect")
  .attr("x", 20)
  .attr("y", 20)
  .attr("width", 20)
  .attr("height", 20)
  .style("fill", "firebrick")
  .on("mouseenter", function() {
    d3.select(this.parentNode).select(".backCircle")
      .transition()
      .attr("r", 50)
  }).on("mouseleave", function() {
    d3.select(this.parentNode).select(".backCircle")
      .transition()
      .attr("r", 40)
  })
<script src="https://d3js.org/d3.v4.min.js"></script>
<svg></svg>

Then, the option with previousSibling (here, you don't even need to set a class). In your case:

d3.select(this.previousSibling.previousSibling)

Hover over the squares:

var svg = d3.select("svg");
var data = [50, 150, 250];
var g = svg.selectAll(null)
  .data(data)
  .enter()
  .append("g")
  .attr("transform", function(d) {
    return "translate(" + d + ",75)"
  });

g.append("circle")
  .attr("r", 40)
  .attr("fill", "teal")

g.append("text")
  .attr("font-size", 20)
  .attr("text-anchor", "middle")
  .text("FOO");

g.append("rect")
  .attr("x", 20)
  .attr("y", 20)
  .attr("width", 20)
  .attr("height", 20)
  .style("fill", "firebrick")
  .on("mouseenter", function() {
    d3.select(this.previousSibling.previousSibling)
      .transition()
      .attr("r", 50)
  }).on("mouseleave", function() {
    d3.select(this.previousSibling.previousSibling)
      .transition()
      .attr("r", 40)
  })
<script src="https://d3js.org/d3.v4.min.js"></script>
<svg></svg>

PS: Have in mind that, since I'm not using an object, there is no need for bind(this) in my snippets.

Upvotes: 2

ksav
ksav

Reputation: 20821

I think you need to select the node that is firing the mouseenter event from within its handler.

      this.node.append("circle")
        .attr("r", 60)
        .attr("fill", "transparent")
        .on( 'mouseenter', function(d) {
          var mouseenterNode = d3.select(this) 
          mouseenterNode.attr("r", 65);
          mouseenterNode.select("#backCircle")
          .transition()
            .attr("r", 80);
        }.bind(this))

Upvotes: 2

Related Questions