---
title: "ggplot2 in RO style"
format:
  html:
    dpi: 72
    tabset: TRUE
vignette: >
  %\VignetteIndexEntry{ggplot2 in RO style}
  %\VignetteEngine{quarto::html}
  %\VignetteEncoding{UTF-8}

---

```{r setup, include=FALSE}
knitr::opts_chunk$set(echo = TRUE, fig.width = 8, fig.height = 5)
```

This vignette walks you through the steps to create a static graph with `ggplot2` according to a fixed set of rules/guidelines. This vignette uses the RIVM guidelines as a basis. In future releases of `ROvis`, we aim to allow adding other guidelines from other Rijksoverheid organisations as well.

::: {.callout-note}
## Important

In this vignette, the examples specifically follow the style guidelines of the National Institue for Public Health and the Envrionment (RIVM).
:::

## Setup

```{r packages, message=FALSE}
library(ggplot2)
library(dplyr)
library(ROvis.utils) # nolint: missing_package_linter.
library(ROvis.ggplot2) # nolint: missing_package_linter.
```

We use a small synthetic dataset for demonstration purposes:

```{r data}
sex_levels <- c("Women", "Men", "Total")

# Reported cases per age group and sex
dat_cases <- data.frame(
  agegroup = rep(c("0-19", "20-39", "40-59", "60-79", "80+"), times = 3),
  sex = factor(rep(sex_levels, each = 5), levels = sex_levels),
  n = c(
    12000,
    48000,
    61000,
    43000,
    18000,
    10000,
    45000,
    58000,
    39000,
    14000,
    22000,
    93000,
    119000,
    82000,
    32000
  )
)

# Reported cases per week (three series)
dat_weekly <- data.frame(
  week = factor(rep(1:12, times = 3), levels = 1:12),
  sex = factor(rep(sex_levels, each = 12), levels = sex_levels),
  n = c(
    800,
    950,
    1100,
    1400,
    1600,
    1550,
    1300,
    1100,
    900,
    750,
    600,
    500,
    700,
    880,
    1020,
    1300,
    1480,
    1420,
    1180,
    990,
    820,
    680,
    540,
    440,
    1500,
    1830,
    2120,
    2700,
    3080,
    2970,
    2480,
    2090,
    1720,
    1430,
    1140,
    940
  )
)

# Reported cases per day of the week
dat_weekday <- data.frame(
  day = factor(
    c("Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"),
    levels = c("Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun")
  ),
  n = c(180, 165, 172, 190, 210, 95, 60)
)
```

## Apply the RO theme

`ro_gg_plot` is designed to be a complete add-on for your `ggplot2` graphs. It's more than a theme. It applies a lower-level theme (`ro_gg_theme`), and makes adjustments on top of that.

Unlike `ro_gg_theme()`, which returns a `theme()` object you add with `+` anywhere in the chain, `ro_gg_plot()` needs to see the **whole plot** (its data, geoms, and scales) to decide what to add. So it takes a complete ggplot2 object as input and returns a modified one. Build your plot fully first, save it to an object, and call `ro_gg_plot()` on that object as the very last step:

```{r basic-bar}
p_basic <- dat_cases |>
  filter(sex == "Women") |>
  ggplot(aes(x = agegroup, y = n)) +
  geom_col(fill = ro_color("hemelblauw")) +
  labs(
    title = "Cases by age group",
    subtitle = "Women only",
    x = "Age class",
    y = "Value"
  )

ro_gg_plot(p_basic)
```

::: {.alert .alert-warning}
**A note on piping**: you cannot pipe straight out of a `+` chain into `ro_gg_plot()`, because `|>` binds *tighter* than `+`. Writing

```r
ggplot(...) +
  geom_col() +
  labs(...) |>
  ro_gg_plot()
```

