Building Vega with Vincent

The Vincent API attempts to map 1:1 to Vega grammar through a set of object relational classes. You can build complex Vega grammar directly with Vincent via simple getters and setters.

Grammar

Here is an example of a simple set of marks for a bar chart in Vega JSON.

    {
  "type": "rect",
  "from": {"data": "table"},
  "properties": {
    "enter": {
      "x": {"scale": "x", "field": "data.idx"},
      "width": {"scale": "x", "band": true, "offset": -1},
      "y": {"scale": "y", "field": "data.val"},
      "y2": {"scale": "y", "value": 0}
    },
    "update": {
      "fill": {"value": "steelblue"}
    },
    "hover": {
      "fill": {"value": "red"}
    }
  }
}

Here’s the same thing being built with Vincent’s API:

from vincent import *

enter_props = PropertySet(x=ValueRef(scale='x', field="data.idx"),
                          y=ValueRef(scale='y', field="data.val"),
                          width=ValueRef(scale='x', band=True, offset=-1),
                          y2=ValueRef(scale='y', value=0))

update_props = PropertySet(fill=ValueRef(value='steelblue'))

mark = Mark(type='rect', from_=MarkRef(data='table'),
            properties=MarkProperties(enter=enter_props,update=update_props))

If you wanted to transform this into a line chart, Vincent makes spec changes simple. Let’s change the fill color. Assuming that your Vincent object is named vis:

vis.marks[0].properties.update.fill.value = 'red'

If you want to check on the grammar, you can call grammar() to return a Python data structure representation of the Vega grammar at almost any level of nesting depth:

>>>vis.marks[0].properties.grammar()
{u'enter': {u'width': {u'band': True, u'offset': -1, u'scale': u'x'},
 u'x': {u'field': u'data.idx', u'scale': u'x'},
 u'y': {u'field': u'data.val', u'scale': u'y'},
 u'y2': {u'scale': u'y', u'value': 0}},
u'update': {u'fill': {u'value': u'steelblue'}}}

Vincent also performs type-checking on grammar elements to try and avoid grammar errors:

>>>vis.marks[0].properties.enter.y2.scale = 1

ValueError: scale must be str

The best way to get an idea of how to build Vega grammar with Vincent is to see the examples in the Github Repo . Building a bar chart from scratch using the Vincent API looks as follows:

from vincent import *

vis = Visualization(width=500, height=300)
vis.scales['x'] = Scale(name='x', type='ordinal', range='width',
                        domain=DataRef(data='table', field="data.idx"))
vis.scales['y'] = Scale(name='y', range='height', nice=True,
                        domain=DataRef(data='table', field="data.val"))
vis.axes.extend([Axis(type='x', scale='x'),
                 Axis(type='y', scale='y')])

#Marks
enter_props = PropertySet(x=ValueRef(scale='x', field="data.idx"),
                          y=ValueRef(scale='y', field="data.val"),
                          width=ValueRef(scale='x', band=True, offset=-1),
                          y2=ValueRef(scale='y', value=0))

update_props = PropertySet(fill=ValueRef(value='steelblue'))

mark = Mark(type='rect', from_=MarkRef(data='table'),
            properties=MarkProperties(enter=enter_props,
                                      update=update_props))
vis.marks.append(mark)

data = Data.from_iter([10, 20, 30, 40, 50])
#Using a Vincent KeyedList here
vis.data['table'] = data

You’ll notice two interesting pieces here: Data , and the KeyedList

Keyed List

The Vega specification consists of high level attributes such as scales, marks, axes, legends, etc, each with an array containing any number of individual marks, scales, etc. It’s useful to be able to index these arrays by name, so Vincent has introduced a Python List that can also be indexed by a name parameter.

So, for example, if we want to introduce a new color scale:

scale = vincent.Scale(name='color', type='ordinal',
                      domain=DataRef(data='table', field='data.col'),
                      range='category20')

We can add it to the scales list and index it by name:

vis.scales['color'] = scale

Note that the name must match the index you inserting:

vis.scales['newscale'] = scale

ValidationError: key must be equal to 'name' attribute

Data Importing

The Vincent Data class has a number of conveniences for import Python data types.

First, from_iter, which will take lists, tuples, or key/value dicts:

list_dat = [10, 20, 30, 40, 50]
data = vincent.Data.from_iter(list_dat)

data.values
[{'col': 'data', 'idx': 0, 'val': 10},
 {'col': 'data', 'idx': 1, 'val': 20},
 {'col': 'data', 'idx': 2, 'val': 30},
 {'col': 'data', 'idx': 3, 'val': 40},
 {'col': 'data', 'idx': 4, 'val': 50}]

dict_dat = {'x': 10, 'y': 20, 'z': 30}
data = vincent.Data.from_iter(dict_dat)

data.values
[{'col': 'data', 'idx': 'y', 'val': 20},
 {'col': 'data', 'idx': 'x', 'val': 10},
 {'col': 'data', 'idx': 'z', 'val': 30}]

