How to Fix Nested Axis Label Alignment with geom_chicklet and legendry in ggplot2
The Challenge: Aligning Nested Labels in Horizontal Chicklet Charts
Creating highly customized, publication-ready visualizations in R often requires combining specialized packages. A common hurdle arises when pairing ggchicklet (used for elegant, rounded-corner bar plots) with legendry (used to create nested axis brackets like guide_axis_nested).
When creating a horizontal bar plot, many developers historically relied on coord_flip(). However, using coord_flip() with nested axes often breaks label alignment, leaving text shifted or uncentered. This happens because legendry relies on native coordinate systems to calculate nested bracket positioning, and coordinate flipping disrupts this layout engine.
The Root Cause
In older versions of ggplot2, coord_flip() was the only way to make bar charts horizontal. Today, ggplot2 natively supports horizontal orientation by swapping the x and y aesthetic mappings.
While some older documentation suggests geom_chicklet doesn't support swapped aesthetics, modern versions of ggchicklet fully support native horizontal rendering. By mapping your categorical variables directly to y and your numeric values to x, you can completely eliminate the need for coord_flip(). This allows legendry to calculate bracket positions accurately.
The Solution: Native Aesthetic Mapping
To resolve the alignment issue, we will map our variables natively and remove coord_flip(). Here is the corrected, optimized code utilizing your dataset:
library(ggplot2)
library(ggchicklet)
library(legendry)
library(ggpubr)
library(ggsci)
# Your provided dataset
excel <- structure(list(
variant_type = structure(c(1L, 1L, 1L, 1L, 2L, 2L, 2L, 2L, 3L, 3L, 3L, 3L, 1L, 1L, 1L, 1L, 2L, 2L, 2L, 2L, 3L, 3L, 3L, 3L),
levels = c("all", "SNVs", "INDELs"), class = "factor"),
sample = structure(c(1L, 2L, 3L, 4L, 1L, 2L, 3L, 4L, 1L, 2L, 3L, 4L, 1L, 2L, 3L, 4L, 1L, 2L, 3L, 4L, 1L, 2L, 3L, 4L),
levels = c("clone0_24h", "clone4_12w", "clone5_12w", "clone7_12w"), class = "factor"),
count = c(3632307, 3610039, 3639793, 3627187, 2947134, 2962191, 2935353, 2934755, 684923, 647589, 704196, 692176, 2351557, 2322460, 2383840, 2347099, 1794417, 1809360, 1792501, 1776124, 556995, 512975, 591205, 570849),
reference = c("CHM13", "CHM13", "CHM13", "CHM13", "CHM13", "CHM13", "CHM13", "CHM13", "CHM13", "CHM13", "CHM13", "CHM13", "hap1", "hap1", "hap1", "hap1", "hap1", "hap1", "hap1", "hap1", "hap1", "hap1", "hap1", "hap1"),
row.names = c(NA, -24L), class = c("tbl_df", "tbl", "data.frame"))
# Plotting with swapped aesthetics directly
ggplot(excel, aes(y = interaction(sample, reference), x = count, fill = variant_type)) +
theme_classic2() +
# ggplot2 automatically detects horizontal orientation based on x/y mapping
geom_chicklet(position = "dodge2", radius = grid::unit(1.5, "mm")) +
facet_wrap(~reference, strip.position = "left", ncol = 1, scales = "free_y") +
theme(
legend.title = element_text(face = 'italic', size = 16),
legend.position = 'bottom',
panel.grid = element_blank(),
axis.text.y = element_text(face = 'bold'),
axis.title.x = element_blank(),
axis.title.y = element_blank(),
strip.background.y = element_blank(),
strip.text.y = element_blank(),
legend.text = element_text(size = 12),
axis.title = element_text(size = 14),
axis.text = element_text(size = 10)
) +
guides(
# Adjusting angle to 0 degrees keeps nested labels readable horizontally
y = guide_axis_nested(
levels_text = list(element_text(angle = 0, hjust = 0.5), element_text()),
levels_brackets = list(element_line(linewidth = .25), element_line(linewidth = .125))
),
fill = guide_legend(
title = 'variants',
title.position = 'top',
title.hjust = .5,
position = "bottom"
)
) +
scale_fill_npg() +
xlab(NULL) +
ylab(NULL)
Key Adjustments Explained
- Removed
coord_flip(): We swapped the aesthetics directly within theaes()call (y = interaction(...)andx = count). - Native Orientation: Modern versions of
geom_chickletautomatically detect the horizontal orientation when numerical data is mapped to the X-axis and categorical data to the Y-axis. - Text Angle Adjustment: Changed the Y-axis nested text angle from
90to0withinguide_axis_nested(). Since we are no longer flipping coordinates post-rendering, keeping the text at 0 degrees ensures it reads perfectly from left to right next to the brackets.