Appendix C — Combinations

Every mark down the side, every attribute across the top: the whole of what the grammar lets you combine, on one page. Four crossings live here. The first is what a mark lets you map (the channels); the second is what a mark lets you derive (the transforms); the third is which two transforms can stand together on one mark; the fourth is where a mark can be drawn (the coordinate spaces). Not only what a mark takes, but what it does not, and the surprising-but-legal pairings you would never think to try.

These grids are the orthogonality law made visible. Law 1 says every compatible atom combines with every other; a table is that promise enumerated, one cell at a time. It is the direct analog of the jamo (자모) chart a Hangeul (한글) learner reads. The jamo are Hangeul’s individual letters: its consonants and its vowels. A syllable block is what they assemble into. This book has used that pairing from the start: a mark is a consonant and a position is a vowel, and neither draws alone. The chart puts the consonants down the side and the vowels across the top. The whole syllable space appears in the crossing.

Legal is not the same as useful. Law 8 guarantees only the first: where a cell says a combination renders, the engine draws it, but whether it should be drawn is your judgment. The empty cells teach as much as the full ones, marking the honest edge of what a geometry can express rather than an oversight.

Nothing here is typed by hand. Every table is generated live from the engine’s own rule table each time the book builds, the same table the engine consults to accept or refuse your plot. A grid that is described can fall out of step with the code; a grid that is generated cannot. Add a mark, a channel, a transform or a space and these pages fill in by themselves.

C.1 Marks and channels

Down the side, the marks; across the top, the channels. A cell says whether you may map a column to that channel on that mark.

Mark x y z color size shape pattern opacity group label play
point
line
area
bar
step
interval
box
ribbon
text
path
rule
zone
surface

Reading the five glyphs:

  • ● must: a required position. The mark does not render without it. A point needs an x and a y; a text needs its label.
  • ✅ maps: bind a column here and the engine draws it today. The ordinary case, a channel doing its job.
  • ○ set: the mark has this feature, but not row by row. A line is one stroke, so its size and opacity are a single value for the whole layer, fixed with style() rather than mapped. Those constants have their own Mark × Setting grid.
  • ◌ not drawn: legal grammar the engine refuses today, with a direction rather than a wrong picture. One glyph, three verdicts, and the refusal message tells you which you have. Owed: a mapped font size on text. Blocked on infrastructure: z on rule and on zone, whose 3-D forms are a plane and a slab with no footprint to sort by, so they need an engine that can tell what hides what. Decided: z on line, step, area and ribbon, which read a domain left to right where a cube has none, and which are pointed at path instead. Only the first of the three will ever fill in. The Mark × Space grid below is the same three readings on its own blanks.
  • — none: the mark has no such feature. A bar has no glyph to shape, a point nothing to group into a line. Binding it means nothing, and the engine says so.

Three things the crossing shows at a glance that prose keeps losing:

  • group splits a line, not a bar. Run down the group column: ✅ on the path and distribution marks (line, step, area, interval, box, ribbon), on point, bar, and text. A bar is split into groups by color, then piled or set side by side with stack / dodge; a bare group has no line to divide.
  • label rides text alone. Its column is ● on text and everywhere else. A labeled scatter is the superposition point + text, never a label hung on the point.
  • A category on x reaches every mark. On y it does not. Read the two position rows against each other and the grid divides the marks in two. The glyph marks (point, text) and the marks whose slot width does the measuring (bar, interval, box) take either type on both positions, which is why a strip plot, a horizontal bar and a categorical box all fall out of one rule. So do path, which sorts nothing and therefore has no domain, rule, which measures nothing on either axis, and zone, which measures nothing by length at all. That last is what makes a heatmap cell a zone rather than a second kind of bar. The path and region marks (line, step, area, ribbon) take either type on x alone: their x is the domain the path is read along and their y is the quantity it traces, and those are not the same kind of axis. A glyph’s two axes are.
  • Two rows have no ● at all. Every other mark must be told where to put something; rule and zone show ✅ on both positions and ● on neither, because each needs one of the two and no per-channel cell can say “either, but not both”. Those obligations are stated once in the engine instead, and a pair of ✅s with no ● is what a mark the panel finishes looks like from here (Rule, Zone).
  • zone’s color takes either type. It is the only fill color that does. Every other region takes a category; a zone takes a number too, and reads the ramp. A hairline has no area to decode a scale from, which is why rule and line refuse it: a rectangle has nothing but area.

