Working with data in protovis – part 2 of 5

8 February, 2011 (19:51) | charts, data visualization, protovis, tips, Uncategorized | By: jerome

Previous post: simple arrays

Multi-dimensional arrays, associative arrays and protovis

Even for a simple chart, chances are that you’ll have more than a single series of numbers to represent. For instance, you could have labels to describe what they mean, several series, and so on and so forth.
So, let’s say we want to add these labels to our original examples, so we know what those bars represent.

var categories = ["a","b","c","d","e","f"];
var vis = new pv.Panel()
  .width(150)
  .height(150);

vis.add(pv.Bar)
  .data([1, 1.2, 1.7, 1.5, .7, .3])
  .width(20)
  .height(function(d) d * 80)
  .bottom(0)
  .left(function() this.index * 25)
  .anchor("bottom").add(pv.Label)
    .text(function() categories[this.index]);

vis.render();

While this did the trick, nothing guarantees that the data proper and the category name will stay coordinated. If one data point is deleted or removed and this is not replicated on the categories, they will no longer match. A more integrated way to proceed would be to group category and data information, like this:

var data = [
  {key:"a", value:1},
  {key:"b", value:1.2},
  {key:"c", value:1.7}, 
  {key:"d", value:1.5},
  {key:"e", value:.7},
  {key:"f", value:.3}
];
var vis = new pv.Panel()
  .width(150)
  .height(150);

vis.add(pv.Bar)
  .data(data)
  .width(20)
  .height(function(d) d.value * 80)
  .bottom(0)
  .left(function() this.index * 25)
  .anchor("bottom").add(pv.Label)
    .text(function(d) d.key);

vis.render();

This time, we group the values and the category names in a single variable, an array of associative arrays.
When drawing the bar chart, protovis will go through this array and retrieve an associative array for each bar.
We have to change the way the height function is written. The data element being accessed is no longer of the form 1 or 1.7, but {key:”a”, value:1} or {key:”c”, value:1.7}. So to get the number part of it, we must write d.value.

Likewise, instead of accessing an array of categories for the text part, we can use the current data element via an accessor function, and write d.key.

Hierarchy and inheritance

So we’ve seen that arrays, or associative arrays, can have several levels and can be nested one into another.
Interestingly, protovis elements, like panels, charts, mark etc. also work in a hierarchy. For instance, when you start working with protovis you create a panel object. Then, you add other objects to that panel, like a bar chart (our example), or another panel. You can add other objects to your new objects, or attach them to your first panel.
This diagram shows the hierarchy between elements in the previous example.

var categories = ["a","b","c","d","e","f"];
var vis = new pv.Panel()
  .width(150)
  .height(150)

var bar = vis.add(pv.Bar)
  .data([1, 1.2, 1.7, 1.5, .7, .3])
  .width(20)
  .height(function(d) d * 80)
  .bottom(0)
  .left(function() this.index * 25)
  .anchor("bottom").add(pv.Label)
  .text(function() categories[this.index]);

vis.render();

The bar object is considered to be the child of vis, who is its parent.

You may know that in protovis, children objects inherit properties of their parents.

For instance, if width wasn’t specified for the bar object, it would have the width of its parent, 150. Each mark would cover the whole screen.

For data, when a new object is added, data is either specified at that level, or obtained from the parent element of this object.

Let’s take our example and tweak it a bit.

var vis = new pv.Panel().width(150).height(150);
var bar = vis.add(pv.Bar)
  .data([1, 1.2, 1.7, 1.5, .7, .3]).width(20) .bottom(0)
  .height(function(d) d * 80).left(function() this.index * 25)
  .anchor("top").add(pv.Label)
vis.render();

Here, I didn’t specify a data or a text value for the labels I added. They just took the value of its parent element – the marks of the pv.Bar object.
Here’s another variation:

var vis = new pv.Panel().width(150).height(150);
vis.add(pv.Panel)
  .data([1, 1.2, 1.7, 1.5, .7, .3]) .left(function() this.index * 25)
  .add(pv.Bar).width(20) .bottom(0)
  .height(function(d) d * 80)
  .anchor("top").add(pv.Label)
vis.render();

Here, I’m adding panels, then a bar in each panel.
From the root panel, I’m adding a group of panel with this data: [1, 1.2, 1.7, 1.5, .7, .3].
Since there are 6 elements here, I’m adding 6 panels.
Here, the left method applies to each of the panel. The first one is to the left, the next one is 25 pixels further, etc.
I’m then adding a bar object to each panel. Is that one group of bar? Technically yes, but each has only one element! Each pv.Bar implicitly gets the data element of its parent, so the first bar gets [1], the next one gets [1.2], etc. The height of each bar is determined by multiplying the value of that element by 80.
Note that since the fillStyle properties are not defined for the bars, they get the ones which are attributed by default, which explains the color changes.

Further refinement: accessing the data of its parent!

var vis = new pv.Panel().width(150).height(150);
vis.add(pv.Panel)
  .data([1, 1.2, 1.7, 1.5, .7, .3]) .left(function() this.index * 25)
  .add(pv.Bar).width(20) .bottom(0)
  .height(function(a,b) b * 80)
  .anchor("top").add(pv.Label)
vis.render();

Well, the output is exactly the same, but how I obtained the data is different. Instead of getting the data using the standard accessor function, I passed two arguments: function(a, b).
What this does is that the first argument corresponds to the current data element of this object, and the second, to that of its parent.

In this example, they happen to be the same, but this is how you can access the data of the parent objects.

Putting it all together

