Interactive Data Visualization with Vega

The rectanglesAnd this is how we define the above using Vega:{ "$schema": "https://vega.



json", "width": 400, "height": 200, "padding": 5, "data": [ { "name": "our_data", "values": [ { "category": "A", "amount": 28 }, { "category": "B", "amount": 55 }, { "category": "C", "amount": 43 } ] } ], "scales": [ { "name": "xscale", "type": "band", "domain": { "data": "our_data", "field": "category" }, "range": "width", "padding": 0.

05 }, { "name": "yscale", "domain": { "data": "our_data", "field": "amount" }, "range": "height" } ], "axes": [ { "orient": "bottom", "scale": "xscale" }, { "orient": "left", "scale": "yscale" } ], "marks": [ { "type": "rect", "from": { "data": "our_data" }, "encode": { "enter": { "x": { "scale": "xscale", "field": "category" }, "width": { "scale": "xscale", "band": 1 }, "y": { "scale": "yscale", "field": "amount" }, "y2": { "scale": "yscale", "value": 0 }, "fill": { "value": "steelblue" } } } } ]}You can try it live here.

Let’s go through each of these definitions.

I’ll explain them briefly here but there are a lot more properties we can use to customize things (it’s a good idea to check the Docs when using each of them).

????."data": []We can define data directly in a specification (like we are doing using the "values" property) or load data from an external file (json or csv for example) using the "url" property.

????."scales": []Vega scales are provided by the d3-scale library.

We specify the type of scale with the "type" keyword (default is linear).

Scale domains can be specified in multiple ways:A data reference object that specifies field values in one or more data sets, like we are doing with {"data": "our_data", "field": "amount"}.

Vega computes the [min,max] array of the amount key from the datasetAs a literal array of domain valuesA signal reference that resolves to a domain value array.

For example, {"signal": "myDomain"} (don’t worry, I’ll talk about Signals later on)????."axes": []Here we need to specify the orientation and the scale that should be used to create the axis.

There are a lot of properties we can use to customize them.

✍????."marks": []We use marks to encode data using geometric primitives (rectangles, circles, lines and so on).

In this bar chart we are using the Rect mark.

They need a given position, width, and height.

We also need to specify what data should be used to build the marks (the "from" property).

"from": {"data":"our_data"}All the definitions for things like "x", "y" and "width" will come from this dataset.

Vega Types might look a little confusing at first, so let’s go through the ones we are using here:"x": {"scale": "xscale", "field": "category"}The "x" property for the rects will be set by the passing the values from "category" field to the "xscale".

"y": {"scale": "xscale", "band": 1}The "y" property for each rect will be the range band width of the band scale xscale.

"fill": {"value": "steelblue"}The "fill" color of the rects will be steelblue.

To define constant values we use the "value" property.

Vega uses the same enter, update, exit pattern that d3 uses:“The enter properties are evaluated when data is processed for the first time and a mark instance is newly added to a scene.

The update properties are evaluated for all existing (non-exiting) mark instances.

The exit properties are evaluated when the data backing a mark is removed, and so the mark is leaving the visual scene.

” — Vega docsWe use the pattern inside the "encode" property.

In this bar chart we are placing the elements when the data is processed:"encode": { "enter": { "x": {"scale": "xscale", "field": "category"}, "width": {"scale": "xscale", "band": 1}, "y": {"scale": "yscale", "field": "amount"}, "y2": {"scale": "yscale", "value": 0}, "fill": {"value": "steelblue"} }}And this was the Vega 101 tour!.To get a better sense of what Vega is capable of let’s build a timeline chart.

Building a timeline with VegaA timeline built with VegaSome Vega properties we'll use to build the timeline1 — ????."data": []Besides loading the data we can also filter, calculate new fields or derive new data streams using Vega Transforms.

