rggplot2colorslegendfacet-wrap

Assign colors of geom_lines with multiple legends by group in a nested facet_wrap


As Stefan suggested here, splitting the legend into multiple rows on a nested facet_wrap using legendry::guide_legend_group works well.
However, the colors are inconsistent (e.g., see the left graph below), and I would like to assign them to the geom_lines and respective legends according to the analyzers within each site: shades of green for site x (SX), of blue for site Y (SY), and of orange for site z (SZ).

I can set the colors manually for a given analyte (e.g., the CCC analyte on the right graph below), but the trick is that the number of analyzers varies depending on the analytes (in my real data, there are more than 100 different analytes, distributed differently depending on the analyzers and the sites).

Question: Considering the colors provided for the analyzers (df0$color_analyzer), how to assign them per site according to the aforementioned nuances, regardless of the selected analyte?

Thanks for help

Inconsistent colors (left) and assigned colors (right): Inconsistent colors (left) and assigned colors (right):

Initial code (inconsistent colors):

library(tidyverse)
library(scales)
library(ggh4x)
library(legendry)

# select analyte
select_analyte <- "CCC"

# Combinations of facetting variables for nested panels (# https://stackoverflow.com/questions/77618900/how-to-implement-conditional-coloring-for-facet-nested-panels-in-r)
combos <- df0 |>
  distinct(qc, site, analyte) |>
  arrange(qc, site, analyte) |> 
  mutate(qc = as.character(qc)) |> 
  filter(analyte == select_analyte)

strip_background <- 
  strip_nested(
    background_x =
      elem_list_rect(
        fill = c(
          case_match(
            unique(combos$qc),
            "" ~ "grey70",
            .default = "grey70"
          ),
          case_match(
            combos$Site,
            "SX" ~ "lightgreen",
            "SY" ~ "lightblue",
            "SZ" ~ "gold",
            .default = "white"
          ))))

# disordered colors
df1<- df0 |>
  filter(analyte == select_analyte) |>
  ggplot(aes(x = year_month, y = cv, group = analyzer)) +
  ggtitle(paste0(select_analyte)) +
  facet_wrap2(
    vars(qc, site),
    ncol = 3,
    drop = FALSE,
    scales = "free_y",
    strip = strip_background
  ) +
  scale_x_discrete(expand = expansion(mult = 0.05)) +
  scale_y_continuous(
    breaks = breaks_pretty(n=4)
  ) +
  geom_line(
    # Add the subgroup name and the seperator = "." used to split into groups
    aes(color = paste0(site, ":.", analyzer)),
    linewidth = 1.3
  ) +
  labs(x = NULL, y = expression("cv")) +
  theme(
    plot.margin = unit(c(0.2, 0.8, 0.2, 0.5), "cm"), # trbl
    plot.title = element_text(size = 16, face = "bold", vjust = 1),
    plot.subtitle = element_text(size = 11, vjust = 1),
    panel.background = element_rect(fill = "white"),
    panel.grid.major = element_line(colour = "grey80", linewidth = 0.3),
    panel.border = element_rect(colour = "grey10", fill = NA, linewidth = 0.4),
    panel.spacing.x = unit(0.6, "cm"),
    panel.spacing.y = unit(0.3, "cm"),
    strip.background = element_rect(color = "black", linetype = "solid"),
    strip.text.x = element_text(size = 13, color = "black"),
    strip.text.y = element_text(size = 13, color = "black", angle = -90),
    axis.title.x = element_text(vjust = -0.5, size = 13),
    axis.title.y = element_text(vjust = 4.8, size = 13),
    axis.text.x = element_text(
      margin = margin(b = 0.1),
      hjust = 1.12, vjust = 1.05, colour = "black", size = 10, angle = 60
    ),
    axis.text.y = element_text(
      margin = margin(l = -5, r = 2.5),
      colour = "black", size = 10
    ),
    legend.margin = margin(),
    legend.position = "top",
    legend.justification = "left",
    legend.text = element_text(size = 12),
    legend.direction = "vertical"
  ) +
  coord_cartesian(clip = "off") +
  guides(colour = guide_legend_group(
    key = key_group_split(sep = "\\."),
    override.aes = list(linewidth = 4),
    nrow = 1,
    title = NULL
  )) +
  theme(
    legendry.legend.subtitle.position = "left",
    legendry.group.spacing = unit(0, "cm"),
    legendry.legend.subtitle = element_text(
      size = 12 # or rel(1)
    )
  )
