cardamom cardamom - 7 months ago 25
Javascript Question

d3.select() is working from developer console in Chrome but not from inside my script

I've been struggling to make a piece of text clickable. I could make other text clickable and the function behaved as expected once this text was clicked. The troublesome text was nested inside something, I think that's why it was not behaving the same with

.on()
I added an
id
to the piece of text to make it easy to select.

Now I finally have a piece of code which makes the text clickable and everything performs as it should - but only when entered in the Chrome developer console! :

d3.select("#patext").on("click", function() {toggleLine();})


Once this is entered in the Chrome console everything works perfectly but in the
index.html
file it does nothing. 'patext' is the id I gave it earlier. The
index.html
contains a
<style></style>
section at the top, then underneath a
<body></body>
. Inside the body are two
<script></script>
the first loads d3.js the second is my script. The
d3.select()
line above is just below the function definition of
toggleLine()
.

Have already gone through the suggestions here and here and my script is in the body and script to load d3 is a separate one to the main script. Any ideas?

As requested, here are 80 of the original 240 lines it's based on a Bostock script hope I didn't remove anything important

<!DOCTYPE html>
<meta charset="utf-8">
<style>

body {
font: 10px sans-serif;
/* background-color: #ffeda0;*/
}
.axis path
</style>
<body>
<script src="//d3js.org/d3.v3.min.js"></script>
<script>
var parseDate = d3.time.format("%Y-%m-%d").parse;
var x = d3.time.scale().range([0, width]);
var svg = d3.select("body").append("svg")
.attr("width", width + margin.left + margin.right)
d3.csv("myfile.csv", function(error, data) {
if (error) throw error;
color.domain(d3.keys(data[0]).filter(function(key) { return key !== "date"; }));
data.forEach(function(d) {
d.date = parseDate(d.date);
});
var cities = color.domain().map(function(name) {
return {
name: name,
values: data.map(function(d) {
return {date: d.date, temperature: +d[name]}; // plus casts a string '55' to a number 55
})
};
});

x.domain(d3.extent(data, function(d) { return d.date; }));

y.domain([
d3.min(cities, function(c) { return d3.min(c.values, function(v) { return v.temperature; }); }),
d3.max(cities, function(c) { return d3.max(c.values, function(v) { return v.temperature; }); })
]);

svg.append("rect") // fill it a colour
.attr("width", 830)
.attr("height", "100%")
.attr("fill", "AliceBlue");

svg.append("g")
.classed("axis x", true)
.call(xAxis2);

var city = svg.selectAll(".city")
.data(cities)
.enter().append("g")
.attr("class", "city");

city.append("path")
.style("stroke", function(d) {return color(d.name); })
.attr("class", "line")
.attr("id", function(d) {console.log((d.name).slice(0,3));return (d.name).slice(0,3);}) // for click fn below.

city.append("text")
.datum(function(d) { return {name: d.name, value: d.values[d.values.length - 1]}; })
.style("stroke", function(d) {return color(d.name); })
.attr("x", 3)
.attr("dy", ".35em")
.text(function(d) { return d.name; })
.attr("id", function(d) {console.log((d.name).slice(0,2)+"text");return ((d.name).slice(0,2)+"text");}) // for click fn
});

function toggleLine() {
var active = gol.active ? false : true,
newOpacity = active ? 0 : 1;
d3.select("#gol").style("opacity", newOpacity);
gol.active = active;}

document.addEventListener("DOMContentLoaded", function(event) {
//... your code
d3.select("#patext").on("click", function() {toggleLine();});
//... more of your code
});
</script>
</body>

Answer

Turns out there was a transition and that delays the DOM manipulation which makes the event listener bind before DOM element was created.

A transition is a special type of selection where the operators apply smoothly over time rather than instantaneously. You derive a transition from a selection using the transition operator. While transitions generally support the same operators as selections (such as attr and style), not all operators are supported; for example, you must append elements before a transition starts. A remove operator is provided for convenient removal of elements when the transition ends.

<body>
  <script src="//d3js.org/d3.v3.min.js"></script>
  <script>

    function toggleLine() {
      var active = gol.active ? false : true,
        newOpacity = active ? 0 : 1;
      d3.select("#gol").style("opacity", newOpacity);
      gol.active = active;
    }

    document.addEventListener("DOMContentLoaded", function(event) {
      var parseDate = d3.time.format("%Y-%m-%d").parse;
      var x = d3.time.scale().range([0, width]);
      var svg = d3.select("body").append("svg")
        .attr("width", width + margin.left + margin.right)
      d3.csv("myfile.csv", function(error, data) {
        if (error) throw error;
        // ...

        city.append("text")
          // bind listener before transition
          .on("click", function(d){
              if(d3.select(this).attr('id') === "patext") {
              toggleLine();
           }
          .datum(function(d) {
            return {
              name: d.name,
              value: d.values[d.values.length - 1]
            };
          })
          .style("stroke", function(d) {
            return color(d.name);
          })
          .transition() 
          .attr("x", 3)
          .attr("dy", ".35em")
          .text(function(d) {
            return d.name;
          })
          .attr("id", function(d) {
            console.log((d.name).slice(0, 2) + "text");
            return ((d.name).slice(0, 2) + "text");
          });
        // for click fn
      });

    });
  </script>
</body>

This allows your code to be executed after DOM is fully loaded.

See $(document).ready equivalent without jQuery for other options to achieve this.

Comments