mirror of
https://github.com/olehomelchenko/astrolabe.git
synced 2026-08-08 02:02:33 +00:00
263 lines
7.3 KiB
TypeScript
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,
|
|
];
|