df1

Data:

df0 <-
structure(list(site = c("SX", "SX", "SX", "SX", "SX", "SX", "SX", 
"SX", "SX", "SX", "SX", "SX", "SX", "SX", "SX", "SX", "SX", "SX", 
"SX", "SX", "SX", "SX", "SX", "SX", "SX", "SX", "SX", "SX", "SX", 
"SX", "SX", "SX", "SX", "SX", "SX", "SX", "SX", "SX", "SX", "SX", 
"SX", "SX", "SX", "SX", "SX", "SX", "SX", "SX", "SX", "SX", "SX", 
"SX", "SX", "SX", "SX", "SX", "SX", "SX", "SX", "SX", "SX", "SX", 
"SX", "SX", "SX", "SX", "SX", "SX", "SX", "SX", "SX", "SX", "SX", 
"SX", "SX", "SX", "SX", "SX", "SX", "SX", "SX", "SX", "SX", "SX", 
"SX", "SX", "SX", "SY", "SY", "SY", "SY", "SY", "SY", "SY", "SY", 
"SY", "SY", "SY", "SY", "SY", "SY", "SY", "SY", "SY", "SY", "SY", 
"SY", "SY", "SY", "SY", "SY", "SY", "SY", "SY", "SY", "SY", "SY", 
"SY", "SY", "SY", "SY", "SY", "SY", "SY", "SY", "SY", "SY", "SY", 
"SY", "SZ", "SZ", "SZ", "SZ", "SZ", "SZ", "SZ", "SZ", "SZ", "SZ", 
"SZ", "SZ", "SZ", "SZ", "SZ", "SZ", "SZ", "SZ", "SZ", "SZ", "SZ", 
"SZ", "SZ", "SZ", "SZ", "SZ", "SZ", "SZ", "SZ", "SZ", "SZ", "SZ", 
"SZ", "SZ", "SZ", "SZ", "SZ", "SZ", "SZ", "SZ", "SZ", "SZ", "SZ", 
"SZ", "SZ", "SZ", "SZ", "SZ", "SZ", "SZ", "SZ", "SZ", "SZ", "SZ", 
"SZ", "SZ", "SZ", "SZ", "SZ", "SZ", "SZ", "SZ", "SZ", "SZ", "SZ", 
"SZ"), analyte = c("AAA", "AAA", "AAA", "AAA", "AAA", "AAA", 
"AAA", "AAA", "AAA", "AAA", "AAA", "AAA", "CCC", "CCC", "CCC", 
"CCC", "CCC", "CCC", "CCC", "CCC", "CCC", "CCC", "CCC", "CCC", 
"CCC", "CCC", "CCC", "CCC", "CCC", "CCC", "CCC", "CCC", "CCC", 
"CCC", "CCC", "CCC", "CCC", "CCC", "CCC", "CCC", "CCC", "CCC", 
"CCC", "CCC", "CCC", "CCC", "CCC", "CCC", "CCC", "CCC", "CCC", 
"CCC", "CCC", "CCC", "CCC", "CCC", "CCC", "CCC", "CCC", "CCC", 
"BBB", "BBB", "BBB", "BBB", "BBB", "BBB", "BBB", "BBB", "BBB", 
"BBB", "BBB", "BBB", "BBB", "BBB", "BBB", "BBB", "BBB", "BBB", 
"BBB", "BBB", "BBB", "BBB", "BBB", "BBB", "BBB", "BBB", "BBB", 
"CCC", "CCC", "CCC", "CCC", "CCC", "CCC", "CCC", "CCC", "CCC", 
"CCC", "CCC", "CCC", "CCC", "CCC", "CCC", "CCC", "CCC", "CCC", 
"CCC", "CCC", "CCC", "CCC", "CCC", "CCC", "BBB", "BBB", "BBB", 
"BBB", "BBB", "BBB", "BBB", "BBB", "BBB", "BBB", "BBB", "BBB", 
"BBB", "BBB", "BBB", "BBB", "BBB", "BBB", "CCC", "CCC", "CCC", 
"CCC", "CCC", "CCC", "CCC", "CCC", "CCC", "CCC", "CCC", "CCC", 
"CCC", "CCC", "CCC", "CCC", "CCC", "CCC", "CCC", "CCC", "CCC", 
"CCC", "CCC", "CCC", "CCC", "CCC", "CCC", "CCC", "CCC", "CCC", 
"CCC", "CCC", "CCC", "CCC", "CCC", "CCC", "CCC", "CCC", "CCC", 
"CCC", "CCC", "CCC", "CCC", "CCC", "CCC", "CCC", "CCC", "CCC", 
"BBB", "BBB", "BBB", "BBB", "BBB", "BBB", "BBB", "BBB", "BBB", 
"BBB", "BBB", "BBB", "BBB", "BBB", "BBB", "BBB", "BBB", "BBB"
), qc = c("QC_A1", "QC_A2", "QC_A1", "QC_A2", "QC_A1", "QC_A2", 
"QC_A1", "QC_A2", "QC_A1", "QC_A2", "QC_A1", "QC_A2", "QC_C1", 
"QC_C2", "QC_C3", "QC_C4", "QC_C1", "QC_C2", "QC_C3", "QC_C4", 
"QC_C1", "QC_C2", "QC_C3", "QC_C4", "QC_C1", "QC_C2", "QC_C3", 
"QC_C4", "QC_C1", "QC_C2", "QC_C3", "QC_C4", "QC_C1", "QC_C2", 
"QC_C3", "QC_C4", "QC_C1", "QC_C2", "QC_C3", "QC_C4", "QC_C1", 
"QC_C2", "QC_C3", "QC_C4", "QC_C1", "QC_C2", "QC_C3", "QC_C4", 
"QC_C1", "QC_C2", "QC_C3", "QC_C4", "QC_C1", "QC_C2", "QC_C3", 
"QC_C4", "QC_C1", "QC_C2", "QC_C3", "QC_C4", "QC_B1", "QC_B2", 
"QC_B3", "QC_B1", "QC_B2", "QC_B3", "QC_B1", "QC_B2", "QC_B3", 
"QC_B1", "QC_B2", "QC_B3", "QC_B1", "QC_B2", "QC_B3", "QC_B1", 
"QC_B2", "QC_B3", "QC_B1", "QC_B2", "QC_B3", "QC_B1", "QC_B2", 
"QC_B3", "QC_B1", "QC_B2", "QC_B3", "QC_C1", "QC_C2", "QC_C3", 
"QC_C4", "QC_C1", "QC_C2", "QC_C3", "QC_C4", "QC_C1", "QC_C2", 
"QC_C3", "QC_C4", "QC_C1", "QC_C2", "QC_C3", "QC_C4", "QC_C1", 
"QC_C2", "QC_C3", "QC_C4", "QC_C1", "QC_C2", "QC_C3", "QC_C4", 
"QC_B1", "QC_B2", "QC_B3", "QC_B1", "QC_B2", "QC_B3", "QC_B1", 
"QC_B2", "QC_B3", "QC_B1", "QC_B2", "QC_B3", "QC_B1", "QC_B2", 
"QC_B3", "QC_B1", "QC_B2", "QC_B3", "QC_C1", "QC_C2", "QC_C3", 
"QC_C4", "QC_C1", "QC_C2", "QC_C3", "QC_C4", "QC_C1", "QC_C2", 
"QC_C3", "QC_C4", "QC_C1", "QC_C2", "QC_C3", "QC_C4", "QC_C1", 
"QC_C2", "QC_C3", "QC_C4", "QC_C1", "QC_C2", "QC_C3", "QC_C4", 
"QC_C1", "QC_C2", "QC_C3", "QC_C4", "QC_C1", "QC_C2", "QC_C3", 
"QC_C4", "QC_C1", "QC_C2", "QC_C3", "QC_C4", "QC_C1", "QC_C2", 
"QC_C3", "QC_C4", "QC_C1", "QC_C2", "QC_C3", "QC_C4", "QC_C1", 
"QC_C2", "QC_C3", "QC_C4", "QC_B1", "QC_B2", "QC_B3", "QC_B1", 
"QC_B2", "QC_B3", "QC_B1", "QC_B2", "QC_B3", "QC_B1", "QC_B2", 
"QC_B3", "QC_B1", "QC_B2", "QC_B3", "QC_B1", "QC_B2", "QC_B3"
), analyzer = c("X1_C", "X1_C", "X3_C", "X3_C", "X1_C", "X1_C", 
"X3_C", "X3_C", "X1_C", "X1_C", "X3_C", "X3_C", "X1_B1", "X1_B1", 
"X1_B1", "X1_B1", "X1_B2", "X1_B2", "X1_B2", "X1_B2", "X2_B", 
"X2_B", "X2_B", "X2_B", "X3_B", "X3_B", "X3_B", "X3_B", "X1_B1", 
"X1_B1", "X1_B1", "X1_B1", "X1_B2", "X1_B2", "X1_B2", "X1_B2", 
"X2_B", "X2_B", "X2_B", "X2_B", "X3_B", "X3_B", "X3_B", "X3_B", 
"X1_B1", "X1_B1", "X1_B1", "X1_B1", "X1_B2", "X1_B2", "X1_B2", 
"X1_B2", "X2_B", "X2_B", "X2_B", "X2_B", "X3_B", "X3_B", "X3_B", 
"X3_B", "X1_C", "X1_C", "X1_C", "X2_C", "X2_C", "X2_C", "X3_C", 
"X3_C", "X3_C", "X1_C", "X1_C", "X1_C", "X2_C", "X2_C", "X2_C", 
"X3_C", "X3_C", "X3_C", "X1_C", "X1_C", "X1_C", "X2_C", "X2_C", 
"X2_C", "X3_C", "X3_C", "X3_C", "Y1_B", "Y1_B", "Y1_B", "Y1_B", 
"Y2_B", "Y2_B", "Y2_B", "Y2_B", "Y1_B", "Y1_B", "Y1_B", "Y1_B", 
"Y2_B", "Y2_B", "Y2_B", "Y2_B", "Y1_B", "Y1_B", "Y1_B", "Y1_B", 
"Y2_B", "Y2_B", "Y2_B", "Y2_B", "Y1_C", "Y1_C", "Y1_C", "Y2_C", 
"Y2_C", "Y2_C", "Y1_C", "Y1_C", "Y1_C", "Y2_C", "Y2_C", "Y2_C", 
"Y1_C", "Y1_C", "Y1_C", "Y2_C", "Y2_C", "Y2_C", "Z1_B1", "Z1_B1", 
"Z1_B1", "Z1_B1", "Z1_B2", "Z1_B2", "Z1_B2", "Z1_B2", "Z2_B1", 
"Z2_B1", "Z2_B1", "Z2_B1", "Z2_B2", "Z2_B2", "Z2_B2", "Z2_B2", 
"Z1_B1", "Z1_B1", "Z1_B1", "Z1_B1", "Z1_B2", "Z1_B2", "Z1_B2", 
"Z1_B2", "Z2_B1", "Z2_B1", "Z2_B1", "Z2_B1", "Z2_B2", "Z2_B2", 
"Z2_B2", "Z2_B2", "Z1_B1", "Z1_B1", "Z1_B1", "Z1_B1", "Z1_B2", 
"Z1_B2", "Z1_B2", "Z1_B2", "Z2_B1", "Z2_B1", "Z2_B1", "Z2_B1", 
"Z2_B2", "Z2_B2", "Z2_B2", "Z2_B2", "Z1_C", "Z1_C", "Z1_C", "Z2_C", 
"Z2_C", "Z2_C", "Z1_C", "Z1_C", "Z1_C", "Z2_C", "Z2_C", "Z2_C", 
"Z1_C", "Z1_C", "Z1_C", "Z2_C", "Z2_C", "Z2_C"), year_month = c("2025-09", 
"2025-09", "2025-09", "2025-09", "2025-10", "2025-10", "2025-10", 
"2025-10", "2025-11", "2025-11", "2025-11", "2025-11", "2025-09", 
"2025-09", "2025-09", "2025-09", "2025-09", "2025-09", "2025-09", 
"2025-09", "2025-09", "2025-09", "2025-09", "2025-09", "2025-09", 
"2025-09", "2025-09", "2025-09", "2025-10", "2025-10", "2025-10", 
"2025-10", "2025-10", "2025-10", "2025-10", "2025-10", "2025-10", 
"2025-10", "2025-10", "2025-10", "2025-10", "2025-10", "2025-10", 
"2025-10", "2025-11", "2025-11", "2025-11", "2025-11", "2025-11", 
"2025-11", "2025-11", "2025-11", "2025-11", "2025-11", "2025-11", 
"2025-11", "2025-11", "2025-11", "2025-11", "2025-11", "2025-09", 
"2025-09", "2025-09", "2025-09", "2025-09", "2025-09", "2025-09", 
"2025-09", "2025-09", "2025-10", "2025-10", "2025-10", "2025-10", 
"2025-10", "2025-10", "2025-10", "2025-10", "2025-10", "2025-11", 
"2025-11", "2025-11", "2025-11", "2025-11", "2025-11", "2025-11", 
"2025-11", "2025-11", "2025-09", "2025-09", "2025-09", "2025-09", 
"2025-09", "2025-09", "2025-09", "2025-09", "2025-10", "2025-10", 
"2025-10", "2025-10", "2025-10", "2025-10", "2025-10", "2025-10", 
"2025-11", "2025-11", "2025-11", "2025-11", "2025-11", "2025-11", 
"2025-11", "2025-11", "2025-09", "2025-09", "2025-09", "2025-09", 
"2025-09", "2025-09", "2025-10", "2025-10", "2025-10", "2025-10", 
"2025-10", "2025-10", "2025-11", "2025-11", "2025-11", "2025-11", 
"2025-11", "2025-11", "2025-09", "2025-09", "2025-09", "2025-09", 
"2025-09", "2025-09", "2025-09", "2025-09", "2025-09", "2025-09", 
"2025-09", "2025-09", "2025-09", "2025-09", "2025-09", "2025-09", 
"2025-10", "2025-10", "2025-10", "2025-10", "2025-10", "2025-10", 
"2025-10", "2025-10", "2025-10", "2025-10", "2025-10", "2025-10", 
"2025-10", "2025-10", "2025-10", "2025-10", "2025-11", "2025-11", 
"2025-11", "2025-11", "2025-11", "2025-11", "2025-11", "2025-11", 
"2025-11", "2025-11", "2025-11", "2025-11", "2025-11", "2025-11", 
"2025-11", "2025-11", "2025-09", "2025-09", "2025-09", "2025-09", 
"2025-09", "2025-09", "2025-10", "2025-10", "2025-10", "2025-10", 
"2025-10", "2025-10", "2025-11", "2025-11", "2025-11", "2025-11", 
"2025-11", "2025-11"), cv = c(2.64, 3.83, 2.66, 3.11, 1.63, 1.82, 
3.74, 3.81, 1.46, 2.07, 3.78, 3.17, 1.27, 1.05, 1.82, 2.84, 1.12, 
0.97, 1.36, 2.81, 1.16, 2.51, 1.66, 3.24, 1.03, 0.78, 1.11, 2.92, 
1.82, 0.87, 1.46, 2.56, 1.61, 1.24, 1.3, 2.45, 1.68, 1.69, 1.33, 
2.24, 2.11, 0.74, 1.38, 2.33, 1.14, 0.96, 1.23, 3.05, 1.34, 1.01, 
1.09, 2.35, 1.35, 1.06, 1.08, 2.2, 2.22, 0.68, 1.09, 2.56, 3.92, 
2.66, 2.59, 2.38, 2.83, 1.95, 2.26, 1.58, 1.68, 2.59, 1.61, 1.56, 
5.78, 2.05, 1.87, 3.68, 3.65, 1.21, 4.99, 1.23, 1.02, 2.63, 1.79, 
1.13, 2.21, 1.22, 1.37, 1.05, 1.17, 1.05, 4.82, 1.88, 1.07, 1.17, 
4.59, 2.64, 1.55, 1.87, 4.17, 1.87, 1.33, 1.38, 4.74, 1.51, 1.32, 
1.29, 4.05, 1.78, 1.73, 1.43, 3.86, 4.99, 4.02, 4.12, 3.09, 1.32, 
1.39, 4.52, 2.11, 2.97, 5.48, 2.31, 2.85, 2.89, 1.41, 1.31, 2.79, 
1.49, 1.51, 1.35, 1.37, 1.48, 2.91, 1.21, 1.25, 1.25, 2.93, 1.31, 
1.55, 1.41, 3.19, 1.34, 4.05, 1.36, 2.64, 1.24, 1.46, 1.42, 2.82, 
1.78, 1.41, 1.52, 3.01, 2.24, 1.37, 1.84, 3.95, 1.53, 1.21, 1.21, 
3.76, 2.49, 1.55, 1.26, 2.93, 2.39, 1.56, 1.26, 2.52, 2.06, 0.94, 
1.19, 2.25, 1.73, 1.02, 1.31, 2.72, 2.24, 1.08, 2.09, 5.57, 3.65, 
3.56, 3.19, 2.45, 2.14, 3.2, 1.8, 2.13, 2.56, 1.23, 1.21, 2.62, 
1.44, 1.37), color_analyzer = c("greenyellow", "greenyellow", 
"green4", "green4", "greenyellow", "greenyellow", "green4", "green4", 
"greenyellow", "greenyellow", "green4", "green4", "greenyellow", 
"greenyellow", "greenyellow", "greenyellow", "green2", "green2", 
"green2", "green2", "green4", "green4", "green4", "green4", "darkgreen", 
"darkgreen", "darkgreen", "darkgreen", "greenyellow", "greenyellow", 
"greenyellow", "greenyellow", "green2", "green2", "green2", "green2", 
"green4", "green4", "green4", "green4", "darkgreen", "darkgreen", 
"darkgreen", "darkgreen", "greenyellow", "greenyellow", "greenyellow", 
"greenyellow", "green2", "green2", "green2", "green2", "green4", 
"green4", "green4", "green4", "darkgreen", "darkgreen", "darkgreen", 
"darkgreen", "greenyellow", "greenyellow", "greenyellow", "green2", 
"green2", "green2", "green4", "green4", "green4", "greenyellow", 
"greenyellow", "greenyellow", "green2", "green2", "green2", "green4", 
"green4", "green4", "greenyellow", "greenyellow", "greenyellow", 
"green2", "green2", "green2", "green4", "green4", "green4", "dodgerblue1", 
"dodgerblue1", "dodgerblue1", "dodgerblue1", "dodgerblue1", "dodgerblue1", 
"dodgerblue1", "dodgerblue1", "dodgerblue1", "dodgerblue1", "dodgerblue1", 
"dodgerblue1", "dodgerblue1", "dodgerblue1", "dodgerblue1", "dodgerblue1", 
"dodgerblue1", "dodgerblue1", "dodgerblue1", "dodgerblue1", "dodgerblue1", 
"dodgerblue1", "dodgerblue1", "dodgerblue1", "blue", "blue", 
"blue", "blue", "blue", "blue", "blue", "blue", "blue", "blue", 
"blue", "blue", "blue", "blue", "blue", "blue", "blue", "blue", 
"gold", "gold", "gold", "gold", "orange", "orange", "orange", 
"orange", "darkorange2", "darkorange2", "darkorange2", "darkorange2", 
"orangered4", "orangered4", "orangered4", "orangered4", "gold", 
"gold", "gold", "gold", "orange", "orange", "orange", "orange", 
"darkorange2", "darkorange2", "darkorange2", "darkorange2", "orangered4", 
"orangered4", "orangered4", "orangered4", "gold", "gold", "gold", 
"gold", "orange", "orange", "orange", "orange", "darkorange2", 
"darkorange2", "darkorange2", "darkorange2", "orangered4", "orangered4", 
"orangered4", "orangered4", "gold", "gold", "gold", "orange2", 
"orange2", "orange2", "gold", "gold", "gold", "orange2", "orange2", 
"orange2", "gold", "gold", "gold", "orange2", "orange2", "orange2"
)), row.names = c(NA, -195L), class = c("tbl_df", "tbl", "data.frame"
))

