vbracket
January 26, 2026 · View on GitHub
Custom legends with statistical comparison brackets for ggplot2
Overview
vbracket is an R package that adds publication-quality custom legends with vertical brackets to ggplot2 plots. It's designed for displaying statistical comparisons between groups, commonly used in scientific publications for showing significance levels.
Features
- ✅ Easy Integration - Works seamlessly with ggplot2 using the
+operator - ✅ Flexible Comparisons - Support for asterisks (*, **, ***), p-values (p<0.001), or custom labels
- ✅ Adaptive Positioning - Automatically adjusts to different plot sizes (4×3, 6×4, 8×6 inches)
- ✅ Smart Layout - Automatically detects and spaces overlapping brackets
- ✅ Font Support - Auto-inherits font family from ggplot2 theme (sans, serif, mono)
- ✅ Precise Positioning - Uses actual rendered text width for accurate bracket placement
- ✅ Publication Ready - Works with regular
ggsave()for easy figure export - ✅ Auto-Scaling - Legend symbol size and spacing scale automatically with text_size (v1.1.0)
- ✅ Manual Override - Fine-tune line_length, line_width, item_spacing individually (v1.2.0)
- ✅ Bracket Layer Control - Adjust spacing between bracket layers with bracket_layer_spacing (v1.3.0)
Installation
From GitHub
# Install devtools if needed
install.packages("devtools")
# Install vbracket
devtools::install_github("h20gg702/vbracket")
From Source
# Clone the repository and source the files
source("R/helpers.R")
source("R/custom_legend_with_brackets.R")
source("R/annotation_grob.R")
source("R/ggplot_integration.R")
source("R/print_method.R")
source("R/ggsave_vbracket.R")
Quick Start
library(ggplot2)
library(vbracket)
# Prepare sample data
set.seed(42)
days <- c(0, 5, 10, 15, 20, 25)
groups <- c("WT", "WT/Dox", "CH3+5", "CH3+5/Dox", "PUMA-KO", "PUMA-KO/Dox")
colors <- c("WT" = "#00AA00", "WT/Dox" = "#FF4400", "CH3+5" = "#00AAFF",
"CH3+5/Dox" = "#0000CC", "PUMA-KO" = "#AA7700", "PUMA-KO/Dox" = "#AA00FF")
df_line <- do.call(rbind, lapply(groups, function(grp) {
if (grp == "WT/Dox") {
mean_values <- c(0, 50, 150, 200, 500, 950)
} else if (grp == "WT") {
mean_values <- c(0, 50, 100, 350, 900, 2100)
} else if (grp == "CH3+5") {
mean_values <- c(0, 45, 95, 380, 950, 1900)
} else if (grp == "CH3+5/Dox") {
mean_values <- c(0, 48, 105, 360, 920, 2000)
} else if (grp == "PUMA-KO") {
mean_values <- c(0, 52, 110, 370, 880, 2150)
} else {
mean_values <- c(0, 50, 100, 355, 910, 2200)
}
sd_values <- mean_values * 0.08 + 10
data.frame(days = days, volume = mean_values, sd = sd_values, group = grp)
}))
# Define statistical comparisons
# Note: These group comparisons are examples - replace with your own groups
comparisons <- add_bracket_comparisons(
groups1 = c("WT/Dox", "CH3+5/Dox"), # First groups to compare
groups2 = c("CH3+5", "PUMA-KO"), # Second groups to compare
labels = c("*", "*") # Significance labels
)
# Create your plot
p <- ggplot(df_line, aes(x = days, y = volume, color = group, group = group)) +
geom_line(linewidth = 1.2) +
geom_point(size = 3) +
geom_errorbar(aes(ymin = volume - sd, ymax = volume + sd), width = 1.2, linewidth = 0.8) +
scale_color_manual(values = colors) +
scale_x_continuous(breaks = c(0, 5, 10, 15, 20, 25), limits = c(0, 27)) +
scale_y_continuous(breaks = seq(0, 2500, 500), limits = c(0, 2500)) +
labs(title = expression(paste("HCT116 Sh ", italic("WRN"))),
x = "Days",
y = expression(paste("Tumor Volume (mm"^3, ")"))) +
theme_classic(base_size = 14) +
theme(plot.title = element_text(hjust = 0.5, size = 16, face = "bold"),
legend.position = "none") +
# Add vbracket legend
legend_bracket(
labels = groups,
colors = colors,
comparisons = comparisons,
position = "topleft",
output_width = 6,
output_height = 4
)
# Save with regular ggsave
ggsave("plot.png", p, width = 6, height = 4, dpi = 300)