C.2 Marks and transforms

The second crossing: not what a mark maps, but what it derives. A transform enters through * (bar * bin, interval * range, point * jitter), and which transform a mark takes is decided by the mark’s geometry against the transform’s kind. There are three kinds, and each has its own grid below.

Three glyphs read every cell:

  • ✅ combines: the transform applies to this mark and renders.
  • ● required: the transform’s kind is the mark’s minimum syllable. An interval and a ribbon float between a low and a high, so they will not draw until one transform of that kind supplies the pair. The cells in a row are a choice, not a list: pick one.
  • — none: the mark has no use for this kind of transform, and the engine refuses it with a direction toward the transform its geometry does want.

C.2.1 Statistics: a value per group

The summarizing transforms each reduce the data to one value at each x: a bin’s count, a group’s mean, a smoothed height. Any mark that draws a value at each x can draw that summary, so the whole block moves together, which is why a distribution has three spellings and no histogram(element=) family: bar * bin fills it, line * bin is the frequency polygon, step * bin the staircase.

Mark bin smooth count density proportion sum mean median max min partition
point
line
area
bar
step
interval
box
ribbon
text
path
rule
zone
surface

The five drawing marks (point, line, area, bar, step) take the lot; the other four take none, and each for a reason the next two grids make good. A span mark (interval, ribbon) needs a pair, not a single value. A box takes no statistic because it carries its own: the five-number summary is injected by the mark, not composed, so it never appears as a column here. And a text draws its label, which no statistic produces.

C.2.2 Ranges and bands

Three transforms produce a pair of values, a low boundary and a high one, per group. range and confidence compute the pair from your data; bounds reshapes a pair you computed already. Four marks draw such a pair: interval whiskers it, ribbon fills it, line and step trace its two boundaries as an unfilled band.

Mark range confidence bounds
point
line
area
bar
step
interval
box
ribbon
text
path
rule
zone
surface

The on interval and ribbon is the one place this crossing shows a requirement, not just a permission. Neither mark has a baseline to fall back on, so neither renders until a pair transform gives it a low and a high: interval * range is the whole minimum syllable, a bare interval is refused. A line or a step already draws fine on its own, so the same transform is optional there, a second way to use the mark. Everywhere else the pair has nowhere to go: on a point or a bar it is refused toward the whisker, on an area toward the ribbon that is its filled twin.

C.2.3 Collision modifiers: an offset, not a statistic

When marks collide at one position, three offsets pull them apart, and which one a mark takes is decided by its geometry and by the axis the collision is on. dodge subdivides a width, so it serves the marks that have one (bar, box, interval). stack accumulates along the measure axis, so it serves the marks that can spend a span: bar and area fill it with length, and a point, having no length to stretch, spends it on how many dots there are, which is the dot plot. jitter spreads a mark with no width along a categorical axis, which is point alone.

Mark dodge stack jitter
point
line
area
bar
step
interval
box
ribbon
text
path
rule
zone
surface

No mark is left without one, and exactly one takes two: point, which is the only mark that can meet two of the three preconditions, on different axes. stack answers its overlap along the measure axis (a pile of dots, which can be counted); jitter answers the same mark’s overlap along a categorical one (a nudge, because a category has no magnitude to spend). Everywhere else the division is exclusive, which is why the engine can always name the right tool when you reach for the wrong one. Ask for point * dodge and it points at jitter; ask for area * dodge and it points at stack. A box shows the split cleanly: it takes no statistic (its own is built in) yet still takes dodge, because setting grouped boxes side by side is a question about position, orthogonal to the summary each box already carries.

