30  Flow

Where did each part of a total go? Some tables record movement: each row says that some amount started in one place and finished in another. A flow diagram draws that movement. The places are grouped into stages, and the stages are drawn side by side. Bands carry each amount from stage to stage. The thicker the band, the larger the amount it carries. This picture is an alluvial diagram, one kind of Sankey diagram. The section Links in layers shows a Sankey diagram drawn from a table of links, and Where the limits are says what gog does not draw. No atom spells either name, just as there is no pie() atom.

gog builds that picture out of the table’s own columns. A stage is one of the table’s categorical columns. Its distinct values are the possible places at that stage, and each row is in one of them. The flow transform computes the whole diagram from one table of such columns. This chapter uses the passengers and crew of the Titanic, the ship that sank in 1912. titanic holds one row per group of people. Four columns describe each group: class, sex, age (child or adult) and survived. A fifth, n, counts the people in that group. The table has one row for every combination of those four columns, so some rows count zero people:

titanic: first 5 of 32 rows
class sex age survived n
1st Male Adult Yes 57
1st Male Adult No 118
1st Male Child Yes 5
1st Male Child No 0
1st Female Adult Yes 140

30.1 The finished diagram

Who survived the sinking, and did class and sex change their chance of survival? The diagram below answers both:

data(titanic) + y(n) +
  ribbon * flow(class, sex, survived) + color(survived) +
  zone * flow(class, sex, survived) +
  text * flow(class, sex, survived) + label(name) +
  title("Who survived, by class and sex")
(data(titanic) + y(col.n) +
  ribbon * flow(col["class"], col.sex, col.survived) + color(col.survived) +
  zone * flow(col["class"], col.sex, col.survived) +
  text * flow(col["class"], col.sex, col.survived) + label(col.name) +
  title("Who survived, by class and sex"))
data(titanic) + y(:n) + ribbon * flow(:class, :sex, :survived) +
  color(:survived) + zone * flow(:class, :sex, :survived) +
  text * flow(:class, :sex, :survived) + label(:name) +
  title("Who survived, by class and sex")
plot(data(titanic), y(col.n),
  layer(ribbon, flow(col.class, col.sex, col.survived)),
  color(col.survived),
  layer(zone, flow(col.class, col.sex, col.survived)),
  layer(text, flow(col.class, col.sex, col.survived)), label(col.name),
  title("Who survived, by class and sex"))
1st 2nd 3rd Crew Male Female Yes No class sex survived 0 1K 2K Who survived, by class and sex N Survived Yes No

“Given titanic: y is n, ribbons derived by flow through class, sex and survived, colored by survived, and also zones derived by flow through class, sex and survived, and text derived by flow through class, sex and survived, label by name.”

Read it from left to right. Each white rectangle marks a slot: the part of a stage that one place takes, such as a class, a sex or an outcome. Each band is a group of people who share a class, a sex and an outcome, and its color is the outcome: blue for the people who survived, orange for the people who did not. The blue bands that leave the Female slot are much thicker than the orange ones, because 344 of the 470 women and girls survived. At the Male slot the orange bands are thicker, because only 367 of the 1,731 men and boys survived. Class mattered too. 203 of the 325 people in first class survived, compared with 178 of the 706 in third class.

The axis on the left counts people. The count axis and the gridlines help while you learn to build a flow diagram, so most plots in this chapter keep them. A reader compares the thicknesses of the bands, not numbers on an axis. So a flow diagram you publish usually leaves out both, as Leaving out the count axis and the grid shows.

30.2 Three layers

The sentence above has three layers. In each one, * joins the same transform, flow(...), to a different mark, and each mark draws one part of the picture:

Layer What it draws
ribbon * flow(...) the bands, which carry the counts from stage to stage
zone * flow(...) the slots, one rectangle for each place at each stage
text * flow(...) + label(name) the names, one inside each slot

The third layer reads a column called name, and titanic has no such column. flow writes it: the column holds each slot’s name, such as 1st or Female. Other transforms write columns too, and Columns a transform writes lists them.

The three layers compute the same positions from the same stages. So the bands end at the edges of the slots, and each name is inside its slot. The next three sections add the layers one at a time, in the order of the table. They use two stages, class and survived, and Adding a stage then adds sex between them.

30.3 The bands

To build the diagram, start with the bands alone. Class is one stage and the outcome is another. Name the stages inside flow(), from left to right, and bind the count to y:

data(titanic) + ribbon * flow(class, survived) + y(n) +
  title("Who survived, by class")
(data(titanic) + ribbon * flow(col["class"], col.survived) + y(col.n) +
  title("Who survived, by class"))
data(titanic) + ribbon * flow(:class, :survived) + y(:n) +
  title("Who survived, by class")
plot(data(titanic), layer(ribbon, flow(col.class, col.survived)),
  y(col.n), title("Who survived, by class"))
class survived 0 1K 2K Who survived, by class N

“Given titanic: ribbons derived by flow through class and survived, y is n.”

Each band is one path: one place at each stage, here a class and then an outcome. This plot does not outline the slots or name them, so find a path by its position. Inside each stage the slots stack from the bottom, in the order the column declares: 1st to Crew, and Yes before No (Choosing the order of the slots shows how to change it). So the lowest band leaves the 1st slot and arrives at the Yes slot. It carries the 203 first-class passengers who survived, and it keeps that thickness at both ends. Rows that share a path become one band: their counts are added, and the band carries the total. The four 1st rows that end in Yes differ only in sex and age, and the atom names neither, so the four become one band.

flow() also sets both axes. With the count on y, the stages are drawn along the horizontal axis, each named by its column, in the order you name them inside flow(). The vertical axis is a continuous axis for the count. The bands at each stage stack from zero with no gaps. So the top edge of any band is the total of that band and every band below it.

The count is optional. Leave out y, and every row counts as 1, so the diagram counts rows instead of summing a column. partition uses that default too, and count tallies rows the same way. This table needs y(n): it holds one row per combination, so counting its rows would count combinations rather than people. A table with one row per person needs no y, as One row per person shows.

30.4 The slots

In the plot above, the bands that leave one place touch the bands that leave the next, so you cannot see where one place ends and the next begins. A zone layer draws the slots. Add zone * flow(class, survived), with the same stages as the bands. Both layers now use the count, so write y(n) before any mark, where it belongs to the plot and every layer reads it (Encoding scope):

data(titanic) + y(n) +
  ribbon * flow(class, survived) +
  zone * flow(class, survived) +
  title("The same flow, with its slots")
(data(titanic) + y(col.n) +
  ribbon * flow(col["class"], col.survived) +
  zone * flow(col["class"], col.survived) +
  title("The same flow, with its slots"))
data(titanic) + y(:n) + ribbon * flow(:class, :survived) +
  zone * flow(:class, :survived) + title("The same flow, with its slots")
plot(data(titanic), y(col.n),
  layer(ribbon, flow(col.class, col.survived)),
  layer(zone, flow(col.class, col.survived)),
  title("The same flow, with its slots"))
class survived 0 1K 2K The same flow, with its slots N

“Given titanic: y is n, ribbons derived by flow through class and survived, and also zones derived by flow through class and survived.”

Each rectangle marks one slot. It covers the same part of the count axis as the bands that leave it or arrive at it, so each slot is as tall as the number of people at that place. A categorical axis also gives each category a slot, but there every slot is the same size. The top slot of the class stage is the crew, the largest group, with 885 people. At the outcome stage the larger slot is No: 1,490 of the 2,201 people did not survive.

Each slot is white with a thin black line around it, so only the bands have color and the edge of every slot is visible. Those are the defaults, and Choosing the colors shows how to change them. The slots still have no names, so you still count from the bottom to find each slot.

