Embedded interactive visualisations in Anvil using e.g. Vega Lite?

Yes, it is entirely possible to embed Vega Lite – like any Javascript library – into an Anvil app. You have to write a little bit of Javascript to glue it together, but once that’s done you can use it entirely from Python. Here’s an example app, and a step-by-step guide to how I built it.

Here’s the example app, which embeds the example you just linked to:
https://anvil.works/build#clone:EBK7KDYPKS7V4ADB=YQK2LLDCPHBOZAITZIB3BWS6

And here’s how I did it:

1. Import the Vega embedding libraries

I found the documentation on the Vega Lite website about embedding Vega Lite visualisations into a web app, and I found out what HTML snippet it wanted me to add to my app. I copied-and-pasted these into the Native Libraries section of my app:

<script src="https://cdn.jsdelivr.net/npm/vega@4.4.0"></script>
<script src="https://cdn.jsdelivr.net/npm/vega-lite@3.0.0-rc12"></script>
<script src="https://cdn.jsdelivr.net/npm/vega-embed@3.29.1"></script>

2. Initialise Vega with Javascript

The website also provided sample Javascript code for setting up a visualisation in a particular HTML element. I made a new Custom HTML form, and I put this code into a function in a <script> tag, so I could call it from Python.

Here’s the whole Custom HTML from the VegaLite form:

<div class="vis"></div>
<script>
  function initVegaLite(spec) {
    var visElt = $(this).find(".vis")[0];
    vegaEmbed(visElt, spec);
  }
</script>

3. Make a Python custom component

I set up my new form as a Custom Component, and gave it a property called vl_spec (type “object”, because it’ll want complex nested dictionaries/lists/etc). I then wrote the Python @property getter and setter to store that property, and call the initVegaLite() Javascript function when the form is shown or when the property is updated.

Here’s the full code for that form:

class VegaLite(VegaLiteTemplate):
  def __init__(self, **properties):
    self._vl_spec = {}
    self._shown = False
    
    self.init_components(**properties)

  @property
  def vl_spec(self):
    return self._vl_spec
  
  @vl_spec.setter
  def vl_spec(self, new_val):
    self._vl_spec = new_val
    if self._shown:
      self.call_js('initVegaLite', self._vl_spec)
    
  def form_show(self, **event_args):
    self._shown = True
    # Fire the setter again to call the init function
    self.vl_spec = self._vl_spec

4. Use the component

Now I’ve created a Custom Component, the VegaLite form is in my Toolbox, ready to use from my other forms. I drop one onto Form1, and then set up its vl_spec from Form1’s __init__ function:

self.vega_lite_1.vl_spec = {...}

(The example vl_spec is copied from the example @jim linked to, although I had to change the data URL from "data/movies.json" to "https://vega.github.io/vega-datasets/data/movies.json")

And there we have it – now you have a component you can use to put any Vega Lite visualisation into an Anvil app. And now we’ve built it, you don’t have to touch the Javascript again – you can do it all from Python!

3 Likes