Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,10 @@ This repo contains two main components:
## Features


- **Semantic chart specs.** Flint captures what each field means using 70+
semantic types such as `Rank`, `Temperature`, `Price`, or `Country`.
- **Semantic chart specs.** Flint captures what each field means using 40+
semantic types such as `Rank`, `Temperature`, `Price`, or `Country`, plus
optional `unit` / `intrinsicDomain` annotations that enable currency and
percent axis formatting.
- **Automatic layout.** Flint adapts sizing, spacing, labels, marks, and legends
to the data cardinality, chart design, and canvas constraints.
- **Multiple backends.** Compile one input to 30+ chart types across
Expand Down
45 changes: 41 additions & 4 deletions agent-skills/flint-chart-author/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ published, use the npm package or MCP server for released workflows.
interface ChartAssemblyInput {
// Bound by the HOST or by you, depending on the situation (see below).
data: { values: any[] } | { url: string };
semantic_types?: Record<string, string>; // field → semantic type ← you write this
semantic_types?: Record<string, string | SemanticAnnotation>; // field → type or annotation ← you write this
chart_spec: { // ← you write this
chartType: string; // e.g. "Scatter Plot"
encodings: Record<string, EncodingValue>; // channel → { field, ... } (or array)
Expand Down Expand Up @@ -323,16 +323,52 @@ more. Pick the most specific type for each field. Full registered set:

What choosing well gets you (automatically):

- `Price` / `Amount` → currency formatting, zero baseline, sequential color
- `Price` / `Amount` → zero baseline, sequential color; add `unit` for
currency axis labels (see "Annotation objects" below)
- `Temperature` → diverging color scheme, no forced zero baseline
- `Correlation` → fixed `[-1, 1]` diverging domain
- `Rank` → reversed axis (1 on top), discrete color
- `Date` / `DateTime` → temporal axis with auto-granularity formatting
- `Percentage` → percent formatting, 0–100 domain awareness
- `Percentage` → 0–100 domain awareness; add `intrinsicDomain` for percent
axis labels (see "Annotation objects" below)

If you don't know, use `Quantity` for numbers, `Category` for strings,
`Date`/`DateTime` for date-shaped values. Do **not** invent type names.

### Annotation objects: `unit` and `intrinsicDomain`

Each `semantic_types` entry is either a bare type string or a
`SemanticAnnotation` object — `"Price"` is shorthand for
`{ "semanticType": "Price" }`:

```json
"semantic_types": {
"revenue": { "semanticType": "Price", "unit": "USD" },
"share": { "semanticType": "Percentage", "intrinsicDomain": [0, 1] },
"rating": { "semanticType": "Score", "intrinsicDomain": [1, 5] },
"region": "Region"
}
```

Without these fields Flint stays conservative: it falls back to plain
number formatting rather than guess a currency or rescale values it cannot
verify.

| Annotation | Effect |
|---|---|
| `Price` + `unit` with a known currency code (`USD`, `EUR`, `GBP`, `JPY`, `CNY`, …) | Currency symbol on value-axis labels (`$1,200.00`) |
| `Percentage` + `intrinsicDomain: [0, 1]` over fractional data | Percent axis labels (`0.42` → `42%`) and a domain capped at 1 |
| `Percentage` + `intrinsicDomain: [0, 100]` | Domain capped at 100; whole numbers stay readable as-is |
| Bounded type + `intrinsicDomain` (e.g. `Score` `[1, 5]`) | Scale anchored to the intrinsic range instead of the data extent |
| `Temperature` (and other physical measures) + `unit` (`°C`, `kg`, `km`, …) | Unit suffix on tooltip values |

Axis-label formatting above is fully wired in the Vega-Lite backend today;
other backends may not apply every format decision yet.

Add `unit` / `intrinsicDomain` when the user states them or the data makes
them unambiguous (a `price_usd` column; shares that sum to 1). If neither
is known, keep the bare string — do not guess.

## Chart-level properties (`chartProperties`)

`chartProperties` is an optional per-chart tuning map. Set a property only
Expand Down Expand Up @@ -558,7 +594,8 @@ Before returning, verify:

1. `chartType` is an exact registered name supported by the target backend.
2. Every `field` referenced in `encodings` is a real column name.
3. Every encoded field has an entry in `semantic_types` (specific type).
3. Every encoded field has an entry in `semantic_types` (specific type;
add `unit` / `intrinsicDomain` when known — see "Annotation objects").
4. Required channels for the chart type are present (e.g. Bullet→`goal`,
Candlestick→`open/high/low/close`, Pie→`size`+`color`).
5. Any `chartProperties` keys are valid for that chart type and in range.
Expand Down
5 changes: 5 additions & 0 deletions docs/api-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,11 @@ interface SemanticAnnotation {

Bare string shorthand: `"Price"` is equivalent to `{ semanticType: "Price" }`.

`unit` and `intrinsicDomain` also drive formatting: a known currency `unit`
(`USD`, `EUR`, `CNY`, …) puts the symbol on `Price` axis labels, and
`intrinsicDomain: [0, 1]` renders fractional `Percentage` values as percent
axis labels (`0.42` → `42%`) with a capped domain (Vega-Lite backend today).

---

# §2 Assemblers
Expand Down
45 changes: 41 additions & 4 deletions packages/flint-mcp/assets/flint-chart-author.SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ published, use the npm package or MCP server for released workflows.
interface ChartAssemblyInput {
// Bound by the HOST or by you, depending on the situation (see below).
data: { values: any[] } | { url: string };
semantic_types?: Record<string, string>; // field → semantic type ← you write this
semantic_types?: Record<string, string | SemanticAnnotation>; // field → type or annotation ← you write this
chart_spec: { // ← you write this
chartType: string; // e.g. "Scatter Plot"
encodings: Record<string, EncodingValue>; // channel → { field, ... } (or array)
Expand Down Expand Up @@ -323,16 +323,52 @@ more. Pick the most specific type for each field. Full registered set:

What choosing well gets you (automatically):

- `Price` / `Amount` → currency formatting, zero baseline, sequential color
- `Price` / `Amount` → zero baseline, sequential color; add `unit` for
currency axis labels (see "Annotation objects" below)
- `Temperature` → diverging color scheme, no forced zero baseline
- `Correlation` → fixed `[-1, 1]` diverging domain
- `Rank` → reversed axis (1 on top), discrete color
- `Date` / `DateTime` → temporal axis with auto-granularity formatting
- `Percentage` → percent formatting, 0–100 domain awareness
- `Percentage` → 0–100 domain awareness; add `intrinsicDomain` for percent
axis labels (see "Annotation objects" below)

If you don't know, use `Quantity` for numbers, `Category` for strings,
`Date`/`DateTime` for date-shaped values. Do **not** invent type names.

### Annotation objects: `unit` and `intrinsicDomain`

Each `semantic_types` entry is either a bare type string or a
`SemanticAnnotation` object — `"Price"` is shorthand for
`{ "semanticType": "Price" }`:

```json
"semantic_types": {
"revenue": { "semanticType": "Price", "unit": "USD" },
"share": { "semanticType": "Percentage", "intrinsicDomain": [0, 1] },
"rating": { "semanticType": "Score", "intrinsicDomain": [1, 5] },
"region": "Region"
}
```

Without these fields Flint stays conservative: it falls back to plain
number formatting rather than guess a currency or rescale values it cannot
verify.

| Annotation | Effect |
|---|---|
| `Price` + `unit` with a known currency code (`USD`, `EUR`, `GBP`, `JPY`, `CNY`, …) | Currency symbol on value-axis labels (`$1,200.00`) |
| `Percentage` + `intrinsicDomain: [0, 1]` over fractional data | Percent axis labels (`0.42` → `42%`) and a domain capped at 1 |
| `Percentage` + `intrinsicDomain: [0, 100]` | Domain capped at 100; whole numbers stay readable as-is |
| Bounded type + `intrinsicDomain` (e.g. `Score` `[1, 5]`) | Scale anchored to the intrinsic range instead of the data extent |
| `Temperature` (and other physical measures) + `unit` (`°C`, `kg`, `km`, …) | Unit suffix on tooltip values |

Axis-label formatting above is fully wired in the Vega-Lite backend today;
other backends may not apply every format decision yet.

Add `unit` / `intrinsicDomain` when the user states them or the data makes
them unambiguous (a `price_usd` column; shares that sum to 1). If neither
is known, keep the bare string — do not guess.

## Chart-level properties (`chartProperties`)

`chartProperties` is an optional per-chart tuning map. Set a property only
Expand Down Expand Up @@ -558,7 +594,8 @@ Before returning, verify:

1. `chartType` is an exact registered name supported by the target backend.
2. Every `field` referenced in `encodings` is a real column name.
3. Every encoded field has an entry in `semantic_types` (specific type).
3. Every encoded field has an entry in `semantic_types` (specific type;
add `unit` / `intrinsicDomain` when known — see "Annotation objects").
4. Required channels for the chart type are present (e.g. Bullet→`goal`,
Candlestick→`open/high/low/close`, Pie→`size`+`color`).
5. Any `chartProperties` keys are valid for that chart type and in range.
Expand Down
7 changes: 6 additions & 1 deletion packages/flint-mcp/src/tools/schemas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,12 @@ export function buildAssemblyInputShape(disableFileReference = false) {
semantic_types: z
.record(z.string(), z.any())
.optional()
.describe('Field name → semantic type, e.g. { revenue: "Quantity", country: "Country" }.'),
.describe(
'Field name → semantic type, e.g. { revenue: "Quantity", country: "Country" }. ' +
'An entry may also be an annotation object { semanticType, unit, intrinsicDomain }: ' +
'{ revenue: { semanticType: "Price", unit: "USD" } } adds a currency symbol to axis labels; ' +
'{ share: { semanticType: "Percentage", intrinsicDomain: [0, 1] } } formats values as percentages.',
),
chart_spec: chartSpecSchema,
options: z
.record(z.string(), z.any())
Expand Down