11  Area

How large was the quantity at each date? area fills the region between the data and a baseline at zero. Its top edge is the boundary a line would draw. The fill below that edge is what makes the plot read as a quantity rather than as a stroke.

11.1 Basic usage

How large were sales in each of five years? Most examples in this chapter start from the smallest sentence that draws an area: a source, the mark, and its two positions.

actuals: all 5 rows
year sales
2019 120
2020 135
2021 128
2022 152
2023 168
data(actuals) + area + x(year) + y(sales) +
  y_label("Sales") + title("Sales, 2019 to 2023")
(data(actuals) + area + x(col.year) + y(col.sales) +
  y_label("Sales") + title("Sales, 2019 to 2023"))
data(actuals) + area + x(:year) + y(:sales) + y_label("Sales") +
  title("Sales, 2019 to 2023")
plot(data(actuals), area, x(col.year), y(col.sales), y_label("Sales"),
  title("Sales, 2019 to 2023"))
2019 2020 2021 2022 2023 0 50 100 150 Sales, 2019 to 2023 Sales Year

“Given the actuals: an area, x is year, y is sales.”

Notice that the fill reaches the left and right edges of the panel. With point, the axis ends a little past the data. A dot drawn at the last value would otherwise be cut in half by the panel edge. An area has no dots. Its edge is the data itself, so nothing can be cut off, and the axis ends exactly at the first and last year. Scales states the rule in full.

An area’s two axes are not interchangeable, and the rest of this chapter follows from that. x is the domain, the axis the area is read along. y is the measure, the quantity it fills to. A line has the same two axes. A bar draws horizontally when you swap its bindings. A line does not, because its measure is always y, as the profile section of the Line chapter explains. An area follows the same rule, so it has no horizontal form either. The one exception is density, whose curve runs along whichever axis is continuous; With transforms shows both of its forms.

11.2 The baseline has to be zero

A line says where the value was; an area says how much there was. The fill is the quantity, so it has to start from zero. An area drawn from any other baseline would show a height that measures nothing. gog does not stop you from starting the axis somewhere else. y(sales, limits = c(100, NA)) starts it at 100, the fill starts there too, and no warning is printed. That decision is yours to make: the fill then shows sales minus 100 rather than the sales, and the reader of your plot has to know it. Scales explains limits.

So the choice of mark is about the data, not the appearance. Sales have a true zero, where zero means none at all, so the first plot is correct: the fill is the sales. Life expectancy in years has a true zero too, and several plots below fill to it. A quantity whose zero is only a convention has no amount for a fill to show. A temperature in degrees Celsius is one, and so is a calendar year. Draw those with line.

Life expectancy has a true zero, so the data allows either mark, and the question you are asking decides between them. An area and a line draw the same top edge. Here is that edge drawn with line and split by color, as in the Line chapter. It shows where each country’s value was, and nothing about how much:

gapminder_asia: first 5 of 60 rows
country continent year life population gdp
China Asia 1952 44.00000 556263527 400.4486
China Asia 1957 50.54896 637408000 575.9870
China Asia 1962 44.50136 665770000 487.6740
China Asia 1967 58.38112 754550000 612.7057
China Asia 1972 63.11888 862030000 676.9001
data(gapminder_asia) + line + x(year) + y(life) + color(country) +
  title("Life expectancy as paths, not quantities")
(data(gapminder_asia) + line + x(col.year) + y(col.life) + color(col.country) +
  title("Life expectancy as paths, not quantities"))
data(gapminder_asia) + line + x(:year) + y(:life) + color(:country) +
  title("Life expectancy as paths, not quantities")
plot(data(gapminder_asia), line, x(col.year), y(col.life),
  color(col.country), title("Life expectancy as paths, not quantities"))
1960 1980 2000 40 50 60 70 80 Life expectancy as paths, not quantities Life Year Country China India Indonesia Japan Korea, Rep.