C.3 Transforms and transforms

The third crossing is the only one where a mark does not appear. Two transforms can follow one mark: bar * bin * mean cuts the axis into bands, then averages inside each. Which pairs can do that is decided by the transforms alone, and by one rule.

A transform has a job. There are four of them, and every transform in gog does at least one:

job the question it answers the transforms that answer it
extent where are the cells? bin, density, smooth, partition, and bounds on a zone
measure what is in them? count, sum, mean, median, max, min, range, confidence, bounds
scale what scale is the answer read on? proportion, and stack(share = TRUE)
position where do the marks sit? dodge, stack, jitter

Two transforms compose when they do different jobs, and contradict when they do the same one. A cell holds one extent, one measurement, one scale and one arrangement. So a second transform doing a job the first already did leaves the engine no reading that keeps both, and it refuses rather than drawing one and discarding the other.

One transform is the exception, and it is the exception that makes chaining useful. bin does two jobs: it cuts the axis, and it counts what lands in each band. The count is a by-product of the cut, so bin can give it up, and it gives it up to a transform you handed a column. That is why bar * bin * mean composes: bin supplies the cells and mean supplies their value. And it is why bar * bin * count does not, since a tally was never handed a column to reduce.

bin smooth count density proportion sum mean median max min range confidence bounds partition dodge stack jitter
bin ·
smooth ·
count ·
density ·
proportion ·
sum ·
mean ·
median ·
max ·
min ·
range ·
confidence ·
bounds ·
partition ·
dodge ·
stack ·
jitter ·

The grid is symmetric, and that is the rule showing rather than being claimed. Whether two transforms contradict is a fact about their jobs. It has nothing to do with which one you typed first. So order is yours to choose: write them in the order they happen, and the sentence reads the way it runs.

Read the block of in the middle. Nine transforms measure, so no two of them combine, and that single square is the whole reason bar * count * mean is refused, and bar * mean * sum, and interval * range * confidence. bin is the row with a where the others have , which is the exception above, drawn.

Two collisions the grid cannot show, because both depend on something outside the pair. stack(share = TRUE) divides each pile by its own total, which is a scale, so it contradicts proportion: two divisions, and the second reads as shares of nothing. And bounds says where a zone’s sides are. On that one mark it does the extent job, so it contradicts bin. On every other mark it names the low and the high on the measure axis. The engine reads both cases correctly, and the grid above shows the reading that applies everywhere else.

Chaining stops at four, and nobody chose the number. Four jobs, one transform each, so a fifth transform has to repeat a job, and repeating one is the contradiction this whole section is about.

C.3.1 The shapes a long chain can take

Three and four transforms sound like a large space, and they are not. Once each job is filled at most once, and the marks are asked which transforms they take, every chain of three or four has one of six shapes. Two slots stand for a family rather than a transform: <aggregation> is any of sum, mean, median, max, min, and <collision modifier> is any of dodge, stack, jitter.

how many the shape the jobs it fills
3 <aggregation> * proportion * <collision modifier> measure, scale, position
3 bin * <aggregation> * <collision modifier> extent, measure, position
3 bin * <aggregation> * proportion extent, measure, scale
3 bin * proportion * <collision modifier> extent, scale, position
3 count * proportion * <collision modifier> measure, scale, position
4 bin * <aggregation> * proportion * <collision modifier> extent, measure, scale, position

Read the four-transform row first, because it is the whole ceiling in one line. It fills each job exactly once, and only one of its slots has any real choice in it. bin is the only extent transform that hands its measurement over. proportion is the only one that rescales. The family in the last slot is picked by the mark. So the aggregation is the only free decision, which is why four is reachable and not roomy.

The shapes say what the transforms allow. A mark can still refuse one of them for a reason about its own geometry, and the grids earlier in this chapter are where that is decided. point * proportion * stack is the clearest case. A dot plot piles one whole dot per observation, and a share is less than one dot. So the chain is refused on point and drawn on bar.

C.4 Marks and spaces

