Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to avoid overlapping tooltips of multi-series line chart d3.js

I' ve created tooltips on multi-series line chart following the answer here. If I mouse over the last date as you can see in this picture:

enter image description here

the tooltips are overlapping. What I want is when tooltips are overlapping, move any of them a little higher or lower. I was trying to do this by changing the code below.

   var beginning = 0,
        end = lines[i].getTotalLength(),
        target = null;
    //console.log(lines[i])             
    //console.log(end)
    while (true){
      target = Math.floor((beginning + end) / 2);
      pos = lines[i].getPointAtLength(target);
      if ((target === end || target === beginning) && pos.x !== mouse[0]) {
          break;
      }
      console.log(pos)
      if (pos.x > mouse[0])      end = target;
      else if (pos.x < mouse[0]) beginning = target;
      else break; //position found
    } 

My thought was recalculating the end. If the substraction of lines[0].getTotalLength() and lines[1].getTotalLength() is less than or larger than a value, then update the value of end(eg. end = end + 20).But I didn't get the code work here.

Does anybody know how to do this? Or is there an easier way to avoid tooltips overlapping?

like image 553
asas Avatar asked Mar 02 '16 20:03

asas


1 Answers

See changes here:

https://jsfiddle.net/fk6gfwjr/1/

Basically the tooltips need to be sorted by y position, and then we make sure neighbouring tooltips in that sort order are separated by a minimum distance (i picked 15px). The offset to the previously calculated y position is then added to the tooltip text. I also coloured the text to make them easier to tell which is which.

    var ypos = [];

    d3.selectAll(".mouse-per-line")
      .attr("transform", function(d, i) {
        // same code as before
        // ...
          // add position to an array
          ypos.push ({ind: i, y: pos.y, off: 0});

        return "translate(" + mouse[0] + "," + pos.y +")";
      })
      // sort this array by y positions, and make sure each is at least 15 pixels separated
      // from the last, calculate an offset from their current y value,
      // then resort by index
      .call(function(sel) {
        ypos.sort (function(a,b) { return a.y - b.y; });
        ypos.forEach (function(p,i) {
            if (i > 0) {
            var last = ypos[i-1].y;
           ypos[i].off = Math.max (0, (last + 15) - ypos[i].y);
            ypos[i].y += ypos[i].off;
          }
        })
        ypos.sort (function(a,b) { return a.ind - b.ind; });
      })
      // Use the offset to move the tip text from it's g element
      // don't want to move the circle too
      .select("text")
        .attr("transform", function(d,i) {
            return "translate (10,"+(3+ypos[i].off)+")";
        }
      ;
like image 113
mgraham Avatar answered Oct 23 '22 11:10

mgraham