Vue-Vega(1.0.0-alpha.13)

Vega Lite and Vega bridge to Vue.js ecosystem

  • Vega-lite support from 2.0.0
  • Sync Data
  • Control Encoding from Parent
  • Control Data from Parent
  • Vuex support by default
  • Layering
  • Faceting
  • Composition
  • Interactivity
  • +99% test coverage

Getting started


Installation

via npm

npm install vue-vega --save

via CDN

<script src="https://unpkg.com/vue-vega"></script>

Basic usage

Let's pick a trivial use case of creation dashboard with single bar chart

src/components/Dashboard.vue

<template>
 <div class='dashboard'>
  <vega-lite :data="values" mark="bar" :encoding="encoding"/>
 </div>
</template>
import Vue from 'vue'
import VueVega from 'vue-vega'

Vue.use(VueVega)

export default{
  data () {
    return {
      values: [
        {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}
      ],
      encoding: {
        x: {field: 'a', type: 'ordinal'},
        y: {field: 'b', type: 'quantitative'}
      }
    }
  }
}

Another way to use the same bar chart is to create component directly from prepared spec

src/components/Dashboard.vue

<template>
  <div class='dashboard'>
   <BarChart/>
  <div/>
</template>
import {mapVegaLiteSpec} from 'vue-vega'
import BarChartSpec from 'spec/vega-lite/bar.vl.json'

export default {
  components: {
    BarChart: mapVegaLiteSpec(BarChartSpec)
  }
}

As you may noticed, it's very natural to convert spec in component options with the help of a webpack. We are working on one for you vue-vega-webpack

Examples


Reuse Specs

One of the main features of vega it's an ability to reuse in in different environemtns.

In that case you even no need to create spec yourself but take it from some collection

import VueVega from 'vue-vega'
import BarChartSpec from 'spec/vega-lite/bar.vl.json'

export default {
  components: {
    BarChart: VueVega.mapVegaLiteSpec(BarChartSpec)
  }
}

BarChart

<BarChart></BarChart>

Data Uri

In some scenarious you even no need to fetch data manually, for example data is already stored in JSON, CSV file, provide url and vega spec would load data under the hood

import VueVega from 'vue-vega'
import BarChartSpec from 'spec/vega-lite/bar_aggregate_empty.json'

export default {
  components: {
    BarChart: VueVega.mapVegaLiteSpec(BarChartSpec)
  }
}

BarChart(
  :data="{url: 'data/population.json'}"
)

<BarChart :data="{url: 'data/population.json'}"></BarChart>

Spec as prop

Pass all spec as property

export default {
  data () {
    return {
      spec: {
        '$schema': 'https://vega.github.io/schema/vega-lite/v2.json',
        'data': {'url': 'data/cars.json'},
        'mark': 'bar',
        'encoding': {
          'x': {
            'bin': {'maxbins': 15},
            'field': 'Horsepower',
            'type': 'quantitative'
          },
          'y': {
            'aggregate': 'count',
            'type': 'quantitative'
          }
        }
      }

    }
  }
}

div
  vega-lite(
    :spec='spec'
  )

<div>
  <vega-lite :spec="spec"></vega-lite>
</div>

Update Data

Change data and it would propagate changes up tp vega view

export default{
  data () {
    return {
      values: [
        {val: 1}, {val: 5}, {val: 3},
        {val: 4}, {val: 5}, {val: 6},
        {val: 7}, {val: 8}, {val: 9}
      ],
      encoding: {
        x: {bin: true, field: 'val', type: 'quantitative'},
        y: {aggregate: 'count', type: 'quantitative'}
      }
    }
  },
  methods: {
    refreshNumbers () {
      this.values = this.values.map(() => {
        return {val: Math.random() * 10}
      })
    }
  }
}

div
  vega-lite(
    mark="bar",
    :data="values",
    :encoding="encoding"
  )
  button.button.button--medium(@click="refreshNumbers") Refresh

<div>
  <vega-lite mark="bar" :data="values" :encoding="encoding"></vega-lite>
  <button class="button button--medium" @click="refreshNumbers">Refresh</button>
