How much was there across a span, for each group? area fills the region between the data and a baseline at zero. It is line plus the ground beneath it: the same boundary, drawn as a quantity rather than as a path.
data(gapminder_asia) + area +x(year) +y(life) +group(country) +style(opacity =0.4) +title("Life expectancy, five Asian countries")
(data(gapminder_asia) + area + x(col.year) + y(col.life) + group(col.country) + style(opacity =0.4) + title("Life expectancy, five Asian countries"))
data(gapminder_asia) + area +x(:year) +y(:life) +group(:country) +style(opacity =0.4) +title("Life expectancy, five Asian countries")
plot(data(gapminder_asia), area,x(col.year),y(col.life),group(col.country),style({ opacity:0.4 }),title("Life expectancy, five Asian countries"))
“Given gapminder Asia: areas, x is year, y is life, grouped by country.”
An area’s two axes are different kinds of thing, and it is worth naming the difference early because most of the mark follows from it. x is the domain, the axis the boundary is read along; y is the measure, the quantity the region fills to. That is why an area has no horizontal form the way a bar does: a bar reads which of its axes measures off the bindings, and an area does not need to, because the answer is always y.
Notice the fill reaches the left and right edges of the panel. A filled region has no glyph to clip against the frame, so its position axis sits flush to the data rather than leaving the breathing margin a scatter gets; the reason is in Scales.
11.2 The baseline is the whole point
A line says where; an area says how much. The fill is the quantity, so it has to start from zero: an area drawn from an arbitrary floor would put a number on the page that nothing measured.
That makes the mark a real choice rather than decoration. Life expectancy has no meaningful zero, and the plot above spends most of its ink on the fifty years below every observation. Sales do have a meaningful zero, and the same mark reads correctly:
data(gapminder_asia) + area +x(year) +y(life) +color(country) +style(opacity =0.45) +title("One region per country")
(data(gapminder_asia) + area + x(col.year) + y(col.life) + color(col.country) + style(opacity =0.45) + title("One region per country"))
data(gapminder_asia) + area +x(:year) +y(:life) +color(:country) +style(opacity =0.45) +title("One region per country")
plot(data(gapminder_asia), area,x(col.year),y(col.life),color(col.country),style({ opacity:0.45 }),title("One region per country"))
Regions are filled, so unlike polylines they hide one another. Write that plot without an opacity and gog says so rather than letting you read a chart with a series missing underneath:
data(gapminder_asia) + area +x(year) +y(life) +color(country) +title("Opaque regions: the last one drawn wins")
(data(gapminder_asia) + area + x(col.year) + y(col.life) + color(col.country) + title("Opaque regions: the last one drawn wins"))
data(gapminder_asia) + area +x(:year) +y(:life) +color(:country) +title("Opaque regions: the last one drawn wins")
plot(data(gapminder_asia), area,x(col.year),y(col.life),color(col.country),title("Opaque regions: the last one drawn wins"))
gog: `area` split by `country` draws 5 regions on top of one another, and the last one drawn can hide the rest. Add `style(opacity = 0.5)` to see through them, or use `line` to compare shapes without filling.
Compare the legend with the picture: every country is named in the key, and the ones with the lowest values are painted over by the ones drawn after them. The note above the plot is advice rather than a refusal: the plot still renders, because the grammar has no objection to it, and gog guides taste without blocking it. Stacking the regions instead of overlaying them is the real answer, and it ships as area * stack, an area’s collision modifier. Its sibling dodge subdivides a width, which an area has none of; an area is offset by accumulating, so stack is its tool. Each region sits on the cumulative height of the ones below, and they abut into one band instead of hiding each other:
data(gapminder_asia) + area * stack +x(year) +y(population) +color(country) +y_label("Population") +title("Five Asian countries, population stacked")
(data(gapminder_asia) + area * stack + x(col.year) + y(col.population) + color(col.country) + y_label("Population") + title("Five Asian countries, population stacked"))
data(gapminder_asia) + area * stack +x(:year) +y(:population) +color(:country) +y_label("Population") +title("Five Asian countries, population stacked")
plot(data(gapminder_asia),layer(area, stack),x(col.year),y(col.population),color(col.country),y_label("Population"),title("Five Asian countries, population stacked"))
Read one color’s thickness for that country; the band’s top edge is the five-country total. The measure moved from life to population on purpose: you stack quantities that add (populations sum to a real total), not indices that do not (a stack of life expectancies would be meaningless). The Transforms chapter covers stack, and stacked bars, in full.
11.4 Giving an area an edge
An area takes no outline of its own. The edge on top of a filled region is a line, and layering one over the other is how you ask for it:
data(actuals) +x(year) +y(sales) + area +style(color ="steelblue", opacity =0.25) + line +style(color ="steelblue", size =2.5) +title("area + line: the fill and its edge, composed")
(data(actuals) + x(col.year) + y(col.sales) + area + style(color ="steelblue", opacity =0.25) + line + style(color ="steelblue", size =2.5) + title("area + line: the fill and its edge, composed"))
data(actuals) +x(:year) +y(:sales) + area +style(color ="steelblue", opacity =0.25) + line +style(color ="steelblue", size =2.5) +title("area + line: the fill and its edge, composed")
plot(data(actuals),x(col.year),y(col.sales), area,style({ color:"steelblue",opacity:0.25 }), line,style({ color:"steelblue",size:2.5 }),title("area + line: the fill and its edge, composed"))
There is deliberately no outline option on area to reach for instead. Two marks that already exist, combined with +, beat a new knob on one of them, and the composed version is more capable anyway, since the edge can take its own color, width and opacity as a line.
11.5 One region, one fill
An area is a single filled region, so opacity is a setting here and not a channel: there is nothing for a per-row opacity to vary along, since a row is a vertex of the boundary rather than a region of its own. That is the same reasoning that makes opacity a setting on line, one dimension up:
render_svg(data(gapminder_asia) + area +x(year) +y(life) +opacity(gdp))
Error:
! gog: `opacity` cannot be bound to `area` — an area has no opacity feature. Remove `opacity(gdp)`, or use a mark that has one.
gog: nothing was rendered. Fix the above, or set GOG_STRICT=0 to draw anyway.
size goes further: on area it cannot even be set. A stroke’s width is free, so line lets you set it, but an area’s extent is pinned by its x, its y and its baseline, and there is no size left over to choose:
render_svg(data(actuals) + area +x(year) +y(sales) +style(size =4))
Error:
! gog: `style(size = 4)` — an area has no size to set. Remove it, or use a mark that has one.
gog: nothing was rendered. Fix the above, or set GOG_STRICT=0 to draw anyway.
Two marks, two different answers, both derived from what the mark is rather than listed in a table of options. style(color = ) and style(opacity = ) work on both.
11.6 With transforms
Transforms behave identically on every mark, so an area takes the same ones a bar or a line does. Binned, it is a filled histogram:
Give that same sentence a category to stand in and a number to spread along, and the estimate is drawn across each category’s slot instead of along the whole axis: one distribution per group, closed on the slot’s center line. It is the half violin, and the mirrored form is ribbon * density, where the choice between the two readings of its width is also explained:
data(gapminder_2007) + area * density +x(continent) +y(life) +y_label("Life expectancy") +title("area * density + x(continent): the half violin")
(data(gapminder_2007) + area * density + x(col.continent) + y(col.life) + y_label("Life expectancy") + title("area * density + x(continent): the half violin"))
data(gapminder_2007) + area * density +x(:continent) +y(:life) +y_label("Life expectancy") +title("area * density + x(continent): the half violin")
plot(data(gapminder_2007),layer(area, density),x(col.continent),y(col.life),y_label("Life expectancy"),title("area * density + x(continent): the half violin"))
The two sentences differ by one binding, and that difference is exactly what the extra binding buys. With only x(life) there is a free axis for the estimate to be measured along. With a category there is not, so it is measured across the slot instead.
And an aggregation summarizes before filling, exactly as it does for bar:
data(gapminder_asia) + area * mean +x(year) +y(life) +y_label("Mean life expectancy") +title("area * mean: five countries averaged per year")
(data(gapminder_asia) + area * mean + x(col.year) + y(col.life) + y_label("Mean life expectancy") + title("area * mean: five countries averaged per year"))
data(gapminder_asia) + area * mean +x(:year) +y(:life) +y_label("Mean life expectancy") +title("area * mean: five countries averaged per year")
plot(data(gapminder_asia),layer(area, mean),x(col.year),y(col.life),y_label("Mean life expectancy"),title("area * mean: five countries averaged per year"))
11.7 A category on the domain
The domain takes a category as readily as a number. Each category gets a slot, the boundary runs across them in axis order, and the region fills to the same baseline it always fills to:
data(gapminder_2007) + area * mean +x(continent) +y(life) +y_label("Mean life expectancy") +title("area * mean + x(continent): the filled profile")
(data(gapminder_2007) + area * mean + x(col.continent) + y(col.life) + y_label("Mean life expectancy") + title("area * mean + x(continent): the filled profile"))
data(gapminder_2007) + area * mean +x(:continent) +y(:life) +y_label("Mean life expectancy") +title("area * mean + x(continent): the filled profile")
plot(data(gapminder_2007),layer(area, mean),x(col.continent),y(col.life),y_label("Mean life expectancy"),title("area * mean + x(continent): the filled profile"))
Whether that is the right picture is your call, not the grammar’s. The straight segments between two categories pass through space where there is no data, which is a real objection: nothing lives between Africa and the Americas. Read the vertices and the segments are a guide for the eye; read the segments and they say something the data does not. When the comparison is what matters, the bar chart of the same sentence is the safer reading, and it is one atom away.
What the fill does earn you is a shape, and shapes survive being bent. This is the same expression in polar coordinates, where it is the plot everyone calls a radar:
data(gapminder_2007) + area * mean +x(continent) +y(life) +polar() +title("the same sentence, read in a circle")
(data(gapminder_2007) + area * mean + x(col.continent) + y(col.life) + polar() + title("the same sentence, read in a circle"))
data(gapminder_2007) + area * mean +x(:continent) +y(:life) +polar() +title("the same sentence, read in a circle")
plot(data(gapminder_2007),layer(area, mean),x(col.continent),y(col.life),polar(),title("the same sentence, read in a circle"))
11.8 What you can set
Setting
Value
style(color = )
any CSS color name or hex
style(opacity = )
0 to 1
style(pattern = )
solid, hatch, crosshatch, grid, dots
And these vary per row if you map them to a column instead: color() (categories), pattern() (categories), group() (categories), play() (either).
An area takes the shortest list in the book, and both absences are arguments. There is no size, because the region’s extent comes from the data and the baseline. And there is no border, because the edge you would want is a data curve, which is already a line:
data(actuals) +x(year) +y(sales) + area +style(color ="seagreen", pattern ="hatch", opacity =0.5) + line +style(color ="seagreen", size =2) +y_label("Sales") +title("A textured fill, and a line for the edge")
(data(actuals) + x(col.year) + y(col.sales) + area + style(color ="seagreen", pattern ="hatch", opacity =0.5) + line + style(color ="seagreen", size =2) + y_label("Sales") + title("A textured fill, and a line for the edge"))
data(actuals) +x(:year) +y(:sales) + area +style(color ="seagreen", pattern ="hatch", opacity =0.5) + line +style(color ="seagreen", size =2) +y_label("Sales") +title("A textured fill, and a line for the edge")
plot(data(actuals),x(col.year),y(col.sales), area,style({ color:"seagreen",pattern:"hatch",opacity:0.5 }), line,style({ color:"seagreen",size:2 }),y_label("Sales"),title("A textured fill, and a line for the edge"))
Layering the line is strictly more capable than a border setting would be: it can be dashed, colored differently, or smoothed, and it traces only the data boundary rather than the closure down to the baseline that a rim would also stroke.
11.9 What it refuses
The measure is where the mark draws its line. A region fills to a baseline at zero, and a category has no zero to fill to, so a categorical y is refused with the direction back to the mark that lays categories along the measured axis:
render_svg(data(gapminder_2007) + area * mean +x(life) +y(continent))
Error:
! gog: `y(continent)` maps a categorical (text) column, but `y` on `area` needs a continuous (numeric) column. On these marks `x` is the domain and `y` the measure, and a category is not a quantity to measure: a mean of category names is not a number, and a region has no categorical baseline to close on. Put the category on `x` instead — `line * mean + x(<category>) + y(<number>)` is the profile plot, and `area * mean` fills it. Unlike `bar`/`box`/`interval`, these marks do not read their orientation off the bindings, because their two axes do not have the same role.
gog: nothing was rendered. Fix the above, or set GOG_STRICT=0 to draw anyway.
The transforms that describe how values spread along an axis refuse a category too, on either axis, and for the same reason: they need a number line. area * bin and area * density want a continuous domain, and each refusal names the atom that asks their question of categories.
render_svg(data(gapminder_2007) + area * bin +x(continent))
Error:
! gog: `bin` cuts a continuous axis into intervals, and `x(continent)` is categorical — a category is one slot, with no width to cut. To tally rows per category, `count` is the transform that does it: `bar * count`.
gog: nothing was rendered. Fix the above, or set GOG_STRICT=0 to draw anyway.