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.

Open example in new window

...
    // 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)
      })
...

Open example in new window