</div>

Update Mark

Change mark of visualization, yep you can build some dynamic graph builder

import Multiselect from 'vue-multiselect'

export default {
  components: {
    Multiselect
  },
  data () {
    return {
      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}
      ],
      encoding: {
        x: {field: 'a', type: 'ordinal'},
        y: {field: 'b', type: 'quantitative'}
      },
      mark: 'bar',
      availableMarks: ['bar', 'point', 'circle', 'line']
    }
  }
}

div
  multiselect(
    v-model="mark",
    :options="availableMarks",
    :searchable="false",
    :close-on-select="true",
    :show-labels="false",
    placeholder="Pick a mark"
  )
  vega-lite(
    :data="data",
    :mark="mark",
    :encoding="encoding"
  )

<div>
  <multiselect v-model="mark" :options="availableMarks" :searchable="false" :close-on-select="true" :show-labels="false" placeholder="Pick a mark"></multiselect>
  <vega-lite :data="data" :mark="mark" :encoding="encoding"></vega-lite>
</div>

Update Encoding

Update encoding by adding and removing encoding channels

import Vue from 'vue'
import Multiselect from 'vue-multiselect'
import cars from './data/cars.json'

export default {
  components: {
    Multiselect
  },
  data () {
    return {
      data: cars,
      mark: 'point',
      encoding: {
        x: {field: 'Horsepower', type: 'quantitative'},
        y: {field: 'Miles_per_Gallon', type: 'quantitative'}
      },
      options: [
        {
          name: 'Origin - Color',
          channel: {
            color: {field: 'Origin', type: 'nominal'}
          }
        },
        {
          name: 'Origin - Shape',
          channel: {
            shape: {field: 'Origin', type: 'nominal'}
          }
        },
        {
          name: 'Displacement - Opacity',
          channel: {
            opacity: {field: 'Displacement', type: 'quantitative'}
          }
        }
      ]
    }
  },
  methods: {
    addEncodingChannel (newOption) {
      const channel = newOption.channel
      const channelName = Object.keys(channel).pop()
      const channelVal = Object.values(channel).pop()
      Vue.set(this.encoding, channelName, channelVal)
    },
    removeEncodingChannel (option) {
      const channel = option.channel
      const channelName = Object.keys(channel).pop()
      Vue.delete(this.encoding, channelName)
    }
  }
}

div
  multiselect(
    :options='options',
    :multiple='true',
    :close-on-select='true',
    :clear-on-select='false',
    :hide-selected='true',
    :preserve-search='false',
    placeholder='Add Channel'
    label='name',
    track-by='name',
    @select='addEncodingChannel',
    @remove='removeEncodingChannel'
  )
  vega-lite(
    :data='data',
    :mark='mark',
    :encoding='encoding'
  )

<div>
  <multiselect :options="options" :multiple="true" :close-on-select="true" :clear-on-select="false" :hide-selected="true" :preserve-search="false" placeholder="Add Channel" label="name" track-by="name" @select="addEncodingChannel" @remove="removeEncodingChannel"></multiselect>
  <vega-lite :data="data" :mark="mark" :encoding="encoding"></vega-lite>
</div>

Layering

Able to render layering with resolve

import VueVega from 'vue-vega'
import BarDualAxis from 'spec/vega-lite/layer_bar_dual_axis.vl.json'

export default {
  components: {
    BarDualAxis: VueVega.mapVegaLiteSpec(BarDualAxis)
  }
}

BarDualAxis

<BarDualAxis></BarDualAxis>

Trellis

Able to render layering with resolve

import VueVega from 'vue-vega'
import CarTrellis from 'spec/vega-lite/trellis_bar_histogram.vl.json'

export default {
  components: {
    CarTrellis: VueVega.mapVegaLiteSpec(CarTrellis)
  }
}

CarTrellis

<CarTrellis></CarTrellis>

Event Interactivity

Allow to subscribe to all native-like events and get information about selected datum.

import VueVega from 'vue-vega'
import Interactive from 'spec/vega-lite/interactive.vl.json'