30.5 The names

A text layer names the slots. The transform writes each slot’s name into a column called name, exactly as partition does. Add a third layer, text * flow(class, survived), with the same stages again, and bind label(name) to draw that column:

data(titanic) + y(n) +
  ribbon * flow(class, survived) +
  zone * flow(class, survived) +
  text * flow(class, survived) + label(name) +
  title("The same flow, with its slots named")
(data(titanic) + y(col.n) +
  ribbon * flow(col["class"], col.survived) +
  zone * flow(col["class"], col.survived) +
  text * flow(col["class"], col.survived) + label(col.name) +
  title("The same flow, with its slots named"))
data(titanic) + y(:n) + ribbon * flow(:class, :survived) +
  zone * flow(:class, :survived) + text * flow(:class, :survived) +
  label(:name) + title("The same flow, with its slots named")
plot(data(titanic), y(col.n),
  layer(ribbon, flow(col.class, col.survived)),
  layer(zone, flow(col.class, col.survived)),
  layer(text, flow(col.class, col.survived)), label(col.name),
  title("The same flow, with its slots named"))
1st 2nd 3rd Crew Yes No class survived 0 1K 2K The same flow, with its slots named N

“Given titanic: y is n, ribbons derived by flow through class and survived, and also zones derived by flow through class and survived, and text derived by flow through class and survived, label by name.”

Each name is drawn at the center of its slot. The layers are drawn in the order they are written, so the text layer comes last. If the text layer came before the zone layer, the white slots would hide the names.

The 1st slot is now easy to find, and the thicker of its two bands arrives at Yes. It carries the 203 of the 325 people in first class who survived, the number given under the finished diagram. Every later plot in this chapter keeps the three layers.

name is the only column the flow writes for a text layer to show, so you can leave label(name) out and get the same names.

30.6 One row per person

Every plot so far binds y(n), because each row of titanic stands for a group of people. Data you collect yourself, such as survey answers, often has one row per person and no column of counts. You do not need to count the people before you draw. Leave out y, and each row counts as 1. The table below has nine rows, one for each person:

people <- data.frame(
  class    = c("1st", "1st", "1st", "2nd", "2nd", "3rd", "3rd", "3rd", "3rd"),
  survived = c("Yes", "Yes", "No", "Yes", "No", "Yes", "No", "No", "No")
)

data(people) +
  ribbon * flow(class, survived) +
  zone * flow(class, survived) +
  text * flow(class, survived) + label(name) +
  title("One row per person, no count column")
people = {
  "class": ["1st", "1st", "1st", "2nd", "2nd", "3rd", "3rd", "3rd", "3rd"],
  "survived": ["Yes", "Yes", "No", "Yes", "No", "Yes", "No", "No", "No"],
}
(data(people) +
  ribbon * flow(col["class"], col.survived) +
  zone * flow(col["class"], col.survived) +
  text * flow(col["class"], col.survived) + label(col.name) +
  title("One row per person, no count column"))
people = (
  class = ["1st", "1st", "1st", "2nd", "2nd", "3rd", "3rd", "3rd", "3rd"],
  survived = ["Yes", "Yes", "No", "Yes", "No", "Yes", "No", "No", "No"],
)
data(people) + ribbon * flow(:class, :survived) +
  zone * flow(:class, :survived) + text * flow(:class, :survived) +
  label(:name) + title("One row per person, no count column")
const people = {
  class: ["1st", "1st", "1st", "2nd", "2nd", "3rd", "3rd", "3rd", "3rd"],
  survived: ["Yes", "Yes", "No", "Yes", "No", "Yes", "No", "No", "No"],
};
plot(data(people), layer(ribbon, flow(col.class, col.survived)),
  layer(zone, flow(col.class, col.survived)),
  layer(text, flow(col.class, col.survived)), label(col.name),
  title("One row per person, no count column"))
1st 2nd 3rd Yes No class survived 0 2 4 6 8 One row per person, no count column

Each band is as thick as the number of rows that share its path. Two of the three people in first class survived, so the lowest band, from 1st to Yes, is 2 rows thick. Three of the four people in third class did not survive, so the top band, from 3rd to No, is 3 rows thick, from 6 to 9 on the count axis. In people, class and survived declare no order, so their slots follow the order their values first appear in the table (Declaring the order of categories). That is why the Yes slot is below the No slot: Yes appears in the first row, and in the alphabet No would come first.

The same is true of the full titanic table. If each row of titanic is repeated as many times as its n says, the result has one row per person: 2,201 rows. Remove y(n) from the finished diagram’s sentence, and it draws the same bands and slots from those rows.

Counting first has one advantage: a table with one row per group has fewer rows, so it draws faster. With millions of rows, count them first and bind the count to y. How much data comes back shows the counting done in SQL. The next section draws titanic again, with y(n).

30.7 Adding a stage

The finished diagram has a third stage, sex, between class and the outcome. Add it by naming one more column inside flow(), in all three layers. Each path now passes through three stages, and each band is as thick as its count at every one:

data(titanic) + y(n) +
  ribbon * flow(class, sex, survived) +
  zone * flow(class, sex, survived) +
  text * flow(class, sex, survived) + label(name) +
  title("Class, then sex, then survival")
(data(titanic) + y(col.n) +
  ribbon * flow(col["class"], col.sex, col.survived) +
  zone * flow(col["class"], col.sex, col.survived) +
  text * flow(col["class"], col.sex, col.survived) + label(col.name) +
  title("Class, then sex, then survival"))
data(titanic) + y(:n) + ribbon * flow(:class, :sex, :survived) +
  zone * flow(:class, :sex, :survived) +
  text * flow(:class, :sex, :survived) + label(:name) +
  title("Class, then sex, then survival")
plot(data(titanic), y(col.n),
  layer(ribbon, flow(col.class, col.sex, col.survived)),
  layer(zone, flow(col.class, col.sex, col.survived)),
  layer(text, flow(col.class, col.sex, col.survived)), label(col.name),
  title("Class, then sex, then survival"))
1st 2nd 3rd Crew Male Female Yes No class sex survived 0 1K 2K Class, then sex, then survival N

Each band is now a group of people with one class, one sex and one outcome. sex declares no order, so its slots follow the order its values first appear in the table: Male at the bottom, then Female. The band from 1st through Female to Yes carries 141 people, the women and girls in first class who survived. Most people in the Female slot arrive at Yes, and most in the Male slot arrive at No, as in the finished diagram.

30.8 Choosing the order of the slots

The order of the slots in a stage decides how often the bands cross one another. In the plot above, Male is at the bottom of the middle stage, beside Yes. But most men arrive at No, at the top, and most women arrive at Yes, at the bottom, so their bands cross. You can choose an order with fewer crossings.

The order is not part of the sentence. It belongs to the column, so you set it in the table before you draw. Without a declaration, a column follows the order its values first appear in the table, as the last two sections showed. A declared order takes the place of that order. The book declares the order of class and survived when it reads titanic, as A declared order recommends, and leaves sex as plain text. In R the declaration is a factor, and in Python, Julia and JavaScript it is ordered(). The example below declares Female before Male. It declares the order on a copy, women_first, so that titanic keeps its own order for the rest of this chapter:

women_first <- titanic
women_first$sex <- factor(women_first$sex, levels = c("Female", "Male"))

data(women_first) + y(n) +
  ribbon * flow(class, sex, survived) + color(survived) +
  zone * flow(class, sex, survived) +
  text * flow(class, sex, survived) + label(name) +
  title("Female first, declared in the table")
women_first = titanic.copy()
women_first["sex"] = ordered(women_first["sex"], ["Female", "Male"])

