Tooltip on a line (single line)
Doing a hover event is simple with a circle - you just say hey, when I mouse over the circle, do something! It’s different with a line, though, because they’re generally very very thin. As a result, you need to use a d3.bisector
to say “what’s the closest data point?”.
You can play around with it by clicking “Open example in new window,” but I’ve also put a downloadable version over here.
...
// Draw your SINGLE line (but remember it isn't called a line)
svg.append("path")
.datum(datapoints)
.attr("fill", "none")
.attr("stroke", "black")
.attr("d", line);
// Create the element that will be our tooltip
// by default it's hidden with display: none
// DO NOT GIVE IT THE CLASS TOOLTIP
// IT WILL CONFLICT WITH SOME BOOTSTRAP THING
// AND WILL NOT SHOW UP AND YOU'LL BE VERY
// VERY CONFUSED AND VERY VERY SAD
var tooltip = svg.append("g")
.attr("class", "tip")
.style("display", "none");
// give the tooltip a circle to highlight our
// data point
tooltip.append("circle")
.attr("r", 3);
// give the tooltip a text element, but push
// it to the right and down little bit
tooltip.append("text")
.attr("dx", 5)
.attr("dy", 15);
// draw an invisible rectangle over the ENTIRE page
// but even though it's invisible, make it catch
// everything your pointer (mouse) does
svg.append("rect")
.attr("fill", "none")
.style("pointer-events", "all")
.attr("width", width)
.attr("height", height)
.on("mousemove", function(d) {
// When the mouse is moved on top of the rectangle,
// compute what the data point is and where to draw it
// If you'd understand better, console.log all of these variables
// as we step through the process
// STEP ONE: Get the position of the mouse - how many pixels
// to the right is it?
var mouse = d3.mouse(this);
var mousePositionX = mouse[0];
// STEP TWO: Use the x position scale BACKWARDS to estimate
// the number of years for our mouse position
// if we're 200 pixels out, how many years would that be?
var mouseYear = xPositionScale.invert(mousePositionX);
// STEP THREE: We have a year, but it's probably not exactly
// on one of our data points. The bisector will take the
// year we're at and round it down to the closest 'actual' year
//
// e.g. our year is 1973 but we only have 1970 and 1975, so it
// figures that 1970 is the one to the left gives us back 1970
//
// (but it doesn't actually give us 1970 as a value, or even
// tell us what our datapoint is - it gives us back the index,
// saying "it's the 12th element")
var index = d3.bisector(function(d) { return d.Year; })
.left(datapoints, mouseYear);
// STEP FOUR: Use the index to get the datapoint
var d = datapoints[index];
// STEP FIVE: What's the x and y of that datapoint? Let's
// move the tooltip to there
var xPos = xPositionScale(d.Year);
var yPos = yPositionScale(d.life_expectancy);
d3.select(".tip")
.attr("transform", "translate(" + xPos + "," + yPos + ")");
// STEP FIVE: Use the datapoint information to fill in the tooltip
d3.select(".tip").select("text").text(d.life_expectancy + " years")
})
.on("mouseout", function(d) {
// Hide the tooltip when you're moving off of the visulization
svg.select(".tip").style("display", "none")
})
.on("mouseover", function(d) {
// Display the tooltip when you're over the rectangle
// why is it null? I dunno, stole it from
// https://bl.ocks.org/mbostock/3902569
svg.select(".tip").style("display", null)
})
...