Skip to content

Scenes

Same chart, different states — composed into a story that a reader can step through.

Why this matters

Journalism rarely sits still on a single chart. A finding has a build-up, a turn, and a punchline. In Blueprint Chart, a scene is a named visualisation state — the same chart with different data, highlighting, annotations, or styling — and a sequence of scenes is the chart's story. You write scenes in the same .bpc document, and the runtime gives the reader Previous / Next controls (or your own UI) to walk through them.

Quickstart

A bar chart with four narrative beats — the first three re-spotlight a different country, and the last reframes the same story per capita:

bpc
chart bar-horizontal {
  title = "China emits more CO₂ than the US and India combined"
  description = "Annual emissions in billion tonnes, 2023"
  source = "Our World in Data"
  sourceUrl = "https://ourworldindata.org/co2-emissions"
  byline = "Pierre Romera"

  data {
    "China" = 12.17
    "United States" = 4.92
    "India" = 3.06
    "Russia" = 1.73
    "Japan" = 0.99
    "Germany" = 0.59
  }

  scene "China spotlight" {
    title = "China alone emits more than the US and India together"
    highlight "China"
  }

  scene "India rising" {
    title = "India passed the EU to become the third-largest emitter"
    highlight "India"
  }

  scene "Japan declining" {
    title = "Japan's emissions have fallen since their 2013 peak"
    highlight "Japan"
  }

  scene "Per capita reframe" {
    title = "Per person, Gulf states dwarf the biggest emitters"
    description = "CO₂ emissions per capita, tonnes per person, 2023"
    highlight "Qatar"
    data {
      "Qatar" = 40.13
      "United Arab Emirates" = 19.77
      "United States" = 14.32
      "China" = 8.56
      "India" = 2.13
    }
  }
}

From the sample library

This is packages/lib/src/samples/co2-emissions-story.bpc verbatim. The first three scenes keep the same base data and only shift the title and highlight; the fourth swaps in per-capita data for a closing reframe.

Open this in the editor to see the scene timeline appear automatically; embed it on a page and readers get a Previous / Next nav.

How it works

A scene block accepts the same member set as the top-level chart. At parse time, each scene becomes a SceneNode in the AST. The DSL converter merges scene members on top of the base chart to produce the effective ChartData and ChartOptions for that scene.

At render time:

  1. The runtime collects the chart's scenes into a SceneDefinition[].
  2. createSceneController(container, scenes, onSceneChange) injects a small <nav> (Previous / Next / counter) into the container.
  3. On every scene change, the callback re-renders the chart with transition = true, which triggers the motion helpers (snapshotForFadeOut, commitFadeOut, fadeIn) and animates the crossfade.
  4. goTo(index) clamps and wraps so goTo(-1) cycles to the last scene.

The pipeline that runs per scene is the same eleven-step sequence documented in Embedding and the DSL spec — scene overrides are merged at step 3 (transforms), so everything downstream sees the post-scene state.

Recipes

Highlight a different point per scene

The short-form highlight "<name>" is the workhorse. Drop one per scene and the rest of the chart greys out:

bpc
scene "China spotlight" {
  title = "China alone emits more than the US and India together"

  highlight "China"
}

scene "India rising" {
  title = "India passed the EU to become the third-largest emitter"

  highlight "India"
}

From the sample library

Two adjacent scenes from packages/lib/src/samples/co2-emissions-story.bpc. Each scene retitles the chart and shifts the spotlight without touching the base data.

Replace data wholesale in a scene

Any scene can carry its own data block, which replaces the base data for that scene's render. The Bulgaria scene from farm-compass drops the EU-wide aggregate in favour of a country-specific time series, and keeps the same area-stacked chart type:

