Question

I have a treemap that I've created. Now I'm trying to get the hover over to work properly. I would like the text for each treemap.leaf to only appear when the user hovers over that particular leaf.

I've tried to follow this example to no avail http://mbostock.github.com/protovis/docs/interaction.html

The code I have so far is as follows:

var json = {
    "sectors":{
        "electronics": { "Sony": 85, "AMD": 70, "Techtronics": 20, "Apple": 220, "Microsoft": 340},
        "automotive": {"Chevy": 43, "Audi":120, "BMW": 200}},
    "ids":{"Sony":72833,"AMD":582926,"Techtronics":839261, "Apple":822463, "Microsoft":242512, "Chevy":627363, "Audi":524362,"BMW":25143}   
};

var tree = json.sectors;
var ids = json.ids;

var nodes = pv.dom(tree).root("tree").nodes();
color = pv.Colors.category10().by(function(d){return  d.parentNode.nodeName});

var vis = new pv.Panel()
 .width(400)
 .height(400)
 .canvas("test");
var treemap = vis.add(pv.Layout.Treemap)
 .nodes(nodes)
 .round(true);  

treemap.leaf.add(pv.Panel)
 .def("active", false)
 .fillStyle(function(d) d.active ? "lightcoral" : color(d))
 .strokeStyle("#fff")
 .lineWidth(1)
 .antialias(false)
 .cursor("pointer")
 .event("mouseover", function(d) { return this.active(true)});

treemap.label.add(pv.Label)
 .visible(function() {return this.parent.children[0].active()})
 .textStyle(function(d) {return pv.rgb(0, 0, 0, 1)});

vis.render();
Was it helpful?

Solution

There are a couple of issues here:

  • When you use the .event() method, and the function you pass in returns an instance of pv.Mark, Protovis will re-render the mark and its children. (The documentation is pretty opaque about the requirement that you return the mark you want to re-render.)

  • Within the Treemap layout, labels are not the children of the nodes - they're a separate group of children of the layout. So when you update a node, you won't get the corresponding label to re-render.

  • You have a typo in the line:

    .fillStyle(function(d) d.active ? "lightcoral" : color(d))
    

    d is the data, not the instance. It should be:

    .fillStyle(function() this.active() ? "lightcoral" : color(d))
    

    But as noted above, this still won't update the label (and while I didn't play with this too much, just correcting this line seems to highlight all of the nodes, not just the one you're over).

So to fix all this, you want to set the active def on treemap, not on the node. Instead of just using true/false, you can set the index of the active node, and then use the same index to refer to the label:

var treemap = vis.add(pv.Layout.Treemap)
 .nodes(nodes)
 .round(true)
 // define the active node on the layout
 .def("active", -1);  

treemap.leaf.add(pv.Panel)
 .fillStyle(function(d) { 
     return treemap.active() == this.index ? "lightcoral" : color(d) 
 })
 // ...
 .event("mouseover", function() { 
     return treemap.active(this.index);
 })
 .event("mouseout", function() { 
     return treemap.active(-1);
 });

treemap.label.add(pv.Label)
 .visible(function() {
     return treemap.active() == this.index;
 });

Working jsFiddle here.

The downside here is that you're re-rendering the entire treemap each time. I think there's probably a way to only re-render the specific node and label, but it would be more complex, so if the performance doesn't seem to be an issue, I wouldn't bother.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top