Examples
Note: The following examples use tumor volume data with specific group names (WT, WT/Dox, CH3+5, etc.) for demonstration purposes. Replace these with your own data and group names when using vbracket.
Example 1: Basic Usage - Two Comparisons
# Two comparisons between groups
comparisons <- add_bracket_comparisons(
groups1 = c("WT/Dox", "CH3+5/Dox"),
groups2 = c("CH3+5", "PUMA-KO"),
labels = c("*", "*")
)
p <- ggplot(df_line, aes(x = days, y = volume, color = group, group = group)) +
geom_line(linewidth = 1.2) +
geom_point(size = 3) +
geom_errorbar(aes(ymin = volume - sd, ymax = volume + sd), width = 1.2, linewidth = 0.8) +
scale_color_manual(values = colors) +
theme_classic(base_size = 14) +
theme(legend.position = "none") +
legend_bracket(labels = groups, colors = colors, comparisons = comparisons,
position = "topleft", output_width = 6, output_height = 4)
ggsave("example1_basic.png", p, width = 6, height = 4, dpi = 300)

Example 2: Multiple Asterisk Symbols
Show different significance levels using asterisks:
comparisons <- add_bracket_comparisons(
groups1 = c("WT/Dox", "CH3+5/Dox", "PUMA-KO"),
groups2 = c("CH3+5", "PUMA-KO", "PUMA-KO/Dox"),
labels = c("*", "**", "***") # *, **, *** for different p-values
)

Example 3: P-value Notation
Use p-value text instead of asterisks:
comparisons <- add_bracket_comparisons(
groups1 = c("WT/Dox", "CH3+5/Dox"),
groups2 = c("CH3+5", "PUMA-KO"),
labels = c("p<0.001", "ns") # Explicit p-values or "ns" for not significant
)

