1

After reading variable scope in d3 javascript I assumed that in general you should place all your functions within the d3.json() {} request.

I am working with some map zooming code from http://techslides.com/demos/d3/us-zoom-county.html and am trying to figure out why the clicked(d) function is outside of the d3.json request.

Additionally, why won't the code work with the clicked(d) function inside the d3.json request?

var width = 960,
    height = 500,
    centered;

var projection = d3.geo.albersUsa()
    .scale(1070)
    .translate([width / 2, height / 2]);

var path = d3.geo.path()
    .projection(projection);

var svg = d3.select("body").append("svg")
    .attr("width", width)
    .attr("height", height);

svg.append("rect")
    .attr("class", "background")
    .attr("width", width)
    .attr("height", height)
    .on("click", clicked);

var g = svg.append("g");

d3.json("data/us.json", function(error, us) {


 g.append("g")
      .attr("id", "counties")
    .selectAll("path")
      .data(topojson.feature(us, us.objects.counties).features)
    .enter().append("path")
  .attr("d", path)
  .attr("class", "county-boundary")
      .on("click", countyclicked);

  g.append("g")
      .attr("id", "states")
    .selectAll("path")
      .data(topojson.feature(us, us.objects.states).features)
    .enter().append("path")
  .attr("d", path)
  .attr("class", "state")
      .on("click", clicked);


  g.append("path")
      .datum(topojson.mesh(us, us.objects.states, function(a, b) { return a !== b; }))
      .attr("id", "state-borders")
      .attr("d", path);
});


function clicked(d) {
  var x, y, k;

  if (d && centered !== d) {
    var centroid = path.centroid(d);
    x = centroid[0];
    y = centroid[1];
    k = 4;
    centered = d;
  } else {
    x = width / 2;
    y = height / 2;
    k = 1;
    centered = null;
  }

  g.selectAll("path")
      .classed("active", centered && function(d) { return d === centered; });

  g.transition()
      .duration(750)
      .attr("transform", "translate(" + width / 2 + "," + height / 2 + ")scale(" + k + ")translate(" + -x + "," + -y + ")")
      .style("stroke-width", 1.5 / k + "px");
}

function countyclicked(d) {
  alert(d.id);
}
Community
  • 1
  • 1
As3adTintin
  • 2,406
  • 12
  • 33
  • 59

1 Answers1

2

I am trying to figure out why the clicked(d) function is outside of the d3.json request.

Where a function is defined is mostly irrelevant, it just needs to be accessible from where it should be referenced. clicked is accessible inside the d3.json callback, so that's fine.

The location of the function is only important if it is supposed to be a closure, i.e. needs access to values that are not passed as arguments. E.g. if clicked needed to access usdirectly, it would have to be defined inside the callback.
That's not the case here though, clicked gets all the data it needs via its parameter.

However, what is important here is that the function is only being called after the data was received, since it is bound as event handler inside the callback (.on("click", clicked)) based on the data that was received.

why won't the code work with the clicked(d) function inside the d3.json request?

I don't see any reason why that would be the case.

That's because the function is also bound outside the callback to the container here:

svg.append("rect")
  .attr("class", "background")
  .attr("width", width)
  .attr("height", height)
  .on("click", clicked);

If you move inside the callback it is not accessible outside to that code anymore.

Note that clicked itself checks whether data is available or not by checking whether the argument d is set or not.

Felix Kling
  • 795,719
  • 175
  • 1,089
  • 1,143
  • thanks for your answer. To clarify, isn't the function outside the callback? `d3.json(...){...} function clicked(d){...}`. The callback is `d3.json` and the function is `clicked(d)`, yes? – As3adTintin May 26 '16 at 15:52
  • okay i must be misinterpreting what you said then about "the function... is bound inside the callback based on the data that was received". is this because (as you mentioned) the parameter is passed from inside the callback? – As3adTintin May 26 '16 at 15:56
  • 1
    `.on("click", clicked);` is inside the callback. It binds the `clicked` function as event handler to the element that was just created. – Felix Kling May 26 '16 at 15:57
  • 1
    In other words, *when* is more important than *where*. It doesn't matter *where* the function is defined but *when* the function is called. Functions that need access to the data received from an Ajax must be called *after* the data was received. – Felix Kling May 26 '16 at 16:00
  • however, I just double checked, when I move the `clicked` function inside the callback to `d3.json(...) {... function clicked(d) {...} }`, i receive `ReferenceError: clicked is not defined`. Why is that? – As3adTintin May 26 '16 at 16:01
  • Ah, it is actually also referenced outside: `svg.append("rect").attr("class", "background").attr("width", width).attr("height", height).on("click", clicked);` – Felix Kling May 26 '16 at 16:02
  • oh so that's why I need it ouside the callback... i forgot about that reference. Thanks, Felix! – As3adTintin May 26 '16 at 16:03
  • @As3adTintin if you define `clicked` inside a function, then it is only accessible by the scope enclosed in the function, but not outside. In this case your other code outside tried to access it. – paradite May 26 '16 at 16:03