“Given gapminder Asia: lines, x is year, y is life, color by country.”

11.3 Multiple series

The table holds five countries, so the sentence has to name the column that splits them. Without a split, the boundary runs through all sixty rows as one region. color splits the mark into one region per category and names each one in a legend. It is the same split that gives line one stroke per category. The sentence also sets a low opacity, so the regions drawn first show through the ones drawn over them:

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"))
1960 1980 2000 0 20 40 60 80 One region per country Life Year Country China India Indonesia Japan Korea, Rep.

“Given gapminder Asia: areas, x is year, y is life, color by country.”

Five lines can cross without hiding each other, because a line is a thin stroke. Five areas cannot: a fill covers whatever lies behind it, and at the default opacity only a little shows through. Write the same sentence with no opacity, and gog prints a warning before it draws:

data(gapminder_asia) + area + x(year) + y(life) + color(country) +
  title("Five regions, no opacity set: each one covers the ones before it")
(data(gapminder_asia) + area + x(col.year) + y(col.life) + color(col.country) +
  title("Five regions, no opacity set: each one covers the ones before it"))
data(gapminder_asia) + area + x(:year) + y(:life) + color(:country) +
  title("Five regions, no opacity set: each one covers the ones before it")
plot(data(gapminder_asia), area, x(col.year), y(col.life),
  color(col.country),
  title("Five regions, no opacity set: each one covers the ones before it"))
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.
1960 1980 2000 0 20 40 60 80 Five regions, no opacity set: each one covers the ones before it Life Year Country China India Indonesia Japan Korea, Rep.

Compare the legend with the plot. Every country is named there, but five regions cannot be distinguished. Each region covers the ones drawn before it, so you can read a region only where it rises above every region drawn after it. The warning is advice rather than a refusal: the sentence is legal, so gog draws it.

The other way to draw it is to stack the regions instead of overlaying them. That is written area * stack. stack is a collision modifier: it moves marks that would otherwise be drawn in the same place, rather than summarizing them. Its sibling dodge sets marks beside each other within a slot, and an area is one connected shape across the axis, not a mark in a slot. So of the two, only stack applies to an area. Each region starts at the top of the ones below it, so the five form one band and none is covered:

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"))
1960 1980 2000 0 1B 2B Five Asian countries, population stacked Population Year Country China India Indonesia Japan Korea, Rep.

“Given gapminder Asia: areas derived by stack, x is year, y is population, color by country.”

Each country’s region is as thick as its population, and the band’s top edge is the five-country total. This plot measures population instead of life on purpose. You stack quantities that add: populations add up to a total that means something, and life expectancies do not. gog does not stop you from stacking them. Put y(life) in place of y(population) in the sentence above, and gog draws five regions to a top edge near 370 years, with no warning. That number means nothing. So use stack with care: you always get a plot, and only you know whether the sum of the parts means something.

Which country’s share of the five grew? The stacked plot cannot say, because every region’s baseline moves with the ones below it. stack(share = TRUE) divides each year’s pile by that year’s total. The band’s top edge is then flat at 1, and each region’s thickness is that country’s share:

data(gapminder_asia) + area * stack(share = TRUE) + x(year) + y(population) +
  color(country) +
  y_label("Share of population") +
  title("Five Asian countries, population as shares")
(data(gapminder_asia) + area * stack(share = True) + x(col.year) + y(col.population) +
  color(col.country) +
  y_label("Share of population") +
  title("Five Asian countries, population as shares"))
data(gapminder_asia) + area * stack(share = true) + x(:year) +
  y(:population) + color(:country) + y_label("Share of population") +
  title("Five Asian countries, population as shares")
plot(data(gapminder_asia), layer(area, stack({ share: true })),
  x(col.year), y(col.population), color(col.country),
  y_label("Share of population"),
  title("Five Asian countries, population as shares"))
