Lezhi Li Lezhi Li - 1 month ago 11
Javascript Question

d3 with bootstrap tooltip: titles not working with data updating

I'm creating a bar chart where hovering over each rectangle will generate a tooltip. So I created a "title" attribute for each rectangle and this was working

var hist = this.svg.selectAll(".hist")
.data(displayData, function(d) { return d.time; });
hist.enter().append("rect")
.attr('title', function (d) {
return tooltipDateFmt(d.time) + ": " + d.value.toFixed(1) + " gallons"; })
.attr("class", "hist");

hist.transition().duration(400)
.attr("x", function (d) { return x(d.time); })
.attr("y", function (d) { return y(d.value); })
.attr("width", function (d) { return 2.5; })
.attr("height", function (d) { return height - y(d.value); });
hist.exit().remove();

$('svg .hist').tooltip({
'container': 'body',
'placement': 'top'
});


But I then realized the titles are not updating when I updated the rects by clicking on a button and bind a new set of data into them. So I put the title in the update phase

var hist = this.svg.selectAll(".hist")
.data(displayData, function(d) { return d.time; });
hist.enter().append("rect")
.attr("class", "hist");

hist.transition().duration(400)
.attr('title', function (d) {
return tooltipDateFmt(d.time) + ": " + d.value.toFixed(1) + " gallons"; })
.attr("x", function (d) { return x(d.time); })
.attr("y", function (d) { return y(d.value); })
.attr("width", function (d) { return 2.5; })
.attr("height", function (d) { return height - y(d.value); });
hist.exit().remove();


But this is not showing any tooltips. Can anyone tell me what I have been wrong with?

Answer

bootstrap tooltip inserts a div (with the original title) into the DOM for each tooltip and then hides/shows it on mouseover. Changing the title later does not change the inserted div.

Instead of setting a title, I would use the title option and return a dynamic title.

  $('svg .hist').tooltip({
    'container': 'body',
    'placement': 'top',
    'title': function(){
       var d = d3.select(this).datum(); // get data bound to rect
       return tooltipDateFmt(d.time) + ":  " + d.value.toFixed(1) + " gallons"; });
    }
  });

Here's a working example:

<!DOCTYPE html>
<html>

  <head>
    <meta charset="utf-8" />
    <link data-require="bootstrap-css@3.3.1" data-semver="3.3.1" rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.1/css/bootstrap.min.css" />
    <script data-require="jquery@2.1.3" data-semver="2.1.3" src="http://code.jquery.com/jquery-2.1.3.min.js"></script>
    <script data-require="bootstrap@3.3.2" data-semver="3.3.2" src="//maxcdn.bootstrapcdn.com/bootstrap/3.3.2/js/bootstrap.min.js"></script>
    <style>

body {
  font: 10px sans-serif;
}

.axis path,
.axis line {
  fill: none;
  stroke: #000;
  shape-rendering: crispEdges;
}

.bar {
  fill: steelblue;
}

.x.axis path {
  display: none;
}

    </style>
  </head>

  <body>
    <button id="changeTips">Change Tips</button>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.5/d3.min.js"></script>
    <script>

var data = [
    {
      x: 1660,
      y: 1
    },{
      x: 1670,
      y: 2
    },{
      x: 1680,
      y: 3
    },{
      x: 1690,
      y: 4
    }
  ];

var margin = {top: 20, right: 20, bottom: 30, left: 40},
    width = 960 - margin.left - margin.right,
    height = 500 - margin.top - margin.bottom;

var x = d3.scale.linear()
  .range([0,width])
  .domain([1650, 1700]);

var y = d3.scale.linear()
    .range([height, 0])
    .domain([0,5]);

var xAxis = d3.svg.axis()
    .scale(x)
    .orient("bottom");

var yAxis = d3.svg.axis()
    .scale(y)
    .orient("left");

var svg = d3.select("body").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 + ")");

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

  svg.append("g")
      .attr("class", "y axis")
      .call(yAxis);
      
  var barWidth = (width / xAxis.ticks()[0]);

  var bars = svg.selectAll(".bar")
    .data(data)
    .enter().append("rect")
    /*
     .attr('title', function (d) { 
       return d.y;
     })
    */
    .attr("class","bar")
    .attr("width", barWidth)
    .attr("x", function(d) { return x(d.x) - (barWidth / 2); })
    .attr("y", function(d) { return y(d.y); })
    .attr("height", function(d) { return height - y(d.y); });
    
    
    var showY = true;
    $('svg .bar').tooltip({
        'container': 'body',
        'placement': 'top',
        'title': function(){
          if (showY)
            return (d3.select(this).datum().y);
          else
            return (d3.select(this).datum().x);
        }
      });

    d3.select('#changeTips').on('click',function(){
      showY = !showY;
    });
    
    </script>
  </body>

</html>