- Expert Data Visualization
- Jos Dirksen
- 517字
- 2025-04-04 19:31:10
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:
- 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.
- 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.
- Now that we have the positions, we move the circles and line we created to those positions.
- 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.