(data(women_first) + y(col.n) +
  ribbon * flow(col["class"], col.sex, col.survived) + color(col.survived) +
  zone * flow(col["class"], col.sex, col.survived) +
  text * flow(col["class"], col.sex, col.survived) + label(col.name) +
  title("Female first, declared in the table"))
women_first = copy(titanic)
women_first["sex"] = ordered(women_first["sex"], ["Female", "Male"])

data(women_first) + y(:n) +
  ribbon * flow(:class, :sex, :survived) + color(:survived) +
  zone * flow(:class, :sex, :survived) +
  text * flow(:class, :sex, :survived) + label(:name) +
  title("Female first, declared in the table")
const women_first = { ...titanic };
women_first.sex = ordered(women_first.sex, ["Female", "Male"]);

plot(data(women_first), y(col.n),
  layer(ribbon, flow(col.class, col.sex, col.survived)), color(col.survived),
  layer(zone, flow(col.class, col.sex, col.survived)),
  layer(text, flow(col.class, col.sex, col.survived)), label(col.name),
  title("Female first, declared in the table"));
1st 2nd 3rd Crew Female Male Yes No class sex survived 0 1K 2K Female first, declared in the table N Survived Yes No

The sentence is the finished diagram’s, with women_first in place of titanic and its own title. Female is now the lowest slot of the middle stage, beside Yes, and Male is beside No. Most bands between the last two stages are now nearly horizontal, and few of them cross. The bands, the slots and the names all moved together, because every layer reads the same column.

The first line of each tab makes the copy. In R, women_first <- titanic already makes a copy. In Python, Julia and JavaScript, women_first = titanic would only give titanic a second name, so the declaration would change titanic as well. In your own work, declare the order once, on the table you draw from.

List every value of the column when you declare it. In R, a value you leave out becomes a missing value, and gog leaves its rows out of the diagram and says how many. In Python, Julia and JavaScript, a value you leave out gets a slot after the listed ones (Declaring the order of categories). In other plots order() sorts a categorical axis. With flow, gog refuses it, and its message points to the declaration.

30.9 Color from a stage

With three stages the bands cross one another, and when every band has the same color, one band is hard to follow through a crossing. A band runs through every stage, so any stage can color it. The finished diagram at the start of this chapter colors each band by survived, the slot it arrives at. Coloring by class gives each band the color of the slot it leaves at the first stage instead:

data(titanic) + y(n) +
  ribbon * flow(class, sex, survived) + color(class) +
  zone * flow(class, sex, survived) +
  text * flow(class, sex, survived) + label(name) +
  title("The same paths, colored by class")
(data(titanic) + y(col.n) +
  ribbon * flow(col["class"], col.sex, col.survived) + color(col["class"]) +
  zone * flow(col["class"], col.sex, col.survived) +
  text * flow(col["class"], col.sex, col.survived) + label(col.name) +
  title("The same paths, colored by class"))
data(titanic) + y(:n) + ribbon * flow(:class, :sex, :survived) +
  color(:class) + zone * flow(:class, :sex, :survived) +
  text * flow(:class, :sex, :survived) + label(:name) +
  title("The same paths, colored by class")
plot(data(titanic), y(col.n),
  layer(ribbon, flow(col.class, col.sex, col.survived)), color(col.class),
  layer(zone, flow(col.class, col.sex, col.survived)),
  layer(text, flow(col.class, col.sex, col.survived)), label(col.name),
  title("The same paths, colored by class"))
1st 2nd 3rd Crew Male Female Yes No class sex survived 0 1K 2K The same paths, colored by class N Class 1st 2nd 3rd Crew

Now you can see which classes arrive in each later slot. Coloring by survived and coloring by class answer two questions: who lived, and where each class went.

color() is written after the ribbon layer, so it belongs to the bands alone. Written before the marks, it would reach the slots and the names as well. gog refuses that, because a slot and its name take their color from style().

30.10 Color from another column

Did the women in third class survive as often as the women in first class? A color can show it inside each band, with no third stage. Any categorical column can color the bands, not only a stage. A column that is not a stage splits each band into one part for each of its values. A categorical color splits a line the same way, into one line for each category. The parts of one band lie next to each other across its thickness and follow its path, and the slots are the same as without the color. With survived as a color instead of a stage, a flow through class and sex shows who survived in every band:

data(titanic) + y(n) +
  ribbon * flow(class, sex) + color(survived) +
  zone * flow(class, sex) +
  text * flow(class, sex) + label(name) +
  title("Class, then sex, colored by survival")
(data(titanic) + y(col.n) +
  ribbon * flow(col["class"], col.sex) + color(col.survived) +
  zone * flow(col["class"], col.sex) +
  text * flow(col["class"], col.sex) + label(col.name) +
  title("Class, then sex, colored by survival"))
data(titanic) + y(:n) + ribbon * flow(:class, :sex) + color(:survived) +
  zone * flow(:class, :sex) + text * flow(:class, :sex) + label(:name) +
  title("Class, then sex, colored by survival")
plot(data(titanic), y(col.n), layer(ribbon, flow(col.class, col.sex)),
  color(col.survived), layer(zone, flow(col.class, col.sex)),
  layer(text, flow(col.class, col.sex)), label(col.name),
  title("Class, then sex, colored by survival"))
1st 2nd 3rd Crew Male Female class sex 0 1K 2K Class, then sex, colored by survival N Survived Yes No

“Given titanic: y is n, ribbons derived by flow through class and sex, colored by survived, and also zones derived by flow through class and sex, and text derived by flow through class and sex, label by name.”

Each band from a class to a sex now has a blue part for the people who survived and an orange part for the people who did not. The band from 1st to Female is nearly all blue: 141 of its 145 people survived. The band from 3rd to Female is less than half blue: 90 of 196. The finished diagram shows the same split with survived as a stage, where the outcome also gets slots of its own. As a color, the outcome needs no slots of its own, and the legend names its values.

Only a categorical column can split a band, because its values are groups. A continuous column is refused, as it is on every ribbon.

30.11 Choosing the colors

Every color in this chapter so far is a default. The bands take gog’s default palette and are translucent, the slots are white with a thin black line around them, and the names are near-black. You choose each one in its own layer.

The bands take their colors from a column, and palette() chooses the color for each value of that column. Write each value beside the color you want, as Colors by name explains. Name every value, since gog refuses a palette that leaves one out:

data(titanic) + y(n) +
  ribbon * flow(class, sex, survived) + color(survived) +
  palette(c(Yes = "steelblue", No = "darkgray")) +
  zone * flow(class, sex, survived) +
  text * flow(class, sex, survived) + label(name) +
  title("The survivors in blue")
(data(titanic) + y(col.n) +
  ribbon * flow(col["class"], col.sex, col.survived) + color(col.survived) +
  palette({"Yes": "steelblue", "No": "darkgray"}) +
  zone * flow(col["class"], col.sex, col.survived) +
  text * flow(col["class"], col.sex, col.survived) + label(col.name) +
  title("The survivors in blue"))
data(titanic) + y(:n) + ribbon * flow(:class, :sex, :survived) +
  color(:survived) + palette(["Yes" => "steelblue", "No" => "darkgray"]) +
  zone * flow(:class, :sex, :survived) +
  text * flow(:class, :sex, :survived) + label(:name) +
  title("The survivors in blue")
plot(data(titanic), y(col.n),
  layer(ribbon, flow(col.class, col.sex, col.survived)),
  color(col.survived), palette({ Yes: "steelblue", No: "darkgray" }),
  layer(zone, flow(col.class, col.sex, col.survived)),
  layer(text, flow(col.class, col.sex, col.survived)), label(col.name),
  title("The survivors in blue"))
