Skip to content

Commit 1b49b94

Browse files
CopilotCopilot
andcommitted
Sync agents-chart source from data-formulator
Update all library and test-data files to match the latest data-formulator agents-chart source. Key changes: - Add gallery/ module (regional-survey + BI/KPI card tests) - Add encoding-actions.ts, encoding-overrides.ts to core - Add bar-table and kpi-card vegalite templates - Add gallery-tree navigation structure in test-data - Update line chart behavior (quantitative color uses direct encoding instead of gray-line+points layer pattern) - Add docs/ directory with design documentation - Update smoke tests to match new behavior Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent cacc16d commit 1b49b94

73 files changed

Lines changed: 8957 additions & 721 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

package.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,11 @@
6464
"types": "./dist/test-data/index.d.ts",
6565
"import": "./dist/test-data/index.js",
6666
"require": "./dist/test-data/index.cjs"
67+
},
68+
"./gallery": {
69+
"types": "./dist/gallery/index.d.ts",
70+
"import": "./dist/gallery/index.js",
71+
"require": "./dist/gallery/index.cjs"
6772
}
6873
},
6974
"files": [

src/README.md

Lines changed: 292 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,292 @@
1+
# agents-chart
2+
3+
A semantic-level visualization library that compiles data + semantic annotations
4+
into chart specifications for multiple rendering backends. The LLM outputs only
5+
chart type, field assignments, and a **semantic type** per field (e.g. `Revenue`,
6+
`Rank`, `CategoryCode`). A deterministic compiler derives all low-level
7+
parameters — sizing, zero-baseline, formatting, color schemes, and mark
8+
templates — so charts look good *and* stay editable without calling the LLM again.
9+
10+
Pure TypeScript · No UI framework dependencies · Data-in, spec-out
11+
12+
> For full motivation & comparisons, see [docs/story.md](docs/story.md).
13+
> For architecture details, see [docs/design_v3.md](docs/design_v3.md).
14+
15+
---
16+
17+
## Why
18+
19+
LLM-generated chart specs face a dilemma:
20+
21+
| Approach | Looks good | Editable | Bespoke charts | Cost to re-encode |
22+
|----------|:---:|:---:|:---:|:---:|
23+
| Library defaults |||| 0 |
24+
| LLM-tuned spec ||| Sometimes | 1 LLM call |
25+
| **agents-chart** | **** | **** | **** | **0** |
26+
27+
**Simple specs** are editable but look bad (wrong sizing, misleading
28+
encodings). **Polished specs** look great but are brittle (hard-coded
29+
values break on every field swap). agents-chart resolves this: when a user
30+
swaps fields, changes chart type, or adds facets for exploration, the
31+
compiler re-derives all parameters automatically — no LLM call needed.
32+
33+
Because the output is native library code (Vega-Lite, ECharts, or Chart.js),
34+
users retain full control over aesthetic fine-tuning — fonts, colors, legends,
35+
annotations — using each library's own API. There is no abstraction tax or
36+
reduced expressiveness.
37+
38+
### Key insight: semantic types as the contract
39+
40+
Instead of asking the LLM to set dozens of low-level parameters, we ask
41+
it one thing: **what does this data mean?** — expressed as a semantic type.
42+
43+
```
44+
Semantic type (e.g. "Revenue")
45+
├── Encoding type: quantitative
46+
├── Zero baseline: true
47+
├── Domain padding: 0%
48+
├── Scale direction: normal
49+
├── Axis formatting: "$,.0f"
50+
├── Color scheme: sequential
51+
└── Sizing model: per-axis stretch
52+
```
53+
54+
When the user swaps a field, the compiler re-derives everything from the new
55+
semantic type. No hard-coded constants go stale. No LLM call needed.
56+
57+
### The workflow
58+
59+
```
60+
1. LLM generates: chart type + semantic types (~10-line JSON)
61+
2. User edits: swap field / change mark / add facet → compiler handles it (no AI)
62+
3. Fine-tune (2%): edit the generated spec directly for bespoke styling
63+
```
64+
65+
---
66+
67+
## Quick start
68+
69+
### Vega-Lite
70+
71+
```ts
72+
import { assembleVegaLite } from './lib/agents-chart';
73+
74+
const spec = assembleVegaLite({
75+
data: { values: myData },
76+
semantic_types: { weight: 'Quantity', mpg: 'Quantity', origin: 'Country' },
77+
chart_spec: {
78+
chartType: 'Scatter Plot',
79+
encodings: { x: { field: 'weight' }, y: { field: 'mpg' }, color: { field: 'origin' } },
80+
canvasSize: { width: 400, height: 300 },
81+
},
82+
});
83+
```
84+
85+
### ECharts
86+
87+
```ts
88+
import { assembleECharts } from './lib/agents-chart';
89+
90+
const option = assembleECharts({
91+
data: { values: myData },
92+
semantic_types: { weight: 'Quantity', mpg: 'Quantity' },
93+
chart_spec: {
94+
chartType: 'Scatter Plot',
95+
encodings: { x: { field: 'weight' }, y: { field: 'mpg' } },
96+
},
97+
});
98+
```
99+
100+
### Chart.js
101+
102+
```ts
103+
import { assembleChartjs } from './lib/agents-chart';
104+
105+
const config = assembleChartjs({
106+
data: { values: myData },
107+
semantic_types: { weight: 'Quantity' },
108+
chart_spec: { chartType: 'Bar Chart', encodings: { x: { field: 'category' }, y: { field: 'value' } } },
109+
});
110+
```
111+
112+
---
113+
114+
## Architecture
115+
116+
```
117+
index.ts ← public API (re-exports core/ + all backends)
118+
119+
core/ ← target-language-agnostic
120+
types.ts ← shared type definitions (ChartAssemblyInput, ChartTemplateDef, …)
121+
semantic-types.ts ← ~70 semantic types + VisCategory helpers
122+
decisions.ts ← pure decision functions (layout, encoding type)
123+
resolve-semantics.ts ← Phase 0: semantic resolution
124+
compute-layout.ts ← Phase 1: layout computation
125+
filter-overflow.ts ← overflow filtering
126+
127+
vegalite/ ← Vega-Lite backend
128+
assemble.ts ← assembleVegaLite() orchestrator
129+
instantiate-spec.ts ← Phase 2: VL spec instantiation
130+
templates/ ← chart templates (bar, scatter, bump, …)
131+
132+
echarts/ ← ECharts backend
133+
assemble.ts ← assembleECharts() orchestrator
134+
instantiate-spec.ts ← Phase 2: EC option instantiation
135+
templates/ ← chart templates
136+
137+
chartjs/ ← Chart.js backend
138+
assemble.ts ← assembleChartjs() orchestrator
139+
instantiate-spec.ts ← Phase 2: CJS config instantiation
140+
templates/ ← chart templates
141+
```
142+
143+
### Type resolution pipeline
144+
145+
```
146+
semantic type → getVisCategory() → VisCategory → channel/chart rules → encoding type
147+
148+
(fallback: inferVisCategory() inspects raw data)
149+
```
150+
151+
---
152+
153+
## Public API
154+
155+
### Assembly functions
156+
157+
Each backend has its own assembly function. All accept the same
158+
`ChartAssemblyInput` shape:
159+
160+
| Function | Output | Import |
161+
|----------|--------|--------|
162+
| `assembleVegaLite(input)` | Vega-Lite spec | `import { assembleVegaLite } from './lib/agents-chart'` |
163+
| `assembleECharts(input)` | ECharts option object | `import { assembleECharts } from './lib/agents-chart'` |
164+
| `assembleChartjs(input)` | Chart.js config object | `import { assembleChartjs } from './lib/agents-chart'` |
165+
166+
### Input types
167+
168+
```ts
169+
interface ChartAssemblyInput {
170+
data: { values: any[] } | { url: string }; // inline rows or URL
171+
semantic_types?: Record<string, string>; // field → semantic type
172+
chart_spec: {
173+
chartType: string; // e.g. "Scatter Plot"
174+
encodings: Record<string, ChartEncoding>; // channel → encoding map
175+
canvasSize?: { width: number; height: number }; // default 400×320
176+
chartProperties?: Record<string, any>; // template-specific knobs
177+
};
178+
options?: AssembleOptions; // layout tuning
179+
}
180+
```
181+
182+
| Key | Description |
183+
|---|---|
184+
| `data` | Data source — either `{ values: [...] }` (inline row objects) or `{ url: "..." }` (JSON/CSV URL) |
185+
| `semantic_types` | Per-column semantic annotations (e.g., `{ revenue: "Price", country: "Country" }`) |
186+
| `chart_spec` | What to draw — chart type, encodings, canvas size, properties |
187+
| `options` | Layout tuning (elasticity, step sizes, tooltips, etc.) |
188+
189+
```ts
190+
interface ChartEncoding {
191+
field?: string;
192+
type?: 'quantitative' | 'nominal' | 'ordinal' | 'temporal';
193+
aggregate?: 'count' | 'sum' | 'average';
194+
sortOrder?: 'ascending' | 'descending';
195+
sortBy?: string;
196+
scheme?: string;
197+
}
198+
199+
interface AssembleOptions {
200+
addTooltips?: boolean; // default false
201+
elasticity?: number; // axis stretch exponent (default 0.5)
202+
maxStretch?: number; // axis stretch cap (default 2)
203+
facetElasticity?: number; // facet stretch exponent (default 0.3)
204+
maxStretch?: number; // unified stretch cap (default 2)
205+
minStep?: number; // min px per discrete tick (default 6)
206+
minSubplotSize?: number; // min facet subplot px (default 60)
207+
}
208+
```
209+
210+
### Template registries
211+
212+
Each backend has its own set of supported chart types and template
213+
definitions. Templates are organized by category and can be looked up by
214+
chart type name.
215+
216+
| Backend | Template map | Flat list | Lookup | Channels |
217+
|---------|-------------|-----------|--------|----------|
218+
| Vega-Lite | `vlTemplateDefs` | `vlAllTemplateDefs` | `vlGetTemplateDef(name)` | `vlGetTemplateChannels(name)` |
219+
| ECharts | `ecTemplateDefs` | `ecAllTemplateDefs` | `ecGetTemplateDef(name)` | `ecGetTemplateChannels(name)` |
220+
| Chart.js | `cjsTemplateDefs` | `cjsAllTemplateDefs` | `cjsGetTemplateDef(name)` | `cjsGetTemplateChannels(name)` |
221+
222+
```ts
223+
// Example: list available Vega-Lite chart categories
224+
import { vlTemplateDefs } from './lib/agents-chart';
225+
Object.keys(vlTemplateDefs); // ["Scatter & Point", "Bar", "Line & Area", ...]
226+
227+
// Example: get channels for a specific chart type
228+
import { vlGetTemplateChannels } from './lib/agents-chart';
229+
vlGetTemplateChannels('Scatter Plot'); // ["x", "y", "color", "size", "shape"]
230+
```
231+
232+
### Semantic types (~70 types)
233+
234+
| Group | Examples |
235+
|-------|---------|
236+
| Temporal | `DateTime`, `Date`, `Year`, `Month` |
237+
| Measures | `Quantity`, `Count`, `Price`, `Percentage` |
238+
| Discrete numerics | `Rank`, `Score`, `ID` |
239+
| Geographic | `Latitude`, `Longitude`, `Country`, `City` |
240+
| Categorical | `PersonName`, `Company`, `Status`, `Boolean` |
241+
| Ranges | `Range`, `AgeGroup`, `Bucket` |
242+
| Fallbacks | `String`, `Number`, `Unknown` |
243+
244+
### Core utilities (shared across backends)
245+
246+
These are re-exported from `core/` and available at the top level:
247+
248+
```ts
249+
import {
250+
// Semantic type helpers
251+
inferVisCategory, // infer VisCategory from raw data
252+
getVisCategory, // look up VisCategory for a known semantic type
253+
254+
// Shared types
255+
type ChartAssemblyInput,
256+
type ChartEncoding,
257+
type ChartTemplateDef,
258+
type AssembleOptions,
259+
type ChartWarning,
260+
261+
// Layout constants
262+
channels,
263+
channelGroups,
264+
} from './lib/agents-chart';
265+
```
266+
267+
---
268+
269+
## What the compiler handles automatically
270+
271+
- **Sizing** — spring model for discrete axes, pressure model for continuous;
272+
composable with facets and layers. No more 6400 px charts from 80 × 4 facets.
273+
- **Zero baseline** — Revenue → include zero; Temperature → don't; Rank → don't.
274+
- **Scale direction** — Rank → reversed; others → normal.
275+
- **Formatting** — Revenue → `$,.0f`; Percentage → `.0%`; Year → `%Y`.
276+
- **Color schemes** — categorical codes → distinct hues; measures → sequential.
277+
- **Label overflow** — auto-rotation and truncation from count + string lengths.
278+
- **Bespoke marks** — lollipops, bump charts, candlesticks as single templates.
279+
- **Semantic validation** — actionable errors before rendering, not after crashing.
280+
281+
## Design principles
282+
283+
1. **No UI dependencies** — pure data-in, spec-out.
284+
2. **Semantic types drive everything** — the caller annotates fields; the
285+
compiler derives all config. Fallback: `inferVisCategory()` inspects raw data.
286+
3. **Callers own the data** — no aggregation transforms applied.
287+
4. **Layout is configurable** — elastic stretch, facet sizing, step sizes
288+
exposed in `AssembleOptions`.
289+
5. **Templates are declarative** — each chart type is a `ChartTemplateDef`
290+
with a skeleton, channel list, and optional post-processor.
291+
6. **Backend-agnostic semantics** — the same semantic reasoning targets
292+
Vega-Lite, ECharts, and Chart.js through separate assembly functions.

src/chartjs/assemble.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,13 +24,15 @@
2424
*/
2525

2626
import {
27+
ChartEncoding,
2728
ChartTemplateDef,
2829
ChartAssemblyInput,
2930
AssembleOptions,
3031
LayoutDeclaration,
3132
InstantiateContext,
3233
} from '../core/types';
3334
import type { ChartWarning } from '../core/types';
35+
import { applyEncodingOverrides } from '../core/encoding-overrides';
3436
import { cjsGetTemplateDef } from './templates';
3537
import { resolveChannelSemantics, convertTemporalData } from '../core/resolve-semantics';
3638
import { computeZeroDecision } from '../core/semantic-types';
@@ -59,7 +61,7 @@ import { cjsApplyLayoutToSpec, cjsApplyTooltips } from './instantiate-spec';
5961
*/
6062
export function assembleChartjs(input: ChartAssemblyInput): any {
6163
const chartType = input.chart_spec.chartType;
62-
const encodings = input.chart_spec.encodings;
64+
const rawEncodings = input.chart_spec.encodings;
6365
const data = input.data.values ?? [];
6466
const semanticTypes = input.semantic_types ?? {};
6567
const canvasSize = input.chart_spec.canvasSize ?? { width: 400, height: 320 };
@@ -70,6 +72,12 @@ export function assembleChartjs(input: ChartAssemblyInput): any {
7072
throw new Error(`Unknown Chart.js chart type: ${chartType}. Use cjsAllTemplateDefs to see available types.`);
7173
}
7274

75+
// Compose Category-B encoding-action overrides (stored by the host in
76+
// chartProperties, keyed by action key) onto the base encodings before any
77+
// pipeline phase runs. Flint owns the transform; the host only stores the
78+
// override value. See applyEncodingOverrides / EncodingActionDef.
79+
const encodings = applyEncodingOverrides(chartTemplate, rawEncodings, chartProperties);
80+
7381
const warnings: ChartWarning[] = [];
7482

7583
// ═══════════════════════════════════════════════════════════════════════
@@ -137,7 +145,7 @@ export function assembleChartjs(input: ChartAssemblyInput): any {
137145
budgets, allMarkTypes,
138146
);
139147

140-
const values = overflowResult.filteredData;
148+
let values = overflowResult.filteredData;
141149
warnings.push(...overflowResult.warnings);
142150

143151
// ═══════════════════════════════════════════════════════════════════════
@@ -177,6 +185,7 @@ export function assembleChartjs(input: ChartAssemblyInput): any {
177185
channelSemantics,
178186
layout: layoutResult,
179187
table: values,
188+
fullTable: convertedData,
180189
resolvedEncodings,
181190
encodings,
182191
chartProperties,

src/chartjs/instantiate-spec.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@
2222
*/
2323

2424
import type {
25+
ChannelSemantics,
26+
LayoutResult,
2527
InstantiateContext,
2628
ChartWarning,
2729
} from '../core/types';
@@ -40,11 +42,11 @@ export function cjsApplyLayoutToSpec(
4042
context: InstantiateContext,
4143
warnings: ChartWarning[],
4244
): void {
43-
const { channelSemantics: _channelSemantics, layout, canvasSize } = context;
45+
const { channelSemantics, layout, canvasSize } = context;
4446

4547
// ── Axis-less chart types (pie, radar, doughnut) ─────────────────────
4648
const hasAxes = !!(config.options?.scales?.x || config.options?.scales?.y);
47-
const _isRadar = config.type === 'radar';
49+
const isRadar = config.type === 'radar';
4850

4951
// ── Canvas dimensions ────────────────────────────────────────────────
5052
// Chart.js uses the canvas element dimensions.

src/chartjs/templates/area.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ import {
1212
extractCategories,
1313
groupBy,
1414
buildCategoryAlignedData,
15+
DEFAULT_COLORS,
16+
DEFAULT_BG_COLORS,
1517
getChartJsPalette,
1618
getSeriesBorderColor,
1719
getSeriesBackgroundColor,

0 commit comments

Comments
 (0)