Files
astrolabe/src/core/theme-preview-specs.ts
T

263 lines
7.3 KiB
TypeScript

/**
* Theme Builder gallery specs (docs/chart-theming-scope.md §4.4).
*
* A fixed set of small, self-contained Vega-Lite specs the Theme Builder
* renders side by side with the draft config, so an edit is previewed across
* every chart surface a config styles: titles and subtitles, axes and grids,
* facet headers, the major mark types, and — so every color control has a
* mirror — each color family: categorical (`range.category`), sequential
* (`range.heatmap` for rect, `range.ramp` for a continuous legend), and
* diverging (`range.diverging`, selected by a quantitative color scale with a
* `domainMid`). Inline data only, compact fixed sizes — swatches, not analyses.
*/
import type { JsonObject } from './spec-config';
const SCHEMA = 'https://vega.github.io/schema/vega-lite/v6.json';
export interface ThemePreviewSpec {
/** Stable key for React lists and test assertions. */
id: string;
/** What surface this card exercises (shown as the card caption). */
caption: string;
spec: JsonObject;
}
const bar: ThemePreviewSpec = {
id: 'bar',
caption: 'Bar — title, axes, grid',
spec: {
$schema: SCHEMA,
title: 'Revenue by region',
width: 200,
height: 140,
data: {
values: [
{ region: 'North', revenue: 42 },
{ region: 'South', revenue: 61 },
{ region: 'East', revenue: 28 },
{ region: 'West', revenue: 55 },
{ region: 'Central', revenue: 47 },
],
},
mark: 'bar',
encoding: {
x: { field: 'region', type: 'nominal', axis: { labelAngle: 0 } },
y: { field: 'revenue', type: 'quantitative' },
},
},
};
const line: ThemePreviewSpec = {
id: 'line',
caption: 'Line — subtitle, series legend',
spec: {
$schema: SCHEMA,
title: { text: 'Signups over time', subtitle: 'Weekly, by plan' },
width: 200,
height: 140,
data: {
values: [
{ week: 1, plan: 'Free', n: 20 },
{ week: 2, plan: 'Free', n: 28 },
{ week: 3, plan: 'Free', n: 26 },
{ week: 4, plan: 'Free', n: 34 },
{ week: 1, plan: 'Pro', n: 8 },
{ week: 2, plan: 'Pro', n: 11 },
{ week: 3, plan: 'Pro', n: 17 },
{ week: 4, plan: 'Pro', n: 21 },
{ week: 1, plan: 'Team', n: 3 },
{ week: 2, plan: 'Team', n: 4 },
{ week: 3, plan: 'Team', n: 9 },
{ week: 4, plan: 'Team', n: 12 },
],
},
mark: { type: 'line', point: true },
encoding: {
x: { field: 'week', type: 'quantitative', axis: { tickCount: 4 } },
y: { field: 'n', type: 'quantitative' },
color: { field: 'plan', type: 'nominal' },
},
},
};
const area: ThemePreviewSpec = {
id: 'area',
caption: 'Stacked area — categorical palette',
spec: {
$schema: SCHEMA,
width: 200,
height: 140,
data: {
values: [
{ q: 1, channel: 'Web', v: 30 },
{ q: 2, channel: 'Web', v: 36 },
{ q: 3, channel: 'Web', v: 41 },
{ q: 4, channel: 'Web', v: 38 },
{ q: 1, channel: 'Store', v: 22 },
{ q: 2, channel: 'Store', v: 19 },
{ q: 3, channel: 'Store', v: 24 },
{ q: 4, channel: 'Store', v: 27 },
{ q: 1, channel: 'Partner', v: 12 },
{ q: 2, channel: 'Partner', v: 16 },
{ q: 3, channel: 'Partner', v: 14 },
{ q: 4, channel: 'Partner', v: 18 },
],
},
mark: 'area',
encoding: {
x: { field: 'q', type: 'quantitative', axis: { tickCount: 4 } },
y: { field: 'v', type: 'quantitative' },
color: { field: 'channel', type: 'nominal' },
},
},
};
const scatter: ThemePreviewSpec = {
id: 'scatter',
caption: 'Scatter — gradient legend',
spec: {
$schema: SCHEMA,
width: 200,
height: 140,
data: {
values: [
{ x: 4, y: 7, z: 12 },
{ x: 8, y: 3, z: 31 },
{ x: 12, y: 11, z: 45 },
{ x: 16, y: 6, z: 22 },
{ x: 20, y: 14, z: 60 },
{ x: 24, y: 9, z: 38 },
{ x: 28, y: 17, z: 74 },
{ x: 32, y: 12, z: 51 },
{ x: 36, y: 20, z: 88 },
],
},
mark: { type: 'point', filled: true, size: 80 },
encoding: {
x: { field: 'x', type: 'quantitative' },
y: { field: 'y', type: 'quantitative' },
color: { field: 'z', type: 'quantitative' },
},
},
};
const heatmap: ThemePreviewSpec = {
id: 'heatmap',
caption: 'Heatmap — sequential scale',
spec: {
$schema: SCHEMA,
width: 200,
height: 140,
data: {
values: ['Mon', 'Tue', 'Wed', 'Thu'].flatMap((day, d) =>
['AM', 'Noon', 'PM'].map((slot, s) => ({ day, slot, v: (d + 1) * (s + 2) * 3 })),
),
},
mark: 'rect',
encoding: {
x: { field: 'day', type: 'nominal', axis: { labelAngle: 0 } },
y: { field: 'slot', type: 'nominal' },
color: { field: 'v', type: 'quantitative' },
},
},
};
const diverging: ThemePreviewSpec = {
id: 'diverging',
caption: 'Diverging — scale around a midpoint',
spec: {
$schema: SCHEMA,
title: 'Net change by topic',
width: 200,
height: 140,
data: {
values: [
{ topic: 'Cost', delta: -8 },
{ topic: 'Speed', delta: -3 },
{ topic: 'Help', delta: 1 },
{ topic: 'Look', delta: 6 },
{ topic: 'Value', delta: 11 },
],
},
mark: 'bar',
encoding: {
x: { field: 'topic', type: 'nominal', axis: { labelAngle: 0 } },
y: { field: 'delta', type: 'quantitative' },
// `domainMid` makes this a diverging color scale, so it reads from
// `range.diverging` rather than `range.ramp`/`heatmap` (the others above).
color: {
field: 'delta',
type: 'quantitative',
scale: { domainMid: 0 },
legend: { title: null },
},
},
},
};
const donut: ThemePreviewSpec = {
id: 'donut',
caption: 'Donut — palette, symbol legend',
spec: {
$schema: SCHEMA,
width: 200,
height: 140,
data: {
values: [
{ browser: 'Firefox', share: 32 },
{ browser: 'Chrome', share: 41 },
{ browser: 'Safari', share: 18 },
{ browser: 'Other', share: 9 },
],
},
mark: { type: 'arc', innerRadius: 32 },
encoding: {
theta: { field: 'share', type: 'quantitative' },
color: { field: 'browser', type: 'nominal' },
},
},
};
const facet: ThemePreviewSpec = {
id: 'facet',
caption: 'Facets — header labels',
spec: {
$schema: SCHEMA,
width: 70,
height: 110,
data: {
values: [
{ team: 'Alpha', month: 'Jan', v: 14 },
{ team: 'Alpha', month: 'Feb', v: 21 },
{ team: 'Alpha', month: 'Mar', v: 17 },
{ team: 'Beta', month: 'Jan', v: 9 },
{ team: 'Beta', month: 'Feb', v: 13 },
{ team: 'Beta', month: 'Mar', v: 19 },
{ team: 'Gamma', month: 'Jan', v: 11 },
{ team: 'Gamma', month: 'Feb', v: 8 },
{ team: 'Gamma', month: 'Mar', v: 15 },
],
},
mark: 'bar',
encoding: {
x: { field: 'month', type: 'nominal', axis: { labelAngle: 0 } },
y: { field: 'v', type: 'quantitative' },
column: { field: 'team', type: 'nominal' },
},
},
};
/** The gallery, in display order — the three continuous-color examples
* (scatter → ramp, heatmap → heatmap, diverging → diverging) sit together. */
export const THEME_PREVIEW_SPECS: ReadonlyArray<ThemePreviewSpec> = [
bar,
line,
area,
scatter,
heatmap,
diverging,
donut,
facet,
];