1960 1980 2000 0.0 0.2 0.4 0.6 0.8 1.0 Five Asian countries, population as shares Share of population Year Country China India Indonesia Japan Korea, Rep.

“Given gapminder Asia: areas derived by stack as shares, x is year, y is population, color by country.”

Between 1952 and 2007, India’s share rose from 0.33 to 0.39, and Japan’s fell from 0.08 to 0.05. The total is gone from this plot: every year reaches 1, so it shows which shares grew and no longer how large the total was. Draw both when you need both answers. The Transforms chapter covers stack in full, with a section on stacked areas.

Every stack so far was split by color. A stack can also be split by group. It splits the rows as color does, but every region gets the same color and there is no legend. Use it when the total matters more than telling the regions apart. Because the regions share one color, gog draws a thin line between them in the panel’s background color. A stacked bar separates its segments the same way. The channel_sales table holds each quarter’s revenue from two ways of selling. Its channel column names them, Retail and Online:

channel_sales: first 5 of 6 rows
quarter channel revenue
Q1 Retail 4
Q1 Online 3
Q2 Retail 5
Q2 Online 6
Q3 Retail 4
data(channel_sales) + area * stack + x(quarter) + y(revenue) + group(channel)
data(channel_sales) + area * stack + x(col.quarter) + y(col.revenue) + group(col.channel)
data(channel_sales) + area * stack + x(:quarter) + y(:revenue) +
  group(:channel)
plot(data(channel_sales), layer(area, stack), x(col.quarter),
  y(col.revenue), group(col.channel))
Q1 Q2 Q3 0 5 10 Revenue Quarter

“Given the channel sales table: areas derived by stack, x is quarter, y is revenue, grouped by channel.”

quarter is a category, so each quarter gets a slot of its own, and the fill runs from the middle of the first slot to the middle of the last. A category on the domain describes this shape.

The top edge is each quarter’s total. Retail comes first in the table, and gog piles the groups in that order, so Retail is the lower region. The thin line marks where the retail region ends and the online region begins. No color or legend tells the two regions apart. When the reader needs to know which is which, another layer has to label them. This line is not a border: an area takes no border, as the next section explains. gog draws the line only when group alone splits the stack, and no setting removes it.

11.4 Giving an area an edge

A low opacity, like the 0.45 in Multiple series, leaves a faint top edge, and the top edge is where the data is. A stroke along it makes the boundary easy to read. An area takes no border of its own. The edge on top of a filled region is a line, so you layer one over the other:

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"))
2019 2020 2021 2022 2023 0 50 100 150 area + line: the fill and its edge, composed Sales Year

“Given the actuals: x is year, y is sales, an area and also a line.”

There is no border setting on area to use instead. A layered line gives the edge its own color, width, opacity and dash. A bar’s border settings, border_color and border_size, give only a color and a width.

11.5 One region, one fill

The plot above set one opacity for the whole area. Can you map a column to an area’s opacity instead? No, and the reason is the shape. An area is a single filled region, so opacity is a setting here and not a channel. Each row of the table is one vertex, a corner of the top edge, not a piece of the fill. No part of the region belongs to one row, so a column cannot make one row lighter or darker than another. opacity is a setting on line for the same reason.

size is refused both ways: area neither maps it nor sets it. A stroke has a width, so line lets you set one. An area’s extent comes from its x, its y and its baseline, so there is no size to choose. Both marks take style(color = ) and style(opacity = ). What it refuses shows the refusals for opacity and size.

11.6 With transforms

Apart from the stack, every plot so far filled to values already in the table. A transform computes new values first, and the area fills to what it computed. A distribution, for example, can be drawn as a filled shape. The question is the one a histogram answers: how many rows fall in each interval? bin, density and the aggregation family mean the same thing on an area as on a bar. bin counts the rows in each interval. The boundary then runs through the bin centers, and the region below it is filled. That shape is the frequency polygon line * bin draws, with its inside filled:

