Which point is which? text draws a string at each (x, y) position. It is point’s sibling: both place one glyph per row, but where a point’s glyph is a dot, a text mark’s glyph is a string, and the string comes from a column, through the label channel.
16.1 The mark and its content
A point tells you where a row sits; it cannot tell you which row. Five medal counts drawn as dots are five anonymous points:
data(medals) + text +x(gold) +y(silver) +label(country) +x_label("Gold") +y_label("Silver") +title("The same five, named")
(data(medals) + text + x(col.gold) + y(col.silver) + label(col.country) + x_label("Gold") + y_label("Silver") + title("The same five, named"))
data(medals) + text +x(:gold) +y(:silver) +label(:country) +x_label("Gold") +y_label("Silver") +title("The same five, named")
plot(data(medals), text,x(col.gold),y(col.silver),label(col.country),x_label("Gold"),y_label("Silver"),title("The same five, named"))
That is the whole difference between the two marks: point draws a glyph at the row, text draws the row’s own value as the glyph.
16.2 Both at once: the labeled scatter
They compose. Superpose a point and a text over one (x, y) and you get a dot located precisely and its name attached: a labeled scatterplot built from two marks, not a special geom. A label would otherwise land right on the dot, so nudge it clear with style(nudge = "up"):
data(medals) + point +style(size =5, color ="#9e9e9e") + text +x(gold) +y(silver) +label(country) +style(nudge ="up") +x_label("Gold") +y_label("Silver") +title("Located by the dot, named just above")
(data(medals) + point + style(size =5, color ="#9e9e9e") + text + x(col.gold) + y(col.silver) + label(col.country) + style(nudge ="up") + x_label("Gold") + y_label("Silver") + title("Located by the dot, named just above"))
data(medals) + point +style(size =5, color ="#9e9e9e") + text +x(:gold) +y(:silver) +label(:country) +style(nudge ="up") +x_label("Gold") +y_label("Silver") +title("Located by the dot, named just above")
plot(data(medals), point,style({ size:5,color:"#9e9e9e" }), text,x(col.gold),y(col.silver),label(col.country),style({ nudge:"up" }),x_label("Gold"),y_label("Silver"),title("Located by the dot, named just above"))
There is no “labeled point” mark to learn: a labeled scatter is just point + text, superposition (the +operator) doing the work.
16.2.1 Nudging the label off its point
style(nudge = ) shifts a text label a little off its (x, y), one of "up", "down", "left", or "right":
The distance is derived from the font size, just enough to clear a default dot, so there is nothing to specify; nudge takes only the direction. It is a setting, not a jitter/dodge-style adjustment, and the difference is exact: those two compute data-derived offsets (a seeded spread, a per-group slot) to separate a mark’s own overlapping elements, whereas a nudge is one fixed displacement that derives nothing from the data. It is text-only: nudging a point or bar would move the data itself, so the engine refuses it there with direction. (When many labels collide with each other, a different overlap, a data-derived repel is the answer; that one is not built yet.)
16.3 Numeric labels: the value on each mark
A label need not be a name. Bind a numeric column and its value is drawn, formatted so an integer keeps no stray decimals. Layer text over bar to print each bar’s height at its top:
data(medals) + bar +style(color ="steelblue") + text +x(country) +y(gold) +label(gold) +y_label("Gold medals") +title("Bars that state their value")
(data(medals) + bar + style(color ="steelblue") + text + x(col.country) + y(col.gold) + label(col.gold) + y_label("Gold medals") + title("Bars that state their value"))
data(medals) + bar +style(color ="steelblue") + text +x(:country) +y(:gold) +label(:gold) +y_label("Gold medals") +title("Bars that state their value")
plot(data(medals), bar,style({ color:"steelblue" }), text,x(col.country),y(col.gold),label(col.gold),y_label("Gold medals"),title("Bars that state their value"))
16.4 Color
color splits the labels by group and earns a legend, the same discrete split line and bar make. Here each country’s name takes its own hue:
data(medals) + text +x(gold) +y(silver) +label(country) +color(country) +x_label("Gold") +y_label("Silver") +title("One hue per country")
(data(medals) + text + x(col.gold) + y(col.silver) + label(col.country) + color(col.country) + x_label("Gold") + y_label("Silver") + title("One hue per country"))
data(medals) + text +x(:gold) +y(:silver) +label(:country) +color(:country) +x_label("Gold") +y_label("Silver") +title("One hue per country")
plot(data(medals), text,x(col.gold),y(col.silver),label(col.country),color(col.country),x_label("Gold"),y_label("Silver"),title("One hue per country"))
16.5 Styling: one color, one size
style(color = ) sets the ink and style(size = ) the font, in pixels, for the whole layer:
A per-row font size (a word cloud, where the biggest word is the most frequent) is a parked follow-up; for now size is a layer setting, not a mapped channel.
16.6 Why label is a channel
textrequires a label: it is the mark’s minimum syllable, the way x and y are. Ask for a text with nothing to say and the engine refuses with direction rather than drawing blank:
data(medals) + text +x(gold) +y(silver)
Error:
! gog: `text` needs `label()` but none is set. Add `label(<column>)` — a text cannot be drawn without it.
gog: nothing was rendered. Fix the above, or set GOG_STRICT=0 to draw anyway.
The reason label is a channel and not, say, a text("USA") argument is the grammar’s own rule: a string is data, and data reaches a mark only through a channel, never as an argument to the mark. So a fixed annotation comes from a one-row table (data), not a setting; and label rides text alone: every other mark refuses it, which is why a labeled scatter is point + text and not a label hung on point. text is the shape; label is the column it draws.
16.7 A note is a layer
That one-row table is worth seeing drawn, because it is the whole of what other libraries spell as an annotate() function. A second data() opens a second layer over its own table (the Data chapter’s rule), and a table with one row in it puts one string on the plot:
notes <-data.frame(gdp =35000, life =66, what ="rich, and dying young")data(gapminder_2007) + point +x(gdp) +y(life) +data(notes) + text +label(what) +title("A note is a layer, not a function")
notes = {"gdp": [35000], "life": [66], "what": ["rich, and dying young"]}(data(gapminder_2007) + point + x(col.gdp) + y(col.life) + data(notes) + text + label(col.what) + title("A note is a layer, not a function"))
notes = (gdp = [35000], life = [66], what = ["rich, and dying young"],)data(gapminder_2007) + point +x(:gdp) +y(:life) +data(notes) + text +label(:what) +title("A note is a layer, not a function")
const notes = { gdp: [35000],life: [66],what: ["rich, and dying young"] };plot(data(gapminder_2007), point,x(col.gdp),y(col.life),data(notes), text,label(col.what),title("A note is a layer, not a function"))
Nothing was added to the grammar to reach that. data() already binds the nearest table, text already draws one string per row, and a note is a table that happens to have one row. Because the note is data like any other, it is scaled like any other: move it outside the cloud and the axes stretch to include it, which is the honest behavior and occasionally a surprise.
One thing to know while writing one. x and y are shared by the whole plot, so the note’s table has to carry the base table’s position column names (gdp and life here), and a note that names its own columns is waiting on per-layer positions. Everything else an annotate() call does in the data’s own units is already sayable: a marker is point over the same table, and several notes at once are simply several rows.
16.8 What you can set
Setting
Value
style(color = )
any CSS color name or hex
style(opacity = )
0 to 1
style(size = )
pixels
style(nudge = )
up, down, left, right
And these vary per row if you map them to a column instead: color() (categories), label() (either), play() (either).
nudge is the text mark’s own, and it exists so a superposed point + text does not draw the label on top of the dot:
data(medals) +x(country) +y(gold) + point +style(color ="gray", size =6) + text +label(gold) +style(nudge ="up", size =11, color ="black") +y_label("Gold medals") +title("Labels nudged clear of their points")
(data(medals) + x(col.country) + y(col.gold) + point + style(color ="gray", size =6) + text + label(col.gold) + style(nudge ="up", size =11, color ="black") + y_label("Gold medals") + title("Labels nudged clear of their points"))
data(medals) +x(:country) +y(:gold) + point +style(color ="gray", size =6) + text +label(:gold) +style(nudge ="up", size =11, color ="black") +y_label("Gold medals") +title("Labels nudged clear of their points")
plot(data(medals),x(col.country),y(col.gold), point,style({ color:"gray",size:6 }), text,label(col.gold),style({ nudge:"up",size:11,color:"black" }),y_label("Gold medals"),title("Labels nudged clear of their points"))
There is no distance to give it. The offset is derived from the font size, about one line, so a label clears its point at every plot size and in every facet; a pixel count would mean something different in each. nudge shifts every label the same way, which is why it is a setting and not a collision modifier: it derives nothing from the data, so it cannot help when many labels collide with each other.
A mapped size on text is valid grammar the engine does not draw yet, so size(population) is refused as unsupported rather than ignored. Set it instead, and the per-row font size (a word cloud) stays on the list of things not built.
16.9 What it refuses
A text mark’s glyph is its content, so it has nothing left for shape to vary:
data(medals) + text +x(gold) +y(silver) +label(country) +shape(country)
Error:
! gog: `shape` cannot be bound to `text` — a text has no shape feature. Remove `shape(country)`, or use a mark that has one.
gog: nothing was rendered. Fix the above, or set GOG_STRICT=0 to draw anyway.
Form comes from label here, which is the argument of the section above. For the same reason a string takes no texture, being neither a stroke to dash nor a region to hatch:
data(medals) + text +x(gold) +y(silver) +label(country) +style(pattern ="hatch")
Error:
! gog: `style(pattern = )` is a stroke's dash or a fill's texture, and a `text` is a glyph, not either — a point's form is set by `shape`, a text's by its content. The strokes (`line`/`step`/`interval`/`path`/`rule`) take a dash; the fills (`area`/`bar`/`box`/`ribbon`/`zone`) a texture.
gog: nothing was rendered. Fix the above, or set GOG_STRICT=0 to draw anyway.