2

I know this is a simple problem - something similar was solved in these questions: Multiseries line chart with mouseover tooltip and How to as mouseover to Line Graph Interactive in D3 . I tried to use the code from the first situation for a similar visualization. The original datafile is in csv, in a format like

date    m_total_calories    rhr
20160101    2792    72
20160102    2047    65
20160103    2201    62
20160104    2624    67
20160105    2169    64
20160106    3011    59
20160107    1893    62
20160108    3035    56
20160109    1946    57

In my visualization, I'm using 2 axes, so I have to define the variables in a slightly different way. As you can see below, I have variables line_l and line_r that are visualized, and they should be referred to by the object "lines". I tried defining it as an array, but it didn't work. Do you know what could the solution be?

(I apologize for asking such a simple question, I'm new to JavaScript.)

<html>
<head>
<title> Data from Jawbone UP3 </title>
</head>

<body>
  <h1> <center> Data collected by Jawbone UP3 </center> </h1>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script src="d3.tip"></script>
<script>


function myFunction() {
    d3.csv('2016_up.csv', draw);}

function draw(data) {
    "use strict";
    var margin = 60,
        width = 950 - margin,
        height = 475 - margin;
var parseDate = d3.time.format("%Y%m%d").parse;

data.forEach(function(d) {
    d.date = parseDate(d.date);
    d.m_total_calories = +d.m_total_calories;
    d.rhr = +d.rhr;
});

var x_scale = d3.time.scale()
    .domain(d3.extent(data,function(d){return d.date}))
    .range([margin, width]);

var y_left_scale = d3.scale.linear()
   .domain(d3.extent(data,function(d){return d.m_total_calories}))
   .range([height, margin]);

var y_right_scale = d3.scale.linear()
   .domain(d3.extent(data,function(d){return d.rhr}))
   .range([height, margin]);

var x_axis = d3.svg.axis()
    .scale(x_scale)
    .orient("bottom");

var y_axis_left = d3.svg.axis()
    .scale(y_left_scale)
    .orient("left");

var y_axis_right = d3.svg.axis()
    .scale(y_right_scale)
    .orient("right");


var line_l = d3.svg.line()
    .x(function(d){return x_scale(d.date);})
    .y(function(d){return y_left_scale(d.m_total_calories);});

var line_r = d3.svg.line()
    .x(function(d){return x_scale(d.date);})
    .y(function(d){return y_right_scale(d.rhr);});


d3.select("body")
  .append("svg")
    .attr("width", width+margin)
    .attr("height", height+margin)
  .append('g')
    .attr("transform","translate(" + margin + "," + margin + ")");

d3.select('svg')
  .append('path')
    .datum(data)
    .attr('d', line_l)
    .style("stroke", "steelblue")
    .style("fill", "none");

d3.select('svg')
  .append('path')
    .datum(data)
    .attr('d', line_r)
    .style("stroke", "red")
    .style("fill", "none");

d3.select("svg")
  .append("g")
  .attr("class", "x axis")
  .attr("transform", "translate(0," + height + ")")
  .call(x_axis);

d3.select("svg")
  .append("g")
  .attr("class", "y axis")
  .attr("transform", "translate(" + margin + ",0)")
  .style("fill", "steelblue")
  .call(y_axis_left)

  .append("text")
    .attr("y", 30)
    .attr("dy", ".71em")
    .style("text-anchor", "middle")
    .text("Calories burned")

d3.select("svg")
  .append("g")
  .attr("class", "y axis")
  .attr("transform", "translate(" + width + " ,0)")
  .style("fill", "red")
  .call(y_axis_right)

  .append("text")
    .attr("y", 30)
    .attr("dy", ".71em")
    .style("text-anchor", "middle")
    .text("Average heart rate")

  ;

    // var mouseG = svg.append("g")
  var mouseG = d3.select("svg").append("g")
       .attr("class", "mouse-over-effects");

     mouseG.append("path") // this is the black vertical line to follow mouse
       .attr("class", "mouse-line")
       .style("stroke", "black")
       .style("stroke-width", "1px")
       .style("opacity", "0");

     // THIS IS PROBLEMATIC
     var lines = document.getElementsByClassName('line');

     var mousePerLine = mouseG.selectAll('.mouse-per-line')
       .data(data) //   cities)
       .enter()
       .append("g")
       .attr("class", "mouse-per-line");

     mousePerLine.append("circle")
       .attr("r", 7)
       .style("fill", "none")
       .style("stroke-width", "1px")
       .style("opacity", "0");

     mousePerLine.append("text")
       .attr("transform", "translate(10,3)");

     mouseG.append('svg:rect') // append a rect to catch mouse movements on canvas
       .attr('width', width) // can't catch mouse events on a g element
       .attr('height', height)
       .attr('fill', 'none')
       .attr('pointer-events', 'all')
       .on('mouseout', function() { // on mouse out hide line, circles and text
         d3.select(".mouse-line")
           .style("opacity", "0");
         d3.selectAll(".mouse-per-line circle")
           .style("opacity", "0");
         d3.selectAll(".mouse-per-line text")
           .style("opacity", "0");
       })
       .on('mouseover', function() { // on mouse in show line, circles and text
         d3.select(".mouse-line")
           .style("opacity", "1");
         d3.selectAll(".mouse-per-line circle")
           .style("opacity", "1");
         d3.selectAll(".mouse-per-line text")
           .style("opacity", "1");
       })
       .on('mousemove', function() { // mouse moving over canvas
         var mouse = d3.mouse(this);
         d3.select(".mouse-line")
           .attr("d", function() {
             var d = "M" + mouse[0] + "," + height;
             d += " " + mouse[0] + "," + 0;
             return d;
           });

         d3.selectAll(".mouse-per-line")
           .attr("transform", function(d, i) {
             console.log(width/mouse[0])
             var xDate = x_scale.invert(mouse[0]), // x.invert(mouse[0]),
                 bisect = d3.bisector(function(data) { return data.date; }).right,    //d) { return d.date; }).right;
                 idx = bisect(data.values, xDate);

             var beginning = 0,
                 // HERE COMES A PROBLEM WITH LINES DEFINITION
                 end = lines[i].getTotalLength(),
                 end = 100,
                 target = null;

             while (true){
               target = Math.floor((beginning + end) / 2);
               // HERE COMES ANOTHER PROBLEM WITH LINES DEFINITION
               pos = lines[i].getPointAtLength(target);
               if ((target === end || target === beginning) && pos.x !== mouse[0]) {
                   break;
               }
               if (pos.x > mouse[0])      end = target;
               else if (pos.x < mouse[0]) beginning = target;
               else break; //position found
             }

             d3.select(this).select('text')
               .text(y.invert(pos.y).toFixed(2));

             return "translate(" + mouse[0] + "," + pos.y +")";
           });
              });

</script>

<button onclick="myFunction()"> Show me the Graph! </button>


</body>
</html>

I would be really grateful if someone could give me some advice / help me find a solution. Thank you very much in advance!

Community
  • 1
  • 1

1 Answers1

1

Your main problem is that your data is in a different format then the linked question and you haven't accounted for it. Mainly you have a single array with both y datapoints in it where the other question had an array of arrays. In addition, the "problematic" line is because you haven't assigned a class to the lines for the selection later one. Finally, I also took the liberty of re-factoring a little bit of the code, particularly the initial plot set-up (no need to d3.select('svg') over and over).

<html>

<head>
  <title> Data from Jawbone UP3 </title>
</head>

<body>
  <h1> <center> Data collected by Jawbone UP3 </center> </h1>
  <script src="http://d3js.org/d3.v3.min.js"></script>
  <script>
    function myFunction() {
      //d3.csv('2016_up.csv', draw);
      var data = "date,m_total_calories,rhr\n20160101,2792,72\n20160102,2047,65\n20160103,2201,62\n20160104,2624,67\n20160105,2169,64\n20160106,3011,59\n20160107,1893,62\n20160108,3035,56\n20160109,1946,57";
      draw(d3.csv.parse(data));
    }

    function draw(data) {
      "use strict";
      var margin = 60,
        width = 950 - margin,
        height = 475 - margin;
      var parseDate = d3.time.format("%Y%m%d").parse;

      data.forEach(function(d) {
        d.date = parseDate(d.date);
        d.m_total_calories = +d.m_total_calories;
        d.rhr = +d.rhr;
      });

      var x_scale = d3.time.scale()
        .domain(d3.extent(data, function(d) {
          return d.date
        }))
        .range([0, width]);

      var y_left_scale = d3.scale.linear()
        .domain(d3.extent(data, function(d) {
          return d.m_total_calories
        }))
        .range([height, 0]);

      var y_right_scale = d3.scale.linear()
        .domain(d3.extent(data, function(d) {
          return d.rhr
        }))
        .range([height, 0]);

      var x_axis = d3.svg.axis()
        .scale(x_scale)
        .orient("bottom");

      var y_axis_left = d3.svg.axis()
        .scale(y_left_scale)
        .orient("left");

      var y_axis_right = d3.svg.axis()
        .scale(y_right_scale)
        .orient("right");


      var line_l = d3.svg.line()
        .x(function(d) {
          return x_scale(d.date);
        })
        .y(function(d) {
          return y_left_scale(d.m_total_calories);
        });

      var line_r = d3.svg.line()
        .x(function(d) {
          return x_scale(d.date);
        })
        .y(function(d) {
          return y_right_scale(d.rhr);
        });


      var svg = d3.select("body")
        .append("svg")
        .attr("width", width + margin)
        .attr("height", height + margin)
        .append('g')
        .attr("transform", "translate(" + margin + "," + 0 + ")");

      svg
        .append('path')
        .attr('class', 'line')
        .datum(data)
        .attr('d', line_l)
        .style("stroke", "steelblue")
        .style("fill", "none");

      svg
        .append('path')
        .attr('class', 'line')
        .datum(data)
        .attr('d', line_r)
        .style("stroke", "red")
        .style("fill", "none");

      svg
        .append("g")
        .attr("class", "x axis")
        .attr("transform", "translate(0," + height + ")")
        .call(x_axis);

      svg
        .append("g")
        .attr("class", "y axis")
        .attr("transform", "translate(0,0)")
        .style("fill", "steelblue")
        .call(y_axis_left)
        .append("text")
        .attr("y", 30)
        .attr("dy", ".71em")
        .style("text-anchor", "middle")
        .text("Calories burned");

      svg
        .append("g")
        .attr("class", "y axis")
        .attr("transform", "translate(" + width + " ,0)")
        .style("fill", "red")
        .call(y_axis_right)
        .append("text")
        .attr("y", 30)
        .attr("dy", ".71em")
        .style("text-anchor", "middle")
        .text("Average heart rate");

      // var mouseG = svg.append("g")
      var mouseG = svg.append("g")
        .attr("class", "mouse-over-effects");

      mouseG.append("path") // this is the black vertical line to follow mouse
        .attr("class", "mouse-line")
        .style("stroke", "black")
        .style("stroke-width", "1px")
        .style("opacity", "0");

      // THIS IS PROBLEMATIC
      var lines = document.getElementsByClassName('line');

      var mousePerLine = mouseG.selectAll('.mouse-per-line')
        .data([0,1]) //   cities)
        .enter()
        .append("g")
        .attr("class", "mouse-per-line");

      mousePerLine.append("circle")
        .attr("r", 7)
        .style("fill", "none")
        .style("stroke-width", "1px")
        .style("stroke", function(d,i){
          return (i === 0) ? "steelblue" : "red";
        })
        .style("opacity", "0");

      mousePerLine.append("text")
        .attr("transform", "translate(10,3)");

      mouseG.append('svg:rect') // append a rect to catch mouse movements on canvas
        .attr('width', width) // can't catch mouse events on a g element
        .attr('height', height)
        .attr('fill', 'none')
        .attr('pointer-events', 'all')
        .on('mouseout', function() { // on mouse out hide line, circles and text
          d3.select(".mouse-line")
            .style("opacity", "0");
          d3.selectAll(".mouse-per-line circle")
            .style("opacity", "0");
          d3.selectAll(".mouse-per-line text")
            .style("opacity", "0");
        })
        .on('mouseover', function() { // on mouse in show line, circles and text
          d3.select(".mouse-line")
            .style("opacity", "1");
          d3.selectAll(".mouse-per-line circle")
            .style("opacity", "1");
          d3.selectAll(".mouse-per-line text")
            .style("opacity", "1");
        })
        .on('mousemove', function() { // mouse moving over canvas
          var mouse = d3.mouse(this);
          d3.select(".mouse-line")
            .attr("d", function() {
              var d = "M" + mouse[0] + "," + height;
              d += " " + mouse[0] + "," + 0;
              return d;
            });

          d3.selectAll(".mouse-per-line")
            .attr("transform", function(d, i) {

              var beginning = 0,
                end = lines[i].getTotalLength(),
                target = null,
                pos = null;

              while (true) {
                target = Math.floor((beginning + end) / 2);
                pos = lines[i].getPointAtLength(target);
                if ((target === end || target === beginning) && pos.x !== mouse[0]) {
                  break;
                }
                if (pos.x > mouse[0]) end = target;
                else if (pos.x < mouse[0]) beginning = target;
                else break; //position found
              }
              
              var y = (i === 0) ? y_left_scale : y_right_scale;

              d3.select(this).select('text')
                .text(y.invert(pos.y).toFixed(2));

              return "translate(" + mouse[0] + "," + pos.y + ")";
            });
        });
    }
  </script>

  <button onclick="myFunction()"> Show me the Graph! </button>


</body>

</html>
Mark
  • 106,305
  • 20
  • 172
  • 230