Fixing "Error: object 'decor' not found" in legendry and ggplot2 Nested Axis Guides
Creating hierarchical, nested axis labels in ggplot2 is one of the best ways to present grouped experimental data. However, if you are using modern guide extension packages like legendry or ggh4x alongside newer versions of ggplot2 (3.5.0+), you may run into a confusing error:
Error:
! object 'decor' not found
In this guide, we will unpack why this error occurs and walk through multiple robust solutions to achieve clean, nested axis brackets for your categorical data.
Why Does the 'decor' Not Found Error Occur?
This error typically stems from one of three issues interacting at once:
- Syntax Mismatch between
legendryandggh4x: Arguments likelevels_textandlevels_bracketsbelong toggh4x::guide_axis_nested(). Inlegendry(a newer package developed by the author ofggh4xto align withggplot2 >= 3.5.0's revamped guide system), styling is handled via primitives (such asbracket = primitive_bracket()). Passingggh4xparameters tolegendrytriggers internal rendering failures wheredecorcannot resolve. - Conflict Between
facet_wrap()and Nested Guides: You are faceting bysamplewhile also trying to nest bysamplealong the x-axis. Axis guides are calculated per panel. When combined withscales = "free_x", the guide layout builder tries to evaluate brackets across panels where categories do not span multiple positions, breaking the decoration calculation. - Interaction Factor Ordering: To display nested axis levels correctly, the innermost level (the subcategory, e.g.,
experiment) must come first, followed by the outer group (e.g.,sample). Usinginteraction(sample, experiment)inverts the hierarchy.
Solution 1: Use ggh4x::guide_axis_nested() (Single Panel)
If your goal is to have grouped brackets along a unified x-axis, remove facet_wrap() and use ggh4x::guide_axis_nested(). Note the order inside interaction(experiment, sample):
library(ggplot2)
library(dplyr)
library(ggpattern)
library(ggh4x)
# Filter subsets
df_none <- uf_vcf_m |> filter(filters == "none")
df_qual <- uf_vcf_m |> filter(filters != "none")
ggplot(df_none, aes(x = interaction(experiment, sample), y = count)) +
geom_col(
aes(fill = sample, alpha = experiment),
color = "black",
position = "dodge2"
) +
geom_col_pattern(
data = df_qual,
aes(x = interaction(experiment, sample), y = count, pattern = "stripe"),
fill = NA,
color = "black",
pattern_angle = 30,
pattern_size = 0.075,
pattern_spacing = 0.0075,
position = "dodge2"
) +
scale_alpha_manual(values = c(0.2, 0.4, 0.6, 0.8, 1)) +
scale_pattern_manual(name = "filters", values = "stripe", labels = "QUAL > 0") +
guides(
x = ggh4x::guide_axis_nested(
levels_text = list(
element_text(size = 9),
element_text(size = 11, face = "bold")
),
levels_brackets = list(
element_line(linewidth = 0.375),
element_line(linewidth = 0.125)
)
)
) +
scale_y_continuous(expand = expansion(mult = c(0, 0.05))) +
theme_classic() +
theme(
ggh4x.axis.nestline = element_line(color = "black"),
legend.position = "top",
axis.title = element_blank()
)
Solution 2: Use Modern legendry Syntax (ggplot2 >= 3.5.0)
If you prefer to stay on modern legendry, construct nested guides using guide_axis_nested() with its updated primitives. In legendry, brackets are configured using primitive_bracket() rather than levels_brackets:
library(ggplot2)
library(dplyr)
library(ggpattern)
library(legendry)
ggplot(df_none, aes(x = interaction(experiment, sample), y = count)) +
geom_col(
aes(fill = sample, alpha = experiment),
color = "black",
position = "dodge2"
) +
geom_col_pattern(
data = df_qual,
aes(x = interaction(experiment, sample), y = count),
pattern = "stripe",
fill = NA,
color = "black",
position = "dodge2"
) +
scale_alpha_manual(values = c(0.2, 0.4, 0.6, 0.8, 1)) +
guides(
x = legendry::guide_axis_nested(
delim = ".",
bracket = primitive_bracket(colour = "black", linewidth = 0.5)
)
) +
theme_classic() +
theme(
legend.position = "top",
axis.title = element_blank()
)
Solution 3: Native Facet Bottom Strips (No Guide Extensions Needed)
In many scientific workflows, faceting is preferred over nesting on a continuous axis because it visually isolates independent clones. You can create a visually nested appearance natively in ggplot2 by placing the facet strip on the bottom:
ggplot(df_none, aes(x = experiment, y = count)) +
geom_col(
aes(fill = sample, alpha = experiment),
color = "black",
position = "dodge2"
) +
geom_col_pattern(
data = df_qual,
aes(x = experiment, y = count),
pattern = "stripe",
fill = NA,
color = "black",
position = "dodge2"
) +
facet_wrap(~sample, nrow = 1, strip.position = "bottom", scales = "free_x") +
theme_classic() +
theme(
strip.placement = "outside",
strip.background = element_rect(fill = "grey90", color = "black"),
strip.text = element_text(face = "bold", size = 11),
panel.spacing = unit(0, "lines"), # Glues panels together like nested labels
axis.title.x = element_blank()
)
Summary of Best Practices
- Check package versions: If you use
ggh4xfunctions, import fromggh4x; do not mix parameters withlegendry. - Order your factor interactions: Always place the lower-level variable first:
interaction(inner_var, outer_var). - Avoid combining
scales = "free_x"with nested guide axes: Either drop faceting completely to allow brackets to render, or let facet strips represent your higher-level hierarchy.