Themes & Accessibility
In Lesson 1, Maya the baker had three branches, Downtown, Riverside and the big Airport shop, and you split her crowded chart into small multiples with facets, then bent the scales so every branch was readable. The numbers were finally clear.
Now she wants the chart to look the part: clean, on-brand, and, because one of her investors is colourblind, readable by everyone in the room. None of that means changing a single data point. It means changing the theme (the fonts, gridlines and background) and the colours. Below is exactly that, the same bar chart, restyled live. Flip the Palette and Theme controls and watch the look change while the bars keep their values.
By the end of this lesson you will be able to:
- Restyle a whole plot with a built-in
theme_*()and explain that a theme changes appearance, never the data - Pick the right colour scale for the data type: a discrete scale for categories, a continuous scale for a number
- Choose a colourblind-safe palette (viridis or Okabe-Ito) and never rely on hue alone
Prerequisites: you can build a basic ggplot and map a column to colour, ggplot(data, aes(...)) + geom_*() (from Data Visualization with ggplot2 and Lesson 1 of this course, Facets and Scales). Every new function is defined as it appears.
The data, and swapping the whole look with a theme
Each lesson starts in a fresh R session, so let us put Maya's three branches back on the page. Run this block once and the rest of the lesson builds on it:
Every ggplot is two separate things stacked together. There is the data ink, the bars, points and lines that encode your numbers, and there is everything else: the grey background, the gridlines, the tick labels, the legend box, the fonts. That second part, the non-data furniture, is what ggplot2 calls the theme. The whole point of this lesson is that you can completely change the theme and the colours and the data ink stays exactly where it was. Styling is reversible decoration; it never edits a value.
ggplot2's default look, the grey panel with white gridlines, is fine for exploring but rarely what you want in a report. You change the theme by adding a theme function as one more layer with +, exactly like you add a geom. Each one is a complete, pre-built look:
theme_minimal()strips the panel border and background for a clean, modern feeltheme_bw()keeps gridlines but on white, good for printtheme_classic()gives you bare axis lines and no gridlines, a traditional looktheme_void()removes nearly everything, for maps and standalone graphics
Here is Maya's daily revenue for one branch, drawn once with the default theme and once with theme_minimal(). Only the layer at the end changes:
The bars are identical; the background, gridlines and spacing are what moved. The widget below makes that tangible across all three branches: switch the Theme control and watch the panel restyle while the bars hold steady. (Ignore the Palette control for one more step.)
theme_*() layer restyles the non-data parts of a plot in one move. It changes how the chart looks, never what it says: the same data drawn under theme_void() and theme_bw() reports identical numbers.Colour: pick the scale that matches the data type
Colour is the other big lever, and the single most common mistake is using the wrong kind of colour scale. The rule is simple once you see it: the scale you need depends on the type of the variable you are colouring by.
First, a vocabulary point. In ggplot2, colour controls the outline of points and lines, while fill controls the inside of bars and areas. You map either one to a column inside aes(), and ggplot picks a default scale. To take control, you add a scale_* layer. Which one depends on the data:
- A discrete (categorical) variable like
branchhas a few distinct labels with no order. It needs a qualitative scale that gives each category a clearly different colour:scale_fill_brewer(),scale_fill_viridis_d()(thedis for discrete), orscale_fill_manual()to hand-pick colours. - A continuous variable like
revenueis a number on a range. It needs a sequential scale where colour varies smoothly from low to high:scale_fill_viridis_c()(thecis for continuous) orscale_fill_gradient().
Maya's branch is categorical, so a discrete scale is right. The widget below shows the bar chart and, beside it, the exact ggplot code, so you can see the mapping from data to aes to geom. Picture a scale_fill_* line added to that code as you read on:
Here are two good discrete choices on Maya's branches, run them and compare:
_d scales are for discrete categories, _c scales are for continuous numbers.Did the theme change the data?
Maya draws her revenue bars, then adds + theme_dark() to match her slide deck. A colleague glances at the darker chart and worries: "the styling changed, are the revenue numbers still right?" What is the correct answer?
theme_dark() and theme_minimal() reports identical numbers.Give the branches a deliberate colour scale
Below is Maya's branch-total bar chart with fill = branch mapped, but ggplot is using its dull default colours. Add the layer that applies the discrete viridis palette so the three categories get clearly distinct, deliberate colours.
Show answer
totals <- aggregate(revenue ~ branch, data = sales, FUN = sum)
ggplot(totals, aes(branch, revenue, fill = branch)) +
geom_col() +
scale_fill_viridis_d() +
theme_minimal()Why default colours can fail a real reader
Here is the part most tutorials skip. Around 1 in 12 men and 1 in 200 women have some form of colour vision deficiency (colourblindness); the most common kind makes red and green hard to tell apart. That is not a rare edge case, it is likely someone in any room Maya presents to, including her investor.
When you encode groups with colour alone, and you pick colours that only differ in hue, those readers can lose the distinction entirely. A red line and a green line of similar brightness can collapse into the same muddy tone. The chart that looks crisp to you becomes unreadable to them.
The fix has two halves, and good design uses both:
- Choose a colourblind-safe palette whose colours differ in lightness as well as hue, so they stay distinct even when hue is lost.
- Never rely on hue alone: also encode the group with shape, linetype, direct labels, or facets, so the chart still works in greyscale or for a colourblind reader.
The widget below is the same chart under three palettes. Switch the Palette control to colorblind-safe to see the Okabe-Ito set, eight colours chosen by Masataka Okabe and Kei Ito specifically to stay distinguishable for colour vision deficiency:
viridis: safe, and readable in greyscale
The viridis palettes (the ones behind scale_*_viridis_c and scale_*_viridis_d) are a particularly good default because they were engineered to be perceptually uniform: equal steps in the data look like equal steps in colour, so the scale does not exaggerate some ranges and flatten others. As a bonus, viridis varies strongly in lightness from dark to bright, which means it stays distinguishable for colourblind readers and survives being printed or photocopied in black and white.
Viridis shines for a continuous variable. Here Maya colours each day's point by its revenue, a number, so the continuous viridis scale (_c) is the right tool:
Darker points are low-revenue days, brighter ones are high-revenue days, and that reading holds even in greyscale because the scale changes lightness, not just hue.
scale_*_viridis_c() for a continuous variable and scale_*_viridis_d() for categories.How should Maya colour her chart for everyone?
Maya needs her three-branch chart to be readable by her colourblind investor. Her colleague offers some advice. Which approach is actually correct?
Make it survive without colour
Here is Maya's scatter coloured by branch with a colourblind-safe viridis palette already in place. To be safe even in pure greyscale, add a second, non-colour channel: map shape to branch as well, so each branch is both a colour and a marker shape. Fill in the missing aes() mapping.
Show answer
ggplot(sales, aes(foot_traffic, revenue, colour = branch, shape = branch)) +
geom_point(size = 3) +
scale_colour_viridis_d() +
theme_minimal()Bake your style into one custom theme
Once Maya settles on a look, she does not want to retype the same theme tweaks on every chart. The trick is that a theme is just an R object, so you can build your own and reuse it. Start from a built-in theme, add a theme() layer to adjust individual elements, and save the result:
theme() is the fine-grained control panel: it sets individual pieces like legend.position, plot.title, axis text and gridlines, using helpers such as element_text() for text and element_blank() to remove an element entirely. Bundle the pieces you like into one object once, and every future chart gets the same look with a single + house_style.
theme_*() picks a complete preset; theme() overrides individual elements on top of it. Saving preset + theme(...) to an object gives you a consistent house style you can apply to any plot, the styling equivalent of writing a function once and reusing it.References
A few authoritative places to take this further:
- ggplot2 reference: complete themes - every built-in
theme_*()with a preview of each look. - ggplot2 reference: scale_viridis_* - the discrete and continuous viridis scales you used, with all their options.
- Okabe and Ito, Color Universal Design (the colourblind-safe palette) - the original source for the eight CVD-safe colours and the reasoning behind them.
- ggplot2: Elegant Graphics for Data Analysis, the Themes chapter (free online) - the canonical treatment of theming and building your own, by the package author.
Lesson 2 complete
You restyled Maya's chart four ways, none of which touched a data point: a built-in theme_*() to swap the whole look, the right colour scale for the data type (_d for categories, _c for numbers), a colourblind-safe palette (viridis or Okabe-Ito) so every reader can see the groups, the habit of never relying on hue alone (adding shape = branch as a second channel), and a reusable house_style object so your look stays consistent across charts.
Next, Lesson 3: Annotate and Compose Plots. You have controlled what the chart shows and how it looks; now you will make it explain itself with annotations and reference lines, place non-overlapping labels with ggrepel, and stitch several plots into one figure with patchwork.