There’s also a from_mult_iters convenience method, in which you must provide a common index:

x = [0, 1, 2, 3, 4]
y = [10, 20, 30, 40, 50]
z = [70, 80, 90, 100, 110]
data = vincent.Data.from_mult_iters(index=x, values1=y, values2=z, idx='index')

data.values
[{'col': 'values1', 'idx': 0, 'val': 10},
 {'col': 'values1', 'idx': 1, 'val': 20},
 {'col': 'values1', 'idx': 2, 'val': 30},
 {'col': 'values1', 'idx': 3, 'val': 40},
 {'col': 'values1', 'idx': 4, 'val': 50},
 {'col': 'values2', 'idx': 0, 'val': 70},
 {'col': 'values2', 'idx': 1, 'val': 80},
 {'col': 'values2', 'idx': 2, 'val': 90},
 {'col': 'values2', 'idx': 3, 'val': 100},
 {'col': 'values2', 'idx': 4, 'val': 110}]

This indexing structure allows for faceting on col or idx for charts like stacked or grouped bars.

The best way to get data into Vincent is with the Pandas Series and DataFrame. These provide built-in indexing and index sorting, and will generally make your charts appear nicer. We’ll start with Series:

series = pd.Series([10, 20, 30, 40, 50])
data = vincent.Data.from_pandas(series)

data.values
[{'col': 'data', 'idx': 0, 'val': 10},
 {'col': 'data', 'idx': 1, 'val': 20},
 {'col': 'data', 'idx': 2, 'val': 30},
 {'col': 'data', 'idx': 3, 'val': 40},
 {'col': 'data', 'idx': 4, 'val': 50}]

If the series has a name, this will be your col value:

series.name = 'metric'
data = vincent.Data.from_pandas(series)

data.values
[{'col': 'metric', 'idx': 0, 'val': 10},
 {'col': 'metric', 'idx': 1, 'val': 20},
 {'col': 'metric', 'idx': 2, 'val': 30},
 {'col': 'metric', 'idx': 3, 'val': 40},
 {'col': 'metric', 'idx': 4, 'val': 50}]

DataFrames are just as simple:

farm_1 = {'apples': 10, 'berries': 32, 'squash': 21}
farm_2 = {'apples': 15, 'berries': 43, 'squash': 17}
farm_3 = {'apples': 6, 'berries': 24, 'squash': 22}

farm_data = [farm_1, farm_2, farm_3]
farm_index = ['Farm 1', 'Farm 2', 'Farm 3']
df = pd.DataFrame(farm_data, index=farm_index)
data = vincent.Data.from_pandas(df)

data.values
[{'col': 'apples', 'idx': 'Farm 1', 'val': 10},
 {'col': 'berries', 'idx': 'Farm 1', 'val': 32},
 {'col': 'squash', 'idx': 'Farm 1', 'val': 21},
 {'col': 'apples', 'idx': 'Farm 2', 'val': 15},
 {'col': 'berries', 'idx': 'Farm 2', 'val': 43},
 {'col': 'squash', 'idx': 'Farm 2', 'val': 17},
 {'col': 'apples', 'idx': 'Farm 3', 'val': 6},
 {'col': 'berries', 'idx': 'Farm 3', 'val': 24},
 {'col': 'squash', 'idx': 'Farm 3', 'val': 22}]

You can also key on a column, rather than the index:

data = vincent.Data.from_pandas(df, key_on='apples')

data.values
[{'col': 'apples', 'idx': 10, 'val': 10},
 {'col': 'berries', 'idx': 10, 'val': 32},
 {'col': 'squash', 'idx': 10, 'val': 21},
 {'col': 'apples', 'idx': 15, 'val': 15},
 {'col': 'berries', 'idx': 15, 'val': 43},
 {'col': 'squash', 'idx': 15, 'val': 17},
 {'col': 'apples', 'idx': 6, 'val': 6},
 {'col': 'berries', 'idx': 6, 'val': 24},
 {'col': 'squash', 'idx': 6, 'val': 22}]

Finally, if you turn on grouped, it will add an additional iterative key for Vega grouping that groups on the column values:

data = vincent.Data.from_pandas(df, grouped=True)]

data.values
[{'col': 'apples', 'group': 0, 'idx': 'Farm 1', 'val': 10},
 {'col': 'berries', 'group': 1, 'idx': 'Farm 1', 'val': 32},
 {'col': 'squash', 'group': 2, 'idx': 'Farm 1', 'val': 21},
 {'col': 'apples', 'group': 0, 'idx': 'Farm 2', 'val': 15},
 {'col': 'berries', 'group': 1, 'idx': 'Farm 2', 'val': 43},
 {'col': 'squash', 'group': 2, 'idx': 'Farm 2', 'val': 17},
 {'col': 'apples', 'group': 0, 'idx': 'Farm 3', 'val': 6},
 {'col': 'berries', 'group': 1, 'idx': 'Farm 3', 'val': 24},
 {'col': 'squash', 'group': 2, 'idx': 'Farm 3', 'val': 22}]