1st 2nd 3rd Crew Male Female Yes No class sex survived 0 1K 2K The survivors in blue N Survived Yes No

“Given titanic: y is n, ribbons derived by flow through class, sex and survived, colored by survived, with a palette naming Yes steelblue and No darkgray, and also zones derived by flow through class, sex and survived, and text derived by flow through class, sex and survived, label by name.”

The bands of the survivors are blue and the others are gray, so the survivors are easy to find. A band is translucent, so it looks lighter than the color you name, and two bands that cross both stay visible. To give every band the same color, leave out color() and write style(color = ) after the ribbon layer. style(opacity = ) there sets how translucent the bands are, with or without color().

A slot belongs to one stage, so no single column can color every slot, and the same is true of the names. The zone and text layers take their settings from style() instead, one value for each layer. On the zone layer, color fills the slots, and border_color and border_size draw the line around each one. On the text layer, color and size set the color and the size of the names:

data(titanic) + y(n) +
  ribbon * flow(class, sex, survived) + color(survived) +
  palette(c(Yes = "steelblue", No = "darkgray")) +
  zone * flow(class, sex, survived) +
  style(color = "midnightblue", border_color = "white", border_size = 2) +
  text * flow(class, sex, survived) + label(name) +
  style(color = "white", size = 14) +
  title("Dark slots with white names")
(data(titanic) + y(col.n) +
  ribbon * flow(col["class"], col.sex, col.survived) + color(col.survived) +
  palette({"Yes": "steelblue", "No": "darkgray"}) +
  zone * flow(col["class"], col.sex, col.survived) +
  style(color = "midnightblue", border_color = "white", border_size = 2) +
  text * flow(col["class"], col.sex, col.survived) + label(col.name) +
  style(color = "white", size = 14) +
  title("Dark slots with white names"))
data(titanic) + y(:n) + ribbon * flow(:class, :sex, :survived) +
  color(:survived) + palette(["Yes" => "steelblue", "No" => "darkgray"]) +
  zone * flow(:class, :sex, :survived) +
  style(color = "midnightblue", border_color = "white", border_size = 2) +
  text * flow(:class, :sex, :survived) + label(:name) +
  style(color = "white", size = 14) + title("Dark slots with white names")
plot(data(titanic), y(col.n),
  layer(ribbon, flow(col.class, col.sex, col.survived)),
  color(col.survived), palette({ Yes: "steelblue", No: "darkgray" }),
  layer(zone, flow(col.class, col.sex, col.survived)),
  style({ color: "midnightblue", border_color: "white", border_size: 2 }),
  layer(text, flow(col.class, col.sex, col.survived)), label(col.name),
  style({ color: "white", size: 14 }),
  title("Dark slots with white names"))
1st 2nd 3rd Crew Male Female Yes No class sex survived 0 1K 2K Dark slots with white names N Survived Yes No

“Given titanic: y is n, ribbons derived by flow through class, sex and survived, colored by survived, with a palette naming Yes steelblue and No darkgray, and also zones derived by flow through class, sex and survived, colored midnightblue, with border color white and border size 2, and text derived by flow through class, sex and survived, label by name, colored white, with size 14.”

Each style() is written after the mark it changes, so it changes that layer and no other. On the dark fill, the white line shows where each slot ends, and border_size = 0 would remove it. The zone layer also takes opacity, as every mark does, and pattern, as the five filled regions do (Style).

30.12 Room for long names

In a flow that runs left to right the slots are tall and narrow. The names in titanic are short, so every one fits. A longer name, such as Member of the crew, would be wider than its slot, and gog would draw it across the slot’s edges, over the bands. There are two ways to give it more space.

The first turns the names. style(angle = 90) on the text layer turns every name upward, so it runs along its slot, as Turning a label explains:

data(titanic) + y(n) +
  ribbon * flow(class, sex, survived) + color(survived) +
  zone * flow(class, sex, survived) +
  text * flow(class, sex, survived) + label(name) + style(angle = 90) +
  title("Names turned 90 degrees")
(data(titanic) + y(col.n) +
  ribbon * flow(col["class"], col.sex, col.survived) + color(col.survived) +
  zone * flow(col["class"], col.sex, col.survived) +
  text * flow(col["class"], col.sex, col.survived) + label(col.name) + style(angle = 90) +
  title("Names turned 90 degrees"))
data(titanic) + y(:n) + ribbon * flow(:class, :sex, :survived) +
  color(:survived) + zone * flow(:class, :sex, :survived) +
  text * flow(:class, :sex, :survived) + label(:name) +
  style(angle = 90) + title("Names turned 90 degrees")
plot(data(titanic), y(col.n),
  layer(ribbon, flow(col.class, col.sex, col.survived)),
  color(col.survived),
  layer(zone, flow(col.class, col.sex, col.survived)),
  layer(text, flow(col.class, col.sex, col.survived)), label(col.name),
  style({ angle: 90 }), title("Names turned 90 degrees"))
1st 2nd 3rd Crew Male Female Yes No class sex survived 0 1K 2K Names turned 90 degrees N Survived Yes No

“Given titanic: y is n, ribbons derived by flow through class, sex and survived, colored by survived, and also zones derived by flow through class, sex and survived, and text derived by flow through class, sex and survived, label by name, with angle 90.”

Each name is now written upward, so it must fit the slot’s height, not the slot’s width.

The second places the stages from top to bottom. Bind the count to x instead of y, and the first stage is at the top of the plot. Each slot is then wide and short, so a long name fits across it unless the slot’s count is small. The sentence is the finished diagram’s, with x(n) in place of y(n) and a new title. gog takes the direction from the axis that holds the count, as it does for a bar chart (Horizontal bars):

data(titanic) + x(n) +
  ribbon * flow(class, sex, survived) + color(survived) +
  zone * flow(class, sex, survived) +
  text * flow(class, sex, survived) + label(name) +
  title("The same flow, from top to bottom")
(data(titanic) + x(col.n) +
  ribbon * flow(col["class"], col.sex, col.survived) + color(col.survived) +
  zone * flow(col["class"], col.sex, col.survived) +
  text * flow(col["class"], col.sex, col.survived) + label(col.name) +
  title("The same flow, from top to bottom"))
data(titanic) + x(:n) + ribbon * flow(:class, :sex, :survived) +
  color(:survived) + zone * flow(:class, :sex, :survived) +
  text * flow(:class, :sex, :survived) + label(:name) +
  title("The same flow, from top to bottom")
plot(data(titanic), x(col.n),
  layer(ribbon, flow(col.class, col.sex, col.survived)),
  color(col.survived),
  layer(zone, flow(col.class, col.sex, col.survived)),
  layer(text, flow(col.class, col.sex, col.survived)), label(col.name),
  title("The same flow, from top to bottom"))
1st 2nd 3rd Crew Male Female Yes No 0 1K 2K survived sex class The same flow, from top to bottom N Survived Yes No

“Given titanic: x is n, ribbons derived by flow through class, sex and survived, colored by survived, and also zones derived by flow through class, sex and survived, and text derived by flow through class, sex and survived, label by name.”

The stage names are now on the left: class at the top, then sex, then survived. The count is on the bottom axis, and each name fits inside its slot.

30.13 Leaving out the count axis and the grid

The count axis helped while the diagram was being built: it showed that the bands at each stage add up to the same total, just over 2,000 people. A reader of the finished diagram compares the thicknesses of the bands and the slots, and needs no numbers for that. So the count axis, the gridlines and the frame (the two lines along the bottom and the left of the panel) can be left out, and the gray panel can be white. For a diagram you publish, leaving them out is usually better: the bands are easier to follow without them. Write axis = FALSE on the count. In theme(), grid = "none" and frame = "none" leave out the gridlines and the frame, and background = "white" colors the panel (Axes, legends, gridlines and the frame). The stage names stay, because they belong to the other axis, which is still drawn:

