This is the multi-page printable view of this section. Click here to print.
Cookbooks
- 1: Quick Plot
- 2: Vega Plotting
1 - Quick Plot
Overview
The geom and gg packages provide a set of composable helper
functions for building Vega-Lite
plot specifications. Inspired by R’s
ggplot2, specifications are
constructed by combining independent layers — geometry, labels, scales,
coordinates and themes — rather than writing monolithic JSON-like
plists by hand.
Each helper returns a plist. The function merge-plists recursively
merges them into a single Vega-Lite spec, which vega:defplot
compiles and plot:plot renders.
Package setup
The examples below
assume you are working in the LS-USER package so you need to use the
qualified forms (geom:point, gg:label, etc.) or import the
appropriate symbols into the current package (which should be
LS-USER):
;; Import marks
(import '(geom:point geom:bar geom:box-plot geom:histogram geom:line geom:loess geom:func))
;; Import modifiers
(import '(gg:label gg:axes gg:coord gg:theme gg:tooltip gg:layer))
;; Import quick plotting
(import '(qplot:qplot))
Design philosophy
In ggplot2, a plot is built from independent concerns:
| ggplot2 | Lisp-Stat | Package | Responsibility |
|---|---|---|---|
geom_point() |
point |
geom |
Mark type and encoding |
geom_bar() |
bar |
geom |
Mark type and encoding |
geom_boxplot() |
box-plot |
geom |
Mark type and encoding |
geom_histogram() |
histogram |
geom |
Binning, aggregation |
geom_line() |
line |
geom |
Series connection, interpolation |
labs() |
label |
gg |
Axis titles |
scale_*() |
axes |
gg |
Axis transforms, domains, color schemes |
coord_cartesian() |
coord |
gg |
Viewport clipping |
theme() |
theme |
gg |
Dimensions, fonts, appearance |
| (no direct equiv.) | tooltip |
gg |
Hover field definitions |
Each function knows about one concern and nothing else. A mark
function never sets axis titles; label never touches mark types;
theme never alters encodings. This separation means any helper can
be used with any plot type.
The merge pattern
Every helper returns a plist fragment. merge-plists performs a
recursive deep merge: when two plists both supply a nested plist for
the same key (e.g. :encoding), the inner plists are merged rather
than one replacing the other. This is what allows label to add
:axis entries to the :encoding that point already created.
Note that merge-plists is a utility function, not a layer helper —
it is the mechanism that makes composition work, not a layer you
pass to qplot yourself. See the API reference for full details.
Two ways to plot
Lisp-Stat provides two entry points for creating plots. Choose the one that fits your workflow.
defplot + plot:plot — the explicit pattern
The traditional approach separates definition from rendering. Use this
when you want full control, or when writing scripts and notebooks.
Replace :x-field, :y-field, and my-data with your actual field
names and data source:
(plot:plot
(vega:defplot my-plot
(merge-plists
`(:title "My Plot"
:data (:values ,my-data))
(point :x-field :y-field)
(label :x "X Label" :y "Y Label")
(theme :width 600))))
defplot is a macro that:
- Calls
%defplotto create avega-plotobject - Binds it to a global variable (
my-plot) - Registers it in
*all-plots*soshow-plotscan list it
plot:plot then renders the object to the browser.
qplot — quick plot for the REPL
For interactive exploration, the three-line scaffold of plot:plot /
vega:defplot / merge-plists is repetitive. qplot collapses it
into a single function call:
(qplot 'my-plot my-data
`(:title "My Plot")
(point :x-field :y-field)
(label :x "X Label" :y "Y Label")
(theme :width 600))
qplot takes a name (a symbol), a data object (a data frame,
plist data, or URI), and any number of layer plists. It:
- Prepends
(:data (:values ,data))and merges all layers - Creates the plot object via
%defplot - Binds it to the named global variable
- Registers it in
*all-plots* - Renders immediately via
plot:plot - Returns the plot object
Because qplot binds a named variable, the standard REPL workflow is
to re-evaluate the same form as you iterate on a plot. Each call
overwrites the previous definition — there is no accumulation in
*all-plots* or in the global namespace:
;; First attempt — rough sketch
(qplot 'cars vgcars
(point :horsepower :miles-per-gallon))
;; Second attempt — add color and a title
(qplot 'cars vgcars
`(:title "HP vs MPG")
(point :horsepower :miles-per-gallon :color :origin :filled t))
;; Third attempt — polish for presentation
(qplot 'cars vgcars
`(:title "HP vs MPG")
(point :horsepower :miles-per-gallon :color :origin :filled t)
(label :x "Horsepower" :y "Fuel Efficiency")
(theme :width 600 :height 400))
;; Later — the variable is still bound
cars ; => #<VEGA-PLOT ...>
(plot:plot cars) ; re-render
(describe cars) ; inspect the spec
(show-plots) ; lists one 'cars' entry
qplot vs defplot
qplot and defplot produce identical plot objects. The only
differences are syntactic: qplot separates the data argument,
handles the merge, and renders immediately. You can freely mix both
styles in the same session — they share *all-plots* and the global
namespace.
Helpers reference
The layering helpers work with all mark types.
label — Set axis titles.
(label :x "Horsepower" :y "Miles per Gallon")
axes — Set axis types, domains, ranges, and color schemes.
(axes :x-type :log :color-scheme :dark2)
(axes :x-domain #(0 300) :y-domain #(0 50))
tooltip — Add hover tooltips. Each argument is a field spec.
(tooltip '(:field :name :type :nominal)
'(:field :horsepower :type :quantitative))
coord — Restrict the visible viewport and clip marks, like
coord_cartesian() in ggplot2. Only data within the domain is
visible; points outside are clipped rather than dropped.
(coord :x-domain #(50 150) :y-domain #(20 40))
theme — Set dimensions, font, background, or named presets.
(theme :width 600 :height 400 :font "Georgia")
coord vs. axes
axes with :x-domain changes the axis range but does not clip
marks — points outside the domain overflow visibly. coord sets the
domain and clips marks to the viewport, matching coord_cartesian()
semantics. Choose coord when you want to zoom into a region;
choose axes when you want to change the axis transform without
hiding data.
Vectors for JSON arrays
Vega-Lite expects JSON arrays for domain, range, tooltip, and
similar properties. In the JSON serializer, lists become JSON
objects and vectors become JSON arrays. Always use #(...) or
(vector ...) when you need a JSON array:
;; Correct — produces [50, 150]
(coord :x-domain #(50 150))
;; Wrong — produces {"50": 150}
(coord :x-domain '(50 150))
Keywords vs. strings for field names
Field names are passed as keywords (e.g. :horsepower,
:origin), not strings. The helpers convert keywords to the
camelCase Vega-Lite field names automatically. Only human-readable
labels — axis titles, plot titles, descriptions — are strings.
;; Correct — keywords for fields, strings for labels
(point :horsepower :miles-per-gallon :color :origin)
(label :x "Engine Horsepower" :y "Fuel Efficiency")
;; Wrong — strings for field names
(point :horsepower :miles-per-gallon :color "origin")
Keywords vs. strings for color values
The :color parameter accepts both keywords and strings, but they
mean different things:
- Keyword → a data field mapped to the color encoding channel. Each unique value in the field gets a distinct hue.
- String → a literal CSS color applied directly to the mark.
;; Keyword — map the :origin field to color (encoding)
(point :horsepower :miles-per-gallon :color :origin)
;; String — paint every point teal (mark property)
(point :horsepower :miles-per-gallon :color "teal")
;; Same convention applies to all mark types
(histogram :miles-per-gallon :color :origin) ; stacked by origin
(histogram :miles-per-gallon :color "darkslategray") ; uniform bar color
(bar :origin :miles-per-gallon :color "teal") ; uniform bar color
This convention is consistent across all mark helpers (point,
bar, histogram, box-plot, line) and all visual channel
parameters (:color, :size, :opacity, etc.): keywords are
field names, strings are literal values.
Loading example data
The examples below use datasets from the Vega datasets collection. Load them into your session before running the examples:
(vega:load-vega-examples)
This makes the following variables available in your environment:
vgcars— Automobile specifications (horsepower, MPG, origin, etc.) for 406 cars.stocks— Daily closing prices for several major tech stocks over multiple years.
For datasets loaded using vega:read-vega, field names are
automatically converted to Lisp-style keywords (e.g.
Miles_per_Gallon becomes :miles-per-gallon). Note that the
:year field in vgcars is stored as a date string (e.g.
"1970-01-01") rather than an integer, which is why the line chart
examples pass :x-type :temporal for that field.
Scatter plot
A scatter plot maps two quantitative variables to x and y positions.
Use point for exploring relationships, correlations and clusters in
data. The name follows the ggplot2 convention where geom_point()
produces a scatter plot.
Basic scatter plot
The simplest scatter plot needs just a name, a data frame, and two field names:
(qplot 'cars-basic vgcars
`(:title "Horsepower vs. MPG")
(point :horsepower :miles-per-gallon))
Color by category
Pass a keyword to :color to map a nominal variable to hue. Use
:filled t to fill the point marks:
(qplot 'cars-colored vgcars
`(:title "Cars by Origin")
(point :horsepower :miles-per-gallon
:color :origin :filled t))
Bubble plot
When :size is a keyword, it encodes a third quantitative field as
point area — a bubble plot:
(qplot 'cars-bubble vgcars
`(:title "Bubble: Size = Acceleration")
(point :horsepower :miles-per-gallon
:color :origin :size :acceleration
:filled t)
(label :x "Horsepower" :y "Miles per Gallon"))
Adding axis labels
Use label to give axes meaningful titles:
(qplot 'cars-with-labels vgcars
`(:title "Vega Cars")
(point :horsepower :miles-per-gallon :filled t)
(label :x "Engine Horsepower" :y "Fuel Efficiency (MPG)"))
Log scale with custom color scheme
Use axes to transform an axis and change the color palette:
(qplot 'cars-log-scale vgcars
`(:title "Horsepower (log) vs. MPG")
(point :horsepower :miles-per-gallon
:color :origin :filled t)
(axes :x-type :log :color-scheme :dark2)
(label :x "Horsepower (log scale)" :y "Miles per Gallon"))
Tooltips on hover
Add tooltip to show details when the user hovers over a point:
(qplot 'cars-tooltip vgcars
`(:title "Car Details on Hover")
(point :horsepower :miles-per-gallon
:color :origin :filled t)
(tooltip '(:field :name :type :nominal)
'(:field :horsepower :type :quantitative)
'(:field :miles-per-gallon :type :quantitative)
'(:field :origin :type :nominal)))
Zoom into a region
Use coord to restrict the visible area. Unlike axes, this
clips marks that fall outside the domain — only points within the
viewport are drawn:
(qplot 'cars-zoomed vgcars
`(:title "Cars: 50-150 HP, 20-40 MPG")
(point :horsepower :miles-per-gallon
:color :origin :filled t)
(coord :x-domain #(50 150) :y-domain #(20 40))
(label :x "Horsepower" :y "Miles per Gallon"))
Custom theme
Use theme to set plot dimensions, font, and visual style:
(qplot 'cars-themed vgcars
`(:title "Themed Scatter Plot")
(point :horsepower :miles-per-gallon
:color :origin :filled t)
(label :x "Horsepower" :y "MPG")
(theme :width 600 :height 400 :font "Georgia"))
Full example — all layers
Combine every layer for a production-quality plot:
(qplot 'cars-full vgcars
`(:title "Complete Example: All Layers"
:description "Demonstrating label, axes, tooltip, coord, and theme")
(point :horsepower :miles-per-gallon
:color :origin
:size :acceleration
:filled t)
(label :x "Engine Horsepower" :y "Fuel Efficiency (MPG)")
(axes :color-scheme :category10)
(tooltip '(:field :name :type :nominal)
'(:field :horsepower :type :quantitative)
'(:field :miles-per-gallon :type :quantitative)
'(:field :origin :type :nominal))
(coord :x-domain #(40 240) :y-domain #(5 50))
(theme :width 700 :height 450))
LOESS smoother
A LOESS (locally estimated scatterplot smoothing) curve fits a
non-parametric smooth line through data, revealing trends without
assuming a fixed functional form. Use loess to overlay a trend
line on a scatter plot or to compare smoothed trajectories across
groups.
Scatter plot with LOESS smoother
Use gg:layer to compose the scatter points and the smoother into
a single layered view:
(qplot 'cars-loess vgcars
`(:title "HP vs. MPG with LOESS Smoother")
(gg:layer
(point :horsepower :miles-per-gallon
:color :origin :filled t :opacity 0.5)
(loess :horsepower :miles-per-gallon
:group :origin
:stroke-width 2)))
The :group :origin argument fits a separate curve for each origin
and encodes it with the matching hue automatically. Increase
:bandwidth toward 1.0 for a flatter, more global fit; decrease
it toward 0.05 for a curve that tracks local variation closely.
Histogram
A histogram bins a quantitative variable and counts observations per
bin. Use histogram to visualize the distribution of a single
variable.
Basic histogram
Pass a single field name. The default uses Vega-Lite’s automatic binning and counts occurrences:
(qplot 'mpg-hist vgcars
`(:title "Distribution of Miles per Gallon")
(histogram :miles-per-gallon)
(label :x "Miles per Gallon" :y "Count"))
Custom bin count
Control granularity with the :bin keyword. Pass a plist with
:maxbins to limit the number of bins:
(qplot 'mpg-hist-bins vgcars
`(:title "MPG Distribution (10 bins)")
(histogram :miles-per-gallon :bin '(:maxbins 10))
(label :x "Miles per Gallon" :y "Count"))
Horizontal histogram
Set :orient :horizontal to place bins on the y-axis:
(qplot 'mpg-hist-horiz vgcars
`(:title "MPG Distribution (Horizontal)")
(histogram :miles-per-gallon :orient :horizontal)
(label :x "Count" :y "Miles per Gallon"))
Stacked histogram by group
Pass :group to split bins by a nominal field. Vega-Lite
automatically stacks the bars:
(qplot 'mpg-hist-stacked vgcars
`(:title "MPG Distribution by Origin")
(histogram :miles-per-gallon :group :origin)
(label :x "Miles per Gallon" :y "Count"))
Layered histogram
Use :stack :null with :opacity to overlay distributions
transparently instead of stacking them:
(qplot 'mpg-hist-layered vgcars
`(:title "MPG: Overlaid by Origin")
(histogram :miles-per-gallon
:group :origin
:stack :null
:opacity 0.5)
(label :x "Miles per Gallon" :y "Count"))
Normalized (100%) stacked histogram
Use :stack :normalize to show proportions instead of counts:
(qplot 'mpg-hist-normalized vgcars
`(:title "MPG: Proportion by Origin")
(histogram :miles-per-gallon
:group :origin
:stack :normalize)
(label :x "Miles per Gallon" :y "Proportion"))
Styled histogram
Use :color, :corner-radius-end, and :bin-spacing for visual
polish. Combine with theme for custom dimensions:
(qplot 'mpg-hist-styled vgcars
`(:title "Styled Histogram")
(histogram :miles-per-gallon
:color "darkslategray"
:corner-radius-end 3
:bin-spacing 0)
(label :x "Miles per Gallon" :y "Count")
(theme :width 500 :height 300))
Bar chart
A bar chart maps a categorical variable to position and a
quantitative variable to bar length. Use bar when your x-axis is
nominal or ordinal rather than a continuous distribution. The name
follows the ggplot2 convention where geom_bar() produces a bar
chart.
Basic bar chart
Supply the categorical field and the quantitative field:
(qplot 'origin-bar vgcars
`(:title "Average MPG by Origin")
(bar :origin :miles-per-gallon :aggregate :mean)
(label :x "Origin" :y "Mean Miles per Gallon"))
Horizontal bar chart
Set :orient :horizontal to swap axes — useful for long category
labels:
(qplot 'origin-bar-horiz vgcars
`(:title "Average MPG by Origin (Horizontal)")
(bar :origin :miles-per-gallon
:aggregate :mean
:orient :horizontal)
(label :x "Mean Miles per Gallon" :y "Origin"))
Grouped (stacked) bar chart
Pass :group to split bars by a second nominal field. Bars are
stacked by default:
(qplot 'cylinders-by-origin vgcars
`(:title "Car Count: Cylinders by Origin")
(bar :cylinders :miles-per-gallon
:aggregate :count
:group :origin)
(label :x "Cylinders" :y "Count"))
Styled bar chart
Combine visual options with layering helpers:
(qplot 'origin-bar-styled vgcars
`(:title "Mean MPG by Origin")
(bar :origin :miles-per-gallon
:aggregate :mean
:color "teal"
:corner-radius-end 4)
(label :x "Origin" :y "Mean MPG")
(theme :width 400 :height 300 :font "Helvetica"))
Box plot
A box plot summarizes the distribution of a quantitative variable,
showing the median, interquartile range and outliers. Use box-plot
to compare distributions across groups.
1D box plot
A single quantitative field produces a box plot summarizing the entire variable:
(qplot 'mpg-box-1d vgcars
`(:title "MPG Distribution")
(box-plot :miles-per-gallon)
(label :x "Miles per Gallon"))
2D box plot — compare groups
Pass :category to split the box plot by a nominal field:
(qplot 'mpg-box-by-origin vgcars
`(:title "MPG by Origin")
(box-plot :miles-per-gallon :category :origin)
(label :x "Miles per Gallon" :y "Origin"))
Vertical orientation
Set :orient :vertical to place categories on the x-axis and values
on the y-axis:
(qplot 'mpg-box-vertical vgcars
`(:title "MPG by Cylinders (Vertical)")
(box-plot :miles-per-gallon
:category :cylinders
:orient :vertical)
(label :x "Cylinders" :y "Miles per Gallon"))
Min-max whiskers
Set :extent "min-max" to extend whiskers to the minimum and
maximum values instead of the default 1.5× IQR (Tukey) whiskers:
(qplot 'mpg-box-minmax vgcars
`(:title "MPG by Origin (Min-Max Whiskers)")
(box-plot :miles-per-gallon
:category :origin
:extent "min-max") ; note string value
(label :x "Miles per Gallon" :y "Origin"))
Styled box plot
Combine visual options with layering helpers for presentation:
(qplot 'mpg-box-styled vgcars
`(:title "MPG by Origin")
(box-plot :miles-per-gallon
:category :origin
:orient :vertical
:size 40)
(label :x "Origin" :y "Miles per Gallon")
(theme :width 500 :height 350))
Line chart
A line chart connects data points in order, typically along a
temporal or sequential x-axis. Use line for time series, trends,
and any data where the relationship between consecutive points is
meaningful. The name follows the ggplot2 convention where
geom_line() produces a line chart.
Basic line chart
The simplest line chart needs two field names. Points are connected in x-axis order:
(qplot 'stock-basic stocks
`(:title "Google Stock Price"
:transform #((:filter "datum.symbol === 'GOOG'")))
(line :date :price :x-type :temporal)
(label :x "Date" :y "Price (USD)"))
Multiple series by color
Pass a keyword to :color to draw a separate line for each
category:
(qplot 'stock-colored stocks
`(:title "Stock Prices by Company")
(line :date :price :color :symbol :x-type :temporal)
(label :x "Date" :y "Price (USD)"))
Smoothed interpolation
Set :interpolate to control how points are connected. Common
values are :linear (default), :monotone (smooth, monotonic
curves), :step (step function), :basis (B-spline), and
:cardinal:
(qplot 'stock-smooth stocks
`(:title "Stock Prices (Smoothed)")
(line :date :price :color :symbol
:interpolate :monotone
:x-type :temporal)
(label :x "Date" :y "Price (USD)"))
Line with point markers
Set :point t to overlay point marks on each data position — useful
for sparse data or when exact values matter. Note that :x-type :temporal is required here because the :year field in vgcars is
stored as a date string rather than an integer:
(qplot 'mpg-trend vgcars
`(:title "Mean MPG by Model Year")
(line :year :miles-per-gallon
:point t :x-type :temporal)
(label :x "Model Year" :y "Miles per Gallon"))
Custom stroke width
Use :stroke-width to set a fixed line thickness:
(qplot 'stock-thick stocks
`(:title "AAPL Stock Price"
:transform #((:filter "datum.symbol === 'AAPL'")))
(line :date :price
:stroke-width 3
:x-type :temporal)
(label :x "Date" :y "Price (USD)")
(theme :width 600 :height 300))
Dashed lines
Use :stroke-dash with a vector to create dashed or dotted lines.
The vector specifies alternating dash and gap lengths:
(qplot 'stock-dashed stocks
`(:title "Stock Prices (Dashed)")
(line :date :price
:color :symbol
:stroke-dash #(6 3)
:opacity 0.8
:x-type :temporal)
(label :x "Date" :y "Price (USD)"))
Step chart
Use :interpolate :step for piecewise-constant lines — useful for
data that changes at discrete intervals (e.g. interest rates,
pricing tiers):
(qplot 'mpg-step vgcars
`(:title "MPG by Year (Step)")
(line :year :miles-per-gallon
:interpolate :step
:x-type :temporal)
(label :x "Model Year" :y "Miles per Gallon"))
Styled multi-series line chart
Combine all options with layering helpers for a polished presentation:
(qplot 'stock-full stocks
`(:title "Stock Comparison"
:description "Daily closing prices for major tech stocks")
(line :date :price
:color :symbol
:interpolate :monotone
:stroke-width 2
:x-type :temporal)
(label :x "Date" :y "Closing Price (USD)")
(axes :color-scheme :dark2)
(tooltip '(:field :symbol :type :nominal)
'(:field :date :type :temporal)
'(:field :price :type :quantitative))
(theme :width 700 :height 400))
Function curves
geom:func plots a Lisp function as a smooth line by evaluating it at
evenly-spaced sample points and embedding the resulting (x, y) pairs
directly in the Vega-Lite specification. It mirrors the behaviour of
R’s geom_function()
from ggplot2.
Unlike the data-driven helpers (point, bar, histogram, etc.),
func is self-contained: it carries its own :data block and
requires no external data frame. Use it anywhere you want to visualise
a mathematical relationship — probability densities, regression curves,
physical models, or any other computable function.
Import func alongside the other helpers you use:
(import '(geom:func))
How it works
func calls aops:linspace to generate n evenly-spaced x values
over the closed interval [xmin, xmax] specified by :xlim. It
then calls fn at each x, collects the (x, y) pairs into a vector
of plists, and embeds them as an inline Vega-Lite :data block.
A :line mark connects the points using the chosen interpolation
method (:monotone by default, giving smooth curves without
overshoot).
Points where fn signals a condition (e.g. (log 0), (/ 1 0))
or returns a non-finite value (± infinity, NaN) are silently
dropped. Vega-Lite renders a visible gap at each discontinuity —
the correct visual for functions like tan or 1/x.
Design note: self-contained data
All other geom helpers return only :mark and :encoding keys and
rely on the caller to supply :data. func also returns a :data
key, because the data is the function. This means it composes
slightly differently from the other geom helpers:
| Helper | Data source | Typical entry point |
|---|---|---|
point, bar, histogram, … |
external data frame | qplot |
func |
self-generated inline | defplot + vega:merge-plists |
For multi-layer plots (function overlaid on data) use Vega-Lite’s
:layer array directly inside defplot; see
Overlay on scatter data below.
The following table describes the ggplot2 equivalent and responsibility:
| ggplot2 | Lisp-Stat | Package | Responsibility |
|---|---|---|---|
geom_function() |
func |
geom |
Sample a function, encode as a line |
Reference
(geom:func fn &key xlim n color stroke-width stroke-dash opacity interpolate)
| Parameter | Type | Default | Description |
|---|---|---|---|
fn |
function | — | A Lisp function (real → real). Receives a double-float; must return a real. |
:xlim |
vector | #(0d0 1d0) |
Domain #(xmin xmax). Both endpoints are always sampled. |
:n |
integer ≥ 2 | 100 |
Number of sample points. Increase for oscillatory functions. |
:color |
string | nil |
CSS color for the line, e.g. "steelblue" or "#e63946". nil lets Vega-Lite choose. |
:stroke-width |
number | nil |
Line thickness in pixels. |
:stroke-dash |
vector | nil |
Dash/gap pattern, e.g. #(6 3) for dashes or #(2 2) for dots. |
:opacity |
number 0–1 | nil |
Line opacity. |
:interpolate |
keyword | :monotone |
Vega-Lite interpolation method. :linear, :basis, :cardinal, :step are also accepted. |
Vectors for JSON arrays
:xlim and :stroke-dash must be vectors (e.g. #(0 10)), not
lists. In the JSON serializer, lists become JSON objects while
vectors become JSON arrays.
color is always a CSS string
Unlike the other mark helpers,func does not support a keyword
:color for field-based color encoding — a function curve is a single
computed series and carries no nominal grouping field. :color always
takes a CSS color string (e.g. "steelblue").
Top-level :data in layer specs
vega:defplot always validates the top-level :data key. In a
:layer plot where every layer is self-contained (as all func
layers are), pass :data (:values #()) as a placeholder. Vega-Lite
discards it when each layer declares its own :data. This same
placeholder is needed for any defplot form that uses :layer
without shared top-level data.
Basic function plot
Supply a function and a domain. vega:merge-plists combines the
self-contained func layer with a title and axis labels:
(vega:defplot sine-wave
(vega:merge-plists
`(:title "Sine Wave")
(func #'sin :xlim #(-6.283 6.283) :n 200)
(label :x "x" :y "sin(x)")))
Custom domain and resolution
Increase :n for functions that oscillate rapidly. Use :xlim to
set the evaluation domain precisely:
(vega:defplot damped-oscillation
(vega:merge-plists
`(:title "Damped Oscillation")
(func (lambda (x) (* (exp (* -0.3 x)) (sin (* 4 x))))
:xlim #(0 20)
:n 400)
(label :x "t" :y "Amplitude")))
Functions with singularities
Points where fn raises a condition or returns ±infinity are silently
dropped. Vega-Lite draws a gap at each discontinuity — the correct
rendering for functions like tan(x):
(vega:defplot tangent-curve
(vega:merge-plists
`(:title "tan(x) — gaps at singularities")
(func #'tan :xlim #(-4.5 4.5) :n 500)
(label :x "x" :y "tan(x)")
(axes :y-domain #(-10 10))))
Controlling the visible range
For functions with large excursions near a singularity, useaxes
with :y-domain to restrict the visible range. Unlike coord,
axes changes the axis extent without clipping the line marks, which
avoids visual artefacts near the asymptotes.
Probability density function
Plot a probability density function using the distributions system.
Load it before running these examples:
(asdf:load-system :distributions)
Create a distribution object with distributions:r-normal, then pass
its pdf method to func. r-normal takes mean and
variance (not standard deviation), so the standard normal is
(r-normal 0d0 1d0):
(let ((d (distributions:r-normal 0d0 1d0))) ; mean=0, variance=1
(vega:defplot normal-pdf
(vega:merge-plists
`(:title "Standard Normal PDF")
(func (lambda (x) (distributions:pdf d x))
:xlim #(-4 4)
:n 200)
(label :x "x" :y "Density")
(theme :width 500 :height 300))))
r-normal takes variance, not standard deviation
distributions:r-normal is parameterised as (r-normal mean variance).
To construct a distribution from a standard deviation sigma, pass
(expt sigma 2) as the second argument. Passing sigma directly
will produce a distribution with the wrong spread and no error.
Styled function curve
Pass :color, :stroke-width, and :stroke-dash for visual polish.
To show two styled curves together, use :layer — each func call
carries its own inline data:
(vega:defplot sin-and-cos-styled
`(:title "sin and cos — styled lines"
:data (:values #())
:layer
#(,(func #'sin
:xlim #(-6.283 6.283)
:n 200
:color "steelblue"
:stroke-width 2)
,(func #'cos
:xlim #(-6.283 6.283)
:n 200
:color "firebrick"
:stroke-width 2
:stroke-dash #(8 4)))))
Step interpolation
Use :interpolate :step for piecewise-constant functions — useful for
visualising floor, ceiling, or any discrete-valued function:
(vega:defplot step-function
(vega:merge-plists
`(:title "Floor function")
(func #'ffloor
:xlim #(0 6)
:n 300
:interpolate :step
:color "darkslategray"
:stroke-width 2)
(label :x "x" :y "floor(x)")))
Two functions on the same axes
Use Vega-Lite’s :layer array directly inside defplot. Each
func call produces an independent layer with its own inline data:
(vega:defplot sin-vs-cos
`(:title "sin(x) and cos(x)"
:data (:values #())
:layer
#(;; Layer 1 — sine
,(func #'sin
:xlim #(-6.283 6.283)
:n 200
:color "steelblue"
:stroke-width 2)
;; Layer 2 — cosine
,(func #'cos
:xlim #(-6.283 6.283)
:n 200
:color "firebrick"
:stroke-width 2
:stroke-dash #(6 3)))))
Axis labels in layered plots
When using:layer directly, add axis titles inside each layer’s
:encoding entry, or use the top-level :encoding key for shared
axes — Vega-Lite will merge them automatically. Alternatively, wrap
the :layer spec in vega:merge-plists and add a label layer
at the outer level.
Family of curves
Plot a parameterised family by building the :layer vector in a loop:
;; Gaussian PDFs with increasing standard deviations
(let* ((sigmas #(0.5 1.0 1.5 2.0))
(colors #("steelblue" "seagreen" "darkorange" "firebrick"))
(layers (map 'vector
(lambda (sigma color)
(func (lambda (x)
(* (/ 1 (* sigma (sqrt (* 2 pi))))
(exp (* -0.5 (expt (/ x sigma) 2)))))
:xlim #(-5 5)
:n 200
:color color))
sigmas
colors)))
(vega:defplot gaussian-family
`(:title "Gaussian PDFs for sigma = 0.5, 1, 1.5, 2"
:data (:values #())
:layer ,layers)))
Overlay on scatter data
When overlaying a function on top of data, each layer supplies its own
:data. The data layer uses the original data frame; the function
layer uses the inline data generated by func:
(vega:defplot cars-with-trend
`(:title "HP vs MPG with Quadratic Trend"
:data (:values #())
:layer
#(;; Layer 1: raw data as a scatter plot
(:data (:values ,vgcars)
:mark (:type :point :filled t :opacity 0.5)
:encoding (:x (:field :horsepower :type :quantitative
:title "Horsepower")
:y (:field :miles-per-gallon :type :quantitative
:title "Miles per Gallon")
:color (:field :origin :type :nominal)))
;; Layer 2: fitted quadratic y = 52 - 0.23x + 3e-4*x^2
,(func (lambda (x)
(+ 52.0d0
(* -0.23d0 x)
(* 3.0d-4 (expt x 2))))
:xlim #(40 230)
:n 300
:color "firebrick"
:stroke-width 2.5))))
Field names in overlay plots
The function layer always uses the internal field names:x and :y.
The data layer uses the actual field names from your data frame (e.g.
:horsepower, :miles-per-gallon). Vega-Lite resolves the axis
scales across layers automatically when the quantitative ranges
overlap, which is why the function curve aligns correctly with the
scatter points.
Normal distribution fit over a histogram
Overlay the theoretical PDF on an empirical histogram. Use
select:select to extract the column as a vector, statistics:sd
for the standard deviation, and distributions:r-normal to construct
the fitted distribution — recall that r-normal takes the variance,
so pass (expt sigma 2):
(let* ((mpg (select:select vgcars t :miles-per-gallon))
(mu (statistics:mean mpg))
(sigma (statistics:sd mpg))
(d (distributions:r-normal mu (expt sigma 2))))
(vega:defplot mpg-fit
`(:title "MPG: Empirical Histogram with Normal Fit"
:data (:values #())
:layer
#(;; Histogram layer
(:data (:values ,vgcars)
:mark :bar
:encoding (:x (:field :miles-per-gallon
:bin (:maxbins 15)
:type :quantitative
:title "Miles per Gallon")
:y (:aggregate :count
:stack :null
:type :quantitative
:title "Count")))
;; Density curve — scaled by (n × bin-width) to match count axis
,(func (lambda (x) (* 406 3 (distributions:pdf d x)))
:xlim #(5 50)
:n 300
:color "firebrick"
:stroke-width 2)))))
Chebyshev approximation
numerical-utilities provides chebyshev-regression and
evaluate-chebyshev for polynomial approximation. Plot the exact
function and its approximation together to inspect accuracy:
(let* ((coeffs (nu:chebyshev-regression
(lambda (x) (exp (- (nu:square x))))
12)) ; 12-term approximation
(approx (lambda (x) (nu:evaluate-chebyshev coeffs x))))
(vega:defplot chebyshev-approx
`(:title "exp(-x^2): Exact vs 12-Term Chebyshev Approximation"
:data (:values #())
:layer
#(,(func (lambda (x) (exp (- (nu:square x))))
:xlim #(-1 1)
:n 200
:color "steelblue"
:stroke-width 2)
,(func approx
:xlim #(-1 1)
:n 200
:color "firebrick"
:stroke-width 1.5
:stroke-dash #(6 3))))))
Combining layers across plot types
Because every helper returns an independent plist, you can mix and match freely. Here are patterns that work with all mark types.
Labels on any plot
label works identically on every plot type — it only touches
:encoding :x/:y :axis :title. The examples below produce the same
visual results as the per-section examples above; they are shown here
to illustrate that label requires no changes across mark types:
;; On a scatter plot
(qplot 'point-labeled vgcars
(point :horsepower :miles-per-gallon)
(label :x "Engine Horsepower" :y "Fuel Efficiency"))
;; On a histogram
(qplot 'hist-labeled vgcars
(histogram :horsepower)
(label :x "Engine Horsepower" :y "Number of Cars"))
;; On a bar chart
(qplot 'bar-labeled vgcars
(bar :origin :miles-per-gallon :aggregate :mean)
(label :x "Country of Origin" :y "Average MPG"))
;; On a box plot
(qplot 'box-labeled vgcars
(box-plot :miles-per-gallon :category :origin)
(label :x "Fuel Efficiency" :y "Manufacturing Origin"))
Theme on any plot
theme sets top-level properties that apply to every plot type:
(qplot 'hist-themed vgcars
`(:title "Horsepower Distribution")
(histogram :horsepower :color "darkslategray")
(label :x "Horsepower" :y "Count")
(theme :width 500 :height 300 :font "Georgia"))
coord with continuous axes
coord works best with quantitative (continuous) axes. For bar
charts and box plots whose axes are categorical, use coord with
:clip nil to avoid cutting marks, or use axes with :x-domain
or :y-domain instead:
;; Zoom a scatter plot — clip marks outside the viewport
(coord :x-domain #(50 150) :y-domain #(20 40))
;; Restrict a bar chart's quantitative axis — no clipping
(coord :y-domain #(0 35) :clip nil)
;; Alternatively, use axes for categorical axes
(axes :y-domain #(0 35))
Recipe: exploring a new dataset
When you first load a dataset, a quick exploratory sequence might
look like this. Note how the same plot name can be reused — each
qplot call overwrites the previous definition.
Step 1 — Distribution of a key variable
Start with a histogram to understand the shape of the data:
(qplot 'explore-1 vgcars
`(:title "Horsepower Distribution")
(histogram :horsepower))
Step 2 — Scatter plot of two main variables
Look for relationships between variables:
(qplot 'explore-2 vgcars
`(:title "HP vs. MPG")
(point :horsepower :miles-per-gallon :filled t))
Step 3 — Compare groups
Break the data down by category to see if patterns differ:
(qplot 'explore-3 vgcars
`(:title "MPG by Origin")
(box-plot :miles-per-gallon :category :origin
:orient :vertical)
(label :x "Origin" :y "MPG"))
Step 4 — Add detail
Color by group and add labels to confirm the story:
(qplot 'explore-4 vgcars
`(:title "HP vs. MPG by Origin")
(point :horsepower :miles-per-gallon
:color :origin :filled t)
(label :x "Horsepower" :y "Miles per Gallon")
(tooltip '(:field :name :type :nominal)
'(:field :origin :type :nominal)
'(:field :horsepower :type :quantitative)
'(:field :miles-per-gallon :type :quantitative)))
At the REPL, every call overwrites the same explore variable.
The variable always holds the latest version:
explore ; => #<VEGA-PLOT ...>
(show-plots) ; one entry for EXPLORE
Tip
For the tutorial we use distinct plot names (explore-1 through
explore-4) so all four plots render on the page. At the REPL you
would reuse 'explore for all of them — only the latest version
is kept.
Recipe: presentation-ready plot
For reports or publications, combine all the layering helpers:
(qplot 'presentation vgcars
`(:title "Automobile Performance by Country of Origin"
:description "Horsepower vs fuel efficiency for 406 cars")
(point :horsepower :miles-per-gallon
:color :origin :filled t)
(label :x "Engine Horsepower" :y "Fuel Efficiency (MPG)")
(axes :color-scheme :tableau10)
(tooltip '(:field :name :type :nominal)
'(:field :horsepower :type :quantitative)
'(:field :miles-per-gallon :type :quantitative))
(theme :width 700 :height 450 :font "Helvetica"))
Recipe: saving a plot for later
Once you are happy with a plot created via qplot, it is a
first-class vega-plot object. You can re-render, inspect, or
write it to a file. This example assumes presentation was created
in the recipe above:
;; Re-render in the browser
(plot:plot presentation)
;; Inspect the spec
(describe presentation)
;; List all named plots
(show-plots)
Recipe: exploring a function
A typical REPL workflow when investigating a mathematical function.
Note how the same plot name is reused — each defplot call overwrites
the previous definition.
Step 1 — Quick sketch over the natural domain
(vega:defplot explore-fn
(vega:merge-plists
`(:title "First look")
(func (lambda (x) (/ (sin x) x)) ; sinc
:xlim #(-20 20)
:n 400)))
Step 2 — Zoom in on a region of interest
(vega:defplot explore-fn
(vega:merge-plists
`(:title "sinc(x) — detail near origin")
(func (lambda (x) (/ (sin x) x))
:xlim #(-6.283 6.283)
:n 400)
(label :x "x" :y "sin(x)/x")
(axes :y-domain #(-0.3 1.1))))
Step 3 — Presentation polish
(vega:defplot sinc-final
(vega:merge-plists
`(:title "sinc(x) = sin(x)/x"
:description "Classic sinc function over [-2pi, 2pi]")
(func (lambda (x) (/ (sin x) x))
:xlim #(-6.283 6.283)
:n 500
:color "steelblue"
:stroke-width 2)
(label :x "x" :y "sin(x) / x")
(theme :width 600 :height 350 :font "Georgia")))
Recipe: comparing model fits
Overlay multiple fitted curves on a scatter plot to compare competing models visually:
(let* ((linear (lambda (x) (+ 39.9 (* -0.158 x))))
(quadratic (lambda (x) (+ 52.0 (* -0.23 x) (* 3.0e-4 (expt x 2))))))
(vega:defplot model-comparison
`(:title "Linear vs Quadratic Fit"
:data (:values #())
:layer
#(;; Raw data
(:data (:values ,vgcars)
:mark (:type :point :filled t :color "lightgray")
:encoding (:x (:field :horsepower :type :quantitative
:title "Horsepower")
:y (:field :miles-per-gallon :type :quantitative
:title "Miles per Gallon")))
;; Linear fit
,(func linear
:xlim #(40 230) :n 200
:color "steelblue" :stroke-width 2)
;; Quadratic fit
,(func quadratic
:xlim #(40 230) :n 200
:color "firebrick" :stroke-width 2
:stroke-dash #(6 3))))))
Recipe: dropping down to raw Vega-Lite
The helpers cover common patterns. When you need something they don’t support — transforms, selections, parameters, calculated fields, multi-view compositions — you can write raw Vega-Lite directly.
Example: brush selection with linked data table
This example reproduces the
Vega-Lite brush table:
drag a rectangle over the scatter plot to see the selected cars in a
table. It uses hconcat (side-by-side views), params (interactive
brush), and transform (filter + rank) — none of which the helpers
generate. For specs this complex, write the full Vega-Lite plist and
use defplot directly:
(plot:plot
(vega:defplot brush-table
`(:description "Drag a rectangular brush to show selected points in a table."
:data (:values ,vgcars)
:transform #((:window #((:op :row-number :as "row_number"))))
:hconcat
#(;; Left panel: scatter plot with brush
(:params #((:name "brush" :select "interval"))
:mark :point
:encoding
(:x (:field :horsepower :type :quantitative)
:y (:field :miles-per-gallon :type :quantitative)
:color (:condition (:param "brush"
:field :cylinders
:type :ordinal)
:value "grey")))
;; Right panel: table of selected points
(:transform #((:filter (:param "brush"))
(:window #((:op :rank :as "rank")))
(:filter (:field "rank" :lt 20)))
:hconcat
#((:width 50
:title "Horsepower"
:mark :text
:encoding
(:text (:field :horsepower :type :nominal)
:y (:field "row_number" :type :ordinal :axis :null)))
(:width 50
:title "MPG"
:mark :text
:encoding
(:text (:field :miles-per-gallon :type :nominal)
:y (:field "row_number" :type :ordinal :axis :null)))
(:width 50
:title "Origin"
:mark :text
:encoding
(:text (:field :origin :type :nominal)
:y (:field "row_number" :type :ordinal :axis :null))))))
:resolve (:legend (:color :independent)))))
This plot cannot be built with qplot and the layering helpers;
the hconcat layout requires two independent views with different
marks, encodings, and transforms. The rule of thumb:
- Use
qplot+ helpers for single-view plots (scatter, line, bar, histogram, box plot) with standard encodings - Use
defplot+ raw plists for multi-view layouts (hconcat,vconcat,layer,facet), complex interactions, or any Vega-Lite feature the helpers don’t cover
Both approaches produce the same vega-plot objects and coexist
in the same session.
2 - Vega Plotting
The plots here show equivalents to the Vega-Lite example gallery. Before you begin working with these example, be certain to read the plotting tutorial where you will learn the basics of working with plot specifications and data.
Preliminaries
Load Vega-Lite
Load Vega-Lite and network libraries:
(asdf:load-system :plot/vega)
and change to the Lisp-Stat user package:
(in-package :ls-user)
Load example data
The examples in this section use the vega-lite data sets. Load them all now:
(vega:load-vega-examples)
Bar charts
Bar charts are used to display information about categorical variables.
Simple bar chart
In this simple bar chart example we’ll demonstrate using literal
embedded data in the form of a plist. Later you’ll see how to use a data-frame directly.
(plot:plot
(vega:defplot simple-bar-chart
`(:mark :bar
:data (:values ,(plist-df '(:a #(A B C D E F G H I)
:b #(28 55 43 91 81 53 19 87 52))))
:encoding (:x (:field :a :type :nominal :axis ("labelAngle" 0))
:y (:field :b :type :quantitative)))))
Grouped bar chart
(plot:plot
(vega:defplot grouped-bar-chart
`(:mark :bar
:data (:values ,(plist-df '(:category #(A A A B B B C C C)
:group #(x y z x y z x y z)
:value #(0.1 0.6 0.9 0.7 0.2 1.1 0.6 0.1 0.2))))
:encoding (:x (:field :category)
:y (:field :value :type :quantitative)
:x-offset (:field :group)
:color (:field group)))))
Stacked bar chart
This example uses Seattle weather from the Vega website. Load it into a data frame like so:
(defdf seattle-weather (read-csv vega:seattle-weather))
;=> #<DATA-FRAME (1461 observations of 6 variables)>
We’ll use a data-frame as the data source via the Common Lisp
backquote
mechanism.
The spec list begins with a backquote (`) and then the data frame is
inserted as a literal value with a comma (,). We’ll use this
pattern frequently.
(plot:plot
(vega:defplot stacked-bar-chart
`(:mark :bar
:data (:values ,seattle-weather)
:encoding (:x (:time-unit :month
:field :date
:type :ordinal
:title "Month of the year")
:y (:aggregate :count
:type :quantitative)
:color (:field :weather
:type :nominal
:title "Weather type"
:scale (:domain #("sun" "fog" "drizzle" "rain" "snow")
:range #("#e7ba52", "#c7c7c7", "#aec7e8", "#1f77b4", "#9467bd")))))))
Population pyramid
Vega calls this a diverging stacked bar chart. It is a population pyramid for the US in 2000, created using the stack feature of vega-lite. You could also create one using concat.
First, load the population data if you haven’t done so:
(defdf population (vega:read-vega vega:population))
;=> #<DATA-FRAME (570 observations of 4 variables)>
Note the use of read-vega in this case. This is because the data in
the Vega example is in an application specific JSON format (Vega, of
course).
(plot:plot
(vega:defplot pyramid-bar-chart
`(:mark :bar
:data (:values ,population)
:width 300
:height 200
:transform #((:filter "datum.year == 2000")
(:calculate "datum.sex == 2 ? 'Female' : 'Male'" :as :gender)
(:calculate "datum.sex == 2 ? -datum.people : datum.people" :as :signed-people))
:encoding (:x (:aggregate :sum
:field :signed-people
:title "population")
:y (:field :age
:axis nil
:sort :descending)
:color (:field :gender
:scale (:range #("#675193" "#ca8861"))))
:config (:view (:stroke nil)
:axis (:grid :false)))))
Histograms & density
Basic
For this simple histogram example we’ll use the IMDB film rating data set.
(plot:plot
(vega:defplot imdb-plot
`(:mark :bar
:data (:values ,imdb)
:encoding (:x (:bin (:maxbins 8) :field :imdb-rating)
:y (:aggregate :count)))))
Relative frequency
Use a relative frequency histogram to compare data sets with different numbers of observations.
The data is binned with first transform. The number of values per bin and the total number are calculated in the second and the third transform to calculate the relative frequency in the last transformation step.
(plot:plot
(vega:defplot relative-frequency-histogram
`(:title "Relative Frequency"
:data (:values ,vgcars)
:transform #((:bin t
:field :horsepower
:as #(:bin-horsepower :bin-horsepower-end))
(:aggregate #((:op :count
:as "Count"))
:groupby #(:bin-horsepower :bin-horsepower-end))
(:joinaggregate #((:op :sum
:field "Count"
:as "TotalCount")))
(:calculate "datum.Count/datum.TotalCount"
:as :percent-of-total))
:mark (:type :bar :tooltip t)
:encoding (:x (:field :bin-horsepower
:title "Horsepower"
:bin (:binned t))
:x2 (:field :bin-horsepower-end)
:y (:field :percent-of-total
:type "quantitative"
:title "Relative Frequency"
:axis (:format ".1~%"))))))
2D histogram scatterplot
If you haven’t already loaded the imdb data set, do so now:
(defparameter imdb
(vega:read-vega vega:movies))
(plot:plot
(vega:defplot histogram-scatterplot
`(:mark :circle
:data (:values ,imdb)
:encoding (:x (:bin (:maxbins 10) :field :imdb-rating)
:y (:bin (:maxbins 10) :field :rotten-tomatoes-rating)
:size (:aggregate :count)))))
Stacked density
(plot:plot
(vega:defplot stacked-density
`(:title "Distribution of Body Mass of Penguins"
:width 400
:height 80
:data (:values ,penguins)
:mark :bar
:transform #((:density |BODY-MASS-(G)|
:groupby #(:species)
:extent #(2500 6500)))
:encoding (:x (:field :value
:type :quantitative
:title "Body Mass (g)")
:y (:field :density
:type :quantitative
:stack :zero)
:color (:field :species
:type :nominal)))))
Note the use of the multiple escape
characters
(|) surrounding the field BODY-MASS-(G). This is required because
the JSON data set has parenthesis in the variable names, and these are
reserved characters in Common Lisp. The JSON importer wrapped these
in the escape character.
Scatter plots
Basic
A basic Vega-Lite scatterplot showing horsepower and miles per gallon for various cars.
(plot:plot
(vega:defplot hp-mpg
`(:title "Horsepower vs. MPG"
:data (:values ,vgcars)
:mark :point
:encoding (:x (:field :horsepower :type "quantitative")
:y (:field :miles-per-gallon :type "quantitative")))))
Colored
In this example we’ll show how to add additional information to the cars scatter plot to show the cars origin. The Vega-Lite example shows that we have to add two new directives to the encoding of the plot:
(plot:plot
(vega:defplot hp-mpg-plot
`(:title "Vega Cars"
:data (:values ,vgcars)
:mark :point
:encoding (:x (:field :horsepower :type "quantitative")
:y (:field :miles-per-gallon :type "quantitative")
:color (:field :origin :type "nominal")
:shape (:field :origin :type "nominal")))))
With this change we can see that the higher horsepower, lower efficiency, cars are from the USA, and the higher efficiency cars from Japan and Europe.
Text marks
The same information, but further indicated with a text marker. This Vega-Lite example uses a data transformation.
(plot:plot
(vega:defplot colored-text-hp-mpg-plot
`(:title "Vega Cars"
:data (:values ,vgcars)
:transform #((:calculate "datum.origin[0]" :as "OriginInitial"))
:mark :text
:encoding (:x (:field :horsepower :type "quantitative")
:y (:field :miles-per-gallon :type "quantitative")
:color (:field :origin :type "nominal")
:text (:field "OriginInitial" :type "nominal")))))
Notice here we use a string for the field value and not a symbol.
This is because Vega is case sensitive, whereas Lisp is not. We could
have also used a lower-case :as value, but did not to highlight this
requirement for certain Vega specifications.
Mean & SD overlay
This Vega-Lite scatterplot with mean and standard deviation overlay demonstrates the use of layers in a plot.
Lisp-Stat equivalent
(plot:plot
(vega:defplot mean-hp-mpg-plot
`(:title "Vega Cars"
:data (:values ,vgcars)
:layer #((:mark :point
:encoding (:x (:field :horsepower :type "quantitative")
:y (:field :miles-per-gallon
:type "quantitative")))
(:mark (:type :errorband :extent :stdev :opacity 0.2)
:encoding (:y (:field :miles-per-gallon
:type "quantitative"
:title "Miles per Gallon")))
(:mark :rule
:encoding (:y (:field :miles-per-gallon
:type "quantitative"
:aggregate :mean)))))))
Linear regression
(plot:plot
(vega:defplot linear-regression
`(:data (:values ,imdb)
:layer #((:mark (:type :point :filled t)
:encoding (:x (:field :rotten-tomatoes-rating
:type :quantitative
:title "Rotten Tomatoes Rating")
:y (:field :imdb-rating
:type :quantitative
:title "IMDB Rating")))
(:mark (:type :line :color "firebrick")
:transform #((:regression :imdb-rating
:on :rotten-tomatoes-rating))
:encoding (:x (:field :rotten-tomatoes-rating
:type :quantitative
:title "Rotten Tomatoes Rating")
:y (:field :imdb-rating
:type :quantitative
:title "IMDB Rating")))
(:transform #((:regression :imdb-rating
:on :rotten-tomatoes-rating
:params t)
(:calculate "'R²: '+format(datum.rSquared, '.2f')"
:as :r2))
:mark (:type :text
:color "firebrick"
:x :width
:align :right
:y -5)
:encoding (:text (:type :nominal :field :r2)))))))
Loess regression
(plot:plot
(vega:defplot loess-regression
`(:data (:values ,imdb)
:layer #((:mark (:type :point :filled t)
:encoding (:x (:field :rotten-tomatoes-rating
:type :quantitative
:title "Rotten Tomatoes Rating")
:y (:field :imdb-rating
:type :quantitative
:title "IMDB Rating")))
(:mark (:type :line
:color "firebrick")
:transform #((:loess :imdb-rating
:on :rotten-tomatoes-rating))
:encoding (:x (:field :rotten-tomatoes-rating
:type :quantitative
:title "Rotten Tomatoes Rating")
:y (:field :imdb-rating
:type :quantitative
:title "IMDB Rating")))))))
Residuals
A dot plot showing each film in the database, and the difference from
the average movie rating. The display is sorted by year to visualize
everything in sequential order. The graph is for all films before
2019. Note the use of the filter-rows function.
(plot:plot
(vega:defplot residuals
`(:data (:values
,(filter-rows imdb
'(and (not (eql imdb-rating :na))
(local-time:timestamp< release-date
(local-time:parse-timestring "2019-01-01")))))
:transform #((:joinaggregate #((:op :mean
:field :imdb-rating
:as :average-rating)))
(:calculate "datum['imdbRating'] - datum.averageRating"
:as :rating-delta))
:mark :point
:encoding (:x (:field :release-date
:type :temporal
:title "Release Date")
:y (:field :rating-delta
:type :quantitative
:title "Rating Delta")
:color (:field :rating-delta
:type :quantitative
:scale (:domain-mid 0)
:title "Rating Delta")))))
Query
The cars scatterplot allows you to see miles per gallon vs. horsepower. By adding sliders, you can select points by the number of cylinders and year as well, effectively examining 4 dimensions of data. Drag the sliders to highlight different points.
(plot:plot
(vega:defplot scatter-queries
`(:data (:values ,vgcars)
:transform #((:calculate "year(datum.year)" :as :year))
:layer #((:params #((:name :cyl-year
:value #((:cylinders 4
:year 1799))
:select (:type :point
:fields #(:cylinders :year))
:bind (:cylinders (:input :range
:min 3
:max 8
:step 1)
:year (:input :range
:min 1969
:max 1981
:step 1))))
:mark :circle
:encoding (:x (:field :horsepower
:type :quantitative)
:y (:field :miles-per-gallon
:type :quantitative)
:color (:condition (:param :cyl-year
:field :origin
:type :nominal)
:value "grey")))
(:transform #((:filter (:param :cyl-year)))
:mark :circle
:encoding (:x (:field :horsepower
:type :quantitative)
:y (:field :miles-per-gallon
:type :quantitative)
:color (:field :origin
:type :nominal)
:size (:value 100)))))))
External links
You can add external links to plots.
(plot:plot
(vega:defplot scatter-external-links
`(:data (:values ,vgcars)
:mark :point
:transform #((:calculate "'https://www.google.com/search?q=' + datum.name", :as :url))
:encoding (:x (:field :horsepower
:type :quantitative)
:y (:field :miles-per-gallon
:type :quantitative)
:color (:field :origin
:type :nominal)
:tooltip (:field :name
:type :nominal)
:href (:field :url
:type :nominal)))))
Strip plot
The Vega-Lite strip plot example shows the relationship between horsepower and the number of cylinders using tick marks.
(plot:plot
(vega:defplot strip-plot
`(:title "Vega Cars"
:data (:values ,vgcars)
:mark :tick
:encoding (:x (:field :horsepower :type :quantitative)
:y (:field :cylinders :type :ordinal)))))
1D strip plot
(plot:plot
(vega:defplot 1d-strip-plot
`(:title "Seattle Precipitation"
:data (:values ,seattle-weather)
:mark :tick
:encoding (:x (:field :precipitation :type :quantitative)))))
Bubble plot
This Vega-Lite example is a visualization of global deaths from natural disasters. A copy of the chart from Our World in Data.
(plot:plot
(vega:defplot natural-disaster-deaths
`(:title "Deaths from global natural disasters"
:width 600
:height 400
:data (:values ,(filter-rows disasters '(not (string= entity "All natural disasters"))))
:mark (:type :circle
:opacity 0.8
:stroke :black
:stroke-width 1)
:encoding (:x (:field :year
:type :temporal
:axis (:grid :false))
:y (:field :entity
:type :nominal
:axis (:title ""))
:size (:field :deaths
:type :quantitative
:title "Annual Global Deaths"
:legend (:clip-height 30)
:scale (:range-max 5000))
:color (:field :entity
:type :nominal
:legend nil)))))
Note how we modified the example by using a lower case entity in the
filter to match our default lower case variable names. Also note how
we are explicit with parsing the year field as a temporal column.
This is because, when creating a chart with inline data, Vega-Lite
will parse the field as an integer instead of a date.
Line plots
Simple
(plot:plot
(vega:defplot simple-line-plot
`(:title "Google's stock price from 2004 to early 2010"
:data (:values ,(filter-rows stocks '(string= symbol "GOOG")))
:mark :line
:encoding (:x (:field :date
:type :temporal)
:y (:field :price
:type :quantitative)))))
Point markers
By setting the point property of the line mark definition to an object defining a property of the overlaying point marks, we can overlay point markers on top of line.
(plot:plot
(vega:defplot point-mark-line-plot
`(:title "Stock prices of 5 Tech Companies over Time"
:data (:values ,stocks)
:mark (:type :line :point t)
:encoding (:x (:field :date
:time-unit :year)
:y (:field :price
:type :quantitative
:aggregate :mean)
:color (:field :symbol
:type nominal)))))
Multi-series
This example uses the custom symbol encoding for variables to generate the proper types and labels for x, y and color channels.
(plot:plot
(vega:defplot multi-series-line-chart
`(:title "Stock prices of 5 Tech Companies over Time"
:data (:values ,stocks)
:mark :line
:encoding (:x (:field stocks:date)
:y (:field stocks:price)
:color (:field stocks:symbol)))))
Step
(plot:plot
(vega:defplot step-chart
`(:title "Google's stock price from 2004 to early 2010"
:data (:values ,(filter-rows stocks '(string= symbol "GOOG")))
:mark (:type :line
:interpolate "step-after")
:encoding (:x (:field stocks:date)
:y (:field stocks:price)))))
Stroke-dash
(plot:plot
(vega:defplot stroke-dash
`(:title "Stock prices of 5 Tech Companies over Time"
:data (:values ,stocks)
:mark :line
:encoding (:x (:field stocks:date)
:y (:field stocks:price)
:stroke-dash (:field stocks:symbol)))))
Confidence interval
Line chart with a confidence interval band.
(plot:plot
(vega:defplot line-chart-ci
`(:data (:values ,vgcars)
:encoding (:x (:field :year
:time-unit :year))
:layer #((:mark (:type :errorband
:extent :ci)
:encoding (:y (:field :miles-per-gallon
:type :quantitative
:title "Mean of Miles per Gallon (95% CIs)")))
(:mark :line
:encoding (:y (:field :miles-per-gallon
:aggregate :mean)))))))
Area charts
Simple
(plot:plot
(vega:defplot area-chart
`(:title "Unemployment across industries"
:width 300
:height 200
:data (:values ,unemployment-ind)
:mark :area
:encoding (:x (:field :date
:time-unit :yearmonth
:axis (:format "%Y"))
:y (:field :count
:aggregate :sum
:title "count")))))
Stacked
Stacked area plots
(plot:plot
(vega:defplot stacked-area-chart
`(:title "Unemployment across industries"
:width 300
:height 200
:data (:values ,unemployment-ind)
:mark :area
:encoding (:x (:field :date
:time-unit :yearmonth
:axis (:format "%Y"))
:y (:field :count
:aggregate :sum
:title "count")
:color (:field :series
:scale (:scheme "category20b"))))))
Horizon graph
A horizon graph is a technique for visualising time series data in a manner that makes comparisons easier. It is based on work done at the UW Interactive Data Lab. See Sizing the Horizon: The Effects of Chart Size and Layering on the Graphical Perception of Time Series Visualizations for more details on Horizon Graphs.
(plot:plot
(vega:defplot horizon-graph
`(:title "Horizon graph with 2 layers"
:width 300
:height 50
:data (:values ,(plist-df `(:x ,(aops:linspace 1 20 20)
:y #(28 55 43 91 81 53 19 87 52 48 24 49 87 66 17 27 68 16 49 15))))
:encoding (:x (:field :x
:scale (:zero :false
:nice :false))
:y (:field :y
:type :quantitative
:scale (:domain #(0 50))
:axis (:title "y")))
:layer #((:mark (:type :area
:clip t
:orient :vertical
:opacity 0.6))
(:transform #((:calculate "datum.y - 50"
:as :ny))
:mark (:type :area
:clip t
:orient :vertical)
:encoding (:y (:field "ny"
:type :quantitative
:scale (:domain #(0 50)))
:opacity (:value 0.3))))
:config (:area (:interpolate :monotone)))))
With overlay
Area chart with overlaying lines and point markers.
(plot:plot
(vega:defplot area-with-overlay
`(:title "Google's stock price"
:data (:values ,(filter-rows stocks '(string= symbol "GOOG")))
:mark (:type :area
:line t
:point t)
:encoding (:x (:field stocks:date)
:y (:field stocks:price)))))
Note the use of the variable symbols, e.g. stocks:price to fill in
the variable’s information instead of :type :quantitative :title ...
Stream graph
(plot:plot
(vega:defplot stream-graph
`(:title "Unemployment Stream Graph"
:width 300
:height 200
:data (:values ,unemployment-ind)
:mark :area
:encoding (:x (:field :date
:time-unit "yearmonth"
:axis (:domain :false
:format "%Y"
:tick-size 0))
:y (:field count
:aggregate :sum
:axis null
:stack :center)
:color (:field :series
:scale (:scheme "category20b"))))))
Tabular plots
Table heatmap
(plot:plot
(vega:defplot table-heatmap
`(:data (:values ,vgcars)
:mark :rect
:encoding (:x (:field vgcars:cylinders)
:y (:field vgcars:origin)
:color (:field :horsepower
:aggregate :mean))
:config (:axis (:grid t :tick-band :extent)))))
Heatmap with labels
Layering text over a table heatmap
(plot:plot
(vega:defplot heatmap-labels
`(:data (:values ,vgcars)
:transform #((:aggregate #((:op :count :as :num-cars))
:groupby #(:origin :cylinders)))
:encoding (:x (:field :cylinders
:type :ordinal)
:y (:field :origin
:type :ordinal))
:layer #((:mark :rect
:encoding (:color (:field :num-cars
:type :quantitative
:title "Count of Records"
:legend (:direction :horizontal
:gradient-length 120))))
(:mark :text
:encoding (:text (:field :num-cars
:type :quantitative)
:color (:condition (:test "datum['numCars'] < 40"
:value :black)
:value :white))))
:config (:axis (:grid t
:tick-band :extent)))))
Histogram heatmap
(plot:plot
(vega:defplot heatmap-histogram
`(:data (:values ,imdb)
:transform #((:and #((:field :imdb-rating :valid t)
(:field :rotten-tomatoes-rating :valid t))))
:mark :rect
:width 300
:height 200
:encoding (:x (:bin (:maxbins 60)
:field :imdb-rating
:type :quantitative
:title "IMDB Rating")
:y (:bin (:maxbins 40)
:field :rotten-tomatoes-rating
:type :quantitative
:title "Rotten Tomatoes Rating")
:color (:aggregate :count
:type :quantitative))
:config (:view (:stroke :transparent)))))
Circular plots
Pie chart
(plot:plot
(vega:defplot pie-chart
`(:data (:values ,(plist-df `(:category ,(aops:linspace 1 6 6)
:value #(4 6 10 3 7 8))))
:mark :arc
:encoding (:theta (:field :value
:type :quantitative)
:color (:field :category
:type :nominal)))))
Donut chart
(plot:plot
(vega:defplot donut-chart
`(:data (:values ,(plist-df `(:category ,(aops:linspace 1 6 6)
:value #(4 6 10 3 7 8))))
:mark (:type :arc :inner-radius 50)
:encoding (:theta (:field :value
:type :quantitative)
:color (:field :category
:type :nominal)))))
Radial plot
This radial plot uses both angular and radial extent to convey multiple dimensions of data. However, this approach is not perceptually effective, as viewers will most likely be drawn to the total area of the shape, conflating the two dimensions. This example also demonstrates a way to add labels to circular plots.
(plot:plot
(vega:defplot radial-plot
`(:data (:values ,(plist-df '(:value #(12 23 47 6 52 19))))
:layer #((:mark (:type :arc
:inner-radius 20
:stroke "#fff"))
(:mark (:type :text
:radius-offset 10)
:encoding (:text (:field :value
:type :quantitative))))
:encoding (:theta (:field :value
:type :quantitative
:stack t)
:radius (:field :value
:scale (:type :sqrt
:zero t
:range-min 20))
:color (:field :value
:type :nominal
:legend nil)))))
Transformations
Normally data transformations should be done in Lisp-Stat with a data frame. These examples illustrate how to accomplish transformations using Vega-Lite. This might be useful if, for example, you’re serving up a lot of plots and want to move the processing to the users browser.
Difference from avg
(plot:plot
(vega:defplot difference-from-average
`(:data (:values ,(filter-rows imdb '(not (eql imdb-rating :na))))
:transform #((:joinaggregate #((:op :mean ;we could do this above using alexandria:thread-first
:field :imdb-rating
:as :average-rating)))
(:filter "(datum['imdbRating'] - datum.averageRating) > 2.5"))
:layer #((:mark :bar
:encoding (:x (:field :imdb-rating
:type :quantitative
:title "IMDB Rating")
:y (:field :title
:type :ordinal
:title "Title")))
(:mark (:type :rule :color "red")
:encoding (:x (:aggregate :average
:field :average-rating
:type :quantitative)))))))
Frequency distribution
Cumulative frequency distribution of films in the IMDB database.
(plot:plot
(vega:defplot cumulative-frequency-distribution
`(:data (:values ,imdb)
:transform #((:sort #((:field :imdb-rating))
:window #((:op :count
:field :count as :cumulative-count))
:frame #(nil 0)))
:mark :area
:encoding (:x (:field :imdb-rating
:type :quantitative)
:y (:field :cumulative-count
:type :quantitative)))))
Layered & cumulative histogram
(plot:plot
(vega:defplot layered-histogram
`(:data (:values ,(filter-rows imdb '(not (eql imdb-rating :na))))
:transform #((:bin t
:field :imdb-rating
:as #(:bin-imdb-rating :bin-imdb-rating-end))
(:aggregate #((:op :count :as :count))
:groupby #(:bin-imdb-rating :bin-imdb-rating-end))
(:sort #((:field :bin-imdb-rating))
:window #((:op :sum
:field :count :as :cumulative-count))
:frame #(nil 0)))
:encoding (:x (:field :bin-imdb-rating
:type :quantitative
:scale (:zero :false)
:title "IMDB Rating")
:x2 (:field :bin-imdb-rating-end))
:layer #((:mark :bar
:encoding (:y (:field :cumulative-count
:type :quantitative
:title "Cumulative Count")))
(:mark (:type :bar
:color "yellow"
:opacity 0.5)
:encoding (:y (:field :count
:type :quantitative
:title "Count")))))))
Layering averages
Layering averages over raw values.
(plot:plot
(vega:defplot layered-averages
`(:data (:values ,(filter-rows stocks '(string= symbol "GOOG")))
:layer #((:mark (:type :point
:opacity 0.3)
:encoding (:x (:field :date
:time-unit :year)
:y (:field :price
:type quantitative)))
(:mark :line
:encoding (:x (:field :date
:time-unit :year)
:y (:field :price
:aggregate :mean)))))))
Error bars
Confidence interval
Error bars showing confidence intervals.
(plot:plot
(vega:defplot error-bar-ci
`(:data (:values ,barley)
:encoding (:y (:field :variety
:type :ordinal
:title "Variety"))
:layer #((:mark (:type :point
:filled t)
:encoding (:x (:field :yield
:aggregate :mean
:type :quantitative
:scale (:zero :false)
:title "Barley Yield")
:color (:value "black")))
(:mark (:type :errorbar :extent :ci)
:encoding (:x (:field :yield
:type :quantitative
:title "Barley Yield")))))))
Standard deviation
Error bars showing standard deviation.
(plot:plot
(vega:defplot error-bar-sd
`(:data (:values ,barley)
:encoding (:y (:field :variety
:type :ordinal
:title "Variety"))
:layer #((:mark (:type :point
:filled t)
:encoding (:x (:field :yield
:aggregate :mean
:type :quantitative
:scale (:zero :false)
:title "Barley Yield")
:color (:value "black")))
(:mark (:type :errorbar :extent :stdev)
:encoding (:x (:field :yield
:type :quantitative
:title "Barley Yield")))))))
Box plots
Min/max whiskers
A vertical box plot showing median, min, and max body mass of penguins.
(plot:plot
(vega:defplot box-plot-min-max
`(:data (:values ,penguins)
:mark (:type :boxplot
:extent "min-max")
:encoding (:x (:field :species
:type :nominal
:title "Species")
:y (:field |BODY-MASS-(G)|
:type :quantitative
:scale (:zero :false)
:title "Body Mass (g)")
:color (:field :species
:type :nominal
:legend nil)))))
Tukey
A vertical box plot showing median and lower and upper quartiles of the distribution of body mass of penguins.
(plot:plot
(vega:defplot box-plot-tukey
`(:data (:values ,penguins)
:mark :boxplot
:encoding (:x (:field :species
:type :nominal
:title "Species")
:y (:field |BODY-MASS-(G)|
:type :quantitative
:scale (:zero :false)
:title "Body Mass (g)")
:color (:field :species
:type :nominal
:legend nil)))))
Summaries
Box plot with pre-computed summaries. Use this pattern to plot
summaries done in a data-frame.
(plot:plot
(vega:defplot box-plot-summaries
`(:title "Body Mass of Penguin Species (g)"
:data (:values ,(plist-df '(:species #("Adelie" "Chinstrap" "Gentoo")
:lower #(2850 2700 3950)
:q1 #(3350 3487.5 4700)
:median #(3700 3700 5000)
:q3 #(4000 3950 5500)
:upper #(4775 4800 6300)
:outliers #(#() #(2700 4800) #()))))
:encoding (:y (:field :species
:type :nominal
:title null))
:layer #((:mark (:type :rule)
:encoding (:x (:field :lower
:type :quantitative
:scale (:zero :false)
:title null)
:x2 (:field :upper)))
(:mark (:type :bar :size 14)
:encoding (:x (:field :q1
:type :quantitative)
:x2 (:field :q3)
:color (:field :species
:type :nominal
:legend null)))
(:mark (:type :tick
:color :white
:size 14)
:encoding (:x (:field :median
:type :quantitative)))
(:transform #((:flatten #(:outliers)))
:mark (:type :point :style "boxplot-outliers")
:encoding (:x (:field :outliers
:type :quantitative)))))))
Layered
Rolling average
Plot showing a 30 day rolling average with raw values in the background.
(plot:plot
(vega:defplot moving-average
`(:width 400
:height 300
:data (:values ,seattle-weather)
:transform #((:window #((:field :temp-max
:op :mean
:as :rolling-mean))
:frame #(-15 15)))
:encoding (:x (:field :date
:type :temporal
:title "Date")
:y (:type :quantitative
:axis (:title "Max Temperature and Rolling Mean")))
:layer #((:mark (:type :point :opacity 0.3)
:encoding (:y (:field :temp-max
:title "Max Temperature")))
(:mark (:type :line :color "red" :size 3)
:encoding (:y (:field :rolling-mean
:title "Rolling Mean of Max Temperature")))))))
Histogram w/mean
(plot:plot
(vega:defplot histogram-with-mean
`(:data (:values ,imdb)
:layer #((:mark :bar
:encoding (:x (:field :imdb-rating
:bin t
:title "IMDB Rating")
:y (:aggregate :count)))
(:mark :rule
:encoding (:x (:field :imdb-rating
:aggregate :mean
:title "Mean of IMDB Rating")
:color (:value "red")
:size (:value 5)))))))
Interactive
This section demonstrates interactive plots.
Scatter plot matrix
This Vega-Lite interactive scatter plot matrix includes interactive elements and demonstrates creating a SPLOM (scatter plot matrix).
(defparameter vgcars-splom
(vega::make-plot "vgcars-splom"
vgcars
`("$schema" "https://vega.github.io/schema/vega-lite/v5.json"
:title "Scatterplot Matrix for Vega Cars"
:repeat (:row #(:horsepower :acceleration :miles-per-gallon)
:column #(:miles-per-gallon :acceleration :horsepower))
:spec (:data (:url "/data/vgcars-splom-data.json")
:mark :point
:params #((:name "brush"
:select (:type "interval"
:resolve "union"
:on "[mousedown[event.shiftKey], window:mouseup] > window:mousemove!"
:translate "[mousedown[event.shiftKey], window:mouseup] > window:mousemove!"
:zoom "wheel![event.shiftKey]"))
(:name "grid"
:select (:type "interval"
:resolve "global"
:translate "[mousedown[!event.shiftKey], window:mouseup] > window:mousemove!"
:zoom "wheel![!event.shiftKey]")
:bind :scales))
:encoding (:x (:field (:repeat "column") :type "quantitative")
:y (:field (:repeat "row") :type "quantitative" :axis ("minExtent" 30))
:color (:condition (:param "brush" :field :origin :type "nominal")
:value "grey"))))))
(plot:plot vgcars-splom)
This example is one of those mentioned in the plotting
tutorial that uses a non-standard location for
the data property.
Weather exploration
This graph shows an interactive view of Seattle’s weather, including maximum temperature, amount of precipitation, and type of weather. By clicking and dragging on the scatter plot, you can see the proportion of days in that range that have sun, rain, fog, snow, etc.
(plot:plot
(vega:defplot weather-exploration
`(:title "Seattle Weather, 2012-2015"
:data (:values ,seattle-weather)
:vconcat #(;; upper graph
(:encoding (:color (:condition (:param :brush
:title "Weather"
:field :weather
:type :nominal
:scale (:domain #("sun" "fog" "drizzle" "rain" "snow")
:range #("#e7ba52", "#a7a7a7", "#aec7e8", "#1f77b4", "#9467bd")))
:value "lightgray")
:size (:field :precipitation
:type :quantitative
:title "Precipitation"
:scale (:domain #(-1 50)))
:x (:field :date
:time-unit :monthdate
:title "Date"
:axis (:format "%b"))
:y (:field :temp-max
:type :quantitative
:scale (:domain #(-5 40))
:title "Maximum Daily Temperature (C)"))
:width 600
:height 300
:mark :point
:params #((:name :brush
:select (:type :interval
:encodings #(:x))))
:transform #((:filter (:param :click))))
;; lower graph
(:encoding (:color (:condition (:param :click
:field :weather
:scale (:domain #("sun", "fog", "drizzle", "rain", "snow")
:range #("#e7ba52", "#a7a7a7", "#aec7e8", "#1f77b4", "#9467bd")))
:value "lightgray")
:x (:aggregate :count)
:y (:field :weather
:title "Weather"))
:width 600
:mark :bar
:params #((:name :click
:select (:type :point
:encodings #(:color))))
:transform #((:filter (:param :brush))))))))
Interactive scatterplot
(plot:plot
(vega:defplot global-health
`(:title "Global Health Statistics by Country and Year"
:data (:values ,gapminder)
:width 800
:height 500
:layer #((:transform #((:filter (:field :country
:equal "afghanistan"))
(:filter (:param :year)))
:mark (:type :text
:font-size 100
:x 420
:y 250
:opacity 0.06)
:encoding (:text (:field :year)))
(:transform #((:lookup :cluster
:from (:key :id
:fields #(:name)
:data (:values #(("id" 0 "name" "South Asia")
("id" 1 "name" "Europe & Central Asia")
("id" 2 "name" "Sub-Saharan Africa")
("id" 3 "name" "America")
("id" 4 "name" "East Asia & Pacific")
("id" 5 "name" "Middle East & North Africa"))))))
:encoding (:x (:field :fertility
:type :quantitative
:scale (:domain #(0 9))
:axis (:tick-count 5
:title "Fertility"))
:y (:field :life-expect
:type :quantitative
:scale (:domain #(20 85))
:axis (:tick-count 5
:title "Life Expectancy")))
:layer #((:mark (:type :line
:size 4
:color "lightgray"
:stroke-cap "round")
:encoding (:detail (:field :country)
:order (:field :year)
:opacity (:condition (:test (:or #((:param :hovered :empty :false)
(:param :clicked :empty :false)))
:value 0.8)
:value 0)))
(:params #((:name :year
:value #((:year 1955))
:select (:type :point
:fields #(:year))
:bind (:name :year
:input :range
:min 1955
:max 2005
:step 5))
(:name :hovered
:select (:type :point
:fields #(:country)
:toggle :false
:on :mouseover))
(:name :clicked
:select (:type :point
:fields #(:country))))
:transform #((:filter (:param :year)))
:mark (:type :circle
:size 100
:opacity 0.9)
:encoding (:color (:field :name
:title "Region")))
(:transform #((:filter (:and #((:param :year)
(:or #((:param :clicked :empty :false)
(:param :hovered :empty :false)))))))
:mark (:type :text
:y-offset -12
:font-size 12
:font-weight :bold)
:encoding (:text (:field :country)
:color (:field :name
:title "Region")))
(:transform #((:filter (:param :hovered :empty :false))
(:filter (:not (:param :year))))
:layer #((:mark (:type :text
:y-offset -12
:font-size 12
:color "gray")
:encoding (:text (:field :year)))
(:mark (:type :circle
:color "gray"))))))))))
Crossfilter
Cross-filtering makes it easier and more intuitive for viewers of a plot to interact with the data and understand how one metric affects another. With cross-filtering, you can click a data point in one dashboard view to have all dashboard views automatically filter on that value.
Click and drag across one of the charts to see the other variables filtered.
(plot:plot
(vega:defplot cross-filter
`(:title "Cross filtering of flights"
:data (:values ,flights-2k)
:transform #((:calculate "hours(datum.date)", :as "time")) ;what does 'hours' do?
:repeat (:column #(:distance :delay :time))
:spec (:layer #((:params #((:name :brush
:select (:type :interval
:encodings #(:x))))
:mark :bar
:encoding (:x (:field (:repeat :column)
:bin (:maxbins 20))
:y (:aggregate :count)
:color (:value "#ddd")))
(:transform #((:filter (:param :brush)))
:mark :bar
:encoding (:x (:field (:repeat :column)
:bin (:maxbins 20))
:y (:aggregate :count))))))))