gapminder_2007: first 5 of 142 rows
country continent year life population gdp
Afghanistan Asia 2007 43.828 31889923 974.5803
Albania Europe 2007 76.423 3600523 5937.0295
Algeria Africa 2007 72.301 33333216 6223.3675
Angola Africa 2007 42.731 12420476 4797.2313
Argentina Americas 2007 75.320 40301927 12779.3796
data(gapminder_2007) + area * bin + x(life) +
  x_label("Life expectancy (years)") +
  title("area * bin: a filled frequency polygon")
(data(gapminder_2007) + area * bin + x(col.life) +
  x_label("Life expectancy (years)") +
  title("area * bin: a filled frequency polygon"))
data(gapminder_2007) + area * bin + x(:life) +
  x_label("Life expectancy (years)") +
  title("area * bin: a filled frequency polygon")
plot(data(gapminder_2007), layer(area, bin), x(col.life),
  x_label("Life expectancy (years)"),
  title("area * bin: a filled frequency polygon"))
50 60 70 80 0 10 20 30 area * bin: a filled frequency polygon Count Life expectancy (years)

“Given gapminder 2007: an area derived by bin, x is life.”

bin(30) and bin(width = 5) set the bins here exactly as they do for a bar. Choosing the bin count shows both, and Choosing a width explains how to pick a width.

density replaces the counts with a smooth curve, and the area fills the region under it:

data(gapminder_2007) + area * density + x(life) +
  x_label("Life expectancy (years)") +
  title("area * density")
(data(gapminder_2007) + area * density + x(col.life) +
  x_label("Life expectancy (years)") +
  title("area * density"))
data(gapminder_2007) + area * density + x(:life) +
  x_label("Life expectancy (years)") + title("area * density")
plot(data(gapminder_2007), layer(area, density), x(col.life),
  x_label("Life expectancy (years)"), title("area * density"))
40 60 80 0.00 0.01 0.02 0.03 0.04 area * density Density Life expectancy (years)

“Given gapminder 2007: an area derived by density, x is life.”

density is not the area’s own transform. Five other marks estimate the same curve, and only the shape changes:

Mark derived by density Shape
line * density the curve as a stroke
step * density the curve as a staircase
point * density one dot at each point where the curve was estimated
bar * density one thin bar at each of those points
ribbon * density with a category, the curve mirrored: the violin
area * density the curve filled to the baseline, the plot above

Put line, step, point or bar in place of area in the sentence above, and each one draws. ribbon refuses it, because a ribbon spans between two boundaries and one curve gives only one. With a category, ribbon * density + x(<category>) + y(<number>) draws the violin.

Give the area * density sentence a category on x and a number on y, and the curve runs along y instead of x. One estimate is drawn across each category’s slot. This plot is the half violin:

data(gapminder_2007) + area * density + x(continent) + y(life) +
  color(continent) +
  y_label("Life expectancy") +
  title("area * density + x(continent): the half violin")
(data(gapminder_2007) + area * density + x(col.continent) + y(col.life) +
  color(col.continent) +
  y_label("Life expectancy") +
  title("area * density + x(continent): the half violin"))
data(gapminder_2007) + area * density + x(:continent) + y(:life) +
  color(:continent) + 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), color(col.continent), y_label("Life expectancy"),
  title("area * density + x(continent): the half violin"))
Asia Europe Africa Americas Oceania 40 60 80 area * density + x(continent): the half violin Life expectancy Continent Continent Asia Europe Africa Americas Oceania

“Given gapminder 2007: areas derived by density, x is continent, y is life, color by continent.”

The two sentences differ in three places: x moved from life to continent, y(life) was added, and so was color(continent). With only x(life), life runs along x and y holds no column, so y carries the density estimate, as it carried the count for bin. With a category on x and a number on y, both axes hold a column, so each estimate is drawn across its category’s slot, one distribution per continent. color does what it did in Multiple series: a categorical column gives each region its own fill and a legend. On a line the same channel colors the stroke; on an area it colors the fill. The slots already keep the continents apart, so here color splits nothing; it gives each slot its own fill and a legend entry.

