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
2 changes: 2 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,12 @@ jobs:
node-version: ${{ matrix.node }}
cache: npm
- run: npm ci
- run: npm run check:chart-types -w packages/flint-js
- run: npm run typecheck
- run: npm run lint
- run: npm run test
- run: npm run build
- run: npm run test:types -w packages/flint-js

site:
name: Build site
Expand Down
16 changes: 16 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
[![arXiv: 2607.20775](https://img.shields.io/badge/arXiv-2607.20775-b31b1b.svg)](https://arxiv.org/abs/2607.20775)
[![CI](https://github.com/microsoft/flint-chart/actions/workflows/ci.yml/badge.svg)](https://github.com/microsoft/flint-chart/actions/workflows/ci.yml)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
[![Discord](https://img.shields.io/badge/discord-chat-green?logo=discord)](https://discord.gg/hnm4Y8YX8)
[![WeChat](https://img.shields.io/badge/wechat-group-07C160?logo=wechat&logoColor=white)](#wechat)

**Please visit:** [**Flint Project Site**](https://microsoft.github.io/flint-chart/) | [**Visual Themes**](https://microsoft.github.io/flint-chart/#/themes) | [**MCP Server Guide**](https://microsoft.github.io/flint-chart/#/mcp) | [**中文主页**](https://microsoft.github.io/flint-chart/#/zh)

Expand Down Expand Up @@ -297,6 +299,19 @@ Flint is built by [Microsoft Research](https://www.microsoft.com/en-us/research/
in collaboration with the [IDEAS Lab](https://ideas-lab.net/), Renmin University
of China. We welcome you to join us — see [Contributing](#contributing) to get involved.

## Community

Chat with the team and other users:

- **Discord** — [join the server](https://discord.gg/hnm4Y8YX8)
- **WeChat** — scan the QR code to join the user group

<a id="wechat"></a>

<p align="center">
<img src="docs/figs/QR-code.png" alt="Flint WeChat user group QR code" width="240">
</p>

A research paper describing Flint is coming soon.

## Trademarks
Expand All @@ -311,3 +326,4 @@ or logos is subject to those third parties' policies.
## License

[MIT](LICENSE) © Microsoft Corporation

28 changes: 28 additions & 0 deletions docs/adding-a-chart-template.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,34 @@ In `packages/flint-js/src/<backend>/templates/index.ts`:

Vega-Lite also runs `withInjectedProperties()` to attach shared facet and log-scale properties across templates. Follow existing entries in that file when your chart needs the same hooks.

### Regenerate Vega-Lite authoring types

After changing Vega-Lite registry names, channels, properties, or encoding-action
controls, run from the repository root:

```bash
npm run gen:chart-types -w packages/flint-js
npm run check:chart-types -w packages/flint-js
npm run typecheck:js
npm run test -w packages/flint-js -- tests/vegalite-chart-types.test.ts
npm run build:js
npm run test:types -w packages/flint-js
```

Check in `packages/flint-js/src/vegalite/chart-types.generated.ts`; do not edit it
by hand. The generator in `packages/flint-js/scripts/vegalite-chart-types.ts`
reads the source registry (including injected properties), not `dist`. Builds
regenerate before declaration emission. CI checks drift **before** any build can
rewrite the output, then runs source and built-package TypeScript fixtures.
The built-package check uses public package entrypoints without path aliases,
with `skipLibCheck: false` and `exactOptionalPropertyTypes: true`.

New metadata/value shapes need explicit generator support and tests; unsupported
shapes fail generation rather than producing loose types. Keep the two
assembler-backed supplements (`facetColumns` and deprecated `showTextLabels`)
aligned with `vegalite/assemble.ts`. Do not infer static domains from runtime
applicability callbacks or dynamic pivot/arrangement IDs.

---

# §4 Add test data and gallery coverage
Expand Down
Binary file added docs/figs/QR-code.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
11 changes: 0 additions & 11 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

59 changes: 59 additions & 0 deletions packages/flint-js/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,65 @@ const xl = assembleExcel(input); // Native Excel chart artifact
Both ESM (`import`) and CommonJS (`require`) builds are published, with type
declarations for every entry point.

## Typed Vega-Lite authoring

Use `satisfies VegaLiteChartSpec` to check an authored spec without changing
`ChartAssemblyInput` or the assembly functions:

```ts
import { assembleVegaLite, type VegaLiteChartSpec } from 'flint-chart';

const chart = {
chartType: 'Area Chart',
title: 'Revenue by quarter',
encodings: { x: 'quarter', y: 'revenue', color: 'region' },
chartProperties: { stackMode: 'layered', interpolate: 'monotone' },
} satisfies VegaLiteChartSpec;

const spec = assembleVegaLite({
data: { values: [{ quarter: 'Q1', revenue: 120, region: 'North' }] },
chart_spec: chart,
});
```

The package root and `flint-chart/vegalite` export three **type-only** helpers:

| Type | Contract |
|---|---|
| `VegaLiteChartType` | Literal names from the Vega-Lite template registry |
| `VegaLiteChartPropertiesMap` | Per-chart optional property shapes, e.g. `VegaLiteChartPropertiesMap['Bar Chart']` |
| `VegaLiteChartSpec` | Nongeneric union discriminated by `chartType`, preserving the native title, subtitle, sizes, and encodings (including shorthand and static series) |

Properties come from the registry's `properties` and `encodingActions` controls;
both are stored in `chart_spec.chartProperties`. For example, bar `sort` accepts
`'value-asc'` or `'value-desc'`, and only Area Chart accepts the `'layered'`
stack mode. Discrete values are literals, not display labels; registered array
options retain their tuple values. Continuous controls remain `number`, not a
UI slider range. Omit a property or pass `undefined` to request default behavior,
including when using TypeScript's `exactOptionalPropertyTypes`.

Known limitation: `projectionCenter` types describe registered presets, but
runtime normalization still compares arrays by reference, so fresh or
JSON-round-tripped tuples can warn and fall back to the default; this behavior
is unchanged.

Two assembler-supported inputs supplement that metadata: `facetColumns?: number`
on templates declaring a `column` channel, and deprecated `showTextLabels?: boolean`
where `showValueLabels` is declared. Legacy `showTextLabels: true` requests labels;
`false` or omission leaves the automatic choice intact. An explicit boolean
`showValueLabels` takes precedence over the legacy input. The alias adds no
second UI control.

This is an opt-in **static authoring** contract, not runtime validation or a
guarantee of applicability. Data and encodings still determine which options
apply; inspect `getChartOptions` for the current chart. It does not restrict
encoding channels beyond the native encoding types. Data-dependent
`chartProperties.chartType`, `pivot`, and `arrange` IDs, and other runtime inputs
outside the declared metadata, are deliberately excluded. Dynamic callers can
continue using the unchanged, broader `ChartAssemblyInput`. As with other
TypeScript object types, excess-key checks apply to fresh literals; these types
do not validate external JSON.

## Rendering

The web backends produce **specs**, not pixels. To render those specs to PNG or SVG
Expand Down
9 changes: 6 additions & 3 deletions packages/flint-js/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -118,16 +118,19 @@
"LICENSE"
],
"scripts": {
"build": "tsup",
"dev": "tsup --watch",
"build": "npm run gen:chart-types && tsup",
"dev": "npm run gen:chart-types && tsup --watch",
"gen:chart-types": "node scripts/gen-vegalite-chart-types.mjs",
"check:chart-types": "node scripts/gen-vegalite-chart-types.mjs --check",
"test:types": "tsc --noEmit -p tsconfig.package-test.json",
"typecheck": "tsc --noEmit && tsc --noEmit -p tsconfig.test.json",
"test": "vitest run",
"test:watch": "vitest",
"lint": "eslint \"src/**/*.{ts,tsx}\"",
"lint:fix": "eslint \"src/**/*.{ts,tsx}\" --fix",
"format": "prettier --write \"src/**/*.{ts,tsx,json,md}\"",
"clean": "rimraf dist",
"prepublishOnly": "npm run clean && npm run typecheck && npm run test && npm run build",
"prepublishOnly": "npm run clean && npm run typecheck && npm run test && npm run build && npm run test:types",
"release": "npm publish --access public"
},
"peerDependencies": {
Expand Down
34 changes: 34 additions & 0 deletions packages/flint-js/scripts/gen-vegalite-chart-types.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

import { existsSync, readFileSync, writeFileSync } from 'node:fs';
import { fileURLToPath } from 'node:url';
import { build } from 'esbuild';

const args = process.argv.slice(2);
if (args.length > 1 || (args.length === 1 && args[0] !== '--check')) {
throw new Error('Usage: node scripts/gen-vegalite-chart-types.mjs [--check]');
}

// Bootstrap from source, as gen-chart-reference does, without requiring dist.
// Keeping the bundle in memory avoids temporary files and platform-specific cleanup.
const bundle = await build({
entryPoints: [fileURLToPath(new URL('./vegalite-chart-types.ts', import.meta.url))],
bundle: true,
platform: 'node',
format: 'esm',
write: false,
});
const { generateVegaLiteChartTypes } = await import(
`data:text/javascript;base64,${Buffer.from(bundle.outputFiles[0].text).toString('base64')}`
);
const output = new URL('../src/vegalite/chart-types.generated.ts', import.meta.url);
const expected = generateVegaLiteChartTypes();
const actual = existsSync(output) ? readFileSync(output, 'utf8').replace(/\r\n/g, '\n') : undefined;
if (args[0] === '--check') {
if (actual !== expected) {
throw new Error('Vega-Lite authoring types are stale. Run npm run gen:chart-types -w packages/flint-js.');
}
} else if (actual !== expected) {
writeFileSync(output, expected);
}
Loading
Loading