Styling Charts

Data

For this exercise, we’ll use data on coastal ocean temperatures by depth. The dataset was used as a TidyTuesday dataset so you can load it in directly from GitHub.

ocean_temperature <- readr::read_csv("https://raw.githubusercontent.com/rfordatascience/tidytuesday/main/data/2026/2026-03-31/ocean_temperature.csv")
ocean_temperature_deployments <- readr::read_csv("https://raw.githubusercontent.com/rfordatascience/tidytuesday/main/data/2026/2026-03-31/ocean_temperature_deployments.csv")
NoteDownload data

Alternatively, you can download the CSV files:

Load the data from your local copy:

ocean_temperature <- readr::read_csv("../data/ocean_temperature.csv")
ocean_temperature_deployments <- readr::read_csv("../data/ocean_temperature_deployments.csv")

For more information on the datasets, look at the data dictionaries.

Code

Here’s some R code which produces that chart below. You can either use this code as a starting point, or write your own to complete the exercises below. The aim of the chart is to compare when temperatures peak at different depths.

First load the required packages and prepare the data:

library(ggplot2)
library(dplyr)
library(lubridate)
plot_data <- ocean_temperature |>
  mutate(sensor_depth_factor = factor(sensor_depth_at_low_tide_m)) |>
  filter(year(date) >= 2023, year(date) <= 2025)

Then create a default line chart:

g <- ggplot(
  data = plot_data,
  mapping = aes(
    x = date, y = mean_temperature_degree_c,
    colour = sensor_depth_factor
  )
) +
  geom_line()
g

Exercises

  1. Choose and implement a more appropriate colour palette. Hint: Use scale_colour_manual() or scale_colour_brewer().

  2. Is your new colour palette accessible? Hint: Use ColorBrewer or VizPalette to check the colours.

  3. Edit the chart design to improve how easy it is to compare temperatures at different depths. Hint: Consider marks at the end of lines, facets, or labels.

  4. Add an appropriate title, subtitle, and caption. Hint: Use the labs() function to add text. You may also want to look at the ggtext package for automatic text wrapping.

  5. Bonus: Add annotations highlighting an interesting aspect of the data e.g. times when data is missing.

  6. Bonus: How might you change the chart type to show deviations rather than overall trend and seasonal patterns? How might you show uncertainty? Should you show uncertainty? You can calculate confidence intervals using the code below.

plot_data_ci <- plot_data |>
  mutate(
    lower = mean_temperature_degree_c - qnorm(0.975) * (sd_temperature_degree_c / sqrt(n_obs)),
    upper = mean_temperature_degree_c + qnorm(0.975) * (sd_temperature_degree_c / sqrt(n_obs))
  )

The default palette makes it hard to tell the lines apart. Instead, we’ll use the viridis package to find better colours. Although the depths are categories, they are ordered.

library(viridis)
g <- g +
  scale_colour_viridis(discrete = TRUE)
g

It’s still hard to tell the lines apart, so let’s remove the dependency on colour by using facets. The gghighlight package allows us to keep other lines in the background as reference lines. This means we don’t need colour at all.

library(gghighlight)
g <- g +
  facet_wrap(~sensor_depth_factor,
    labeller = as_labeller(function(x) paste(x, "metres"))
  ) +
  gghighlight(use_direct_label = FALSE) +
  scale_colour_manual(values = rep("black", 7))
g

We can use the labs() function to add text. We remove the y-axis title as the information is in the subtitle, and the x-axis title as it’s clearly a date.

g <- g +
  labs(
    title = "Ocean surface temperatures peak earlier in the year compared to deeper water",
    subtitle = "Daily mean temperature (°C) observed at seven different depths in Nova Scotia, Canada between 2023 and 2025.",
    caption = "Source: Nova Scotia Open Data Portal | Graphic: Nicola Rennie",
    colour = "Sensor depth (m)",
    x = NULL,
    y = "°C"
  )
g

Since the subtitle text is quite long, it gets cropped to the chart width. We want it to automatically wrap onto multiple lines which we can do by using the ggtext package to edit the theme. The legend takes up a lot of space but doesn’t add any more information that isn’t already given by the y-axis so we can remove it.

library(ggtext)
g <- g +
  theme_minimal() +
  theme(
    # auto-wrap text
    plot.title = element_textbox_simple(
      face = "bold"
    ),
    plot.subtitle = element_textbox_simple(
      margin = margin(b = 10)
    ),
    plot.caption = element_textbox_simple(
      margin = margin(t = 10)
    ),
    axis.title.y = element_text(angle = 0, vjust = 1),
    # align to left edge of plot, not panel
    plot.title.position = "plot",
    plot.caption.position = "plot",
    # remove legend
    legend.position = "none",
    # subplot styling
    panel.spacing.x = unit(1, "lines"),
    strip.text = element_text(hjust = 0, face = "bold")
  )
g

Further edits should deal with the line showing for missing data. The tsibble package can make dealing with time series data easier.