| Title: | Grammar of Graphics |
|---|---|
| Description: | Draw a plot by describing it. You name a table, a mark to draw, and which column goes to which part of the picture, and one engine turns that description into an SVG. The same description is written the same way in R, 'Python', 'Julia' and 'JavaScript', and is drawn by a single engine written in 'Rust' that ships inside the package, so nothing has to be installed or added to the path. |
| Authors: | psychometrician [aut, cre] |
| Maintainer: | psychometrician <[email protected]> |
| License: | Apache License (== 2.0) |
| Version: | 0.6.0 |
| Built: | 2026-10-12 20:33:44 UTC |
| Source: | https://github.com/psychometrician/gog |
Derive a compound layer atom from a mark and a transform.
## S3 method for class 'gog_atom' e1 * e2 ## S3 method for class 'gog_callable' e1 * e2## S3 method for class 'gog_atom' e1 * e2 ## S3 method for class 'gog_callable' e1 * e2
e1 |
The mark, or a layer already carrying one transform. |
e2 |
The transform being joined to it. |
A layer gog_atom carrying the mark and its transforms.
+.Compose plot atoms with +.
## S3 method for class 'gog_spec' lhs + rhs## S3 method for class 'gog_spec' lhs + rhs
lhs |
The plot so far, or the first atom of a sentence. |
rhs |
The atom, layer or table being added to it. |
A gog_spec carrying both sides.
Bin a continuous variable into equal-width buckets (the histogram transform).
bin(bins = NULL, width = NULL, tiling = NULL, ...)bin(bins = NULL, width = NULL, tiling = NULL, ...)
bins |
Number of bins — one positive whole number. Positional: |
width |
Bin width in the data's own units, e.g. |
tiling |
How the plane is partitioned when both axes are cut:
|
... |
Not used. Any argument here is refused with direction. |
Pre-computed bounds.
bounds(lower, upper, start, end)bounds(lower, upper, start, end)
lower, upper
|
Columns bounding the measure axis — the span every band
mark draws. Required by |
start, end
|
Columns bounding the domain axis. |
Box-and-whisker mark.
box(whiskers = NULL)box(whiskers = NULL)
whiskers |
|
brush puts a bound on one column's values. Rows inside it keep the
plot's colors; rows outside it are dimmed, so a selection is read against
what it was taken from. Where the page can run the engine, dragging moves the
bound; on paper it stays where the sentence put it.
brush(field, at = NULL)brush(field, at = NULL)
field |
Column the bound is read on, as a bare name. |
at |
Where the selection opens: two numbers on a column that measures,
or the names to select on a column of categories. Left out, nothing is
selected and the plot draws exactly as it would with no |
A brush highlights. It never removes rows. Removing rows before
the statistics run is what limits does, on the binding, and it counts
what it dropped. The two are the same shape and different operations: change
a domain and a histogram re-bins the survivors, brush it and the same bars
stay, with the selected part standing out.
One column per brush. Write two for a rectangle:
brush(gdp, at = c(1200, 45000)) + brush(life, at = c(55, 78)).
A mark can be brushed when one row is one shape: point, text,
rule and zone. A line draws one shape through many rows,
so there is no single row to select, and gog says so rather than guessing.
A summarized layer is drawn whole, because a selection of twelve of a bar's
forty rows has no honest picture.
A gog_atom added to a plot with +.
The leaves are the levels of the bound categorical position, and each one is
described by a profile: its value at every level of the over column.
The engine measures the distance between every pair of profiles, joins the
closest two, and keeps joining until one tree holds them all. The other
position stays unbound and carries the merge distance, which the axis names
itself.
cluster(value, over)cluster(value, over)
value |
The numeric column distances are measured on, bare. Omit it on
a tile plot, where |
over |
The profile column, bare: each leaf holds one value at every level of it. Omit it only when the table has one row per leaf. |
Two marks read the one tree. path * cluster(amount, over = nutrient) + x(food) draws it: each join stands at the height its two branches merged,
so the reader can see which leaves sit close and where the groups part.
y(food) instead lays the same tree on its side. zone * cluster(over = nutrient) is the other reading: the tile plot unchanged, with the leaf
axis's slots reordered to the tree's leaf order, so alike rows sit together.
On a tile plot the values are already on color, which is why value is
omitted there.
Composed with | and /, a clustered panel decides the order of any axis
it shares, which is what puts a tree flush against a reordered tile plot.
The statistic is fixed: distances are Euclidean on the values as given, and branches join at their average distance. Rescale the value column where the data lives if its units should not weigh in.
Map fill/stroke color to a column.
color( field, scale = NULL, base = NULL, limits = NULL, tick_count = NULL, speed = NULL, free = FALSE, legend = NULL, axis = NULL, decimals = NULL, short = NULL )color( field, scale = NULL, base = NULL, limits = NULL, tick_count = NULL, speed = NULL, free = FALSE, legend = NULL, axis = NULL, decimals = NULL, short = NULL )
field |
Column to bind (bare name). Text picks colors from the palette; a number runs along a sequential ramp. |
scale |
How the number becomes a position: |
base |
Base of a |
limits |
For a continuous column, the two ends of the color scale,
as |
tick_count |
Not taken: only a position, |
speed |
Not taken: only |
free |
Not taken: |
legend |
|
axis |
Not taken: only a position, |
decimals |
Not drawn yet on this channel: only |
short |
Not drawn yet on this channel: only |
color(), refused with direction.gog writes American English throughout and accepts no second spelling, which
is Law 2 applied to the vocabulary itself: two ways to write one word is a
silent letter, and the reader pays for it. ggplot2 accepts colour and
color both, so a reader arriving from there types colour and,
unexported, would meet R's "could not find function" — a message that names
no fix. Exported for the same reason JavaScript still exports facet
(spec §13): a word a reader will plausibly type earns a refusal that says
what to write instead.
colour(...)colour(...)
... |
Ignored. The call is always refused. |
interval mark.Confidence interval of the mean per group, for the interval mark.
confidence(level = NULL)confidence(level = NULL)
level |
Confidence level, one number strictly between 0 and 1; default
0.95. Positional: |
cumulative adds each position's value to the values before it, in the order
the axis draws its positions: left to right on a number, in the categories'
own order on a category. It adds up what the layer already measures – the
column named on y, or what a transform before it made – and every mark draws
the total as it draws any measure. The total is the column cumulative, while
the measure's own column keeps each row's value, so label(count) and
label(cumulative) print the two.
cumulative(share = NULL)cumulative(share = NULL)
share |
|
Each color or group split runs its own total. A category axis is added up
in its declared order, so sort it in the table rather than with order().
A gog_atom joined to a mark with *.
transforms for the measures it adds up; stack(), which adds up the
groups at one position instead of the positions of one group.
# Each year's sales added to the years before it. sales <- data.frame(year = 2019:2023, sales = c(120, 135, 128, 150, 162)) r <- data(sales) + line * cumulative + x(year) + y(sales) # The cumulative histogram, and one distribution function per species. h <- data(iris) + step * bin * cumulative + x(Sepal.Length) e <- data(iris) + step * bin * cumulative(share = TRUE) + x(Sepal.Length) + color(Species)# Each year's sales added to the years before it. sales <- data.frame(year = 2019:2023, sales = c(120, 135, 128, 150, 162)) r <- data(sales) + line * cumulative + x(year) + y(sales) # The cumulative histogram, and one distribution function per species. h <- data(iris) + step * bin * cumulative + x(Sepal.Length) e <- data(iris) + step * bin * cumulative(share = TRUE) + x(Sepal.Length) + color(Species)
Every plot begins by naming its table. The columns are then written as bare names in the channels that follow, and the nearest table wins where a plot has more than one.
df |
An R data frame. |
name |
Optional table name (auto-derived from the variable name if omitted). |
Masks utils::data(), R's dataset loader. Use utils::data() to
load a built-in dataset; this one binds a table you already have. Both
readings of data(mtcars) are well formed, so that one case cannot be
told apart and is the reason the collision is documented rather than caught.
A gog_spec – the start of a sentence, extended with +.
data(df, name = NULL)
The Usage section is written out here rather than generated, because
R CMD check reads a generated \usage\{data(...)\} as base R's dataset
loader and then reports df as a missing dataset. Suppressing the generated
one is the documented way out; the signature is not lost, only moved.
p <- data(mtcars) + point + x(wt) + y(mpg) # Naming the table explicitly, which matters when a plot has more than one. q <- data(mtcars, name = "cars") + point + x(wt) + y(mpg)p <- data(mtcars) + point + x(wt) + y(mpg) # Naming the table explicitly, which matters when a plot has more than one. q <- data(mtcars, name = "cars") + point + x(wt) + y(mpg)
Estimate the smooth probability density of a continuous variable (a KDE).
density( adjust = NULL, bandwidth = NULL, levels = NULL, compare = NULL, reach = NULL )density( adjust = NULL, bandwidth = NULL, levels = NULL, compare = NULL, reach = NULL )
adjust |
Multiplier on the automatically-chosen bandwidth: |
bandwidth |
Absolute bandwidth in the data's own units, e.g.
|
levels |
How many levels to cut a field into: |
compare |
What a violin's width means from one slot to the next:
|
reach |
How far each violin reaches from the line its category sits on,
in slots: |
Spread band per group, mean +/- k standard deviations.
deviation(multiplier = NULL)deviation(multiplier = NULL)
multiplier |
How many standard deviations reach each side of the mean;
default 1. Positional: |
facet() joins the plot with the facet operators, not +:
facet(field, wrap = NULL)facet(field, wrap = NULL)
field |
Column to facet by (bare name). |
wrap |
Panels to draw before the line of them turns. |
plot | facet(cyl) one panel per category, side by side plot / facet(drv) one panel per category, stacked plot / facet(drv) | facet(cyl) crossed into a grid (rows x columns)
The panels share one scale — that is what makes them comparable — and any transform in the plot runs within each panel's rows. The facet column must be a category column: a number is a position along an axis, not a name for a panel.
wrap folds a long line of panels into a rectangle: twelve countries
side by side are twelve slivers, and wrap = 4 makes them a 4 x 3 grid.
The number is how many panels before the line turns, and which way
the line runs is the operator's to say, not wrap's:
plot | facet(country, wrap = 4) levels run across, 4 to a row plot / facet(country, wrap = 4) levels run down, 4 to a column
That is one number where ggplot2 has nrow and ncol, and it
cannot contradict itself. Wrapping a crossed facet is refused: a crossing
already fills a rectangle.
| facets into panel columns and sets plots side by side;
/ facets into panel rows and sets them one above the other.
## S3 method for class 'gog_spec' e1 | e2 ## S3 method for class 'gog_spec' e1 / e2## S3 method for class 'gog_spec' e1 | e2 ## S3 method for class 'gog_spec' e1 / e2
e1, e2
|
The plot on the left, and the facet or plot joined to it. |
A faceted gog_spec, or a gog_arrangement when two plots are composed.
The stages arrive as columns, in reading order: one row of the table is
one path through all of them, and flow(class, sex, survived) runs each row
from its class to its survived. Rows sharing a path add together, which
quietly sets aside any column the atom did not name.
flow(..., shared = FALSE, layered = FALSE)flow(..., shared = FALSE, layered = FALSE)
... |
The stage columns, bare names, in reading order. At least two: one column has no between. |
shared |
|
layered |
|
Three marks read the one layout. ribbon * flow(...) draws the bands, a
path's magnitude carried between adjacent stages; zone * flow(...) draws
each stage's slots, stacked with no padding so the measure axis stays
honest; text * flow(...) + label(name) names each slot where it sits.
Layer any two or all three.
What each path is weighed by rides on y: + y(count) weighs that column,
and binding nothing at all makes every path weigh 1. Bound to x instead,
the count runs the stages from top to bottom. On the band layer,
color(<stage>) colors every band by the category its path holds at that
stage, and color() of any other categorical column splits each band by its
values; the slots take their paint from style().
The stage axis is drawn from the atom's own columns, so there is nothing for
x() to say — to reorder the stages, reorder the arguments.
Two columns that name one set of places take shared = TRUE:
flow(exporter, importer, shared = TRUE) reads each row as an amount leaving
one place and arriving at another, gives each place one slot whichever column
names it, and lays every slot on the one count axis. Flat, that draws the arc
diagram, each band arching from one place to the other; in polar() the
count goes around the circle and it draws the chord diagram. Each slot is in
two parts with no line between them: the ends where the place is named in the
first column, in the slot's color, then the ends where it is named in the
second, a shade darker. So flow(importer, exporter, shared = TRUE) shades
the exports instead of the imports. The panel is white and has no frame
unless theme() says otherwise, since the plot has no numbers to read.
A list of links whose places stand in layers takes layered = TRUE:
flow(source, target, layered = TRUE) reads each row as an amount moving
from one place to another, and puts each place in the first layer after
every place that sends to it. That draws the Sankey diagram: a band may
skip a layer, and a flow may stop before the last layer. Each layer is
sorted by where its places' partners are, the places of a layer stand a gap
apart, and each name stands beside its slot. Links that form a cycle have
no layers and are refused.
The globe coordinate space. x is longitude and y is latitude,
both in degrees, and each mark stands at its place on the sphere's surface.
The marks that draw on a map() draw here: point is a place, path is a
route bending along great circles, text is a name at a place, rule holds
one coordinate whole (a meridian pole to pole, or a parallel all the way
round), and zone with group() fills each region of a boundary. The
graticule is the panel grid, turned off like any other with
theme(grid = "none").
globe(turn = 0, tilt = 0)globe(turn = 0, tilt = 0)
turn |
Degrees of longitude the view faces. Default 0. |
tilt |
Degrees of latitude the view faces, -90 to 90. Default 0. |
A globe shows one half of the earth. turn and tilt name the place the
view faces, in the words space() already uses: globe(turn = 178, tilt = -18) puts Fiji at the center of the disk. A bearing wraps, so any
turn is a view; tilt is a latitude and stops at the poles. Rows on the
far half are hidden behind the sphere, and gog says how many rather than
dropping them in silence. In the web edition, dragging the globe turns it.
Both positions are spent on the place, so a mark that measures along an axis
has none left; carry a quantity on size() or color() instead, as on a
map. A binned field on the sphere is designed and not drawn yet: its correct
tiling is hexagonal, because rectangular bins do not cover equal area on a
sphere, and that equal-area grid is not built.
## Not run: data(quakes_fiji) + point + x(east) + y(north) + size(magnitude) + globe(turn = 178, tilt = -18) data(borders) + zone + x(lon) + y(lat) + group(country) + color(continent) + globe(turn = 20, tilt = 5) ## End(Not run)## Not run: data(quakes_fiji) + point + x(east) + y(north) + size(magnitude) + globe(turn = 178, tilt = -18) data(borders) + zone + x(lon) + y(lat) + group(country) + color(continent) + globe(turn = 20, tilt = 5) ## End(Not run)
Fetches a table published beside the manual and returns it ready to plot. The full list of names is in the book's data chapter.
gog_table(name, text = character())gog_table(name, text = character())
name |
The table's name without the extension, such as
|
text |
Columns that must stay text. A CSV records what a value is and
never what kind of thing it is, so a column of |
A data frame.
## Not run: gapminder_2007 <- gog_table("gapminder_2007") data(gapminder_2007) + point + x(gdp) + y(life) # `session` holds labels that look like numbers, so it stays text. sessions <- gog_table("sessions", text = "session") ## End(Not run)## Not run: gapminder_2007 <- gog_table("gapminder_2007") data(gapminder_2007) + point + x(gdp) + y(life) # `session` holds labels that look like numbers, so it stays text. sessions <- gog_table("sessions", text = "session") ## End(Not run)
Group line/path marks by a column (connects points within the same group). Use color(field) when you also want color distinction — it implies grouping.
group( field, scale = NULL, base = NULL, limits = NULL, tick_count = NULL, speed = NULL, free = FALSE, legend = NULL, axis = NULL, decimals = NULL, short = NULL )group( field, scale = NULL, base = NULL, limits = NULL, tick_count = NULL, speed = NULL, free = FALSE, legend = NULL, axis = NULL, decimals = NULL, short = NULL )
field |
Column to group by, as a bare name. |
scale, base, limits
|
Not taken: this channel distinguishes categories rather than measuring them, so there is no scale or range along it. gog refuses each of them here. |
tick_count |
Not taken: only a position, |
speed |
Not taken: only |
free |
Not taken: |
legend |
Not taken: |
axis |
Not taken: only a position, |
decimals |
Not drawn yet on this channel: only |
short |
Not drawn yet on this channel: only |
A gog_atom added to a plot with +.
Spread a strip plot's coincident points apart, along the categorical axis only.
jitter(amount = NULL)jitter(amount = NULL)
amount |
Spread as a multiple of the default, one number from 0 to 1.25;
default 1. Positional: |
knitr print method for an arrangement.
knit_print.gog_arrangement(x, ...)knit_print.gog_arrangement(x, ...)
x |
The plot or arrangement being displayed. |
... |
Passed on by the display host; unused. |
knitr print method: the plot inline as SVG in HTML, and as a converted
knit_print.gog_spec(x, ...)knit_print.gog_spec(x, ...)
x |
The plot or arrangement being displayed. |
... |
Passed on by the display host; unused. vector figure on a LaTeX page. Called automatically by knitr when a gog_spec is the last expression in a code chunk. |
text mark's content.label supplies the string a text mark draws, the way x
and y supply its position: text + x(a) + y(b) + label(name). A
string column is drawn as-is, a numeric one is formatted. It is text's
required channel — no other mark accepts it, so a labeled scatter is the
superposition point + text. A scale is refused: a label is
content, like shape, not a magnitude to run a scale along.
label( field, scale = NULL, base = NULL, limits = NULL, tick_count = NULL, speed = NULL, free = FALSE, legend = NULL, axis = NULL, decimals = NULL, short = NULL )label( field, scale = NULL, base = NULL, limits = NULL, tick_count = NULL, speed = NULL, free = FALSE, legend = NULL, axis = NULL, decimals = NULL, short = NULL )
field |
Column whose values are drawn as the text, as a bare name. |
scale, base, limits
|
Not taken: this channel distinguishes categories rather than measuring them, so there is no scale or range along it. gog refuses each of them here. |
tick_count |
Not taken: only a position, |
speed |
Not taken: only |
free |
Not taken: |
legend |
Not taken: a label is read where it is written, so it draws no legend. gog refuses it here and names the five channels that do. |
axis |
Not taken: only a position, |
decimals |
How many decimal places each number is printed with, a
whole number from 0 to 10: |
short |
|
A gog_atom added to a plot with +.
The two columns name each row's endpoints, and one row of the table is one
edge between its two values. The nodes derive: their names from the columns'
union, their degree by counting the rows that name them. The engine computes every position, the
same way in every binding, so one table is one picture wherever it is drawn.
layout(from, to)layout(from, to)
from |
The column naming each edge's first endpoint, bare. |
to |
The column naming each edge's second endpoint, bare. |
Three marks read the one placement, inside network(). edge * layout(from, to) draws the connections, point * layout(...) the nodes, and text * layout(...) + label(name) names them. On the node layers, size(degree)
and color(degree) read that count.
The positions mean nothing as quantities – the picture can be mirrored
without changing what the graph says – so nothing binds to x, y or z,
and the third dimension, when you want it, is the space's:
network(turn = 30, tilt = 25).
The cartographic coordinate space. x is longitude and y is
latitude, both in degrees, and the projection decides where each place
lands. Nothing else about the sentence changes: point is a place, path is a
route through places in the table's order, text is a name at a place, and
rule spans the axis it does not name, which on a map is a meridian or a
parallel.
map(preserve = "area")map(preserve = "area")
preserve |
What the flattening keeps: |
Both positions are spent on the place, so a mark that measures along an axis
has none left. Carry a quantity on a channel instead. size(<column>) gives
the proportional-symbol map and color(<column>) shades each place, which is
what cartography does once the two axes are gone.
A sphere cannot be laid flat without giving something up, and area and angle
cannot both survive. preserve names which one does:
"area" – every region gets ink in proportion to its true size. The
default, because a map is usually read by area: a projection that inflates
Greenland says something false about the number inside it. Uses the Equal
Earth projection, which reaches both poles.
"angle" – every small shape keeps its true form, and area is what pays.
Uses Mercator, so Greenland arrives the size of Africa while being fourteen
times smaller. Mercator sends the poles infinitely far away, so it stops at
85.05 degrees and says how many rows it could not reach.
The panel is shaped by the projection rather than by the page, so
theme(ratio = ) is refused here: an equal-area map stretched to fit a box is
no longer equal-area, though it still looks like a map.
## Not run: data(quakes_fiji) + point + x(east) + y(north) + size(magnitude) + map() data(borders) + path + x(lon) + y(lat) + group(country) + map() data(borders) + path + x(lon) + y(lat) + group(country) + map(preserve = "angle") ## End(Not run)## Not run: data(quakes_fiji) + point + x(east) + y(north) + size(magnitude) + map() data(borders) + path + x(lon) + y(lat) + group(country) + map() data(borders) + path + x(lon) + y(lat) + group(country) + map(preserve = "angle") ## End(Not run)
A mark is the shape a layer draws, and every plot needs exactly one. A mark carries no data of its own: it is combined with channels that say which column goes to which part of the picture, and the engine decides every stroke from there.
point line path rule zone area bar step interval ribbon text surface edgepoint line path rule zone area bar step interval ribbon text surface edge
Each is a gog_atom object, used bare rather than called.
Marks are the grammar's consonants. They are plain objects rather than functions, so they are written bare in a sentence:
data(table) + point + x(column) + y(column)
A mark and its required positions together are the smallest thing that draws; neither renders alone. Which channels a mark accepts is a property of the mark, and asking for one it cannot carry is refused with a direction rather than silently ignored.
pointOne glyph per row. How two measurements move together.
lineConnects rows in x order. How one value changed over time.
pathConnects rows in data order rather than x order, so a series may double back on itself.
areaFills the region between the line and the baseline.
barA rectangle from the baseline to the value. How large each category is.
stepThe line family's right-angled member: a value holds steady until it jumps.
intervalA segment from a low value to a high one. Needs a pair, so
it is usually written with range or confidence.
ribbonA band between two values across a span.
textDraws the string given by label at each position.
ruleA reference line across the panel – the threshold everything else is compared against.
zoneShades a rectangular region of the panel.
surfaceA sheet over a grid of samples, for three-dimensional data.
box is a mark too, but takes an argument, so it is documented separately.
box() for the box-and-whisker mark; x() and y() for positions;
style() for settings that do not vary with the data.
# A mark plus its positions is the smallest sentence that draws. p <- data(mtcars) + point + x(wt) + y(mpg) # The same data, a different shape: one atom is all that changes. q <- data(mtcars) + line + x(wt) + y(mpg)# A mark plus its positions is the smallest sentence that draws. p <- data(mtcars) + point + x(wt) + y(mpg) # The same data, a different shape: one atom is all that changes. q <- data(mtcars) + line + x(wt) + y(mpg)
The nested coordinate space, and the treemap is what it draws. Every row's measure becomes an area rather than a length, and the areas partition the panel: a region's share of the picture is its share of the total.
nest()nest()
Nothing else about the sentence changes, which is what makes this a space and
not a chart type. bar * sum + y(revenue) + color(region) is a stacked column
flat, a pie in polar(), and a treemap here — three answers to the one
question of what carries a share, and the words are the same in all three.
A packed panel has no axes: its two directions carry no variable, and two neighboring regions are not near each other in the data. So there is nothing to label and nothing to tick, and the color legend is what decodes the regions. Bind a position to split the packing one level further — each category gets a region of its own and its rows are packed inside it.
dodge, stack and jitter are refused here: they decide what happens when
two marks land in the same place, and in a packing none can.
## Not run: data(sales) + bar * sum + y(revenue) + color(region) + nest() data(sales) + bar * sum + x(region) + y(revenue) + color(product) + nest() ## End(Not run)## Not run: data(sales) + bar * sum + y(revenue) + color(region) + nest() data(sales) + bar * sum + x(region) + y(revenue) + color(product) + nest() ## End(Not run)
layout()'s placement is drawn.A layout's positions are the graph's, not the data's, so this space draws no
axes, no ticks and no grid: there is nothing there for them to number. It is
the one space a layout draws in, and a layout is the one thing that
stands in it.
network(turn = NULL, tilt = NULL)network(turn = NULL, tilt = NULL)
turn |
Degrees around the upright axis, stated only for the cube. |
tilt |
Degrees of elevation, stated only for the cube. |
Stating a viewing angle states the third dimension. Bare network() draws
the graph flat; network(turn = 30, tilt = 25) computes the placement in a
cube and draws it from that angle, with each unstated angle taking the
standard view. In the web edition the cube turns with a drag, the way the
cube in space() turns.
Map opacity to a numeric column.
opacity( field, scale = NULL, base = NULL, limits = NULL, tick_count = NULL, speed = NULL, free = FALSE, legend = NULL, axis = NULL, decimals = NULL, short = NULL )opacity( field, scale = NULL, base = NULL, limits = NULL, tick_count = NULL, speed = NULL, free = FALSE, legend = NULL, axis = NULL, decimals = NULL, short = NULL )
field |
Column to bind (bare name). |
scale |
How the number becomes a position: |
base |
Base of a |
limits |
The two ends of the scale, as |
tick_count |
Not taken: only a position, |
speed |
Not taken: only |
free |
Not taken: |
legend |
|
axis |
Not taken: only a position, |
decimals |
Not drawn yet on this channel: only |
short |
Not drawn yet on this channel: only |
Every atom that takes a column is a noun naming a property, and its argument
is the column that drives it — color(species), size(population),
order(gold). This one was called sort_by until it became the
last survivor of a _by convention that color_by → color
had already retired: the suffix marked the argument as a key, which says
nothing when every atom in the group takes a key.
order(field, desc = FALSE)order(field, desc = FALSE)
field |
Column to order by (bare name). Use a numeric column (e.g. y) to order by value, which overrides a factor's levels. Use the category column itself to ask for that column's own order: alphabetical for plain text, and the declared levels for a factor. |
desc |
Logical. |
Masks base::order, as data, title, and palette
mask theirs. Use base::order() for the base function.
## Not run: data(medals) + bar + x(gold) + y(country) + order(gold, desc = TRUE) ## End(Not run)## Not run: data(medals) + bar + x(gold) + y(country) + order(gold, desc = TRUE) ## End(Not run)
Set the categorical color palette.
palette(pal)palette(pal)
pal |
Either a palette name ("gog", "okabe") or a character vector
of hex colors. The palette is used in first-appearance order; if there
are more categories than palette entries, additional colors are generated
automatically via the HSL color wheel so no two categories share a color.
Name the colors to bind each one to a level instead, as in
|
The hierarchy arrives as columns, outermost first: one row of the table is
one leaf, and partition(group, item, detail) says which columns spell the
path down to it. A blank level ends that branch early, which is what gives a
real hierarchy its ragged rim.
partition(..., cross = FALSE)partition(..., cross = FALSE)
... |
The hierarchy's columns, bare names, outermost first. |
cross |
Cross the levels rather than nesting them down one axis —
|
zone * partition(...) flat is the icicle; the same sentence + polar()
is the sunburst, and that one atom is the whole difference. text * partition(...) + label(name) names each node where it sits, reading the center
the same computation published.
What each branch is weighed by rides on x: + x(amount) apportions that
column, and binding nothing at all makes every leaf weigh 1. Compose with
proportion for shares. y(depth, limits = c(0, 4)) puts the hole in the
middle, depth being the ring the transform synthesizes.
Every number belongs to a leaf. An interior node carrying one of its own is ambiguous — is its arc the children's total, or its own beside them? — so it is refused rather than guessed.
cross = TRUE turns the levels across each other instead of down one axis:
the first divides the width, the second divides the height within each of
those columns. That is the mosaic, and because both directions are then
spent on the hierarchy there is no ring left to step and only the leaves are
drawn.
shape's twin.pattern tells categories apart by texture rather than hue: on a
fill (bar/box/area/ribbon) each category draws as a
hatch, on a stroke (line/step/interval) as a dash. It is
the color-free way to separate series, so it survives grayscale printing and
color-blindness; pairing it with color on the same column is the
redundant encoding that is the accessibility best practice. A scale
is refused: like shape it answers "which one?", not "how much?".
pattern( field, scale = NULL, base = NULL, limits = NULL, tick_count = NULL, speed = NULL, free = FALSE, legend = NULL, axis = NULL, decimals = NULL, short = NULL )pattern( field, scale = NULL, base = NULL, limits = NULL, tick_count = NULL, speed = NULL, free = FALSE, legend = NULL, axis = NULL, decimals = NULL, short = NULL )
field |
Column to bind to the texture, as a bare name. |
scale, base, limits
|
Not taken: this channel distinguishes categories rather than measuring them, so there is no scale or range along it. gog refuses each of them here. |
tick_count |
Not taken: only a position, |
speed |
Not taken: only |
free |
Not taken: |
legend |
|
axis |
Not taken: only a position, |
decimals |
Not drawn yet on this channel: only |
short |
Not drawn yet on this channel: only |
Distinct from the style(pattern = ) setting, which fixes one
texture for the whole layer; pattern() maps a column to several.
A gog_atom added to a plot with +.
play is facet read in time. Both split the rows by a column's
distinct values; | facet(continent) lays the pieces out across the page
and play(year) lays them out in sequence, so
point + x(gdp) + y(life) + play(year) is the time-lapse of the same
data the faceted grid shows all at once.
play( field, speed = NULL, scale = NULL, base = NULL, limits = NULL, tick_count = NULL, free = FALSE, legend = NULL, axis = NULL, decimals = NULL, short = NULL )play( field, speed = NULL, scale = NULL, base = NULL, limits = NULL, tick_count = NULL, free = FALSE, legend = NULL, axis = NULL, decimals = NULL, short = NULL )
field |
Column whose distinct values become the frames, as a bare name. |
speed |
A multiple of the normal pace: |
scale, base, limits
|
Not taken: this channel distinguishes categories rather than measuring them, so there is no scale or range along it. gog refuses each of them here. |
tick_count |
Not taken: only a position, |
free |
Not taken: |
legend |
Not taken: each frame is named in the strip above the panel
as it is shown, so |
axis |
Not taken: only a position, |
decimals |
Not drawn yet on this channel: only |
short |
Not drawn yet on this channel: only |
Every scale, the color map and every legend are fitted across the whole
sequence, never per frame — a scale that re-fitted would move the axis under
the data and make a still point look like a moving one. A layer that does not
bind play is drawn in every frame, so a reference line stands still
behind the marks that move.
Unlike facet, a number is welcome: panels compete for page
area, where a hundred of them are unreadable at any size, but frames compete
for time, where a hundred is a longer loop rather than a smaller picture.
speed is how many times faster than normal the frames run — 2 is
twice as fast, 0.5 half. Where a static image is made from the plot (the PDF
of this book, or any SVG converter) the first frame is what it shows.
A gog_atom added to a plot with +.
The polar coordinate space. x becomes the angle and y the radius,
so the axis a bar chart stands its categories on is the axis a rose wraps
around the circle. Nothing else about the sentence changes: bar * count + x(month) is a bar chart flat and a rose in polar, from the same words.
polar(start = 0)polar(start = 0)
start |
Degrees clockwise from the top where the circle begins. |
The angular axis is periodic — one turn spans exactly the fitted range of
x, so the circle closes with no seam. Angles run clockwise from twelve
o'clock.
## Not run: data(winds) + bar * count + x(direction) + polar() data(winds) + bar * count + x(direction) + polar(start = -22.5) ## End(Not run)## Not run: data(winds) + bar * count + x(direction) + polar() data(winds) + bar * count + x(direction) + polar(start = -22.5) ## End(Not run)
y per group.The p-th quantile of y per group.
quantile(p = NULL)quantile(p = NULL)
p |
The quantile probability, one number between 0 and 1. Positional:
|
query() stands exactly where data() stands, and nothing after it
changes — the same operators, channels, bare column names and transforms:
query(con, sql, name = NULL)query(con, sql, name = NULL)
con |
A DBI connection. |
sql |
A single |
name |
What to call the table. Defaults to |
data(orders) + bar + x(status) query(con, "SELECT * FROM orders") + bar + x(status)
The SQL is confined to this one argument and never enters the grammar, which
is the point: x(status) is still a bare column resolved by the same mask,
not a fragment of another language.
The connection is the caller's own — gog opens none. It is a DBI connection, R's database standard, so RSQLite, RPostgres, RMariaDB, odbc and bigrquery all reach this. DBI is Suggests rather than Imports: a user who never writes SQL should not install a database stack to draw a plot.
The query is not run here. It runs once, at render.
A gog_spec.
Quantile band per group, for the span marks.
range(low = NULL, high = NULL)range(low = NULL, high = NULL)
low, high
|
The band's two ends, as quantile probabilities between 0 and
|
Render a gog_spec to an SVG string.
render_svg(gog)render_svg(gog)
gog |
A |
A character string containing the SVG.
text * repel reads where the labels and the points actually sit and pushes
each label a different way until they stop overlapping, drawing a thin leader
line back to the point when a label has moved clear of it. Where there is no
arrangement that fits, every label is still drawn and the plot reports how many
are still crowded.
repelrepel
A gog_atom joined to a mark with *.
style()'s nudge, the constant offset this is the data-derived
counterpart to; jitter(), the same idea for a scatter's points.
## Not run: # Every country named, with the names moved off one another. gapminder_2007 <- gog_table("gapminder_2007") p <- data(gapminder_2007) + point + text * repel + x(gdp) + y(life) + label(country) ## End(Not run)## Not run: # Every country named, with the names moved off one another. gapminder_2007 <- gog_table("gapminder_2007") p <- data(gapminder_2007) + point + text * repel + x(gdp) + y(life) + label(country) ## End(Not run)
repr method: an arrangement as inline SVG in a Jupyter cell.
repr_html.gog_arrangement(obj, ...)repr_html.gog_arrangement(obj, ...)
obj |
The plot or arrangement being displayed. |
... |
Passed on by the display host; unused. |
repr method: the plot as inline SVG in a Jupyter cell.
repr_html.gog_spec(obj, ...)repr_html.gog_spec(obj, ...)
obj |
The plot or arrangement being displayed. |
... |
Passed on by the display host; unused. |
repr method: a one-line description of an arrangement.
repr_text.gog_arrangement(obj, ...)repr_text.gog_arrangement(obj, ...)
obj |
The plot or arrangement being displayed. |
... |
Passed on by the display host; unused. |
repr method: a one-line description for the text/plain fallback.
repr_text.gog_spec(obj, ...)repr_text.gog_spec(obj, ...)
obj |
The plot or arrangement being displayed. |
... |
Passed on by the display host; unused. |
A plot that binds play() moves in a browser, because the SVG carries
its own timing. Most other places do not read that: a message to a friend, a
slide, a post. This writes the same sequence as a GIF, which they do read.
save_gif(gog, path, scale = 1)save_gif(gog, path, scale = 1)
gog |
A played |
path |
Where to write, ending in |
scale |
Multiplier on the plot's canvas. A plot is 800 by 600 unless its
theme says otherwise, which is small for a post; |
The frames come out of the one renderer, so the file cannot disagree with the plot. Every scale, the color map and each legend are fitted across the whole sequence at once, and the frames are cut from that single drawing rather than drawn again one at a time.
Nothing needs to be installed. The engine converts and encodes on its own.
The path, invisibly.
The drawing render_svg() returns, written to path byte for
byte, so the file is the same in every binding and on every platform.
save_svg(gog, path)save_svg(gog, path)
gog |
A |
path |
Where to write, ending in |
The plot is drawn first and written second, so a plot gog refuses leaves a
file already at path as it was.
The path, invisibly.
A scale is refused: shape answers "which one?", and there is no
distance between circle and square for a scale to run along.
shape( field, scale = NULL, base = NULL, limits = NULL, tick_count = NULL, speed = NULL, free = FALSE, legend = NULL, axis = NULL, decimals = NULL, short = NULL )shape( field, scale = NULL, base = NULL, limits = NULL, tick_count = NULL, speed = NULL, free = FALSE, legend = NULL, axis = NULL, decimals = NULL, short = NULL )
field |
Column to bind to the glyph shape, as a bare name. |
scale, base, limits
|
Not taken: this channel distinguishes categories rather than measuring them, so there is no scale or range along it. gog refuses each of them here. |
tick_count |
Not taken: only a position, |
speed |
Not taken: only |
free |
Not taken: |
legend |
|
axis |
Not taken: only a position, |
decimals |
Not drawn yet on this channel: only |
short |
Not drawn yet on this channel: only |
A gog_atom added to a plot with +.
Map point radius to a numeric column.
size( field, scale = NULL, base = NULL, limits = NULL, tick_count = NULL, speed = NULL, free = FALSE, legend = NULL, axis = NULL, decimals = NULL, short = NULL )size( field, scale = NULL, base = NULL, limits = NULL, tick_count = NULL, speed = NULL, free = FALSE, legend = NULL, axis = NULL, decimals = NULL, short = NULL )
field |
Column to bind (bare name). |
scale |
How the number becomes a position: |
base |
Base of a |
limits |
The two ends of the size scale, as |
tick_count |
Not taken: only a position, |
speed |
Not taken: only |
free |
Not taken: |
legend |
|
axis |
Not taken: only a position, |
decimals |
Not drawn yet on this channel: only |
short |
Not drawn yet on this channel: only |
smooth line: the fit's confidence band.smooth's pair form, as confidence is mean's. At each point of the
LOESS curve it gives the fit's low and high ends, so
ribbon * smooth_band + line * smooth draws the trend with its band.
smooth_band(level = NULL)smooth_band(level = NULL)
level |
Confidence level, one number strictly between 0 and 1; default
0.95. Positional: |
Binding z is what makes a plot three-dimensional — one more vowel, not a
chart type. space() sets the angle that plot is viewed from. That angle is
a view parameter of the coordinate space — the way a polar plot needs a start
angle — not a channel (it maps no column) and not a mark. Omit it and a 3-D
plot takes a default three-quarter view that shows all three axes.
space(turn = 30, tilt = 25)space(turn = 30, tilt = 25)
turn |
Degrees swung around the upright axis — which side you view the scene from. Default 30. |
tilt |
Degrees the eye is lifted above the floor — how steeply you look down. Default 25. |
## Not run: data(clusters) + point + x(a) + y(b) + z(c) + space(turn = -45, tilt = 30) ## End(Not run)## Not run: data(clusters) + point + x(a) + y(b) + z(c) + space(turn = -45, tilt = 30) ## End(Not run)
stack, dodge and jitter
When several marks land in the same place, something has to decide where they
go. These are collision modifiers: they do not compute a statistic, they
move marks that would otherwise cover each other. Like transforms they ride the
* slot, so they compose with a statistic rather than replacing it –
bar * count * stack bins, counts, and then piles the result.
dodge stack(share = NULL, baseline = NULL)dodge stack(share = NULL, baseline = NULL)
share |
|
baseline |
Where the pile starts: |
stackPiles the marks of each group end to end, so the total is the height of the pile.
dodgePlaces them side by side instead, so each is measured from the
same baseline and they can be compared directly. On point it moves each
point across its category only as far as it must to clear its neighbors,
so no two touch: the beeswarm.
On bars, boxes and whiskers both need something to separate: a color or
group binding that says which mark belongs to which group. Without one there
is nothing to stack or dodge, and the engine says so. point * dodge needs
none, since the points in one category already collide.
A gog_atom joined to a mark with *.
jitter(), which resolves overlap by displacing marks slightly rather
than by arranging them; transforms for the statistics these compose with.
# Piled: the height of the pile is the total. s <- data(mtcars) + bar * count * stack + x(cyl) + color(gear) # Side by side: each bar measured from the same baseline. d <- data(mtcars) + bar * count * dodge + x(cyl) + color(gear) # Every pile the same height, so the plot shows shares. f <- data(mtcars) + bar * count * stack(share = TRUE) + x(cyl) + color(gear)# Piled: the height of the pile is the total. s <- data(mtcars) + bar * count * stack + x(cyl) + color(gear) # Side by side: each bar measured from the same baseline. d <- data(mtcars) + bar * count * dodge + x(cyl) + color(gear) # Every pile the same height, so the plot shows shares. f <- data(mtcars) + bar * count * stack(share = TRUE) + x(cyl) + color(gear)
Channels map: color(species) asks the reader "which species?"
and earns a legend to answer it. style() sets: it fixes a
visual property at one value for the whole layer, maps no column, consumes
no scale, and produces no legend — there is nothing to decode.
style( color = NULL, opacity = NULL, size = NULL, shape = NULL, border_color = NULL, border_size = NULL, caps = NULL, center = NULL, nudge = NULL, pattern = NULL, arrow = NULL, reach = NULL, angle = NULL, gap = NULL, ... )style( color = NULL, opacity = NULL, size = NULL, shape = NULL, border_color = NULL, border_size = NULL, caps = NULL, center = NULL, nudge = NULL, pattern = NULL, arrow = NULL, reach = NULL, angle = NULL, gap = NULL, ... )
color |
CSS color name ( |
opacity |
Number in 0–1. Set literally — unlike the |
size |
Pixels: point radius (default 4.5), or line stroke width (default 2). |
shape |
One of |
border_color |
Outline color for a closed glyph; on |
border_size |
Outline width for a closed glyph; on |
caps |
|
center |
|
nudge |
Offset a text label from its point, in pixels. |
pattern |
A stroke's dash pattern, or a fill's hatch tile. |
arrow |
Draw the stroke as an arrow. |
reach |
How far a density curve runs past the data. |
angle |
Degrees to turn a |
gap |
The share of a |
... |
Further settings; an unknown one is refused by name rather than ignored. |
A constant is therefore not a channel, by the same reasoning that keeps rotation out of the channel set: a channel maps a data column to a visual feature, and a constant maps nothing.
## Not run: data(df) + x(a) + y(b) + point + style(color = "tomato", opacity = 0.3) data(df) + x(a) + y(b) + line + style(color = "gray70", size = 1) ## End(Not run)## Not run: data(df) + x(a) + y(b) + point + style(color = "tomato", opacity = 0.3) data(df) + x(a) + y(b) + line + style(color = "gray70", size = 1) ## End(Not run)
Everything here maps no column, so each is a setting; but none of it
belongs to a mark either, which is why it is not style(). A layer has no
gridlines and a plot has no fill, so the two property sets are disjoint and
telling them apart by where they were written would make a sub-expression
mean different things in different places (Law 6). Spec §7 is the ruling.
theme( preset = NULL, grid = NULL, ratio = NULL, tick_angle = NULL, font_size = NULL, background = NULL, strip = NULL, strip_text = NULL, frame = NULL, axis_label = NULL, legend = NULL, width = NULL, height = NULL, gap = NULL )theme( preset = NULL, grid = NULL, ratio = NULL, tick_angle = NULL, font_size = NULL, background = NULL, strip = NULL, strip_text = NULL, frame = NULL, axis_label = NULL, legend = NULL, width = NULL, height = NULL, gap = NULL )
preset |
One of |
grid |
Which gridlines are drawn: |
ratio |
The panel's width divided by its height. |
tick_angle |
Degrees to turn the x tick labels through, between -90 and 90. Category names that do not fit side by side are turned to 90 without it, and thinned when even that is not enough, so state an angle only to choose one. |
font_size |
How many pixels a tick label is — and, through it, the size
of every other piece of text the plot draws. One number rather than three,
because the axis names and the title are a fixed step above it: |
background |
The panel's fill — any color the rest of the grammar
accepts, including |
strip |
The facet strip's fill: the band above a panel that names the
level it holds. Same colors as |
strip_text |
The ink of the strip's label. Leave it out and gog picks
whichever of its two defaults reads on the band, so
|
frame |
How the panel is bounded: |
axis_label |
Where each axis's name sits: |
legend |
Which side of the plot its legends sit on: |
width, height
|
How many pixels the plot asks for. On its own that is the
image; composed into an arrangement with |
gap |
The space between an arrangement's plots, in pixels, 0 or more;
20 when unset. An arrangement's own setting, like its size: write it on the
arrangement, |
A named preset comes first and anything named adjusts it, because a preset you cannot adjust sends you straight back to asking for knobs.
Set the plot title.
title(text)title(text)
text |
The label, as a character string. |
A transform replaces the rows with a summary of them, and the mark then
draws that summary. It is joined to a mark with *, which reads as
derivation: bar * count is a bar of counts, and it is where a histogram
comes from rather than being a chart type to look up.
smooth count sum mean median max min proportionsmooth count sum mean median max min proportion
Each is a gog_atom object, used bare rather than called.
The same transform behaves identically on every mark it is legal on. Keep the statistic and change the shape and the same numbers draw a line, an area or a step – that is the point of separating the two.
countThe number of rows for each value of the grouping position.
proportionEach group's share of the total, rather than its count.
sum, mean, median, max, min
The aggregate of the measure within each group.
rangeThe lowest and highest value in each group, as a pair – which
is what interval and ribbon need.
smoothA fitted trend through the points rather than the points.
Transforms that take an argument are documented on their own pages:
bin(), density(), confidence(), bounds(), partition(),
cumulative().
stack(), dodge and jitter() are a different kind of atom – they resolve
the overlap of marks rather than computing a statistic.
marks for the shapes these are drawn with; stack() for resolving
overlap.
# A histogram is a bar of binned counts -- derived, not a chart type. h <- data(mtcars) + bar * count + x(cyl) # The same statistic, a different shape. l <- data(mtcars) + line * count + x(cyl) # An aggregate of a measure within each group. m <- data(mtcars) + bar * mean + x(cyl) + y(mpg)# A histogram is a bar of binned counts -- derived, not a chart type. h <- data(mtcars) + bar * count + x(cyl) # The same statistic, a different shape. l <- data(mtcars) + line * count + x(cyl) # An aggregate of a measure within each group. m <- data(mtcars) + bar * mean + x(cyl) + y(mpg)
Bind the x-axis to a column.
x( field, scale = NULL, base = NULL, limits = NULL, tick_count = NULL, speed = NULL, free = FALSE, legend = NULL, axis = NULL, decimals = NULL, short = NULL )x( field, scale = NULL, base = NULL, limits = NULL, tick_count = NULL, speed = NULL, free = FALSE, legend = NULL, axis = NULL, decimals = NULL, short = NULL )
field |
Column to bind (bare name). |
scale |
How the number becomes a position: |
base |
Base of a |
limits |
The two ends of the axis, as |
tick_count |
How many ticks the axis should aim for. A target, not a promise: the chosen values are rounded to readable numbers. |
speed |
Not taken: only |
free |
When the plot is faceted, |
legend |
Not taken by a position, which is read off its axis rather
than from a legend; gog refuses it here and names the five channels that draw
a legend: |
axis |
|
decimals |
Not drawn yet on this channel: only |
short |
Not drawn yet on this channel: only |
## Not run: data(gapminder_2007) + point + x(gdp, scale = "log") + y(life) data(audio) + line + x(freq, scale = "log", base = 2) + y(level) ## End(Not run)## Not run: data(gapminder_2007) + point + x(gdp, scale = "log") + y(life) data(audio) + line + x(freq, scale = "log", base = 2) + y(level) ## End(Not run)
Override the x-axis label.
x_label(text)x_label(text)
text |
The label, as a character string. |
Bind the y-axis to a column.
y( field, scale = NULL, base = NULL, limits = NULL, tick_count = NULL, speed = NULL, free = FALSE, legend = NULL, axis = NULL, decimals = NULL, short = NULL )y( field, scale = NULL, base = NULL, limits = NULL, tick_count = NULL, speed = NULL, free = FALSE, legend = NULL, axis = NULL, decimals = NULL, short = NULL )
field |
Column to bind (bare name). |
scale |
How the number becomes a position: |
base |
Base of a |
limits |
The two ends of the axis, as |
tick_count |
How many ticks the axis should aim for. A target, not a promise: the chosen values are rounded to readable numbers. |
speed |
Not taken: only |
free |
When the plot is faceted, |
legend |
Not taken by a position, which is read off its axis rather
than from a legend; gog refuses it here and names the five channels that draw
a legend: |
axis |
|
decimals |
Not drawn yet on this channel: only |
short |
Not drawn yet on this channel: only |
Override the y-axis label.
y_label(text)y_label(text)
text |
The label, as a character string. |
Bind the z-axis to a column.
z( field, scale = NULL, base = NULL, limits = NULL, tick_count = NULL, speed = NULL, free = FALSE, legend = NULL, axis = NULL, decimals = NULL, short = NULL )z( field, scale = NULL, base = NULL, limits = NULL, tick_count = NULL, speed = NULL, free = FALSE, legend = NULL, axis = NULL, decimals = NULL, short = NULL )
field |
Column to bind (bare name). |
scale |
How the number becomes a position: |
base |
Base of a |
limits |
The two ends of the axis, as |
tick_count |
How many ticks the axis should aim for. A target, not a promise: the chosen values are rounded to readable numbers. |
speed |
Not taken: only |
free |
When the plot is faceted, |
legend |
Not taken by a position, which is read off its axis rather
than from a legend; gog refuses it here and names the five channels that draw
a legend: |
axis |
|
decimals |
Not drawn yet on this channel: only |
short |
Not drawn yet on this channel: only |
The third position takes a label override for the same reason it takes a scale and a tick: the positions are a family of three, and two of them having one is the per-channel exception the orthogonality law catches. Read only in 3-D, where the axis names sit on the projected cube's edges.
z_label(text)z_label(text)
text |
The label, as a character string. |