| Title: | A Next-Generation Grammar of Interactive Graphics |
|---|---|
| Description: | A modern visualization grammar that treats interactivity, animation, and composable layouts as first-class concepts rather than afterthoughts. Designed to address key limitations of existing grammars: native hover, click, and zoom events, 'WebGL'-accelerated rendering for large datasets, built-in multi-plot composition, and a token-based theming system. Renders to interactive HTML widgets via 'D3.js' or static SVG from a single declarative specification. |
| Authors: | Joash Joshua Ayo [aut, cre] |
| Maintainer: | Joash Joshua Ayo <[email protected]> |
| License: | MIT + file LICENSE |
| Version: | 0.1.0 |
| Built: | 2026-07-07 11:45:58 UTC |
| Source: | https://github.com/josh45-source/glyph |
Add animation/transition behavior
animate( spec, transition = "morph", duration = 500, stagger = 0, by = NULL, easing = "ease-in-out", loop = FALSE )animate( spec, transition = "morph", duration = 500, stagger = 0, by = NULL, easing = "ease-in-out", loop = FALSE )
spec |
A glyph_spec |
transition |
Transition type: "morph", "fade", "slide", "none" |
duration |
Duration in milliseconds |
stagger |
Delay between successive marks in ms (for entrance effects) |
by |
Column that defines animation keyframes (like gganimate's transition_states). e.g. by = year plays through years. |
easing |
Easing function: "linear", "ease-in", "ease-out", "ease-in-out", "bounce", "elastic" |
loop |
Repeat animation (TRUE, FALSE, or number of times) |
Modified glyph_spec
# Entrance animation: bars grow from baseline glyph(mtcars, x = cyl, y = mpg) |> mark_bar() |> animate(transition = "slide", stagger = 50) # Keyframe animation: morph between groups glyph(mtcars, x = wt, y = mpg) |> mark_point(size = hp, color = cyl) |> animate(by = gear, transition = "morph", duration = 800)# Entrance animation: bars grow from baseline glyph(mtcars, x = cyl, y = mpg) |> mark_bar() |> animate(transition = "slide", stagger = 50) # Keyframe animation: morph between groups glyph(mtcars, x = wt, y = mpg) |> mark_point(size = hp, color = cyl) |> animate(by = gear, transition = "morph", duration = 800)
Animations in glyph are declarative state transitions, not frame-by-frame rendering. You describe what changes and how it should interpolate, and the renderer handles the rest.
Two animation paradigms:
Transitions: animate between data states (e.g. year 2020 → 2021). Marks morph smoothly, entering/exiting as data changes.
Entrances: animate marks appearing on first render (stagger bars growing from zero, points fading in).
Compile a glyph spec to a resolved representation
compile(spec, engine = "auto", width = NULL, height = NULL) ## S3 method for class 'glyph_spec' compile(spec, engine = "auto", width = NULL, height = NULL) ## S3 method for class 'glyph_layout' compile(spec, engine = "auto", width = NULL, height = NULL)compile(spec, engine = "auto", width = NULL, height = NULL) ## S3 method for class 'glyph_spec' compile(spec, engine = "auto", width = NULL, height = NULL) ## S3 method for class 'glyph_layout' compile(spec, engine = "auto", width = NULL, height = NULL)
spec |
A glyph_spec or glyph_layout |
engine |
Rendering backend: "auto", "html", "svg", "canvas", "webgl" |
width |
Width in pixels (NULL for auto) |
height |
Height in pixels (NULL for auto) |
A glyph_compiled object (a list with resolved data + JSON)
A glyph_compiled object containing the resolved spec,
JSON string, and engine selection
A glyph_compiled object containing the resolved layout spec
and JSON string
Compose multiple glyph specs into a layout
compose( ..., type = "hstack", widths = NULL, heights = NULL, gap = 10, shared_scales = FALSE, linked_selections = FALSE, title = NULL )compose( ..., type = "hstack", widths = NULL, heights = NULL, gap = 10, shared_scales = FALSE, linked_selections = FALSE, title = NULL )
... |
glyph_spec objects or layout objects (for nesting) |
type |
Layout type: "hstack", "vstack", "grid", "wrap" |
widths |
Relative widths for columns (e.g. c(2, 1) for 2:1 split) |
heights |
Relative heights for rows |
gap |
Gap between plots in px |
shared_scales |
Share axis scales across plots: TRUE, FALSE, "x", "y", or "both" |
linked_selections |
Share interaction selections across plots |
title |
Overall layout title |
A glyph_layout object
p1 <- glyph(mtcars, x = wt, y = mpg) |> mark_point() p2 <- glyph(mtcars, x = wt, y = hp) |> mark_point() p3 <- glyph(mtcars, x = hp, y = mpg) |> mark_line() # Horizontal stack compose(p1, p2, type = "hstack") # Grid with linked brushing compose(p1, p2, p3, type = "wrap", linked_selections = TRUE, shared_scales = "x")p1 <- glyph(mtcars, x = wt, y = mpg) |> mark_point() p2 <- glyph(mtcars, x = wt, y = hp) |> mark_point() p3 <- glyph(mtcars, x = hp, y = mpg) |> mark_line() # Horizontal stack compose(p1, p2, type = "hstack") # Grid with linked brushing compose(p1, p2, p3, type = "wrap", linked_selections = TRUE, shared_scales = "x")
Export to various formats
export(spec, file, width = 800, height = 600)export(spec, file, width = 800, height = 600)
spec |
A glyph_spec or glyph_compiled |
file |
Output file path. Extension determines format: .html, .svg, .png, .pdf, .json (exports the raw spec) |
width |
Width in pixels |
height |
Height in pixels |
Invisibly returns the output file path
Like ggplot2's facet_wrap/facet_grid but with a key improvement: each facet can have independent geom parameters and scales by default (instead of forced uniformity).
facet(spec, rows = NULL, cols = NULL, free_scales = "none", wrap = NULL)facet(spec, rows = NULL, cols = NULL, free_scales = "none", wrap = NULL)
spec |
A glyph_spec |
rows |
Row faceting variable (bare name or NULL) |
cols |
Column faceting variable (bare name or NULL) |
free_scales |
"none", "x", "y", "both" |
wrap |
If only one variable, wrap into a grid with this many columns |
Modified glyph_spec
Entry point for the glyph grammar. Creates a specification object that describes a visualization declaratively. Unlike ggplot2, the spec is a pure data structure (nested list) that can be serialized, inspected, and compiled to multiple backends.
glyph(data = NULL, ...)glyph(data = NULL, ...)
data |
A data.frame or tibble. Unlike ggplot2, glyph also accepts lists-of-lists, URLs to CSV/JSON, or arrow tables (planned). |
... |
Global aesthetic mappings using bare column names (no aes() needed). e.g. glyph(mtcars, x = wt, y = mpg) |
A glyph_spec object
spec <- glyph(mtcars, x = wt, y = mpg) |> mark_point() summary(spec)spec <- glyph(mtcars, x = wt, y = mpg) |> mark_point() summary(spec)
Add an inset plot
inset(spec, inset, position = "top-right")inset(spec, inset, position = "top-right")
spec |
The main glyph_spec |
inset |
A glyph_spec to place as an inset |
position |
Where to place: "top-right", "top-left", "bottom-right", "bottom-left", or a list(x, y, width, height) with proportions 0-1 |
Modified glyph_spec
Add interactive behaviors to a glyph spec
interact( spec, tooltip = FALSE, zoom = FALSE, brush = FALSE, hover = NULL, click = NULL, crossfilter = FALSE, nearest = FALSE )interact( spec, tooltip = FALSE, zoom = FALSE, brush = FALSE, hover = NULL, click = NULL, crossfilter = FALSE, nearest = FALSE )
spec |
A glyph_spec |
tooltip |
Show values on hover. TRUE for auto-generated, or a glue-style template string like "{x}: {y} ({color})". |
zoom |
Enable scroll-to-zoom and pan |
brush |
Enable rectangular brush selection |
hover |
Highlight mark on hover ("enlarge", "brighten", "outline", or NULL) |
click |
Action on click: "select", "filter", "url", or a callback name |
crossfilter |
Link this plot's selections to other plots in a layout |
nearest |
Snap selection to nearest point (useful for line charts) |
Modified glyph_spec
glyph(mtcars, x = wt, y = mpg) |> mark_point(color = cyl) |> interact( tooltip = "{cyl} cylinders\n{mpg} mpg at {wt} tons", zoom = TRUE, hover = "enlarge", brush = TRUE, crossfilter = TRUE )glyph(mtcars, x = wt, y = mpg) |> mark_point(color = cyl) |> interact( tooltip = "{cyl} cylinders\n{mpg} mpg at {wt} tons", zoom = TRUE, hover = "enlarge", brush = TRUE, crossfilter = TRUE )
Unlike ggplot2 where interactivity is bolted on via ggplotly(), glyph treats interaction as part of the visualization grammar. Interactions are declared in the spec and compiled to the appropriate backend (D3 events for HTML, Shiny bindings for server-side).
Design philosophy: interactions are selections that filter or highlight marks. This follows Vega-Lite's insight that most interactions are really about defining subsets of the data. A tooltip is "select the nearest point, show its values." Brushing is "select points in a rectangle, highlight them."
Built-in multi-plot composition. No external packages needed. Layouts can express grids, stacks, insets, marginal plots, and arbitrary nesting. Composed plots can share selections for cross-filtering (linked brushing).
Adds marginal distributions to a plot's axes — a common pattern that requires ggExtra or manual grid manipulation in ggplot2.
marginals(spec, x = "histogram", y = "histogram", size = 0.15)marginals(spec, x = "histogram", y = "histogram", size = 0.15)
spec |
A glyph_spec |
x |
Marginal type for x-axis: "histogram", "density", "boxplot", NULL |
y |
Marginal type for y-axis: "histogram", "density", "boxplot", NULL |
size |
Proportion of plot area for marginals (0.0 to 0.4) |
Modified glyph_spec
glyph(mtcars, x = wt, y = mpg) |> mark_point(color = cyl) |> marginals(x = "histogram", y = "density")glyph(mtcars, x = wt, y = mpg) |> mark_point(color = cyl) |> marginals(x = "histogram", y = "density")
Add an area mark
mark_area(spec, ..., data = NULL, style = list())mark_area(spec, ..., data = NULL, style = list())
spec |
A glyph_spec |
... |
Aesthetic mappings (x, y, color, size, shape, alpha, tooltip) |
data |
Optional per-mark data override |
style |
Named list of fixed visual properties |
Modified glyph_spec object with the area mark added
Add a bar mark
mark_bar(spec, ..., data = NULL, style = list(), orient = "vertical")mark_bar(spec, ..., data = NULL, style = list(), orient = "vertical")
spec |
A glyph_spec |
... |
Aesthetic mappings (x, y, color, size, shape, alpha, tooltip) |
data |
Optional per-mark data override |
style |
Named list of fixed visual properties |
orient |
"vertical" or "horizontal" |
Modified glyph_spec object with the bar mark added
Add a line mark
mark_line(spec, ..., data = NULL, style = list(), interpolate = "monotone")mark_line(spec, ..., data = NULL, style = list(), interpolate = "monotone")
spec |
A glyph_spec |
... |
Aesthetic mappings (x, y, color, size, shape, alpha, tooltip) |
data |
Optional per-mark data override |
style |
Named list of fixed visual properties |
interpolate |
Interpolation method: "linear", "monotone", "step", "basis" |
Modified glyph_spec object with the line mark added
Add a link/edge mark (for networks, sankeys, slope graphs)
mark_link(spec, ..., data = NULL, style = list())mark_link(spec, ..., data = NULL, style = list())
spec |
A glyph_spec |
... |
Aesthetic mappings (x, y, color, size, shape, alpha, tooltip) |
data |
Optional per-mark data override |
style |
Named list of fixed visual properties |
Modified glyph_spec object with the link mark added
Add a point mark (scatterplot)
mark_point(spec, ..., data = NULL, style = list())mark_point(spec, ..., data = NULL, style = list())
spec |
A glyph_spec |
... |
Aesthetic mappings (x, y, color, size, shape, alpha, tooltip) |
data |
Optional per-mark data override |
style |
Named list of fixed visual properties |
Modified glyph_spec
spec <- glyph(mtcars, x = wt, y = mpg) |> mark_point(color = cyl, size = hp, tooltip = "{cyl} cyl, {mpg} mpg")spec <- glyph(mtcars, x = wt, y = mpg) |> mark_point(color = cyl, size = hp, tooltip = "{cyl} cyl, {mpg} mpg")
Add a ribbon/band mark (for confidence intervals, ranges)
mark_ribbon(spec, ..., data = NULL, style = list())mark_ribbon(spec, ..., data = NULL, style = list())
spec |
A glyph_spec |
... |
Aesthetic mappings (x, y, color, size, shape, alpha, tooltip) |
data |
Optional per-mark data override |
style |
Named list of fixed visual properties |
Modified glyph_spec object with the ribbon mark added
Add a rule (reference line) mark
mark_rule( spec, ..., data = NULL, style = list(), x_intercept = NULL, y_intercept = NULL )mark_rule( spec, ..., data = NULL, style = list(), x_intercept = NULL, y_intercept = NULL )
spec |
A glyph_spec |
... |
Aesthetic mappings (x, y, color, size, shape, alpha, tooltip) |
data |
Optional per-mark data override |
style |
Named list of fixed visual properties |
x_intercept |
Fixed x position for vertical rule |
y_intercept |
Fixed y position for horizontal rule |
Modified glyph_spec object with the rule mark added
Add a text/label mark
mark_text(spec, ..., data = NULL, style = list(), smart_repel = TRUE)mark_text(spec, ..., data = NULL, style = list(), smart_repel = TRUE)
spec |
A glyph_spec |
... |
Aesthetic mappings (x, y, color, size, shape, alpha, tooltip) |
data |
Optional per-mark data override |
style |
Named list of fixed visual properties |
smart_repel |
Automatically avoid label overlaps (TRUE by default). This is a first-class feature, not an extension package. |
Modified glyph_spec object with the text mark added
Marks are the visual encodings in glyph — analogous to ggplot2's geoms but with key differences:
Mappings without aes(): pass bare column names directly.
Per-mark data: each mark can have its own data source, enabling multi-dataset plots without awkward data parameter overrides.
Built-in interaction hints: marks carry interaction metadata (what happens on hover, click, drag) as part of the spec.
Transition-aware: marks know how to interpolate between states for animation.
Auto-render when printed (like ggplot2)
## S3 method for class 'glyph_spec' print(x, ...)## S3 method for class 'glyph_spec' print(x, ...)
x |
A glyph_spec object |
... |
Additional arguments (ignored) |
Invisibly returns the glyph_spec object
Render a glyph spec as an htmlwidget
render(spec, width = NULL, height = NULL)render(spec, width = NULL, height = NULL)
spec |
A glyph_spec, glyph_layout, or glyph_compiled |
width |
Widget width |
height |
Widget height |
An htmlwidget object that renders the visualization
in an HTML viewer
The rendering pipeline compiles a glyph_spec into output. The spec is first resolved (evaluate quosures, compute stats, merge defaults), then serialized to JSON, then handed to a backend:
"html" (default): htmlwidgets + D3.js for interactive viewing
"svg": Static SVG file (publication quality)
"canvas": HTML5 Canvas for large-data performance
"webgl": WebGL via regl/deck.gl for 100K+ points (planned)
"pdf": Direct PDF output via R's pdf() device (planned)
Key architectural difference from ggplot2: the spec is a pure data
structure. The compile() step resolves it into a concrete render tree.
This means you can:
Inspect the compiled spec as JSON (for debugging or export)
Serialize it and render on a different machine
Export it to Vega-Lite JSON (near 1:1 mapping)
Compile to multiple backends from one spec
Define a scale for an aesthetic channel
scale( spec, aesthetic, type = "auto", domain = NULL, range = NULL, nice = TRUE, zero = FALSE, reverse = FALSE, label = NULL, format = NULL )scale( spec, aesthetic, type = "auto", domain = NULL, range = NULL, nice = TRUE, zero = FALSE, reverse = FALSE, label = NULL, format = NULL )
spec |
A glyph_spec |
aesthetic |
Which channel: "x", "y", "color", "size", "shape", "alpha" |
type |
Scale type: "linear", "log", "sqrt", "time", "ordinal", "band", "quantize", "threshold" |
domain |
Explicit domain (data range). NULL for auto. |
range |
Explicit output range. For color: a palette name or vector. |
nice |
Round domain to nice values (TRUE/FALSE) |
zero |
Force zero in domain (TRUE/FALSE) |
reverse |
Reverse the scale |
label |
Axis/legend label (NULL for auto from column name) |
format |
Format string for tick labels (e.g. "$.2f", "%b %Y") |
Modified glyph_spec
glyph(mtcars, x = wt, y = mpg) |> mark_point(color = cyl) |> scale("y", "linear", zero = TRUE, label = "Miles per gallon") |> scale("color", "ordinal", range = "Set2")glyph(mtcars, x = wt, y = mpg) |> mark_point(color = cyl) |> scale("y", "linear", zero = TRUE, label = "Miles per gallon") |> scale("color", "ordinal", range = "Set2")
Scales map data values to visual properties. Glyph simplifies
ggplot2's scale_<aesthetic>_<type> explosion into a composable system:
scale(<aesthetic>, <type>, ...).
scale_color(spec, palette = "Tableau10", ...) scale_log(spec, aesthetic = "y", base = 10, ...) scale_time(spec, aesthetic = "x", ...)scale_color(spec, palette = "Tableau10", ...) scale_log(spec, aesthetic = "y", base = 10, ...) scale_time(spec, aesthetic = "x", ...)
spec |
A glyph_spec |
palette |
A named palette: "viridis", "Set2", "Tableau10", "Blues", etc. |
... |
Additional arguments passed to |
aesthetic |
Which channel: "x", "y", "color", "size", "shape", "alpha" |
base |
Log base (default 10) |
Modified glyph_spec object
Modified glyph_spec object
Modified glyph_spec object
scale_color(): Set color palette by name
scale_log(): Log-transform an axis
scale_time(): Time/date axis
For complex interactions: define a named selection that can be referenced by marks and scales. This is how you build linked views, conditional encoding, and interactive legends.
selection( spec, name, type = "point", on = "click", fields = NULL, resolve = "global" )selection( spec, name, type = "point", on = "click", fields = NULL, resolve = "global" )
spec |
A glyph_spec |
name |
Selection name (referenced in conditional encodings) |
type |
"point", "interval", or "legend" |
on |
Event trigger: "click", "mouseover", "drag" |
fields |
Which data fields the selection projects onto |
resolve |
For composed views: "global", "union", "intersect" |
Modified glyph_spec
# Interactive legend: click legend entries to filter glyph(mtcars, x = wt, y = mpg) |> mark_point(color = cyl) |> selection("legend_filter", type = "legend", fields = "cyl")# Interactive legend: click legend entries to filter glyph(mtcars, x = wt, y = mpg) |> mark_point(color = cyl) |> selection("legend_filter", type = "legend", fields = "cyl")
Set theme tokens
theme_tokens( spec, preset = NULL, font = NULL, font_size = NULL, bg = NULL, fg = NULL, accent = NULL, grid = NULL, border = NULL, padding = NULL, title_size = NULL )theme_tokens( spec, preset = NULL, font = NULL, font_size = NULL, bg = NULL, fg = NULL, accent = NULL, grid = NULL, border = NULL, padding = NULL, title_size = NULL )
spec |
A glyph_spec |
preset |
A named preset: "light" (default), "dark", "minimal", "publication", "presentation". Presets set all tokens to coherent defaults. |
font |
Font family for all text |
font_size |
Base font size in px (axis labels scale relative to this) |
bg |
Background color |
fg |
Primary text/axis color |
accent |
Primary accent color (used for single-series marks) |
grid |
Grid line visibility: TRUE, FALSE, "x", "y" |
border |
Plot border: TRUE/FALSE |
padding |
Padding around the plot area in px |
title_size |
Title font size multiplier (relative to font_size) |
Modified glyph_spec
glyph(mtcars, x = wt, y = mpg) |> mark_point() |> theme_tokens(preset = "dark") glyph(mtcars, x = wt, y = mpg) |> mark_point() |> theme_tokens(font = "IBM Plex Sans", bg = "#fafafa", grid = "y")glyph(mtcars, x = wt, y = mpg) |> mark_point() |> theme_tokens(preset = "dark") glyph(mtcars, x = wt, y = mpg) |> mark_point() |> theme_tokens(font = "IBM Plex Sans", bg = "#fafafa", grid = "y")
Instead of ggplot2's 90+ theme() arguments, glyph uses a design-token system. A small set of semantic tokens (font, colors, spacing, sizes) cascade through the entire visualization. Think of it like CSS custom properties for plots.
Tokens cascade: setting bg changes the background, but also automatically
adjusts text color for contrast, grid line opacity, and tooltip styling.
You override only what you want; everything else adapts.
Add a title, subtitle, or caption
titles(spec, title = NULL, subtitle = NULL, caption = NULL)titles(spec, title = NULL, subtitle = NULL, caption = NULL)
spec |
A glyph_spec |
title |
Main title |
subtitle |
Subtitle (below title) |
caption |
Caption (bottom of plot, for source attribution) |
Modified glyph_spec object with updated title metadata
Because glyph's spec is structurally similar to Vega-Lite, we can export to Vega-Lite JSON for use in Python (Altair), JavaScript, or the Vega Editor.
to_vegalite(spec)to_vegalite(spec)
spec |
A glyph_spec |
A JSON string in Vega-Lite format