only applies `ro_gg_plot()` to the result of `labs(...)`, not to the whole plot (and will error, since that's not a ggplot object). Always assign the finished plot to a variable first!
:::

## Chart types

### Column chart (grouped)

```{r grouped-bar}
p_grouped <- dat_cases |>
  filter(sex != "Total") |>
  ggplot(aes(x = agegroup, y = n, fill = sex)) +
  geom_col(position = "dodge") +
  scale_fill_manual(
    values = c(Women = ro_color("robijnrood"), Men = ro_color("hemelblauw"))
  ) +
  labs(
    title = "Cases by age group and sex",
    x = "Age class",
    y = "Value",
    fill = "Sex"
  )

ro_gg_plot(p_grouped)
```

Notice that we set `fill = "Sex"` in `labs()`, but no legend title is shown: RO style never displays one, so `ro_gg_plot()` removes it automatically.

`ro_gg_plot()` also fixes up the bar/column styling to match RO style: each group of bars occupies 80% of its category slot (the remaining 20% is the gap between groups), bars within a group sit flush against each other with no gap, and bars get no outline. The x-axis ticks are moved from under each category to the boundary between groups — that's the small tick marks you can see between "0-19"/"20-39"/"40-59" above, instead of one centered under each category.

### Column chart with a discrete axis

Any discrete x-axis works the same:

```{r weekday-bar}
p_weekday <- dat_weekday |>
  ggplot(aes(x = day, y = n)) +
  geom_col(fill = ro_color("hemelblauw")) +
  labs(
    title = "Cases by day of the week",
    x = "Day",
    y = "Value"
  )

ro_gg_plot(p_weekday)
```

The group ticks sit at the boundary between each day, same as for the age-group chart above.

### Horizontal bar chart

For a horizontal bar chart, swap `x` and `y` in `aes()` and set `flip = TRUE`:

```{r horizontal-bar}
p_horizontal <- dat_cases |>
  filter(sex == "Women") |>
  ggplot(aes(x = n, y = agegroup)) +
  geom_col(fill = ro_color("hemelblauw")) +
  labs(
    title = "Cases by age group",
    subtitle = "Women only",
    x = "Value",
    y = "Age class"
  )

ro_gg_plot(p_horizontal, flip = TRUE)
```

### Line chart

```{r line}
p_line <- dat_weekly |>
  filter(sex != "Total") |>
  ggplot(aes(x = week, y = n, color = sex, group = sex)) +
  geom_line(linewidth = 1) +
  scale_color_manual(
    values = c(Women = ro_color("robijnrood"), Men = ro_color("hemelblauw"))
  ) +
  labs(
    title = "Weekly cases by sex",
    x = "Week",
    y = "Value",
    color = "Sex"
  )

ro_gg_plot(p_line)
```

`ro_gg_plot()` detects that this is not a bar/column chart, so it leaves ggplot2's default axis expansion and limits alone (a line chart usually shouldn't touch the x-axis the way a bar chart does): it only adds the thousands separator to the y-axis labels and still removes the legend title and relocates the y-axis title.

## Color palettes

`ro_gg_plot()` does not choose colors for you. You can use `ROvis.utils::ro_color()` or `ROvis.utils::ro_color_palette()` in your `scale_fill_manual()`/`scale_color_manual()` calls, exactly as you would without it:

# ```{r palettes, fig.height=3}
# ROvis.utils::ro_show_colors("categorical")
# ROvis.utils::ro_show_colors("gender_con")
# ```

## Making further changes after `ro_gg_plot()`

`ro_gg_plot()` returns a normal ggplot2 object, so you can keep adding to it with `+`. Things like colors, text sizes, legend position, `labs()`, `coord_*()`, and so on behave exactly as they would on any other plot, because later `theme()`/`labs()` calls simply override the earlier ones.

```{r post-theme-tweak}
ro_gg_plot(p_basic) +
  theme(plot.title = element_text(size = 16, face = "bold", color = "red"))
```

So, making changes on top, or overriding them completely, is no problem. However, there are some things to take into account, which we will discuss below.

### The y-axis title position is baked in, not a theme element