Solution

  • You can use scale_color_manual with an appropriately defined named color vector:

    library(tidyverse)
    library(scales)
    library(ggh4x)
    library(legendry)
    
    # Named color vector
    pal_color <- df0 |>
      distinct(
        site, analyzer, color_analyzer
      ) |>
      unite(
        "site_analyzer", site, analyzer,
        sep = ":."
      ) |> 
      deframe()
    # Looks like color assignment was wrong
    pal_color["SY:.Y2_B"] <- "blue"
    pal_color["SY:.Y1_C "] <- "dodgerblue1"
    
    # disordered colors
    df0 |>
      filter(analyte == select_analyte) |>
      ggplot(aes(x = year_month, y = cv, group = analyzer)) +
      ggtitle(paste0(select_analyte)) +
      facet_wrap2(
        vars(qc, site),
        ncol = 3,
        drop = FALSE,
        scales = "free_y",
        strip = strip_background
      ) +
      scale_x_discrete(expand = expansion(mult = 0.05)) +
      scale_y_continuous(
        breaks = breaks_pretty(n = 4)
      ) +
      scale_color_manual(
        values = pal_color
      ) +
      geom_line(
        # Add the subgroup name and the seperator = "." used to split into groups
        aes(color = paste0(site, ":.", analyzer)),
        linewidth = 1.3
      ) +
      labs(x = NULL, y = expression("cv")) +
      theme(
        plot.margin = unit(c(0.2, 0.8, 0.2, 0.5), "cm"), # trbl
        plot.title = element_text(size = 16, face = "bold", vjust = 1),
        plot.subtitle = element_text(size = 11, vjust = 1),
        panel.background = element_rect(fill = "white"),
        panel.grid.major = element_line(colour = "grey80", linewidth = 0.3),
        panel.border = element_rect(colour = "grey10", fill = NA, linewidth = 0.4),
        panel.spacing.x = unit(0.6, "cm"),
        panel.spacing.y = unit(0.3, "cm"),
        strip.background = element_rect(color = "black", linetype = "solid"),
        strip.text.x = element_text(size = 13, color = "black"),
        strip.text.y = element_text(size = 13, color = "black", angle = -90),
        axis.title.x = element_text(vjust = -0.5, size = 13),
        axis.title.y = element_text(vjust = 4.8, size = 13),
        axis.text.x = element_text(
          margin = margin(b = 0.1),
          hjust = 1.12, vjust = 1.05, colour = "black", size = 10, angle = 60
        ),
        axis.text.y = element_text(
          margin = margin(l = -5, r = 2.5),
          colour = "black", size = 10
        ),
        legend.margin = margin(),
        legend.position = "top",
        legend.justification = "left",
        legend.text = element_text(size = 12),
        legend.direction = "vertical"
      ) +
      coord_cartesian(clip = "off") +
      guides(colour = guide_legend_group(
        key = key_group_split(sep = "\\."),
        override.aes = list(linewidth = 4),
        nrow = 1,
        title = NULL
      )) +
      theme(
        legendry.legend.subtitle.position = "left",
        legendry.group.spacing = unit(0, "cm"),
        legendry.legend.subtitle = element_text(
          size = 12 # or rel(1)
        )
      )
    

    enter image description here