t-SNE and UMAP Embedding Visualization — ggplot2

A scatter plot displaying high-dimensional data projected into 2D space using non-linear dimensionality reduction techniques such as t-SNE or UMAP. Points are colored by cluster or class label, revealing groupings and latent structure in the data. This is a standard visualization in machine learning for exploring embeddings, single-cell RNA-seq data, and NLP document clustering, helping practitioners verify that learned representations capture meaningful distinctions.

t-SNE and UMAP Embedding Visualization rendered with ggplot2

Renders

R source (ggplot2)

#' anyplot.ai
#' scatter-embedding: t-SNE and UMAP Embedding Visualization
#' Library: ggplot2 3.5.1 | R 4.4.1
#' Quality: 90/100 | Created: 2026-08-11

library(ggplot2)
library(tibble)
library(ragg)

set.seed(42)

# --- Theme tokens ------------------------------------------------------------
THEME       <- Sys.getenv("ANYPLOT_THEME", "light")
PAGE_BG     <- if (THEME == "light") "#FAF8F1" else "#1A1A17"
ELEVATED_BG <- if (THEME == "light") "#FFFDF6" else "#242420"
INK         <- if (THEME == "light") "#1A1A17" else "#F0EFE8"
INK_SOFT    <- if (THEME == "light") "#4A4A44" else "#B8B7B0"
IMPRINT_PALETTE <- c("#009E73", "#C475FD", "#4467A3", "#BD8233", "#AE3030", "#2ABCCD")

# --- Data: UMAP projection of PBMC single-cell RNA-seq profiles -------------
# Cluster geometry (center, spread, rotation) mimics the elongated, irregular
# blobs a real non-linear embedding produces, rather than circular Gaussians.
cell_types <- c("CD4+ T cells", "CD8+ T cells", "B cells", "NK cells", "Monocytes", "Dendritic cells")
cluster_n  <- c(700, 550, 450, 350, 400, 250)
center_x   <- c(-6.0, -3.5, 5.5, -8.2, 6.5, 1.5)
center_y   <- c(2.0, -3.0, 2.5, -2.6, -2.0, 4.5)
spread_x   <- c(1.3, 1.0, 1.1, 0.8, 1.2, 0.8)
spread_y   <- c(0.55, 0.5, 0.6, 0.4, 0.5, 0.4)
angle_deg  <- c(20, -30, 10, 60, -15, 45)

n_total    <- sum(cluster_n)
cluster_id <- factor(rep(seq_along(cluster_n), cluster_n), labels = cell_types)
cx_vec     <- rep(center_x, cluster_n)
cy_vec     <- rep(center_y, cluster_n)
sx_vec     <- rep(spread_x, cluster_n)
sy_vec     <- rep(spread_y, cluster_n)
angle_vec  <- rep(angle_deg, cluster_n) * pi / 180

raw_x <- rnorm(n_total, mean = 0, sd = sx_vec)
raw_y <- rnorm(n_total, mean = 0, sd = sy_vec)

embedding <- tibble::tibble(
  x         = raw_x * cos(angle_vec) - raw_y * sin(angle_vec) + cx_vec,
  y         = raw_x * sin(angle_vec) + raw_y * cos(angle_vec) + cy_vec,
  cell_type = cluster_id
)

# Cluster centroids for the optional label annotations called out in the spec.
centroids <- aggregate(cbind(x, y) ~ cell_type, data = embedding, FUN = mean)

# --- Title (scales fontsize to length; mandated title is well under the
# 67-char baseline here, so this resolves to the library default of 12pt) ---
title_text  <- "scatter-embedding · r · ggplot2 · anyplot.ai"
title_ratio <- if (nchar(title_text) > 67) 67 / nchar(title_text) else 1.0
title_size  <- max(8, round(12 * title_ratio))

# --- Plot ---------------------------------------------------------------
p <- ggplot(embedding, aes(x = x, y = y, color = cell_type, shape = cell_type)) +
  geom_point(size = 1.8, alpha = 0.5, stroke = 0.4) +
  geom_label(
    data          = centroids,
    aes(x = x, y = y, label = cell_type),
    inherit.aes   = FALSE,
    color         = INK,
    fill          = ELEVATED_BG,
    alpha         = 0.85,
    size          = 2.5,
    label.size    = 0,
    label.padding = unit(0.12, "lines"),
    fontface      = "bold"
  ) +
  scale_color_manual(values = IMPRINT_PALETTE, name = "Cell type") +
  scale_shape_manual(values = c(16, 17, 15, 18, 3, 8), name = "Cell type") +
  labs(
    title    = title_text,
    subtitle = "UMAP projection (n_neighbors = 15) of PBMC single-cell RNA-seq profiles",
    x        = "UMAP 1",
    y        = "UMAP 2"
  ) +
  theme_minimal(base_size = 8) +
  theme(
    plot.background   = element_rect(fill = PAGE_BG, color = PAGE_BG),
    panel.background  = element_rect(fill = PAGE_BG, color = NA),
    panel.grid        = element_blank(),
    axis.text         = element_blank(),
    axis.ticks        = element_blank(),
    axis.line         = element_line(color = INK_SOFT, linewidth = 0.4),
    axis.title        = element_text(color = INK, size = 10),
    plot.title        = element_text(color = INK, size = title_size, face = "bold"),
    plot.subtitle     = element_text(color = INK_SOFT, size = 9),
    plot.margin       = margin(t = 10, r = 14, b = 10, l = 10),
    legend.position   = "right",
    legend.background = element_rect(fill = ELEVATED_BG, color = INK_SOFT, linewidth = 0.3),
    legend.margin     = margin(t = 6, r = 8, b = 6, l = 8),
    legend.key        = element_rect(fill = ELEVATED_BG, color = NA),
    legend.key.size   = unit(0.9, "lines"),
    legend.text       = element_text(color = INK_SOFT, size = 8),
    legend.title      = element_text(color = INK, size = 9, face = "bold")
  )

# --- Save --------------------------------------------------------------
ggsave(
  filename = sprintf("plot-%s.png", THEME),
  plot     = p,
  device   = ragg::agg_png,
  width    = 8,
  height   = 4.5,
  units    = "in",
  dpi      = 400
)

Part of t-SNE and UMAP Embedding Visualization on anyplot.ai.

Other implementations