Harrison Cramer
Harrison Cramer

Reputation: 4506

Canceling Mouseover on Newly Created DOM elements in D3

I've created a DOM element which, when it fires a mouseover event, creates new DOM elements such as labels or a tooltip. Unfortunately, sometimes those elements are created underneath the mouse's current position. This causes the mouseleave event of that DOM element to fire, which is often responsible for removing the newly created DOM element.

In other words, when the new elements are created, the mouse is no longer "hovering" over the DOM element that originally fired the event, but the new DOM element. The browser reads this as a "mouseleave" event, which then fires the "mouseleave" function. This event, as it so happens, then removes the new element – even though the user has not moved the mouse.

This causes a cycle to occur, where the new elements are created and removed in rapid-fire, causing the blinking effect when a certain part of the DOM element is moused over. Here's a simplified version of the problem:

https://jsfiddle.net/KingOfCramers/8wbfjap4/2/

var svg = d3.select("svg").style("background-color","grey")

var data = ["This is a circle"]

var circle = svg.selectAll("circle")
    .data(data)
  .enter()
  .append("circle")
    .attr("cx",100)
  .attr("cy",100)
  .attr("r", 20)
  .style("fill","red")


circle
.on("mouseover",function(d){
    var xPos = d3.select(this).attr("cx")
  var yPos = d3.select(this).attr("cy")
  svg
  .append("text").text(d)
  .attr("x",xPos)
  .attr("y",yPos)
})
.on("mouseleave",function(){
    d3.select("text").remove();
})

Obviously this example is silly, but in instances where the data is much more crowded, simply moving the label by 10 or 15 pixels up or down is not a practical solution. I also cannot just create the label relative to the mouse cursor, because I will often be creating many at once using D3 data, for multiple DOM elements. What do most people do to solve this issue?

Thanks.

Upvotes: 1

Views: 50

Answers (1)

Gerardo Furtado
Gerardo Furtado

Reputation: 102218

If you don't need to interact with that new element, just use pointer-events: none;:

.attr("pointer-events", "none")

Here is your code with that change:

var svg = d3.select("svg").style("background-color", "grey")

var data = ["This is a circle"]

var circle = svg.selectAll("circle")
  .data(data)
  .enter()
  .append("circle")
  .attr("cx", 100)
  .attr("cy", 100)
  .attr("r", 20)
  .style("fill", "red")


circle
  .on("mouseover", function(d) {
    var xPos = d3.select(this).attr("cx")
    var yPos = d3.select(this).attr("cy")
    svg
      .append("text").text(d)
      .attr("x", xPos)
      .attr("y", yPos)
      .attr("pointer-events", "none")
  })
  .on("mouseleave", function() {
    d3.select("text").remove();
  })
<script src="https://d3js.org/d3.v4.min.js"></script>
<svg width=500 height=500></svg>

Upvotes: 1

Related Questions