gg-plot
A declarative plotting system built on top of the Cairo graphics library which provides ggplot2-style plotting routines, with s-expression plot specifications, automatic scale training, and four output formats (PNG, SVG, PostScript, PDF).
Requirements
- cairo (Cairo graphics bindings)
- matchable
- datatype
- yasos
- statistics
Overview
The library is organised into nine modules that correspond to the tiers of the Grammar of Graphics. Most users only need to (import gg-plot); the lower-level modules are available for custom rendering pipelines.
gg-plot- Full declarative plot API
gg-vge- Virtual Graphics Engine — algebraic IR for graphics instructions
gg-backend- Abstract backend protocol (YASOS interface)
gg-backend-cairo- Cairo backend; PNG, SVG, PostScript, and PDF output
gg-primitives-vge- Composable drawer abstraction; shapes, text, transforms
gg-scales- Data-to-visual mappings (linear, log, ordinal, color)
gg-data- Columnar data utilities
gg-aes- Aesthetic (column-channel) mapping
gg-geom- Geometry functions (scatter, line, bar, errorbar, raster...)
gg-guides- Axis and legend drawers
The translation pipeline
s-expression spec
-> normalize / validate defaults
-> train scales from all layers
-> compute facet panels
-> render each panel (background, geometries, axes)
-> render global elements (title, legends)
-> emit via Cairo (PNG / SVG / PS / PDF)Module: gg-plot
The top-level declarative API. Import only this module for typical use.
(import gg-plot)Plot construction
(ggplot data . specs)Create a plot-spec record.
data is a columnar alist. specs is a mixed list of:
- an
(aes ...)call for the default aesthetic mapping, - layer constructors (
layer-point,layer-line, …), - scale specifications (
scale-x-continuous, …), - a coordinate system (
coord-cartesian, …), - a faceting specification (
facet-wrap,facet-grid), - a theme (
theme-minimal, …), (labs ...)for title and axis labels.
(define p
(ggplot data (aes #:x 'time #:y 'value #:color 'condition)
(layer-line #:width 2)
(layer-point #:size 5)
(scale-x-continuous #:name "Time (ms)")
(scale-y-continuous #:name "Response (mV)")
(scale-color-manual #:values '(("Control" . "blue") ("Treatment" . "red")))
(theme-minimal #:base-size 12)
(labs #:title "Treatment vs Control")))Rendering
(render-plot plot-spec backend)Execute the full rendering pipeline and write output. backend is a Cairo backend constructed with one of the make-cairo-*-backend procedures from gg-backend-cairo.
(ggsave plot-spec filename #!key width height format scale)Save to a file. format is inferred from the extension (.png, .svg, .ps) or can be given explicitly as 'png, 'svg, or 'ps. width/height default to 800×600.
(ggdisplay plot-spec #!key width height)Render to an X11 window.
Plot serialisation
(plot->sexp plot-spec)Convert a plot specification to a pure s-expression (no closures). The result can be written to a file with write and read back later.
(sexp->plot sexp)Reconstruct a plot-spec from a serialised s-expression.
Layer constructors
All layer constructors accept #:data (override dataset), #:mapping (override or extend the plot-level aesthetic mapping), plus geometry-specific keyword arguments.
Basic geometries
Reference lines
linetype is 'solid, 'dashed, or 'dotted.
Statistical geometry layers
(layer-histogram #!key data mapping bins binwidth breaks method fill color alpha)bins- number of bins (default 30)
binwidth- bin width in data units (overrides
bins) method'sturges(default),'scott,'fd,'equal-width
(layer-density #!key data mapping bandwidth kernel n adjust fill color alpha)kernel'gaussian(default),'epanechnikov,'rectangular,'triangular,'biweightn- number of evaluation points (default 512)
(layer-boxplot #!key data mapping coef varwidth fill color outlier-color outlier-size width)coef- IQR multiplier for whisker length (default 1.5)
(layer-violin #!key data mapping scale draw-quantiles bandwidth kernel fill color alpha)scale'area(equal area, default) or'count(proportional to n)draw-quantiles- list of quantile levels, e.g.
'(0.25 0.5 0.75)
(layer-errorbar #!key data mapping stat fun-ymin fun-ymax color width cap-width)stat'identity(useymin/ymaxcolumns) or'summary(compute from raw values)
position'stack(default),'dodge,'fill
Annotation layers
Coordinates are in data space.
For reference lines with full linetype support, the following are recommended over layer-hline/layer-vline when annotation-layer dispatch is needed:
Scale specifications
Scale specification functions return tagged lists consumed by ggplot.
Continuous position scales
name- axis title string
limits- explicit domain
(min max) breaks- explicit tick positions
trans- transformation symbol:
'log,'log10,'sqrt, … expand- fractional padding added to domain on each side
Shorthand for a continuous scale with trans: 'log and the given base (default 10).
Discrete position scales
Color / fill scales
values is an alist: '(("Control" . "blue") ("Treatment" . "red")).
(scale-color-gradient #!key low high limits)Two-stop continuous gradient.
Coordinate systems
(coord-cartesian #!key xlim ylim)Default Cartesian coordinates with optional axis limits.
(coord-fixed #!key ratio xlim ylim)Fixed aspect ratio (default 1:1).
(coord-flip)Swap x and y axes.
Faceting
(facet-null)No faceting (single panel, default).
(facet-wrap var #!key ncol nrow scales)Wrap panels by var (a quoted symbol). scales is one of "fixed" (default), "free", "free_x", "free_y".
(facet-grid row-var col-var #!key scales space shrink labeller)Two-dimensional panel grid. Pass #f for either variable to omit that dimension.
Themes
(theme-gray #!key base-size base-family)Gray background with white grid lines (the default ggplot2-style theme).
(theme-minimal #!key base-size base-family)White background, no border, gray grid lines. base-size defaults to 11.
(theme-classic #!key base-size base-family)White background, no grid lines, axis lines present.
(theme-bw #!key base-size)White background, black border, gray grid lines.
(theme-linedraw #!key base-size base-family)White background, black grid lines and border.
(theme-light #!key base-size base-family)Light gray border and grid lines.
(theme-dark #!key base-size base-family)Dark background with contrasting grid lines; suitable for bright-colored data.
(theme-void #!key base-size)No background, no axes, no grid.
Legend position modifiers
(theme-legend-position position)position: 'right (default), 'left, 'top, 'bottom, 'none, 'inside, 'outside.
(theme-legend-inside position)position: 'top-right, 'top-left, 'bottom-right, 'bottom-left, 'center.
(theme-legend-outside position)position: 'right, 'left, 'top, 'bottom.
Labels
Collision-avoiding text
Repel overlapping text labels using a force-directed algorithm. Accepts the same arguments as layer-text.
Plot-spec accessors
Module: gg-primitives-vge
Low-level composable drawing layer. A drawer is a record wrapping a procedure that emits instructions into a VGE (Virtual Graphics Engine). Drawers are first-class values: they can be stored, composed with combine, and wrapped with style or transform combinators before being rendered to any backend.
Coordinate convention: right-handed, origin at bottom-left, Y-up. All coordinates are in user space; the backend maps them to device pixels.
Drawer record
(make-drawer proc bounds)Create a drawer from a procedure (vge -> void) and an optional bounding box (or #f).
(drawer? obj)True if obj is a drawer record.
Accessors.
empty-drawerA drawer that does nothing (identity for combine).
Composition
(combine drawer ...)Return a new drawer that executes each argument drawer left-to-right with no additional scoping.
(combine-group drawer ...)Like combine but wraps the result in a gfx:group scope.
Rendering
(render-drawer drawer vge)Emit a drawer's instructions into vge (a VGE accumulator created by make-vge from gg-vge). Does not perform I/O; call vge-render! afterwards to send the instructions to a backend.
Scoped state wrappers
Each wrapper produces a gfx:group, so the state change is automatically scoped; no explicit push/pop is needed.
(with-pushed-state drawer)Raw push/pop scope (low-level; prefer the typed wrappers below).
angle in radians.
Style wrappers
(with-pen-color color drawer)color is a color string ("steelblue", "#3f7ab5") or a color:rgba / color:named value from gg-backend.
width in user coordinates.
(with-dash dashes offset drawer)dashes is a list of on/off lengths; offset is the phase.
(with-font family size slant weight drawer)family is a font family string (e.g. "sans-serif"); slant is 'normal or 'italic; weight is 'normal or 'bold.
Stroked drawing primitives
pts is a list of (x . y) pairs.
(rect-drawer x y w h)Outline rectangle; bottom-left corner at (x, y).
Filled drawing primitives
Text
(text-drawer x y text #!key halign valign)Draw text anchored at (x, y).
halign'left,'center(default), or'rightvalign'top,'center,'bottom, or'baseline(default)
Convenience drawers
cmds is a list of path commands from gg-backend: path:move-to, path:line-to, path:curve-to, path:arc, path:close.
Module: gg-backend-cairo
Cairo graphics backend. Implements the abstract backend protocol from gg-backend for four output formats.
(make-cairo-png-backend filename width height)PNG raster output. width and height are in pixels.
(make-cairo-svg-backend filename width height)SVG vector output.
(make-cairo-ps-backend filename width height)PostScript output.
(make-cairo-pdf-backend filename width height)PDF output. width and height are in points (72 pt = 1 inch; A4 portrait = 595 × 842 pt).
Module: gg-scales
Scales map between the data domain and the visual range. All scale objects implement the same protocol (via YASOS), so geometry and guide code is scale-type–agnostic.
Scale protocol
Returns a symbol: 'linear, 'log, 'sqrt, 'power, 'ordinal, 'band, 'color-gradient, 'color-manual, …
(scale-train! scale values)Extend the domain to include values (a list). Mutates the scale.
(scale-map scale value)Map a single domain value to the visual range.
(scale-inverse scale value)Map from visual range back to domain.
(scale-breaks scale [n])Return a list of n (default 5) nicely-placed tick positions.
(scale-labels scale breaks)Return formatted label strings for the given break positions.
(scale-domain scale)Current domain as a (min . max) pair, or a list for discrete scales.
(scale-range scale)Current visual range as a (min . max) pair.
Mutating setters.
(scale-bandwidth scale)Width of a single band (for band scales only).
Functional scale combinators
These return a new scale and leave the original unmodified.
transform-type is one of: 'log, 'log10, 'log2, 'ln, 'sqrt, 'identity.
(scale-with-breaks scale explicit-breaks)Override automatic break computation with a fixed list.
Continuous scale constructors
base defaults to 10.
exponent defaults to 2.
Discrete scale constructors
(make-scale-ordinal #!key domain range)Maps each domain value to the corresponding entry in range, cycling if necessary.
(make-scale-band #!key domain range padding)Like ordinal but maps to a continuous sub-interval (band) for bar charts. padding (default 0.1) is the fraction of each step reserved for spacing.
Color scale constructors
(make-scale-color-gradient #!key domain low high)Continuous gradient interpolating between low and high color strings. Accepts named colors ("blue") and hex strings ("#3f7ab5").
(make-scale-color-diverging #!key domain low mid high midpoint)Two-segment gradient with a midpoint color.
(make-scale-color-ordinal #!key domain palette)Ordinal scale backed by a list of color strings.
(make-scale-color-manual value-map)Explicit category -> color alist, e.g. `(("Control" . "blue") ("Treatment" . "red"))
Break utilities
Extend domain by the fractional expansion on each side (e.g. 0.05 adds 5 % padding).
Module: gg-data
Columnar data frames: an association list mapping column names (symbols) to parallel value lists.
(define data
'((time . (0 10 20 30 40 50))
(value . (1.2 1.5 2.3 1.8 1.4 1.9))
(group . ("A" "A" "A" "B" "B" "B"))))(data-column data column-name)Extract a column list. Signals an error if the column is absent.
(data-columns data)List of column name symbols.
Single row as an association list ((col . value) ...).
(data-rows data)All rows as a list of association lists. Used internally by geometries.
Convert between the columnar and row-oriented representations.
Module: gg-aes
Aesthetic mappings link data columns to visual channels.
Creating mappings
Both are equivalent. Column references are symbols; literal values are strings or numbers:
(aes #:x 'time #:y 'value #:color 'condition) ; x, y, color mapped to columns
(aes #:x 'time #:y 'value #:color "steelblue") ; color is a constant(aesthetic-mapping? obj)Accessors
Utilities
(aes-merge base override)Merge two mappings; override wins on conflicts.
Resolve an aesthetic: if the stored value is a symbol, look it up in data-row (an association list); otherwise return the literal value.
Module: gg-geom
Geometry functions take a columnar data frame and an aesthetic mapping and return (values drawer trained-scales). They are normally used indirectly through the layer constructors in gg-plot, but can be called directly for custom pipelines.
Core geometries
(geom-point data aes #!key scales size shape alpha)Scatter plot. Required aesthetics: x, y. Optional: color, fill, size.
(geom-line data aes #!key scales width style)Line plot. Groups rows by the color or group aesthetic and draws a separate polyline per group. Required: x, y.
(geom-path data aes #!key scales width color)Like geom-line but connects points in data order without sorting.
(geom-area data aes #!key scales fill alpha)Filled area. With only x/y aesthetics, fills from 0 to y (simple area). With ymin/ymax aesthetics, fills between the two curves (ribbon).
(geom-bar data aes #!key scales fill width)Bar chart. Required: x, y.
(geom-rect data aes #!key scales)Draws rectangles from xmin/xmax/ymin/ymax aesthetics.
(geom-text data aes #!key scales size color)Text labels positioned at x/y, string from label aesthetic.
(geom-segment data aes #!key scales color width)Line segments from (x, y) to (xend, yend).
Reference lines.
Event plot
(geom-eventplot data aes #!key scales line-length width color)Draws one vertical tick per event time. Expects x to hold a list of timestamps per row and y to hold the row index (trial number).
(define spike-data
'((trial . (0 1 2 3 4))
(spikes . ((12 25 38) (15 28 42) (10 23 40) (18 30 45) (13 27 41)))))
(geom-eventplot spike-data (aes #:x 'spikes #:y 'trial)
#:line-length 0.7 #:width 1 #:color "black")Statistical geometries
Module: gg-guides
Axis and legend rendering. Returns drawers positioned in axis-local coordinates; gg-plot translates them to panel coordinates.
Axes
Convenience wrappers around:
(make-axis scale position #!key label tick-count tick-length tick-width label-size label-offset line-color)position'bottom,'top,'left, or'righttick-count- target number of ticks (default 5)
tick-length- length of tick marks in user units (default 5)
tick-width- stroke width of ticks (default 1)
label-size- font size for tick labels (default 10.0)
label-offset- distance from axis line to label anchor in user units (default 15)
line-color- color string (default
"black")
(axis-drawer axis-config)Return a drawer for the axis. The drawer is in axis-local coordinates (the axis line lies at x=0 for vertical axes, y=0 for horizontal ones); the caller must apply with-translate to place it at the panel edge.
Legends
Data format
All geometries expect data as a columnar alist, an association list mapping column names (symbols) to lists of equal length:
;; Columnar alist (required by the library)
'((time . (0 10 20 30 40 50))
(response . (0.89 1.29 2.35 1.68 2.58 2.08))
(condition . ("A" "A" "A" "B" "B" "B")))Use columns->rows / rows->columns from gg-data to convert between row-oriented and column-oriented representations.
Examples
Minimal scatter plot
(import gg-plot gg-backend-cairo)
(define data
'((x . (1 2 3 4 5))
(y . (2.1 3.8 2.9 5.1 4.3))))
(define p
(ggplot data (aes #:x 'x #:y 'y)
(layer-point #:size 6 #:color "steelblue")
(scale-x-continuous #:name "X")
(scale-y-continuous #:name "Y")
(theme-minimal)
(labs #:title "Simple Scatter")))
(ggsave p "scatter.png" #:width 600 #:height 400)Time series with confidence ribbon
(import gg-plot gg-backend-cairo)
(define data
'((time . (0 10 20 30 40 50 60 70 80 90 100))
(value . (0.89 1.29 2.35 1.68 1.15 1.81 2.58 2.08 1.57 1.94 2.35))
(upper . (1.66 1.95 2.72 2.12 1.71 2.21 2.96 2.39 2.00 2.27 2.67))
(lower . (0.55 0.83 1.56 0.75 0.76 1.16 2.19 1.50 0.91 1.16 2.00))))
(define p
(ggplot data (aes #:x 'time #:y 'value)
;; Shaded confidence band
(layer-area #:mapping (aes #:ymin 'lower #:ymax 'upper)
#:fill "steelblue" #:alpha 0.3)
;; Central line and points
(layer-line #:width 2 #:color "black")
(layer-point #:size 5 #:color "black")
(scale-x-continuous #:name "Time (seconds)")
(scale-y-continuous #:name "Response (mV)")
(theme-minimal #:base-size 12)
(labs #:title "Time Series with Confidence Band"
#:subtitle "Example of layered composition")))
(ggsave p "timeseries.png" #:width 800 #:height 600)Multi-series line plot with manual colors
(import gg-plot gg-backend-cairo)
(define data
'((time . (0 10 20 30 40 50 60 0 10 20 30 40 50 60))
(value . (1.2 2.3 1.8 2.5 2.1 1.9 2.4
0.8 1.5 1.2 1.8 1.6 1.4 1.7))
(condition . ("Control" "Control" "Control" "Control"
"Control" "Control" "Control"
"Treatment" "Treatment" "Treatment"
"Treatment" "Treatment" "Treatment" "Treatment"))))
(define p
(ggplot data (aes #:x 'time #:y 'value #:color 'condition)
(layer-line #:width 2)
(layer-point #:size 4)
(scale-x-continuous #:name "Time (ms)")
(scale-y-continuous #:name "Response")
(scale-color-manual #:values '(("Control" . "blue")
("Treatment" . "red")))
(theme-classic #:base-size 11)
(labs #:title "Treatment vs Control")))
(ggsave p "multiseries.png" #:width 800 #:height 600)Bar chart with categorical x axis
(import gg-plot gg-backend-cairo)
(define data
'((region . ("V1" "V2" "V4" "MT" "IT"))
(firing-rate . (12.5 8.3 15.7 22.1 9.4))))
(define p
(ggplot data (aes #:x 'region #:y 'firing-rate)
(layer-bar #:fill "steelblue" #:width 0.7)
(scale-x-discrete #:name "Brain Region")
(scale-y-continuous #:name "Firing Rate (Hz)" #:limits '(0 25))
(theme-bw)
(labs #:title "Firing Rates Across Brain Regions")))
(ggsave p "bars.png" #:width 700 #:height 500)Spike raster (neuroscience event plot)
(import gg-plot gg-backend-cairo)
(define spike-data
'((trial . (0 1 2 3 4 5 6 7 8 9))
(spikes . ((12 25 38 55 67)
(15 28 42 58 71)
(10 23 40 52 65)
(18 30 45 60 73)
(13 27 41 56 68)
(16 29 43 59 72)
(11 24 39 54 66)
(14 26 44 57 70)
(17 31 46 61 74)
(9 22 37 53 64)))
(condition . ("Pre" "Pre" "Pre" "Pre" "Pre"
"Post" "Post" "Post" "Post" "Post"))))
(define p
(ggplot spike-data (aes #:x 'spikes #:y 'trial #:color 'condition)
(layer-eventplot #:line-length 0.7 #:width 4)
;; Stimulus onset marker
(layer-vline #:xintercept 20 #:color "red" #:linetype 'dashed #:width 1.5)
(scale-x-continuous #:name "Time (ms)" #:limits '(0 80))
(scale-y-continuous #:name "Trial #")
(scale-color-manual #:values '(("Pre" . "black") ("Post" . "royalblue")))
(theme-classic #:base-size 11)
(labs #:title "Spike Raster Plot"
#:subtitle "Pre vs Post stimulus")))
(ggsave p "raster.png" #:width 800 #:height 600)Logarithmic axes
(import gg-plot gg-backend-cairo)
(define data
'((concentration . (0.1 1 10 100 1000))
(response . (2.1 8.5 42.3 201.7 987.2))))
(define p
(ggplot data (aes #:x 'concentration #:y 'response)
(layer-point #:size 6 #:color "coral")
(layer-line #:width 2 #:color "coral")
(scale-x-log #:name "Concentration (uM)" #:base 10)
(scale-y-log #:name "Response (% baseline)" #:base 10)
(theme-minimal #:base-size 12)
(labs #:title "Dose-Response Curve" #:subtitle "Log-log axes")))
(ggsave p "logscale.png" #:width 700 #:height 600)Faceted plot (small multiples)
(import gg-plot gg-backend-cairo)
(define data
'((time . (0 10 20 30 0 10 20 30 0 10 20 30))
(power . (8 12 15 11 5 9 14 10 12 18 22 17))
(band . ("Alpha" "Alpha" "Alpha" "Alpha"
"Beta" "Beta" "Beta" "Beta"
"Gamma" "Gamma" "Gamma" "Gamma"))))
(define p
(ggplot data (aes #:x 'time #:y 'power)
(layer-line #:color "steelblue" #:width 2)
(layer-point #:size 4 #:color "darkblue")
(facet-wrap 'band #:ncol 3 #:scales "free_y")
(scale-x-continuous #:name "Time (s)")
(scale-y-continuous #:name "Power (dB)")
(theme-bw #:base-size 10)
(labs #:title "Power Across Frequency Bands")))
(ggsave p "faceted.png" #:width 1200 #:height 400)Multiple output formats
(import gg-plot gg-backend-cairo)
(define p (ggplot data (aes #:x 'x #:y 'y)
(layer-point #:size 6)
(theme-minimal)
(labs #:title "Multi-format")))
;; PNG raster
(ggsave p "out.png" #:width 600 #:height 400)
;; High-resolution PNG (larger pixel dimensions)
(ggsave p "out-hires.png" #:width 2400 #:height 1600)
;; SVG vector
(ggsave p "out.svg" #:width 600 #:height 400)
;; PostScript
(ggsave p "out.ps" #:width 600 #:height 400)
;; PDF (A4 portrait, dimensions in points)
(render-plot p (make-cairo-pdf-backend "out.pdf" 595 842))Plot specification serialisation
(import gg-plot gg-backend-cairo)
(define p (ggplot data (aes #:x 'x #:y 'y)
(layer-point #:size 6)
(scale-x-continuous #:name "X")
(scale-y-continuous #:name "Y")
(theme-classic)
(labs #:title "Saved plot")))
;; Serialise to an s-expression and write to disk
(define spec (plot->sexp p))
(with-output-to-file "my-plot.scm"
(lambda () (write spec) (newline)))
;; Later: load and re-render
(define restored
(sexp->plot (with-input-from-file "my-plot.scm" read)))
(ggsave restored "restored.png" #:width 700 #:height 500)Programmatic plot generation
;; Build a plot that layers one line per variable
(define (multi-line-plot data vars)
(apply ggplot data (aes #:x 'time)
(append
(map (lambda (var)
(layer-line #:mapping (aes #:y var) #:width 2))
vars)
(list (scale-x-continuous #:name "Time (s)")
(scale-y-continuous #:name "Value")
(theme-minimal)
(labs #:title (format "Variables: ~a" vars))))))Architecture notes
Scale training
Scales are mutable: scale-train! extends the domain and scale-map reflects the updated domain. The plot pipeline trains each scale by scanning all layers before any geometry is rendered, which allows automatic axis limits that span the full data range across layers.
Drawer composition model
All rendering is expressed as composable drawer values (drawer?). The pipeline builds one large combined drawer per panel and then executes it in a single pass over the plotter.
Author
Repository
https://github.com/iraikov/chicken-ggplot
Version History
- 1.3
- Ported to CHICKEN 6
- 1.0
- Initial release
License
LPGL-3
References
- Wilkinson, Leland. The grammar of graphics. Springer Science & Business Media, 2005.
- The R ggplot2 package is described in the manuscript, "A layered grammar of graphics", by Hadley Wickham in the Journal of Computational and Graphical Statistics, 2010.