View Source VegaLite (VegaLite v0.1.11)

Elixir bindings to Vega-Lite.

Vega-Lite offers a high-level grammar for composing interactive graphics, where every graphic is specified in a declarative fashion relying solely on JSON syntax. To learn more about Vega-Lite please refer to the documentation and explore numerous examples.

This package offers a tiny layer of functionality that makes it easier to build a Vega-Lite graphics specification.

Composing graphics

We offers a light-weight pipeline API akin to the JSON specification. Translating existing Vega-Lite specifications to such specification should be very intuitive in most cases.

Composing a basic Vega-Lite graphic usually consists of the following steps:

alias VegaLite, as: Vl

# Initialize the specification, optionally with some top-level properties
Vl.new(width: 400, height: 400)

# Specify data source for the graphic, see the data_from_* functions
|> Vl.data_from_values(iteration: 1..100, score: 1..100)
# |> Vl.data_from_values([%{iteration: 1, score: 1}, ...])
# |> Vl.data_from_url("...")

# Pick a visual mark for the graphic
|> Vl.mark(:line)
# |> Vl.mark(:point, tooltip: true)

# Map data fields to visual properties of the mark, like position or shape
|> Vl.encode_field(:x, "iteration", type: :quantitative)
|> Vl.encode_field(:y, "score", type: :quantitative)
# |> Vl.encode_field(:color, "country", type: :nominal)
# |> Vl.encode_field(:size, "count", type: :quantitative)

Then, you can compose multiple graphics using layers/2, concat/3, repeat/3 or facet/3.

Vl.new()
|> Vl.data_from_url("https://vega.github.io/editor/data/weather.csv")
|> Vl.transform(filter: "datum.location == 'Seattle'")
|> Vl.concat([
  Vl.new()
  |> Vl.mark(:bar)
  |> Vl.encode_field(:x, "date", time_unit: :month, type: :ordinal)
  |> Vl.encode_field(:y, "precipitation", aggregate: :mean),
  Vl.new()
  |> Vl.mark(:point)
  |> Vl.encode_field(:x, "temp_min", bin: true)
  |> Vl.encode_field(:y, "temp_max", bin: true)
  |> Vl.encode(:size, aggregate: :count)
])

Additionally, you can use transform/2 to preprocess the data, param/3 for introducing interactivity and config/2 for global customization.

Option casing

Note that the specification uses snake-case instead of camel-case. See Options.

Using JSON specification

Alternatively you can parse a Vega-Lite JSON specification directly. This approach makes it easy to explore numerous examples available online.

alias VegaLite, as: Vl

Vl.from_json("""
{
  "data": { "url": "https://vega.github.io/editor/data/cars.json" },
  "mark": "point",
  "encoding": {
    "x": { "field": "Horsepower", "type": "quantitative" },
    "y": { "field": "Miles_per_Gallon", "type": "quantitative" }
  }
}
""")

The result of VegaLite.from_json/1 function can then be passed through any other function to further customize the specification. In particular, it may be useful to parse a JSON specification and add your custom data with VegaLite.data_from_values/3.

Options

Most VegaLite functions accept an optional list of options, which are converted directly as the specification properties. To provide a more Elixir-friendly experience, the options are automatically normalized, so you can use keyword lists and snake-case atom keys. For example, if you specify axis: [label_angle: -45], this library will automatically rewrite it labelAngle, which is the name used by the VegaLite specification.

Export

VegaLite graphics can be exported into various formats, such as SVG, PNG and PDF thorugh the :vega_lite_convert package.

Summary

Functions

Builds a concatenated multi-view specification from the given list of single view specifications.

Adds view configuration to the specification.

Sets data properties in the specification.

Sets data URL in the specification.

Sets inline data in the specification.

Specifies top-level datasets.

Adds an encoding entry to the specification.

Adds field encoding entry to the specification.

Adds repeated field encoding entry to the specification.

Builds a facet multi-view specification from the given single-view template.

Parses the given Vega-Lite JSON specification and wraps in the VegaLite struct for further processing.

Wraps the given Vega-Lite specification in the VegaLite struct for further processing.

Builds a layered multi-view specification from the given list of single view specifications.

Sets mark type in the specification.

Returns a new specification wrapped in the VegaLite struct.

Adds a parameter to the specification.

Adds a projection spec to the specification.

Builds a repeated multi-view specification from the given single-view template.

Adds a resolve entry to the specification.

Returns the underlying Vega-Lite specification.

Adds a transformation to the specification.

Types

@type spec() :: map()
@type t() :: %VegaLite{spec: spec()}

Functions

Link to this function

concat(vl, child_views, type \\ :wrappable)

