Skip to content

Building Chart Modules

Adam Hooper edited this page Mar 19, 2021 · 5 revisions

Workbench lets your module output a custom HTML page.

We call these "chart modules" for brevity. The pythoncode module uses the same system. A more accurate term is, "iframe modules".

Your chart becomes the module.html in this diagram:

┏━━━━ Workbench ━━━━┓
┃                   ┃
┃  ┌ module.html ┐  ┃
┃  │             │  ┃
┃  │   ▃ ▄ ▁ █   │  ┃
┃  └─────────────┘  ┃
┗━━━━━━━━━━━━━━━━━━━┛

Your module needs to provide...

html_output: true in the [id_name].yaml file

Add html_output: true to your module specification. This tells Workbench to render an iframe.

... a [id_name].html file

Supply an HTML file to render. Workbench will render an <iframe> with this URL.

And there you have it! Workbench now renders your HTML.

Of course, you'll want to supply data. So you need:

... json from your render() function

Your render() function must return a dictionary with at least dataframe and json keys. The json can be any JSON dictionary.

Most charts output the exact dataframe they use as input. They output Vega and Vega-Lite objects in their json. (Today's only exception is Pythoncode.)

... JavaScript that fetches from dataUrl query string parameter

Workbench supplies a ?dataUrl=... parameter for your HTML. It's either the empty string, or a complete URL.

If it's an empty string, that means Workbench is rendering.

Otherwise, you can pull it from JavaScript:

window.addEventListener('DOMContentLoaded', () => {
  const dataUrl = new URL(window.location).searchParams.get('dataUrl')
  if (dataUrl) {
    fetch(dataUrl, { credentials: 'same-origin' }).then(/* ... */)
  } else {
    // ...
  }
})

Workbench will respond at dataUrl with:

  • HTTP 403: This is a private workflow and you aren't permitted to access its data. (Ensure you are logged in and credentials is being used correctly.)
  • HTTP 404: The render() function did not return any json. (Check the render() function.)
  • HTTP 404: Workbench just rendered new values at a different dataUrl. (Workbench will update the iframe's src with a new dataUrl; the user can refresh the page if it doesn't.)
  • HTTP 404: The Workflow or Step was deleted.

Subscribing to dataUrl changes

When users fiddle with chart parameters in the workflow editor, the chart's dataUrl changes often. Normally, this would cause the whole iframe to reload with every change, since a new dataUrl implies a new src.

Workbench has an opt-in window.postMessage-based system to supply a new dataUrl without setting a new src. To use it, check whether Workbench supplies the origin query-string parameter:

let dataUrl = new URL(window.location).searchParams.get('dataUrl')
const messageOrigin = new URL(window.location).searchParams.get('origin')
if (messageOrigin) {
  function handleMessage (ev) {
    if (ev.source !== window.parent || ev.origin !== messageOrigin) {
      return
    }

    if (ev.data.type === 'set-data-url') {
      if (dataUrl !== ev.data.dataUrl) {
        dataUrl = ev.data.dataUrl
        startLoading()
      }
    }
  }
  window.addEventListener('message', handleMessage)

  // Tell Workbench to send us these messages instead of changing iframe `src`
  window.parent.postMessage({ type: 'subscribe-to-data-url' }, messageOrigin)
}

This feature is mainly for the workflow editor. As of 2021-03-19, the embed HTML doesn't open a Websockets connection to Workbench; so embedded charts won't refresh until the people viewing them click "Refresh". When subscription is disabled, Workbench won't supply an origin query-string parameter.

Variable-height charts in the workflow editor

By default, a chart in the workflow editor is full-height. And charts in reports and embeds always have a fixed height.

There's an exception: pythoncode uses an experimental system for setting the height of the chart, only within the workflow editor. It uses the same origin query parameter as the dataUrl-subscription system:

function notifySize () {
  if (!messageOrigin) {
    return
  }

  // To be stable, make height always factor in scrollbar width
  // Otherwise, making the scrollbar appear would change height,
  // which would make the scrollbar disappear, which would change
  // height, and we'd never be stable.
  document.body.style.overflow = 'hidden scroll'
  const height = pre.clientHeight || 0
  document.body.style.overflow = 'hidden auto'

  window.parent.postMessage({ type: 'resize', height }, messageOrigin)
}

When the workflow editor receives this message, it adjusts the height of the iframe. This can create a very-tall iframe. Workbench will supply the scrollbar such that the iframe and data table will both be visible.

As a special case, height: 0 hides the iframe altogether. It's still there, though. It can subscribe to dataUrl changes and send a new height when desired.