Example 4: Different Legend Positions
vbracket supports four preset positions:
# Try different positions: "topleft", "topright", "bottomleft", "bottomright"
p + legend_bracket(labels, colors, comparisons,
position = "topright", # Change position here
output_width = 6, output_height = 4)
| Top Left | Top Right |
|---|---|
![]() | ![]() |
| Bottom Left | Bottom Right |
|---|---|
![]() | ![]() |
Example 5: Custom Bracket Margin
Adjust the space between legend text and brackets:
# Smaller margin (0.05) - brackets closer to text
p + legend_bracket(labels, colors, comparisons,
position = "topleft",
bracket_margin = 0.05, # Adjust this value
output_width = 6, output_height = 4)
| Margin 0.05 | Margin 0.15 | Margin 0.30 |
|---|---|---|
![]() | ![]() | ![]() |
Example 6: Adaptive Positioning for Different Plot Sizes
vbracket automatically adapts to different plot dimensions:
# Small plot
p + legend_bracket(labels, colors, comparisons,
position = "topleft",
output_width = 4, output_height = 3)
ggsave("small.png", p, width = 4, height = 3)
# Medium plot
p + legend_bracket(labels, colors, comparisons,
position = "topleft",
output_width = 6, output_height = 4)
ggsave("medium.png", p, width = 6, height = 4)
# Large plot
p + legend_bracket(labels, colors, comparisons,
position = "topleft",
output_width = 8, output_height = 6)
ggsave("large.png", p, width = 8, height = 6)
| 4×3 inches | 6×4 inches | 8×6 inches |
|---|---|---|
![]() | ![]() | ![]() |
Example 7: Font Family Support
vbracket automatically inherits font family from your ggplot theme:
# Sans-serif font
p + theme_classic(base_family = "sans") +
legend_bracket(...)
# Times New Roman (recommended for scientific publications)
p + theme_classic(base_family = "Times New Roman") +
legend_bracket(...)
# Monospace font
p + theme_classic(base_family = "mono") +
legend_bracket(...)
| Sans | Times New Roman | Mono |
|---|---|---|
![]() | ![]() | ![]() |
Example 8: Custom Text Sizes
Customize legend text size and significance symbol size:
# Small text
p + legend_bracket(labels, colors, comparisons,
position = "topleft",
text_size = 8, # Legend text size
sig_size = 12, # Significance symbol size
output_width = 6, output_height = 4)
# Medium text (default)
p + legend_bracket(..., text_size = 10, sig_size = 14, ...)
# Large text
p + legend_bracket(..., text_size = 12, sig_size = 18, ...)
| Small (8/12) | Medium (10/14) | Large (12/18) |
|---|---|---|
![]() | ![]() | ![]() |
Example 9: Manual Override for Legend Symbol and Spacing (v1.2.0)
By default, legend symbol size and item spacing scale automatically with text_size. You can override these individually:
# Default auto-scaling (text_size controls everything)
p + legend_bracket(labels, colors, comparisons,
position = "topleft",
text_size = 12,
output_width = 6, output_height = 4)
# Manual override: longer legend lines
p + legend_bracket(..., text_size = 12, line_length = 0.08)
# Manual override: thicker legend lines
p + legend_bracket(..., text_size = 12, line_width = 6)
# Manual override: more spacing between items
p + legend_bracket(..., text_size = 12, item_spacing = 0.12)
| Default (auto) | line_length = 0.08 |
|---|---|
![]() | ![]() |
| line_width = 6 | item_spacing = 0.12 |
|---|---|
![]() | ![]() |
Example 10: Bracket Layer Spacing (v1.3.0)
Control the horizontal spacing between bracket layers when multiple comparisons overlap:
# Default auto-spacing (calculated based on text size and label type)
p + legend_bracket(labels, colors, comparisons,
position = "topleft",
output_width = 6, output_height = 4)
# Manual bracket layer spacing - very tight
p + legend_bracket(..., bracket_layer_spacing = 0.02)
# Manual bracket layer spacing - very wide
p + legend_bracket(..., bracket_layer_spacing = 0.20)
| Default (auto) | Very Tight (0.02) | Very Wide (0.20) |
|---|---|---|
![]() | ![]() | ![]() |
Main Functions
add_bracket_comparisons()
Create comparison specifications for brackets.
Parameters:
groups1- Character vector of first groups to comparegroups2- Character vector of second groups to comparelabels- Character vector of significance labels (e.g., "*", "**", "p<0.05", "ns")
Returns: Data frame with comparison specifications
legend_bracket()
Add custom legend with brackets to a ggplot object.
Parameters:
labels- Character vector of group names (in order)colors- Named character vector of group colorscomparisons- Comparison data frame fromadd_bracket_comparisons()position- Preset position: "topleft", "topright", "bottomleft", "bottomright"output_width- Output figure width in inches (required for METHOD 1)output_height- Output figure height in inches (required for METHOD 1)bracket_margin- Custom spacing between text and brackets (optional)text_size- Legend text size (default: 10). Also controls auto-scaling of symbol size and spacingsig_size- Significance symbol size (default: 11)text_family- Font family (default: inherits from ggplot theme)line_length- Manual override for legend symbol line length (default: NULL = auto-scaled by text_size)line_width- Manual override for legend symbol line width (default: NULL = auto-scaled by text_size)item_spacing- Manual override for vertical spacing between legend items (default: NULL = auto-scaled by text_size)bracket_layer_spacing- Manual override for horizontal spacing between bracket layers (default: NULL = auto-calculated)
Returns: A vbracket_legend object that can be added to ggplot
Usage Methods
METHOD 1: With Output Dimensions (Recommended)
Specify output_width and output_height to use with regular ggsave():
p <- ggplot(...) +
legend_bracket(labels, colors, comparisons,
position = "topleft",
output_width = 6,
output_height = 4)
ggsave("plot.png", p, width = 6, height = 4) # Regular ggsave works!
METHOD 2: Without Output Dimensions
Use ggsave_vbracket() or print() for display:
p <- ggplot(...) +
legend_bracket(labels, colors, comparisons,
position = "topleft")
ggsave_vbracket("plot.png", p, width = 6, height = 4) # Custom save function
# OR
print(p) # Display in R/RStudio
Advanced Features
Custom Positioning
Override automatic positioning with custom coordinates:
legend_bracket(labels, colors, comparisons,
legend_x = 0.15,
legend_y = 0.85,
output_width = 6,
output_height = 4)
Custom Bracket Spacing
Adjust the space between legend text and brackets:
legend_bracket(labels, colors, comparisons,
position = "topleft",
bracket_margin = 0.10,
output_width = 6,
output_height = 4)
Font Customization
Font family is automatically inherited from ggplot theme:
p <- ggplot(...) +
theme_classic(base_family = "serif") + # vbracket inherits serif
legend_bracket(...)
Or specify manually:
legend_bracket(labels, colors, comparisons,
text_family = "serif",
text_size = 11,
sig_size = 14)
Tutorial Scripts
The package includes comprehensive tutorial scripts:
getting_started.R
Complete beginner's guide with 10 examples covering:
- Basic usage with single comparison
- Multiple asterisks (*, **, ***)
- P-value notation (p<0.001, ns)
- All legend positions
- Custom bracket margins
- Different plot sizes
- Font family support
- Custom text sizes
- Manual override (line_length, line_width, item_spacing)
- Bracket layer spacing control
Run the tutorial:
source("getting_started.R")
test_all_features.R
Comprehensive test suite with 10 feature tests:
- Basic usage
- Multiple asterisk symbols
- P-value notation
- All legend positions
- Custom positioning
- Custom bracket margin
- Adaptive positioning for different plot sizes
- Font family inheritance
- Adaptive bracket spacing
- Non-overlapping bracket detection
Run the tests:
source("test_all_features.R")
How It Works
Accurate Text Width Calculation
vbracket uses grid graphics' grobWidth() to calculate the actual rendered width of legend text, ensuring brackets are positioned precisely without overlapping:
# Instead of estimating based on character count
text_width <- max(sapply(labels, function(label) {
tg <- textGrob(label, gp = gpar(fontsize = text_size, fontfamily = text_family))
convertWidth(grobWidth(tg), "npc", valueOnly = TRUE)
}))
Adaptive Bracket Spacing
Brackets automatically adjust spacing based on:
- Label type: Asterisks use narrower spacing (0.06), text labels use wider spacing (0.10)
- Plot size: Smaller plots get relatively more spacing
- Text size: Larger text gets proportionally more spacing
Overlap Detection
When multiple brackets would overlap, vbracket automatically spaces them horizontally:
# Detects overlapping Y ranges
if (!(max_y_i < min_y_j || min_y_i > max_y_j)) {
bracket_layers[i] <- max(bracket_layers[i], bracket_layers[j] + 1)
}
Symbol Centering
Asterisks are positioned slightly lower than text symbols for better visual centering:
if (grepl("^\\*+$", label)) {
sig_y <- mid_y - (sig_size / 72) * 0.050 # Lower for asterisks
} else {
sig_y <- mid_y + (sig_size / 72) * 0.003 # Standard for text
}
Package Structure
vbracket/
├── R/
│ ├── helpers.R # Utility functions
│ ├── annotation_grob.R # METHOD 1 implementation
│ ├── custom_legend_with_brackets.R # METHOD 2 implementation
│ ├── ggplot_integration.R # Main user interface
│ ├── print_method.R # Display support for METHOD 2
│ └── ggsave_vbracket.R # Save support for METHOD 2
├── getting_started.R # Beginner tutorial
├── test_all_features.R # Comprehensive test suite
└── README.md # This file
Tips for Best Results
-
Always specify output dimensions for publication-quality figures:
legend_bracket(..., output_width = 6, output_height = 4) ggsave("plot.png", p, width = 6, height = 4) -
Match dimensions exactly between
legend_bracket()andggsave():# GOOD legend_bracket(..., output_width = 6, output_height = 4) ggsave("plot.png", p, width = 6, height = 4) # BAD - dimensions don't match legend_bracket(..., output_width = 6, output_height = 4) ggsave("plot.png", p, width = 8, height = 6) -
Hide default legend to avoid conflicts:
theme(legend.position = "none") -
Use appropriate significance levels:
*for p < 0.05**for p < 0.01***for p < 0.001nsfor not significant
-
Check for overlaps when using many comparisons - vbracket will auto-space them
Troubleshooting
Brackets overlapping with text
Increase the bracket margin:
legend_bracket(..., bracket_margin = 0.15)
Legend too close to Y-axis
Use custom positioning:
legend_bracket(..., legend_x = 0.20)
Brackets not appearing in saved file
Make sure to specify output dimensions:
legend_bracket(..., output_width = 6, output_height = 4)
Font not matching plot
Font should auto-inherit from theme, but you can override:
legend_bracket(..., text_family = "serif")
Requirements
- R >= 3.5.0
- ggplot2 >= 3.0.0
- grid (included with base R)
License
MIT License
Citation
If you use vbracket in your research, please cite:
@software{vbracket,
title = {vbracket: Custom Legends with Statistical Comparison Brackets for ggplot2},
author = {Yoshiaki Sato},
year = {2025},
url = {https://github.com/h20gg702/vbracket}
}
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
Support
For questions, issues, or feature requests, please open an issue on GitHub.
Acknowledgments
Built with ggplot2 and the grid graphics system in R.






