Can the two bindings be swapped? For density, yes. Put life on x and the category on y, and each curve runs along life, rising from its own category’s place on the y axis. That is the ridgeline plot, and it is the half violin’s sentence with x and y exchanged:

data(gapminder_2007) + area * density + x(life) + y(continent) +
  color(continent) +
  x_label("Life expectancy") + title("area * density + x(life): the ridgeline")
(data(gapminder_2007) + area * density + x(col.life) + y(col.continent) +
  color(col.continent) +
  x_label("Life expectancy") + title("area * density + x(life): the ridgeline"))
data(gapminder_2007) + area * density + x(:life) + y(:continent) +
  color(:continent) + x_label("Life expectancy") +
  title("area * density + x(life): the ridgeline")
plot(data(gapminder_2007), layer(area, density), x(col.life),
  y(col.continent), color(col.continent), x_label("Life expectancy"),
  title("area * density + x(life): the ridgeline"))
40 60 80 Oceania Americas Africa Europe Asia area * density + x(life): the ridgeline Continent Life expectancy Continent Asia Europe Africa Americas Oceania

“Given gapminder 2007: areas derived by density, x is life, y is continent, color by continent.”

Ribbon shows all four forms in one section: one side or two, and the category on either axis. Its violin section says what the width of a violin can mean.

The half violin and the ridgeline give each group a place of its own. To compare the groups in one panel instead, overlay their curves. Histograms of several groups drawn in one panel overlap, as the Bar chapter’s Overlaid histograms section showed with three species of iris. Smooth curves are easier to distinguish, because they cross at a point instead of sharing bin edges. In the iris flowers table, color(species) splits the rows, so density makes one estimate for each species, all in one panel.

As in Multiple series, a low opacity lets each region show through the others. As in Giving an area an edge, a line layered on top draws each region’s top edge. The line takes density too, so it traces the same curve the area fills. color is written before both marks, so it reaches both layers, as Encoding scope explains.

iris_flowers: first 5 of 150 rows
sepal_length sepal_width petal_length species
5.1 3.5 1.4 setosa
4.9 3.0 1.4 setosa
4.7 3.2 1.3 setosa
4.6 3.1 1.5 setosa
5.0 3.6 1.4 setosa
data(iris_flowers) + x(petal_length) + color(species) +
  area * density + style(opacity = 0.25) +
  line * density +
  x_label("Petal length (cm)") + title("Petal length, one curve per species")
(data(iris_flowers) + x(col.petal_length) + color(col.species) +
  area * density + style(opacity = 0.25) +
  line * density +
  x_label("Petal length (cm)") + title("Petal length, one curve per species"))
data(iris_flowers) + x(:petal_length) + color(:species) + area * density +
  style(opacity = 0.25) + line * density + x_label("Petal length (cm)") +
  title("Petal length, one curve per species")
plot(data(iris_flowers), x(col.petal_length), color(col.species),
  layer(area, density), style({ opacity: 0.25 }), layer(line, density),
  x_label("Petal length (cm)"),
  title("Petal length, one curve per species"))
2 4 6 0.0 0.5 1.0 1.5 Petal length, one curve per species Density Petal length (cm) Species setosa versicolor virginica

“Given the iris flowers: x is petal length, color by species, areas derived by density and also lines derived by density.”

The area under each density curve is 1, so the species whose petals vary least draws the tallest curve. Every setosa petal is between 1.0 and 1.9 cm long, so its curve is alone on the left and rises highest. Versicolor and virginica petals overlap from 4.5 to 5.1 cm, and around there each fill shows through the other. All three curves share one bandwidth, the width that decides how smooth each curve is. So a difference in their shapes comes from the data, not from the smoothing. Transforms explains how the shared bandwidth is chosen.