The fourth crossing is not an attribute of the mark at all. It is the coordinate space the whole plot sits in, and it asks a different question of every mark: once the plane is bent, or stood up into a cube, can this geometry still be drawn? A polar() bar is a wedge, a polar() line is a closed curve, and a z scatter is a projected cloud, all from the same sentences that draw them flat.

Two glyphs here, not five. Either the engine draws this mark in this space today, or it does not and says so:

  • ✅ drawn: the mark renders in this space.
  • — not drawn: legal to write and refused with direction, never half-drawn. As on the channel grid, one glyph covers owed, blocked and decided alike; which one a blank is, is the subject of the three paragraphs under the table.
Mark flat space polar nest map
point
line
area
bar
step
interval
box
ribbon
text
path
rule
zone
surface

flat is full but for one cell, and that cell is worth stopping on: surface does not draw in the plane. Every other mark was built there and some have since learned to stand up, while a surface is a sheet through three positions and there is no such thing without the third, so its minimum syllable includes the cube. It is the only mark whose row reads the other way round from every other (Surface).

space holds six marks, and which six is a reading rather than a list of what got built: a point is a place, a path is an order, a surface is a sheet, and bar, interval and box are the three marks that stand in a slot and measure along the other axis: a length, a span and a summary, which the grammar has treated as one family since orientation was decided. Each means in a cube exactly what it meant on the page.

The blanks in that column are not one kind of thing, and the engine now says which of three each is. line, step, area and ribbon read a domain left to right, and a cube has no left to right, so they are refused, a decision, not a queue entry, with path as the direction. rule and zone are blocked: their 3-D forms are a plane and a slab, neither of which has a footprint to sort by, so placing them among the data needs an engine that can tell what hides what (Space). polar is the one column with no empty half at all: every mark that draws on the page draws in the circle. It reached that state last, and by losing an argument rather than by winning one: five marks were held out on the ground that their straight edges would have to become arcs, and three of them turned out never to have needed an arc, a band’s boundaries being the same chords a line draws. The single blank is surface, and it is not a polar refusal but the same one its flat cell carries: a sheet needs the cube (Polar).

nest is the opposite shape of column: two cells filled and eleven blank, and the blanks are not a queue. This space asks a different question from the other four. Everywhere else a mark bends because its geometry survives a map of the plane; a packing is not a map of the plane at all, so what it can draw is whatever it can hand a rectangle to. bar can take one, because a bar is a measure and this space is the third thing a measure can become, after a length and an angle. text can too, and it is the only mark here that names a region rather than filling one: the rectangle is already computed, so a label goes to its center, which is what a published treemap has and what a legend cannot do once the split runs to three figures (Nest).

The eleven blanks have one reason between them, and it is not the one the column’s shape suggests. They are the marks placed by a position: point needs somewhere to sit, line, step, path, area and ribbon need an order along an axis (and two touching regions here are explicitly not near each other in the data), rule and zone span an axis that is not there, and box, interval and surface measure along one. text is not among them because it is not placed by a position here either; it is placed by the region, which is the same thing that places the bar.

map is empty all the way down, and it is in the grid for that reason. It is named in the kernel and has no renderer behind it, so the column is the honest edge of the engine rather than a gap in the table. A whole column of is what an unbuilt idea looks like when nothing is hidden.

Every mark in the kernel is in all three crossings, and what a mark’s rows say about it is worth reading across them. A path has a full row on Mark × Channel and an entirely empty one on Mark × Transform, because a path is its rows in order and every statistic replaces the rows. rule reads the same way on the transform grid for a reason one axis over, having handed a whole axis to the panel, so there is no measure to compute. And surface has a row of the transforms that tile a floor: density, which estimates a value at every node for the sheet to interpolate between, and bin, which cuts adjacent cells for it to lay a flat lid on, with the aggregation family riding bin to name the column each cell holds. path keeps the shorter row beside it, tracing the one field it derives where a surface raises it.

With these grids the orthogonality matrix is stated in full: what each mark maps, what it sets (the settings grid), what it derives, and where it can be drawn.