View Source
@spec concat(t(), [t()], :wrappable | :horizontal | :vertical) :: t()

Builds a concatenated multi-view specification from the given list of single view specifications.

The concat type must be either :wrappable (default), :horizontal or :vertical.

Examples

Vl.new()
|> Vl.data_from_values(...)
|> Vl.concat([
  Vl.new()
  |> ...,
  Vl.new()
  |> ...,
  Vl.new()
  |> ...
])

Vl.new()
|> Vl.data_from_values(...)
|> Vl.concat(
  [
    Vl.new()
    |> ...,
    Vl.new()
    |> ...
  ],
  :horizontal
)

See the docs for more details.

@spec config(
  t(),
  keyword()
) :: t()

Adds view configuration to the specification.

Configuration allows for setting general properties of the visualization.

All provided options are converted to configuration properties and merged with the existing configuration in a shallow manner.

Examples

Vl.new()
|> ...
|> Vl.config(
  view: [stroke: :transparent],
  padding: 100,
  background: "#333333"
)

See the docs for more details.

@spec data(
  t(),
  keyword()
) :: t()

Sets data properties in the specification.

Defining the data source is usually the first step when building a graphic. For most use cases it's preferable to use more specific functions like data_from_url/3 or data_from_values/3.

All provided options are converted to data properties.

Examples

Vl.new()
|> Vl.data(sequence: [start: 0, stop: 12.7, step: 0.1, as: "x"])
|> ...

See the docs for more details.

Link to this function

data_from_url(vl, url, opts \\ [])

View Source
@spec data_from_url(t(), String.t(), keyword()) :: t()

Sets data URL in the specification.

The URL should be accessible by whichever client renders the specification, so preferably an absolute one.

All provided options are converted to data properties.

Examples

Vl.new()
|> Vl.data_from_url("https://vega.github.io/editor/data/penguins.json")
|> ...

Vl.new()
|> Vl.data_from_url("https://vega.github.io/editor/data/stocks.csv", format: :csv)
|> ...

See the docs for more details.

Link to this function

data_from_values(vl, values, opts \\ [])

View Source
@spec data_from_values(t(), Table.Reader.t(), keyword()) :: t()

Sets inline data in the specification.

Any tabular data is accepted, as long as it adheres to the Table.Reader protocol.

Options

  • :only - specifies a subset of fields to pick from the data

All other options are converted to data properties.

Examples

data = [
  %{"category" => "A", "score" => 28},
  %{"category" => "B", "score" => 55}
]

Vl.new()
|> Vl.data_from_values(data)
|> ...

Note that any tabular data is accepted, as long as it adheres to the Table.Reader protocol. For example that's how we can pass individual series:

xs = 1..100
ys = 1..100

Vl.new()
|> Vl.data_from_values(x: xs, y: ys)
|> ...

See the docs for more details.

Link to this function

datasets_from_values(vl, datasets)

View Source
@spec datasets_from_values(t(), Enumerable.t()) :: t()

Specifies top-level datasets.

Datasets can be used as a data source further in the specification. This is useful if you need to refer to the data in multiple places or use a transform/2 like :lookup.

Datasets should be a key-value enumerable, where key is the dataset name and value is tabular data as in data_from_values/3.

Examples

results = [
  %{"category" => "A", "score" => 28},
  %{"category" => "B", "score" => 55}
]

points = [
  %{"x" => "1", "y" => 10},
  %{"x" => "2", "y" => 100}
]

Vl.new()
|> Vl.datasets_from_values(results: results, points: points)
# Use one of the data sets as the primary data
|> Vl.data(name: "results")
|> ...

See the docs for more details.

Link to this function

encode(vl, channel, opts)

View Source
@spec encode(t(), atom(), keyword() | [keyword()]) :: t()

Adds an encoding entry to the specification.

Visual channel represents a property of a visual mark, for instance the :x and :y channels specify where a point should be placed. Encoding defines the source of values for those channels.

In most cases you want to map specific data field to visual channels, prefer the encode_field/4 function for that.

All provided options are converted to channel properties.

Examples

Vl.new()
|> Vl.encode(:x, value: 2)
|> ...

Vl.new()
|> Vl.encode(:y, aggregate: :count, type: :quantitative)
|> ...

Vl.new()
|> Vl.encode(:y, field: "price")
|> ...

Alternatively, a list of property lists may be given:

Vl.new()
|> Vl.encode(:tooltip, [
  [field: "height", type: :quantitative],
  [field: "width", type: :quantitative]
])
|> ...

See the docs for more details.

Link to this function

encode_field(vl, channel, field, opts \\ [])

View Source
@spec encode_field(t(), atom(), String.t(), keyword()) :: t()

