0

I am trying to add tooltip on a multiple lines chart following this example. I want the tooltips show on both lines at the same time when mouse over either of them, like the example here. In my real case, I have four lines.

Here is my jsbins https://jsbin.com/budonapeki/edit?html,js,console,output Hit "Run with JS" on the top right corner to see the chart.

My questions are...

  1. In my jsbins, the tooltips are only showing on the first point of the line and the other tooltips do not show when mouse moves. Can anyone help to point out why?

  2. How can I make the tooltips showing on the lines at the same time without repeating too many codes? Since I am using d3.nest()to convert the array, I am not sure if this affect the work of tooltips.

Appreciate!

Updated

Following the link in Mark's comment, I get the tooltips done finally. But I have some other questions...

This is my updated JSbins https://jsbin.com/hoceneneso/edit?html,js,console,output

The first question is.. In my chart, there are two buttons on the right side, the line can disappear or appear when click the buttons. And the corresponding tooltip of the line should disappear or appear with the line together. But in my chart, the tooltips are still there, even I click the button.

I was trying to remove and change the opacity of the tooltips, but I am still not able to make it work. Does anyone have idea about this?

Second question is ..I was trying to make the tooltip start showing at "name1" which is the start point of the lines. I know the grey rectangle is to catch mouse movements on canvas, so I was trying to move the rectangle by .attr("transform", "translate(180,3)") but tooltips still show when mouseover y-axis. Can any explain why and suggestions?

Thanks a lot!

merylz
  • 175
  • 3
  • 11
  • I feel like I've answered this question too many times before: http://stackoverflow.com/a/34887578/16363 – Mark Feb 16 '16 at 19:54
  • Thanks Mark! I got the tooltips done and updated my questions. Do you have any idea? – merylz Feb 16 '16 at 23:23

1 Answers1

3

First question, set a unique id to each "mouse-per-line" so you can toggle it's opacity:

var mousePerLine = mouseG.selectAll('.mouse-per-line')
  .data(dataNest1)
  .enter()
  .append("g")
  .attr("class", "mouse-per-line")
  .attr("id", function(d){
    return "mouse-per-line-" + d.key;
  });   

In your legend click handler:

d3.select("#mouse-per-line-" + d.key)
  .style("opacity", newOpacity);

Second question, instead of transform to move the rect, set the x and width attributes. You can make it dynamic by:

mouseG.append('svg:rect') // append a rect to catch mouse movements on canvas
  .attr('x', x(dataNest1[0].values[0].x))
  .attr('width', x(dataNest1[0].values[dataNest1[0].values.length - 1].x) - x(dataNest1[0].values[0].x)) 
  ...

Updated code:

