How much of the whole does each part take? A share of a whole has to be carried by something you can see. Flat, it is a length: a segment’s height as a fraction of the column it sits in. Bent into a circle, it is an angle, and the column becomes a pie. There is a third answer, and it is the one that fits the most categories into the least page: area.
That is what nest() draws. Each row’s measure becomes the size of a region, and the regions partition the panel. The plot has a name, treemap, and as with the pie there is no atom that spells it.
That name has a precise origin and a mundane one. Ben Shneiderman’s 80-megabyte hard disk was full in 1990, and a directory listing could not tell him what was filling it, so he wrote a display that packs a rectangle with nested rectangles sized by what they hold (Shneiderman, 1992). Thirty years on it is how a share of a whole gets drawn whenever there are more categories than a pie can carry, which is the argument this chapter opened with, arrived at from the other end.
31.1 One sentence, three spaces
Here is the population of each continent as a single column, divided into its parts:
“Given gapminder 2007: bars derived by sum and stack, y is population, color by continent.”
Now the same sentence in the circle. The column’s height becomes the whole turn, and each segment’s share of the height becomes its slice’s share of the circle:
data(gapminder_2007) + bar * sum +y(population) +color(continent) +nest() +title("The same shares, as areas")
(data(gapminder_2007) + bar *sum+ y(col.population) + color(col.continent) + nest() + title("The same shares, as areas"))
data(gapminder_2007) + bar * sum +y(:population) +color(:continent) +nest() +title("The same shares, as areas")
plot(data(gapminder_2007),layer(bar, sum),y(col.population),color(col.continent),nest(),title("The same shares, as areas"))
Three pictures, one grammar. Nothing in any of them names a chart: a bar, summed, colored by continent, and then a word for the space. Asia is a little over half the world’s population in all three, and you can check that against the column, the pie and the packing in turn.
One word did change, and it is worth the sentence: the flat and polar forms carry stack and the packed form does not. stack decides what happens when two marks land in the same place, which presupposes that they can. A packing gives every piece a region of its own, so there is no collision left to resolve, and gog refuses stack here rather than accepting a word with nothing to do.
31.2 Why area, when a column already worked
Read the column again and look for Oceania. At 0.3% of the world it is about two pixels tall, which is thinner than the line drawn around it. In the packing the same share is a strip about six pixels wide, because a column spends its whole length on one dimension and a packing has two to spend.
Three times better is honest, and it is not a transformation. With five parts a packing buys you a little; the gain grows with the number of parts, because a column can only ever get longer while a panel gets subdivided in both directions at once. A packing is for the case where there are many shares and the question is which of them are large, and where a column would have run out of pixels long before it ran out of categories.
The cost is on the other side, and it is the objection that has always been made to the pie: two lengths can be compared exactly, and two areas cannot. If the comparison has to be precise, the flat column is still the plot, and this space is not trying to replace it.
31.3 Two levels, from a position
Bind a position and the packing gains a level. Each category gets a region sized by its own total, and the rows inside it are packed into that region:
data(gm_eras) + bar * sum +x(continent) +y(population) +color(era) +nest() +title("Two eras, inside each continent")
(data(gm_eras) + bar *sum+ x(col.continent) + y(col.population) + color(col.era) + nest() + title("Two eras, inside each continent"))
data(gm_eras) + bar * sum +x(:continent) +y(:population) +color(:era) +nest() +title("Two eras, inside each continent")
plot(data(gm_eras),layer(bar, sum),x(col.continent),y(col.population),color(col.era),nest(),title("Two eras, inside each continent"))
Each continent is a block sized by its total across both eras, and inside it the two eras are packed against each other. The heavier lines are the continents; the thinner ones divide what is inside them. Asia’s block is not only the largest, it is the most lopsided: half a century put far more people into its 2007 half than into any other continent’s.
The nesting is not new vocabulary. Wilkinson makes the point directly (Wilkinson, 2005): a dimension hierarchy is a nested facet, so x here is doing what the outer level of a facet does, with the panel divided by share instead of into equal boxes.
The inner level comes from color, which sets what the second level can usefully be when the legend is what decodes it: a split with few enough values to read down the side. Countries inside continents, the treemap most often printed, would be a legend of 142 entries, and the section after next names the regions in place instead.
31.4 What a packed panel does not have
Look for the axes. There are none, and that is the space’s defining property rather than a setting you could switch back on.
A flat panel’s horizontal direction means something: it is a variable, and a tick is a place on it. In a packing neither direction is a variable. Wilkinson puts it as sharply as it can be put: the two dimensions of a treemap “have no intrinsic meaning related to the data because they can be reordered without changing the metric properties of the tree” (Wilkinson, 2005). Two regions that touch are not near each other in the data, and moving one to the other side of the panel would not change a single share the plot reports.
So there is nothing to tick and nothing to label, and gog refuses to pretend otherwise:
data(gapminder_2007) + bar * sum +y(population) +color(continent) +nest() +x_label("Population")
Error:
! gog: `x_label()` names an axis, and a `nest()` plot has none — its two directions carry no variable and can be reordered without changing what the plot says. Use `title()` for the plot's own name, and the color legend to say what the regions are.
gog: nothing was rendered. Fix the above, or set GOG_STRICT=0 to draw anyway.
What decodes a packed panel is the color legend, exactly as it decodes a pie.
31.5 Naming the regions
A legend works while there are few enough entries to scan. Past that it stops being a key and becomes a second plot to read, and the countries of the world are well past it. So put the name where the region is:
gog: 116 of 142 labels are wider than the region they name, and were left out — the packing drew every share, the names are what is missing. Fewer categories, a larger plot (`theme(width =, height =)`) or a smaller `style(size = )` fits more of them in.
Two layers, and the second is the ordinary text mark: label says which column supplies the string, exactly as it does on a scatter. What it does not say is where to put it, because in this space nothing does. A label goes to the center of the region its own row was packed into, which is the same region the bar drew: one packing, read by both marks, so a name cannot end up in a rectangle that belongs to someone else.
Notice what is missing from that sentence: there is no x on the text layer and no y either, and the plot’s own y(population) is doing both jobs at once. It sizes the regions, and sizing them is what places the labels. Everywhere else in this book a text mark needs two positions before it can be drawn at all; here it needs a measure and a string, and a packing does the rest.
Count the names and there are far fewer than 142, which is what the line above the plot is for. Most of the world’s countries have a share narrower than their own name, so a label that does not fit its rectangle is not drawn. The engine says how many it left out, rather than letting you read the named countries as though they were all of them. Nigeria and Ethiopia are named and most of Africa is not, and every one of those unnamed countries still has a region exactly as wide as its share of the world. The picture is complete and the labeling is not, and the plot is the thing that tells you which.
That message is why this section is not the trap it looks like. A treemap that printed the two dozen names that happened to fit, and said nothing about the rest, would read as a complete labeling of two dozen countries.
Three things move the number, and each is an atom you have already met: fewer categories, a bigger canvas with theme(width =, height =), or smaller type with style(size = ). None of them is a setting for labels in particular, and none of them will make a name fit a region narrower than the word.
31.6 The other refusals
A packing cannot hold a mark that is placed by a position, because it has no positions to place one at:
data(gapminder_2007) + point +x(gdp) +y(life) +nest()
Error:
! gog: `point` is placed by a position, and a packing has none to give it — its two directions are not axes, and two neighboring regions are not near each other in the data (Wilkinson §13.3.4.1). Drop `nest()` to draw `point` flat, or use `bar` or `text`.
gog: nothing was rendered. Fix the above, or set GOG_STRICT=0 to draw anyway.
An area cannot be negative, so a measure that runs below zero has no packing. Flat, a bar below the baseline reads as a loss; a region has no direction to run in, and a treemap whose parts no longer sum to the whole is a treemap that lies.
A log scale is refused too, and the reason is the one worth remembering: a region’s size is its share of the total, which is arithmetic on the values themselves. Re-spacing them changes what a distance means, and a packing has no distances. Accepting the scale would have left the picture byte-for-byte identical, which is the one thing the engine will not do quietly.
data(gapminder_2007) + bar * sum +y(population, scale ="log") +color(continent) +nest()
Error:
! gog: `y(scale = "log")` with `nest()` — a packed region's size is its **share of the total**, which is arithmetic on the values themselves, so a log scale would change nothing about the picture. Drop the scale, or drop `nest()` to read the values against a log axis.
gog: nothing was rendered. Fix the above, or set GOG_STRICT=0 to draw anyway.
31.7 The order is yours
Published treemaps usually sort by size, largest first, because the packing algorithm produces squarer rectangles that way. gog does not do it for you: the regions follow the order the categorical axis already has, so the sort is something you say rather than something the layout decides behind you.
That is the same order() that turns a bar chart round, doing the same job. Had the packing sorted on its own, order() would have been a word with no effect here, and the atom would have quietly meant less than it says.
It sorts a categorical position axis, so it needs one. The one-level treemap above binds no position at all, and there the atom has nothing to sort:
data(gapminder_2007) + bar * sum +y(population) +color(continent) +order(population, desc =TRUE) +nest()
Error:
! gog: `order(population)` sorts a **categorical position axis**, and this plot has none — so there is nothing for it to put in order. Bind a category to `x` or `y`, or, if what you meant was the order of a color split, set the column's factor levels where the data lives.
gog: nothing was rendered. Fix the above, or set GOG_STRICT=0 to draw anyway.
That refusal is not the packed space’s. It is order()’s, and it was always true: the same sentence flat, a single divided column, had been accepting the word and dropping it in silence. Writing this chapter is what found it, which is the ordinary way a new space proves its value: it asks an old atom a question nobody had asked.
31.8 Where the rectangles come from
The packing is squarified, after Bruls, Huizing and van Wijk (Bruls et al., 2000): each run of regions is laid down in the strip that keeps its rectangles closest to square. The naive alternative cuts one direction at a time, which turns any long tail into slivers, and slivers are the shape an area is hardest to judge. There is no setting for this. One layout that fixes a real defect is a feature; a second one beside it, for completeness, would be the kind of vocabulary this grammar exists to refuse.
Bruls, M., Huizing, K., & van Wijk, J. J. (2000). Squarified treemaps. In W. C. de Leeuw & R. van Liere (Eds.), Data visualization 2000: Proceedings of the joint Eurographics and IEEE TCVG symposium on visualization (pp. 33–42). Springer. https://doi.org/10.1007/978-3-7091-6783-0_4
Shneiderman, B. (1992). Tree visualization with tree-maps: 2-d space-filling approach. ACM Transactions on Graphics, 11(1), 92–99.