Adds field encoding entry to the specification.

A shorthand for encode/3, mapping a data field to a visual channel.

For example, if the data has "price" and "time" fields, you could map "time" to the :x channel and "price" to the :y channel. This, combined with a line mark, would then result in price-over-time plot.

All provided options are converted to channel properties.

Types

Field data type is automatically inferred, but oftentimes needs to be specified explicitly to get the desired result. The :type option can be either of:

  • :quantitative - when the field expresses some kind of quantity, typically numerical

  • :temporal - when the field represents a point in time

  • :nominal - when the field represents a category

  • :ordinal - when the field represents a ranked order. It is similar to :nominal, but there is a clear order of values

  • :geojson - when the field represents a geographic shape adhering to the GeoJSON specification

See the docs for more details on types.

Examples

Vl.new()
|> Vl.data_from_values(...)
|> Vl.mark(:point)
|> Vl.encode_field(:x, "time", type: :temporal)
|> Vl.encode_field(:y, "price", type: :quantitative)
|> Vl.encode_field(:color, "country", type: :nominal)
|> Vl.encode_field(:size, "count", type: :quantitative)
|> ...

Vl.new()
|> Vl.encode_field(:x, "date", time_unit: :month, title: "Month")
|> Vl.encode_field(:y, "price", type: :quantitative, aggregate: :mean, title: "Mean product price")
|> ...

See the docs for more details.

Link to this function

encode_repeat(vl, channel, repeat_type, opts \\ [])

View Source
@spec encode_repeat(t(), atom(), :repeat | :row | :column | :layer, keyword()) :: t()

Adds repeated field encoding entry to the specification.

A shorthand for encode/3, mapping a field to a visual channel, as given by the repeat operator.

Repeat type must be either :repeat, :row, :column or :layer and correspond to the repeat definition.

All provided options are converted to channel properties.

Examples

See repeat/3 to see the full picture.

See the docs for more details.

Link to this function

facet(vl, facet_def, child_view)

View Source
@spec facet(t(), keyword(), t()) :: t()

Builds a facet multi-view specification from the given single-view template.

Facet definition must be either a field definition or a row/column mapping.

Note that you can also create facet graphics by using the :facet, :column and :row encoding channels.

Examples

Vl.new()
|> Vl.data_from_values(...)
|> Vl.facet(
  [field: "country"],
  Vl.new()
  |> Vl.mark(:bar)
  |> Vl.encode_field(:x, ...)
  |> Vl.encode_field(:y, ...)
)

Vl.new()
|> Vl.data_from_values(...)
|> Vl.facet(
  [
    row: [field: "country", title: "Country"],
    column: [field: "year", title: "Year"]
  ]
  Vl.new()
  |> Vl.mark(:bar)
  |> Vl.encode_field(:x, ...)
  |> Vl.encode_field(:y, ...)
)

See the docs for more details.

@spec from_json(String.t()) :: t()

Parses the given Vega-Lite JSON specification and wraps in the VegaLite struct for further processing.

Examples

Vl.from_json("""
{
  "data": { "url": "https://vega.github.io/editor/data/cars.json" },
  "mark": "point",
  "encoding": {
    "x": { "field": "Horsepower", "type": "quantitative" },
    "y": { "field": "Miles_per_Gallon", "type": "quantitative" }
  }
}
""")

See the docs for more details.

@spec from_spec(spec()) :: t()

Wraps the given Vega-Lite specification in the VegaLite struct for further processing.

There is also from_json/1 that handles JSON parsing for you.

See the docs for more details.

@spec layers(t(), [t()]) :: t()

Builds a layered multi-view specification from the given list of single view specifications.

Examples

Vl.new()
|> Vl.data_from_values(...)
|> Vl.layers([
  Vl.new()
  |> Vl.mark(:line)
  |> Vl.encode_field(:x, ...)
  |> Vl.encode_field(:y, ...),
  Vl.new()
  |> Vl.mark(:rule)
  |> Vl.encode_field(:y, ...)
  |> Vl.encode(:size, value: 2)
])

Vl.new()
|> Vl.data_from_values(...)
# Note: top-level data, encoding, transforms are inherited
# by the child views unless overridden
|> Vl.encode_field(:x, ...)
|> Vl.layers([
  ...
])

See the docs for more details.

Link to this function

mark(vl, type, opts \\ [])

View Source
@spec mark(t(), atom(), keyword()) :: t()

Sets mark type in the specification.

Mark is a predefined visual object like a point or a line. Visual properties of the mark are defined by encoding.

All provided options are converted to mark properties.

Examples

Vl.new()
|> Vl.mark(:point)
|> ...

