Observable Framework 1.7.0 GitHub️ 1.8k

JavaScript

Use JavaScript to render charts, inputs, and other dynamic, interactive, and graphical content on the client. JavaScript in Markdown can be expressed either as fenced code blocks or inline expressions. You can also import JavaScript modules to share code across pages.

JavaScript runs on load, and re-runs reactively when variables change.

Fenced code blocks

JavaScript fenced code blocks (```js) are typically used to display content such as charts and inputs. They can also be used to declare top-level variables, say to load data or declare helper functions.

JavaScript blocks come in two forms: expression blocks and program blocks. An expression block looks like this (and note the lack of semicolon):

```js
1 + 2
```

Expression blocks implicitly display, producing:

A program block looks like this (note the semicolon):

const foo = 1 + 2;

A program block doesn’t display anything by default, but you can call display to display something.

JavaScript blocks do not show their code by default. If you want to show the code, use the echo directive:

```js echo
1 + 2
```

The code is rendered below the output, like so:

1 + 2

Alternatively, if you just want to show the code without running it, set the run directive to false:

```js run=false
1 + 2
```

If an expression evaluates to a DOM node, the node is inserted into the page directly above the code block. Use this to create dynamic content such as charts and tables.

document.createTextNode("[insert chart here]") // some imagination required

You can use the DOM API to create content as above, but typically you’ll use a helper library such as Hypertext Literal, Observable Plot, or D3 to create content. For example, here’s a component that displays a greeting:

function greeting(name) {
  return html`Hello, <i>${name}</i>!`;
}
greeting("world")

And here’s a line chart of Apple’s stock price using Observable Plot:

Plot.lineY(aapl, {x: "Date", y: "Close"}).plot({y: {grid: true}})

Code blocks automatically re-run when referenced reactive variables change, or when you edit the page during preview. The block below references the built-in variable now representing the current time in milliseconds; because now is reactive, this block runs sixty times a second and each each new span it returns replaces the one previously displayed.

html`<span style=${{color: `hsl(${(now / 10) % 360} 100% 50%)`}}>Rainbow text!</span>`

Inline expressions

Inline expressions ${…} interpolate values into Markdown. They are typically used to display numbers such as metrics, or to arrange visual elements such as charts into rich HTML layouts.

For example, this paragraph simulates rolling a 20-sided dice:

You rolled ${Math.floor(Math.random() * 20) + 1}.

You rolled . (Reload the page to re-roll.)

Like fenced code blocks, inline expressions automatically re-run when referenced reactive variables change or when you edit the page during preview.

The current time is .

The current time is ${new Date(now).toLocaleTimeString("en-US")}.

Likewise, if an inline expression evaluates to a DOM element or node, it will be inserted into the page. For example, you can…

interpolate a sparkline

interpolate a sparkline ${Plot.plot({axis: null, margin: 0, width: 80, height: 17, x: {type: "band", round: false}, marks: [Plot.rectY(aapl.slice(-15 - number, -1 - number), {x: "Date", y1: 150, y2: "Close", fill: "var(--theme-foreground-focus)"})]})}

or even a reactive input

or even a reactive input ${Inputs.bind(html`<input type=range style="width: 120px;">`, numberInput)} ${number}

into prose.

const numberInput = Inputs.input(0);
const number = Generators.input(numberInput);

Expressions cannot declare top-level reactive variables. To declare a variable, use a code block instead. You can declare a variable in a code block (without displaying it) and then display it somewhere else using an inline expression.

Explicit display

The built-in display function displays the specified value.

const x = Math.random();

display(x);

You can display structured objects, too. Click on the object below to inspect it.

display({hello: {subject: "world"}, numbers: [1, 2, 3, 4]})

Calling display multiple times will display multiple values. Values are displayed in the order they are received. Previously-displayed values will be cleared when the associated code block or inline expression is invalidated.

for (let i = 0; i < 5; ++i) {
  display(i);
}

If you pass display a DOM node, it will be inserted directly into the page. Use this technique to render dynamic displays of data, such as charts and tables.

display(html`Your lucky number is ${Math.floor(Math.random () * 10)}!`);

This is a contrived example — you normally use an inline expression to interpolate a value into Markdown. For example:

Your lucky number is ${Math.floor(Math.random () * 10)}!

The display function returns the passed-in value. You can display any value (any expression) in code, not only top-level variables; use this as an alternative to console.log to debug your code.

const y = display(Math.random());

The value of y is .

The value of `y` is ${y}.

When the value passed to display is not a DOM element or node, the behavior of display depends on whether it is called within a fenced code block or an inline expression. In fenced code blocks, display will use the inspector.

display([1, 2, 3]);

In inline expressions, display will coerce non-DOM values to strings and concatenate iterables.

${display([1, 2, 3])}

The display function is scoped to each code block, meaning that the display function is a closure bound to where it will display on the page. But you can capture a code block’s display function by assigning it to a top-level variable:

const displayThere = display;

Then you can reference it from other cells:

Inputs.button("Click me", {value: 0, reduce: (i) => displayThere(++i)})

Implicit display

JavaScript expression fenced code blocks are implicitly wrapped with a call to display. For example, this arithmetic expression displays implicitly:

1 + 2 // implicit display

Implicit display only applies to expression code blocks, not program code blocks: the value won’t implicitly display if you add a semicolon. (Watch out for Prettier!)

1 + 2; // no implicit display

Implicit display also doesn’t apply if you reference the display function explicitly (i.e., we wouldn’t want to show 2 twice below).

display(1), display(2) // no implicit display

The same is true for inline expressions ${…}.

${1 + 2}

${display(1), display(2)}

Implicit display also implicitly awaits promises.

Responsive display

In Markdown, the built-in width reactive variable represents the current width of the main element. This variable is implemented by Generators.width and backed by a ResizeObserver. The reactive width can be a handy thing to pass, say, as the width option to Observable Plot.

The current width is ${width}.

For more control, or in a grid where you want to respond to either width or height changing, use the built-in resize helper. This takes a render function which is called whenever the width or height changes, and the element returned by the render function is inserted into the DOM.

<div class="grid grid-cols-4">
  <div class="card">
    ${resize((width) => `This card is ${width}px wide.`)}
  </div>
</div>

If your container defines a height, such as 240px in the example below, then you can use both the width and height arguments to the render function:

<div class="grid grid-cols-2" style="grid-auto-rows: 240px;">
  <div class="card" style="padding: 0;">
    ${resize((width, height) => Plot.barY([9, 4, 8, 1, 11, 3, 4, 2, 7, 5]).plot({width, height}))}
  </div>
  <div class="card" style="padding: 0;">
    ${resize((width, height) => Plot.barY([3, 4, 2, 7, 5, 9, 4, 8, 1, 11]).plot({width, height}))}
  </div>
</div>
If you are using resize with both width and height and see nothing rendered, it may be because your parent container does not have its own height specified. When both arguments are used, the rendered element is implicitly position: absolute to avoid affecting the size of its parent and causing a feedback loop.