Ses choix
- Un titre qui dit ce que montre la figure, un sous-titre si besoin, et la source des données en petit, en bas à droite (
caption). - Un axe fléché, en noir comme dans les schémas du lexique, pour une variable numérique ou ordonnée ; pas d'axe pour des catégories (
arrows = "x","y","xy"ou""). - Une marque « ) » au début d'un axe dont l'échelle ne commence pas à 0, avec de la place avant la première valeur (
break_not_zero = "x","y"ou"xy") ; pas pour les années, ni quand 0 ne veut rien dire. - Une grille fine et gris clair, sur l'axe qui porte les valeurs seulement (
grid = "x","y","xy"ou"") ; ni grille secondaire ni graduations : la grille suffit à lire les valeurs. - Le texte en gris foncé (#535353, le gris des schémas du lexique), le titre en noir et en gras, aligné à gauche sur toute la figure : on lit d'abord le message.
- Jusqu'à trois catégories, pas de légende : leur nom est écrit sur la figure, dans leur couleur, au plus près des données. Au-delà, la légende en haut à gauche, là où l'œil commence ; les titres de facettes à gauche et en gras.
- Des gris par défaut, ceux des schémas. Une couleur code toujours une variable : soit une information que la figure ne montre pas autrement, soit, en double encodage, des groupes qu'un axe nomme déjà, pour les reconnaître d'un coup d'œil d'une figure à l'autre (chaque espèce de manchot garde sa couleur dans tous les exemples). Jusqu'à trois catégories, les couleurs du site : prune, bleu, orange. De quatre à huit, la palette IdEst
Rattner, lisible par les daltoniens ; au-delà, la couleur ne suffit plus à distinguer les catégories. Une quantité ou un ordre prend la palette séquentielle IdEstHokusai3, du clair au foncé : pour un facteur ordonné, ajouterscale_colour_ordinal(type = idest_seq). Chaque continent garde sa couleur,continent_colors: cinq teintes de même clarté OKLCH (0,62), pour qu'aucun ne ressorte plus qu'un autre, de chroma 0,2 quand le sRGB le permet ; leur nom, écrit dans leur couleur, prend une clarté commune plus basse (0,55,continent_text_colors), pour un contraste de 4,5:1 sur blanc. À clarté égale, seule la teinte les distingue, ce qui est plus fragile pour un daltonien et se perd en gris : leur nom est toujours écrit, sur la figure ou en légende. - Les milliers séparés par une espace fine, comme l'écrivent le Système international et le BIPM : 20 000. Le thème ne peut pas mettre en forme les nombres ; chaque échelle numérique prend
labels = lex_number, sauf les années, qui ne sont pas des quantités. - La police Quicksand, celle du texte du site, intégrée à chaque image. Sans elle installée, R prend la police sans empattement par défaut.
- Un fond transparent, ajouté par le script qui produit les images : la page pose le sien.
Son code
Il simplifie theme_idest() du paquet ggplotpq, d'Adrien Taudière. Pour l'employer, copier le code ci-dessous dans sa session, avec ggplot2 chargé : il demande ggplot2 ≥ 4.0.
## Common ggplot2 theme of the lexicon's examples, shown as is on the theme
## page of the site (R/theme_page.R): comments stay in English.
## Colors, always encoding a variable (redundantly when an axis already names
## the groups, which keep their color across examples): the site's plum, blue and
## orange for up to three categories; beyond, the IdEst palettes.
lex_colors <- c(plum = "#9b2367", blue = "#2e7891", orange = "#dc863b")
## IdEst "Rattner", safe for color vision deficiencies: up to eight
## categories, no more. For n categories, the colors whose rank is n or less.
rattner <- c("#de8e69", "#f1be99", "#c1bd38", "#7a9132",
"#4c849a", "#184363", "#5d5686", "#a39fc9")
rattner_rank <- c(1, 5, 6, 2, 3, 7, 8, 4)
## IdEst "Hokusai3", light to dark: quantities and ordered categories.
idest_seq <- c("#d8d97a", "#95c36e", "#74c8c3", "#5a97c1", "#295384", "#0a2e57")
## IdEst "Picabia", brown to slate through a light grey: values on either
## side of a centre, such as correlations around 0.
## `scale_fill_gradientn(colours = idest_div, limits = c(-1, 1))`.
idest_div <- c("#53362e", "#744940", "#9f7064", "#c99582", "#e6bcac", "#e2d8d6",
"#a5a6ae", "#858794", "#666879", "#515260", "#3d3d47")
## Continents (gapminder): the same color in every example, whatever the
## continents shown. One OKLCH lightness (0.62), so that no continent stands
## out more than another; chroma 0.2 where sRGB allows it. Hue alone tells
## them apart, which is weaker for color vision
## deficiencies and lost in grayscale: continents are always named, on the
## plot or in a legend. `scale_fill_manual(values = continent_colors)`.
continent_colors <- c(Africa = "#e44063", Americas = "#a38203", Asia = "#0b999c",
Europe = "#7a6df8", Oceania = "#c050c5")
## Their names, written in their color: the same hues at one darker lightness
## (0.555), so that small text reaches a 4.5:1 contrast with white (WCAG AA).
continent_text_colors <- c(Africa = "#cc2651", Americas = "#8c6f05", Asia = "#038386",
Europe = "#6958e2", Oceania = "#ab3ab0")
## Thousands separated by a thin space, as the SI and the BIPM write them:
## 20 000. Quicksand has no narrow no-break space (U+202F); text in an SVG
## never wraps, so the thin space (U+2009) does not break either. A theme
## cannot format numbers: each numeric scale takes `labels = lex_number`,
## except years, which are not quantities (2007, not 2 007).
lex_number <- scales::label_number(big.mark = "\u2009")
## An axis with a ")" mark needs room before its first value, so that the
## data start after the mark: `expand = lex_break_expand` in its scale,
lex_break_pad <- c(0.12, 0.05)
lex_break_expand <- expansion(mult = lex_break_pad)
## and no tick in that room, where its label would meet the mark:
## `breaks = lex_break_breaks`. ggplot2 gives it the expanded range, from
## which the data range is recovered.
lex_break_breaks <- function(limits) {
span <- diff(limits) / (1 + sum(lex_break_pad))
b <- scales::breaks_extended()(limits)
b[b >= limits[1] + lex_break_pad[1] * span]
}
lex_pal_discrete <- function(n) {
if (n <= 3) {
unname(lex_colors[seq_len(n)])
} else {
rattner[rattner_rank <= n]
}
}
## An axis line that starts with a ")" mark: its scale does not start at 0.
## The mark is a half circle 3 mm from the corner, bulging the way the axis
## goes; the line starts just after it, leaving the corner blank.
element_line_break <- S7::new_class("element_line_break", parent = element_line,
constructor = function(...) S7::new_object(element_line(...)))
registerS3method("element_grob", "element_line_break", envir = asNamespace("ggplot2"), function(element, x = 0:1, y = 0:1, ...) {
r <- 1.8 # radius of the mark, mm
at <- 3 # from the corner to the centre of the mark, mm
t <- seq(-90, 90, length.out = 19) * pi / 180
horizontal <- length(unique(as.numeric(y))) == 1
gp <- grid::gpar(col = element@colour, lwd = 0.75 * element@linewidth * .pt)
if (horizontal) {
mark <- grid::linesGrob(x[1] + unit(at + r * cos(t), "mm"), y[1] + unit(r * sin(t), "mm"), gp = gp)
x[1] <- x[1] + unit(at + r + 0.3, "mm")
} else {
mark <- grid::linesGrob(x[1] + unit(r * sin(t), "mm"), y[1] + unit(at + r * cos(t), "mm"), gp = gp)
y[1] <- y[1] + unit(at + r + 0.3, "mm")
}
## NextMethod() draws the line from the shifted start.
grid::grobTree(NextMethod(), mark)
})
## `grid`: the axes that carry a grid, "x", "y", "xy" or "".
## `arrows`: the axes drawn as an arrow, those of a numeric or ordered
## variable, "x", "y", "xy" or "" (a theme cannot see the variables).
## `break_not_zero`: the arrowed axes whose scale does not start at 0, marked by ")";
## not for years, nor where 0 means nothing.
## Each plot has a title, a subtitle if needed, and the source of its data
## in `caption`, at the bottom right.
## Up to three categories, name them on the plot in their color rather than
## in a legend.
## Ordered factors ignore the theme's palettes: add
## `scale_colour_ordinal(type = idest_seq)` (or `scale_fill_ordinal()`).
theme_lex_viz <- function(base_size = 10, grid = "xy", arrows = "xy", break_not_zero = "") {
grid_line <- element_line(colour = "#d6d6d6", linewidth = 0.25)
arrow_line <- function(axis) {
new <- if (grepl(axis, break_not_zero)) { element_line_break } else { element_line }
new(colour = "black", linewidth = 0.5,
arrow = grid::arrow(length = unit(1.6, "mm"), angle = 25, type = "closed"))
}
theme_minimal(base_size = base_size, base_family = "Quicksand") +
theme(
text = element_text(colour = "#535353"),
plot.title = element_text(colour = "black", face = "bold", hjust = 0),
## A text box, so that a long subtitle wraps to the plot's width
## instead of running off it; it reads Markdown (escape _ and *).
plot.subtitle = ggtext::element_textbox_simple(margin = margin(b = 6)),
plot.caption = element_text(colour = "#7d7d7d", size = rel(0.7), hjust = 1),
plot.title.position = "plot",
plot.caption.position = "plot",
axis.text = element_text(colour = "#535353"),
axis.ticks = element_blank(),
axis.line.x = if (grepl("x", arrows)) { arrow_line("x") } else { element_blank() },
axis.line.y = if (grepl("y", arrows)) { arrow_line("y") } else { element_blank() },
panel.grid.minor = element_blank(),
panel.grid.major.x = if (grepl("x", grid)) { grid_line } else { element_blank() },
panel.grid.major.y = if (grepl("y", grid)) { grid_line } else { element_blank() },
legend.position = "top",
legend.justification = "left",
strip.text = element_text(face = "bold", hjust = 0),
geom = element_geom(family = "Quicksand"),
palette.colour.discrete = lex_pal_discrete,
palette.fill.discrete = lex_pal_discrete,
palette.colour.continuous = idest_seq,
palette.fill.continuous = idest_seq
)
}