We can sort the items by name using the collect transform:"data": [ { "name": "libraries", "format": { "type": "json", "parse": { "release": "date:'%Y'" } }, "values": [ { "name": "vega", "release": "2013", "license": "BSD 3-Clause", "description": "Vega is a visualization grammar, a declarative language for creating, saving, and sharing interactive visualization designs" }, { "name": "d3", "release": "2011", "license": "BSD 3-Clause", "description": "D3 (Data-Driven Documents or D3.

js) is a JavaScript library for visualizing data using web standards" }, { "name": "plotly", "release": "2012", "license": "MIT", "description": "Plotly.

js is an open source JavaScript library for creating graphs and dashboards" } ], "transform": [ { "type": "collect", "sort": { "field": "name" } } ] } ]Another great thing about Vega is that it's possible the inspect the contents of all the data we use to build the visualizations:The dataset sorted by name2—????."scales": []We'll need a time scale for the x-axis and an ordinal scale to color the rectangles:"scales": [ { "name": "xScale", "type": "time", "domain": { "data": "libraries", "field": "release" }, "range": "width", "nice": "year" }, { "name": "colorScale", "type": "ordinal", "domain": { "data": "libraries", "field": "license" }, "range": { "scheme": "dark2" } } ]3 —????."axes": []Let's place an axis in the bottom and show the year in the labels:"axes": [ { "scale": "xScale", "orient": "bottom", "format": "%Y" } ]4 — ✍????."marks": []There are three marks: the rectangles, the text inside the rectangles and the line from each rectangle to the axis.

We'll use "rect", "text" and "rule" marks to define each of them.

But first let's introduce an important Vega property: Signals.

❗️SignalsSignals are dynamic variables.

As the documentation says, the signal values are reactive: they can update in response to input event streams, external API calls, or changes to upstream signals.

Here we'll use them with initial values, but their power comes from being able to update them (we'll see how to do that another time).

"signals": [ { "name": "rectWidth", "value": 50 }, { "name": "rectHeight", "value": 40 }, { "name": "rectY", "value": 85 }, { "name": "rectCenter", "init": "[rectWidth/2,rectY+rectHeight/2]" } ]Now that we have the signals we can use them to place the marks.

Signals can also hold Vega expressions.

A very used one is the scale:scale(name, value[, group])Applies the named scale transform (or projection) to the specified value.

The optional group argument takes a scenegraph group mark item to indicate the specific scope in which to look up the scale or projection.

In this example we'll use an expression to place the rectangles in the middle of each year with this expression:"signal": "scale('xScale',datum.

release)-rectWidth/2" //scale(name, value[,group]As we saw earlier, we need to specify what data should be used to build the marks with the "from" property.

Vega is so great that we can specify the data from another mark itself!.In this case, we'll use the data from the rect marks so we can get the center for each rectangle and place the text in the middle.

To access the data points we use "datum" inside the expression.

"marks": [ { "type": "rect", "name": "rectangles", "from": { "data": "libraries" }, "encode": { "enter": { "width": { "signal": "rectWidth" }, "height": { "signal": "rectHeight" }, "x": { "signal": "scale('xScale',datum.

release)-rectWidth/2" }, "y": { "signal": "rectY" }, "fill": { "signal": "scale('colorScale', datum.

license)" }, "tooltip": { "signal": "{'Description': datum.

description}" } }, "update": { "fillOpacity": { "value": 1 } }, "hover": { "fillOpacity": { "value": 0.

5 } } } }, { "type": "text", "name": "labels", "from": { "data": "rectangles" // ⬅️cool }, "encode": { "enter": { "text": { "signal": "datum.


name" }, "x": { "signal": "datum.

x+rectCenter[0]" //datum.

x is from rect }, "y": { "signal": "rectCenter[1]" }, "align": { "value": "center" }, "baseline": { "value": "middle" }, "fontWeight": { "value": "bold" }, "fill": { "value": "black" } } }, "interactive": false }, { "type": "rule", "from": { "data": "labels" // ⬅️cool }, "encode": { "enter": { "x": { "signal": "datum.

x" }, "x2": { "signal": "datum.

x" }, "y": { "signal": "datum.

y+rectCenter[0]-5" }, "y2": { "signal": "height" }, "strokeWidth": { "value": 2 } } } } ]5 — ????."legends": []Legends definitions are similar to mark definitions.

To customize is the addressable elements are:legend for the legend group mark,title for the title text mark,labels for label text marks,symbols for legend symbol marks,entries for symbol legend group marks containing a symbol / label pair, andgradient for a gradient rect marks: one rect with gradient fill for continuous gradient legends, multiple rect marks with solid fill for discrete gradient legends.

Here we'll only set the "x" position for the legend (the whole group) and set the fontSize for the title and the labels.

"legends": [ { "title": "License", "fill": "colorScale", "orient": "none", "encode": { "title": { "update": { "fontSize": { "value": 15 } } }, "labels": { "update": { "fontSize": { "value": 12 } } }, "legend": { "update": { "x": { "value": 500 } } } } } ]6 — ⚙️ "config" and "title": []The config object defines default visual values to set a visualization’s theme.

Here we are setting the typefaces for the text of the graph.

The title directive adds a descriptive title to a chart.

"config": { "text": { "font": "Ideal Sans, Avenir Next, Helvetica" }, "title": { "font": "Ideal Sans, Avenir Next, Helvetica", "fontWeight": 500, "fontSize": 17, "limit": -1 }, "axis": { "labelFont": "Ideal Sans, Avenir Next, Helvetica", "labelFontSize": 12 } },"title": { "text": "Data visualization tools release dates", "orient": "top", "anchor": "start", "frame": "group", "encode": { "update": { "dx": { "value": -1 } } } }And we are done!. More details

Leave a Reply