`ro_gg_y_title()` (called internally) blanks out the normal `axis.title.y` and replaces it with a **text grob** (text graphical object). A text grob is just a piece of text drawn at a specific position. In this case, that is at a specific y-coordinate, namely the height of the top gridline *at the moment `ro_gg_plot()` was called*. Because of this, `theme(axis.title.y = ...)` calls added afterward have no effect, because there's no axis title left to style, only that positioned grob.

Most of the time it works wonderful, but in some cases it might not. If the default position doesn't work for your plot, set `auto_set_y_title = FALSE` to disable the text grob completely. `ro_gg_plot()` then still applies the cosmetic theme, legend cleanup, and axis changes, but leaves the y-axis title as a normal, styleable `axis.title.y` element:

```{r auto-set-y-title-off}
ro_gg_plot(p_basic, auto_set_y_title = FALSE) +
  theme(axis.title.y = element_text(angle = 0, vjust = 1.05, hjust = 0))
```

### Adding layers that change the data range

Axis *limits* keep adjusting to new layers, because `ro_gg_plot()` sets `limits = ro_gg_axis`, a function ggplot2 re-evaluates against the full data range at draw time. Adding a `geom_hline()`, another `geom_col()`, etc. after `ro_gg_plot()` correctly widens the axis.

The y-axis title will not follow, though: it's the fixed coordinate described above, computed once from the top gridline at call time. If a later layer pushes the top gridline higher, the title ends up positioned too low. This is why we recommend to call `ro_gg_plot()` last, after all layers, scales, and labels have been added. If you do need to add data afterward, it's best to use `auto_set_y_title = FALSE` within `ro_gg_plot()`.

The same principle applies to legend titles: `ro_gg_plot()` blanks the legend title only for aes or layers that are present when it's called. A `scale_*()` or layer added afterwards, introduces a new legend. This legend will keep the default title, since the removal step has already run. Normally this shouldn't be an issue though. As, again, we advise to call `ro_gg_plot()` last, after all layers, scales, and labels have been added.

## Known limitations

`ro_gg_plot()` aims to automate as much as possible to avoid many manual adjustments. A few things are still manual:

* **Bar styling and tick-mark placement are fixed values.** The 80%/20% group/gap split, zero padding within a group, and the tick marks' color, width, and length are not user-configurable through `ro_gg_plot()`. If you need different values, apply `ro_gg_group_ticks()` and the `geom_col()`/`geom_bar()` parameters yourself instead.
* **Facets.** Only the first panel is used to position the y-axis title and the group ticks; multi-panel plots aren't specifically handled yet.
* **Scale detection relies on ggplot2 internals** (`plot$scales$scales`) that aren't part of the stable, exported API, so this could break in a future `ggplot2` release.

If you've already added your own `scale_y_continuous()`/`scale_x_continuous()` for the continuous axis, `ro_gg_plot()` leaves it untouched rather than overriding it.

## Using the lower-level theme function

It is possible to use the lower-level `ro_gg_theme` as a standalone theme. This does not give you the "full" set of changes, but it gives you a bit more flexibility in what you would like to add (as far as `ro_gg_plot` doesn't allow you already):

````{r low-level-theme}
p_basic <- dat_cases |>
  filter(sex == "Women") |>
  ggplot(aes(x = agegroup, y = n)) +
  geom_col(fill = ro_color("hemelblauw")) +
  labs(
    title = "Cases by age group",
    subtitle = "Women only",
    x = "Age class",
    y = "Value"
  ) +
  ro_gg_theme()

p_basic
````

As you can see, this is pretty bare bones. Typography and colors are applied, but you miss things like thousand separators, axis label positioning, tick mark positioning and bar placement. That why we recommend using `ro_gg_plot` instead: this fixes all those shortcomings. 

## Save your figure

Use `ro_gg_save()` to save your figure for offline publication:

```{r save, eval=FALSE}
ro_gg_save("p_grouped.png", ro_gg_plot(p_grouped))
```