export default {
  components: {
    Interactive: VueVega.mapVegaLiteSpec(Interactive)
  },
  data () {
    return {
      origin: 'N/A',
      cylinders: 0,
      records: 0
    }
  },
  methods: {
    displaySelection (event, item) {
      if (item && item.datum) {
        let datum = item.datum

        this.origin = datum.Origin
        this.cylinders = datum.Cylinders
        this.records = datum['count_*']
      }
    }
  }
}

div
  Interactive(@click="displaySelection")
  div
    span Origin : {{origin}}
  div
    span Cylinders : {{cylinders}}
  div
    span Records : {{records}}

<div>
  <Interactive @click="displaySelection"></Interactive>
  <div><span>Origin : {{origin}}</span></div>
  <div><span>Cylinders : {{cylinders}}</span></div>
  <div><span>Records : {{records}}</span></div>
</div>

Signal Interactivity

Allow to subscribe to low-level vega spec signals, not sure on what to doe with them. If you need just a data prever 'native-like' events over signals

import VueVega from 'vue-vega'
import Interactive from 'spec/vega-lite/interactive.vl.json'

export default {
  components: {
    Interactive: VueVega.mapVegaLiteSpec(Interactive)
  },
  data () {
    return {
      field: '',
      value: ''
    }
  },
  methods: {
    onPointer (tuple) {
      if (tuple && tuple.fields) {
        this.field = tuple.fields[1]
        this.value = tuple.values[1]
      }
    }
  }
}

div
  Interactive(@signal:pointer_tuple="onPointer")
  div
    span {{field}} : {{value}}

<div>
  <Interactive @signal:pointer_tuple="onPointer"></Interactive>
  <div><span>{{field}} : {{value}}</span></div>
</div>

API


There are next functions and objects present in vue-vega:

  • install - vue-vega`s main module is a Vue plugin
  • VegaLiteComponent - vue component wrapper around vega-lite
  • mapVegaLiteSpec - factory function to create VegaLiteComponent from vega-lite spec

install

Register VegaLiteComponent under 'vega-lite' name, so you can use `vega-lite` tag in your Vue templates

NameArgumentsDescription
installVueRegister VegaLiteComponent under 'vega-lite' name

mapVegaLiteSpec

Map vega lite specification object to vue component object definition

NameArgumentsDescription
mapVegaLiteSpecVegaLiteSpecReturns Vue component object definition

VegaLiteComponent

VegaLiteComponent - vue component wrapper around vega-lite. For more details refer to Vega Lit Docs

NameTypeDefaultDescription
Props
$schemaStringURL to JSON schema for a Vega-Lite specification.
descriptionStringAn optional description of this visualization for commenting purpose.
transformArrayThe top-level transform object is an array of objects describing transformations
dataArray || Object{ values: [] }Tabular data, similar to a spreadsheet or a database table. It passable to pass url but without format and names, default format json In vue-vega we provide a shortcut, so you user can pass `[]` instead of `{ values: [] }`
markString||ObjectRequired. A string describing the mark type (one of "bar", "circle", "square", "tick", "line", "area", "point", "rule", and "text") or a mark definition object.
encodingObjectAn integral part of the data visualization process is encoding data with visual properties of graphical marks.
autoResizeBooleanResize is a boolean indicating if autosize layout should be re-calculated on every update.
backgroundStringCSS color property to use as the background of visualization.
paddingNumber || ObjectThe default visualization padding, in pixels
configObjectVega-Lite configuration object.
widthNumberThe width of a visualization.
heightNumberThe height of a visualization.

Events

Events for VegaLiteComponent

NameAttributesListen toDescription
Signal(tuple)@signal:nameSubscribe to signal defined in vega spec, name is a name of signal, you can find all signals at this.vegaSpec.signals
Click(DomEvent, item)@click or @mouseover or etc.Delegate DOM events, you can use 'click', 'mouseover' etc.item.datum contains fields which usually you interested in

Created by Igor Nesterenko @NesterOne

With love from zorko.io