
ggtwotone extends ggplot2 with dual-stroke
and contrast-aware geoms that improve the visibility of annotations,
curves, and labels on heterogeneous backgrounds. The package is designed
for figures containing images, maps, heatmaps, microscopy data, or other
complex visualizations where standard single-color annotations may
become difficult to distinguish.
Complete documentation, reference manuals, and additional examples are available at Reference Manual, or see them in the R help tab after loading the package.
Standard annotations often become difficult to distinguish on complex or heterogeneous backgrounds, such as microscopy images, maps, photographs, or heatmaps. ggtwotone addresses this problem by combining dual-stroke rendering with contrast-aware color selection.
# install.packages("pak")
pak::pak("bwanniarachchige2/ggtwotone")(After CRAN release this section will simply become
install.packages("ggtwotone").)
The example below demonstrates how geom_segment_dual()
and geom_text_contrast() improve measurement overlays on a
microscopy image.
library(ggtwotone)
library(magick)
img_path <- "man/figures/micro_image.jpg"
um_per_px <- 0.05 # <-- calibration: micrometers per pixel
bar_um <- 10 # scale bar length in micrometers
# Load image as a background grob
img <- magick::image_read(img_path)
w <- magick::image_info(img)$width
h <- magick::image_info(img)$height
bg <- grid::rasterGrob(img, width = unit(1, "npc"), height = unit(1, "npc"))
meas <- data.frame(
x = 0.3218, y = 0.4507, xend = 0.7974, yend = 0.6371 # <-- adjust to your line
)
# Compute physical length for the label
dx_px <- abs(meas$xend - meas$x) * w
dy_px <- abs(meas$yend - meas$y) * h
len_um <- sqrt(dx_px^2 + dy_px^2) * um_per_px
lab <- sprintf("%.1f \u00B5m", len_um)
# Midpoint for the label
xm <- (meas$x + meas$xend)/2
ym <- (meas$y + meas$yend)/2
lab_df <- data.frame(x = xm, y = ym + 0.05, label = lab)
#Plot
ggplot() +
# background SEM image
annotation_custom(bg, xmin = 0, xmax = 1, ymin = 0, ymax = 1) +
# measurement line with dual stroke
geom_segment_dual(
data = meas,
aes(x = x, y = y, xend = xend, yend = yend),
colour1 = "#0D0D0D",
colour2 = "#FFFFFF",
linewidth = 1.2,
lineend = "round",
arrow = grid::arrow(ends = "both", length = unit(0.18, "in"), type = "open")
) +
# measurement label (contrast-aware)
geom_text_contrast(
data = lab_df,
aes(x = x, y = y, label = label),
background = "#444444",
size = 4.2
) +
coord_fixed(xlim = c(0, 1), ylim = c(0, 1), expand = FALSE) +
theme_void()
Dual-stroke annotations remain clearly visible regardless of the local background, while labels automatically adapt to maintain contrast.
Image credit
SEM micrograph adapted from Marie Majaura, Own work, licensed under CC BY-SA 3.0. Used under the terms of the license.
The following example demonstrates geom_text_contrast()
on a confusion matrix generated from a linear discriminant analysis
(LDA) classifier fitted to the iris data. Text colours are
selected automatically to maintain readability against tiles with
different background colours.
library(dplyr)
library(ggplot2)
library(ggtwotone)
library(scales)
library(MASS)
set.seed(1)
# Fit LDA classifier on iris
iris_lda <- MASS::lda(
Species ~ Sepal.Length + Sepal.Width + Petal.Length + Petal.Width,
data = iris
)
iris_pred <- predict(iris_lda)$class
# Build confusion matrix
classes <- levels(iris$Species)
cm <- table(
True = iris$Species,
Predicted = iris_pred
) |>
as.data.frame()
cm <- cm |>
group_by(True) |>
mutate(
Accuracy = Freq / sum(Freq),
label = sprintf("%.1f%%", 100 * Accuracy)
)
# Palette and background colors for text contrast
pal <- c("#313695", "#74add1", "#fdae61", "#fee08b")
col_fun <- scales::col_numeric(
palette = pal,
domain = c(0, 1)
)
cm$fill_hex <- col_fun(cm$Accuracy)
# Plot
ggplot(cm, aes(Predicted, True)) +
geom_tile(aes(fill = Accuracy), color = "white", linewidth = 0.8) +
geom_text_contrast(
aes(label = label),
background = cm$fill_hex,
base_colour = "#004488",
method = "auto",
contrast = 4.5,
size = 5,
fontface = "bold"
) +
scale_fill_gradientn(
colours = pal,
limits = c(0, 1),
name = "Accuracy"
) +
coord_fixed() +
labs(
title = "Confusion Matrix for Iris LDA",
x = "Predicted Species",
y = "True Species"
) +
theme_minimal(base_size = 13) +
theme(
panel.grid = element_blank(),
axis.text.x = element_text(angle = 45, hjust = 1)
)
geom_text_contrast() automatically selects a readable
foreground color for each label based on the tile background, improving
readability while preserving the underlying color scale.
If you use ggtwotone in published work, please cite
citation("ggtwotone")(after the package is available on CRAN).
MIT License.