After bin and density, the third kind of transform is the aggregation family: sum, mean, median, max, min and quantile. Each leaves one value per year, and the area fills to those values, exactly as a bar would:

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"))
1960 1980 2000 0 20 40 60 area * mean: five countries averaged per year Mean life expectancy Year

“Given gapminder Asia: an area derived by mean, x is year, y is life.”

A transform can also keep every year and add the years up. cumulative adds each year’s value to the years before it, and the area fills to the total so far. The actuals table holds one year of sales in each row:

data(actuals) + area * cumulative + x(year) + y(sales) +
  title("area * cumulative: sales so far")
(data(actuals) + area * cumulative + x(col.year) + y(col.sales) +
  title("area * cumulative: sales so far"))
data(actuals) + area * cumulative + x(:year) + y(:sales) +
  title("area * cumulative: sales so far")
plot(data(actuals), layer(area, cumulative), x(col.year), y(col.sales),
  title("area * cumulative: sales so far"))
2019 2020 2021 2022 2023 0 200 400 600 area * cumulative: sales so far Cumulative Year

“Given the actuals: an area derived by cumulative, x is year, y is sales.”

The region rises from 120 in 2019 to 703 in 2023, the sales of all five years. stack adds up the groups at one year, and cumulative adds up the years of one group. The two compose: area * cumulative * stack with a color split piles the running totals of the groups.

11.7 A category on the domain

The mean in the area * mean plot above was computed for each year, and year is a continuous domain. Put a category there, and one summary per continent is usually drawn as bars. An area draws the five continent means as one connected shape instead. Use it when the reader should see one overall shape rather than compare five heights. Each category gets one slot, the boundary runs from slot to slot in axis order, and the region still fills down to zero:

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"))
Asia Europe Africa Americas Oceania 0 20 40 60 80 area * mean + x(continent): the filled profile Mean life expectancy Continent

gog draws that sentence. The boundary runs from Asia to Oceania in axis order, and the fill reaches from each mean down to zero. The segments between the categories are where the plot can claim more than was measured. Nothing was measured between Africa and the Americas, and a straight segment is drawn there anyway. A line across that gap claims a change. A fill claims a quantity as well, at every point between the two categories. If you read only the vertices, the segments help you compare the five heights. If you read the segments themselves, they claim what was never measured. To compare the five means and nothing else, swap area for bar: one atom away, and no segments to read.

Keep the fill when the overall shape is what the reader should see. Continents have no order, but a straight axis still puts one of them first and another last. A circle has no first or last place. Add polar(), and the same sentence draws the filled radar chart:

data(gapminder_2007) + area * mean + x(continent) + y(life) + polar() +
    title("area * mean + polar(): the filled radar")
(data(gapminder_2007) + area * mean + x(col.continent) + y(col.life) + polar() +
    title("area * mean + polar(): the filled radar"))
data(gapminder_2007) + area * mean + x(:continent) + y(:life) + polar() +
  title("area * mean + polar(): the filled radar")
plot(data(gapminder_2007), layer(area, mean), x(col.continent),
  y(col.life), polar(), title("area * mean + polar(): the filled radar"))
Asia Europe Africa Americas Oceania 20 40 60 80 area * mean + polar(): the filled radar Life Continent

“Given gapminder 2007: an area derived by mean, x is continent, y is life, in polar.”

11.8 What you can set

A setting changes how the region looks without reading a column, and each mark takes its own. These are an area’s, with the values each accepts:

Setting Value
style(color = ) any CSS color name or hex
style(opacity = ) 0 to 1
style(pattern = ) solid, hatch, crosshatch, stripes, grid, dots

That is the shortest settings list in the book, shared with ribbon. line has one more, size, and bar has two, border_color and border_size. Both are left out on purpose: One region, one fill explains size, and Giving an area an edge explains the border. The one setting this chapter has not shown yet is pattern, a texture on the fill. The plot below hatches the fill and draws the edge with a layered line, as before:

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"))
2019 2020 2021 2022 2023 0 50 100 150 A textured fill, and a line for the edge Sales Year

