# glyph vs ggplot2: Side-by-Side Comparison

## 1. Basic Scatterplot

ggplot2:

    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:

    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:

    library(plotly)

    p <- ggplot(mtcars, aes(x = wt, y = mpg, color = factor(cyl),
                             text = paste("Car:", rownames(mtcars),
                                          "<br>MPG:", mpg,
                                          "<br>Weight:", wt))) +
      geom_point(size = 3)

    ggplotly(p, tooltip = "text")
    # Note: loses theme, some formatting; no brush-to-filter

glyph:

    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:

    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:

    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:

    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:

    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:

    library(ggExtra)

    p <- ggplot(mtcars, aes(wt, mpg, color = factor(cyl))) +
      geom_point() +
      theme_minimal()

    ggMarginal(p, type = "histogram", groupColour = TRUE)

glyph:

    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:

    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:

    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:

    glyph(mtcars, x = wt, y = mpg) |>
      mark_point() |>
      theme_tokens(bg = "#1a1a2e")
      # fg, grid_color, accent all adapt automatically

## 7. Export Flexibility

ggplot2:

    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:

    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)