The tooltip… right. Well, first thing’s first:
that sounds like a recipe for disaster right there. Stick with one thing. Make it work. Then get fancy with it.
I’m going to do things slightly differently than most of these examples, but lets walk through my theory.
Step 1: Create the tooltip.
Right, well, we know how to append stuff to our SVG. So lets do that. I’ma make a group, with a rect in it, and plop a text element in there so i can play around with it later.
let tooltip = svg.append("g")
.attr("transform","translate(100,100)");
tooltip.append("rect")
.attr("width",70)
.attr("height",30)
.attr("fill", "#CCC")
.attr("stroke", "black")
.attr("rx",10);
tooltip.append("text")
.attr("y", 19)
.attr("x",4)
.attr("font-family","Verdana")
.attr("font-size", 10)
.text("I'm a Tooltip");
Most of this stuff is pretty self-explanatory or just “because I felt like making it look that way.”
Important things to note: I’ve got a reference to my tooltip’s group element, tooltip
. That will be necessary later.
Step 2: Babby’s First Movement.
(Remember; keep it simple, make it work, then make it fancy.)
Right, so we’ve got our tooltip. What do we want the tooltip to do?
We want the tooltip to move when we hover over a bit of our graph with some data.
What in our graph has data?
The circles.
So we want, when we hover over a circle, to put the tooltip there, and make it show us the data.
Obviously, this means we need a mouseover event. And we want to go on our circles. So lets go attach our event to the circles.
scatter
.selectAll("dot")
.data(dataset1)
.enter()
.append("circle")
.attr("cx", function (d) { return xScale(d[0]); } )
.attr("cy", function (d) { return yScale(d[1]); } )
.attr("r", 3)
.attr("transform", "translate(" + 100 + "," + 100 + ")")
.style("fill", "#CC0000")
.on("mouseover",movetooltip)
annd…the javascript engine starts screaming at us cause we havent defined movetooltip. Understandable. So let’s write one.
When we trigger this event, it will carry with it two parameters (note that this is because it’s a d3 on
bind, and not a standard eventListener
): the first is the data point of the object, the second is it’s index (may or may not be relevant for other things. We dont really care about it at the moment).
So we hover over something, and we get its data. Well, its data tells us where to put the tooltip, because we can run the numbers through our scales the same as we did when we created the circle.
The tooltip group has a translate applied to it. We can manipulate that, to move the rect and the text along with it.
function movetooltip(d,i) {
I’m not going to be using i
, but its good to show it anyway. You can leave it out.
We want to accomodate for zooming, so we need to use our modified scales - newX and newY.
tooltip.attr("transform","translate("+newX(d[0])+","+newY(d[1])+")")
}
(we’re closing the function at the end there).
Run the script, annnnd… well thats not in the right place, is it?
We forgot that the graph is itself translated by 100,100. So we need to add that in, because our transform is grounded at 0,0…
tooltip.attr("transform","translate("+(100+newX(d[0]))+","+(100+newY(d[1]))+")")
And now our tooltip moves along the graph with our mouse. But it still just says “I’m a Tooltip”. Not very helpful. So we need to fix the text of the text
element within our tooltip.
We’ve got a local scope for our tooltip group, so lets run a select inside that scope.
tooltip.select("text")
and now modify the text of that text element. Keeping it simple and straightforward;
.text(d[0] + ": "+ d[1])
And now our tooltip updates itself.
At a basic level; job done, right?
Step 3: Improvements.
So we’ve kept it simple, we’ve made it work… now to get fancier.
There’s a few ways to go here, and it depends on the features you want in your tooltip.
Want the tooltip to disappear when not in use? you’ll need a mouseout event. This is a one-liner that doesnt need the data, so i’m going to inline it. Feel free to separate into a different function if you want for cleaner code.
.on("mouseout", function(){ return tooltip.style("visibility", "hidden"); });
We’ll hide the tooltip on pageload:
let tooltip = svg.append("g")
.attr("transform","translate(100,100)")
.style("visibility","hidden");
and make it appear when we hover over something:
tooltip.attr("transform","translate("+(100+newX(d[0]))+","+(100+newY(d[1]))+")")
.style("visibility","visible")
You can add your own fancy transitions if you like.
I’d like some clarity on my tooltip, what i’m looking at when i hover.
this
inside of a d3 event binder points to the element that caused the event to fire. In this case, that would be our circle. we can select
this
to get the d3 representation of that object, so we can use d3 methods on it. In this case, i’d like to know what color the circle was; i’m going to color my tooltip border with that color, so the tooltip looks like it belongs to the line, even though i’ve only got 1 tooltip for my entire graph.
We’ll grab the circle (though we could do this inline, maybe we want the circle for other things later; note that we could have used this reference to the circle to get its cx/cy for positioning our tooltip!), and use its Fill to Stroke the rect. We’ll need to select the rect, obviously, to do so.
let circle = d3.select(this);
tooltip.select("rect")
.attr("stroke", circle.style("fill"))
So now my tooltip will let me know what line i’m hovering over, too.
You’ve got the basics of a tooltip now. There are a few extra enhancements that i think you’ll come across you feel you need if you play with this code. I stuck with some basics here, even in the “fancy” section.