---
title: "Get started"
output: rmarkdown::html_vignette
vignette: >
%\VignetteIndexEntry{Get started}
%\VignetteEngine{knitr::rmarkdown}
%\VignetteEncoding{UTF-8}
---
```{r}
#| include: false
knitr::opts_chunk$set(
collapse = TRUE,
comment = "#>",
fig.width = 6,
fig.height = 3,
dpi = 96,
dev = "png"
)
```
vellum is a low-level graphics framework for R, in the spirit of grid, with a
Rust backend. You describe a scene with a small, declarative R API; the scene
graph, unit and layout engine, and rendering all run in Rust; and the same scene
renders to PNG, SVG, or PDF.
It is the foundation layer a grammar of graphics builds on, the way grid
underlies ggplot2. It is not a plotting package itself, so there are no scales,
stats, geoms, or facets here. What it gives you is the drawing substrate:
units, viewports, grobs, layout, and a deterministic renderer.
```{r}
#| label: setup
library(vellum)
```
## A first scene
A scene starts with `vl_scene()`, which fixes the page size (in inches by
default) and background. You then add content with a pipeline of `draw()` calls
and show the result. Auto-printing a scene displays it, so the last line of a
chunk is enough.
```{r}
#| label: hello
vl_scene(width = 6, height = 3, bg = "white") |>
draw(rect_grob(
width = 0.94, height = 0.82,
gp = vl_gpar(fill = linear_gradient(c("#1b2a4a", "#3a7bd5")), col = NA)
)) |>
draw(circle_grob(
x = 0.16, y = 0.5, r = 0.28,
gp = vl_gpar(fill = "#f7c948", col = NA)
)) |>
draw(text_grob(
"vellum", x = 0.62, y = 0.5,
gp = vl_gpar(fontsize = 48, col = "white", fontface = "bold")
))
```
Three ideas do most of the work here.
**Grobs** are the drawable primitives: `rect_grob()`, `circle_grob()`,
`text_grob()`, and a couple of dozen more (see `?grob`). Each is an immutable
value object; building one draws nothing on its own.
**`vl_gpar()`** carries the graphical parameters (fill, stroke colour, line width,
font, opacity) attached to a grob. A `fill` can be a plain colour or a gradient,
as above.
**Coordinates default to `"npc"`**: normalised parent coordinates, where `(0, 0)`
is the bottom-left of the region and `(1, 1)` the top-right. So `x = 0.16` sits
near the left edge and `y = 0.5` is vertically centred.
## Building a scene: push, draw, pop
A scene is a tree of viewports and grobs, built functionally. `push()` descends
into a new `vl_viewport()`, `draw()` adds a grob at the current level, and `pop()`
ascends. A viewport is a rectangular region that establishes its own coordinate
systems, so once you push one with an `xscale` and `yscale`, `"native"` units
map data values straight onto the region.
```{r}
#| label: scatter
#| fig.width: 5
#| fig.height: 3.2
set.seed(1)
x <- runif(60, 0, 10)
y <- 1.8 * x + rnorm(60, 0, 4)
vl_scene(5, 3.2, bg = "white") |>
push(vl_viewport(
x = 0.57, y = 0.57, width = 0.82, height = 0.82,
xscale = c(0, 10), yscale = range(pretty(y))
)) |>
draw(rect_grob(gp = vl_gpar(fill = "#f4f6f8", col = "#cfd8dc"))) |>
draw(points_grob(
vl_unit(x, "native"), vl_unit(y, "native"),
size = vl_unit(3.2, "mm"),
gp = vl_gpar(fill = "#3a7bd5", col = "#1b2a4a", lwd = 1)
)) |>
pop()
```
The panel rectangle is drawn in `"npc"` (it fills its viewport), while the points
are placed in `"native"` units, so their positions follow the data scales. A
single `vl_unit()` vector can even mix coordinate systems per axis. See
`vignette("scene-and-paint")` for the full picture of units, viewports, and the
paint model.
## Rendering to a file
Displaying a scene is convenient while exploring. To write output, call
`render()`; it picks the backend from the file extension.
```{r}
#| label: render
#| eval: false
s <- vl_scene(4, 3) |>
draw(circle_grob(r = 0.3, gp = vl_gpar(fill = "tomato", col = NA)))
render(s, "out.png") # raster (tiny-skia)
render(s, "out.svg") # vector (hand-rolled SVG)
render(s, "out.pdf") # vector (krilla)
```
The same scene value renders to all three formats with consistent geometry, and
raster and PDF output is byte-stable, so it is reproducible and
snapshot-testable.
## Where to go next
- `vignette("scene-and-paint")`: the scene graph, units and viewports, and the
paint model (gradients, patterns, masks).
- `vignette("retained-mode")`: because the scene graph is kept rather than drawn
and forgotten, you can hit-test it and edit nodes by name.
- `vignette("coming-from-grid")`: a translation guide if you already know grid.
- `vignette("grid-interop")`: render existing grid, ggplot2, and lattice output
through the vellum backend.
```