Tracking the mouse

As a last step, we'll add the mouse support that highlights the data points, shows the values at that specific year, and adds a vertical line to make things more clear:

We do this in two steps. First, we create all the elements that make up the line and the markers, and after that we add a mouse listener, which will show these elements in the correct positions. Let's look at the elements:

function addMouseTracker(xScale, yIndexedScale, yIncomeScale, adjustedIndexedData, unadjustedCleaned) { 

...
var focus = chart.append("g").attr("class", "focus").style("display", "none");
focus.append("circle").attr("id", "indexCircle").attr("r", 4.5);
focus.append("circle").attr("id", "incomeCircle").attr("r", 4.5);
focus.append("text").attr("id", "indexText").attr("x", 9).attr("dy", ".35em");
focus.append("text").attr("id", "incomeText").attr("x", 9).attr("dy", ".35em");

var verticalLineP = d3.line()([[0,-10],[0,height+10]]); focus.append("path")
.attr("d", verticalLineP)
.attr("class", "verLine")
.attr("stroke", "grey")
.attr("stroke-dasharray", "6,6")
.attr("stroke-width", "1");

chart.append("rect")
.attr("class", "overlay")
.attr("width", width)
.attr("height", height)
.on("mouseover", function() { focus.style("display", null); })
.on("mouseout", function() { focus.style("display", "none"); })
.on("mousemove", mousemove);

...
}

We first add a group, which we hide (display: none), to which we add two circles, two text elements, and a vertical line. These are the elements we'll show when we move the mouse over the chart. We also add a rectangle that completely covers the chart. To this rectangle, we add our mouse listeners. As you can see, when we enter this rectangle, the focus g element becomes visible, and when we move out of the rectangle, the g element is hidden again. The interesting code, however, is in the mousemove() function:

function mousemove() { 
var x0 = xScale.invert(d3.mouse(this)[0])
var xToShow = Math.round(x0);
var d = adjustedIndexedData[xToShow-1984];
var dIncome = unadjustedCleaned[xToShow-1984];
var xPos = xScale(xToShow);
var yIncomePos = yIncomeScale(dIncome.value);
var yIndexPos = yIndexedScale(d.indexed);

focus.select("#indexCircle").attr("transform", "translate(" + xPos + "," + yIndexPos + ")");
focus.select("#incomeCircle").attr("transform", "translate(" + xPos + "," + yIncomePos + ")");
focus.select(".verLine").attr("transform", "translate(" + xPos + "," + 0 + ")");

var textOffset = (yIncomePos < yIndexPos) ? 5 : -5;

focus.select("#indexText")
.attr("transform", "translate(" + xPos + "," + (yIndexedScale(d.indexed) + textOffset) + ")")
.text(Math.round((d.indexed-100)*100)/100);
focus.select("#incomeText")
.attr("transform", "translate(" + xPos + "," + (yIncomeScale(dIncome.value) - textOffset) + ")")
.text("$ " + dIncome.value);

}

In this code, the following steps are taken whenever we move the mouse over the chart:

  1. We get the positon of the mouse inside the rectangle we defined, and get the value of x from that position (d3.mouse(this)[0]). With this x position, we can determine the year we're working with by using the xScale.invert function. So, at this point we know which year corresponds to the position of the mouse.
  2. Next, we get the relevant data (d and dIncome). With the year and these two values we can get the exact x and y positions of the data elements belonging to the year on the two lines: xPos, yIncomePos, and yIndexPos.
  3. Now that we have the positions, we move the circles and line we created to those positions.
  4. Finally, we move the text elements to the correct positions and set their values to the correct values.

And that's it. With this mouse listener added, we have a line and a set of circles that move to the correct points on the line whenever we move the mouse on the chart.