Vl.new()
|> Vl.mark(:point, tooltip: true)
|> ...

See the docs for more details.

@spec new(keyword()) :: t()

Returns a new specification wrapped in the VegaLite struct.

All provided options are converted to top-level properties of the specification.

Examples

Vl.new(
  title: "My graph",
  width: 200,
  height: 200
)
|> ...

See the docs for more details.

@spec param(t(), String.t(), keyword()) :: t()

Adds a parameter to the specification.

Parameters are the basic building blocks for introducing interactions to graphics.

All provided options are converted to parameter properties.

Examples

Vl.new()
|> Vl.data_from_values(...)
|> Vl.concat([
  Vl.new()
  # Define a parameter named "brush", whose value is a user-selected interval on the x axis
  |> Vl.param("brush", select: [type: :interval, encodings: [:x]])
  |> Vl.mark(:area)
  |> Vl.encode_field(:x, "date", type: :temporal)
  |> ...,
  Vl.new()
  |> Vl.mark(:area)
  # Use the "brush" parameter value to limit the domain of this view
  |> Vl.encode_field(:x, "date", type: :temporal, scale: [domain: [param: "brush"]])
  |> ...
])

Parameters can also be specified using UI inputs, or computed based on other parameters:

Vl.new()
|> Vl.param("height", value: 20, bind: [input: :range, min: 1, max: 100, step: 1])
|> Vl.param("halfHeight", expr: "height / 2")
|> ...

See the docs for more details.

@spec projection(
  t(),
  keyword()
) :: t()

Adds a projection spec to the specification.

Projection maps longitude and latitude pairs to x, y coordinates.

Examples

Vl.new()
|> Vl.data_from_values(...)
|> Vl.projection(type: :albers_usa)
|> Vl.mark(:circle)
|> Vl.encode_field(:longitude, "longitude", type: :quantitative)
|> Vl.encode_field(:latitude, "latitude", type: :quantitative)

See the docs for more details.

Link to this function

repeat(vl, repeat_def, child_view)

View Source
@spec repeat(t(), keyword(), t()) :: t()

Builds a repeated multi-view specification from the given single-view template.

Repeat definition must be either a list of fields or a row/column/layer mapping. Then some channels can be bound to a repeated field using encode_repeat/4.

Examples

# Simple repeat
Vl.new()
|> Vl.data_from_values(...)
|> Vl.repeat(
  ["temp_max", "precipitation", "wind"],
  Vl.new()
  |> Vl.mark(:line)
  |> Vl.encode_field(:x, "date", time_unit: :month)
  # The graphic will be reapeated with :y mapped to "temp_max",
  # "precipitation" and "wind" respectively
  |> Vl.encode_repeat(:y, :repeat, aggregate: :mean)
)

# Grid repeat
Vl.new()
|> Vl.data_from_values(...)
|> Vl.repeat(
  [
    row: [
      "beak_length",
      "beak_depth",
      "flipper_length",
      "body_mass"
    ],
    column: [
      "body_mass",
      "flipper_length",
      "beak_depth",
      "beak_length"
    ]
  ],
  Vl.new()
  |> Vl.mark(:point)
  # The graphic will be repeated for every combination of :x and :y
  # taken from the :row and :column lists above
  |> Vl.encode_repeat(:x, :column, type: :quantitative)
  |> Vl.encode_repeat(:y, :row, type: :quantitative)
)

See the docs for more details.

@spec resolve(t(), atom(), keyword()) :: t()

Adds a resolve entry to the specification.

Resolution defines how multi-view graphics are combined with regard to scales, axis and legend.

Example

Vl.new()
|> Vl.data_from_values(...)
|> Vl.layers([
  Vl.new()
  |> ...,
  Vl.new()
  |> ...
])
|> Vl.resolve(:scale, y: :independent)

See the docs for more details.

@spec to_spec(t()) :: spec()

Returns the underlying Vega-Lite specification.

The result is a nested Elixir datastructure that serializes to Vega-Lite JSON specification.

See the docs for more details.

@spec transform(
  t(),
  keyword()
) :: t()

Adds a transformation to the specification.

Transformation describes an operation on data, like calculating new fields, aggregating or filtering.

All provided options are converted to transform properties.

Examples

Vl.new()
|> Vl.data_from_values(...)
|> Vl.transform(calculate: "sin(datum.x)", as: "sin_x")
|> ...

Vl.new()
|> Vl.data_from_values(...)
|> Vl.transform(filter: "datum.height > 150")
|> ...

Vl.new()
|> Vl.data_from_values(...)
|> Vl.transform(regression: "price", on: "date")
|> ...

See the docs for more details.