Skip to content

Commit 1157043

Browse files
committed
feat(tilemap): add US state tile grid map visualization
Add TileMapSpec as a new top-level spec type across all packages, following the Sankey implementation pattern end-to-end. Supports record-map data ({ CA: 5.4, TX: 4.1 }) and tabular DataRow[] input. 50 states + DC rendered as a square tile grid with sequential color scale (blue, green, orange, purple palettes), gradient legend, dark mode with palette reversal, tooltips, responsive resize, and export. Refactors LegendLayout into a discriminated union (CategoricalLegendLayout | GradientLegendLayout) with backwards- compatible optional type field. Includes engine compilation tests, vanilla mount tests, and Ladle stories with multiple variants (partial data, dark mode, palettes, chrome, tabular encoding, compact layout).
1 parent a5eb628 commit 1157043

35 files changed

Lines changed: 3010 additions & 39 deletions
Lines changed: 294 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,294 @@
1+
/**
2+
* Tile map stories.
3+
*
4+
* Demonstrates US state tile grid maps with sequential color scales,
5+
* record-map and tabular data formats, palettes, dark mode, chrome,
6+
* and compact layouts. Data reflects realistic magnitudes.
7+
*/
8+
9+
import type { TileMapSpec } from '@opendata-ai/openchart-core';
10+
import { TileMap } from '@opendata-ai/openchart-react';
11+
12+
// ---------------------------------------------------------------------------
13+
// Shared data: US unemployment rates by state (%)
14+
// ---------------------------------------------------------------------------
15+
16+
const unemploymentData: Record<string, number> = {
17+
AL: 2.7,
18+
AK: 6.4,
19+
AZ: 3.5,
20+
AR: 3.4,
21+
CA: 5.4,
22+
CO: 3.4,
23+
CT: 4.1,
24+
DE: 4.4,
25+
FL: 3.3,
26+
GA: 3.4,
27+
HI: 3.2,
28+
ID: 3.0,
29+
IL: 4.6,
30+
IN: 3.3,
31+
IA: 2.7,
32+
KS: 3.2,
33+
KY: 4.4,
34+
LA: 3.6,
35+
ME: 3.6,
36+
MD: 1.8,
37+
MA: 3.3,
38+
MI: 4.2,
39+
MN: 2.8,
40+
MS: 3.7,
41+
MO: 3.5,
42+
MT: 2.9,
43+
NE: 2.2,
44+
NV: 5.4,
45+
NH: 2.4,
46+
NJ: 4.8,
47+
NM: 4.1,
48+
NY: 4.5,
49+
NC: 3.5,
50+
ND: 1.9,
51+
OH: 4.0,
52+
OK: 3.9,
53+
OR: 4.2,
54+
PA: 3.4,
55+
RI: 3.8,
56+
SC: 3.3,
57+
SD: 2.0,
58+
TN: 3.5,
59+
TX: 4.1,
60+
UT: 2.9,
61+
VT: 2.3,
62+
VA: 2.9,
63+
WA: 4.6,
64+
WV: 4.0,
65+
WI: 2.9,
66+
WY: 3.2,
67+
DC: 5.2,
68+
};
69+
70+
// ---------------------------------------------------------------------------
71+
// Unemployment Rate: record-map data, all 51 states
72+
// ---------------------------------------------------------------------------
73+
74+
const unemploymentSpec: TileMapSpec = {
75+
type: 'tilemap',
76+
data: unemploymentData,
77+
valueFormat: '.1f',
78+
chrome: {
79+
title: 'US Unemployment Rate',
80+
subtitle: 'By state, seasonally adjusted, %',
81+
source: 'Bureau of Labor Statistics',
82+
},
83+
animation: true,
84+
};
85+
86+
export const UnemploymentRate = () => (
87+
<div className="story-chart story-h-420">
88+
<TileMap spec={unemploymentSpec} />
89+
</div>
90+
);
91+
92+
// ---------------------------------------------------------------------------
93+
// Partial Data: 10 states to show missing-data handling
94+
// ---------------------------------------------------------------------------
95+
96+
const partialSpec: TileMapSpec = {
97+
type: 'tilemap',
98+
data: {
99+
CA: 5.4,
100+
TX: 4.1,
101+
NY: 4.5,
102+
FL: 3.3,
103+
IL: 4.6,
104+
WA: 4.6,
105+
MA: 3.3,
106+
GA: 3.4,
107+
OH: 4.0,
108+
MI: 4.2,
109+
},
110+
valueFormat: '.1f',
111+
chrome: {
112+
title: 'Partial State Coverage',
113+
subtitle: 'Only 10 states with data, remaining show as missing',
114+
},
115+
};
116+
117+
export const PartialData = () => (
118+
<div className="story-chart story-h-420">
119+
<TileMap spec={partialSpec} />
120+
</div>
121+
);
122+
123+
// ---------------------------------------------------------------------------
124+
// Dark Mode: forced dark variant
125+
// ---------------------------------------------------------------------------
126+
127+
const darkModeSpec: TileMapSpec = {
128+
...unemploymentSpec,
129+
darkMode: 'force',
130+
chrome: {
131+
title: 'US Unemployment Rate (Dark)',
132+
subtitle: 'Dark mode variant of the unemployment map',
133+
source: 'Bureau of Labor Statistics',
134+
},
135+
};
136+
137+
export const DarkMode = () => (
138+
<div className="story-chart story-h-420">
139+
<TileMap spec={darkModeSpec} />
140+
</div>
141+
);
142+
143+
// ---------------------------------------------------------------------------
144+
// Green Palette: population data with green sequential scale
145+
// ---------------------------------------------------------------------------
146+
147+
const populationData: Record<string, number> = {
148+
AL: 5.1,
149+
AK: 0.7,
150+
AZ: 7.4,
151+
AR: 3.0,
152+
CA: 39.0,
153+
CO: 5.8,
154+
CT: 3.6,
155+
DE: 1.0,
156+
FL: 22.2,
157+
GA: 10.9,
158+
HI: 1.4,
159+
ID: 1.9,
160+
IL: 12.5,
161+
IN: 6.8,
162+
IA: 3.2,
163+
KS: 2.9,
164+
KY: 4.5,
165+
LA: 4.6,
166+
ME: 1.4,
167+
MD: 6.2,
168+
MA: 7.0,
169+
MI: 10.0,
170+
MN: 5.7,
171+
MS: 2.9,
172+
MO: 6.2,
173+
MT: 1.1,
174+
NE: 2.0,
175+
NV: 3.2,
176+
NH: 1.4,
177+
NJ: 9.3,
178+
NM: 2.1,
179+
NY: 19.5,
180+
NC: 10.7,
181+
ND: 0.8,
182+
OH: 11.8,
183+
OK: 4.0,
184+
OR: 4.2,
185+
PA: 12.8,
186+
RI: 1.1,
187+
SC: 5.3,
188+
SD: 0.9,
189+
TN: 7.1,
190+
TX: 30.0,
191+
UT: 3.4,
192+
VT: 0.6,
193+
VA: 8.6,
194+
WA: 7.7,
195+
WV: 1.8,
196+
WI: 5.9,
197+
WY: 0.6,
198+
DC: 0.7,
199+
};
200+
201+
const greenPaletteSpec: TileMapSpec = {
202+
type: 'tilemap',
203+
data: populationData,
204+
palette: 'green',
205+
valueFormat: '.1f',
206+
chrome: {
207+
title: 'US Population by State',
208+
subtitle: 'Estimated population in millions',
209+
source: 'U.S. Census Bureau',
210+
},
211+
};
212+
213+
export const GreenPalette = () => (
214+
<div className="story-chart story-h-420">
215+
<TileMap spec={greenPaletteSpec} />
216+
</div>
217+
);
218+
219+
// ---------------------------------------------------------------------------
220+
// Tabular Data: DataRow[] format with explicit encoding
221+
// ---------------------------------------------------------------------------
222+
223+
const tabularData = [
224+
{ code: 'CA', rate: 5.4 },
225+
{ code: 'TX', rate: 4.1 },
226+
{ code: 'NY', rate: 4.5 },
227+
{ code: 'FL', rate: 3.3 },
228+
{ code: 'IL', rate: 4.6 },
229+
{ code: 'PA', rate: 3.4 },
230+
{ code: 'OH', rate: 4.0 },
231+
{ code: 'GA', rate: 3.4 },
232+
{ code: 'NC', rate: 3.5 },
233+
{ code: 'MI', rate: 4.2 },
234+
];
235+
236+
const tabularSpec: TileMapSpec = {
237+
type: 'tilemap',
238+
data: tabularData,
239+
encoding: {
240+
state: { field: 'code', type: 'nominal' },
241+
value: { field: 'rate', type: 'quantitative' },
242+
},
243+
valueFormat: '.1f',
244+
chrome: {
245+
title: 'Tabular Data Format',
246+
subtitle: 'Using DataRow[] with explicit encoding channels',
247+
},
248+
};
249+
250+
export const TabularData = () => (
251+
<div className="story-chart story-h-420">
252+
<TileMap spec={tabularSpec} />
253+
</div>
254+
);
255+
256+
// ---------------------------------------------------------------------------
257+
// With Chrome: full editorial chrome
258+
// ---------------------------------------------------------------------------
259+
260+
const withChromeSpec: TileMapSpec = {
261+
type: 'tilemap',
262+
data: unemploymentData,
263+
valueFormat: '.1f',
264+
chrome: {
265+
title: 'US Unemployment Rate',
266+
subtitle: 'By state, seasonally adjusted, %',
267+
source: 'Bureau of Labor Statistics',
268+
byline: 'Data as of March 2025',
269+
},
270+
};
271+
272+
export const WithChrome = () => (
273+
<div className="story-chart story-h-420">
274+
<TileMap spec={withChromeSpec} />
275+
</div>
276+
);
277+
278+
// ---------------------------------------------------------------------------
279+
// Compact: narrow width variant for responsive testing
280+
// ---------------------------------------------------------------------------
281+
282+
const compactSpec: TileMapSpec = {
283+
...unemploymentSpec,
284+
chrome: {
285+
title: 'Unemployment Rate',
286+
subtitle: 'Compact layout at 360px',
287+
},
288+
};
289+
290+
export const Compact = () => (
291+
<div className="story-chart story-h-420" style={{ maxWidth: '360px' }}>
292+
<TileMap spec={compactSpec} />
293+
</div>
294+
);