Let’s see how we can use protovis and the properties of hierarchy! This example is less trivial than the ones we’ve seen so far but with what we’ve seen it is quite accessible.
The challenge: re-create square pie charts.
How it’s done:

var data=[36,78,63,24],  // arbitrary numbers
cellSize = 16,
cellPadding = 1,
squarePadding=5,
colors=pv.Colors.category20().range()
;

var vis=new pv.Panel()
    .width(4*(10*cellSize+squarePadding))
    .height(10*cellSize)
    ;

var square = vis.add(pv.Panel)
    .data(data)
    .width(10*cellSize)
    .height(10*cellSize)
    .left(function() this.index*(cellSize*10+squarePadding))
    ;

var row = square.add(pv.Panel)
	.data([0,1,2,3,4,5,6,7,8,9])
	.height(cellSize)
	.bottom(function(d) d*cellSize)
	;

var cell = row.add(pv.Panel)
    .data([0,1,2,3,4,5,6,7,8,9])
    .height(cellSize-2*cellPadding)
    .width(cellSize-2*cellPadding)
    .top(cellPadding)
    .left(function(d) d*cellSize+cellPadding)
    .fillStyle(function(a,b,c) (b*10+a)<c?colors[this.parent.parent.index].color:"lightGrey")
;

square.anchor("center")
  .add(pv.Label)
    .text(function(d) d)
    .textStyle("rgba(0,0,0,.1)")
    .font("100px sans-serif");

vis.render();

First, we initiate the data (4 arbitrary numbers from 1 to 100), and various parameters which will help size the square pie – size of the cells, space between them, space between the square pie charts. We also initiate a color palette.
Then, we are going to create 4 panels or groups of panels, each a child of the previous one.
First comes the vis panel, which groups everything,
Then the square panels, which correspond to each square pie. This is to this panel that our data is assigned.
Then come the row panels, and, finally, the cell panels.
The numbers which we want to represent are assigned to the square panel. So, what data are we passing to the row and the cell panels? The only thing we want is to have 10 rows and 10 cells per row. So, we can use an array with 10 items. We are going to use [0,1,2,3,4,5,6,7,8,9] so the data value of the row and that of the cell will correspond to the coordinate of the row and the cell, respectively. In other words, the 5th row will be assigned the data value of 4, and the 7th cell in that row will get the data value of 6. We could retrieve the same numbers using “this.index” but this can lead to obfuscated formulas.

Note that in the next part of the tutorial, we’ll see that in Protovis, there is a more elegant way to write [0,1,2,3,4,5,6,7,8,9] or similar series of numbers. But, we’ll leave this more explicit form for now.

Back to our row panel. We position it with bottom(function(d) d*cellSize). Here, again, d represents the rank of the row, so the 1st row will get 0, and its bottom value will be 0, the next row will get 1, and its bottom value will be 1*cellSize or 16, etc.

Likewise, in the cell panel, the cells are positioned with left(function(d) d*cellSize+cellPadding). This is the same principle. (here, cellPadding is just used to fine-tune the position of the cell).

This is in the final line that we are really going to use hierarchy.

.fillStyle(function(a,b,c) (b*10+a)<c?colors[this.parent.parent.index].color:"lightGrey")

here, a represents the data value of the cell – in other words, the column number.
b, the data value of the cell’s parent, the row. This is the row number.
and c is the data value of the parent of the parent of the row, the square – this is the number that we are trying to represent.

so, what we are going to determine is whether b*10+a<c. If that’s the case, we color the cell, else, we leave it in pale grey. To get the color we need, we go back to the palette that we defined at the beginning, and take the color corresponding to the square number (0 to 3 from left to right).
The square number can be obtained by this.parent.parent.index.

Finally, we add the numbers in large transparent gray digits on top of the squares.

Here is the result:

Next: Javascript and protovis array functions

Comments

Pingback from Tweets that mention Jerome Cukier » Working with data in protovis – part 2 of 5 — Topsy.com
Time February 8, 2011 at 8:27 pm

[...] This post was mentioned on Twitter by Jérôme Cukier and Jeff Hendrickson, Andy Kirk. Andy Kirk said: RT @jcukier: Part 2/5 of my #protovis tutorial on data structures: inheritance & hierarchy http://bit.ly/evqyff [...]

Pingback from Jerome Cukier » Working with data in protovis – part 3 of 5
Time February 9, 2011 at 7:09 pm

[...] Previous : Multi-dimensional arrays, inheritance and hierarchy [...]

Comment from hlomas
Time February 18, 2011 at 6:52 pm

Thanks for the excellent tutorial, it likely took a while to write all this up but it is a wonderful guide and fills a lot of holes in the official documentation.

Comment from jerome
Time February 18, 2011 at 8:13 pm

thanks, I find the the documentation quite good but what’s missing are more examples and a bigger gallery, so that’s my contribution :)

Comment from Sean
Time March 3, 2011 at 4:24 pm

Brilliant write up! Protovis desperately needs more documentation like this. My biggest complaint so far is that the Chrome JS Debugger always reports my mistakes in protovis.js … thus, finding where I went wrong is nearly impossible and forces me into a painful trial and error mode that takes forever (almost ready to settle for FLOT). Any suggestions for dealing with this would be greatly appreciated.

Comment from KKU
Time February 18, 2013 at 11:53 am

I’m unable to draw a pv.Line in Protovis:
points is a 2D array, which is working for painting pv.Dor
for (var k=0;k<points.length;k++)
{
vis.add(pv.Line)
.bottom(points[k][0])
.left(points[k][1])
.cursor("move");
}

Write a comment