| 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 |
48 Design laws
gog is governed by nine laws. They are not preferences or style guides; they are rules the package never breaks.
Every new feature, every argument, every naming decision is checked against these rules. If a proposed addition breaks one, it is rejected.
Laws 1 to 5 govern the grammar you write every day. Laws 6 to 8 govern the grammar’s structure: what composes, what renders, and what the engine may refuse. Those three follow Hangeul (한글), the Korean alphabet, most closely, and each opens with an example from it. Law 9 says what a specification is, and what it must never contain.
48.1 Law 1: Orthogonality
Every compatible atom combines with every other atom. No atom is redundant.
color works with point, line, bar, area, and every other mark. Whenever a mark’s geometry can show a channel, gog accepts that channel on that mark. A mark can still limit the kind of column: a bar takes a categorical color only.
One rule follows from this. A mark-specific version of a channel, such as point_color beside bar_color, breaks the law. The channel must be generalized, not specialized. This section shows no refusal, because gog has no mark-specific channel to refuse.
48.2 Law 2: No Exceptions
A transform behaves identically on every mark. No per-mark special cases. No silent letters.
Without this law you would have to learn each transform again for every mark that can read it. bin makes the same bins and counts the same rows, whichever mark reads it. Change only the mark, and what changes is the geometry:
((data(gapminder_2007) + bar * bin + x(life)) |
(data(gapminder_2007) + line * bin + x(life))) /
((data(gapminder_2007) + area * bin + x(life)) |
(data(gapminder_2007) + step * bin + x(life)))(((data(gapminder_2007) + bar * bin + x(col.life)) |
(data(gapminder_2007) + line * bin + x(col.life))) /
((data(gapminder_2007) + area * bin + x(col.life)) |
(data(gapminder_2007) + step * bin + x(col.life))))((data(gapminder_2007) + bar * bin + x(:life)) |
(data(gapminder_2007) + line * bin + x(:life))) /
((data(gapminder_2007) + area * bin + x(:life)) |
(data(gapminder_2007) + step * bin + x(:life)))below(beside(plot(data(gapminder_2007), layer(bar, bin), x(col.life)),
plot(data(gapminder_2007), layer(line, bin), x(col.life))),
beside(plot(data(gapminder_2007), layer(area, bin), x(col.life)),
plot(data(gapminder_2007), layer(step, bin), x(col.life))))“Given gapminder 2007: bars derived by bin, x is life, beside a line derived by bin, x is life; above an area derived by bin, x is life, beside a step outline derived by bin, x is life.”
The same bins and the same counts are drawn four ways. You get rectangles, a line through the bin centers, that same line filled, and the counts as a staircase. Read any bar’s height, then read the same bin in the panel beside it: the number is the same. The transform never asks which mark will draw its result. A transform that one mark ignored would be a silent letter: a letter that is written but never pronounced, like the k in knife.
This is the hardest law to keep as the package grows. Every user request that begins “but for maps I need…” or “histograms are special because…” is asking for this law to be broken. The engine keeps the law in two places. The code that computes a transform never looks at the mark, and one table records a decision for every pair of a mark and a transform, even when the decision is a refusal.
48.3 Law 3: Plain Names
Every taught name is a common English word. No acronyms. No abbreviations. Two words maximum, joined by
_. The only exceptions arex,yandz.
If a new user has to look up what a name means, it fails this law. svg, png and gif are acronyms, but a new user does not have to look them up: they are the usual names of three file formats. So render_svg(), save_svg(), save_png() and save_gif() keep them. The table pairs a name you may know from another library with the plain name gog uses:
| The name you may know | The plain name here |
|---|---|
alpha |
opacity |
loess, stat_smooth |
smooth |
coord_polar |
polar |
geom_point, aes |
point, and channels called by their plain names: x( ), color( ) |
color_by, size_by |
color, size |
In the last row a word was removed rather than replaced: the by of color_by. A plotting library could reasonably write color_by(species) and size_by(population). The suffix marks the argument as a column, which adds nothing in gog, because every channel takes a column. Now read color(continent) aloud. It is “color by continent”. The word by is already in how you read the sentence, so writing it into the name says it twice. In Law 2’s terms, that is a silent letter, and every channel would have had to carry one.
48.3.1 One spelling of English
The same reasoning decides how words are spelled. gog writes American English everywhere: color, center, the gray palette, and every word of the kernel. There is no British alternative. ggplot2 takes both spellings, so scale_colour_manual() and scale_color_manual() are one function in that package. gog has one spelling and no second one to learn.
Two spellings for one word is a silent letter, and Law 2 refuses silent letters in the vocabulary as well as in the grammar. The person who already knows both spellings notices nothing. Everyone else has more to learn and more to check. If gog took both spellings, a reader of your code would have to know both, a search for color( would miss every colour(, and every list of the vocabulary would list some words twice.
Many readers meet the spelling rule for the first time by typing colour, so the refusal names the word to write instead:
data(gapminder_2007) + point + x(gdp) + y(life) + style(colour = "tomato")data(gapminder_2007) + point + x(col.gdp) + y(col.life) + style(colour = "tomato")data(gapminder_2007) + point + x(:gdp) + y(:life) +
style(colour = "tomato")plot(data(gapminder_2007), point, x(col.gdp), y(col.life),
style({ colour: "tomato" }))Error:
! gog: `style(colour = )` is not a setting. gog spells it `color`: American English is the grammar's only spelling, and unlike ggplot2 there is no British alternative.
That refusal was for a setting, a fixed value written in style(). Writing colour as a channel is refused the same way. colour() is not a channel: it exists only so that gog can name the channel you meant.
data(gapminder_2007) + point + x(gdp) + y(life) + colour(continent)data(gapminder_2007) + point + x(col.gdp) + y(col.life) + colour(col.continent)data(gapminder_2007) + point + x(:gdp) + y(:life) + colour(:continent)plot(data(gapminder_2007), point, x(col.gdp), y(col.life),
colour(col.continent))Error:
! gog: there is no `colour()` channel. gog spells it `color(<column>)`: American English is the grammar's only spelling, and unlike ggplot2 there is no British alternative.
border_colour, centre and palette("grey") are refused too, each with the American word in its message. A color value is different: it is one of the color names web pages use, not a word of the grammar, and that list holds both gray and grey.
48.4 Law 4: Bind Once
A table is named once. Columns are then bare names. The nearest
data()wins.
Other plotting libraries often repeat the table in every call, or quote each column as a string. Naming the table once is what lets the rest of the sentence read as plain words.
# Correct: table named once, columns bare
data(gapminder_2007) + point + x(gdp) + y(life)data(gapminder_2007) + point + x(col.gdp) + y(col.life)data(gapminder_2007) + point + x(:gdp) + y(:life)plot(data(gapminder_2007), point, x(col.gdp), y(col.life))“Given gapminder 2007: points, x is gdp, y is life.”
Quoting a column name is not a typo. It is a habit brought from other plotting libraries, so gog names the fix instead of only reporting the error:
data(gapminder_2007) + point + x("gdp") + y("life")data(gapminder_2007) + point + x("gdp") + y("life")data(gapminder_2007) + point + x("gdp") + y("life")plot(data(gapminder_2007), point, x("gdp"), y("life"))Error:
! gog: `x("gdp")` binds a *value*, and a channel takes a *column*: `x(gdp)` maps the column called `gdp`.
This law makes a plot specification readable as an English sentence. It also lets gog refuse a quoted column as soon as the sentence runs, before the engine draws anything.
The law’s third line is about a second table. A data() written directly before a mark is the nearest one, and that mark reads it. Every other mark reads the plot’s table, the one named first. Data draws both cases.
48.5 Law 5: Explicit Over Implicit
Short is better than long, unless short is ambiguous. Then write the longer form.
Implicit behavior saves typing now and costs clarity later. gog chooses clarity: when a short spelling would make a reader guess what the engine decided, gog asks you for the longer form.
This law also decides what an error message must say. A refusal tells you what gog cannot do and what to write instead, never only that something went wrong.
48.5.1 Setting is not mapping
The clearest case of a short spelling that would make a reader guess is one fixed color for every row. The spelling a reader might try first is color("red"): the same channel, with quotes to say that red is a value and not a column. The quotes cannot always decide. color("continent") is genuinely ambiguous: a column, or a color named “continent”. gog could decide by asking “is this a column in the table?”. Then the same expression would mean one thing with one table and something else with the next.
So gog does not reuse the channel. A fixed value gets a word of its own instead: mapping is a channel, and setting is style():
data(gapminder_2007) + point + x(gdp) + y(life) +
style(color = "seagreen", opacity = 0.5)(data(gapminder_2007) + point + x(col.gdp) + y(col.life) +
style(color = "seagreen", opacity = 0.5))data(gapminder_2007) + point + x(:gdp) + y(:life) +
style(color = "seagreen", opacity = 0.5)plot(data(gapminder_2007), point, x(col.gdp), y(col.life),
style({ color: "seagreen", opacity: 0.5 }))“Given gapminder 2007: points, x is gdp, y is life, colored seagreen, with opacity 0.5.”
Separating them also lets each operation follow its own rules. A mapping gets a legend and a setting does not. And line cannot map opacity, because one stroke has one opacity, but it can still set it.
ggplot2 puts the boundary between mapping and setting in a different place. There the boundary is a pair of parentheses: color = "red" inside aes() is a mapping, and the same words outside aes() are a setting. gog moves that boundary from a punctuation mark to a word, so the atom you write says which one you meant. The parentheses can fail in one way a word cannot. aes(color = "red") is legal, and it maps every row to one category whose only level is “red”. You get a legend with one entry labeled “red”, and points drawn in the palette’s first color rather than in red.
48.5.2 An unknown name must not become a default
A palette name that matches nothing could be answered two ways: refuse it, or quietly draw in the default colors. gog refuses, and the message lists every name it would have accepted:
data(gapminder_2007) + point + x(gdp) + y(life) +
color(continent) + palette("red")(data(gapminder_2007) + point + x(col.gdp) + y(col.life) +
color(col.continent) + palette("red"))data(gapminder_2007) + point + x(:gdp) + y(:life) + color(:continent) +
palette("red")plot(data(gapminder_2007), point, x(col.gdp), y(col.life),
color(col.continent), palette("red"))Error:
! gog: `palette("red")` is not a known palette. Named palettes are gog, okabe, soft for categories; blue, viridis, magma, inferno, plasma, cividis, gray for numbers; blue_red, brown_teal for numbers that diverge from a center. `"red"` is a color, not a palette. To paint every mark one color use `style(color = "red")`; to give each category its own color, give `palette()` several colors, "red" among them.
gog: nothing was rendered. Fix the above, or set GOG_STRICT=0 to draw anyway.
A default breaks this law without announcing it: the plot still appears, so nothing looks wrong, and the person reading the plot is the one misled.
48.5.3 Illegal is not the same as unsupported
Refusing is not enough by itself. gog separates two kinds of refusal, because each one needs you to do something different:
| Refusal | Meaning | What to do |
|---|---|---|
| Illegal | The grammar forbids it. | Rewrite the sentence. |
| Unsupported | The grammar allows it; this engine does not draw it. | Remove the atom the message names, or use one this engine draws. |
Calling a valid combination “illegal” would teach you a rule that does not exist. So an Unsupported refusal says that the grammar allows the sentence and the engine does not draw it. An Illegal refusal gives the reason the grammar forbids it. Here are three Illegal refusals, each for a different reason:
| country | gold | silver | bronze |
|---|---|---|---|
| USA | 46 | 37 | 38 |
| China | 38 | 31 | 22 |
| Great Britain | 29 | 17 | 19 |
| Russia | 19 | 18 | 9 |
| Germany | 17 | 10 | 15 |
# Illegal: size encodes magnitude, and a category has none
data(gapminder_2007) + point + x(gdp) + y(life) + size(continent)data(gapminder_2007) + point + x(col.gdp) + y(col.life) + size(col.continent)data(gapminder_2007) + point + x(:gdp) + y(:life) + size(:continent)plot(data(gapminder_2007), point, x(col.gdp), y(col.life),
size(col.continent))Error:
! gog: `size(continent)` maps a categorical (text) column, but `size` on `point` needs a continuous (numeric) column. Use `color` or `shape` to distinguish categories.
gog: nothing was rendered. Fix the above, or set GOG_STRICT=0 to draw anyway.
# Illegal: a bar has no size feature at all; its extent is y
data(medals) + bar + x(country) + y(gold) + size(gold)data(medals) + bar + x(col.country) + y(col.gold) + size(col.gold)data(medals) + bar + x(:country) + y(:gold) + size(:gold)plot(data(medals), bar, x(col.country), y(col.gold), size(col.gold))Error:
! gog: `size` cannot be bound to `bar` — a bar has no size feature. Remove the `gold` mapping from `size`, or use a mark that maps it: `point` or `text`.
gog: nothing was rendered. Fix the above, or set GOG_STRICT=0 to draw anyway.
# Illegal: `z` gives a `point`, `path` or `bar` a third position (see Space),
# but a `line` is read along its x axis, and a cube has no left to right
data(medals) + line + x(country) + y(gold) + z(silver)data(medals) + line + x(col.country) + y(col.gold) + z(col.silver)data(medals) + line + x(:country) + y(:gold) + z(:silver)plot(data(medals), line, x(col.country), y(col.gold), z(col.silver))Error:
! gog: `line` reads a *domain* left to right — it sorts by `x` and draws one value for each — and a cube has no left to right: `x` is one of three equal positions, and at some viewing angles it runs into the page and becomes depth. A `line` in space would be sorted by an axis the reader cannot see, so this is refused rather than drawn. For a route through three dimensions use `path`, which is `line` with that sort removed: `path + x(<a>) + y(<b>) + z(silver)`.
gog: nothing was rendered. Fix the above, or set GOG_STRICT=0 to draw anyway.
The third refusal is easy to mistake for an Unsupported one. It is not: the grammar forbids a 3-D line, and the refusal above gives the reason. In a cube you can look straight along x, and a line sorted along x would then have no order you can see. The refusal suggests path instead, which is line without that sort, and path draws in space.
An Unsupported refusal is worded differently. It says that what you wrote is valid grammar, but this engine does not draw it:
# Unsupported: a log `z` is valid grammar; this engine draws `z` only on a linear scale
data(medals) + point + x(gold) + y(silver) + z(bronze, scale = "log")data(medals) + point + x(col.gold) + y(col.silver) + z(col.bronze, scale = "log")data(medals) + point + x(:gold) + y(:silver) + z(:bronze, scale = "log")plot(data(medals), point, x(col.gold), y(col.silver),
z(col.bronze, { scale: "log" }))Error:
! gog: a log `z`-axis is valid grammar, but this engine does not draw it: `z` is linear in 3-D. Drop `scale = "log"` on `z`, or put the log channel on `x`/`y`.
gog: nothing was rendered. Fix the above, or set GOG_STRICT=0 to draw anyway.
In every case gog refuses to render. It does not quietly drop the channel and return a plot that looks finished. A silently ignored mapping is a silent letter in software. You wrote it, and it changes nothing in the picture.
An Illegal refusal instead says why the grammar forbids what you wrote. That is the whole distinction. All four refusals are live: the engine prints them when this page is built. A check re-runs every refused sentence in the book, and fails if one draws a plot.
48.5.4 Drawing it anyway
Every refusal the engine makes ends with the same offer: fix it, or set GOG_STRICT=0 to draw anyway. That switch turns refusals into warnings. You get the same message, in the same words, and the plot is drawn as well.
Set GOG_STRICT on a line of its own, before the sentence you want drawn. The table gives that line in each language, and the line that clears the variable again:
| Language | Set it with | Clear it with |
|---|---|---|
| R | Sys.setenv(GOG_STRICT = 0) |
Sys.unsetenv("GOG_STRICT") |
| Python | os.environ["GOG_STRICT"] = "0" |
del os.environ["GOG_STRICT"] |
| Julia | ENV["GOG_STRICT"] = "0" |
delete!(ENV, "GOG_STRICT") |
| JavaScript | process.env.GOG_STRICT = "0" |
delete process.env.GOG_STRICT |
| Shell | export GOG_STRICT=0 |
unset GOG_STRICT |
Only 0 works. GOG_STRICT=false and GOG_STRICT=no still refuse, because a switch this simple is better with one spelling than with a list of words to guess at.
What you get is the plot the engine can still draw. The refused mapping is not always dropped: bar + size(gold) draws the bars at one width, and beside them a size legend running from 17 to 46 that matches no bar. That mismatch is what the refusal was protecting you from.
GOG_STRICT then stays set for the rest of your session. That is the part to remember: every later refusal is a warning too, and after many warnings, people stop reading them. Clear it once you have seen the plot you wanted.
Your binding makes some refusals before the engine is ever asked. The switch cannot change those, because only the engine reads it, and the engine is never reached. Your binding catches a quoted column name, a British spelling such as colour, a sentence that does not begin with data(), and a malformed atom such as box(whiskers = "middle").
48.6 Law 6: Compositional Invariance
A composed sub-expression means the same thing in every context. No enclosing expression may silently reinterpret an inner one.
The Korean syllable 하 (ha) is pronounced identically in 하늘 (haneul, sky), 하루 (haru, a day), and 하지만 (hajiman, but). 하 can be reused because its sound never changes, not because it carries a meaning of its own. English letters do not keep their sound: “ough” is a different sound in though, through, thought, tough, cough, and bough. A gog expression must be 하, never “ough”: layering, faceting, or animating a plot may add to it, but may not change what the existing part meant.
You can see the law by using one sub-expression in two sentences. bar * bin is one sub-expression: a mark and its transform, composed once.
data(gapminder_2007) + bar * bin + x(life)data(gapminder_2007) + bar * bin + x(col.life)data(gapminder_2007) + bar * bin + x(:life)plot(data(gapminder_2007), layer(bar, bin), x(col.life))Now put that same sub-expression inside a faceted sentence:
data(gapminder_2007) + bar * bin + x(life) | facet(continent)data(gapminder_2007) + bar * bin + x(col.life) | facet(col.continent)data(gapminder_2007) + bar * bin + x(:life) | facet(:continent)plot(data(gapminder_2007), layer(bar, bin), x(col.life),
across(col.continent))“Given gapminder 2007: bars derived by bin, x is life, split into panel columns by continent.”
The enclosing expression added panels. It did not reinterpret the sub-expression: each panel bins and counts exactly as the single plot did. That is the whole law, and it is what lets you read a long sentence one piece at a time. A sub-expression can also be kept in a variable, and the variable then stands for bar * bin wherever you write it.
48.7 Law 7: Minimum Syllable
A visible plot is a mark plus its required positions. A mark alone is silent; a position alone has no carrier.
In Hangeul the smallest pronounceable unit is a consonant plus a vowel: ㄱ (g) alone is silent, and 가 (ga) is the minimum syllable. A bare vowel cannot stand alone either: ㅏ (a) is written 아 (a), with the silent consonant ㅇ in front of it as its carrier.
A mark is the silent consonant: point with no positions draws nothing, because there is nowhere to draw it. gog refuses that sentence, and the refusal names each position that is missing:
data(gapminder_2007) + pointdata(gapminder_2007) + pointdata(gapminder_2007) + pointplot(data(gapminder_2007), point)Error:
! gog: `point` needs `x()` but none is set. Add `x(<column>)` — a point cannot be drawn without it.
gog: `point` needs `y()` but none is set. Add `y(<column>)` — a point cannot be drawn without it.
gog: nothing was rendered. Fix the above, or set GOG_STRICT=0 to draw anyway.
Add x(gdp) and y(life) and the same sentence draws. A position with no mark is the vowel with no carrier: x(gdp) alone answers no question, because nothing says what is drawn at that x.
48.8 Law 8: Pronounceable ≠ Usable
The grammar guarantees well-formedness, never good taste. Grammar is law; taste is advice.
꿲 (kkwek) obeys every Hangeul syllable rule and you can pronounce it, yet no Korean word uses it. Plots work the same way. This renders, because it is grammatically sound; whether it says anything is your judgment, not the engine’s:
# Legal, and nearly invisible: the engine obeys the grammar and does not judge
data(gapminder_2007) + point + x(gdp) + y(life) +
style(opacity = 0.05)(data(gapminder_2007) + point + x(col.gdp) + y(col.life) +
style(opacity = 0.05))data(gapminder_2007) + point + x(:gdp) + y(:life) + style(opacity = 0.05)plot(data(gapminder_2007), point, x(col.gdp), y(col.life),
style({ opacity: 0.05 }))It draws, and almost nothing is visible. No legal combination is ever refused for being ugly. A combination that looks pointless to one person can be exactly the plot another person needs.
The law has a second half, that well-formedness is enforced, and two space() arguments show it. space(turn = 390) is a full circle plus 30 degrees, so it is the same view as space(turn = 30), and gog draws it without comment. space(tilt = 95) is refused. The difference is not that one is uglier. A turn repeats every 360 degrees, so 390 is the same direction as 30, and you can stand there. A tilt stops at 90, where you are looking straight down, and going past 90 would turn the scene upside down. Taste never makes gog refuse a plot, and gog enforces well-formedness with no exceptions. Space shows both sentences, and shows why one of them is refused.
The engine will not judge, so here is the advice instead. gog exists so that you can describe a plot in clean, minimal syntax. It will let you combine anything the grammar allows, and that includes combinations that hide what you wanted to show. Leave out anything that does not help the reader see the main idea.
That advice is about the picture. A sentence needs the same care, because other people read it too. Two sentences can draw the identical plot while one is much harder to follow. As Law 4 says, a second data() applies only to the mark written directly after it, and the mark after that one reads the plot’s table again. A reader who does not know this reads such a sentence wrong. The grammar accepts the marks in either order, so the fix is a writing habit: write each mark next to the table it draws from. Data shows both orders.
48.9 Law 9: Universal Transcription
A specification describes the visual, never one renderer’s drawing commands.
A specification is declarative: you say what you want to see, never how to draw it. The form is far older than software. “And God said, Let there be light: and there was light” (King James Bible, 1769) states an outcome and nothing else. There is no procedure in it, no order of steps, and no mention of how the light is made. Every declarative sentence leaves that part out, and something else has to supply it. In gog the engine does, and every stroke on these pages was drawn from a sentence that never mentioned strokes.
What you write becomes a small, renderer-neutral specification: which marks, which columns on which channels, which transforms. One renderer reads that specification, and that renderer draws every SVG on these pages. The same specification, unchanged, is what all four bindings build. Being renderer-neutral also means that a specification holds no drawing command, such as “draw a line from here to there”: it says what to see, and the renderer decides how to draw it.
48.9.1 Why that renderer writes SVG
The law says the specification is neutral. It does not say which format the renderer writes, and SVG is a choice with reasons.
The first reason is visible in the picture itself. An SVG stores shapes and coordinates, not a grid of colored dots. A plot therefore redraws at any size it is given, and stays sharp at every one. Print one at poster size and its curves are still curves.
The second is that text stays text. Every axis label, tick number and title is written as characters in the file, not drawn as a picture of characters. You can select them, a search can find them, and a screen reader can read them aloud. It is also what lets a plot in this book carry Hangeul, or any other writing system, without the engine drawing a single letter: it writes the characters, and the font draws them.
The third reason is the one this project depends on most, and it is the least obvious. An SVG is a text file, so two of them can be compared character by character. That is what makes the promise of four bindings checkable: every sentence in this book is drawn from R, Python, Julia and JavaScript, and the four files must match exactly. Comparing pictures instead would mean comparing colored dots, and those dots change with the version of whatever drew them. An exact test would become an approximate one, and a small disagreement between two languages could be mistaken for a rounding difference.
The fourth is motion. A play animation is written into the same file as the plot, using SVG’s own timing elements. A time-lapse is one picture that moves, with no video file and no JavaScript beside it.
48.9.2 Why there is one renderer
Those four reasons decide the format. One question is left: why only one renderer writes it. gog has exactly one renderer, and the reason is the set of decisions a second renderer would have to copy.
A renderer decides five things the specification does not name: layout, tick selection, palettes, axis ranges, and label formatting. Write a second renderer beside the first and those five decisions exist twice. Two copies of a decision do not stay equal. One renderer would put ticks at 0, 20 and 40, the other at 0, 25 and 50, and one specification would then draw two different pictures.
That would break Law 9 from the renderer’s side: one specification would no longer describe one picture, and nothing in the design would show it. So there is exactly one renderer. A second one is possible only once those five decisions live in a single shared layer that both would read.
Raster output, a picture stored as a grid of colored dots such as a PNG file, comes from converting the SVG rather than from a second renderer. render_svg(), described in Functions around the grammar, returns the plot as ordinary text. It has the same name in all four languages:
svg <- render_svg(data(gapminder_2007) + point + x(gdp) + y(life))
cat(substr(svg, 1, 51))<svg xmlns="http://www.w3.org/2000/svg" width="800"
svg = render_svg(data(gapminder_2007) + point + x(col.gdp) + y(col.life))
print(svg[:51])<svg xmlns="http://www.w3.org/2000/svg" width="800"
svg = render_svg(data(gapminder_2007) + point + x(:gdp) + y(:life))
println(first(svg, 51))<svg xmlns="http://www.w3.org/2000/svg" width="800"
const svg = render_svg(plot(data(gapminder_2007), point, x(col.gdp), y(col.life)));
console.log(svg.slice(0, 51));<svg xmlns="http://www.w3.org/2000/svg" width="800"
Those are the first 51 characters; svg holds the whole plot, about 15,000 of them. Write it to a file and you have the plot on disk. Every picture in another format is made by converting this SVG. Each plot in this book’s PDF, for example, is the SVG converted by rsvg-convert, a command-line SVG converter. Converting keeps every decision about what a plot looks like in the one place that is allowed to make them, which is exactly what Law 9 requires.
The engine does that conversion itself in two cases, and they show which part is drawing and which part is converting. save_png() saves the plot as a picture for a document or a slide. A plot that binds play() moves in a browser and nowhere else, so save_gif() writes the same sequence as a picture that a slide or a message will play. Both are still conversion in the sense this section means. The one renderer draws the plot, and the code that writes each file only turns the drawing into pixels. It chooses no tick and no layout, and its letters are drawn with the fonts installed on the computer, as a browser draws the SVG’s. The GIF code makes one decision of its own: how to fit each frame into the 256 colors a GIF file can hold.
48.10 The settable rule
The nine laws are stated. One case needs a full explanation: a setting. It is where readers most often expect to find an exception.
A setting (style(…)) is an atom like any other, so Orthogonality and No Exceptions govern it as well. A setting is available on every mark whose geometry can carry it, and it behaves the same way on all of them. That uniformity is No Exceptions: there is no mark-specific gap you have to memorize. A setting is absent only where composing two marks already says the same thing better, and that absence follows Law 1’s second line: no atom is redundant.
style(border_color =, border_size =) draws an outline distinct from the fill. It is available on all five closed-shape fills: bar, box, point, zone and surface. No second layer can draw their perimeter, so a setting is the only way to draw it. An area or a ribbon is filled too, but a layered line traces its edge, so neither takes a border. The letters of a label are filled shapes too, so text takes both settings, and there they draw a halo, a band of color under the letters. One setting takes one form in each class, as pattern does below.
style(pattern = ) gives a mark’s paint a texture, and each class of geometry shows that texture its own way. On the six path strokes (line, step, interval, path, rule, edge) it is a dash. On the five filled regions (bar, box, area, ribbon, zone) it is a hatch. One setting takes one form in each class. That is what color already does: a stroke on a line, a fill on a bar.
A curve fill (area, ribbon) takes the hatch, because a region can be textured. It takes no border, because its edge is a data curve, which already is a line (area + line, line * bounds) and can do more than any border. The class decides which settings a mark takes, not the individual mark, and Setting vs mapping defines each class.
This prevents one particular surprise: you learn a setting on one mark, then find it missing on a sibling mark for no reason you can see. A setting written outside its class is refused, and the refusal is Illegal: the grammar forbids it.
48.11 Why these laws matter
Each law refuses the same thing, the expert’s shortcut: a special case that saves an expert a few keystrokes and gives every beginner one more exception to learn. Nobody asks for it by that name. It arrives as a request like these:
- “Can
barhave a different color scale thanpoint?” - “Can
smoothhave aspanparameter?” - “Can I just write
alpha? Everyone knows what it means.”
Each one is reasonable on its own, and each one costs more than the one before it: the tenth exception makes the first nine harder to remember.
The laws are the answer: not “no, because we don’t want to,” but “no, because that would break Law 2 / Law 3 / …” The rule, not the maintainer, says no.
48.12 What comes next
The laws say what the grammar will and will not do, in any language. R opens the four chapters that say what belongs to your language instead, such as how you install gog and how it reads from a database.