var data1 = [
      {x: "Name1", y: 2.5, label: "A"},
   {x: "Name2", y: 3.5, label: "A"},
      {x: "Name3", y: 4.7, label: "A"},
      {x: "Name1", y: 4.7, label: "B"},
   {x: "Name2", y: 3.5, label: "B"},
      {x: "Name3", y: 4.9, label: "B"},
  ];  
 var margin = {top: 20, right: 150, bottom: 60, left: 80},
  width = 1160 - margin.left - margin.right,
  height = 500 - margin.top - margin.bottom; 
 var x = d3.scale.ordinal().
  rangeBands([0, width], 0.4, 0.8);
 var y = d3.scale.linear()
  .range([height, 0]);
 var xAxis = d3.svg.axis()
  .scale(x)
  .orient("bottom");
 var yAxis = d3.svg.axis()
  .scale(y)
  .orient("left");
 var line = d3.svg.line()
  .interpolate("basis")  
  .x(function(d) { return x(d.x); })
  .y(function(d) { return y(d.y); });
 var svg = d3.select("#lineChart").append("svg")
  .attr("width", width + margin.left + margin.right)
  .attr("height", height + margin.top + margin.bottom)
  .append("g")
  .attr("transform", "translate(" + margin.left + "," + margin.top + ")");
 x.domain(data1.map(function(d) { return d.x; }));
 y.domain([0, d3.max(data1, function(d) { return d.y; })]); 
 svg.append("g").
      attr("class", "x axis").
      attr("transform", "translate(-70," + height + ")").
      call(xAxis);
    svg.append("g").
      attr("class", "y axis").
      call(yAxis);
   
 var dataNest1 = d3.nest()
        .key(function(d) {return d.label;})
        .entries(data1);
  //console.log(dataNest1)
  var color = d3.
        scale.
        ordinal().
        range(['red', 'blue']).
        domain(d3.keys(data1[0]).
        filter(function(key) {return key === 'label';})); 
  var legendSpace = width/dataNest1.length; 
  
    dataNest1.forEach(function(d,i) { 
    
        svg.append("path")
            .attr("class", "line1")
            .style("stroke", function() {
                return d.color = color(d.key); })
            .attr("id", 'tag'+d.key.replace(/\s+/g, '')) // assign ID **
            .attr("d", line(d.values));
   
        svg.append("text")
            .attr("x", width - margin.left + 50)
            .attr("y", legendSpace/4 + i*(legendSpace/6))
            .attr("class", "lineLegend1")
   .attr("id", 'tagLegend'+d.key.replace(/\s+/g, '')) // assign ID **
            .style("fill", function() {
                return d.color = color(d.key); })
            .on("click", function(){               
                console.log(d);
                // Determine if current line is visible 
                var active   = d.active ? false : true,  
                newOpacity = active ? 0 : 1;         
                // Hide or show the elements based on the ID
                d3.select("#tag"+d.key.replace(/\s+/g, ''))
    //.remove(); 
                    .transition().duration(500)         
                    .style("opacity", newOpacity);
    //d3.selectAll(".mouse-per-line circle")
    // .style("opacity", newOpacity);
    //d3.selectAll(".mouse-per-line text")
    // .style("opacity", newOpacity);     
                // Update whether or not the elements are active
                d.active = active; 
                d3.select("#mouse-per-line-" + d.key)
                  .style("opacity", newOpacity);
          
                })
            .text(d.key); 
 
   });      
 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");
   
 var lines = document.getElementsByClassName('line1');

    var mousePerLine = mouseG.selectAll('.mouse-per-line')
      .data(dataNest1)
      .enter()
      .append("g")
      .attr("class", "mouse-per-line")
      .attr("id", function(d){
        return "mouse-per-line-" + d.key;
      }); 
 
    mousePerLine.append("circle")
      .attr("r", 7)
      .style("stroke", function(d) {
        return color(d.key);
      })
      .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('x', x(dataNest1[0].values[0].x))
      .attr('width', x(dataNest1[0].values[dataNest1[0].values.length - 1].x) - x(dataNest1[0].values[0].x)) 
      .attr('height', height)
      .attr('fill', 'grey')
   .style('opacity', '0.4')
      .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 xQuater = y.invert(d3.mouse(this)[0]),
                bisect = d3.bisector(function(d) { return d.x; }).right;
                idx = bisect(d.values, xQuater);
            
            var beginning = 0,
                end = lines[i].getTotalLength(),
                target = 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
            }
            
            d3.select(this).select('text')
              .text(y.invert(pos.y).toFixed(2));
              
            return "translate(" + mouse[0] + "," + pos.y +")";
          });
      });
<!DOCTYPE html>
<html>
  <head>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.6/d3.min.js"></script>
    <meta charset='utf-8'>
    <title>Charts</title>  
    </head>
  <body>
    <div align="center" id="lineChart">
    </div>  
  <style>
  .axis {
   font-family: Helvetica;
 font-size: 1em;
 font-weight: bold;
 color: #444444;
}
.axis path,
.axis line {
  fill: none;
  stroke: #000;
  shape-rendering: crispEdges;
}

.line2{
  fill: none;
  stroke: red;
  stroke-width: 1.5px;
}
.line1{
  fill: none;
  stroke: blue;
  stroke-width: 1.5px;
} 
  </style>
  </body>
</html>
Mark
  • 106,305
  • 20
  • 172
  • 230
  • Mark, can you help to answer the second question in this post? http://stackoverflow.com/questions/35590226/tooltips-for-multiple-lines-chart-with-legend-click-d3-js Thanks! – merylz Feb 24 '16 at 19:08