Extending Canvas with Plugins, Part 3

Before reading this guide, you should have worked through part 1 and part 2 first. After that, you should have a couple of custom functions and a custom render function. Together they allow you to query the Github API for tag data on a repo and render the results on a timeline.

In this part, we'll hook those up to the Canvas UI so users no longer need to drop into the expression editor to access them. To do this, we'll create a timeline element, and a timeline expression type. The element will add a control to the Elements toolbar panel, and the expression type add a control to the sidebar.

Let's start with the element. Elements simply provide a name and some additional information for a pre-packaged expression, they don't actually do anything; the render functions do all the work. This definition is read from the elements registry and used to provide a control for users to add the element to their workpad. Create a new path and file at public/elements/timeline.js and add this to it:

export const timeline = () => ({
  name: 'timeline',
  displayName: 'Timeline',
  help: 'A timeline vis, for showing events over a period of time',
  expression: 'github-tags | timeline',
});

That's our element, nice and simple, but like everything else, it needs to be registered. Create a new file, which will be used to collect all elements and register them. Create public/elements/index.js and place the following in it:

import { timeline } from './timeline';

export const elements = [timeline];

Then import and load that collection in public/lib/load_plugins.js by uncommenting the elementsRegistry line and adding the following:

import { elementsRegistry } from 'plugins/canvas/lib/elements_registry';
import { elements } from '../elements';

elements.forEach(elDef => elementsRegistry.register(elDef));

Now the interface will allow users to quickly add the github timeline to their workpad. You should see the timeline in the Elements section of the toolbar, and clicking on it should result in the timeline view you saw in part 2.


Next, let's create an expression type that will allow users to set a font from the sidebar. This is going to require a couple new files again, since the boilerplate also doesn't include any expression types. Create plugins/expression_types/timeline.js and add the following:

export const timeline = () => ({
  name: 'timeline',
  displayName: 'Timeline',
  help: 'Show your time series data on a timeline',
  modelArgs: [],
  args: [
    {
      name: 'font',
      displayName: 'Text settings',
      help: 'Fonts, alignment and color',
      argType: 'font',
    },
  ],
});

Before we dive into that, let's step back a moment; what are "expression types" in Canvas, and why does this look so much like the function definition? The simple answer is that Canvas keeps functionality and the interface separated. You already saw that the you could use the github-tags and timeline functions, and you may have noticed that when you did, you got no controls for them in the interface. Expression types allow you to define arguments as their UI controls, again, independent of the function arguments.

Here, we've told the interface about the font argument in the timeline function, so when you use that function in the expression, the interface knows that the argument exists and how to render an input for it in the sidebar. The properties work as follows:

Now that there's a new expression type, it needs to be registered like the element. Similar to before, create a new index file, public/expression_types/index.js, and put the following in it:

import { timeline } from './timeline';

export const expressionTypes = [timeline];

Then use that collection to register it in public/lib/load_plugin.js. This is done by importing the viewRegistry from Canvas and registering the new expression type in it. Add the following to the index file public/expression_types/index.js:

import { viewRegistry } from 'plugins/canvas/expression_types';
import { expressionTypes } from '../expression_types';

expressionTypes.forEach(expDef => viewRegistry.register(expDef));

With both of these parts done, starting up Canvas with the plugin installed, users can add a timeline from the Element section of the toolbar, and when select that element in the workpad, they'll see a control in the sidebar to change the font used in the timeline.

This concludes the series on extending Canvas with plugins. You now have a plugin with a couple of common functions, a render function, an element definition, and some sidebar controls. With this knowledge, and an understanding of how functions pipe values to new functions, you should be able to do pretty much anything you want in canvas that isn't alraedy provided to you by writing a little bit of code. Datasource adapters for APIs or other databases, elements that use custom datavis libraries, whatever you can think of. Have fun!