Using svg:text as tooltips

4,956 views
Skip to first unread message

Manu

unread,
Jun 7, 2011, 6:50:52 PM6/7/11
to d3-js
Hi there,

I'm having a lot of fun exploring the examples and working with d3.
One thing that has come up often is showing some sort of tooltip. I'm
trying to do a pretty basic thing and would love ideas/opinions on
best practices to do this sort of thing. Basically, I have a bunch of
svg:circles and I want a nicely styled svg:text show up on mouse hover
for each of them. Here's a basic idea of what I have so far:

var vis = d3.select(visualizationDiv)
.append("svg:svg")
.attr("width", w)
.attr("height", h);

var node = vis.selectAll("g.country")
.data(someData)
.enter().append("svg:g")
.attr("class", "country")
//other stuff including translation


node.append("svg:circle")
.attr("r", 0)
.attr("stroke-width", function(d){return d.value*0.5;})
.attr("stroke", "white")
.attr("fill", "#9acd32")
.on("mouseover",function(d,i)
{ d3.select(this).transition().duration(300).attr("fill","#bada55"); })
.on("mouseout",function(d,i)
{ d3.select(this).transition().duration(300).attr("fill","#9acd32"); });

I want to be able to access the 'node' selection from within the
callback to the mouse events so as to append the svg:text node to it
with the translation applied. So, this is something I have in mind:
.on("mouseover",function(d,i)
{ d3.select(this).parent().insert("svg:text").text(d.value); })

Of course, the parent method doesn't exist, but is this the best way
to do this sort of thing? And if so, how to access the parent
selection (the svg:g nodes). If I attach the listener to the svg:g
itself, how do I access the child nodes? Um, yeah.

Thanks for any replies/opinions!

-Manu


Nelson Minar

unread,
Jun 7, 2011, 8:19:20 PM6/7/11
to d3...@googlegroups.com
On Tue, Jun 7, 2011 at 3:50 PM, Manu <manuk...@gmail.com> wrote:
One thing that has come up often is showing some sort of tooltip.

A really low-tech solution is to hang an svg:title off the node you want the tooltip for. They show up after a delay on mouseover. It's not very good UI but it's really easy for a simple tooltip. There's an example here: http://mbostock.github.com/d3/ex/pack.html

I'm using titles on http://windhistory.com/ but am not very satisfied. I've been thinking of adding an absolute positioned floating div to my page and filling it + unhiding it on mouseover. There's a zillion Javascript libraries that do this too; a D3esque tooltip library would be welcome.

I want to be able to access the 'node' selection from within the callback to the mouse events so as to append the svg:text node to it with the translation applied.

One way to do this is to capture the node in a closure for the callback function for the mouse event. I don't have sample code on hand, sorry.

Justin Donaldson

unread,
Jun 7, 2011, 8:33:48 PM6/7/11
to d3...@googlegroups.com
I think this should get you what you want, but I haven't tested:

.on("mouseover",function(d,i)
{ d3.select(this.parentNode)
   .insert("svg:text", this.nextSibling).text(d.value);  })

insert() has an undocumented secondary argument of "before" that helps to direct d3 where to put the new html node.

basically, select the parent node of "this", and insert the svg text before the next sibling of  "this".  That should insert the svg:text directly after the node that caused the mouse event.
svg:text can't be a child node of an svg:shape, so the best place to put it is directly after what you want it on top of, with appropriate positioning arguments.

-Justin

On Tue, Jun 7, 2011 at 3:50 PM, Manu <manuk...@gmail.com> wrote:



--
Justin Donaldson, BigML, Inc.
o: 313-31BIGML | c: 919-BUZZJJD

Mike Bostock

unread,
Jun 7, 2011, 9:30:52 PM6/7/11
to d3...@googlegroups.com
> insert() has an undocumented secondary argument of "before" that helps to
> direct d3 where to put the new html node.

It's documented, but I need to make the API reference more visible
from the website. So much to do!

https://github.com/mbostock/d3/wiki/Selections#insert

I had a fairly good experience using the Tipsy plugin with Protovis;
it'd be possible to do the same with D3, and a bit easier to determine
the correct positioning by inspecting the DOM elements. In particular,
the getBBox method is handy for determining the bounding box of an
arbitrary SVG elements:

http://www.w3.org/TR/SVG/types.html#__svg__SVGLocatable__getBBox

Mike

Manu

