Search code examples
javascriptd3.jssvgonclickmouseout

Prevent mouseout action after click on an element with D3.js


I would like to prevent the mouseout action of an element when the user click on this element. For an example see this JSFiddle (the circle disappear even if I click on the label).

Is there an easy way to achieve my objective with d3.js? Thank you!

The JSFiddle example code:

var svg = d3.select("#content")
                        .append("svg")
            .attr("width", 600)
            .attr("height", 400);
var g = svg.append("g");
var text = g.append("text")
                            .text("Click me")
              .style("fill", "Blue")
              .attr("x", 50)
              .attr("y", 50)
              .on("mouseover", mouseover)
              .on("mouseout", mouseout)
              .on("click", click);

var circle = g.append("circle")
                                .style("fill", "Orange")
                .attr("cx", 150)
                .attr("cy", 90)
                .attr("r", 15)
                .classed("hide", true)
                .classed("someClass", true);

function mouseover(p){
    d3.selectAll("circle.someClass").classed("hide", false);
}
function mouseout(p){
    d3.selectAll("circle.someClass").classed("hide", true);
}
function click(p){
    d3.selectAll("circle.someClass").classed("hide", false);
}

Solution

  • If you plan to only have one element that controls the circle, use a "flag". Messing with conditionally registering/unregistering events is not a good idea.

    Check this updated version of your fiddle:

    https://jsfiddle.net/cze9rqf7/

    var svg = d3.select("#content")
              .append("svg")
              .attr("width", 600)
              .attr("height", 400);
    var g = svg.append("g");
    var text = g.append("text")
              .text("Click me")
              .style("fill", "Blue")
              .attr("x", 50)
              .attr("y", 50)
              .on("mouseover", mouseover)
              .on("mouseout", mouseout)
              .on("click", click);
    
    var circle = g.append("circle")
              .style("fill", "Orange")
              .attr("cx", 150)
              .attr("cy", 90)
              .attr("r", 15)
              .classed("hide", true)
              .classed("someClass", true);
    
    var isClicked = false;
    
    function mouseover(p){
        d3.selectAll("circle.someClass").classed("hide", false);
    }
    
    function mouseout(p){
        if(!isClicked) {
            d3.selectAll("circle.someClass").classed("hide", true);
        }
    }
    
    function click(p){
        isClicked = !isClicked;
        d3.selectAll("circle.someClass").classed("hide", false);
    }
    

    EDITS For Comments

    If you need to "remember" state per element, instead of using a global, you should be using data-binding on those elements:

    var text = g.append("text")
      .datum({isClicked: false})
      .text("Click me")
      ...
    
    function mouseout(p){
        // p is the data-bound object
        if(!p.isClicked) {
            var className = d3.select(this).attr("class");
            d3.selectAll("circle."+className).classed("hide", true);
      }
    }
    
    function click(p){
        // on click edit the data-bound object
        p.isClicked = !p.isClicked;
        var className = d3.select(this).attr("class");
        d3.selectAll("circle."+className).classed("hide", false);
    }
    

    Updated fiddle here.