---
title: "glyph vs ggplot2: Side-by-Side Comparison"
output: rmarkdown::html_vignette
vignette: >
%\VignetteIndexEntry{glyph vs ggplot2: Side-by-Side Comparison}
%\VignetteEngine{knitr::rmarkdown}
---
## 1. Basic Scatterplot
**ggplot2:**
```r
ggplot(mtcars, aes(x = wt, y = mpg, color = factor(cyl))) +
geom_point(size = 3) +
scale_color_brewer(palette = "Set2") +
labs(
title = "Motor Trend Cars",
x = "Weight (1000 lbs)",
y = "Miles per Gallon",
color = "Cylinders"
) +
theme_minimal()
```
**glyph:**
```r
glyph(mtcars, x = wt, y = mpg) |>
mark_point(color = cyl, style = list(size = 6)) |>
scale_color("Set2") |>
scale("x", label = "Weight (1000 lbs)") |>
scale("y", label = "Miles per Gallon") |>
titles(title = "Motor Trend Cars") |>
theme_tokens(preset = "minimal")
```
**What changed:** No `aes()`, no `factor()` coercion, no `+` operator.
Pipeline reads left-to-right with `|>`. Color scale is one function call.
---
## 2. Interactive Exploration
**ggplot2 + plotly:**
```r
library(plotly)
p <- ggplot(mtcars, aes(x = wt, y = mpg, color = factor(cyl),
text = paste("Car:", rownames(mtcars),
"
MPG:", mpg,
"
Weight:", wt))) +
geom_point(size = 3)
ggplotly(p, tooltip = "text")
# Note: loses theme, some formatting; no brush-to-filter
```
**glyph:**
```r
glyph(mtcars, x = wt, y = mpg) |>
mark_point(color = cyl) |>
interact(
tooltip = "Car: {.rownames}\nMPG: {mpg}\nWeight: {wt}",
zoom = TRUE,
brush = TRUE,
hover = "enlarge"
)
# Theme, formatting, all interactions preserved — no conversion step
```
**What changed:** No lossy ggplotly() conversion. Interactions are part
of the spec, not a post-hoc wrapper. Tooltip template is a simple string,
not a paste() call inside aes().
---
## 3. Animated Bar Chart
**ggplot2 + gganimate:**
```r
library(gganimate)
p <- ggplot(gapminder, aes(x = continent, y = lifeExp, fill = continent)) +
geom_col(stat = "summary", fun = "mean") +
transition_states(year, transition_length = 2, state_length = 1) +
labs(title = "Year: {closest_state}") +
theme_minimal() +
enter_grow() +
ease_aes("bounce-out")
animate(p, fps = 20, width = 600, height = 400)
# Renders to GIF (not interactive)
```
**glyph:**
```r
glyph(gapminder, x = continent, y = lifeExp) |>
mark_bar() |>
animate(by = year, transition = "morph", easing = "bounce") |>
interact(tooltip = TRUE) |>
titles(title = "Life Expectancy by Continent") |>
theme_tokens(preset = "minimal")
# Renders as interactive HTML with playback controls
```
**What changed:** Animation is one function call in the pipeline, not a
separate package. Output is interactive HTML (play/pause/scrub), not a
static GIF. Tooltips work during animation.
---
## 4. Multi-Panel Dashboard
**ggplot2 + patchwork:**
```r
library(patchwork)
p1 <- ggplot(mtcars, aes(wt, mpg)) + geom_point() + theme_minimal()
p2 <- ggplot(mtcars, aes(hp, mpg)) + geom_point() + theme_minimal()
p3 <- ggplot(mtcars, aes(factor(cyl), mpg)) + geom_boxplot() + theme_minimal()
(p1 | p2) / p3 +
plot_annotation(title = "Motor Trend Dashboard")
# No linked interactions between panels
```
**glyph:**
```r
p1 <- glyph(mtcars, x = wt, y = mpg) |>
mark_point(color = cyl) |>
interact(brush = TRUE)
p2 <- glyph(mtcars, x = hp, y = mpg) |>
mark_point(color = cyl) |>
interact(brush = TRUE)
p3 <- glyph(mtcars, x = cyl, y = mpg) |>
mark_bar()
compose(p1, p2, p3,
type = "wrap",
linked_selections = TRUE,
title = "Motor Trend Dashboard")
# Brushing in p1 highlights the same cars in p2 and p3
```
**What changed:** No extra package. Linked selections across panels
via `linked_selections = TRUE`. Brush in one panel cross-filters the others.
---
## 5. Marginal Distributions
**ggplot2 + ggExtra:**
```r
library(ggExtra)
p <- ggplot(mtcars, aes(wt, mpg, color = factor(cyl))) +
geom_point() +
theme_minimal()
ggMarginal(p, type = "histogram", groupColour = TRUE)
```
**glyph:**
```r
glyph(mtcars, x = wt, y = mpg) |>
mark_point(color = cyl) |>
marginals(x = "histogram", y = "density", size = 0.2)
```
**What changed:** Built-in. One function call. Size is configurable.
---
## 6. Dark Theme
**ggplot2:**
```r
ggplot(mtcars, aes(wt, mpg)) +
geom_point(color = "#6ec6ff") +
theme(
plot.background = element_rect(fill = "#1a1a2e"),
panel.background = element_rect(fill = "#1a1a2e"),
panel.grid.major = element_line(color = "#2a2a4a"),
panel.grid.minor = element_blank(),
axis.text = element_text(color = "#e0e0e0"),
axis.title = element_text(color = "#e0e0e0"),
text = element_text(color = "#e0e0e0")
)
# 9 lines of theme overrides
```
**glyph:**
```r
glyph(mtcars, x = wt, y = mpg) |>
mark_point() |>
theme_tokens(preset = "dark")
# 1 line. All contrast/grid/text colors auto-derived.
```
Or with custom colors:
```r
glyph(mtcars, x = wt, y = mpg) |>
mark_point() |>
theme_tokens(bg = "#1a1a2e")
# fg, grid_color, accent all adapt automatically
```
---
## 7. Export Flexibility
**ggplot2:**
```r
ggsave("plot.png", width = 8, height = 6) # static only
ggsave("plot.pdf", width = 8, height = 6) # static only
ggsave("plot.svg", width = 8, height = 6) # static only
# No interactive HTML export. No spec export.
```
**glyph:**
```r
spec <- glyph(mtcars, x = wt, y = mpg) |>
mark_point() |>
interact(tooltip = TRUE, zoom = TRUE)
export(spec, "plot.html") # interactive HTML
export(spec, "plot.svg") # static SVG
export(spec, "plot.json") # raw spec (inspect/debug)
cat(to_vegalite(spec)) # Vega-Lite JSON (use in Python/JS)
```
---
## Summary: When to Use Which
**Use ggplot2 when:**
- You need a specific extension (ggridges, ggalluvial, etc.)
- You're producing static plots for print/PDF
- You want maximum community support and StackOverflow answers
- Statistical transforms (smooth, density2d) are central to your workflow
**Use glyph when:**
- Interactivity is part of the deliverable (dashboards, reports, exploration)
- You want linked views without Shiny
- Animation is important (presentations, storytelling)
- You're working with large datasets (>10K points)
- You want a cleaner, more composable API
- You need to export specs to JavaScript (Vega-Lite, D3)
- Theme consistency across many plots matters (token system)