All Posts

Mixing Gatsby, MDX, ObservableHQ, and p5js

This post offers a peek at the technology behind this website. In the spirit of remixing, I’m hoping this provokes ideas for using ObservableHQ in unexpected ways.

Putting Observable Notebooks in Markdown

I recently learned of Chris Biscardi’s gatsby-mdx library, which enables the usage of React components inside Markdown (.md) files. With this library, we are not limited to using vanilla HTML and CSS to write my content.

I built this blog with Gatsby.js instead of a static site generator powered by a server-side language because I wanted to have flexibility to include interactive capabilities. I previously thought I would have to write entirely new React pages to do so, so I was excited to add gatsby-mdx to my code. However, I lacked a specific React-powered use case. Today, one appeared!

Last year, I published several data visualization tutorials on the ObservableHQ platform. However, I hadn’t figured out how to host that content on my own site without resorting to using iframes. While that approach technically works, I wouldn’t have control over how the imported content was styled. There had to be a better way.

Today, I saw Chris Knox’s post about building a bar-chart race graph in Observable. He was able to import his work into a news article for the New Zealand Herald using npm directly. I decided to put his approach into a React component, and try it on one of my notebooks. As a guinea pig, I selected a walkthrough I’d made for a Coding Train video about using p5.js to recreate one of Dave’s (bees and bombs) animations.

This is what the React wrapper component for the notebook looks like:

// breathing-cube-observablehq.jsx
import React, { Component } from 'react';
import { Runtime, Inspector } from '@observablehq/runtime';

// Right click the "export to code" link in the notebook's sharing options
// yarn install
import notebook from 'breathing-cube-in-p5-js';

const mountId = 'observable-mount';

class ImportedNotebook extends Component {
  componentDidMount() {
    const root = document.getElementById(
    // Via

  render() {
    return (<div id={mountId} style={{ textAlign: 'center' }}> </div>);

export default ImportedNotebook;

Then, I included the following in my .mdx file for this post.

import ImportedNotebook from '../../src/components/breathing-cube-observable';

# ... the markdown part of my blog post


# ... conclusion to blog post

That’s it! With just a few lines, the full catalog of ObservableHQ cells and functions are available for usage inside any markdown/MDX files. Note that it’ not just the visual output cells that work - the cells with “knobs” for toggling colors and bar width are all functional.

Aside: The mdx syntax highlighting for this code block works with the help of the recently released gatsby-remark-vscode.

Library Summary

To recap what each acronym is contributing to this post

  1. gatsby converts a folder of JSX files into a JS and HTML blog suitable for hosting as a static site.
  2. JSX is the templating language used by React.js, which lets you mix Javascript and HTML.
  3. Markdown (MD) converts a human readable markup language to HTML. Markdown works inside many tools used by developers, including Github, JIRA, and Slack.
  4. MDX is a superset of Markdown, which accepts React.js components in addition to standard HTML.
  5. ObservableHQ is an online Javascript notebook for running and sharing reactive code snippets online
  6. p5.js is a javascript library for creative visual coding
  7. d3.js can do all sorts of things for data visualization, but here it is just doing a little bit of math.

Everything between the horizontal lines is from the notebook.


There are at 2 least pieces of this import that that did not go as smoothly as I had hoped. I think these are reasons to view this content on ObservableHQ instead of using the import method.

  1. Viewing and editing the source code for each Observable cell isn’t supported.
  2. Some of the CSS for my blog conflicts with the notebook styling, some of the cells are formatted strangely.

Both of these issues can be overcome by writing a React.js wrapper that is more selective about which cells to import, instead of importing every single cell. In future posts, I will curate the imported cells more carefully. I’m leaving this post as-is to show how a variety of cell types perform when imported without special treatment.

Let me know if you end up trying this workflow!