@blueprint-chart/docs 0.1.18
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +29 -0
- package/dist/api.d.ts +13 -0
- package/dist/api.js +29 -0
- package/dist/manifest.json +242 -0
- package/package.json +63 -0
- package/src/charts/area-stacked.md +64 -0
- package/src/charts/area.md +65 -0
- package/src/charts/bar-grouped.md +60 -0
- package/src/charts/bar-horizontal.md +71 -0
- package/src/charts/bar-multi.md +64 -0
- package/src/charts/bar-split.md +61 -0
- package/src/charts/bar-stacked.md +62 -0
- package/src/charts/bar-vertical.md +71 -0
- package/src/charts/column-stacked.md +57 -0
- package/src/charts/donut.md +66 -0
- package/src/charts/index.md +36 -0
- package/src/charts/line-multi.md +64 -0
- package/src/charts/line.md +65 -0
- package/src/charts/pie.md +63 -0
- package/src/guide/accessibility.md +196 -0
- package/src/guide/data-transforms.md +191 -0
- package/src/guide/dsl-editor.md +218 -0
- package/src/guide/embed.md +208 -0
- package/src/guide/getting-started.md +159 -0
- package/src/guide/palettes.md +207 -0
- package/src/guide/scenes.md +223 -0
- package/src/handbook/accessibility.md +109 -0
- package/src/handbook/annotations.md +143 -0
- package/src/handbook/anti-patterns.md +85 -0
- package/src/handbook/axes.md +116 -0
- package/src/handbook/choosing.md +120 -0
- package/src/handbook/color.md +141 -0
- package/src/handbook/design-principles.md +111 -0
- package/src/handbook/frame-elements.md +98 -0
- package/src/handbook/index.md +91 -0
- package/src/handbook/labels.md +117 -0
- package/src/handbook/tooltips.md +98 -0
- package/src/handbook/typography.md +93 -0
- package/src/reference/api/index.md +245 -0
- package/src/reference/dsl/annotations.md +135 -0
- package/src/reference/dsl/index.md +137 -0
- package/src/reference/dsl/properties.md +79 -0
- package/src/reference/dsl/scenes-and-transforms.md +97 -0
- package/src/reference/index.md +18 -0
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Scenes
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# Scenes
|
|
6
|
+
|
|
7
|
+
> Same chart, different states — composed into a story that a reader can step through.
|
|
8
|
+
|
|
9
|
+
## Why this matters
|
|
10
|
+
|
|
11
|
+
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.
|
|
12
|
+
|
|
13
|
+
## Quickstart
|
|
14
|
+
|
|
15
|
+
A bar chart with three narrative beats — each one highlights a different country:
|
|
16
|
+
|
|
17
|
+
```bpc
|
|
18
|
+
chart bar-horizontal {
|
|
19
|
+
title = "Five nations produce 80% of global CO₂"
|
|
20
|
+
description = "Annual emissions in billion tonnes, 2023"
|
|
21
|
+
source = "Global Carbon Project"
|
|
22
|
+
sourceUrl = "https://globalcarbonproject.org"
|
|
23
|
+
sort = descending
|
|
24
|
+
valueLabels = true
|
|
25
|
+
horizontalGridStyle = none
|
|
26
|
+
showVerticalAxis = true
|
|
27
|
+
showVerticalTicks = false
|
|
28
|
+
verticalGridStyle = none
|
|
29
|
+
|
|
30
|
+
data {
|
|
31
|
+
"China" = 11.90
|
|
32
|
+
"United States" = 4.78
|
|
33
|
+
"India" = 2.88
|
|
34
|
+
"Russia" = 1.78
|
|
35
|
+
"Japan" = 1.02
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
scene "China spotlight" {
|
|
39
|
+
title = "China emits more than the US and India combined"
|
|
40
|
+
|
|
41
|
+
highlight "China"
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
scene "India rising" {
|
|
45
|
+
title = "India surpassed the EU in 2023"
|
|
46
|
+
|
|
47
|
+
highlight "India"
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
scene "Japan declining" {
|
|
51
|
+
title = "Japan's emissions fell 20% from their peak"
|
|
52
|
+
|
|
53
|
+
highlight "Japan"
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
::: tip From the sample library
|
|
59
|
+
This is `packages/lib/src/samples/co2-emissions-story.bpc` verbatim — three scenes, each one re-titling the chart and shifting the highlight to a new country. The base data stays the same; only the framing changes.
|
|
60
|
+
:::
|
|
61
|
+
|
|
62
|
+
Open this in the editor to see the scene timeline appear automatically; embed it on a page and readers get a Previous / Next nav.
|
|
63
|
+
|
|
64
|
+
## How it works
|
|
65
|
+
|
|
66
|
+
A scene block accepts the same member set as the top-level chart, **plus** annotation-visibility verbs. 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.
|
|
67
|
+
|
|
68
|
+
At render time:
|
|
69
|
+
|
|
70
|
+
1. The runtime collects the chart's scenes into a `SceneDefinition[]`.
|
|
71
|
+
2. `createSceneController(container, scenes, onSceneChange)` injects a small `<nav>` (Previous / Next / counter) into the container.
|
|
72
|
+
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.
|
|
73
|
+
4. `goTo(index)` clamps and wraps so `goTo(-1)` cycles to the last scene.
|
|
74
|
+
|
|
75
|
+
The pipeline that runs per scene is the same eleven-step sequence documented in [Embedding](/guide/embed) and the DSL spec — scene overrides are merged at step 3 (transforms), so everything downstream sees the post-scene state.
|
|
76
|
+
|
|
77
|
+
## Recipes
|
|
78
|
+
|
|
79
|
+
### Highlight a different point per scene
|
|
80
|
+
|
|
81
|
+
The short-form `highlight "<name>"` is the workhorse. Drop one per scene and the rest of the chart greys out:
|
|
82
|
+
|
|
83
|
+
```bpc
|
|
84
|
+
scene "China spotlight" {
|
|
85
|
+
title = "China emits more than the US and India combined"
|
|
86
|
+
|
|
87
|
+
highlight "China"
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
scene "India rising" {
|
|
91
|
+
title = "India surpassed the EU in 2023"
|
|
92
|
+
|
|
93
|
+
highlight "India"
|
|
94
|
+
}
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
::: tip From the sample library
|
|
98
|
+
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.
|
|
99
|
+
:::
|
|
100
|
+
|
|
101
|
+
### Replace data wholesale in a scene
|
|
102
|
+
|
|
103
|
+
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:
|
|
104
|
+
|
|
105
|
+
```bpc
|
|
106
|
+
scene "Bulgaria: subsidies explode" {
|
|
107
|
+
title = "Subsidies to Bulgarian farmers, million euros"
|
|
108
|
+
description = "85% of Bulgarian subsidies are direct payments — the highest share among new members"
|
|
109
|
+
|
|
110
|
+
data {
|
|
111
|
+
_series = "Indirect subsidies","Direct subsidies"
|
|
112
|
+
"2000" = 0,5
|
|
113
|
+
"2004" = 0,67
|
|
114
|
+
"2007" = 59,250
|
|
115
|
+
"2010" = 79,466
|
|
116
|
+
"2013" = 132,852
|
|
117
|
+
"2015" = 213,677
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
highlight "Direct subsidies"
|
|
121
|
+
}
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
::: tip From the sample library
|
|
125
|
+
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.
|
|
126
|
+
:::
|
|
127
|
+
|
|
128
|
+
### Switch chart type mid-story
|
|
129
|
+
|
|
130
|
+
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:
|
|
131
|
+
|
|
132
|
+
```bpc
|
|
133
|
+
scene "Bulgarian farms grew" {
|
|
134
|
+
title = "Average farm size in Bulgaria quadrupled"
|
|
135
|
+
description = "Average farm size in hectares"
|
|
136
|
+
type = line
|
|
137
|
+
|
|
138
|
+
data {
|
|
139
|
+
"2005" = 5
|
|
140
|
+
"2007" = 6
|
|
141
|
+
"2010" = 12
|
|
142
|
+
"2013" = 18
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
::: tip From the sample library
|
|
148
|
+
Scene #5 of `packages/lib/src/samples/farm-compass.bpc`. The story changes chart type three times across nine scenes — `area-stacked` → `line` → `area` → `area-stacked` → bar — all from one document.
|
|
149
|
+
:::
|
|
150
|
+
|
|
151
|
+
### Hide an annotation in a later scene
|
|
152
|
+
|
|
153
|
+
Use `hide_annotation`, `hide_range`, or `hide_note` with the annotation's id to peel things back as the story progresses:
|
|
154
|
+
|
|
155
|
+
```bpc
|
|
156
|
+
annotation "2015" {
|
|
157
|
+
id = "paris"
|
|
158
|
+
text = "Paris Agreement"
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
scene "Without Paris callout" {
|
|
162
|
+
hide_annotation "paris"
|
|
163
|
+
}
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
To bring it back later, use `show_annotation "paris"` in a subsequent scene.
|
|
167
|
+
|
|
168
|
+
### Drive playback from your own UI
|
|
169
|
+
|
|
170
|
+
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:
|
|
171
|
+
|
|
172
|
+
```ts
|
|
173
|
+
import { createSceneController } from '@blueprint-chart/lib/dist/runtime'
|
|
174
|
+
|
|
175
|
+
const controller = createSceneController(container, scenes, (scene, index) => {
|
|
176
|
+
renderChart(canvas, scene.data, true)
|
|
177
|
+
})
|
|
178
|
+
|
|
179
|
+
document.querySelector('#my-next-button')!.addEventListener('click', () => {
|
|
180
|
+
controller.next()
|
|
181
|
+
})
|
|
182
|
+
|
|
183
|
+
// Programmatic jump:
|
|
184
|
+
controller.goTo(2)
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
Call `controller.destroy()` to remove the injected nav when tearing the chart down.
|
|
188
|
+
|
|
189
|
+
## API surface
|
|
190
|
+
|
|
191
|
+
Exported from `@blueprint-chart/lib/dist/runtime`:
|
|
192
|
+
|
|
193
|
+
| Symbol | One-liner |
|
|
194
|
+
| --- | --- |
|
|
195
|
+
| `createSceneController(container, scenes, onSceneChange)` | Build a scene controller, inject Previous / Next nav, call back on every scene change. |
|
|
196
|
+
| `SceneDefinition` (type) | `{ name: string, data?: Record<string, unknown> }` — one scene's payload. |
|
|
197
|
+
| `SceneController` (type) | Returned object with `currentScene`, `totalScenes`, `next()`, `previous()`, `goTo(index)`, `destroy()`. |
|
|
198
|
+
| `createStepController` / `StepDefinition` / `StepController` | Deprecated aliases retained for backward compatibility — new code should use the `Scene*` names. |
|
|
199
|
+
|
|
200
|
+
Motion helpers used internally during scene transitions (all exported from `@blueprint-chart/lib`):
|
|
201
|
+
|
|
202
|
+
| Symbol | One-liner |
|
|
203
|
+
| --- | --- |
|
|
204
|
+
| `getTransitionDuration()` | Canonical fade duration shared with the editor's UI transitions. |
|
|
205
|
+
| `snapshotForFadeOut(container)` | Capture the outgoing DOM for fade-out. |
|
|
206
|
+
| `commitFadeOut(snapshot)` | Animate the snapshot out. |
|
|
207
|
+
| `fadeIn(container)` | Animate the new render in. |
|
|
208
|
+
| `getCachedChart(container)` | Last render, for diff-based interpolation. |
|
|
209
|
+
|
|
210
|
+
DSL converter helpers (also exported from `@blueprint-chart/lib`):
|
|
211
|
+
|
|
212
|
+
| Symbol | One-liner |
|
|
213
|
+
| --- | --- |
|
|
214
|
+
| `extractSceneOverrides(ast)` | Pull each scene's merged `ChartData` / `ChartOptions` from a parsed AST. |
|
|
215
|
+
| `SceneNode` (type) | AST node for a `scene` block. |
|
|
216
|
+
|
|
217
|
+
See the full list in the [API reference](/reference/api/).
|
|
218
|
+
|
|
219
|
+
## See also
|
|
220
|
+
|
|
221
|
+
- [BPC DSL — Scenes](/reference/dsl/scenes-and-transforms#scenes) for the source-level grammar.
|
|
222
|
+
- [Embedding charts](/guide/embed) for how to drop a scenes-driven chart on a page.
|
|
223
|
+
- [API reference](/reference/api/#runtime-entrypoint) for the runtime entry-point symbols.
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Accessibility
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# Accessibility
|
|
6
|
+
|
|
7
|
+
> Accessible charts are honest charts. CVD simulation, sufficient contrast, meaningful alt text, keyboard and screen-reader support, legible typography, and — above all — never encoding information in a single visual channel.
|
|
8
|
+
|
|
9
|
+
## Color vision deficiency
|
|
10
|
+
|
|
11
|
+
Around **8% of men** and **0.5% of women** have some form of color vision deficiency.
|
|
12
|
+
|
|
13
|
+
- **Never rely on color alone** to convey meaning — combine with shape, pattern, label, or position
|
|
14
|
+
- Ensure colors vary enough in **lightness** — convert your palette to grayscale as a quick test
|
|
15
|
+
- Blue-orange is the safest hue combination for deuteranopia / protanopia
|
|
16
|
+
- Avoid red-green combinations without additional encoding
|
|
17
|
+
- Achieve at least **3:1 contrast ratio** between adjacent colors (WCAG)
|
|
18
|
+
- Test with simulators: Color Oracle, Coblis, Sim Daltonism, Firefox colorblind addon
|
|
19
|
+
|
|
20
|
+
## Text contrast
|
|
21
|
+
|
|
22
|
+
- Minimum **4.5:1 contrast ratio** for normal text (WCAG AA)
|
|
23
|
+
- Minimum **3:1** for large text (18px+ bold or 24px+ regular)
|
|
24
|
+
- Higher contrast needed for small or distant chart elements
|
|
25
|
+
|
|
26
|
+
## Alt text for charts
|
|
27
|
+
|
|
28
|
+
Every chart needs two levels of text alternative:
|
|
29
|
+
|
|
30
|
+
1. **Short alt text** — Identifies the chart type, subject, and scope. *Example: "Bar chart showing quarterly revenue for 2024 by product line"*
|
|
31
|
+
2. **Long description** — Full textual representation of essential data. Can be a linked data table, a `<figcaption>`, or content referenced via `aria-describedby`
|
|
32
|
+
|
|
33
|
+
## Keyboard and screen reader support
|
|
34
|
+
|
|
35
|
+
- Interactive elements (tooltips, filters) must be keyboard-accessible
|
|
36
|
+
- Use semantic HTML (`<figure>`, `<figcaption>`) to wrap charts
|
|
37
|
+
- Provide data tables as an alternative view for complex interactive charts
|
|
38
|
+
- Use `aria-describedby` to connect charts to their descriptions
|
|
39
|
+
|
|
40
|
+
## Font accessibility
|
|
41
|
+
|
|
42
|
+
- Minimum **12px** for chart text
|
|
43
|
+
- Sans-serif fonts with distinct character shapes (l vs. 1 vs. I)
|
|
44
|
+
- Avoid thin / light font weights for data labels
|
|
45
|
+
- Consider **Atkinson Hyperlegible** for maximum legibility
|
|
46
|
+
|
|
47
|
+
## Multiple encoding
|
|
48
|
+
|
|
49
|
+
::: warning
|
|
50
|
+
**Never encode information in a single visual channel.** Combine at least two.
|
|
51
|
+
:::
|
|
52
|
+
|
|
53
|
+
- Color + shape (circles vs. triangles)
|
|
54
|
+
- Color + pattern (solid vs. hatched)
|
|
55
|
+
- Color + label (direct text identification)
|
|
56
|
+
- Position + size + color (scatter / bubble charts)
|
|
57
|
+
|
|
58
|
+
## How Blueprint Chart applies accessibility
|
|
59
|
+
|
|
60
|
+
Blueprint Chart ships a concrete accessibility toolkit:
|
|
61
|
+
|
|
62
|
+
- `wcagContrastRatio` — computes WCAG contrast between two colors
|
|
63
|
+
- `simulateCvdColor` — applies deuteranopia / protanopia / tritanopia simulation
|
|
64
|
+
- `checkCvdColors` — validates that a palette is safe across CVD types
|
|
65
|
+
|
|
66
|
+
These power the editor's live accessibility checks and the palette pickers in the UI. See the [API reference](/reference/api/) for the full helper surface.
|
|
67
|
+
|
|
68
|
+
```ts
|
|
69
|
+
import { wcagContrastRatio, checkCvdColors } from '@blueprint-chart/lib'
|
|
70
|
+
|
|
71
|
+
wcagContrastRatio('#2563A0', '#ffffff') // 5.13 — passes WCAG AA for normal text
|
|
72
|
+
checkCvdColors(['#2563A0', '#F26A1F']) // safe across deuteranopia / protanopia / tritanopia
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## Worked example: lightness variation beats hue variation
|
|
76
|
+
|
|
77
|
+
```bpc
|
|
78
|
+
chart bar-vertical {
|
|
79
|
+
title = "Brazil produces more coffee than the next three countries combined"
|
|
80
|
+
colorPalette = "Harvey"
|
|
81
|
+
valueLabels = true
|
|
82
|
+
|
|
83
|
+
data {
|
|
84
|
+
"Brazil" = 66.4
|
|
85
|
+
"Vietnam" = 29
|
|
86
|
+
"Colombia" = 11.4
|
|
87
|
+
"Indonesia" = 9.9
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
colorize "Brazil" {
|
|
91
|
+
color = "#a4432d"
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
::: info From `packages/lib/src/samples/coffee-production.bpc`
|
|
97
|
+
Coffee uses two encodings for the highlighted bar: a distinct hue (`#a4432d` red-brown vs. the muted Harvey palette) and clear *lightness* contrast against its neighbours. Convert the chart to greyscale and Brazil still reads as the darkest bar — the second encoding (lightness) carries the story even when CVD removes the first (hue). Compare with `valueLabels = true`, which adds a *third* encoding (the literal number).
|
|
98
|
+
:::
|
|
99
|
+
|
|
100
|
+
## Palette catalogue and CVD utilities
|
|
101
|
+
|
|
102
|
+
Blueprint Chart ships 50+ palettes in `packages/lib/src/charts/palettes.ts`; each one is interpolated through HCL so adjacent colours vary in lightness as well as hue. The CVD simulation and validation helpers live in `packages/lib/src/charts/colorblind.ts`. Together they power the editor's live accessibility checks — any palette referenced by `colorPalette = "<name>"` in a `.bpc` file is run through `checkCvdColors` automatically.
|
|
103
|
+
|
|
104
|
+
## See also
|
|
105
|
+
|
|
106
|
+
- [Color & Palettes](/handbook/color)
|
|
107
|
+
- [Typography](/handbook/typography)
|
|
108
|
+
- [Tooltips & Interaction](/handbook/tooltips)
|
|
109
|
+
- [Design Principles](/handbook/design-principles)
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Annotations
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# Annotations
|
|
6
|
+
|
|
7
|
+
> Annotations guide the reader to the story in the data. They explain design elements, highlight significant values, and provide context the chart alone cannot convey. Used well, they do most of the storytelling.
|
|
8
|
+
|
|
9
|
+
## Purpose
|
|
10
|
+
|
|
11
|
+
Annotations guide the reader to the story in the data. They explain design elements, highlight significant values, and provide context that the chart alone cannot convey.
|
|
12
|
+
|
|
13
|
+
A chart without annotations presents data. A chart *with* annotations tells a story.
|
|
14
|
+
|
|
15
|
+
## Types of annotations
|
|
16
|
+
|
|
17
|
+
| Type | Description | Use for |
|
|
18
|
+
|------|-------------|---------|
|
|
19
|
+
| **Reference line (horizontal)** | Line at a specific y-value | Averages, targets, benchmarks, thresholds |
|
|
20
|
+
| **Reference line (vertical)** | Line at a specific x-value | Events in time, policy changes, milestones |
|
|
21
|
+
| **Range band (horizontal)** | Shaded region between two y-values | Acceptable zones, confidence intervals |
|
|
22
|
+
| **Range band (vertical)** | Shaded region between two x-values | Time periods (recessions, campaigns) |
|
|
23
|
+
| **Point annotation** | Marker + label at a specific data point | Outliers, peaks, significant individual values |
|
|
24
|
+
| **Callout** | Label with connector line | Explanations positioned away from crowded areas |
|
|
25
|
+
|
|
26
|
+
## Best practices
|
|
27
|
+
|
|
28
|
+
- Maximum **3-4 annotations per chart** to avoid overwhelming readers
|
|
29
|
+
- Position explanatory text as close to the relevant data as possible
|
|
30
|
+
- Use **two text hierarchy levels** within annotations (e.g., bold primary, regular secondary) — not more
|
|
31
|
+
- Use text outlines when annotations overlay grid lines
|
|
32
|
+
- On mobile, hide less important annotations or move them below the chart
|
|
33
|
+
- Write annotations that add context beyond what the data shows ("why" not just "what")
|
|
34
|
+
|
|
35
|
+
## Annotations as storytelling
|
|
36
|
+
|
|
37
|
+
::: tip
|
|
38
|
+
"If everything is emphasized, nothing is." Prioritize ruthlessly. The annotation should match the chart's headline — reinforce the main point, don't dilute it with tangential observations.
|
|
39
|
+
:::
|
|
40
|
+
|
|
41
|
+
A good annotation reads like a caption to a photograph: it tells the reader what matters here and why. Avoid restating what the axis already shows.
|
|
42
|
+
|
|
43
|
+
::: info Example
|
|
44
|
+
**Bad:** "March: 2,300 units" (redundant with the data label)
|
|
45
|
+
|
|
46
|
+
**Good:** "New packaging launched March 3 — unit sales jumped 40%"
|
|
47
|
+
:::
|
|
48
|
+
|
|
49
|
+
## How Blueprint Chart applies annotations
|
|
50
|
+
|
|
51
|
+
Blueprint Chart implements annotations as first-class citizens: **Point**, **Range**, and **Free** annotations are part of the DSL grammar and the rendering pipeline. Annotations are ordered and rendered above the chart geometry but below tooltips, so they sit visually in the foreground without interfering with hover interaction.
|
|
52
|
+
|
|
53
|
+
```bpc
|
|
54
|
+
chart line {
|
|
55
|
+
title = "Unit sales jumped after new packaging launched"
|
|
56
|
+
|
|
57
|
+
data {
|
|
58
|
+
"Jan" = 1600
|
|
59
|
+
"Feb" = 1700
|
|
60
|
+
"Mar" = 2300
|
|
61
|
+
"Apr" = 2350
|
|
62
|
+
"May" = 2420
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
annotation point {
|
|
66
|
+
at = "Mar"
|
|
67
|
+
label = "New packaging launched March 3"
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
See the [BPC DSL Specification](/reference/dsl/annotations) for the full annotation grammar.
|
|
73
|
+
|
|
74
|
+
## Worked example: a point annotation tied to a peak
|
|
75
|
+
|
|
76
|
+
```bpc
|
|
77
|
+
chart line {
|
|
78
|
+
title = "Bitcoin surged past $90,000 in 2024"
|
|
79
|
+
description = "USD, year-end closing price"
|
|
80
|
+
source = "CoinGecko"
|
|
81
|
+
colors = "#f7931a"
|
|
82
|
+
|
|
83
|
+
data {
|
|
84
|
+
"2020" = 28949
|
|
85
|
+
"2021" = 46306
|
|
86
|
+
"2022" = 16547
|
|
87
|
+
"2024" = 93429
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
annotation "2021" {
|
|
91
|
+
text = "All-time high cycle"
|
|
92
|
+
dy = -12
|
|
93
|
+
showArrow = true
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
::: info From `packages/lib/src/samples/bitcoin-price.bpc`
|
|
99
|
+
The annotation is keyed by the same x-value the data uses (`"2021"`), placed `-12` pixels above the point and connected with `showArrow`. One annotation, one observation — the chart stays under the 3–4 annotation ceiling and the label sits in the negative space above the data, not on top of the line.
|
|
100
|
+
:::
|
|
101
|
+
|
|
102
|
+
## Worked example: a callout with curved leader line
|
|
103
|
+
|
|
104
|
+
```bpc
|
|
105
|
+
chart bar-vertical {
|
|
106
|
+
title = "China emits more CO₂ than the US and India combined"
|
|
107
|
+
description = "Annual emissions in billion tonnes, 2023"
|
|
108
|
+
colors = "#abb8c3"
|
|
109
|
+
valueLabels = true
|
|
110
|
+
|
|
111
|
+
data {
|
|
112
|
+
"China" = 11.9
|
|
113
|
+
"United States" = 4.78
|
|
114
|
+
"India" = 2.88
|
|
115
|
+
"Russia" = 1.78
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
colorize "China" {
|
|
119
|
+
color = "#e15759"
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
annotation "India" {
|
|
123
|
+
text = "Surpassed EU in 2023"
|
|
124
|
+
showLine = true
|
|
125
|
+
anchorDirection = N
|
|
126
|
+
textOffsetX = 66
|
|
127
|
+
textOffsetY = -41
|
|
128
|
+
lineStyle = curve-left
|
|
129
|
+
showArrow = true
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
::: info From `packages/lib/src/samples/co2-emissions.bpc`
|
|
135
|
+
A callout adds the *why* alongside the data ("Surpassed EU in 2023") — a sentence that the bar height alone cannot communicate. `lineStyle = curve-left` plus the text offsets keep the label well clear of the bars; `anchorDirection = N` attaches the leader line to the top of the "India" bar.
|
|
136
|
+
:::
|
|
137
|
+
|
|
138
|
+
## See also
|
|
139
|
+
|
|
140
|
+
- [Frame Elements](/handbook/frame-elements)
|
|
141
|
+
- [Labels & Legends](/handbook/labels)
|
|
142
|
+
- [Design Principles](/handbook/design-principles)
|
|
143
|
+
- [BPC DSL Specification](/reference/dsl/annotations)
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Anti-Patterns
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# Anti-Patterns
|
|
6
|
+
|
|
7
|
+
> A catalog of what goes wrong: misleading practices that distort the truth, design anti-patterns that obscure the story, and the statistical integrity rules that keep charts honest.
|
|
8
|
+
|
|
9
|
+
## Misleading practices
|
|
10
|
+
|
|
11
|
+
| Anti-pattern | Why it misleads | Fix |
|
|
12
|
+
|--------------|-----------------|-----|
|
|
13
|
+
| Truncated y-axis on bar charts | Exaggerates small differences | Start at zero |
|
|
14
|
+
| Dual y-axes | Arbitrary scale alignment implies false correlation | Side-by-side charts or indexed values |
|
|
15
|
+
| 3D effects | Tilted surfaces distort perceived values | Use flat 2D charts |
|
|
16
|
+
| Cherry-picked time ranges | Supports a narrative without full context | Show complete timeframe; note any filtering |
|
|
17
|
+
| Bubble size by radius | Exponential area distortion | Size by area |
|
|
18
|
+
| Unnormalized choropleth | Shows population density, not the intended variable | Normalize per capita / per unit |
|
|
19
|
+
| Rainbow color scales | Perceptually non-uniform; meaningless ordering | Use sequential or diverging palettes |
|
|
20
|
+
|
|
21
|
+
See [Axes & Grid Lines](/handbook/axes) for the baseline rules behind the first row, and [Color & Palettes](/handbook/color) for palette guidance that prevents the last two.
|
|
22
|
+
|
|
23
|
+
## Design anti-patterns
|
|
24
|
+
|
|
25
|
+
| Anti-pattern | Problem | Fix |
|
|
26
|
+
|--------------|---------|-----|
|
|
27
|
+
| Spaghetti chart | Too many overlapping lines | Highlight key lines; grey the rest; small multiples |
|
|
28
|
+
| Pie chart with 10+ slices | Unreadable small slices | Use bar chart or group into "Other" |
|
|
29
|
+
| Decorative color | Color without meaning adds noise | Use grey for non-meaningful elements |
|
|
30
|
+
| Legend far from data | Forces eye-travel; increases cognitive load | Direct labeling |
|
|
31
|
+
| Rotated axis labels | Hard to read | Abbreviate labels or use horizontal bars |
|
|
32
|
+
| Over-annotation | Competing for attention dilutes the message | Maximum 3-4 annotations; prioritize |
|
|
33
|
+
| Missing context | Data without comparison has no story | Add reference lines, targets, time comparisons |
|
|
34
|
+
| Stacking many small segments | Impossible to read individual values | Group small segments; use direct comparison |
|
|
35
|
+
|
|
36
|
+
See [Labels & Legends](/handbook/labels) for direct-labeling technique and [Annotations](/handbook/annotations) for the 3–4 annotation ceiling.
|
|
37
|
+
|
|
38
|
+
## Statistical integrity
|
|
39
|
+
|
|
40
|
+
- **Start axes at zero** for area / bar charts unless there is a compelling, stated reason not to
|
|
41
|
+
- **Avoid implying causation from correlation** (scatter plots show association only)
|
|
42
|
+
- Show **confidence intervals and error bars** when data has uncertainty
|
|
43
|
+
- Don't obscure sample size (**box plots hide it**; add individual points for small N)
|
|
44
|
+
- **Tables are valid** — sometimes better than charts for conveying precise values
|
|
45
|
+
|
|
46
|
+
## Worked example: a shared baseline instead of three deceptive single-bar charts
|
|
47
|
+
|
|
48
|
+
A frequent anti-pattern is splitting a comparison across three charts with different y-axis ranges — each one tuned to "look interesting" in isolation. The honest alternative is one chart with a shared scale, so identical bar lengths mean identical values:
|
|
49
|
+
|
|
50
|
+
```bpc
|
|
51
|
+
chart bar-split {
|
|
52
|
+
title = "Singapore leads the world in all three PISA 2022 subjects"
|
|
53
|
+
description = "Mean scores in Mathematics, Reading, and Science for 15-year-olds"
|
|
54
|
+
source = "OECD PISA 2022"
|
|
55
|
+
valueLabels = true
|
|
56
|
+
sharedScale = true
|
|
57
|
+
|
|
58
|
+
data {
|
|
59
|
+
_series = "Mathematics","Reading","Science"
|
|
60
|
+
"Singapore" = 575,543,561
|
|
61
|
+
"Japan" = 536,516,547
|
|
62
|
+
"Korea" = 527,515,528
|
|
63
|
+
"Estonia" = 510,511,526
|
|
64
|
+
"Canada" = 497,507,515
|
|
65
|
+
"Australia" = 487,498,507
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
::: tip Done right
|
|
71
|
+
`sharedScale = true` forces every panel onto the same baseline-zero axis, so the eye can compare Mathematics directly against Reading directly against Science. `valueLabels = true` then exposes the exact number, removing any temptation to truncate the axis for visual drama. This is the corrective pattern for the "truncated y-axis", "dual y-axes", and "missing context" rows above. From `packages/lib/src/samples/pisa-scores.bpc`.
|
|
72
|
+
:::
|
|
73
|
+
|
|
74
|
+
## The meta-rule
|
|
75
|
+
|
|
76
|
+
::: tip
|
|
77
|
+
Every anti-pattern traces back to a design principle being violated. If a chart feels wrong, walk back through the [design principles](/handbook/design-principles) — purposefulness, clarity, data-ink, restraint, consistency, comparison. One of them will be out of place.
|
|
78
|
+
:::
|
|
79
|
+
|
|
80
|
+
## See also
|
|
81
|
+
|
|
82
|
+
- [Design Principles](/handbook/design-principles)
|
|
83
|
+
- [Choosing the Right Chart](/handbook/choosing)
|
|
84
|
+
- [Axes & Grid Lines](/handbook/axes)
|
|
85
|
+
- [Color & Palettes](/handbook/color)
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Axes & Grid Lines
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# Axes & Grid Lines
|
|
6
|
+
|
|
7
|
+
> When zero matters, when it doesn't, how grid lines should whisper rather than shout, how to format numbers, when to use logarithmic scales, and how aspect ratio silently changes a chart's story.
|
|
8
|
+
|
|
9
|
+
## Baseline rules
|
|
10
|
+
|
|
11
|
+
| Chart type | Must start at zero? | Reason |
|
|
12
|
+
|------------|---------------------|--------|
|
|
13
|
+
| Bar / column | **Yes** | Bar length encodes the value |
|
|
14
|
+
| Stacked bar | **Yes** | Area encodes the value |
|
|
15
|
+
| Area chart | **Yes** | Filled area encodes magnitude |
|
|
16
|
+
| Line chart | **No** | Position (not length) encodes the value |
|
|
17
|
+
| Scatter plot | **No** | Position encodes the value |
|
|
18
|
+
|
|
19
|
+
::: warning
|
|
20
|
+
Truncating the y-axis on a bar or area chart visually exaggerates differences. This is the single most common deceptive pattern in the wild. See [Anti-Patterns](/handbook/anti-patterns).
|
|
21
|
+
:::
|
|
22
|
+
|
|
23
|
+
## Grid lines
|
|
24
|
+
|
|
25
|
+
- Grid lines should be **subtle**: light grey, thin (0.5–1px)
|
|
26
|
+
- Y-axis grid lines are generally more useful than x-axis grid lines
|
|
27
|
+
- Remove grid lines entirely when value labels are shown directly on data
|
|
28
|
+
- Grid lines are reference aids, not primary content — they should never compete with data
|
|
29
|
+
|
|
30
|
+
## Tick marks
|
|
31
|
+
|
|
32
|
+
- Subtle or no ticks are preferred in modern chart design
|
|
33
|
+
- Use consistent intervals (don't skip values)
|
|
34
|
+
- Avoid excessive tick density — 5-7 ticks on a continuous axis is usually sufficient
|
|
35
|
+
|
|
36
|
+
## Number formatting
|
|
37
|
+
|
|
38
|
+
- Use abbreviated formats: **20k, 20m, 20b** (not "in millions")
|
|
39
|
+
- Remove trailing zeros: **27%** not **27.0%**
|
|
40
|
+
- Use thousands separators: **1,000** not **1000**
|
|
41
|
+
- Repeat units on axis labels: **$20k** on each tick, not just "in thousands of dollars"
|
|
42
|
+
|
|
43
|
+
## Logarithmic scales
|
|
44
|
+
|
|
45
|
+
- Use for data spanning multiple orders of magnitude
|
|
46
|
+
- Display relative changes and ratios
|
|
47
|
+
- Add horizontal reference lines at meaningful values
|
|
48
|
+
- Arrange symmetrically around the no-change point
|
|
49
|
+
- Use ratio notation (1/4, 1/2, 1, 2, 4) rather than decimal notation
|
|
50
|
+
- Label clearly — many audiences are unfamiliar with log scales
|
|
51
|
+
|
|
52
|
+
## Aspect ratio
|
|
53
|
+
|
|
54
|
+
- Default to roughly **4:3** or **16:9** for standard charts
|
|
55
|
+
- Use **1:1** (square) when both axes share the same unit or meaning (observed vs. predicted, before vs. after)
|
|
56
|
+
- Aspect ratio dramatically affects perceived slope and trend interpretation
|
|
57
|
+
|
|
58
|
+
::: tip
|
|
59
|
+
A trend that looks alarming at 3:1 can look flat at 3:4. When showing rate of change, pick an aspect ratio that reflects the underlying rate honestly — neither flattened nor exaggerated.
|
|
60
|
+
:::
|
|
61
|
+
|
|
62
|
+
## How Blueprint Chart applies axes
|
|
63
|
+
|
|
64
|
+
Blueprint Chart's `AxisOptions` exposes scale type (linear / log), tick count, tick format, and baseline behavior. D3 scales drive the tick layout, and a shared number formatter is used by the axis, tooltip, and direct labels so the same value is rendered identically wherever it appears.
|
|
65
|
+
|
|
66
|
+
See the [API reference](/reference/api/) for the public options that toggle grid lines, tick density, and baseline zero.
|
|
67
|
+
|
|
68
|
+
## Worked example: number format on the axis
|
|
69
|
+
|
|
70
|
+
```bpc
|
|
71
|
+
chart line {
|
|
72
|
+
title = "Bitcoin surged past $90,000 in 2024"
|
|
73
|
+
description = "USD, year-end closing price"
|
|
74
|
+
source = "CoinGecko"
|
|
75
|
+
colors = "#f7931a"
|
|
76
|
+
verticalNumberFormat = ",.0f"
|
|
77
|
+
lineSymbols = true
|
|
78
|
+
|
|
79
|
+
data {
|
|
80
|
+
"2016" = 963
|
|
81
|
+
"2020" = 28949
|
|
82
|
+
"2021" = 46306
|
|
83
|
+
"2024" = 93429
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
::: info From `packages/lib/src/samples/bitcoin-price.bpc`
|
|
89
|
+
`verticalNumberFormat = ",.0f"` uses a D3 format string to add thousands separators and drop decimals — so the axis shows `93,429` instead of `93429.00`. The same formatter feeds the tooltip and any direct value labels, so identical values render identically in every slot.
|
|
90
|
+
:::
|
|
91
|
+
|
|
92
|
+
## Worked example: gridlines that whisper
|
|
93
|
+
|
|
94
|
+
```bpc
|
|
95
|
+
chart line {
|
|
96
|
+
title = "2024 was the hottest year on record"
|
|
97
|
+
colors = "#e15759"
|
|
98
|
+
showVerticalAxis = false
|
|
99
|
+
showVerticalTicks = false
|
|
100
|
+
verticalGridStyle = "dashed"
|
|
101
|
+
showHorizontalAxis = true
|
|
102
|
+
showHorizontalTicks = false
|
|
103
|
+
horizontalGridStyle = "none"
|
|
104
|
+
}
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
::: info From `packages/lib/src/samples/temperature-anomaly.bpc`
|
|
108
|
+
The vertical axis line and ticks are off; only dashed horizontal gridlines remain as reference. The horizontal axis stays visible but loses its ticks. Net result: gridlines whisper, the line speaks.
|
|
109
|
+
:::
|
|
110
|
+
|
|
111
|
+
## See also
|
|
112
|
+
|
|
113
|
+
- [Labels & Legends](/handbook/labels)
|
|
114
|
+
- [Anti-Patterns](/handbook/anti-patterns)
|
|
115
|
+
- [Design Principles](/handbook/design-principles)
|
|
116
|
+
- [Frame Elements](/handbook/frame-elements)
|