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:
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:
- The runtime collects the chart's scenes into a
SceneDefinition[]. createSceneController(container, scenes, onSceneChange)injects a small<nav>(Previous / Next / counter) into the container.- 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. goTo(index)clamps and wraps sogoTo(-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:
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:
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:
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-stacked → line → area → area-stacked → line-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:
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:
falseor omitted (the default): show only in this scene.- a positive integer
N: show in this scene plus the nextNscenes, sorepeat = 1spans 2 scenes andrepeat = 3spans 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:
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:
| Symbol | One-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 / StepController | Deprecated 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):
| Symbol | One-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):
| Symbol | One-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
- BPC DSL — Scenes for the source-level grammar.
- Embedding charts for how to drop a scenes-driven chart on a page.
- API reference for the runtime entry-point symbols.