data(titanic) + y(n, axis = FALSE) +
  ribbon * flow(class, sex, survived) + color(survived) +
  zone * flow(class, sex, survived) +
  text * flow(class, sex, survived) + label(name) +
  theme(grid = "none", frame = "none", background = "white") +
  title("Who survived, by class and sex")
(data(titanic) + y(col.n, axis = False) +
  ribbon * flow(col["class"], col.sex, col.survived) + color(col.survived) +
  zone * flow(col["class"], col.sex, col.survived) +
  text * flow(col["class"], col.sex, col.survived) + label(col.name) +
  theme(grid = "none", frame = "none", background = "white") +
  title("Who survived, by class and sex"))
data(titanic) + y(:n, axis = false) +
  ribbon * flow(:class, :sex, :survived) + color(:survived) +
  zone * flow(:class, :sex, :survived) +
  text * flow(:class, :sex, :survived) + label(:name) +
  theme(grid = "none", frame = "none", background = "white") +
  title("Who survived, by class and sex")
plot(data(titanic), y(col.n, { axis: false }),
  layer(ribbon, flow(col.class, col.sex, col.survived)),
  color(col.survived),
  layer(zone, flow(col.class, col.sex, col.survived)),
  layer(text, flow(col.class, col.sex, col.survived)), label(col.name),
  theme({ grid: "none", frame: "none", background: "white" }),
  title("Who survived, by class and sex"))
1st 2nd 3rd Crew Male Female Yes No class sex survived Who survived, by class and sex Survived Yes No

“Given titanic: y is n without an axis, ribbons derived by flow through class, sex and survived, colored by survived, and also zones derived by flow through class, sex and survived, and text derived by flow through class, sex and survived, label by name.”

Only the bands, the slots with their names, the legend and the stage names are left. A reader can still see that most of the women survived and most of the men did not. The exact counts are gone from the picture, so give them in the paragraph beside the plot, as The finished diagram does.

30.14 Selecting one place

Color tells the bands apart, but it does not show where the people in one place went. brush keeps the color of the bands that pass through one place and dims the rest. Name a stage, and use at to name the place the selection starts with, as a brush on a categorical column does:

data(titanic) + y(n) +
  ribbon * flow(class, sex, survived) + color(survived) +
  zone * flow(class, sex, survived) +
  text * flow(class, sex, survived) + label(name) +
  brush(sex, at = "Female") +
  title("The women and girls, and where they went")
(data(titanic) + y(col.n) +
  ribbon * flow(col["class"], col.sex, col.survived) + color(col.survived) +
  zone * flow(col["class"], col.sex, col.survived) +
  text * flow(col["class"], col.sex, col.survived) + label(col.name) +
  brush(col.sex, at = "Female") +
  title("The women and girls, and where they went"))
data(titanic) + y(:n) + ribbon * flow(:class, :sex, :survived) +
  color(:survived) + zone * flow(:class, :sex, :survived) +
  text * flow(:class, :sex, :survived) + label(:name) +
  brush(:sex, at = "Female") +
  title("The women and girls, and where they went")
plot(data(titanic), y(col.n),
  layer(ribbon, flow(col.class, col.sex, col.survived)),
  color(col.survived),
  layer(zone, flow(col.class, col.sex, col.survived)),
  layer(text, flow(col.class, col.sex, col.survived)), label(col.name),
  brush(col.sex, { at: "Female" }),
  title("The women and girls, and where they went"))
Male 1st 2nd 3rd Crew Female Yes No class sex survived 0 1K 2K The women and girls, and where they went N Survived Yes No

“Given titanic: y is n, ribbons derived by flow through class, sex and survived, colored by survived, and also zones derived by flow through class, sex and survived, and text derived by flow through class, sex and survived, label by name, and let the reader select sex, starting with Female.”

The bands that pass through Female keep their colors, and every other band is dimmed. A brush on a stage never cuts a band in two. A band is a group of people who share a place at every stage, so either all of them are in the selection or none of them are. The Male slot is dimmed as well. The slots at the other two stages hold both women and men, so they are not dimmed.

In the web edition, a click on a sex slot selects it. Click Male and the selection moves there. Click the selected slot again, a band, or an empty part of the panel, and nothing is dimmed. With a bare brush, a click selects a slot at any stage. A drag selects nothing on a flow, and the pointer shows no values, because each band and each slot stands for many rows. In the PDF, the plot keeps the selection the sentence states.

A flow keeps or dims each band, or each part of a band, as a whole. So the brushed column must be one that each band, or each part, holds a single value of. A stage is one. So is a column given to the bands’ color, because it splits each band into parts, as Color from another column shows. Any other column can hold two values in one band, and gog refuses it. age is a column of titanic, but not a stage of this flow:

data(titanic) + y(n) +
  ribbon * flow(class, sex, survived) + color(survived) +
  zone * flow(class, sex, survived) +
  brush(age, at = "Child")
(data(titanic) + y(col.n) +
  ribbon * flow(col["class"], col.sex, col.survived) + color(col.survived) +
  zone * flow(col["class"], col.sex, col.survived) +
  brush(col.age, at = "Child"))
data(titanic) + y(:n) + ribbon * flow(:class, :sex, :survived) +
  color(:survived) + zone * flow(:class, :sex, :survived) +
  brush(:age, at = "Child")
plot(data(titanic), y(col.n),
  layer(ribbon, flow(col.class, col.sex, col.survived)),
  color(col.survived),
  layer(zone, flow(col.class, col.sex, col.survived)),
  brush(col.age, { at: "Child" }))
Error:
! gog: `brush(age)` on a flow is valid grammar, but this engine does not draw it. A flow selects whole bands, and `age` is neither a stage nor a column that colors the bands, so it would cut across them. Brush a stage, such as `brush(class, at = ...)`, or map `age` to the `ribbon` layer's `color` or `pattern`, which splits each band by its values.
gog: nothing was rendered. Fix the above, or set GOG_STRICT=0 to draw anyway.

30.15 Two columns that name the same places

So far each column named in flow() has been a stage of its own. Some tables record amounts between places of one kind instead. trade_partners, from the Network chapter, records trade between countries: each row says how much one country exported to another, in billions of dollars:

trade_partners: first 5 of 22 rows
exporter importer billions
China United States 500
Mexico United States 455
Canada United States 420
United States Mexico 320
United States Canada 355

China appears in both columns. It exports to the United States, and it imports from Korea. A flow through exporter and importer would put each column at a stage of its own, so China would get two slots. shared = TRUE says that the two columns name one set of places. Each country then gets one slot, and every slot lies on one axis. The length of a country’s slot is the total of its exports and imports:

data(trade_partners) + x(billions) +
  ribbon * flow(exporter, importer, shared = TRUE) + color(exporter) +
  zone * flow(exporter, importer, shared = TRUE) +
  text * flow(exporter, importer, shared = TRUE) + label(name) +
  title("Trade between countries, as arcs")
(data(trade_partners) + x(col.billions) +
  ribbon * flow(col.exporter, col.importer, shared = True) + color(col.exporter) +
  zone * flow(col.exporter, col.importer, shared = True) +
  text * flow(col.exporter, col.importer, shared = True) + label(col.name) +
  title("Trade between countries, as arcs"))
data(trade_partners) + x(:billions) +
  ribbon * flow(:exporter, :importer, shared = true) + color(:exporter) +
  zone * flow(:exporter, :importer, shared = true) +
  text * flow(:exporter, :importer, shared = true) + label(:name) +
  title("Trade between countries, as arcs")
