We need a small chunk of logic to tell our app when to show and when to hide the tooltip. With SVG, this logic is usually straightforward, as we can leverage mouseover and mouseout. With Canvas, we only really have mousemove on the entire Canvas. So, we build our own mouseover and mouseout logic. We start in the mousemove handler called highlightPicking():
function highlightPicking() {
// Here, you find the country index and store it in pickedColor
// and you check if the user’s mouse is in the globe or not with inGlobe
selected = inGlobe && pickedColor[3] === 255 ? pickedColor[0] : false;
requestAnimationFrame(function() {
renderScene(countries, selected);
});
var country = countries.features[selected];
if (selected !== false) showTooltip(pos, country); // build tooltip
if (selected === false) hideTooltip(); // remove tooltip
}
You store the data of the country the mouse is over in country. If selected holds a number (a country index), we trigger the creatively named function showTooltip() and pass it the main Canvas’s mouse positions and the country. If selected returns false, the mouse is not over a country, and we will trigger the equally creatively named function hideTooltip().
The key thing you want to figure out in showTooltip() is when to build a new tooltip and when to just move the existing tooltip. You want to build a new tooltip when the mouse moves from one country to another country. You just want to move the tooltip along with the mouse when the mouse is within the borders of a specific country.
We will achieve this by an array that will work like a queue. You can imagine a stack to stand up vertically, able to only add new data to the top or remove data from the top. In contrast, you can imagine a queue horizontally like a queue in front of an ice-cream shop. People arrive in the queue at the back and leave the queue at the front.
Our queue will, however, only be two-people long. In fact, it won’t be two people long but two countries long. Whenever we move the mouse, the queue will be fed the country we’re over to one side of it (the front actually), immediately pushing off the country at the other side (the back). When we’re moving from one spot in the US to another spot in the US, it will say [“United States of America”, “United States of America”]. As soon as our mouse moves effortlessly over to Mexico, it will add “Mexico” at the front of the queue, pushing the previously 0-indexed “United States of America” to index position 1 and cutting off the array right there. Now, we have an array with [“Mexico”, “United States of America”].
Checking whether we change a country is now a simple affair of comparing the two values in our queue. If they are the same, we just move the mouse; if they are different, we create a new tooltip for Mexico.
This is a textbook example of why SVG or HTML is often preferred over Canvas when the application is interaction heavy. Still, that wasn’t too bad, was it? Let’s implement it. First, you will need to define your yet-empty queue:
var countryQueue = [undefined, undefined];
Then, you need to write showTooltip(), taking in the mouse positions and the element, that is, the country the mouse is over:
function showTooltip(mouse, element) {
var countryProps = element.properties;
countryQueue.unshift(countryProps.admin);
countryQueue.pop();
You save the country’s data in countryProps, add the country’s name to the front of the queue with JavaScript’s own .unshift() method, and pop() off the last value from the queue.
Then, we will establish if there is a country change or not:
if (countryQueue[0] !== countryQueue[1]) {
var headHtml =
'Forest cover: ' + formatPer(countryProps.forest_percent) + '' +
'<br>Forested area: ' + formatNum(countryProps.forest_area) + '
km<sup>2</sup>';
d3.select('#tip-header h1').html(countryProps.admin);
d3.select('#tip-header div').html(headHtml);
svg.selectAll('.bar').attr('fill', function(d) { return d.color; });
d3.select('#' + stripString(countryProps.admin)).attr('fill', 'orange');
d3.select('#tooltip')
.style('left', (mouse[0] + 20) + 'px')
.style('top', (mouse[1] + 20) + 'px')
.transition().duration(100)
.style('opacity', 0.98);
If there is one, you fill the tooltip’s header with the country-specific information. You also color all bars according to the appropriate country color before the bar of this specific country gets colored red. The rest is just moving the tip along with the mouse and cranking its opacity up to make it visible.
If the queue values are the same, you just move the tip:
} else {
d3.select('#tooltip')
.style('left', (mouse[0] + 20) + 'px')
.style('top', (mouse[1] + 20) + 'px');
}
}
Remember, showTooltip() gets shown every time the mouse is over a country, and our selected variable gets filled with a country index. If selected is false, we know we’re not over a country, meaning that we want to remove our tooltip. We do this with, well, hideTooltip():
function hideTooltip() {
countryQueue.unshift(undefined);
countryQueue.pop();
d3.select('#tooltip')
.transition().duration(100)
.style('opacity', 0);
}
We decided to appropriately allocate undefined to the queue if we’re not over a country, so we unshift() it to the front of the queue and pop() off the last value of the array to always keep it in pairs we can compare at the next move. Finally, we will transition the opacity back to zero and it is gone again. That’s it! All done.