Introduction
Deneb.jl simply provides a convenient API to create Vega-Lite JSON specifications programmatically in Julia. Then, adequate show methods enable automatic displaying of charts when Deneb.jl is run on platforms such as Jupyter, Pluto, VSCode, or any other environments that can render rich MIME types. Furthermore, a chart can be saved in several formats using the function save.
Vega-Lite Specifications
Vega-Lite specifications describe visualizations as encoding mappings from data to properties of graphical marks. These specifications are created with a concise declarative JSON syntax. For example:
{
"data": {"url": "https://vega.github.io/vega-datasets/data/seattle-weather.csv"},
"mark": "bar",
"encoding": {
"x": {"timeUnit": "month", "field": "date", "type": "ordinal"},
"y": {"aggregate": "mean", "field": "precipitation"}
}
}represents the following visualization:
The vlspec function
The first way to create any arbitrary Vega-Lite specification in Deneb.jl is using the vlspec function. The previous example can be directly translated to the following vlspec call:
using Deneb
vlspec(
data=(; url="https://vega.github.io/vega-datasets/data/seattle-weather.csv"),
mark=:bar,
encoding=(
x=(timeUnit=:month, field=:date, type=:ordinal),
y=(aggregate=:mean, field=:precipitation),
)
)
The main differences between the JSON spec and the vlspec are:
- JSON strings can be represented either as a Julia
String(e.g."https://vega.github.io/vega-datasets/data/seattle-weather.csv") or as a JuliaSymbol(e.g.:bar). - key-value pairs are represented as a
NamedNtuple(aDictis also accepted), so{}is translated to(),:is translated to=and keys are not surrounded by quotation marks. Note that in Julia aNamedTuplewith a single element needs to be defined either as(; a=1)or as(a=1, ).
Using Deneb.jl one can build a vlspec defining pieces programmatically using standard techniques.
url = "https://vega.github.io/vega-datasets/data/seattle-weather.csv"
data = (; url)
mark = :bar
x=(timeUnit=:month, field=:date, type=:ordinal)
y=(aggregate=:mean, field=:precipitation)
encoding = (; x, y)
vlspec(; data, mark, encoding)
The Deneb.jl API
With vlspec one can build any arbitrary Vega-Lite spec programmatically, providing already an advantage over directly writing JSON specs. However, these direct translations are still rather verbose. Deneb.jl provides an API to create specs in a more concise and convenient way.
data = Data(url="https://vega.github.io/vega-datasets/data/seattle-weather.csv")
chart = data * Mark(:bar) * Encoding("month(date):O", "mean(precipitation)")
The following patterns have been demonstrated in the previous example:
- The sub-spec of the
data,markandencodingproperties have been created withData,MarkandEncoding, and then composed using the*operator. - The
xandychannels have been defined in theEncodingas positional arguments. Alternatively they could've been explicitly defined as keyword arguments. - Inspired by Altair, a string shorthand syntax have been used to conveniently represent the
type,field,aggregateandtimeUnitproperties of the encoding channels.
The convenience of Deneb.jl's API can be further illustrated in the following example of a more elaborated visualization. This visualization represents a bar chart of the average monthly precipitation in Seattle, overlaid with a rule for the overall yearly average, and allows for an interactive moving average for a dragged region using the mouse (a region can be selected and then dragged).
bar = Mark(:bar) * select_interval(
:brush, encodings=[:x],
) * Encoding(
"month(date):O",
"mean(precipitation)",
opacity=condition(:brush, 1, 0.7),
)
rule = Mark(:rule, color=:firebrick, size=3) * transform_filter(
param(:brush)
) * Encoding(y="mean(precipitation)")
chart = data * (bar + rule)
As a comparison, this is how the raw Vega-Lite specification looks like for the example above:
print(chart){
"config": {
"view": {
"continuousWidth": 300,
"continuousHeight": 300,
"step": 25
},
"mark": {
"tooltip": true
}
},
"data": {
"url": "https://vega.github.io/vega-datasets/data/seattle-weather.csv"
},
"layer": [
{
"params": [
{
"name": "brush",
"select": {
"type": "interval",
"encodings": [
"x"
]
}
}
],
"mark": {
"type": "bar"
},
"encoding": {
"opacity": {
"condition": {
"param": "brush",
"value": 1
},
"value": 0.7
},
"x": {
"timeUnit": "month",
"field": "date",
"type": "ordinal"
},
"y": {
"aggregate": "mean",
"field": "precipitation"
}
}
},
{
"transform": [
{
"filter": {
"param": "brush"
}
}
],
"mark": {
"type": "rule",
"color": "firebrick",
"size": 3
},
"encoding": {
"y": {
"aggregate": "mean",
"field": "precipitation"
}
}
}
]
}