unread,
Jun 8, 2011, 5:32:01 AM6/8/11
to d3-js
Thanks for the help everyone. Justin, your snippet got me through!
I've used the svg:titles before, this time I wanted something a bit
fancier :) I have the text nodes precariously located based on subtle
calculations derived from datum values and the translation values of
the svg:g nodes. Basically it's a stop-gap solution at this point, but
it gets the job done. As the project matures, I'm certain a basic UI
library to deal with such common tasks wouldn't go amiss. But then, it
is fairly trivial to use d3 to come up with custom solutions. I will
check out Tipsy and see what it has to offer.

Thanks again!
-Manu

wimdows

unread,
Jun 9, 2011, 10:12:45 AM6/9/11
to d3-js
These svg:titles work just fine for me. If it wasn't for one thing: I
can't get them to update in a transistion.

I have some code like this:
vis.selectAll("circle.dot")
.data(currentselection)
.enter().append("svg:circle")
.attr("class", "dot")
.attr("cx", function(d, i) { return x(i); })
.attr("cy", function(d) { return y(d.totaal); })
.attr("r", 2.5)
.style("fill", function(d) { return d.totaal !=0 ? "red" :
"dimgrey" })
.style("stroke", function(d) { return d.totaal !=0 ? "red" :
"dimgrey" })
.append("svg:title")
.text(function(d) { return d.naam + ": " + d.totaal; });

And it works fine.
But I can't seem to get these titles to update together with the
circle.dots they are appended to.
How is that done?

wimdows

unread,
Jun 9, 2011, 10:18:31 AM6/9/11
to d3-js
I also tried a version in which I replaced the svg:titles with xlinks,
like so:
.attr("xlink:title", function(d) { return d.naam + ": " +
d.totaal; })
because I thought they were more likely to upgrade with the
svg:circles, but that did work either.
The tooltips show up alright, but the info in them still doesn't
refresh when I update the data in the svg:circles.

Mike Bostock

unread,
Jun 9, 2011, 10:28:41 AM6/9/11
to d3...@googlegroups.com
> But I can't seem to get these titles to update together with the
> circle.dots they are appended to.

Something like:

vis.selectAll("circle.dot").transition()
… transition circle properties here …
.select("title")
.text(function(d) { … compute new title text here … });

The title text isn't interpolated, but simply set to the new value
when the transition starts.

Mike

wimdows

unread,
Jun 9, 2011, 10:55:36 AM6/9/11
to d3-js
Of course! That's it. I had something like: .selectAll("svg:title")
and then the new attributes.
But there is only one title per dot! And that's probably also the
reason we don't need the "svg:" bit.
Sorry to keep bothering you with these beginners mistakes, Mike.
(Although I must admit I'm having a lot of fun making and correcting
them! :) )

Chris Viau

unread,
Jun 9, 2011, 10:57:14 AM6/9/11
to d3...@googlegroups.com
My naive solution would be to have an invisible div that can just appear at the right place. The div can contain a SVG. 

Mike Bostock

unread,
Jun 9, 2011, 11:39:39 AM6/9/11
to d3...@googlegroups.com
> But there is only one title per dot! And that's probably also the
> reason we don't need the "svg:" bit.

Right. Use select if there's one child (per parent); this propagates
the parent data, and preserves the grouping:

https://github.com/mbostock/d3/wiki/Selections#select

As for the "svg:" bit, that's a bit of an inconsistency between
selecting and creating. When selecting, you don't need to specify the
namespace. (If you did, it would look like "svg|title", but browsers
don't currently support namespaced selectors.) However, you do need to
specify the namespace when creating new nodes, hence, "svg:title".

I could make it a little bit shorter by defaulting to the namespace of
the parent node, so that if you're in svg, you don't have to specify
"svg:" when appending. But I'm not sure that's a great idea, because
you'd still need to remember to say "svg:svg" when you create the
outer SVG container.

Mike

wimdows

unread,
Jun 9, 2011, 11:49:20 AM6/9/11
to d3-js
Thanks, Mike. It was another educational day, I must say.
(There is still a lot to learn. But I'm nearing the point were I think
I can offer my services as a (D3/Protovis) datavisualiser to the media
over here in the Netherlands.

That namespace bit I will just have to remember. (I used it right away
in another bit of code in the same graph I was having trouble with.)

wimdows

unread,
Jun 10, 2011, 3:39:40 AM6/10/11
to d3-js
On 9 jun, 16:57, Chris Viau <christophev...@gmail.com> wrote:
> My naive solution would be to have an invisible div that can just appear at
> the right place. The div can contain a SVG.
> Example <http://bl.ocks.org/1016860>

Seems a decent solution to me. I'll keep it in mind for the future.
But I have my tooltips working properly now. And I see why people
dislike them, but I always sort of had a weak spot for them, so for
now I'll keep them. ;)
Reply all
Reply to author
Forward
0 new messages