Search code examples
protovis

more than one set of labels in protovis?


I'd like to have both the values and the data categories on a graph. This is a bar chart and I'd like to have the data values and a string printed in columns off to the left of the bar:

A 1 #
B 3 ###

I tried chaining a two add(pv.Label) calls onto my graph, but it seems to do nothing - the second label set is not added. Is this something that can even be done with protovis? any advice?

vis = new pv.Panel()
.def("j", -1)
.width(800)
.height(50)
.right(3);

vis.add(pv.Bar)
.data(wData)
.bottom(0)
.width(20)
.height(function(d) d[1] * 1.2)
.left(function() this.index * 27)
.fillStyle(function() vis.j() == this.index ? "orange" : "steelblue")
.add(pv.Label)  **// does nothing!!**
.bottom(0)
.textAlign("center")
.textStyle("white")
.text(function(d) d[0] )
.event("mouseover", function() vis.j(this.index))
.event("mouseout", function() vis.j(-1))
.anchor("top").add(pv.Label)
.visible(function() vis.j() >= 0)
.textStyle("white")
.text(function(d) d[1]);
vis.render();

Solution

  • I actually did see both labels when I tried this out. But there are a couple of things that could be fixed here. The key point is that when you're chaining methods like this, when you add() a new mark, you change the context of the following method calls, e.g.:

    vis.add(pv.Bar)
        // this applies to the Bar
        .width(10)
      .add(pv.Label)
        // this applies to the label
        .top(5);
    

    There are a couple issues with this in your code:

    • Your event() handlers are attached to the Label, not to the Bar - unfortunately, Labels can't receive events in Protovis.

    • Your second Label is attached to the first Label. While this actually seems to work somewhat, it's better to avoid it - you really want it attached to the Bar.

    The easy way to deal with this is to only chain methods on a single mark. You can do this by assigning the parent mark to a variable, then using that variable several times for different child marks. You also have you first Label attached directly to the Bar, and not to an anchor - attaching it to an anchor will usually give you more predictable results.

    Updated code:

    // make a new variable to refer to the bars
    var bars = vis.add(pv.Bar)
        .data(wData)
        .bottom(0)
        .width(20)
        .height(function(d) d[1] * 1.2)
        .left(function() this.index * 27)
        .fillStyle(function() vis.j() == this.index ? "orange" : "steelblue")
        // you need to move the events up to apply 
        // to the bar - labels can't receive events, 
        // and the index will always be 0
        .event("mouseover", function() vis.j(this.index))
        .event("mouseout", function() vis.j(-1));
    
    // now add each label to the bars
    bars.anchor('bottom').add(pv.Label)
        .bottom(0)
        .textAlign("center")
        .textStyle("white")
        .text(function(d) d[0] );
    
    // and again
    bars.anchor("top").add(pv.Label)
        .visible(function() vis.j() >= 0)
        .textStyle("white")
        .text(function(d) d[1]);
    

    There's a working version here: http://jsfiddle.net/nrabinowitz/ABmuq/