plot(data(trade_partners), x(col.billions),
  layer(ribbon, flow(col.exporter, col.importer, { shared: true })),
  color(col.exporter),
  layer(zone, flow(col.exporter, col.importer, { shared: true })),
  layer(text, flow(col.exporter, col.importer, { shared: true })),
  label(col.name), title("Trade between countries, as arcs"))
China United States Mexico Canada Japan Korea Germany France Poland Netherlands Vietnam Australia Brazil India Spain Trade between countries, as arcs Exporter China Mexico Canada United States Korea Germany Netherlands Japan Australia Brazil India France

“Given trade partners: x is billions, ribbons derived by flow through exporter and importer, shared, colored by exporter, and also zones derived by flow through exporter and importer, shared, and text derived by flow through exporter and importer, shared, label by name.”

This picture is an arc diagram. Each band rises from the country that exports and comes down at the country that imports. The farther apart the two countries are on the axis, the higher the band rises. Each end of a band is as wide as the amount it carries. color(exporter) gives each band the color of the country it leaves.

A country’s slot has up to two parts, with no line between them. The bands of its exports leave the white part, and the bands of its imports arrive at the gray part, to the right of the white part. Vietnam only imports, so its slot is all gray. At China, the band to the United States leaves the white part, and the band from Korea arrives at the gray part. The colors show this too: every band of China’s exports has China’s color. The two parts show it even without the legend. A style(color = ) on the zone layer colors the white part, and the gray part becomes a darker shade of that color.

The white part is for the first column in flow(), and the gray part is for the second. To put the exports in the gray part instead, name importer first:

data(trade_partners) + x(billions) +
  ribbon * flow(importer, exporter, shared = TRUE) + color(exporter) +
  zone * flow(importer, exporter, shared = TRUE) +
  text * flow(importer, exporter, shared = TRUE) + label(name) +
  title("Trade between countries, imports first")
(data(trade_partners) + x(col.billions) +
  ribbon * flow(col.importer, col.exporter, shared = True) + color(col.exporter) +
  zone * flow(col.importer, col.exporter, shared = True) +
  text * flow(col.importer, col.exporter, shared = True) + label(col.name) +
  title("Trade between countries, imports first"))
data(trade_partners) + x(:billions) +
  ribbon * flow(:importer, :exporter, shared = true) + color(:exporter) +
  zone * flow(:importer, :exporter, shared = true) +
  text * flow(:importer, :exporter, shared = true) + label(:name) +
  title("Trade between countries, imports first")
plot(data(trade_partners), x(col.billions),
  layer(ribbon, flow(col.importer, col.exporter, { shared: true })),
  color(col.exporter),
  layer(zone, flow(col.importer, col.exporter, { shared: true })),
  layer(text, flow(col.importer, col.exporter, { shared: true })),
  label(col.name), title("Trade between countries, imports first"))
United States China Mexico Canada Japan Korea Germany France Poland Netherlands Vietnam Australia Brazil India Spain Trade between countries, imports first Exporter China Mexico Canada United States Korea Germany Netherlands Japan Australia Brazil India France

Each slot now starts with the country’s imports, in white, and its exports follow in gray. The order of the countries changes too, but only at the start: the United States now comes first and China second. gog puts the countries in the order it first reads their names. In each row it reads the first column in flow() before the second, so the first row now gives the United States before China. If the columns have a declared order, gog uses that order instead.

The axis carries no numbers. Each amount takes space on the axis twice, once at each end of its band, so an axis with numbers would end at twice the total trade. The country names are written in place of the numbers, turned to read upward when they do not fit side by side. With the amount on y, as in y(billions), the axis runs up the page, the names are to its left, and the bands reach to the right.

At the right end, four narrow slots are side by side, and their names overlap: Australia, Brazil, India and Spain. This plot is 800 pixels wide, the default. At theme(width = 1400), 1,400 pixels wide, the four names no longer overlap. In the chord diagram below, each name is turned to read outward from the ring. Neighboring names are close together at the ring and farther apart at their outer ends. India and Spain still touch near the ring.

The panel is white and has no frame, because the plot has no numbers to read values from. theme() changes both, as it does for any plot: background colors the panel, and frame = "axes" draws the two lines along the bottom and the left (Axes, legends, gridlines and the frame).

A shared flow, one with shared = TRUE, reads two columns: the place each amount leaves, and the place it arrives at. A third column, for a route through three places, would be valid grammar, but this engine does not draw it.

30.16 The chord diagram

Add polar() to the arc diagram’s sentence, the one with exporter first. polar() puts the amounts on the angle, as it does for a pie, so the axis bends into a ring. This picture is a chord diagram:

data(trade_partners) + x(billions) +
  ribbon * flow(exporter, importer, shared = TRUE) + color(exporter) +
  zone * flow(exporter, importer, shared = TRUE) +
  text * flow(exporter, importer, shared = TRUE) + label(name) +
  polar() + title("Trade between countries, as chords")
(data(trade_partners) + x(col.billions) +
  ribbon * flow(col.exporter, col.importer, shared = True) + color(col.exporter) +
  zone * flow(col.exporter, col.importer, shared = True) +
  text * flow(col.exporter, col.importer, shared = True) + label(col.name) +
  polar() + title("Trade between countries, as chords"))
data(trade_partners) + x(:billions) +
  ribbon * flow(:exporter, :importer, shared = true) + color(:exporter) +
  zone * flow(:exporter, :importer, shared = true) +
  text * flow(:exporter, :importer, shared = true) + label(:name) +
  polar() + title("Trade between countries, as chords")
plot(data(trade_partners), x(col.billions),
  layer(ribbon, flow(col.exporter, col.importer, { shared: true })),
  color(col.exporter),
  layer(zone, flow(col.exporter, col.importer, { shared: true })),
  layer(text, flow(col.exporter, col.importer, { shared: true })),
  label(col.name), polar(), title("Trade between countries, as chords"))
China United States Mexico Canada Japan Korea Germany France Poland Netherlands Vietnam Australia Brazil India Spain Trade between countries, as chords Exporter China Mexico Canada United States Korea Germany Netherlands Japan Australia Brazil India France

“Given trade partners: x is billions, ribbons derived by flow through exporter and importer, shared, colored by exporter, and also zones derived by flow through exporter and importer, shared, and text derived by flow through exporter and importer, shared, label by name, in polar.”

Each country’s slot is now a piece of the ring, and its length is still the total of the country’s exports and imports. It keeps its two parts, white for exports and gray for imports. Each band curves toward the center of the circle, from the country that exports to the country that imports. Like the arc diagram’s axis, the ring carries no numbers, and the names are outside it.

In a chord diagram, a band’s area can mislead. A band’s width at its two ends is the amount it carries, but its area is not. A band between countries far apart on the ring is long, and a band between two countries side by side on the ring is short. The countries are placed around the ring in the order they first appear in the table, so the length of a band says nothing about the amounts. Canada exported 420 billion dollars to the United States, and China exported 500. Yet Canada’s band covers more of the picture, because China is next to the United States. Read a band at its two ends. The next section shows an easier way to read the diagram: one country at a time.

30.17 Selecting a place in either column

A brush on one column selects by that column alone. brush(exporter, at = "China") keeps the bands that leave China and dims the bands that arrive there. To keep both, brush the flow’s own name, the column that label(name) reads. That brush keeps every band that leaves or arrives at the place. Each band holds two names, one at each end. Selecting one place refuses a column with two values in one band, and name is the one exception.