packages/core/src/helpers/spec-builders.ts

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@ import type {
2222
MarkType,
2323
TableSpec,
2424
ThemeConfig,
25+
TileMapEncoding,
26+
TileMapPalette,
27+
TileMapSpec,
2528
} from '../types/spec';
2629
import type { ColumnConfig } from '../types/table';
2730

@@ -409,3 +412,52 @@ export function dataTable(data: DataRow[], options?: TableBuilderOptions): Table
409412

410413
return spec;
411414
}
415+
416+
// ---------------------------------------------------------------------------
417+
// Tile map builder
418+
// ---------------------------------------------------------------------------
419+
420+
/** Options for the tileMap convenience builder. */
421+
export interface TileMapBuilderOptions {
422+
/** Encoding channels (required when data is DataRow[]). */
423+
encoding?: TileMapEncoding;
424+
/** Sequential color palette name. */
425+
palette?: TileMapPalette;
426+
/** Editorial chrome. */
427+
chrome?: Chrome;
428+
/** Theme overrides. */
429+
theme?: ThemeConfig;
430+
/** Dark mode setting. */
431+
darkMode?: DarkMode;
432+
/** d3-format string for value formatting. */
433+
valueFormat?: string;
434+
}
435+
436+
/**
437+
* Build a TileMapSpec from a state-to-value record or tabular data.
438+
*
439+
* @example Record map (simplest):
440+
* ```ts
441+
* tileMap({ CA: 4.4, TX: 17.6, NY: 4.5 })
442+
* ```
443+
*
444+
* @example Tabular data with encoding:
445+
* ```ts
446+
* tileMap(data, { encoding: { state: { field: 'code' }, value: { field: 'rate' } } })
447+
* ```
448+
*/
449+
export function tileMap(
450+
data: Record<string, number | null> | DataRow[],
451+
options?: TileMapBuilderOptions,
452+
): TileMapSpec {
453+
return {
454+
type: 'tilemap' as const,
455+
data,
456+
...(options?.encoding && { encoding: options.encoding }),
457+
...(options?.palette && { palette: options.palette }),
458+
...(options?.chrome && { chrome: options.chrome }),
459+
...(options?.theme && { theme: options.theme }),
460+
...(options?.darkMode && { darkMode: options.darkMode }),
461+
...(options?.valueFormat && { valueFormat: options.valueFormat }),
462+
};
463+
}

packages/core/src/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,7 @@ export type {
151151
ChartBuilderOptions,
152152
FieldRef,
153153
TableBuilderOptions,
154+
TileMapBuilderOptions,
154155
} from './helpers/spec-builders';
155156
export {
156157
areaChart,
@@ -163,4 +164,5 @@ export {
163164
lineChart,
164165
pieChart,
165166
scatterChart,
167+
tileMap,
166168
} from './helpers/spec-builders';

0 commit comments

Comments
 (0)