How did the whole picture change from year to year? play cuts the plot into frames and shows them in sequence. It is the last channel in the kernel to draw, and the shortest way to say what it does is that it is faceting read in time. A facet splits the rows by a column’s distinct values and lays the pieces out across the page; play splits them exactly the same way and lays them out along the clock.
Four panels, then four frames, over identical rows. The grid is better for comparing two moments carefully, because both are in front of you and your eye can go back and forth. The sequence is better for seeing movement, because a point that shifts left is a point you watch move rather than one you have to find twice. Neither is a different plot; they are one plot read two ways, which is why they are one grammar with one column bound differently.
Print gets the first frame. This page in the PDF shows 1952 with its strip, and that is why the faceted version is here beside it: the grid is what a reader holding the printed book sees instead of the motion.
33.2 The column needs an order
play and facet split the rows the same way, so you might expect the same columns to suit both. They do not. The test is not the column’s type. It is whether the column has an order.
A facet lays its panels out in space, and you read space in any order you like. Your eye goes to the panel it wants, then comes back. A sequence lays its pieces out in time, and time arrives in one order only. So a sequence claims something a grid never claims: this frame comes after that one.
Bind a column with no order, and the plot shows movement the data does not have:
Watch the strip: Asia, Europe, Africa, Americas, Oceania. That order is not alphabetical, and it is not geographic. It is the order the continents first appear in the table, because the rows are sorted by country and Afghanistan comes before Albania. The animation is showing you how a file was sorted. The points jump between frames, and none of the jumps mean anything.
This is the same mistake as a rainbow ramp on a numeric column (Channels). The encoding carries an order the thing itself does not have, so a reader looks for a pattern that was never there. Written as | facet(continent), the five groups sit side by side and claim nothing about sequence.
Ordered does not mean numeric. A year is ordered, and so is a date. So is a text column whose levels you declared: months in calendar order, or a rating from low to high. gog runs the frames in the order the levels declare, the same way it runs an axis (Data). A text column with no declared levels falls back to the order its values first appear, which is the trap above.
How many values there are is a separate question, and a smaller one. Twelve frames and a hundred and forty frames are both worth animating, as long as each one means something beside the one before it. A long sequence costs time rather than sense, and speed is there for that.
33.3 Nothing moves that the data did not move
Every scale, the color map and every legend are fitted across the whole sequence at once, never per frame. That is the property that makes an animation worth trusting, and it is worth seeing what its absence would look like. If each frame fitted its own axis, 1952’s poorest countries and 2007’s would both sit at the left edge. A world that grew six-fold would draw as a world that stood still. The only thing actually moving would be the axis, silently, with nothing on the page to say so.
Because the frames share their scales, the opposite reads correctly: in the plot above, the cloud genuinely walks right and up, and the countries that do not move are countries that did not.
The color map is shared for a second reason. A reader tracks a bubble by its color, so a palette reassigned partway through would break the one thread they are holding on to. Every continent keeps its color in every frame, including frames where it happens to have no rows.
What is not shared is the statistics. A transform sees each frame’s rows as if they were the whole data, exactly as it sees a facet panel’s: the No Exceptions law applied to frames:
Each bar is that year’s tally of the countries that had passed sixty years of life expectancy. Asia climbs from four to thirty, Africa opens with none and ends with twelve, and Europe starts at twenty-three and then runs out of countries to add.
A tally can also have nothing to report, and it is worth seeing that once, because a still animation is sometimes an answer rather than a fault:
Twelve frames run and not a bar moves. The statistic is being recomputed in every one of them; it keeps arriving at the same number, because gapminder holds the same 142 countries in every year, so counting rows per continent counts the table and not the year. Only the strip changes, which is the plot saying so. Filtering first, as the sentence above it does, is what turns a count of countries into a count of something that happened.
What the frames do share is the cut, when there is one. A bin’s cells describe the axis rather than measuring the rows. One set of bins is cut from every frame’s rows at once, and each frame then counts only its own into them. Faceting sets out why, and the rule is the same word for word with “panel” read as “frame”.
The distribution walks right across half a century, and it does so against bars of one width, which is the only way the walk is legible.
33.4 A layer that does not play stands still
play is a channel, so it obeys the ordinary scope rule: written before any mark it reaches every layer, and written after one it binds that layer alone. A layer that never names it is drawn whole in every frame, which is how a fixed reference sits behind something that moves.
The two lines hold still while the cloud climbs past them. Nothing was added to the grammar to allow that: the reference layer simply never mentioned year, and a layer with no play binding has no frames to be cut into.
The same scope rule, in the cube and across panels at once. The route is the whole flight drawn at once, and the marker is where the glider actually is:
play is bound to the second layer because x, y and z were bound to the first, and a channel written after a mark reaches that mark alone. So the coil stands still in both panels while a dot climbs it, and you can see the tighter glider on the right outclimbing the wider one on the left, which is the whole reason the two tables are drawn together.
One detail of the second table is worth knowing, because it decides the size of the plot rather than its meaning. thermal_marks calls its time column instant where thermals calls the same measurement second. The frames are gathered from every table that carries the played column, not only from the layer that bound play, on the same rule that gives the plot one scale rather than one per layer. Had the marker also called its column second, the backdrop’s 170 samples would have set the frame count and the sequence would have run 170 frames instead of 17.
33.5 How fast
speed is a multiple of the normal pace, written on the binding beside scale and limits, because it belongs to play and to nothing else:
The same twelve frames this chapter opened with, three times as fast; speed = 0.5 would run them at half pace. Long sequences are the reason it exists, and gog says so before you ask: past about thirty frames it reports how many seconds the loop will take, because the length of a loop is a default you did not choose and cannot see.
33.6 The column that cannot do both jobs
There is one shape worth knowing because it looks so reasonable. Bind the same column to x and to play, on a mark that reads a function along x, and every frame is left holding a single position:
data(gapminder_asia) + line +x(year) +y(life) +color(country) +play(year, speed =3)
Error:
! gog: `line` reads a function along `x`, but `year` both cuts the plot into frames and supplies `x` — so every frame holds a single `year`, and a line needs at least two positions to read between. Animate a different column — `play` is what the frames advance through, and `x(year)` is what the line draws along. Or use `point`, which places each row on its own and so needs no domain.
gog: nothing was rendered. Fix the above, or set GOG_STRICT=0 to draw anyway.
A line needs at least two positions to read between. If each frame is one year, there is no domain left in it, and what you get is axes and a legend around an empty panel. That refusal is here because the first draft of this chapter contained exactly that sentence, and it rendered, silently, exit 0, a frame strip counting up over nothing. A reader spotted it.
The same is true of the same column split across the page rather than the clock, | facet(year), and for the same reason; the engine refuses both, because panels and frames are one partition asked twice. Marks that place each row on its own (point, bar, box) have no domain to lose, so they sweep through happily:
Five continents, twelve frames, one shared pair of axes. Each panel is its own story and the strip above them all says which year every story is at.
33.8 The cube also moves
Nothing about play belongs to the plane. A frame is a subset of the rows, and the third position is a way of looking at whatever rows you have, so the two compose, and neither needed anything added for the other:
That one is worth reading slowly, because it is the sentence this kernel was designed around. The vocabulary was drawn up with four words held in reserve for things it could not yet draw: the third position, the animation, the per-layer table, and the selection. The sentence above spends three of them at once. The fourth, brush, draws now too. Selection closes by adding it to this very sentence, so all four are spent in one line.
The cube is fitted once across the whole sequence, exactly as a flat plot’s axes are, and it earns more here than it does on the page. A box that resized itself per frame would swing its own corners around a cloud that had not moved, and every apparent motion would belong to the guide rather than to the countries. Fitted once, the height axis has room for 2007’s China from the first frame onward, which is why 1952 opens with the cloud pressed low and only China and India standing well clear of the floor.
A transform inside the cube is the same transform. The floor is cut once from every frame’s rows and each frame then counts only its own into the cells, which is this chapter’s shared-cut rule read with a second axis present:
Follow one continent’s row of columns rather than the whole floor. Each pile slides toward longer lives as the sequence runs, and it slides against cells that never move, so what you are watching is the countries changing which cell they fall in. color(continent) names the same column the y axis does, and it is there so that following one row is possible at all. A floor axis in a cube runs diagonally into the picture. Picking a row off it by eye means tracing a receding edge while five rows of columns stand in the way. The legend does in one glance what the axis makes you work for.
The palette is a choice the cube makes for you, and it is the same argument the okabe set was built on arriving from an unexpected direction. A solid in space is shaded per face, so one category is already three tones before any two categories are compared, and a palette whose hues sit close together loses the distinction that shading eats. Okabe-Ito’s eight are spaced for readers who cannot separate some hues at all, which turns out to be the same requirement as separating them after a third of the light has been taken out.
What the frames do not touch is the viewing angle. space(turn = , tilt = ) is a property of the space rather than of the data, so one angle serves the whole sequence and every frame is seen from the same place. That is not a gap waiting to be filled: a camera that swung while the sequence played would be motion carrying no column, and a reader would then have to subtract it from the motion that does carry one. play moves the data past a fixed eye.
You can still turn the plot yourself while it runs, and that is a different thing. The refusal above is about a camera that moves on its own, with no column behind it. A reader who drags knows which motion is theirs, and can stop it. This is worth more here than anywhere else in the book. Something is moving inside a solid shape. The only way to see what the near edge hides is to look around it.
Panels and frames do not come apart here, which is the point. They are one split asked twice, so crossing them costs nothing: the panels divide the page, the frames divide the clock, and each panel projects the cube its own rectangle has room for.
Five cubes, twelve moments, and one strip above them all naming the year, because a frame is the whole plot where a panel is one piece of it. That is four of the kernel’s ideas in one sentence: the third position, the panels, the frames, and a scale shared across every one of them so the cubes stay comparable while they run.
And the sheet moves too. ripples is a wave tank tapped in one corner, measured on a grid at twelve instants, in a shallow tank and a deep one.
ripples: first 5 of 5400 rows
across
along
height
tank
instant
20
20
3.547
Shallow
1
40
20
5.845
Shallow
1
60
20
4.353
Shallow
1
80
20
-0.178
Shallow
1
100
20
-4.265
Shallow
1
One row is one grid node at one instant in one tank, so instant counts frames from 1 to 12 rather than measuring a time, and height is a signed height in millimeters. Depth sets how fast water carries a wave, so the same tapping makes crests 140 mm apart in the shallow tank and 220 mm apart in the deep one:
Watch one arc leave the corner and cross the floor. It is the same wave in both panels and it is drawn against one shared height scale, so the difference you see is a wavelength and not a scaling. speed = 4 is doing real work here: at the normal pace twelve frames take nine and a half seconds, which is a slideshow of a wave rather than a wave.
The height is said twice, once as the third position and once as the color, and that is worth doing rather than a redundancy to apologize for. A sheet seen from one angle hides part of itself: the far corner is foreshortened and the near crests stand in front of the troughs behind them, so a shape that is plain at the front is guesswork at the back. Bright crest bands survive the projection where the height alone does not, and the wavelength this plot exists to compare is the spacing of those bands. palette("plasma") runs dark magenta through orange to yellow; Scales has the rest, including two diverging ramps whose neutral would sit at the still water line if the sign were the point instead.
33.9 The circle also moves
One space over, the same holds and for the same reason. polar() bends the panel, the frames choose the rows, and neither has to know about the other:
A rose of mean life expectancy, one petal per continent, opening across half a century. The radial axis is fitted over the whole sequence exactly as a flat one is, which is why the ring at 80 is already drawn in 1952 with nothing reaching it: the petals grow inside a circle that does not resize under them. Asia’s is the one to watch, from 46 years to 71. Africa’s climbs steadily until the late 1980s and then stops for fifteen years, which is a decade the average is reporting rather than smoothing away.
The pie is where that shared axis says something surprising, and it is worth following, because it is the flat rule bent rather than a rule of its own:
The circle does not close until the last frame. A pie’s turn is its measure axis, that axis is fitted across the sequence like every other, and 1952’s world holds a little under two fifths of 2007’s, so 1952 sweeps a little under two fifths of the turn. The identical sentence without polar() draws a stacked column that stops well short of the top of its axis, and nobody finds that surprising. A pie that does not close is that column bent.
If the question is each year’s shares rather than each year’s total, the sentence can say so, and then every frame closes:
The turn is one whole in every frame now, and what moves is how it divides: Asia widens a little from 58% to 61%, Europe falls from 17% to 9%, and Africa grows by half again, from 10% to 15%. Two sentences, one atom apart, answering two different questions about the same column. Choosing between them is the reader’s job and the grammar’s business is to keep both sayable.
33.10 Taking the sequence with you
Every plot on this page moves because the SVG carries its own timing. A browser reads that timing. Most other places do not. A message to a friend, a slide in a talk, a post on a social site: each of them shows the first frame and stops.
save_gif() writes the same sequence as a file those places do play:
Nothing has to be installed first. The engine converts and encodes the file itself, so the same line works on every machine that can draw the plot at all.
scale multiplies the canvas. A plot is 800 by 600 pixels unless its theme says otherwise, which is small for a post, so scale = 2 doubles both numbers. The pace carries over without being restated: this sequence was written speed = 4, and each frame holds for a fifth of a second in the file as it does on the page.
The file cannot disagree with the plot beside it, and the reason is worth one paragraph, because it is the same reason the last section gives for having one renderer. The frames are not drawn again one at a time. The plot is drawn once, with every scale, the color map and each legend fitted across the whole sequence, and each frame is that single drawing with one moment left showing. So there is no second set of decisions that could come out different.
Two sentences are refused. A plot with no play() has no moments to write, and saying so is more useful than writing a file of one frame:
still <-data(gapminder_2007) + point +x(gdp) +y(life)save_gif(still, file.path(tempdir(), "still.gif"))
Error:
! gog: this plot does not play, so it has no moments to write. Bind a column with an order to `play` — `play(year)` — or save the still picture it already is.
gog: nothing was rendered. Fix the above, or set GOG_STRICT=0 to draw anyway.
And the path has to end in .gif, because the name of a file is a claim about what is inside it:
moving <-data(gm_all) + point +x(gdp) +y(life) +play(year)save_gif(moving, file.path(tempdir(), "wave.png"))
Error:
! gog: `save_gif()` writes a GIF, so the path ends in `.gif` — `save_gif(p, "wave.gif")`.
One thing changes on the way out, and it is worth knowing before you send the file rather than after. A GIF is pixels. The text stops being text, so it no longer sharpens when a reader zooms in, and the colors are fitted to a palette of 256. The SVG remains the plot itself. The GIF is a copy of it made for somewhere that cannot read the original.
33.10.1 Why a GIF and not a video
A video file would be smaller and sharper than a GIF, and a few places accept video and nothing else. gog writes no video, and the reason is about what a package can carry rather than about what a plot is.
Making a video needs a video encoder. The common one is more than ten times the size of this whole engine, once for every platform a package supports, and its license does not allow it to travel inside a package licensed the way this one is. The other route is to require the reader to install it separately. That sounds harmless until you count who cannot. A reader who installs a prebuilt package rather than building from source gets whatever the package carries, so a tool that cannot fit inside it never reaches them at all. The feature would work for some readers and refuse for others, which is a worse answer than not having it.
A GIF asks for nothing. The engine writes it on every machine that can draw the plot, in every one of the four languages. When the file looks soft, raise scale. When you genuinely need a video, converting a GIF into one is a solved problem, and there are free converters online that do it in a step.
33.11 What it refuses
One column names the panels or names the frames, and not both. Drawn, it would put one panel’s rows in one frame and leave every other panel empty:
data(gm_four) + point +x(gdp) +y(life) +play(period) |facet(period)
Error:
! gog: `period` cannot both name the frames and name the panels — every panel but one would be empty in every frame. Split the page by one column and the time by another, or drop `| facet(period)` and let `play(period)` carry it.
gog: nothing was rendered. Fix the above, or set GOG_STRICT=0 to draw anyway.
A speed that is not a pace is refused where you wrote it:
data(gm_all) + point +x(gdp) +y(life) +play(year, speed =0)
Error:
! gog: `speed = 0` — a speed is a multiple of the normal pace, so it has to be above zero. `speed = 2` is twice as fast, `speed = 0.5` half.
speed belongs to play and to nothing else, because nothing else in the grammar spends time: a color is drawn all at once, so there is no duration for a multiplier to scale. That one is not shown here as a gog refusal, and the reason is worth saying. No binding gives any other channel a speed argument at all, so color(continent, speed = 2) fails in R (or in Python, or Julia, or JavaScript) before a specification is ever built. The engine still checks it, because a specification can also be written by hand, but you will never see that message from a sentence: the language you are writing in refuses first, which is the earlier and better place for it.
33.12 What is not here yet
Two things people expect from an animation are missing. Neither is an oversight, and the two are missing for different reasons. One waits on a word the grammar does not have. The other waits on nothing but the work.
There is no tweening: frames cut, they do not glide. A bubble in 1957 and a bubble in 1962 are two complete pictures shown in turn, not one bubble eased between two positions. What easing needs first is a key, something that says Afghanistan in one frame is the same country as Afghanistan in the next, and the grammar has no word for that yet. group looks like the word until you notice that a point does not take it, and the bubble everyone wants to see glide is a point. So the missing piece is a vocabulary decision rather than a drawing technique, which is why it is owed rather than half-built.
There is no transport: no play button, no pause, no scrub bar. The sequence loops.
For a while this looked like the harder of the two. A scrub bar seems to pick out a stretch of frames, and picking things out is what the brush word is for. That reading turned out to be wrong, and seeing why takes one paragraph. It is worth the paragraph, because it decides where the buttons belong.
Every frame is already in the file. The whole sequence is drawn once, and a clock decides which frame you see at each moment. So a scrub bar moves the clock. It chooses no rows, hides no rows, and changes nothing the sentence said. Play and pause do the same thing, one step smaller.
That puts all three on the same side of a line this book has already drawn. You can turn a plot in the cube with the mouse (Space). Turning it changes how you look, never what is drawn, so no word was added to the grammar for it. The transport is the same kind of control, for time instead of angle. It belongs to the page a plot is shown on, not to the sentence that describes the plot. Nothing in the grammar has to grow to get these buttons. They are simply not built yet.