data(trade_partners) + x(billions) +
  ribbon * flow(exporter, importer, shared = TRUE) + color(exporter) +
  zone * flow(exporter, importer, shared = TRUE) +
  text * flow(exporter, importer, shared = TRUE) + label(name) +
  brush(name, at = "China") +
  title("What China exports and imports")
(data(trade_partners) + x(col.billions) +
  ribbon * flow(col.exporter, col.importer, shared = True) + color(col.exporter) +
  zone * flow(col.exporter, col.importer, shared = True) +
  text * flow(col.exporter, col.importer, shared = True) + label(col.name) +
  brush(col.name, at = "China") +
  title("What China exports and imports"))
data(trade_partners) + x(:billions) +
  ribbon * flow(:exporter, :importer, shared = true) + color(:exporter) +
  zone * flow(:exporter, :importer, shared = true) +
  text * flow(:exporter, :importer, shared = true) + label(:name) +
  brush(:name, at = "China") + title("What China exports and imports")
plot(data(trade_partners), x(col.billions),
  layer(ribbon, flow(col.exporter, col.importer, { shared: true })),
  color(col.exporter),
  layer(zone, flow(col.exporter, col.importer, { shared: true })),
  layer(text, flow(col.exporter, col.importer, { shared: true })),
  label(col.name), brush(col.name, { at: "China" }),
  title("What China exports and imports"))
United States Mexico Canada Japan Korea Germany France Poland Netherlands Vietnam Australia Brazil India Spain China What China exports and imports Exporter China Mexico Canada United States Korea Germany Netherlands Japan Australia Brazil India France

“Given trade partners: x is billions, ribbons derived by flow through exporter and importer, shared, colored by exporter, and also zones derived by flow through exporter and importer, shared, and text derived by flow through exporter and importer, shared, label by name, and let the reader select name, starting with China.”

Every band that leaves or arrives at China keeps its color. Every other band is dimmed, and so is every other country’s slot. In the web edition, a click on a country’s slot selects that country instead. A click on the selected slot, on a band, or on an empty part of the panel clears the selection. show rows lists the selected rows with both exporter and importer, so each row says whether it is an export from China or an import into China.

The same brush and the same click work on the chord diagram:

data(trade_partners) + x(billions) +
  ribbon * flow(exporter, importer, shared = TRUE) + color(exporter) +
  zone * flow(exporter, importer, shared = TRUE) +
  text * flow(exporter, importer, shared = TRUE) + label(name) +
  polar() + brush(name, at = "China") +
  title("What China exports and imports, as chords")
(data(trade_partners) + x(col.billions) +
  ribbon * flow(col.exporter, col.importer, shared = True) + color(col.exporter) +
  zone * flow(col.exporter, col.importer, shared = True) +
  text * flow(col.exporter, col.importer, shared = True) + label(col.name) +
  polar() + brush(col.name, at = "China") +
  title("What China exports and imports, as chords"))
data(trade_partners) + x(:billions) +
  ribbon * flow(:exporter, :importer, shared = true) + color(:exporter) +
  zone * flow(:exporter, :importer, shared = true) +
  text * flow(:exporter, :importer, shared = true) + label(:name) +
  polar() + brush(:name, at = "China") +
  title("What China exports and imports, as chords")
plot(data(trade_partners), x(col.billions),
  layer(ribbon, flow(col.exporter, col.importer, { shared: true })),
  color(col.exporter),
  layer(zone, flow(col.exporter, col.importer, { shared: true })),
  layer(text, flow(col.exporter, col.importer, { shared: true })),
  label(col.name), polar(), brush(col.name, { at: "China" }),
  title("What China exports and imports, as chords"))
United States Mexico Canada Japan Korea Germany France Poland Netherlands Vietnam Australia Brazil India Spain China What China exports and imports, as chords Exporter China Mexico Canada United States Korea Germany Netherlands Japan Australia Brazil India France

In the ring too, China’s bands keep their colors, and every other band and slot is dimmed. On a flow with stages, brush(name, at = "Female") keeps the same bands as brush(sex, at = "Female"). The two differ only when one name appears at two stages, and name then selects both.

30.19 A Sankey diagram and an alluvial diagram

A layered flow adds up the amounts that arrive at a place. Salary and Side work both arrive at Income. The band from Income to Housing holds money from each, and the picture cannot show how much. The alluvial diagram at the start of this chapter keeps each group of people on its own band, from the first stage to the last. So when every row has a value at every stage, name those columns as stages, and each band keeps its group. Use layered = TRUE for a table of links, which stage columns cannot hold.

Adding up at each place also decides what a selection keeps. brush(name, at = "Housing") keeps the links of Housing itself: the band that arrives at Housing, and the bands that leave it for Rent, Utilities and Repairs:

data(budget) + y(amount) +
  ribbon * flow(source, target, layered = TRUE) + color(source) +
  zone * flow(source, target, layered = TRUE) +
  text * flow(source, target, layered = TRUE) + label(name) +
  brush(name, at = "Housing") +
  title("What arrives at Housing and what leaves it")
(data(budget) + y(col.amount) +
  ribbon * flow(col.source, col.target, layered = True) + color(col.source) +
  zone * flow(col.source, col.target, layered = True) +
  text * flow(col.source, col.target, layered = True) + label(col.name) +
  brush(col.name, at = "Housing") +
  title("What arrives at Housing and what leaves it"))
data(budget) + y(:amount) +
  ribbon * flow(:source, :target, layered = true) + color(:source) +
  zone * flow(:source, :target, layered = true) +
  text * flow(:source, :target, layered = true) + label(:name) +
  brush(:name, at = "Housing") +
  title("What arrives at Housing and what leaves it")
plot(data(budget), y(col.amount),
  layer(ribbon, flow(col.source, col.target, { layered: true })),
  color(col.source),
  layer(zone, flow(col.source, col.target, { layered: true })),
  layer(text, flow(col.source, col.target, { layered: true })),
  label(col.name), brush(col.name, { at: "Housing" }),
  title("What arrives at Housing and what leaves it"))
Salary Income Side work Taxes Food Transport Leisure Savings Rent Utilities Repairs Energy Water Groceries Eating out Car Season ticket Fuel Insurance Holidays Hobbies Subscriptions Housing What arrives at Housing and what leaves it Source Salary Side work Income Housing Utilities Food Transport Car Leisure

The bands from Utilities to Energy and Water stay dimmed, although here all of their money passed through Housing. A place can receive money from several places, as Income does. A band that leaves such a place carries some of each, and the picture cannot show how much. So a selection keeps only the bands that arrive at the place or leave it, whatever the table holds. In the web edition, a click on a slot selects that place, as on the other flows.

30.20 What flow refuses

A flow needs at least two stages, because movement needs a place to start and a place to finish:

data(titanic) + y(n) + ribbon * flow(class)
data(titanic) + y(col.n) + ribbon * flow(col["class"])
data(titanic) + y(:n) + ribbon * flow(:class)
plot(data(titanic), y(col.n), layer(ribbon, flow(col.class)))
Error:
! gog: `flow()` needs at least two stage columns, in reading order — `flow(class, sex, survived)` runs each row from its `class` to its `survived`. One column has no between.

flow() puts the stages on the axis that does not hold the count. With y(n) that is the x axis, so x(class) asks for the same axis as the stages, and gog refuses it:

data(titanic) + y(n) + x(class) + ribbon * flow(class, survived)
data(titanic) + y(col.n) + x(col["class"]) + ribbon * flow(col["class"], col.survived)
data(titanic) + y(:n) + x(:class) + ribbon * flow(:class, :survived)
plot(data(titanic), y(col.n), x(col.class),
  layer(ribbon, flow(col.class, col.survived)))