bpc
scene "Bulgaria: subsidies explode" {
  title = "Bulgarian farmers received almost nothing before 2007 accession"
  description = "More than three-quarters of Bulgarian subsidies are direct payments — the highest share among new members"

  data {
    series = "Indirect subsidies","Direct subsidies"
    "2000" = 0,5
    "2004" = 0,67
    "2007" = 59,250
    "2010" = 79,466
    "2013" = 132,852
    "2015" = 213,677
  }

  highlight "Direct subsidies"
}

From the sample library

Trimmed scene from packages/lib/src/samples/farm-compass.bpc (full sample has the 2000–2015 yearly series). The scene swaps data and keeps the parent chart's area-stacked type.

Switch chart type mid-story

A scene can override the chart type with type =. The same farm-compass story leaves the parent area-stacked chart and pivots to a line for the "farms grew" beat, then back to an area-stacked later on:

bpc
scene "Bulgarian farms grew" {
  title = "Average farm size in Bulgaria quadrupled"
  description = "Average farm size in hectares"
  type = line

  data {
    "2005" = 5
    "2007" = 6
    "2010" = 12
    "2013" = 18
  }
}

From the sample library

Scene #4 of packages/lib/src/samples/farm-compass.bpc. The story changes chart type four times across its nine scenes — area-stackedlineareaarea-stackedline-multi — all from one document.

Control how long an annotation stays

By default an annotation shows only in the scene where it is declared. Use repeat to carry it into later scenes:

bpc
scene "Crash" {
  range {
    start = 2008
    end = 2009
    text = "Financial crisis"
    repeat = 1         // this scene plus the next one (2 scenes total)
  }
}

scene "Recovery" {
  annotation "2015" {
    text = "Paris Agreement"
    repeat = true      // this scene and every scene after it
  }
}

repeat accepts three forms:

  • false or omitted (the default): show only in this scene.
  • a positive integer N: show in this scene plus the next N scenes, so repeat = 1 spans 2 scenes and repeat = 3 spans 4.
  • true: show in this scene and every scene after it.

A top-level annotation (declared on the chart itself, outside any scene) anchors at the base chart frame, the first thing a reader sees. With no repeat it appears only there; repeat carries it forward into the scenes that follow.

Drive playback from your own UI

The controller is decoupled from the nav DOM it inserts — you can ignore the built-in buttons and call next(), previous(), or goTo(index) from any custom UI:

ts
import { createSceneController } from '@blueprint-chart/lib/dist/runtime'

const controller = createSceneController(container, scenes, (scene, index) => {
  renderChart(canvas, scene.data, true)
})

document.querySelector('#my-next-button')!.addEventListener('click', () => {
  controller.next()
})

// Programmatic jump:
controller.goTo(2)

Call controller.destroy() to remove the injected nav when tearing the chart down.

API surface

Exported from @blueprint-chart/lib/dist/runtime:

SymbolOne-liner
createSceneController(container, scenes, onSceneChange)Build a scene controller, inject Previous / Next nav, call back on every scene change.
SceneDefinition (type){ name: string, data?: Record<string, unknown> } — one scene's payload.
SceneController (type)Returned object with currentScene, totalScenes, next(), previous(), goTo(index), destroy().
createStepController / StepDefinition / StepControllerDeprecated aliases retained for backward compatibility — new code should use the Scene* names.

Motion helpers used internally during scene transitions (all exported from @blueprint-chart/lib):

SymbolOne-liner
getTransitionDuration()Canonical fade duration shared with the editor's UI transitions.
snapshotForFadeOut(container)Capture the outgoing DOM for fade-out.
commitFadeOut(snapshot)Animate the snapshot out.
fadeIn(container)Animate the new render in.
getCachedChart(container)Last render, for diff-based interpolation.

DSL converter helpers (also exported from @blueprint-chart/lib):

SymbolOne-liner
extractSceneOverrides(ast)Pull each scene's merged ChartData / ChartOptions from a parsed AST.
SceneNode (type)AST node for a scene block.

See the full list in the API reference.

See also

Released under the MIT License. Built static-first — your data never leaves the page.