# Plotting with VegaLite ```elixir Mix.install([ {:vega_lite, "~> 0.1.6"}, {:kino_vega_lite, "~> 0.1.11"} ]) ``` ## Introduction We need two libraries for plotting in Livebook: * The [`vega_lite`](https://hexdocs.pm/vega_lite/) package allows us to define our graph specifications * The [`kino_vega_lite`](https://hexdocs.pm/kino_vega_lite/) package instructs Livebook how to render our specifications Let's install them by running the setup cell above. When building graphics we make extensive use of the functions from `VegaLite`, so it's useful to alias the module as something shorter. ```elixir alias VegaLite, as: Vl ``` ## The Chart smart cell Before we get into exploring all the various chart types, let's have a look at an awesome feature that comes with `kino_vega_lite` - the **Chart** smart cell! Coding up a chart usually involves a couple steps. If you don't know the API of a particular library, it may be a bit challenging. On the other hand, if you know the API in-and-out, it's a rather repetitive task. That's where the **Chart** smart cell comes in, it is a high-level UI that helps us write our chart code. It is great a tool for learning and for automating our workflows. Let's give it a try! First, we need some data to work with, here's a small excerpt from the popular [Iris dataset](https://www.kaggle.com/datasets/uciml/iris): ```elixir iris = [ %{"petal_length" => 5.1, "petal_width" => 1.9, "species" => "Iris-virginica"}, %{"petal_length" => 4.0, "petal_width" => 1.3, "species" => "Iris-versicolor"}, %{"petal_length" => 1.6, "petal_width" => 0.2, "species" => "Iris-setosa"}, %{"petal_length" => 1.6, "petal_width" => 0.2, "species" => "Iris-setosa"}, %{"petal_length" => 4.6, "petal_width" => 1.4, "species" => "Iris-versicolor"}, %{"petal_length" => 4.8, "petal_width" => 1.8, "species" => "Iris-virginica"}, %{"petal_length" => 5.6, "petal_width" => 2.2, "species" => "Iris-virginica"}, %{"petal_length" => 5.1, "petal_width" => 1.6, "species" => "Iris-versicolor"}, %{"petal_length" => 1.5, "petal_width" => 0.3, "species" => "Iris-setosa"}, %{"petal_length" => 4.5, "petal_width" => 1.6, "species" => "Iris-versicolor"} ] :ok ``` Now, to insert a new Chart cell, place your cursor between cells, click on the + Smart button and select Chart. You can see an example of that below. Click on the "Evaluate" button to see the chart, then see how it changes as you customize the parameters. ```elixir Vl.new(width: 400, height: 200, title: "Iris") |> Vl.data_from_values(iris, only: ["petal_length", "petal_width", "species"]) |> Vl.mark(:point) |> Vl.encode_field(:x, "petal_length", type: :quantitative) |> Vl.encode_field(:y, "petal_width", type: :quantitative) |> Vl.encode_field(:color, "species") ``` Under cell actions there is a "Source" button, click on it to see the source code of your chart. You can even convert it to a regular Code cell for further adjustments! The Chart smart cell is one of many Smart cells available in Livebook ⚡ Not only that, you can create your own Smart cells too, which we discuss in the [Exploring Smart cells](/learn/notebooks/smart-cells) notebook! ## Basic concepts Composing a basic Vega-Lite graphic usually consists of the following steps: ```elixir # Initialize the specification, optionally with some top-level properties Vl.new(width: 400, height: 400) # Specify data source for the graphic using one of the data_from_* functions |> Vl.data_from_values(iteration: 1..100, score: 1..100) # Pick a visual mark |> Vl.mark(:line) # Map data fields to visual properties of the mark, in this case point positions |> Vl.encode_field(:x, "iteration", type: :quantitative) |> Vl.encode_field(:y, "score", type: :quantitative) ``` Below you can find a number of example graphics for common use cases. For a number of plain Vega-Lite examples you can look [here](https://vega.github.io/vega-lite/examples). ## Bar charts ### Simple bar chart A bar chart encodes quantitative values as the length of regular bars. ```elixir # Source: https://vega.github.io/vega-lite/examples/bar.html data = [ %{"a" => "A", "b" => 28}, %{"a" => "B", "b" => 55}, %{"a" => "C", "b" => 43}, %{"a" => "D", "b" => 91}, %{"a" => "E", "b" => 81}, %{"a" => "F", "b" => 53}, %{"a" => "G", "b" => 19}, %{"a" => "H", "b" => 87}, %{"a" => "I", "b" => 52} ] Vl.new(width: 400, height: 300) |> Vl.data_from_values(data) |> Vl.mark(:bar) |> Vl.encode_field(:x, "a", type: :nominal, axis: [label_angle: 0]) |> Vl.encode_field(:y, "b", type: :quantitative) ``` ### Stacked bar chart A stacked bar chart contains multi-color bars to represent several quantitative values at once. ```elixir # Source: https://vega.github.io/vega-lite/examples/stacked_bar_weather.html Vl.new(width: 300, height: 200) |> Vl.data_from_url("https://vega.github.io/editor/data/seattle-weather.csv") |> Vl.mark(:bar) |> Vl.encode_field(:x, "date", time_unit: :month, type: :ordinal, title: "Month of the year") |> Vl.encode(:y, aggregate: :count, type: :quantitative, title: "Number of days") |> Vl.encode_field(:color, "weather", type: :nominal, title: "Weather type", scale: [ domain: ["sun", "fog", "drizzle", "rain", "snow"], range: ["#e7ba52", "#c7c7c7", "#aec7e8", "#1f77b4", "#9467bd"] ] ) ``` ### Grouped bar chart Graphing one bar plot per group. ```elixir # Source: https://vega.github.io/vega-lite/examples/bar_grouped.html Vl.new(width: [step: 12]) |> Vl.data_from_url("https://vega.github.io/editor/data/population.json") |> Vl.transform(filter: "datum.year == 2000") |> Vl.transform(calculate: "datum.sex == 2 ? 'Female' : 'Male'", as: "gender") |> Vl.mark(:bar) |> Vl.encode_field(:column, "age", type: :ordinal, spacing: 10) |> Vl.encode_field(:y, "people", aggregate: :sum, title: "population", axis: [grid: false]) |> Vl.encode_field(:x, "gender", title: nil) |> Vl.encode_field(:color, "gender") |> Vl.config(view: [stroke: nil]) ``` ## Histograms, density plots and dot plots ### Histogram A histogram represents the value frequency in predefined intervals. ```elixir # Source: https://vega.github.io/vega-lite/examples/histogram.html Vl.new() |> Vl.data_from_url("https://vega.github.io/editor/data/movies.json") |> Vl.mark(:bar) |> Vl.encode_field(:x, "IMDB Rating", bin: true) |> Vl.encode(:y, aggregate: :count) ``` ### Density plot A density plot represents the distribution estimate of a numeric value. ```elixir # Source: https://vega.github.io/vega-lite/examples/area_density.html Vl.new(width: 400, height: 100) |> Vl.data_from_url("https://vega.github.io/editor/data/movies.json") |> Vl.transform(density: "IMDB Rating") |> Vl.mark(:area) |> Vl.encode_field(:x, "value", type: :quantitative, title: "IMDB rating") |> Vl.encode_field(:y, "density", type: :quantitative) ``` ### Stacked density estimates Several density plots stacked together. ```elixir # Source: https://vega.github.io/vega-lite/examples/area_density_stacked.html Vl.new(width: 400, height: 80) |> Vl.data_from_url("https://vega.github.io/editor/data/penguins.json") |> Vl.transform(density: "Body Mass (g)", groupby: ["Species"], extent: [2500, 6500]) |> Vl.mark(:area) |> Vl.encode_field(:x, "value", type: :quantitative, title: "Body mass (g)") |> Vl.encode_field(:y, "density", type: :quantitative, stack: true) |> Vl.encode_field(:color, "Species", type: :nominal) ``` ### 2D Histogram scatterplot A 2D version of a regular histogram, with intervals in both axis and frequency represented by point size. ```elixir # Source: https://vega.github.io/vega-lite/examples/circle_binned.html Vl.new() |> Vl.data_from_url("https://vega.github.io/editor/data/movies.json") |> Vl.mark(:circle) |> Vl.encode_field(:x, "IMDB Rating", bin: [maxbins: 10]) |> Vl.encode_field(:y, "Rotten Tomatoes Rating", bin: [maxbins: 10]) |> Vl.encode(:size, aggregate: :count) ``` ### 2D Histogram heatmap Another version of 2D histogram, with color scale representing value frequency. ```elixir # Source: https://vega.github.io/vega-lite/examples/rect_binned_heatmap.html Vl.new(width: 300, height: 200) |> Vl.data_from_url("https://vega.github.io/editor/data/movies.json") |> Vl.transform( filter: [ and: [ [field: "IMDB Rating", valid: true], [field: "Rotten Tomatoes Rating", valid: true] ] ] ) |> Vl.mark(:rect) |> Vl.encode_field(:x, "IMDB Rating", bin: [maxbins: 60]) |> Vl.encode_field(:y, "Rotten Tomatoes Rating", bin: [maxbins: 40]) |> Vl.encode(:color, aggregate: :count) |> Vl.config(view: [stroke: nil]) ``` ### 2D Ordinal heatmap A heatmap similar to the above, but with already discrete categories. ```elixir # Source: https://vega.github.io/vega-lite/examples/rect_heatmap_weather.html Vl.new(title: "Daily max temperatures (C) in Seattle, WA") |> Vl.data_from_url("https://vega.github.io/editor/data/seattle-weather.csv") |> Vl.mark(:rect) |> Vl.encode_field(:x, "date", time_unit: :date, type: :ordinal, title: "Day", axis: [label_angle: 0, format: "%e"] ) |> Vl.encode_field(:y, "date", time_unit: :month, type: :ordinal, title: "Month" ) |> Vl.encode_field(:color, "temp_max", aggregate: :max, type: :quantitative, legend: [title: nil] ) |> Vl.config(view: [stroke: nil]) ``` ## Scatter and strip plots ### Scatterplot A scatterplot represents 2D data directly as geometric points. ```elixir # Source: https://vega.github.io/vega-lite/examples/point_2d.html Vl.new(width: 400, height: 300) |> Vl.data_from_url("https://vega.github.io/editor/data/cars.json") |> Vl.mark(:point) |> Vl.encode_field(:x, "Horsepower", type: :quantitative) |> Vl.encode_field(:y, "Miles_per_Gallon", type: :quantitative) ``` ### Strip plot Shows the relationship between two values using tick marks. ```elixir # Source: https://vega.github.io/vega-lite/examples/tick_strip.html Vl.new() |> Vl.data_from_url("https://vega.github.io/editor/data/cars.json") |> Vl.mark(:tick) |> Vl.encode_field(:x, "Horsepower", type: :quantitative) |> Vl.encode_field(:y, "Cylinders", type: :ordinal) ``` ### Colored scatterplot Scatterplot with clear point groups. ```elixir # Source: https://vega.github.io/vega-lite/examples/point_color_with_shape.html Vl.new(width: 400, height: 300) |> Vl.data_from_url("https://vega.github.io/editor/data/penguins.json") |> Vl.mark(:point) |> Vl.encode_field(:x, "Flipper Length (mm)", type: :quantitative, scale: [zero: false]) |> Vl.encode_field(:y, "Body Mass (g)", type: :quantitative, scale: [zero: false]) |> Vl.encode_field(:color, "Species", type: :nominal) |> Vl.encode_field(:shape, "Species", type: :nominal) ``` ## Line charts ### Line chart A simple chart resulting from linking individual points. ```elixir # Source: https://vega.github.io/vega-lite/examples/line.html Vl.new(width: 400, height: 300) |> Vl.data_from_url("https://vega.github.io/editor/data/stocks.csv") |> Vl.transform(filter: "datum.symbol == 'GOOG'") |> Vl.mark(:line) |> Vl.encode_field(:x, "date", type: :temporal) |> Vl.encode_field(:y, "price", type: :quantitative) ``` ### Multi series line chart Multiple line charts combined together. ```elixir # Source: https://vega.github.io/vega-lite/examples/line_color.html Vl.new(width: 400, height: 300) |> Vl.data_from_url("https://vega.github.io/editor/data/stocks.csv") |> Vl.mark(:line) |> Vl.encode_field(:x, "date", type: :temporal) |> Vl.encode_field(:y, "price", type: :quantitative) |> Vl.encode_field(:color, "symbol", type: :nominal) ``` ### Line chart with point markers Marking individual points on top of the line. ```elixir # Source: https://vega.github.io/vega-lite/examples/line_color.html Vl.new(width: 400, height: 300) |> Vl.data_from_url("https://vega.github.io/editor/data/stocks.csv") |> Vl.mark(:line, point: true) |> Vl.encode_field(:x, "date", time_unit: :year, type: :temporal) |> Vl.encode_field(:y, "price", aggregate: :mean, type: :quantitative) |> Vl.encode_field(:color, "symbol", type: :nominal) ``` ### Sequence generators Line charts using generated data. ```elixir # Source: https://vega.github.io/vega-lite/examples/sequence_line_fold.html Vl.new(width: 300, height: 150) |> Vl.data(sequence: [start: 0, stop: 12.7, step: 0.1, as: "x"]) |> Vl.transform(calculate: "sin(datum.x)", as: "sin(x)") |> Vl.transform(calculate: "cos(datum.x)", as: "cos(x)") |> Vl.transform(fold: ["sin(x)", "cos(x)"]) |> Vl.mark(:line) |> Vl.encode_field(:x, "x", type: :quantitative) |> Vl.encode_field(:y, "value", type: :quantitative) |> Vl.encode_field(:color, "key", type: :nominal, title: nil) ``` ## Area charts and streamgraphs ### Area chart An area chart represents quantitative data and is based on line chart. ```elixir # Source: https://vega.github.io/vega-lite/examples/area.html Vl.new(width: 400, height: 300) |> Vl.data_from_url("https://vega.github.io/editor/data/unemployment-across-industries.json") |> Vl.mark(:area) |> Vl.encode_field(:x, "date", time_unit: :yearmonth, axis: [format: "%Y"]) |> Vl.encode_field(:y, "count", aggregate: :sum, title: "count") ``` ### Stacked area chart A combination of multiple area charts allowing for easy visual comparison. ```elixir # Source: https://vega.github.io/vega-lite/examples/stacked_area.html Vl.new(width: 400, height: 300) |> Vl.data_from_url("https://vega.github.io/editor/data/unemployment-across-industries.json") |> Vl.mark(:area) |> Vl.encode_field(:x, "date", time_unit: :yearmonth, axis: [format: "%Y"]) |> Vl.encode_field(:y, "count", aggregate: :sum, title: "count") |> Vl.encode_field(:color, "series", scale: [scheme: "category20b"]) ``` ### Streamgraph A streamgraph is a type of area chart which is displaced around a central axis. ```elixir Vl.new(width: 400, height: 300) |> Vl.data_from_url("https://vega.github.io/editor/data/unemployment-across-industries.json") |> Vl.mark(:area) |> Vl.encode_field(:x, "date", time_unit: :yearmonth, axis: [format: "%Y"]) |> Vl.encode_field(:y, "count", aggregate: :sum, axis: nil, stack: :center) |> Vl.encode_field(:color, "series", scale: [scheme: "category20b"]) ``` ## Circular plots ### Pie chart A pie chart encodes proportional differences among a set of numeric values as the angular extent and area of a circular slice. ```elixir data = [ %{"category" => 1, "value" => 4}, %{"category" => 2, "value" => 6}, %{"category" => 3, "value" => 10}, %{"category" => 4, "value" => 3}, %{"category" => 5, "value" => 7}, %{"category" => 6, "value" => 8} ] Vl.new() |> Vl.data_from_values(data) |> Vl.mark(:arc) |> Vl.encode_field(:theta, "value", type: :quantitative) |> Vl.encode_field(:color, "category", type: :nominal) |> Vl.config(view: [stroke: nil]) ``` ### 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. ```elixir # Source: https://vega.github.io/vega-lite/examples/arc_radial.html Vl.new() |> Vl.data_from_values(data: [12, 23, 47, 6, 52, 19]) |> Vl.encode_field(:theta, "data", type: :quantitative, stack: true) |> Vl.encode_field(:radius, "data", scale: [type: :sqrt, zero: true, range_min: 20]) |> Vl.encode_field(:color, "data", type: :nominal, legend: nil) |> Vl.layers([ Vl.new() |> Vl.mark(:arc, inner_radius: 20, stroke: "#fff"), Vl.new() |> Vl.mark(:text, radius_offset: 10) |> Vl.encode_field(:text, "data", type: :quantitative) ]) |> Vl.config(view: [stroke: nil]) ``` ## Calculations ### Layering rolling averages over raw values Raw value points and a calculated rolling average on top. ```elixir # Source: https://vega.github.io/vega-lite/examples/layer_line_rolling_mean_point_raw.html Vl.new(width: 400, height: 300) |> Vl.data_from_url("https://vega.github.io/editor/data/seattle-weather.csv") |> Vl.transform( window: [ [field: "temp_max", op: :mean, as: "rolling_mean"] ], frame: [-15, 15] ) |> Vl.encode_field(:x, "date", type: :temporal, title: "Date", opacity: 0.3) |> Vl.layers([ Vl.new() |> Vl.mark(:point, opacity: 0.3) |> Vl.encode_field(:y, "temp_max", type: :quantitative), Vl.new() |> Vl.mark(:line, color: :red, size: 3) |> Vl.encode_field(:y, "rolling_mean", type: :quantitative, title: "Rolling mean of max temperature" ) ]) ``` ### Linear regression Linear regression is an approach of finding a line that best represents the relationship in the data. ```elixir # Source: https://vega.github.io/vega-lite/examples/layer_point_line_regression.html Vl.new(width: 400, height: 300) |> Vl.data_from_url("https://vega.github.io/editor/data/movies.json") |> Vl.layers([ Vl.new() |> Vl.mark(:point, filled: true) |> Vl.encode_field(:x, "Rotten Tomatoes Rating", type: :quantitative) |> Vl.encode_field(:y, "IMDB Rating", type: :quantitative), Vl.new() |> Vl.mark(:line, color: :firebrick) |> Vl.transform(regression: "IMDB Rating", on: "Rotten Tomatoes Rating") |> Vl.encode_field(:x, "Rotten Tomatoes Rating", type: :quantitative) |> Vl.encode_field(:y, "IMDB Rating", type: :quantitative) ]) ``` ## Composite marks ### Error bars showing standard deviation Adding observations standard deviation alongside the mean point. ```elixir # Source: https://vega.github.io/vega-lite/examples/layer_point_errorbar_ci.html Vl.new(width: 400, height: 300) |> Vl.data_from_url("https://vega.github.io/editor/data/barley.json") |> Vl.encode_field(:y, "variety", type: :ordinal) |> Vl.layers([ Vl.new() |> Vl.mark(:point, filled: true) |> Vl.encode_field(:x, "yield", aggregate: :mean, type: :quantitative, scale: [zero: false], title: "Barley yield" ) |> Vl.encode(:color, value: :black), Vl.new() |> Vl.mark(:errorbar, extent: :stdev) |> Vl.encode_field(:x, "yield", type: :quantitative, title: "Barley yield") ]) ``` ### Line chart with confidence interval band Line with confidence band, which represents the uncertainty of an estimate function. ```elixir # Source: https://vega.github.io/vega-lite/examples/layer_line_errorband_ci.html Vl.new(width: 400, height: 300) |> Vl.data_from_url("https://vega.github.io/editor/data/cars.json") |> Vl.encode_field(:x, "Year", time_unit: :year) |> Vl.layers([ Vl.new() |> Vl.mark(:errorband, extent: :ci) |> Vl.encode_field(:y, "Miles_per_Gallon", type: :quantitative, title: "Mean of miles per gallon (95% CIs)" ), Vl.new() |> Vl.mark(:line) |> Vl.encode_field(:y, "Miles_per_Gallon", aggregate: :mean) ]) ``` ## Box plots ### Box plot with min/max whiskers A vertical box plot showing median, min, and max values. ```elixir # Source: https://vega.github.io/vega-lite/examples/boxplot_minmax_2D_vertical.html Vl.new(width: 400, height: 300) |> Vl.data_from_url("https://vega.github.io/editor/data/penguins.json") |> Vl.mark(:boxplot, extent: "min-max") |> Vl.encode_field(:x, "Species", type: :nominal) |> Vl.encode_field(:color, "Species", type: :nominal, legend: nil) |> Vl.encode_field(:y, "Body Mass (g)", type: :quantitative, scale: [zero: false]) ``` ## Faceting ### Trellis bar chart Trellis display is a series of graphs with the same scale and axes split according to some criteria, so that they are easy to compare. ```elixir # Source: https://vega.github.io/vega-lite/examples/trellis_bar.html Vl.new(width: [step: 17]) |> Vl.data_from_url("https://vega.github.io/editor/data/population.json") |> Vl.transform(filter: "datum.year == 2000") |> Vl.transform(calculate: "datum.sex == 2 ? 'Female' : 'Male'", as: "gender") |> Vl.mark(:bar) |> Vl.encode_field(:row, "gender") |> Vl.encode_field(:x, "age") |> Vl.encode_field(:y, "people", aggregate: :sum, title: "population") |> Vl.encode_field(:color, "gender") ``` ### Trellis area chart Similar to the above, except for area charts. ```elixir # Source: https://vega.github.io/vega-lite/examples/trellis_area.html Vl.new(width: 400, height: 60) |> Vl.data_from_url("https://vega.github.io/editor/data/stocks.csv") |> Vl.transform(filter: "datum.symbol !== 'GOOG'") |> Vl.mark(:area) |> Vl.encode_field(:x, "date", type: :temporal, axis: [grid: false]) |> Vl.encode_field(:y, "price", type: :quantitative, axis: [grid: false]) |> Vl.encode_field(:color, "symbol", type: :nominal, legend: nil) |> Vl.encode_field(:row, "symbol", type: :nominal, title: "Symbol") ``` ### Trellis multi-level scatterplot Again, the trellis display, but this time for scatterplot and grouped data. ```elixir # Source: https://vega.github.io/vega-lite/examples/trellis_barley.html Vl.new(name: "Trellis Barley", height: [step: 12]) |> Vl.data_from_url("https://vega.github.io/editor/data/barley.json") |> Vl.mark(:point) |> Vl.encode_field(:facet, "site", type: :ordinal, columns: 2, sort: [op: :median, field: "yield"] ) |> Vl.encode_field(:x, "yield", aggregate: :median, type: :quantitative, scale: [zero: false]) |> Vl.encode_field(:y, "variety", type: :ordinal, sort: "-x") |> Vl.encode_field(:color, "year", type: :nominal) ``` ## Repeated graphics ### Repeated layer A multi-layer chart composed by repeating the same specification over several fields. ```elixir # Source: https://vega.github.io/vega-lite/examples/repeat_layer.html Vl.new(width: 400, height: 300) |> Vl.data_from_url("https://vega.github.io/editor/data/movies.json") |> Vl.repeat( [layer: ["US Gross", "Worldwide Gross"]], Vl.new() |> Vl.mark(:line) |> Vl.encode_field(:x, "IMDB Rating", bin: true, type: :quantitative) |> Vl.encode_repeat(:y, :layer, aggregate: :mean, type: :quantitative, title: "Mean of US and Worldwide Gross" ) |> Vl.encode(:color, datum: [repeat: :layer], type: :nominal) ) ``` ### Repeated figure A multi-view chart composed by repeating the same specification over several fields. ```elixir # Source: https://vega.github.io/vega-lite/docs/repeat.html#repeated-line-charts Vl.new() |> Vl.data_from_url("https://vega.github.io/editor/data/weather.csv") |> Vl.repeat( ["temp_max", "precipitation", "wind"], Vl.new() |> Vl.mark(:line) |> Vl.encode_field(:x, "date", time_unit: :month) |> Vl.encode_repeat(:y, :repeat, aggregate: :mean) |> Vl.encode_field(:color, "location") ) ``` ### Scatterplot Matrix (SPLOM) Scatterplot matrix (SPLOM) is a series of graphics for different pairs of variables, it's useful to determine possible correlation between some variables. ```elixir # Source: https://vega.github.io/vega-lite/docs/repeat.html#scatterplot-matrix-splom Vl.new() |> Vl.data_from_url("https://vega.github.io/editor/data/penguins.json") |> Vl.repeat( [ row: [ "Beak Length (mm)", "Beak Depth (mm)", "Flipper Length (mm)", "Body Mass (g)" ], column: [ "Body Mass (g)", "Flipper Length (mm)", "Beak Depth (mm)", "Beak Length (mm)" ] ], Vl.new(width: 150, height: 150) |> Vl.mark(:point) |> Vl.encode_repeat(:x, :column, type: :quantitative, scale: [zero: false]) |> Vl.encode_repeat(:y, :row, type: :quantitative, scale: [zero: false]) |> Vl.encode_field(:color, "Species", type: :nominal) ) ``` ## Layering ### Layered charts with separate scales Layered charts may concern variables of different units and scales, in which case we can display the scales separately. ```elixir # Source: https://vega.github.io/vega-lite/docs/layer.html#combined-scales-and-guides Vl.new(width: 400, height: 300) |> Vl.data_from_url("https://vega.github.io/editor/data/weather.csv") |> Vl.transform(filter: "datum.location == 'Seattle'") |> Vl.encode_field(:x, "date", time_unit: :month, axis: [format: "%b", title: nil]) |> Vl.layers([ Vl.new() |> Vl.mark(:area, opacity: 0.3, color: "#85C5A6") |> Vl.encode_field(:y, "temp_max", aggregate: :average, scale: [domain: [0, 30]], title: "Avg. Temperature (°C)", axis: [title_color: "#85C5A6"] ) |> Vl.encode_field(:y2, "temp_min", aggregate: :average), Vl.new() |> Vl.mark(:line, interpolate: :monotone, stroke: "#85A9C5") |> Vl.encode_field(:y, "precipitation", aggregate: :average, title: "Precipitation (inches)", axis: [title_color: "#85A9C5"] ) ]) |> Vl.resolve(:scale, y: :independent) ``` ## Concatenation ### Arbitrary charts You can concatenate arbitrary charts, but it's most useful if they concern the same data. ```elixir # Source: https://vega.github.io/vega-lite/examples/vconcat_weather.html 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) ], :vertical ) ``` ## Maps (geographic displays) ### Projection A cartographic projection allows for mapping longitude and latitude pairs to x, y coordinates. ```elixir # Source: https://vega.github.io/vega-lite/docs/projection.html Vl.new(width: 500, height: 300) |> Vl.data_from_url("https://vega.github.io/editor/data/airports.csv") |> Vl.projection(type: :albers_usa) |> Vl.mark(:circle) |> Vl.encode_field(:longitude, "longitude", type: :quantitative) |> Vl.encode_field(:latitude, "latitude", type: :quantitative) |> Vl.encode(:size, value: 10) |> Vl.config(view: [stroke: nil]) ``` ### Choropleth map A Choropleth map is a map composed of colored polygons, used to represent spatial variations of a quantity. ```elixir # Source: https://vega.github.io/vega-lite/examples/geo_choropleth.html Vl.new(width: 500, height: 300) |> Vl.data_from_url("https://vega.github.io/editor/data/us-10m.json", format: [type: :topojson, feature: "counties"] ) |> Vl.transform( lookup: "id", from: [ data: [url: "https://vega.github.io/editor/data/unemployment.tsv"], key: "id", fields: ["rate"] ] ) |> Vl.projection(type: :albers_usa) |> Vl.mark(:geoshape) |> Vl.encode_field(:color, "rate", type: :quantitative) |> Vl.config(view: [stroke: nil]) ``` ## Interactive graphics ### Overview and detail Two charts - one for selecting the range of interest, and the other for displaying that specific range. ```elixir # Source: https://vega.github.io/vega-lite/examples/interactive_overview_detail.html Vl.new() |> Vl.data_from_url("https://vega.github.io/editor/data/sp500.csv") |> Vl.concat( [ Vl.new(width: 480) |> Vl.mark(:area) |> Vl.encode_field(:x, "date", type: :temporal, scale: [domain: [param: "brush"]], axis: [title: nil] ) |> Vl.encode_field(:y, "price", type: :quantitative), Vl.new(width: 480, height: 60) |> Vl.param("brush", select: [type: :interval, encodings: [:x]]) |> Vl.mark(:area) |> Vl.encode_field(:x, "date", type: :temporal) |> Vl.encode_field(:y, "price", type: :quantitative, axis: [tick_count: 3, grid: false]) ], :vertical ) ``` ### Scatterplot with external links and tooltips A scatterplot with each point having a tooltip and linking to some page. ```elixir # Source: https://vega.github.io/vega-lite/examples/point_href.html Vl.new(width: 400, height: 300) |> Vl.data_from_url("https://vega.github.io/editor/data/cars.json") |> Vl.transform( calculate: "'https://www.google.com/search?q=' + datum.Name", as: "url" ) |> Vl.mark(:point) |> Vl.encode_field(:x, "Horsepower", type: :quantitative) |> Vl.encode_field(:y, "Miles_per_Gallon", type: :quantitative) |> Vl.encode_field(:color, "Origin", type: :nominal) |> Vl.encode_field(:tooltip, "Name", type: :nominal) |> Vl.encode_field(:href, "url", type: :nominal) ``` ### Regular brush Highlighting points by selecting and dragging a rectangular area. ```elixir # Source: https://vega.github.io/vega-lite/examples/interactive_brush.html Vl.new(width: 400, height: 300) |> Vl.data_from_url("https://vega.github.io/editor/data/cars.json") |> Vl.param("brush", select: :interval) |> Vl.mark(:point) |> Vl.encode_field(:x, "Horsepower", type: :quantitative) |> Vl.encode_field(:y, "Miles_per_Gallon", type: :quantitative) |> Vl.encode(:color, condition: [param: "brush", field: "Cylinders", type: :ordinal], value: :gray ) ``` ### Interactive mean A brush selection parameterizing the range of data points to calculate mean over. ```elixir # Source: https://vega.github.io/vega-lite/examples/selection_layer_bar_month.html Vl.new() |> Vl.data_from_url("https://vega.github.io/editor/data/weather.csv") |> Vl.transform(filter: "datum.location == 'Seattle'") |> Vl.layers([ Vl.new() |> Vl.param("brush", select: [type: :interval, encodings: ["x"]]) |> Vl.mark(:bar) |> Vl.encode_field(:x, "date", time_unit: :month, type: :ordinal) |> Vl.encode_field(:y, "precipitation", aggregate: :mean) |> Vl.encode(:opacity, value: 0.7, condition: [param: "brush", value: 1]), Vl.new() |> Vl.transform(filter: [param: "brush"]) |> Vl.mark(:rule) |> Vl.encode_field(:y, "precipitation", aggregate: :mean) |> Vl.encode(:color, value: :firebrick) |> Vl.encode(:size, value: 3) ]) ``` ### Single point Highlighting a single point by clicking on it. ```elixir Vl.new(width: 400, height: 300) |> Vl.data_from_url("https://vega.github.io/editor/data/cars.json") |> Vl.param("pts", select: :point) |> Vl.mark(:point) |> Vl.encode_field(:x, "Horsepower", type: :quantitative) |> Vl.encode_field(:y, "Miles_per_Gallon", type: :quantitative) |> Vl.encode(:color, condition: [ param: "pts", field: "Cylinders", type: :ordinal, scale: [scheme: "yelloworangebrown"] ], value: :gray ) |> Vl.encode(:size, condition: [param: "pts", empty: false, value: 200], value: 50) ``` ### Multiple points Highlighting a group of matching points by clicking on one of them. ```elixir Vl.new(width: 400, height: 300) |> Vl.data_from_url("https://vega.github.io/editor/data/cars.json") |> Vl.param("pts", select: [type: :point, fields: ["Cylinders"]]) |> Vl.mark(:point) |> Vl.encode_field(:x, "Horsepower", type: :quantitative) |> Vl.encode_field(:y, "Miles_per_Gallon", type: :quantitative) |> Vl.encode(:color, condition: [ param: "pts", field: "Cylinders", type: :ordinal, scale: [scheme: "yelloworangebrown"] ], value: :gray ) |> Vl.encode(:size, condition: [param: "pts", empty: false, value: 200], value: 50) ``` ### Map connections An interactive visualization of connections between locations on a map. ```elixir # Source: https://vega.github.io/vega-lite/examples/airport_connections.html Vl.new(width: 800, height: 500) |> Vl.projection(type: :albers_usa) |> Vl.layers([ # Map with regions Vl.new() |> Vl.data_from_url("https://vega.github.io/editor/data/us-10m.json", format: [type: :topojson, feature: "states"] ) |> Vl.mark(:geoshape, fill: "#ddd", stroke: "#fff", stroke_width: 1), # Connection lines Vl.new() |> Vl.data_from_url("https://vega.github.io/editor/data/flights-airport.csv") |> Vl.mark(:rule, color: "#000", opacity: 0.35) |> Vl.transform(filter: [param: "org", empty: false]) |> Vl.transform( lookup: "origin", from: [ data: [url: "https://vega.github.io/editor/data/airports.csv"], key: "iata", fields: ["latitude", "longitude"] ] ) |> Vl.transform( lookup: "destination", from: [ data: [url: "https://vega.github.io/editor/data/airports.csv"], key: "iata", fields: ["latitude", "longitude"] ], as: ["latitude2", "longitude2"] ) |> Vl.encode_field(:latitude, "latitude") |> Vl.encode_field(:longitude, "longitude") |> Vl.encode_field(:latitude2, "latitude2") |> Vl.encode_field(:longitude2, "longitude2"), # Points Vl.new() |> Vl.data_from_url("https://vega.github.io/editor/data/flights-airport.csv") |> Vl.transform(aggregate: [[op: :count, as: "routes"]], groupby: ["origin"]) |> Vl.transform( lookup: "origin", from: [ data: [url: "https://vega.github.io/editor/data/airports.csv"], key: "iata", fields: ["state", "latitude", "longitude"] ] ) |> Vl.transform(filter: "datum.state !== 'PR' && datum.state !== 'VI'") |> Vl.param("org", select: [type: :point, on: :mouseover, nearest: true, fields: ["origin"]]) |> Vl.mark(:circle) |> Vl.encode_field(:latitude, "latitude") |> Vl.encode_field(:longitude, "longitude") |> Vl.encode_field(:size, "routes", type: :quantitative, scale: [max_range: 1000], legend: nil) |> Vl.encode_field(:order, "routes", sort: :descending) ]) |> Vl.config(view: [stroke: nil]) ```