“Given the actuals: x is year, y is sales, an area colored seagreen, with pattern hatch and opacity 0.5, and also a line colored seagreen, with size 2.”

That layered line traces only the top boundary. A border would also draw a line along the two sides and the baseline.

The grid of every mark and every setting shows which other marks share this list. What a mark maps rather than sets is its row on the companion grid.

11.9 What it refuses

The first two refusals come from an axis. The first comes from the measure. A region fills to a baseline at zero, and a category has no zero. So a categorical y is refused, and the message asks for the category on x instead:

data(gapminder_2007) + area * mean + x(life) + y(continent)
data(gapminder_2007) + area * mean + x(col.life) + y(col.continent)
data(gapminder_2007) + area * mean + x(:life) + y(:continent)
plot(data(gapminder_2007), layer(area, mean), x(col.life),
  y(col.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.

density is the exception. Its curve runs along the continuous axis, and each category’s place on y is the baseline its curve fills to. With y(continent) it draws the ridgeline in With transforms.

The second comes from the domain. bin cuts a continuous axis into intervals, and a category is one slot with no width to cut. The message names count, the transform that tallies the rows in each category:

data(gapminder_2007) + area * bin + x(continent)
data(gapminder_2007) + area * bin + x(col.continent)
data(gapminder_2007) + area * bin + x(:continent)
plot(data(gapminder_2007), layer(area, bin), x(col.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: `area * count`.
gog: nothing was rendered. Fix the above, or set GOG_STRICT=0 to draw anyway.

density needs a continuous axis too, but either x or y can be that axis, so it refuses only when neither is continuous. Its message names proportion in the same way.

The next two come from the fill. A mapped opacity is the third. Each row is one vertex of the top edge, so no part of the region belongs to a single row, as One region, one fill explained.

data(gapminder_asia) + area + x(year) + y(life) + opacity(gdp)
data(gapminder_asia) + area + x(col.year) + y(col.life) + opacity(col.gdp)
data(gapminder_asia) + area + x(:year) + y(:life) + opacity(:gdp)
plot(data(gapminder_asia), area, x(col.year), y(col.life),
  opacity(col.gdp))
Error:
! gog: `opacity` cannot be bound to `area` — an area takes one opacity for the whole layer, not one per row. Drop the `gdp` mapping and set it with `style(opacity = )`, or use a mark that maps `opacity`: `point`, `bar`, `text` or `surface`.
gog: nothing was rendered. Fix the above, or set GOG_STRICT=0 to draw anyway.

A size is the fourth, and it is refused both ways. An area’s extent comes from its x, its y and its baseline, so there is no size to map and none to set:

data(actuals) + area + x(year) + y(sales) + style(size = 4)
data(actuals) + area + x(col.year) + y(col.sales) + style(size = 4)
data(actuals) + area + x(:year) + y(:sales) + style(size = 4)
plot(data(actuals), area, x(col.year), y(col.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: `point`, `line`, `step`, `interval`, `box`, `text`, `path` or `rule`.
gog: nothing was rendered. Fix the above, or set GOG_STRICT=0 to draw anyway.

A line accepts a size, because a stroke has a width to choose.

The fifth comes from the series. An area needs at least two positions in each series, as a line does. A plot in which every series holds one would draw nothing, so gog refuses it, as Line shows.

11.10 In short

An area measures on y, so it does not draw horizontally the way a bar does. The one exception is density, whose curve runs along whichever axis is continuous. An area’s interior belongs to no single row, so opacity can only be set. Its extent comes from its x, its y and its baseline, so size is refused both ways.

11.11 What comes next

An area slopes from one measurement to the next, and that slope claims the value changed steadily in between. Step claims instead that each value held until the next measurement, and then jumped.