Guide
Graphics And Devices
How base graphics, grid graphics, file devices, and the optional GUI fit together
miniR's graphics stack is layered. The important split is between data model, R-facing builtins, file devices, and the optional GUI.
Interpreter-Owned Graphics State
Graphics state lives on Interpreter, not in process-global statics. The important pieces are:
par_statefor base-graphics parameterscurrent_plotfor the accumulatedPlotStatefile_devicefor active SVG/PNG/JPEG/BMP/PDF outputcolor_palettefor indexed color lookupgrid_display_listandgrid_viewport_stackfor R-level grid objectsgrid_grob_store,grid_rust_display_list, andgrid_rust_viewport_stackfor renderer-oriented grid stateplot_txfor the optional GUI channel when theplotfeature is enabled
That state split is what lets multiple interpreters coexist cleanly.
Base Graphics Path
Base-graphics builtins live in src/interpreter/builtins/graphics.rs.
Their pattern is:
- Decode R arguments into plotting parameters
- Accumulate
PlotItems into aPlotState - Either send the plot to the GUI, or keep accumulating until
dev.off()writes a file
The plotting builtins are not directly drawing pixels. They are building an intermediate plot model first.
Grid Graphics Path
Grid support lives in src/interpreter/grid.rs and src/interpreter/builtins/grid.rs.
At the R level, grid objects are ordinary R lists with classes such as:
unitgparviewportgrob
The builtin layer converts those into Rust-side grobs, display-list entries, and viewport-stack operations. flush_grid() then translates the grid display list into the same PlotState pipeline used by base graphics.
That reuse is deliberate: grid and base graphics can share downstream rendering infrastructure even though their front ends are different.
File Devices
miniR supports file-device entry points such as:
svg()png()jpeg()bmp()pdf()dev.off()
The current file-device flow is especially important:
- Device builtins set
file_deviceand start a freshPlotState - Plotting commands accumulate into that state
dev.off()renders through the SVG pipeline first- Optional backends convert SVG into raster or PDF output when those features are enabled
So the device stack is intentionally layered rather than implementing five unrelated renderers.
Why SVG Sits In The Middle
The rendering stack is feature-shaped:
| Feature | Role |
|---|---|
svg-device | canonical vector rendering path |
raster-device | rasterize SVG into PNG, JPEG, or BMP |
pdf-device | convert SVG output into PDF |
plot / gui | interactive display in egui |
If raster or PDF features are absent, miniR degrades pragmatically instead of pretending nothing happened. For example, raster output can fall back to writing SVG plus a warning.
Interactive GUI
The egui viewer lives in src/interpreter/graphics/egui_device.rs.
The architecture is:
- main thread runs the egui event loop
- REPL and interpreter run on a background thread
- a channel carries
PlotMessagevalues from interpreter to GUI
That keeps the REPL from blocking on plot windows while still satisfying platform requirements such as macOS's main-thread GUI rules.
Where To Extend
| If you want to add... | Start here |
|---|---|
| A new high-level plotting builtin | src/interpreter/builtins/graphics.rs |
| A new grid primitive or viewport behavior | src/interpreter/builtins/grid.rs and src/interpreter/grid/ |
| A new file backend | src/interpreter/graphics/ |
| GUI behavior or plot window UX | src/interpreter/graphics/egui_device.rs |
The graphics stack is easiest to work with when you keep the layers separate: R-facing builtins build plot state, renderers turn plot state into bytes or windows, and interpreter state owns the live device/session context.