@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,196 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Accessibility
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# Accessibility
|
|
6
|
+
|
|
7
|
+
> WCAG contrast checks, colour-vision-deficiency simulation, and palette-safety audits — built into the library.
|
|
8
|
+
|
|
9
|
+
## Why this matters
|
|
10
|
+
|
|
11
|
+
Around 8% of men and 0.5% of women have some form of colour vision deficiency. Many readers consume charts at thumbnail sizes, in dim rooms, or in dark mode. Blueprint Chart bakes the toolkit for these cases into `@blueprint-chart/lib`: WCAG contrast ratio + grading, automatic palette tuning for the actual background, programmatic CVD simulation, and an SVG filter you can drop on a live chart for instant preview.
|
|
12
|
+
|
|
13
|
+
## Quickstart
|
|
14
|
+
|
|
15
|
+
Audit a colour pair, then a whole palette:
|
|
16
|
+
|
|
17
|
+
```ts
|
|
18
|
+
import {
|
|
19
|
+
wcagContrastRatio,
|
|
20
|
+
wcagLevel,
|
|
21
|
+
resolvePalette,
|
|
22
|
+
checkCvdColors,
|
|
23
|
+
} from '@blueprint-chart/lib'
|
|
24
|
+
|
|
25
|
+
const ratio = wcagContrastRatio('#ffffff', '#2563A0')
|
|
26
|
+
wcagLevel(ratio) // 'AA' — 4.5 ≤ ratio < 7
|
|
27
|
+
|
|
28
|
+
const palette = resolvePalette('Egypt')!
|
|
29
|
+
const issues = checkCvdColors(palette)
|
|
30
|
+
// issues is empty if every pair stays distinguishable under
|
|
31
|
+
// protanopia / deuteranopia / tritanopia (ΔE ≥ 10)
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
In the editor, the CVD toggle in the toolbar swaps in an SVG filter so authors can preview their chart through each dichromacy live. No layout shift; the chart stays interactive.
|
|
35
|
+
|
|
36
|
+
## How it works
|
|
37
|
+
|
|
38
|
+
Two toolkits ship from `packages/lib/src/charts/`:
|
|
39
|
+
|
|
40
|
+
- **`contrast.ts`** — WCAG 2.1 contrast ratio (`wcagContrastRatio`) and grading (`wcagLevel` → `'AAA' | 'AA' | 'Fail'`). It also provides `resolveBackgroundColor(el)` which walks up the DOM until it finds a non-transparent background, and `adjustColorsForBackground(colors, bg)` which nudges palette lightness until every entry clears WCAG AA against the background **and** every adjacent pair has a CIE2000 deltaE of at least 12. Hue and saturation are preserved; only lightness moves.
|
|
41
|
+
|
|
42
|
+
- **`colorblind.ts`** — Three Viénot dichromacy matrices (`protanopia`, `deuteranopia`, `tritanopia`) applied two ways: programmatically by `simulateCvdColor(hex, type)` (linearise sRGB → matrix multiply → companding) and at render time via `createCvdSvgFilter(type)`, which builds an `<feColorMatrix>` filter you apply through CSS `filter: url(#bc-cvd-deuteranopia)`. Both paths share the same matrices, so on-screen preview matches programmatic checks.
|
|
43
|
+
|
|
44
|
+
`checkCvdColors(palette)` runs `simulateCvdColor` over every pair, for every dichromacy, and reports any pair whose simulated colours collapse below ΔE 10 — the threshold where chart marks start to read as "the same colour".
|
|
45
|
+
|
|
46
|
+
> [!warning]
|
|
47
|
+
> The `CvdType` union covers the three dichromacies only — `'protanopia' | 'deuteranopia' | 'tritanopia'`. The editor's UI toggle includes an "off" state, but the typed surface in `colorblind.ts` has three values.
|
|
48
|
+
|
|
49
|
+
## Recipes
|
|
50
|
+
|
|
51
|
+
### Audit a palette before publishing
|
|
52
|
+
|
|
53
|
+
A common build-time check: pick a palette, validate it against the background you actually ship on, and flag CVD collisions.
|
|
54
|
+
|
|
55
|
+
```ts
|
|
56
|
+
import {
|
|
57
|
+
resolvePalette,
|
|
58
|
+
wcagContrastRatio,
|
|
59
|
+
wcagLevel,
|
|
60
|
+
checkCvdColors,
|
|
61
|
+
} from '@blueprint-chart/lib'
|
|
62
|
+
|
|
63
|
+
const palette = resolvePalette('Blueprint')!
|
|
64
|
+
const bg = '#ffffff'
|
|
65
|
+
|
|
66
|
+
const contrastReport = palette.map((color) => ({
|
|
67
|
+
color,
|
|
68
|
+
ratio: wcagContrastRatio(color, bg),
|
|
69
|
+
level: wcagLevel(wcagContrastRatio(color, bg)),
|
|
70
|
+
}))
|
|
71
|
+
|
|
72
|
+
const cvdIssues = checkCvdColors(palette)
|
|
73
|
+
if (cvdIssues.length > 0) {
|
|
74
|
+
for (const issue of cvdIssues) {
|
|
75
|
+
console.warn(issue.label, issue.pairs)
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### Audit a sample chart's hand-picked colours
|
|
81
|
+
|
|
82
|
+
The medal-count sample uses three hand-picked metallic colours — gold, silver, bronze. The same three colours are exactly the case where CVD audit matters: under deuteranopia, yellow and white-grey can pull close on the L* axis. Drop the inline `colors` list straight into the audit:
|
|
83
|
+
|
|
84
|
+
```bpc
|
|
85
|
+
chart bar-multi {
|
|
86
|
+
title = "USA tops Paris 2024 with 126 medals across all categories"
|
|
87
|
+
colors = "#eeca3b, #c0c0c0, #cd7f32"
|
|
88
|
+
legendPosition = "top"
|
|
89
|
+
|
|
90
|
+
data {
|
|
91
|
+
_series = "Gold","Silver","Bronze"
|
|
92
|
+
"USA" = 40,44,42
|
|
93
|
+
"China" = 38,32,18
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
::: tip From the sample library
|
|
99
|
+
Trimmed from `packages/lib/src/samples/medal-count.bpc` (the full sample lists the top six nations). The three hand-picked hex colours are the audit input — none of them resolve through `resolvePalette()`, so pull them straight from the DSL string.
|
|
100
|
+
:::
|
|
101
|
+
|
|
102
|
+
```ts
|
|
103
|
+
import { checkCvdColors, wcagContrastRatio } from '@blueprint-chart/lib'
|
|
104
|
+
|
|
105
|
+
const medalColors = ['#eeca3b', '#c0c0c0', '#cd7f32']
|
|
106
|
+
|
|
107
|
+
const cvdIssues = checkCvdColors(medalColors)
|
|
108
|
+
const onLight = medalColors.map((c) => wcagContrastRatio(c, '#ffffff'))
|
|
109
|
+
// Silver (#c0c0c0) clears 1.6:1 on white — under WCAG AA it would fail for
|
|
110
|
+
// thin marks. Pair it with a label or border, or substitute a CVD-safe
|
|
111
|
+
// palette like `Blueprint` if you're not committed to medal colours.
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
The sample ships these colours because the semantic mapping (gold = 1st, bronze = 3rd) is part of the story. When that semantic mapping isn't worth defending, swap to a CVD-checked named palette via `colorPalette = "<name>"` instead.
|
|
115
|
+
|
|
116
|
+
### Build a CVD-safe palette from scratch
|
|
117
|
+
|
|
118
|
+
`checkCvdColors` returns the pairs that need attention. The cheapest fix is usually to vary lightness — keep the hues, but pull adjacent entries further apart on the L* axis. `adjustColorsForBackground` does exactly that for the WCAG dimension; you can apply it before running `checkCvdColors` to compress the failure surface:
|
|
119
|
+
|
|
120
|
+
```ts
|
|
121
|
+
import { adjustColorsForBackground, checkCvdColors } from '@blueprint-chart/lib'
|
|
122
|
+
|
|
123
|
+
const tuned = adjustColorsForBackground(myCandidates, '#0a0a0a')
|
|
124
|
+
const remainingIssues = checkCvdColors(tuned)
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
If a pair still collides, replace one entry or pair the encoding with a non-colour affordance (direct label, shape, pattern). The [accessibility handbook](/handbook/accessibility) calls this out as the most important rule: never encode information in a single visual channel.
|
|
128
|
+
|
|
129
|
+
### Auto-tune chart marks to the actual background
|
|
130
|
+
|
|
131
|
+
Opt a chart into automatic contrast adjustment by setting `autoContrast = true` in the DSL. At render time the engine calls `resolveBackgroundColor(container)` on the chart's frame and runs `adjustColorsForBackground` over the resolved palette before the chart-type renderer paints marks. Same `.bpc` source works in light and dark themes.
|
|
132
|
+
|
|
133
|
+
### Preview a chart under CVD in a non-editor consumer
|
|
134
|
+
|
|
135
|
+
The SVG filter pattern works in any DOM context — drop the filter into your page once, then toggle the `filter` style on the chart container.
|
|
136
|
+
|
|
137
|
+
```ts
|
|
138
|
+
import { createCvdSvgFilter, getCvdFilterId, type CvdType } from '@blueprint-chart/lib'
|
|
139
|
+
|
|
140
|
+
function installCvdFilter(type: CvdType) {
|
|
141
|
+
const ns = 'http://www.w3.org/2000/svg'
|
|
142
|
+
let host = document.querySelector<SVGSVGElement>('#bc-cvd-host')
|
|
143
|
+
if (!host) {
|
|
144
|
+
host = document.createElementNS(ns, 'svg')
|
|
145
|
+
host.id = 'bc-cvd-host'
|
|
146
|
+
host.style.position = 'absolute'
|
|
147
|
+
host.style.width = '0'
|
|
148
|
+
host.style.height = '0'
|
|
149
|
+
document.body.appendChild(host)
|
|
150
|
+
}
|
|
151
|
+
host.appendChild(createCvdSvgFilter(type))
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
installCvdFilter('deuteranopia')
|
|
155
|
+
chartContainer.style.filter = `url(#${getCvdFilterId('deuteranopia')})`
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
### Validate text contrast on titles, axes, and annotations
|
|
159
|
+
|
|
160
|
+
WCAG AA wants 4.5:1 for body text, 3:1 for large text (18px+ bold or 24px+ regular). `wcagContrastRatio` accepts any chroma-parseable input — hex, named colours, `rgb(…)` — so it pairs well with `getComputedStyle()`:
|
|
161
|
+
|
|
162
|
+
```ts
|
|
163
|
+
import { wcagContrastRatio, wcagLevel } from '@blueprint-chart/lib'
|
|
164
|
+
|
|
165
|
+
function checkLabel(labelEl: HTMLElement) {
|
|
166
|
+
const style = getComputedStyle(labelEl)
|
|
167
|
+
const ratio = wcagContrastRatio(style.color, style.backgroundColor || '#ffffff')
|
|
168
|
+
return { ratio, level: wcagLevel(ratio) }
|
|
169
|
+
}
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
## API surface
|
|
173
|
+
|
|
174
|
+
Exported from `@blueprint-chart/lib`:
|
|
175
|
+
|
|
176
|
+
| Symbol | One-liner |
|
|
177
|
+
| --- | --- |
|
|
178
|
+
| `wcagContrastRatio(fg, bg)` | WCAG 2.1 relative-luminance ratio between two colours (1–21). |
|
|
179
|
+
| `wcagLevel(ratio)` | Maps a ratio to `'AAA' \| 'AA' \| 'Fail'`. |
|
|
180
|
+
| `resolveBackgroundColor(el)` | Walks ancestors until it finds a non-transparent background; falls back to `#fff`. |
|
|
181
|
+
| `adjustColorsForBackground(colors, bg)` | Returns a copy of `colors` tuned for WCAG ≥ 4.5 and adjacent ΔE ≥ 12 against `bg`. |
|
|
182
|
+
| `simulateCvdColor(hex, type)` | Returns the perceived hex under a given dichromacy. |
|
|
183
|
+
| `createCvdSvgFilter(type)` | Builds an `<feColorMatrix>` filter element, applied via CSS `filter: url(#…)`. |
|
|
184
|
+
| `getCvdFilterId(type)` | Deterministic filter id, e.g. `'bc-cvd-deuteranopia'`. |
|
|
185
|
+
| `checkCvdColors(colors)` | Returns `CvdIssue[]` for every dichromacy where any pair collapses (ΔE < 10). |
|
|
186
|
+
| `CvdType` (type) | `'protanopia' \| 'deuteranopia' \| 'tritanopia'`. |
|
|
187
|
+
| `CvdIssue` (type) | `{ type, label, pairs: { a, b, deltaE }[] }`. |
|
|
188
|
+
|
|
189
|
+
See [the API reference](/reference/api/#color-and-accessibility) for the full export list.
|
|
190
|
+
|
|
191
|
+
## See also
|
|
192
|
+
|
|
193
|
+
- [Accessibility handbook](/handbook/accessibility) — CVD, contrast, alt text, keyboard, multi-channel encoding.
|
|
194
|
+
- [Palettes guide](/guide/palettes) — picking and overriding palettes.
|
|
195
|
+
- [Colour handbook](/handbook/color) — the lightness-variance and blue-orange rules.
|
|
196
|
+
- [BPC DSL — Properties](/reference/dsl/properties#properties) for `autoContrast`.
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Data Transforms
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# Data Transforms
|
|
6
|
+
|
|
7
|
+
> A composable pipeline that shapes raw tabular data into the rows the chart actually renders.
|
|
8
|
+
|
|
9
|
+
## Why this matters
|
|
10
|
+
|
|
11
|
+
Raw data is rarely chart-ready. Columns have the wrong types, rows need filtering, groups need aggregating, the table is the wrong way around. Blueprint Chart bakes a small **transform pipeline** into the editor so you can clean and reshape data inside the chart rather than juggling external spreadsheets. The same pipeline can also be expressed in the `.bpc` DSL via `transform` nodes, which means the cleaning step travels with the chart source.
|
|
12
|
+
|
|
13
|
+
## Quickstart
|
|
14
|
+
|
|
15
|
+
A `.bpc` chart with a single sort transform — the canonical example. The transform runs **before** the chart-type renderer paints marks:
|
|
16
|
+
|
|
17
|
+
```bpc
|
|
18
|
+
chart bar-vertical {
|
|
19
|
+
title = "Brazil produces more coffee than the next three countries combined"
|
|
20
|
+
description = "Million 60-kg bags, 2023/24 crop year"
|
|
21
|
+
colorPalette = "Harvey"
|
|
22
|
+
valueLabels = true
|
|
23
|
+
|
|
24
|
+
data {
|
|
25
|
+
"Brazil" = 66.4
|
|
26
|
+
"Vietnam" = 29
|
|
27
|
+
"Colombia" = 11.4
|
|
28
|
+
"Indonesia" = 9.9
|
|
29
|
+
"Ethiopia" = 8.7
|
|
30
|
+
"Honduras" = 6.3
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
colorize "Brazil" {
|
|
34
|
+
color = "#a4432d"
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
transform sort {
|
|
38
|
+
column = "value"
|
|
39
|
+
direction = descending
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
::: tip From the sample library
|
|
45
|
+
This is `packages/lib/src/samples/coffee-production.bpc` — the only sample currently shipping with a `transform` block. The data is authored in arbitrary order on disk; `transform sort` flips it to descending before the bar-vertical renderer reads it.
|
|
46
|
+
:::
|
|
47
|
+
|
|
48
|
+
In the editor, the same pipeline is built interactively in the Data panel — each step appears as a card, can be reordered by drag, and is re-applied on every keystroke.
|
|
49
|
+
|
|
50
|
+
## How it works
|
|
51
|
+
|
|
52
|
+
The editor's pipeline lives in `packages/editor/src/stores/dataTransforms.ts` as a `dataTransforms` Pinia store. It holds an ordered array of `TransformStep` objects:
|
|
53
|
+
|
|
54
|
+
```ts
|
|
55
|
+
interface TransformStep {
|
|
56
|
+
id: string
|
|
57
|
+
type: TransformType // 'sort' | 'filter' | 'hide-columns' | …
|
|
58
|
+
config: Record<string, string>
|
|
59
|
+
}
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
A `TransformResult` (`{ columns, rows, columnTypes }`) flows through the pipeline. Each step is a pure function from `(result, config) → result`. The store exposes `applyTransforms(columns, rows, columnTypes)` which folds every step over the input, and `getColumnsAtStep(stepIndex, …)` which lets the UI show what the table will look like at any point in the chain — handy when configuring downstream steps that need the columns produced upstream.
|
|
63
|
+
|
|
64
|
+
Eight transform types are recognised today (`Computed` is reserved but not implemented):
|
|
65
|
+
|
|
66
|
+
| Type | Effect |
|
|
67
|
+
| --- | --- |
|
|
68
|
+
| `Sort` | Sort rows by one or more columns, ascending or descending. |
|
|
69
|
+
| `Filter` | Keep rows matching a condition on a single column. |
|
|
70
|
+
| `HideColumns` | Drop columns from the result. |
|
|
71
|
+
| `Transpose` | Swap rows and columns. |
|
|
72
|
+
| `Parse` | Re-type or reformat a column (e.g. parse numbers, normalise strings). |
|
|
73
|
+
| `Rename` | Rename a column. |
|
|
74
|
+
| `GroupBy` | Group rows by one or more columns and aggregate the rest. |
|
|
75
|
+
| `Computed` | Reserved — not yet implemented. |
|
|
76
|
+
|
|
77
|
+
When the chart is serialised back to `.bpc`, transform steps emit as `transform <name> { … }` blocks, parsed back into the AST as `TransformNode`s on the round-trip.
|
|
78
|
+
|
|
79
|
+
## Recipes
|
|
80
|
+
|
|
81
|
+
### Sort the data shown on the chart
|
|
82
|
+
|
|
83
|
+
The cheapest cleanup. `transform sort` runs before the chart renders, so the bar order on disk doesn't need to match the visual order:
|
|
84
|
+
|
|
85
|
+
```bpc
|
|
86
|
+
transform sort {
|
|
87
|
+
column = "value"
|
|
88
|
+
direction = descending
|
|
89
|
+
}
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
::: tip From the sample library
|
|
93
|
+
Excerpted from `packages/lib/src/samples/coffee-production.bpc` (also the [quickstart](#quickstart) above). Drop a Sort step into the editor's Data panel and pick a column — it serialises to this same `transform sort` block on save.
|
|
94
|
+
:::
|
|
95
|
+
|
|
96
|
+
### Filter rows by a condition
|
|
97
|
+
|
|
98
|
+
`Filter` supports five conditions (see `FilterCondition` in `packages/editor/src/enums.ts`):
|
|
99
|
+
|
|
100
|
+
| Condition | Match |
|
|
101
|
+
| --- | --- |
|
|
102
|
+
| `equals` | Exact match (string compare). |
|
|
103
|
+
| `not-equals` | Inverse of `equals`. |
|
|
104
|
+
| `contains` | Substring, case-insensitive. |
|
|
105
|
+
| `greater-than` | Numeric `>`; currency symbols and commas in the cell are stripped. |
|
|
106
|
+
| `less-than` | Numeric `<`; same stripping. |
|
|
107
|
+
|
|
108
|
+
The serialised form pairs a condition with a target column and value:
|
|
109
|
+
|
|
110
|
+
```bpc
|
|
111
|
+
transform filter {
|
|
112
|
+
column = "year"
|
|
113
|
+
condition = greater-than
|
|
114
|
+
value = "2000"
|
|
115
|
+
}
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
::: warning No sample uses `transform filter` today
|
|
119
|
+
The block above is synthesized from `packages/editor/src/utils/transforms/applyFilter.ts` rather than copied from a `.bpc` file — none of the 38 shipped samples filter their data on render. Treat the syntax as canonical (it round-trips through the editor) but expect to author it by hand for now.
|
|
120
|
+
:::
|
|
121
|
+
|
|
122
|
+
If the target value is empty for any condition other than `equals` / `not-equals`, the filter is a no-op (the row is kept).
|
|
123
|
+
|
|
124
|
+
### Group rows and aggregate
|
|
125
|
+
|
|
126
|
+
`GroupBy` collapses rows that share one or more group columns and folds the rest with an aggregate function. The `aggregates` config field is a comma-separated list of `column:fn` pairs — `fn` is one of `sum`, `avg`, `min`, `max`, `count`. For `count` the `column` is ignored.
|
|
127
|
+
|
|
128
|
+
```bpc
|
|
129
|
+
transform group-by {
|
|
130
|
+
groupColumns = "country"
|
|
131
|
+
aggregates = "exports:sum,population:avg,exports:count"
|
|
132
|
+
}
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
::: warning No sample uses `transform group-by` today
|
|
136
|
+
Synthesized from `packages/editor/src/utils/transforms/applyGroupBy.ts` and `packages/editor/src/enums.ts` (`TransformType.GroupBy = 'group-by'`). Build the step in the editor's Data panel and copy the serialised output if you want a verified starting point.
|
|
137
|
+
:::
|
|
138
|
+
|
|
139
|
+
### Use the pipeline outside the editor
|
|
140
|
+
|
|
141
|
+
The whole store is a plain Pinia composable. If you're building your own Vue app on top of `@blueprint-chart/editor` internals, import it directly and feed it any table:
|
|
142
|
+
|
|
143
|
+
```ts
|
|
144
|
+
import { useDataTransforms } from '@/composables/useDataTransforms'
|
|
145
|
+
|
|
146
|
+
const { addStep, applyTransforms, snapshot, hydrate } = useDataTransforms()
|
|
147
|
+
|
|
148
|
+
addStep(TransformType.Sort, { column: 'value', direction: 'descending' })
|
|
149
|
+
addStep(TransformType.Filter, { column: 'year', condition: 'greater-than', value: '2000' })
|
|
150
|
+
|
|
151
|
+
const { columns, rows, columnTypes } = applyTransforms(
|
|
152
|
+
inputColumns,
|
|
153
|
+
inputRows,
|
|
154
|
+
inputColumnTypes,
|
|
155
|
+
)
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
`snapshot()` returns a serialisable copy of the pipeline; `hydrate(steps)` replaces it. The store also persists to local storage in the editor, so reload-survival is automatic in that context.
|
|
159
|
+
|
|
160
|
+
### Validate a step before adding it
|
|
161
|
+
|
|
162
|
+
`validateStep(step, columns, columnTypes)` returns either `null` (valid) or a human-readable string explaining why the step is misconfigured — column not found, missing required field, incompatible type for a `Parse` operation. The Data panel calls this on every config change to disable the Apply button.
|
|
163
|
+
|
|
164
|
+
## API surface
|
|
165
|
+
|
|
166
|
+
The transform pipeline lives in `@blueprint-chart/editor` rather than `@blueprint-chart/lib`. The `lib` half is the DSL representation:
|
|
167
|
+
|
|
168
|
+
| Symbol (from `@blueprint-chart/lib`) | One-liner |
|
|
169
|
+
| --- | --- |
|
|
170
|
+
| `TransformNode` (type) | AST node for a `transform <name> { … }` block. |
|
|
171
|
+
| `DslNodeType.Transform` (enum value) | Discriminator on the AST union. |
|
|
172
|
+
|
|
173
|
+
The `editor` half (used internally by the editor app):
|
|
174
|
+
|
|
175
|
+
| Symbol (from the editor) | One-liner |
|
|
176
|
+
| --- | --- |
|
|
177
|
+
| `useDataTransforms()` / `useDataTransformsStore()` | Pipeline composable + raw store. |
|
|
178
|
+
| `TransformStep` (type) | `{ id, type, config }` — one row in the pipeline. |
|
|
179
|
+
| `TransformType` (enum) | `Sort \| Filter \| HideColumns \| Transpose \| Parse \| Rename \| GroupBy \| Computed`. |
|
|
180
|
+
| `TransformResult` (type) | `{ columns, rows, columnTypes }` — the value flowing through the chain. |
|
|
181
|
+
| `ParseOperation` / `parseOperations` | Catalogue of `Parse` operations and their accepted input types. |
|
|
182
|
+
| `NULL_VALUE` | Sentinel returned by `Parse` when a cell can't be coerced. |
|
|
183
|
+
|
|
184
|
+
> [!info] Public-API status
|
|
185
|
+
> The transform pipeline is currently shipped inside `@blueprint-chart/editor` and is consumed by the editor app. It is not re-exported from `@blueprint-chart/lib` today; if you need it in a non-editor consumer, copy `packages/editor/src/utils/transforms/*` or watch for a future move into `lib`.
|
|
186
|
+
|
|
187
|
+
## See also
|
|
188
|
+
|
|
189
|
+
- [BPC DSL — Transforms](/reference/dsl/scenes-and-transforms#transforms) for the source-level grammar.
|
|
190
|
+
- [Scenes guide](/guide/scenes) — scenes can replace a chart's data wholesale; transforms operate before that swap.
|
|
191
|
+
- [API reference](/reference/api/#dsl) for `TransformNode` and the converter helpers.
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: DSL Editor
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# DSL Editor
|
|
6
|
+
|
|
7
|
+
> The BPC source editor — CodeMirror 6, a Lezer-generated grammar, syntax highlighting tuned for prose-shaped charts, and two-way sync with the visual panels.
|
|
8
|
+
|
|
9
|
+
## Why this matters
|
|
10
|
+
|
|
11
|
+
Blueprint Chart is text-first. Every chart in the editor has a `.bpc` source under the hood, and any change you make through the visual panels — picking a palette, dragging an annotation, reordering scenes — is reflected back in that source. The DSL editor is the surface where authors who prefer code can stay in code, and the surface where a journalist swapping numbers can do it without ever opening a panel. CodeMirror 6 powers the editor; a separate Lezer grammar drives the syntax highlighting.
|
|
12
|
+
|
|
13
|
+
## Quickstart
|
|
14
|
+
|
|
15
|
+
Type `.bpc` directly in the editor's left pane. The right pane re-renders on every parse-clean keystroke:
|
|
16
|
+
|
|
17
|
+
```bpc
|
|
18
|
+
chart line {
|
|
19
|
+
title = "2024 was the hottest year on record"
|
|
20
|
+
description = "Deviation from the 1951–1980 average, in °C"
|
|
21
|
+
source = "NASA GISS"
|
|
22
|
+
sourceUrl = "https://data.giss.nasa.gov/gistemp/"
|
|
23
|
+
colors = "#e15759"
|
|
24
|
+
autoContrast = false
|
|
25
|
+
allowDarkMode = true
|
|
26
|
+
interpolation = "monotoneX"
|
|
27
|
+
lineSymbols = true
|
|
28
|
+
lineSymbolShape = "circle"
|
|
29
|
+
lineSymbolShowOn = "firstLast"
|
|
30
|
+
|
|
31
|
+
data {
|
|
32
|
+
"1980" = 0.26
|
|
33
|
+
"1985" = 0.12
|
|
34
|
+
"1990" = 0.45
|
|
35
|
+
"1995" = 0.45
|
|
36
|
+
"2000" = 0.42
|
|
37
|
+
"2005" = 0.68
|
|
38
|
+
"2010" = 0.72
|
|
39
|
+
"2015" = 0.9
|
|
40
|
+
"2020" = 1.02
|
|
41
|
+
"2024" = 1.29
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
annotation "2015" {
|
|
45
|
+
id = "2o3cx"
|
|
46
|
+
text = "2015 Paris Agreement to limit global warming to 1.5°C "
|
|
47
|
+
maxWidth = 224
|
|
48
|
+
showLine = true
|
|
49
|
+
lineStyle = curve-right
|
|
50
|
+
showArrow = true
|
|
51
|
+
showCircle = true
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
::: tip From the sample library
|
|
57
|
+
This is `packages/lib/src/samples/temperature-anomaly.bpc` — a good stress test for the highlighter because it mixes every common token class: keywords (`chart`, `data`, `annotation`), identifiers (`autoContrast`, `lineSymbolShape`), strings, numbers, enum values (`monotoneX`, `firstLast`, `curve-right`), and booleans. The fold ranges align with each top-level block.
|
|
58
|
+
:::
|
|
59
|
+
|
|
60
|
+
Line comments use `//` and stop at end-of-line. There are no block comments.
|
|
61
|
+
|
|
62
|
+
## How it works
|
|
63
|
+
|
|
64
|
+
The editor runs **two parsers** for the same DSL surface, and the trade-off is deliberate:
|
|
65
|
+
|
|
66
|
+
| Parser | Package | Job | Generation |
|
|
67
|
+
| --- | --- | --- | --- |
|
|
68
|
+
| Peggy | `@blueprint-chart/lib` | AST for chart computation at runtime — full semantics. | `make build-parser` |
|
|
69
|
+
| Lezer | `@blueprint-chart/editor` | Incremental parse tree for syntax highlighting. | `pnpm --filter @blueprint-chart/editor build:parser` |
|
|
70
|
+
|
|
71
|
+
Both grammars describe the same language. The Peggy one is the source of truth for correctness (parses, validates, round-trips); the Lezer one is built for fast incremental re-parse on every keystroke so the editor never blocks.
|
|
72
|
+
|
|
73
|
+
The CodeMirror 6 stack pulls in `@codemirror/view`, `@codemirror/state`, `@codemirror/language`, and `@lezer/highlight`. The DSL language module lives at `packages/editor/src/dsl-lang/` and exposes three entry points:
|
|
74
|
+
|
|
75
|
+
- `bpcLanguage()` — returns a CodeMirror `LanguageSupport` you install on the editor instance.
|
|
76
|
+
- `bpcHighlighter` — a `syntaxHighlighting` extension pre-wired to the class-based highlighter.
|
|
77
|
+
- `highlightDsl(code)` — one-shot server-style highlight, used by read-only previews; returns HTML.
|
|
78
|
+
|
|
79
|
+
The Lezer parser tags DSL keywords (`chart`, `data`, `colorize`, `highlight`, `areafill`, `annotation`, `range`, `note`, `series`, `scene`, `step`, `transform`) and lexical tokens (`Identifier`, `String`, `Number`, `Percentage`, `Equals`, `LineComment`, braces). Each tag maps to a token class in `packages/editor/src/dsl-lang/highlight.scss`, with separate light- and dark-mode rules driven by the `--bc-syn-*` CSS custom properties.
|
|
80
|
+
|
|
81
|
+
## Two-way sync between DSL and panels
|
|
82
|
+
|
|
83
|
+
The editor keeps the DSL text and the visual panels in lockstep through two composables:
|
|
84
|
+
|
|
85
|
+
- **`useDslSync`** — DSL → config and config → DSL. On every keystroke, a debounced parse runs; on success, the resulting AST is diffed against the `chartConfig` store and applied. The reverse direction kicks in when a user edits via a panel: the chart state is serialised back to DSL and pushed into the editor with a cursor-preserving patch.
|
|
86
|
+
- **`useDslOutput`** — formats the current chart state into its canonical DSL string. This is what the "Copy as DSL" button and the export panel consume.
|
|
87
|
+
|
|
88
|
+
Because the grammar round-trips cleanly (`parse(serialize(parse(x)))` is structurally equal to `parse(x)`), the sync loop is stable — a panel-driven change does not surprise the user with reformatted whitespace, and a DSL-driven change does not lose unknown property keys.
|
|
89
|
+
|
|
90
|
+
## Recipes
|
|
91
|
+
|
|
92
|
+
### Embed the BPC language in your own CodeMirror editor
|
|
93
|
+
|
|
94
|
+
`bpcLanguage()` and `bpcHighlighter` are plain CodeMirror 6 extensions — they fit into any consumer that already builds a CodeMirror view:
|
|
95
|
+
|
|
96
|
+
```ts
|
|
97
|
+
import { EditorView, basicSetup } from 'codemirror'
|
|
98
|
+
import { bpcLanguage, bpcHighlighter } from '@/dsl-lang'
|
|
99
|
+
|
|
100
|
+
const view = new EditorView({
|
|
101
|
+
doc: '',
|
|
102
|
+
extensions: [
|
|
103
|
+
basicSetup,
|
|
104
|
+
bpcLanguage(),
|
|
105
|
+
bpcHighlighter,
|
|
106
|
+
],
|
|
107
|
+
parent: document.querySelector('#editor')!,
|
|
108
|
+
})
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### Highlight a read-only DSL block
|
|
112
|
+
|
|
113
|
+
For a docs page, a "view source" tab, or a tooltip preview, `highlightDsl(code)` returns ready-to-inject HTML:
|
|
114
|
+
|
|
115
|
+
```ts
|
|
116
|
+
import { highlightDsl } from '@/dsl-lang'
|
|
117
|
+
|
|
118
|
+
const html = highlightDsl(`chart line-multi {
|
|
119
|
+
title = "Germany stagnated while the US and China bounced back"
|
|
120
|
+
description = "Annual percentage change in real GDP"
|
|
121
|
+
source = "IMF World Economic Outlook"
|
|
122
|
+
colorPalette = "SolLeWitt"
|
|
123
|
+
legend = false
|
|
124
|
+
tooltips = true
|
|
125
|
+
|
|
126
|
+
annotation "2020" {
|
|
127
|
+
text = "COVID-19 recession"
|
|
128
|
+
dy = -10
|
|
129
|
+
showArrow = true
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
data {
|
|
133
|
+
_series = "United States","China","Germany"
|
|
134
|
+
"2018" = 2.9,6.7,1.0
|
|
135
|
+
"2020" = -2.8,2.2,-3.7
|
|
136
|
+
"2024" = 2.8,5.0,-0.2
|
|
137
|
+
}
|
|
138
|
+
}`)
|
|
139
|
+
|
|
140
|
+
document.querySelector('#preview')!.innerHTML = html
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
::: tip From the sample library
|
|
144
|
+
The DSL string is trimmed from `packages/lib/src/samples/gdp-growth.bpc` (full sample has the 2018–2024 yearly series). Good stress-case for the highlighter — multi-series data, palette name, a negative number, an annotation block.
|
|
145
|
+
:::
|
|
146
|
+
|
|
147
|
+
This skips the editor surface entirely and is the right tool when the user shouldn't be able to type.
|
|
148
|
+
|
|
149
|
+
### Parse the editor's content programmatically
|
|
150
|
+
|
|
151
|
+
To act on the DSL outside the editor — validate, transform, or feed it to a renderer — pair `bpcLanguage()` with the lib's `parse()`:
|
|
152
|
+
|
|
153
|
+
```ts
|
|
154
|
+
import { parse } from '@blueprint-chart/lib'
|
|
155
|
+
|
|
156
|
+
try {
|
|
157
|
+
const ast = parse(view.state.doc.toString())
|
|
158
|
+
// ast.chartType, ast.properties, ast.data, ast.scenes …
|
|
159
|
+
} catch (err) {
|
|
160
|
+
// SyntaxError with 1-indexed location { line, column }
|
|
161
|
+
}
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
Errors are `SyntaxError` instances with a 1-indexed `location` field. CodeMirror's `lintGutter` extension is the natural place to surface them.
|
|
165
|
+
|
|
166
|
+
### Read the Lezer tree directly
|
|
167
|
+
|
|
168
|
+
Autocomplete is not exposed by default, but the Lezer parse tree is available if you want to write a completion source, a custom folding range provider, or any other syntax-driven feature:
|
|
169
|
+
|
|
170
|
+
```ts
|
|
171
|
+
import { syntaxTree } from '@codemirror/language'
|
|
172
|
+
|
|
173
|
+
const tree = syntaxTree(view.state)
|
|
174
|
+
tree.iterate({
|
|
175
|
+
enter(node) {
|
|
176
|
+
if (node.name === 'Property') {
|
|
177
|
+
// … node.from / node.to give you the document range
|
|
178
|
+
}
|
|
179
|
+
},
|
|
180
|
+
})
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
## Files in the package
|
|
184
|
+
|
|
185
|
+
The DSL language module ships inside `@blueprint-chart/editor`:
|
|
186
|
+
|
|
187
|
+
- `packages/editor/src/dsl-lang/bpc.grammar` — Lezer grammar source.
|
|
188
|
+
- `packages/editor/src/dsl-lang/bpc-parser.js` — generated Lezer parser (committed to the repo).
|
|
189
|
+
- `packages/editor/src/dsl-lang/build.mjs` — grammar build script.
|
|
190
|
+
- `packages/editor/src/dsl-lang/index.ts` — exports `bpcLanguage()`, `bpcHighlighter`, `highlightDsl()`.
|
|
191
|
+
- `packages/editor/src/dsl-lang/highlight.scss` — token-class colour definitions, driven by `--bc-syn-*` CSS variables.
|
|
192
|
+
|
|
193
|
+
## API surface
|
|
194
|
+
|
|
195
|
+
The DSL editor entry points live in `@blueprint-chart/editor`, not in `@blueprint-chart/lib`. From the lib side, the parsing surface is:
|
|
196
|
+
|
|
197
|
+
| Symbol (from `@blueprint-chart/lib`) | One-liner |
|
|
198
|
+
| --- | --- |
|
|
199
|
+
| `parse(source)` | BPC text → AST. Throws `SyntaxError` with `location`. |
|
|
200
|
+
| `serialize(ast)` | AST → BPC text (pretty-printed). |
|
|
201
|
+
| `compactSerialize(ast)` | AST → BPC text (whitespace-minimised). |
|
|
202
|
+
| `propertyMap` | Catalogue of recognised property keys per chart type. |
|
|
203
|
+
| `ChartNode`, `PropertyNode`, `DataNode`, … | AST node types — see [the API reference](/reference/api/#dsl). |
|
|
204
|
+
|
|
205
|
+
From the editor side:
|
|
206
|
+
|
|
207
|
+
| Symbol (from the editor's `@/dsl-lang`) | One-liner |
|
|
208
|
+
| --- | --- |
|
|
209
|
+
| `bpcLanguage()` | CodeMirror `LanguageSupport` for `.bpc`. |
|
|
210
|
+
| `bpcHighlighter` | `syntaxHighlighting` extension. |
|
|
211
|
+
| `highlightDsl(code)` | One-shot HTML highlight for read-only previews. |
|
|
212
|
+
|
|
213
|
+
## See also
|
|
214
|
+
|
|
215
|
+
- [BPC DSL specification](/reference/dsl/) — the canonical language reference.
|
|
216
|
+
- [Scenes guide](/guide/scenes) — scene syntax inside the DSL.
|
|
217
|
+
- [Data transforms guide](/guide/data-transforms) — transforms inside the DSL.
|
|
218
|
+
- [API reference](/reference/api/#dsl) for the lib's `parse` / `serialize` / converter helpers.
|