0

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); })   
      .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
});

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>
Community
  • 1
  • 1
cardamom
  • 5,099
  • 3
  • 35
  • 77
  • 1
    can you post your whole html file to show the structure? I suspect your code is executed before DOM is initialized. – paradite Apr 29 '16 at 14:13
  • 1
    Chances are the DOM isn't ready when your script gets run. Try putting it on a `DOMContentLoaded` event. – JCOC611 Apr 29 '16 at 14:14
  • I just added this at the bottom like you said @JCOC611 : `` this caused the phrase "DOM fully loaded and parsed" to print in the developer console but sadly my text did not become clickable. Just to check I wasn't imagining things I copied and pasted that last line there into the console and pressed enter and it was clickable and functional as described above. – cardamom Apr 29 '16 at 14:20
  • @cardamom it would help if you post your entire html code (if it is not too long) – paradite Apr 29 '16 at 14:27
  • Ok @paradite it's 243 lines just pulling the guts out of it will post the skeleton.. – cardamom Apr 29 '16 at 14:35
  • @paradite it's there hope there's something obvious there that's causing this trouble.. – cardamom Apr 29 '16 at 14:48
  • `d3.csv()` is async. The text element `#patext` is not yet available when you try to assign the listener to it. The line `d3.select("#patext").on("click", function() {toggleLine();})` needs to be inside the callback provided to `d3.csv()`. – altocumulus Apr 29 '16 at 14:51
  • @cardamom like the comment above pointed out, you need to put your `d3.select` inside the callback of `d3.csv`, see my edited answer. – paradite Apr 29 '16 at 14:56

1 Answers1

2

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.

The solution is to bind the click listener before transition().

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

    });
  </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.

Community
  • 1
  • 1
paradite
  • 5,641
  • 3
  • 35
  • 54
  • Thanks @paradite I copied and pasted **exactly** what you have above above the last two lines of my file which are the closing tags `

    ` and ran it. Didn't generate any error messages but text was sadly not clickable. Then pasted the one line into the browser console and pressed enter and all good. Will check out your link now.

    – cardamom Apr 29 '16 at 14:30
  • Thanks is the definition `function toggleLine()` supposed to go inside this `DOMContentLoaded` wrapping? I've moved `d3.select("#patext").on("click", function() {toggleLine();});` to the line immediately beneath `d3.csv()` – cardamom Apr 29 '16 at 15:05
  • @cardamom you can't move the `d3.select("#patext").on("click", function() {toggleLine();});` to immediately below `d3.csv()` because the element `#patext` is not inside DOM yet. You need to use it right at the end of `d3.vsv()` block, like I showed above. – paradite Apr 29 '16 at 15:14
  • @cardamom also, the `function toggleLine()` does not need to be inside because it will only be executed later, and by the time you execute it, the DOM is already full loaded with data. – paradite Apr 29 '16 at 15:15
  • The end of the file now looks like this: `d3.select("#patext").on("click", function() {toggleLine();}) }); function toggleLine() { var active = gol.active ? false : true, newOpacity = active ? 0 : 1; d3.select("#gol").style("opacity", newOpacity); gol.active = active;} });

    ` The `});` on the second line above is the closing brackets of the d3.csv() thing and the `});` on the third last line goes with `document.addEventListener("DOMContentLoaded", function(event) { `

    – cardamom Apr 29 '16 at 15:24
  • basically it is the same as my answer. so it still doesn't work? @cardamom – paradite Apr 29 '16 at 15:28
  • @altocumulus @paradite this has unfortunately not fixed the problem but it has altered the behaviour. No errors appear when it is run and the text is not clickable. Once I enter the magic line in the console, still no error messages, BUT when the text is clicked it now comes back with `VM9691:1 Uncaught ReferenceError: toggleLine is not defined` instead of performing the desired action. – cardamom Apr 29 '16 at 15:29
  • sorry my apologies, you can change the line to `d3.select("#patext").on("click", toggleLine);` and see if it works. @cardamom – paradite Apr 29 '16 at 15:31
  • @cardamom if not maybe just put `function toggleLine()` inside the `d3.csv`, or move it outside `document.addEventListener("DOMContentLoaded", function(event)` JavaScript scoping can be weird sometimes. – paradite Apr 29 '16 at 15:35
  • @cardamom i have updated my answer to move `toggleLine()` to outside of `DOMContentLoaded`, right at the top, you can try that. – paradite Apr 29 '16 at 15:38
  • Yes thanks @paradite I was just busy doing that moved the function definition outside the last addEventListener closing brackets. Now the original behaviour is back. The text is not clickable until the magic line is entered into Chrome console, and no error messages. Oh - now you've put it before the eventListener? - let me try that.. – cardamom Apr 29 '16 at 15:41
  • I think the problem is caused by the fact that d3 DOM operations are async and the elements wasn't ready yet when you execute that line. try my updated answer, it should have the same effect as your original solution. @cardamom – paradite Apr 29 '16 at 15:49
  • That's interesting what you've got there: ` .on("click", function(){if(d3.select(this).attr('id') === "patext") {toggleLine();}})` I've put it in and also moved the function definition above EventListener but unfortunately still not working actually generating `Uncaught TypeError` but when I post the line into the console everything works again as before despite the error message.. If that had never happened I might have just given up and used an html button instead of trying so hard to make this text clickable.. – cardamom Apr 29 '16 at 16:03
  • @cardamom at this point, it is best if you can provide some sample data in the code without loading the csv so that I can actually run the code to figure out what's wrong. without the ability to run the code it is hard to figure out why it is not working. – paradite Apr 29 '16 at 16:11
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/110661/discussion-between-cardamom-and-paradite). – cardamom Apr 29 '16 at 16:32