Error:
! gog: under `flow` one axis carries the count and the other the stages, and both `x(class)` and `y(n)` are bound. Keep `y(<amount>)` to run the stages left to right, or `x(<amount>)` to run them top to bottom.
gog: nothing was rendered. Fix the above, or set GOG_STRICT=0 to draw anyway.

Only ribbon, zone and text can read what flow computes. Any other mark is refused, and the message names the three:

data(titanic) + y(n) + bar * flow(class, survived)
data(titanic) + y(col.n) + bar * flow(col["class"], col.survived)
data(titanic) + y(:n) + bar * flow(:class, :survived)
plot(data(titanic), y(col.n), layer(bar, flow(col.class, col.survived)))
Error:
! gog: `flow` lays a path's magnitude through its stages — an interval at each stage, and a band between adjacent ones — and `bar` has no reading for that. Three marks do: `ribbon * flow(<a>, <b>)` draws the bands, `zone * flow(<a>, <b>)` draws each stage's slots, and `text * flow(<a>, <b>) + label(name)` names them. Bind `y(<amount>)` to weigh the paths, or bind nothing and each row counts 1.
gog: nothing was rendered. Fix the above, or set GOG_STRICT=0 to draw anyway.

And a continuous column cannot color a band, just as it cannot color any ribbon. n is a continuous column:

data(titanic) + y(n) + ribbon * flow(class, survived) + color(n)
data(titanic) + y(col.n) + ribbon * flow(col["class"], col.survived) + color(col.n)
data(titanic) + y(:n) + ribbon * flow(:class, :survived) + color(:n)
plot(data(titanic), y(col.n),
  layer(ribbon, flow(col.class, col.survived)), color(col.n))
Error:
! gog: `color(n)` maps a continuous (numeric) column, but `color` on `ribbon` needs a categorical (text) column.
gog: nothing was rendered. Fix the above, or set GOG_STRICT=0 to draw anyway.

30.21 Where the limits are

The two limits below are not mistakes in your sentence. Each is valid grammar that this engine does not draw.

A flow with stages is valid grammar in polar() too. Each stage would get a ring of its own, so the picture would be a circular alluvial diagram, not the chord diagram above. This engine does not draw it, and the message points to shared = TRUE for two columns that name one set of places:

data(titanic) + y(n) + ribbon * flow(class, survived) + polar()
data(titanic) + y(col.n) + ribbon * flow(col["class"], col.survived) + polar()
data(titanic) + y(:n) + ribbon * flow(:class, :survived) + polar()
plot(data(titanic), y(col.n),
  layer(ribbon, flow(col.class, col.survived)), polar())
Error:
! gog: `flow` in `polar()` is valid grammar, a flow bent round a rim with a ring for each stage, but this engine does not draw it. Draw the flow flat. If two columns name one set of places, `flow(class, survived)` with `shared` set to true draws them in `polar()` as the chord diagram.
gog: nothing was rendered. Fix the above, or set GOG_STRICT=0 to draw anyway.

A layered flow has a limit too. Its bands may skip a layer, or stop before the last layer, but its links may not form a cycle. Links that form a cycle have no layers, because no place in the cycle comes first. A household that moves money from its checking account to its savings account, and back again, makes a cycle. gog refuses it, and the message names the cycle:

accounts <- data.frame(source = c("Checking", "Savings"),
                       target = c("Savings", "Checking"), amount = c(200, 50))
data(accounts) + y(amount) + ribbon * flow(source, target, layered = TRUE)
accounts = {
  "source": ["Checking", "Savings"],
  "target": ["Savings", "Checking"],
  "amount": [200, 50],
}
data(accounts) + y(col.amount) + ribbon * flow(col.source, col.target, layered = True)
accounts = (
  source = ["Checking", "Savings"],
  target = ["Savings", "Checking"],
  amount = [200, 50],
)
data(accounts) + y(:amount) +
  ribbon * flow(:source, :target, layered = true)
const accounts = {
  source: ["Checking", "Savings"],
  target: ["Savings", "Checking"],
  amount: [200, 50],
};
plot(data(accounts), y(col.amount),
  layer(ribbon, flow(col.source, col.target, { layered: true })))
Error:
! gog: these links form a cycle, Checking → Savings → Checking, and a layered flow stands each place after every place that sends to it, so a cycle has no order. A cycle drawn as a loop is valid grammar this engine does not draw. Set `shared` to true in place of `layered`: `flow(source, target)` then puts every place on one axis and draws a cycle as arcs. Or remove one of the links.
gog: nothing was rendered. Fix the above, or set GOG_STRICT=0 to draw anyway.

Some tools draw a cycle as a band that runs back across the diagram. The message points to shared = TRUE instead, which puts every place on one axis and draws the same links as arcs.

30.22 Choosing a flow diagram

This chapter has drawn four kinds of flow diagram: alluvial, Sankey, arc and chord. To choose one, start from what one row of your table says. The last row of the table is a fifth choice, the network diagram:

What one row says A question it answers The diagram
a group and its place at each of several stages, as in titanic How did each group move from stage to stage? an alluvial diagram (The finished diagram)
a link, an amount that moves from one place to the next, with no cycles, as in budget Where does the total go, step by step? a Sankey diagram (Links in layers)
an amount between two places of one kind, often in both directions, as in trade_partners Who sends to whom, and how much each way? an arc diagram, or the same links on a ring, a chord diagram (Two columns that name the same places)
a connection between two places, when who is connected matters more than how much What is connected to what? a network diagram (Network)

A flow diagram shows the pattern of the movement, but a band is hard to compare with a band in another part of the picture. A tile plot gives each pair of places its own cell, so every pair has a fixed place in a grid:

data(trade_partners) + zone + x(importer) + y(exporter) + color(billions) +
  title("Trade between countries, one cell per pair")
(data(trade_partners) + zone + x(col.importer) + y(col.exporter) + color(col.billions) +
  title("Trade between countries, one cell per pair"))
data(trade_partners) + zone + x(:importer) + y(:exporter) +
  color(:billions) + title("Trade between countries, one cell per pair")
plot(data(trade_partners), zone, x(col.importer), y(col.exporter),
  color(col.billions),
  title("Trade between countries, one cell per pair"))
United States Mexico Canada Japan Korea China Germany France Poland Netherlands Vietnam Spain France India Brazil Australia Japan Netherlands Germany Korea United States Canada Mexico China Trade between countries, one cell per pair Exporter Importer Billions 500 275 50

“Given trade partners: zones, x is importer, y is exporter, color by billions.”

Each row of cells is an exporter, and each column of cells is an importer. The darkest cell is China’s exports to the United States, the largest amount in the table. The cells for Mexico and Canada are nearly as dark, so the shades alone do not say which is larger. A reader compares lengths more accurately than shades of a color, so draw the amounts as bars when the comparison must be precise.

Four more suggestions help you draw a flow diagram. First, color the bands by the column that answers your question. On a flow with stages, the first stage shows where each group goes, and the last shows the outcomes. Second, add brush to select one place: the bands that meet it keep their colors, and the others are dimmed. With brush written, a click on a slot in the web edition selects another place. Third, use few places. Before you draw, give the small places one shared name in your table, such as Other. On a layered flow, give it only to places that do not link to each other: a link from Other to Other is a cycle. Fourth, the order of the places decides how often the bands cross, and in an arc or a chord diagram also how long they are. So declare an order that means something, such as by continent (Choosing the order of the slots).

30.23 What comes next

A flow with stages stacks each stage’s slots in the order you declare, or else in the table’s order. Cluster computes an order from the data instead: it joins the categories that are most alike into a tree, and puts an axis in the tree’s order.