2

I am attempting to log whenever a county on a d3.js map has been clicked for the first time. The following code updates all elements and I haven't been able to find a way to only assign a true/false to only the element that was clicked.

If I click a county, it turns true and fills it in, if I select that county again, it returns false. If I then select an entirely different county, the value is set to false, and remains false. It never resets back to true.

I feel like I need to be using self or this to help define the element-specific boolean but no combination seems to be working.

var noFill = true;

var mousedown = function() {
    var self = d3.select(this);
    var color = document.getElementById('selcolor').value;

    if (noFill) {
        console.log(noFill);
        self.style("fill", color);
        //do some stuff
        noFill = false;
    } else {
        console.log(noFill);
        //do some stuff
    }
}
Matthew Snell
  • 917
  • 3
  • 12
  • 26

1 Answers1

2

Your problem right now is that noFill is a single variable, and it's the same for all elements you click. As yourself have said, you have to define an element-specific boolean.

A simple solution is creating a datum property named noFill for each element you click, and toggling it on the click event. Something like this:

selection.on("click", function(d) {
    d.noFill = d.noFill || false;
    if (!d.noFill) {
        d3.select(this).attr("fill", "teal");
    } else {
        d3.select(this).attr("fill", "none");
    }
    d.noFill = !d.noFill;
})

Here is a demo:

var svg = d3.select("svg");

var data = d3.range(6).map(d => ({
  id: d
}));

var circles = svg.selectAll("foo")
  .data(data)
  .enter()
  .append("circle")
  .attr("cy", 40)
  .attr("r", 16)
  .attr("cx", (d, i) => 40 + 45 * i)
  .attr("stroke", "gray")
  .attr("stroke-width", 2)
  .attr("fill", "none")
  .attr("pointer-events", "all")
  .attr("cursor", "pointer");

circles.on("click", function(d) {
  d.noFill = d.noFill || false;
  if (!d.noFill) {
    d3.select(this).attr("fill", "teal");
  } else {
    d3.select(this).attr("fill", "none");
  }
  d.noFill = !d.noFill;
})
<script src="https://d3js.org/d3.v4.min.js"></script>
<svg></svg>

Notice that in the anonymous function I'm using function(d), which uses the datum (d) of each element as a parameter, not just function().

Gerardo Furtado
  • 100,839
  • 9
  • 121
  • 171
  • Brilliant. Thanks for the simple fix! What exactly are the double pipes doing? I coded it and it runs but I don't know what those mean. – Matthew Snell Mar 30 '17 at 23:39
  • When you click the element for the first time there is no `noFill` property. So, the OR operator (the name of the double pipes) set the property to `false`. See this answer here: http://stackoverflow.com/a/34707750/5768908. It explains very well the pattern I used. – Gerardo Furtado Mar 31 '17 at 07:22