@unovis/mcp 1.7.0-mcp.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +193 -0
- package/README.md +154 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +109 -0
- package/dist/cli.js.map +1 -0
- package/dist/codegen/index.d.ts +14 -0
- package/dist/codegen/index.js +346 -0
- package/dist/codegen/index.js.map +1 -0
- package/dist/env/bbox.d.ts +13 -0
- package/dist/env/bbox.js +335 -0
- package/dist/env/bbox.js.map +1 -0
- package/dist/env/canvas.d.ts +9 -0
- package/dist/env/canvas.js +55 -0
- package/dist/env/canvas.js.map +1 -0
- package/dist/env/computed-style.d.ts +38 -0
- package/dist/env/computed-style.js +186 -0
- package/dist/env/computed-style.js.map +1 -0
- package/dist/env/fonts.d.ts +3 -0
- package/dist/env/fonts.js +103 -0
- package/dist/env/fonts.js.map +1 -0
- package/dist/env/index.d.ts +13 -0
- package/dist/env/index.js +121 -0
- package/dist/env/index.js.map +1 -0
- package/dist/env/raf-queue.d.ts +23 -0
- package/dist/env/raf-queue.js +51 -0
- package/dist/env/raf-queue.js.map +1 -0
- package/dist/env/size.d.ts +9 -0
- package/dist/env/size.js +30 -0
- package/dist/env/size.js.map +1 -0
- package/dist/html/document.d.ts +16 -0
- package/dist/html/document.js +95 -0
- package/dist/html/document.js.map +1 -0
- package/dist/index.d.ts +25 -0
- package/dist/index.js +18 -0
- package/dist/index.js.map +1 -0
- package/dist/recipes/area.d.ts +66 -0
- package/dist/recipes/area.js +76 -0
- package/dist/recipes/area.js.map +1 -0
- package/dist/recipes/bar.d.ts +67 -0
- package/dist/recipes/bar.js +69 -0
- package/dist/recipes/bar.js.map +1 -0
- package/dist/recipes/boxplot.d.ts +64 -0
- package/dist/recipes/boxplot.js +89 -0
- package/dist/recipes/boxplot.js.map +1 -0
- package/dist/recipes/chord.d.ts +41 -0
- package/dist/recipes/chord.js +72 -0
- package/dist/recipes/chord.js.map +1 -0
- package/dist/recipes/choropleth-regions.d.ts +49 -0
- package/dist/recipes/choropleth-regions.js +521 -0
- package/dist/recipes/choropleth-regions.js.map +1 -0
- package/dist/recipes/choropleth.d.ts +27 -0
- package/dist/recipes/choropleth.js +86 -0
- package/dist/recipes/choropleth.js.map +1 -0
- package/dist/recipes/donut.d.ts +25 -0
- package/dist/recipes/donut.js +51 -0
- package/dist/recipes/donut.js.map +1 -0
- package/dist/recipes/graph.d.ts +55 -0
- package/dist/recipes/graph.js +132 -0
- package/dist/recipes/graph.js.map +1 -0
- package/dist/recipes/heatmap.d.ts +21 -0
- package/dist/recipes/heatmap.js +79 -0
- package/dist/recipes/heatmap.js.map +1 -0
- package/dist/recipes/index.d.ts +5 -0
- package/dist/recipes/index.js +35 -0
- package/dist/recipes/index.js.map +1 -0
- package/dist/recipes/line.d.ts +69 -0
- package/dist/recipes/line.js +64 -0
- package/dist/recipes/line.js.map +1 -0
- package/dist/recipes/nested-donut.d.ts +22 -0
- package/dist/recipes/nested-donut.js +47 -0
- package/dist/recipes/nested-donut.js.map +1 -0
- package/dist/recipes/radial-bar.d.ts +25 -0
- package/dist/recipes/radial-bar.js +53 -0
- package/dist/recipes/radial-bar.js.map +1 -0
- package/dist/recipes/sankey.d.ts +41 -0
- package/dist/recipes/sankey.js +61 -0
- package/dist/recipes/sankey.js.map +1 -0
- package/dist/recipes/scatter.d.ts +68 -0
- package/dist/recipes/scatter.js +75 -0
- package/dist/recipes/scatter.js.map +1 -0
- package/dist/recipes/shared.d.ts +144 -0
- package/dist/recipes/shared.js +212 -0
- package/dist/recipes/shared.js.map +1 -0
- package/dist/recipes/timeline.d.ts +31 -0
- package/dist/recipes/timeline.js +123 -0
- package/dist/recipes/timeline.js.map +1 -0
- package/dist/recipes/treemap.d.ts +19 -0
- package/dist/recipes/treemap.js +43 -0
- package/dist/recipes/treemap.js.map +1 -0
- package/dist/recipes/types.d.ts +16 -0
- package/dist/recipes/types.js +2 -0
- package/dist/recipes/types.js.map +1 -0
- package/dist/render/headless.d.ts +62 -0
- package/dist/render/headless.js +158 -0
- package/dist/render/headless.js.map +1 -0
- package/dist/render/materialize.d.ts +35 -0
- package/dist/render/materialize.js +160 -0
- package/dist/render/materialize.js.map +1 -0
- package/dist/render/mutex.d.ts +9 -0
- package/dist/render/mutex.js +14 -0
- package/dist/render/mutex.js.map +1 -0
- package/dist/render/rasterize.d.ts +10 -0
- package/dist/render/rasterize.js +29 -0
- package/dist/render/rasterize.js.map +1 -0
- package/dist/render/renderer.d.ts +21 -0
- package/dist/render/renderer.js +66 -0
- package/dist/render/renderer.js.map +1 -0
- package/dist/render/spec.d.ts +95 -0
- package/dist/render/spec.js +15 -0
- package/dist/render/spec.js.map +1 -0
- package/dist/server.d.ts +4 -0
- package/dist/server.js +44 -0
- package/dist/server.js.map +1 -0
- package/dist/svg/collect-css.d.ts +14 -0
- package/dist/svg/collect-css.js +53 -0
- package/dist/svg/collect-css.js.map +1 -0
- package/dist/svg/css-vars.d.ts +17 -0
- package/dist/svg/css-vars.js +64 -0
- package/dist/svg/css-vars.js.map +1 -0
- package/dist/svg/header.d.ts +8 -0
- package/dist/svg/header.js +81 -0
- package/dist/svg/header.js.map +1 -0
- package/dist/svg/ids.d.ts +12 -0
- package/dist/svg/ids.js +94 -0
- package/dist/svg/ids.js.map +1 -0
- package/dist/svg/postprocess.d.ts +41 -0
- package/dist/svg/postprocess.js +135 -0
- package/dist/svg/postprocess.js.map +1 -0
- package/dist/tools/register.d.ts +12 -0
- package/dist/tools/register.js +127 -0
- package/dist/tools/register.js.map +1 -0
- package/dist/widget/bundle.js +1777 -0
- package/dist/widget/bundle.meta.json +3 -0
- package/dist/widget/entry.d.ts +34 -0
- package/dist/widget/entry.js +118 -0
- package/dist/widget/entry.js.map +1 -0
- package/dist/widget/interactions.d.ts +14 -0
- package/dist/widget/interactions.js +151 -0
- package/dist/widget/interactions.js.map +1 -0
- package/dist/widget/unovis-slim.d.ts +33 -0
- package/dist/widget/unovis-slim.js +34 -0
- package/dist/widget/unovis-slim.js.map +1 -0
- package/docs/README.md +56 -0
- package/docs/architecture.md +158 -0
- package/docs/chart-spec.md +148 -0
- package/docs/getting-started.md +175 -0
- package/docs/interactive.md +125 -0
- package/docs/output-types.md +170 -0
- package/docs/programmatic.md +234 -0
- package/docs/tools.md +273 -0
- package/docs/troubleshooting.md +140 -0
- package/fonts/README.md +19 -0
- package/package.json +73 -0
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
/** Headless chart rendering — the SSR primitive.
|
|
2
|
+
*
|
|
3
|
+
* Everything here is independent of the MCP layer: give it a callback that
|
|
4
|
+
* builds any Unovis chart and it returns standalone SVG. This is the boundary
|
|
5
|
+
* a future `@unovis/ssr` package would follow.
|
|
6
|
+
*
|
|
7
|
+
* Why the callback receives `unovis` instead of importing it: the library
|
|
8
|
+
* captures its environment at module load (emotion inserts stylesheets,
|
|
9
|
+
* text measurement grabs a canvas context), so it must be imported *after*
|
|
10
|
+
* the shims are installed. Handing the already-initialised namespace to the
|
|
11
|
+
* callback is what makes that safe.
|
|
12
|
+
*/
|
|
13
|
+
import { getRenderEnv, defineElementSize } from '../env/index.js';
|
|
14
|
+
import { Mutex } from './mutex.js';
|
|
15
|
+
import { finalizeSvg, CHART_PADDING } from '../svg/postprocess.js';
|
|
16
|
+
const RENDER_TIMEOUT_MS = 5000;
|
|
17
|
+
/** Async layouts (graph force/dagre/elk) may need dynamic imports + solver time */
|
|
18
|
+
const ASYNC_RENDER_TIMEOUT_MS = 20000;
|
|
19
|
+
/** A synchronous render settles within a couple of frames; this only bounds
|
|
20
|
+
* how long a broken one takes to report itself. Async renders are bounded by
|
|
21
|
+
* the deadline instead — counting rounds is meaningless there, because a
|
|
22
|
+
* microtask tick costs microseconds while a cold module import needs
|
|
23
|
+
* milliseconds of real time. */
|
|
24
|
+
const MAX_FLUSH_ROUNDS = 10;
|
|
25
|
+
/** Idle wait while an asynchronous layout is still pending */
|
|
26
|
+
const ASYNC_POLL_MS = 5;
|
|
27
|
+
/** Extra settle passes after an async layout reports completion */
|
|
28
|
+
const ASYNC_GRACE_ROUNDS = 4;
|
|
29
|
+
/** Renders share one jsdom document and one frame queue */
|
|
30
|
+
const mutex = new Mutex();
|
|
31
|
+
const nextMacrotask = () => new Promise(resolve => setImmediate(resolve));
|
|
32
|
+
const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms));
|
|
33
|
+
/**
|
|
34
|
+
* Render a chart to standalone SVG in Node.
|
|
35
|
+
*
|
|
36
|
+
* ```ts
|
|
37
|
+
* const { svg } = await renderToSvg({ width: 800, height: 400 }, (ctx) => {
|
|
38
|
+
* const line = new ctx.unovis.Line({ x: d => d.x, y: d => d.y, duration: 0 })
|
|
39
|
+
* return new ctx.unovis.XYContainer(ctx.container, {
|
|
40
|
+
* components: [line],
|
|
41
|
+
* width: ctx.width,
|
|
42
|
+
* height: ctx.height,
|
|
43
|
+
* duration: 0,
|
|
44
|
+
* onRenderComplete: ctx.onRenderComplete,
|
|
45
|
+
* }, data)
|
|
46
|
+
* })
|
|
47
|
+
* ```
|
|
48
|
+
*/
|
|
49
|
+
export async function renderToSvg(options, build) {
|
|
50
|
+
const env = await getRenderEnv();
|
|
51
|
+
return mutex.run(async () => {
|
|
52
|
+
const { document, lib, raf } = env;
|
|
53
|
+
const warnings = [];
|
|
54
|
+
const padding = options.padding ?? CHART_PADDING;
|
|
55
|
+
const width = Math.max(40, options.width - padding.left - padding.right);
|
|
56
|
+
const height = Math.max(40, options.height - padding.top - padding.bottom);
|
|
57
|
+
const theme = options.theme ?? 'light';
|
|
58
|
+
const host = document.createElement('div');
|
|
59
|
+
defineElementSize(host, width, height);
|
|
60
|
+
document.body.appendChild(host);
|
|
61
|
+
// Theme and palette reach the library through the getComputedStyle shim:
|
|
62
|
+
// the dark flag from the document root, palette overrides from the host's
|
|
63
|
+
// inline custom properties.
|
|
64
|
+
if (theme === 'dark')
|
|
65
|
+
document.documentElement.setAttribute('data-theme', 'dark');
|
|
66
|
+
options.colors?.forEach((color, i) => {
|
|
67
|
+
host.style.setProperty(`--vis-color${i}`, color);
|
|
68
|
+
host.style.setProperty(`--vis-dark-color${i}`, color);
|
|
69
|
+
});
|
|
70
|
+
let renderCompleted = false;
|
|
71
|
+
let componentCompleted = true;
|
|
72
|
+
let needsComponent = false;
|
|
73
|
+
let chart;
|
|
74
|
+
try {
|
|
75
|
+
chart = build({
|
|
76
|
+
container: host,
|
|
77
|
+
unovis: lib,
|
|
78
|
+
width,
|
|
79
|
+
height,
|
|
80
|
+
onRenderComplete: () => { renderCompleted = true; },
|
|
81
|
+
onComponentReady: () => { componentCompleted = true; },
|
|
82
|
+
requireComponentReady: () => {
|
|
83
|
+
needsComponent = true;
|
|
84
|
+
componentCompleted = false;
|
|
85
|
+
},
|
|
86
|
+
});
|
|
87
|
+
// Belt and braces for library builds without the clientWidth fallback
|
|
88
|
+
if (chart?.element)
|
|
89
|
+
defineElementSize(chart.element, width, height);
|
|
90
|
+
const deadline = Date.now() + (needsComponent ? ASYNC_RENDER_TIMEOUT_MS : RENDER_TIMEOUT_MS);
|
|
91
|
+
let rounds = 0;
|
|
92
|
+
for (;;) {
|
|
93
|
+
raf.flushAll();
|
|
94
|
+
await nextMacrotask(); // let 0ms timers (d3-timer) and layout promises progress
|
|
95
|
+
if (raf.size === 0 && renderCompleted && componentCompleted)
|
|
96
|
+
break;
|
|
97
|
+
if (Date.now() > deadline)
|
|
98
|
+
break;
|
|
99
|
+
if (!needsComponent && ++rounds >= MAX_FLUSH_ROUNDS)
|
|
100
|
+
break;
|
|
101
|
+
// Waiting on a layout that resolves through module loading and solver
|
|
102
|
+
// work: yield real time rather than spinning on microtasks
|
|
103
|
+
if (needsComponent && raf.size === 0 && !componentCompleted)
|
|
104
|
+
await sleep(ASYNC_POLL_MS);
|
|
105
|
+
}
|
|
106
|
+
raf.flushAll();
|
|
107
|
+
// Components with async layouts schedule follow-up frames *after*
|
|
108
|
+
// signalling completion — graph fit-view, label placement. Without a
|
|
109
|
+
// grace period the output depends on how loaded the machine is, which
|
|
110
|
+
// shows up as charts that differ run to run.
|
|
111
|
+
if (needsComponent) {
|
|
112
|
+
for (let grace = 0; grace < ASYNC_GRACE_ROUNDS; grace++) {
|
|
113
|
+
await sleep(ASYNC_POLL_MS);
|
|
114
|
+
raf.flushAll();
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
if (!renderCompleted || !componentCompleted) {
|
|
118
|
+
const details = raf.errors.length ? ` Render errors: ${raf.errors.map(e => String(e)).join('; ')}` : '';
|
|
119
|
+
const what = !renderCompleted
|
|
120
|
+
? 'onRenderComplete never fired — is it wired into the container config?'
|
|
121
|
+
: 'component layout never completed';
|
|
122
|
+
throw new Error(`Chart rendering did not complete (${what}).${details}`);
|
|
123
|
+
}
|
|
124
|
+
for (const error of raf.errors)
|
|
125
|
+
warnings.push(`render frame error: ${String(error)}`);
|
|
126
|
+
const svgElement = (chart?.element?.localName === 'svg'
|
|
127
|
+
? chart.element
|
|
128
|
+
: host.querySelector('svg'));
|
|
129
|
+
if (!svgElement)
|
|
130
|
+
throw new Error('Chart produced no SVG element');
|
|
131
|
+
const svg = finalizeSvg(svgElement, {
|
|
132
|
+
document,
|
|
133
|
+
frame: { ...options, theme },
|
|
134
|
+
varMaps: env.varMaps,
|
|
135
|
+
idPrefix: options.idPrefix,
|
|
136
|
+
keepClasses: options.keepClasses,
|
|
137
|
+
padding,
|
|
138
|
+
});
|
|
139
|
+
return {
|
|
140
|
+
svg,
|
|
141
|
+
width: options.width,
|
|
142
|
+
height: parseFloat(svgElement.getAttribute('height') ?? '') || options.height,
|
|
143
|
+
warnings,
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
finally {
|
|
147
|
+
try {
|
|
148
|
+
chart?.destroy?.();
|
|
149
|
+
}
|
|
150
|
+
catch { /* a failed render may leave a partially constructed chart */ }
|
|
151
|
+
host.remove();
|
|
152
|
+
if (theme === 'dark')
|
|
153
|
+
document.documentElement.removeAttribute('data-theme');
|
|
154
|
+
raf.clear();
|
|
155
|
+
}
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
//# sourceMappingURL=headless.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"headless.js","sourceRoot":"","sources":["../../src/render/headless.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/** Materialize a ChartSpec into live Unovis component/container configs.
|
|
2
|
+
*
|
|
3
|
+
* Converts JSON accessor descriptors into real functions. Field access is by
|
|
4
|
+
* property name only — user input is never evaluated as code.
|
|
5
|
+
*/
|
|
6
|
+
import type { UnovisLib } from '../env/index.js';
|
|
7
|
+
import type { AccessorRef, ChartSpec } from './spec.js';
|
|
8
|
+
type AnyFn = (...args: any[]) => any;
|
|
9
|
+
export declare class ChartInputError extends Error {
|
|
10
|
+
}
|
|
11
|
+
export declare function materializeAccessor(ref: AccessorRef): AnyFn | unknown;
|
|
12
|
+
/** Deep-walk a config value, replacing accessor descriptors with functions */
|
|
13
|
+
export declare function materializeValue(value: unknown): unknown;
|
|
14
|
+
export interface MaterializedChart {
|
|
15
|
+
containerType: 'xy' | 'single';
|
|
16
|
+
containerConfig: Record<string, unknown>;
|
|
17
|
+
data: unknown;
|
|
18
|
+
}
|
|
19
|
+
/** Components that finish rendering asynchronously (after layout calculation)
|
|
20
|
+
* and expose their own onRenderComplete hook the renderer must await */
|
|
21
|
+
export declare const ASYNC_COMPONENTS: Set<string>;
|
|
22
|
+
/** Options controlling how a spec becomes live Unovis configs.
|
|
23
|
+
* Static rendering uses `duration: 0` and fixed sizes for deterministic
|
|
24
|
+
* output; interactive rendering animates and lets the chart fill its host. */
|
|
25
|
+
export interface MaterializeOptions {
|
|
26
|
+
/** Animation duration in ms. 0 (default) renders synchronously */
|
|
27
|
+
duration?: number;
|
|
28
|
+
/** Omit width/height so the container sizes from its DOM parent */
|
|
29
|
+
responsive?: boolean;
|
|
30
|
+
onRenderComplete?: () => void;
|
|
31
|
+
/** Called by components whose layout completes asynchronously (Graph) */
|
|
32
|
+
onComponentComplete?: () => void;
|
|
33
|
+
}
|
|
34
|
+
export declare function materializeChart(lib: UnovisLib, spec: ChartSpec, options?: MaterializeOptions): MaterializedChart;
|
|
35
|
+
export {};
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import { isAccessorRef } from './spec.js';
|
|
2
|
+
export class ChartInputError extends Error {
|
|
3
|
+
}
|
|
4
|
+
const coerceNumber = (value) => {
|
|
5
|
+
if (value === null || value === undefined || value === '')
|
|
6
|
+
return undefined;
|
|
7
|
+
const n = Number(value);
|
|
8
|
+
return Number.isNaN(n) ? undefined : n;
|
|
9
|
+
};
|
|
10
|
+
const coerceDate = (value) => {
|
|
11
|
+
if (value === null || value === undefined || value === '')
|
|
12
|
+
return undefined;
|
|
13
|
+
if (typeof value === 'number')
|
|
14
|
+
return value;
|
|
15
|
+
const time = new Date(value).getTime();
|
|
16
|
+
return Number.isNaN(time) ? undefined : time;
|
|
17
|
+
};
|
|
18
|
+
/** Time tick formatter: picks granularity from the value */
|
|
19
|
+
const formatDateTick = (value) => {
|
|
20
|
+
const time = typeof value === 'number' ? value : Number(value);
|
|
21
|
+
if (!Number.isFinite(time))
|
|
22
|
+
return String(value);
|
|
23
|
+
const date = new Date(time);
|
|
24
|
+
const opts = date.getUTCHours() === 0 && date.getUTCMinutes() === 0
|
|
25
|
+
? (date.getUTCDate() === 1 ? { year: 'numeric', month: 'short' } : { month: 'short', day: 'numeric' })
|
|
26
|
+
: { hour: '2-digit', minute: '2-digit' };
|
|
27
|
+
return date.toLocaleString('en-US', { ...opts, timeZone: 'UTC' });
|
|
28
|
+
};
|
|
29
|
+
const formatNumTick = (value) => {
|
|
30
|
+
const n = Number(value);
|
|
31
|
+
if (!Number.isFinite(n))
|
|
32
|
+
return String(value);
|
|
33
|
+
return n.toLocaleString('en-US', { maximumFractionDigits: 6 });
|
|
34
|
+
};
|
|
35
|
+
export function materializeAccessor(ref) {
|
|
36
|
+
if ('$field' in ref) {
|
|
37
|
+
const { $field: field, as } = ref;
|
|
38
|
+
if (as === 'number')
|
|
39
|
+
return (d) => coerceNumber(d?.[field]);
|
|
40
|
+
if (as === 'date')
|
|
41
|
+
return (d) => coerceDate(d?.[field]);
|
|
42
|
+
return (d) => d?.[field];
|
|
43
|
+
}
|
|
44
|
+
if ('$index' in ref)
|
|
45
|
+
return (_, i) => i;
|
|
46
|
+
if ('$const' in ref)
|
|
47
|
+
return () => ref.$const;
|
|
48
|
+
if ('$dateTickFormat' in ref)
|
|
49
|
+
return formatDateTick;
|
|
50
|
+
if ('$numTickFormat' in ref)
|
|
51
|
+
return formatNumTick;
|
|
52
|
+
if ('$lookup' in ref) {
|
|
53
|
+
const values = ref.$lookup;
|
|
54
|
+
return (d) => values[Math.round(Number(d)) % values.length] ?? String(d);
|
|
55
|
+
}
|
|
56
|
+
if ('$format' in ref) {
|
|
57
|
+
const { field, prefix = '', suffix = '' } = ref.$format;
|
|
58
|
+
return (d) => {
|
|
59
|
+
const value = d?.[field];
|
|
60
|
+
if (value === null || value === undefined)
|
|
61
|
+
return '';
|
|
62
|
+
return `${prefix}${formatNumTick(value)}${suffix}`;
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
if ('$mapField' in ref) {
|
|
66
|
+
const { field, mapping, fallback } = ref.$mapField;
|
|
67
|
+
return (d) => mapping[String(d?.[field])] ?? fallback;
|
|
68
|
+
}
|
|
69
|
+
return ref;
|
|
70
|
+
}
|
|
71
|
+
/** Deep-walk a config value, replacing accessor descriptors with functions */
|
|
72
|
+
export function materializeValue(value) {
|
|
73
|
+
if (isAccessorRef(value))
|
|
74
|
+
return materializeAccessor(value);
|
|
75
|
+
if (Array.isArray(value))
|
|
76
|
+
return value.map(materializeValue);
|
|
77
|
+
if (typeof value === 'object' && value !== null) {
|
|
78
|
+
const out = {};
|
|
79
|
+
for (const [key, v] of Object.entries(value))
|
|
80
|
+
out[key] = materializeValue(v);
|
|
81
|
+
return out;
|
|
82
|
+
}
|
|
83
|
+
return value;
|
|
84
|
+
}
|
|
85
|
+
/** Component classes recipes are allowed to instantiate */
|
|
86
|
+
const XY_COMPONENTS = new Set(['Line', 'Area', 'GroupedBar', 'StackedBar', 'Scatter', 'Timeline', 'Boxplot', 'XYLabels', 'Plotband', 'Plotline']);
|
|
87
|
+
const SINGLE_COMPONENTS = new Set(['Donut', 'NestedDonut', 'RadialBar', 'Sankey', 'Heatmap', 'Treemap', 'ChordDiagram', 'Graph', 'TopoJSONMap']);
|
|
88
|
+
/** Components that finish rendering asynchronously (after layout calculation)
|
|
89
|
+
* and expose their own onRenderComplete hook the renderer must await */
|
|
90
|
+
export const ASYNC_COMPONENTS = new Set(['Graph']);
|
|
91
|
+
function instantiateComponent(lib, spec, options) {
|
|
92
|
+
const allowed = XY_COMPONENTS.has(spec.type) || SINGLE_COMPONENTS.has(spec.type);
|
|
93
|
+
if (!allowed)
|
|
94
|
+
throw new ChartInputError(`Unsupported component type: ${spec.type}`);
|
|
95
|
+
const componentClass = lib[spec.type];
|
|
96
|
+
if (!componentClass)
|
|
97
|
+
throw new ChartInputError(`Component ${spec.type} is not available in this @unovis/ts build`);
|
|
98
|
+
const config = materializeValue(spec.config);
|
|
99
|
+
config.duration = options.duration ?? 0;
|
|
100
|
+
if (ASYNC_COMPONENTS.has(spec.type) && options.onComponentComplete)
|
|
101
|
+
config.onRenderComplete = options.onComponentComplete;
|
|
102
|
+
// { $mapProjection: 'AlbersUsa' } → MapProjection.AlbersUsa() — projections
|
|
103
|
+
// are factory functions and can't live in the JSON spec directly
|
|
104
|
+
const projection = config.projection;
|
|
105
|
+
if (projection?.$mapProjection) {
|
|
106
|
+
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
107
|
+
const projections = lib.MapProjection;
|
|
108
|
+
const factory = projections?.[projection.$mapProjection];
|
|
109
|
+
if (!factory)
|
|
110
|
+
throw new ChartInputError(`Unknown map projection: ${projection.$mapProjection}`);
|
|
111
|
+
config.projection = factory();
|
|
112
|
+
}
|
|
113
|
+
// eslint-disable-next-line new-cap
|
|
114
|
+
return new componentClass(config);
|
|
115
|
+
}
|
|
116
|
+
export function materializeChart(lib, spec, options = {}) {
|
|
117
|
+
const base = materializeValue(spec.containerConfig ?? {});
|
|
118
|
+
const duration = options.duration ?? 0;
|
|
119
|
+
const size = options.responsive ? {} : { width: spec.width, height: spec.height };
|
|
120
|
+
if (spec.container === 'xy') {
|
|
121
|
+
const components = spec.components.map(c => {
|
|
122
|
+
if (!XY_COMPONENTS.has(c.type))
|
|
123
|
+
throw new ChartInputError(`${c.type} cannot be used in an XY container`);
|
|
124
|
+
return instantiateComponent(lib, c, options);
|
|
125
|
+
});
|
|
126
|
+
const axisConfig = (axis, type) => {
|
|
127
|
+
if (!axis)
|
|
128
|
+
return undefined;
|
|
129
|
+
return new lib.Axis({ ...materializeValue(axis), type, duration });
|
|
130
|
+
};
|
|
131
|
+
return {
|
|
132
|
+
containerType: 'xy',
|
|
133
|
+
containerConfig: {
|
|
134
|
+
...base,
|
|
135
|
+
components,
|
|
136
|
+
xAxis: axisConfig(spec.xAxis, 'x'),
|
|
137
|
+
yAxis: axisConfig(spec.yAxis, 'y'),
|
|
138
|
+
...size,
|
|
139
|
+
duration,
|
|
140
|
+
onRenderComplete: options.onRenderComplete,
|
|
141
|
+
},
|
|
142
|
+
data: spec.data,
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
if (spec.components.length !== 1)
|
|
146
|
+
throw new ChartInputError('single container requires exactly one component');
|
|
147
|
+
const component = instantiateComponent(lib, spec.components[0], options);
|
|
148
|
+
return {
|
|
149
|
+
containerType: 'single',
|
|
150
|
+
containerConfig: {
|
|
151
|
+
...base,
|
|
152
|
+
component,
|
|
153
|
+
...size,
|
|
154
|
+
duration,
|
|
155
|
+
onRenderComplete: options.onRenderComplete,
|
|
156
|
+
},
|
|
157
|
+
data: spec.data,
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
//# sourceMappingURL=materialize.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"materialize.js","sourceRoot":"","sources":["../../src/render/materialize.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/** Minimal promise-chain mutex.
|
|
2
|
+
*
|
|
3
|
+
* Renders share one jsdom document and one rAF queue, so they must not
|
|
4
|
+
* interleave. Failures don't poison the chain.
|
|
5
|
+
*/
|
|
6
|
+
export class Mutex {
|
|
7
|
+
tail = Promise.resolve();
|
|
8
|
+
run(fn) {
|
|
9
|
+
const result = this.tail.then(fn, fn);
|
|
10
|
+
this.tail = result.catch(() => undefined);
|
|
11
|
+
return result;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
//# sourceMappingURL=mutex.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mutex.js","sourceRoot":"","sources":["../../src/render/mutex.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,MAAM,OAAO,KAAK;IACR,IAAI,GAAqB,OAAO,CAAC,OAAO,EAAE,CAAA;IAElD,GAAG,CAAK,EAAoB;QAC1B,MAAM,MAAM,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,CAAC,CAAA;QACrC,IAAI,CAAC,IAAI,GAAG,MAAM,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAA;QACzC,OAAO,MAAM,CAAA;IACf,CAAC;CACF"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export interface RasterizeOptions {
|
|
2
|
+
/** CSS pixel width of the SVG (defaults to the width attribute) */
|
|
3
|
+
width?: number;
|
|
4
|
+
/** Pixel density multiplier (2 = retina). Default 2 */
|
|
5
|
+
scale?: number;
|
|
6
|
+
/** Background color; PNG has no page to inherit one from */
|
|
7
|
+
background?: string;
|
|
8
|
+
}
|
|
9
|
+
export declare function svgToPng(svg: string, options?: RasterizeOptions): Promise<Buffer>;
|
|
10
|
+
export declare const themeBackground: (theme: "light" | "dark") => string;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/** SVG → PNG rasterization via @resvg/resvg-js.
|
|
2
|
+
*
|
|
3
|
+
* The post-processed SVG is fully self-contained (styles inlined as
|
|
4
|
+
* attributes, no classes, no var(), no external refs) — exactly what resvg
|
|
5
|
+
* handles well. Text is shaped with the same provisioned Inter fonts used
|
|
6
|
+
* for measurement, so PNG output matches the measured layout.
|
|
7
|
+
*
|
|
8
|
+
* resvg ignores the CSS `background-color` style on the root (it's not an
|
|
9
|
+
* SVG rendering attribute), so the theme background is passed explicitly.
|
|
10
|
+
*/
|
|
11
|
+
import { ensureFontsDir } from '../env/fonts.js';
|
|
12
|
+
export async function svgToPng(svg, options = {}) {
|
|
13
|
+
const resvgModule = await import('@resvg/resvg-js');
|
|
14
|
+
const scale = options.scale ?? 2;
|
|
15
|
+
const width = options.width ?? parseFloat(/width="([\d.]+)"/.exec(svg)?.[1] ?? '') ?? undefined;
|
|
16
|
+
const fontsDir = await ensureFontsDir();
|
|
17
|
+
const resvg = new resvgModule.Resvg(svg, {
|
|
18
|
+
fitTo: width ? { mode: 'width', value: Math.round(width * scale) } : { mode: 'original' },
|
|
19
|
+
background: options.background,
|
|
20
|
+
font: {
|
|
21
|
+
fontDirs: fontsDir ? [fontsDir] : [],
|
|
22
|
+
defaultFontFamily: 'Inter',
|
|
23
|
+
loadSystemFonts: true,
|
|
24
|
+
},
|
|
25
|
+
});
|
|
26
|
+
return Buffer.from(resvg.render().asPng());
|
|
27
|
+
}
|
|
28
|
+
export const themeBackground = (theme) => theme === 'dark' ? '#292b34' : '#ffffff';
|
|
29
|
+
//# sourceMappingURL=rasterize.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rasterize.js","sourceRoot":"","sources":["../../src/render/rasterize.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAA;AAWhD,MAAM,CAAC,KAAK,UAAU,QAAQ,CAAE,GAAW,EAAE,UAA4B,EAAE;IACzE,MAAM,WAAW,GAAG,MAAM,MAAM,CAAC,iBAAiB,CAAC,CAAA;IAEnD,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,IAAI,CAAC,CAAA;IAChC,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,IAAI,UAAU,CAAC,kBAAkB,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,IAAI,SAAS,CAAA;IAC/F,MAAM,QAAQ,GAAG,MAAM,cAAc,EAAE,CAAA;IAEvC,MAAM,KAAK,GAAG,IAAI,WAAW,CAAC,KAAK,CAAC,GAAG,EAAE;QACvC,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,CAAC,KAAK,GAAG,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,UAAU,EAAE;QACzF,UAAU,EAAE,OAAO,CAAC,UAAU;QAC9B,IAAI,EAAE;YACJ,QAAQ,EAAE,QAAQ,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,EAAE;YACpC,iBAAiB,EAAE,OAAO;YAC1B,eAAe,EAAE,IAAI;SACtB;KACF,CAAC,CAAA;IACF,OAAO,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC,KAAK,EAAE,CAAC,CAAA;AAC5C,CAAC;AAED,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,KAAuB,EAAU,EAAE,CACjE,KAAK,KAAK,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,SAAS,CAAA"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/** Spec-driven rendering: ChartSpec in, standalone SVG out.
|
|
2
|
+
*
|
|
3
|
+
* The headless mechanics (DOM, frame flushing, serialization) live in
|
|
4
|
+
* headless.ts; this module only knows how to turn a spec into components.
|
|
5
|
+
*/
|
|
6
|
+
import { ChartInputError } from './materialize.js';
|
|
7
|
+
import type { RenderResult } from './headless.js';
|
|
8
|
+
import type { ChartSpec, ComponentSpec } from './spec.js';
|
|
9
|
+
export { ChartInputError };
|
|
10
|
+
export interface RenderOptions {
|
|
11
|
+
/** Deterministic id prefix (snapshot tests) */
|
|
12
|
+
idPrefix?: string;
|
|
13
|
+
/** Keep emotion classes / skip style inlining (debug) */
|
|
14
|
+
keepClasses?: boolean;
|
|
15
|
+
}
|
|
16
|
+
export type { RenderResult } from './headless.js';
|
|
17
|
+
/** Resolve `{ $unovisMap: name }` markers to lazily-imported TopoJSON data
|
|
18
|
+
* (the map bundles are large — only loaded when a map chart renders) and
|
|
19
|
+
* default `mapFeatureName` to the map's feature collection key. */
|
|
20
|
+
export declare function resolveMapMarkers(components: ComponentSpec[]): Promise<ComponentSpec[]>;
|
|
21
|
+
export declare function renderChart(spec: ChartSpec, options?: RenderOptions): Promise<RenderResult>;
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/** Spec-driven rendering: ChartSpec in, standalone SVG out.
|
|
2
|
+
*
|
|
3
|
+
* The headless mechanics (DOM, frame flushing, serialization) live in
|
|
4
|
+
* headless.ts; this module only knows how to turn a spec into components.
|
|
5
|
+
*/
|
|
6
|
+
import { materializeChart, ChartInputError, ASYNC_COMPONENTS } from './materialize.js';
|
|
7
|
+
import { renderToSvg } from './headless.js';
|
|
8
|
+
export { ChartInputError };
|
|
9
|
+
/** Unovis map names accepted in `{ $unovisMap: name }` config markers */
|
|
10
|
+
const UNOVIS_MAPS = new Set([
|
|
11
|
+
'WorldMapTopoJSON', 'WorldMapSimplestTopoJSON', 'WorldMap110mAlphaTopoJSON',
|
|
12
|
+
'USATopoJSON', 'USCountiesTopoJSON', 'GermanyTopoJSON', 'UKTopoJSON',
|
|
13
|
+
'FranceTopoJSON', 'IndiaTopoJSON', 'ChinaTopoJSON',
|
|
14
|
+
]);
|
|
15
|
+
/** Resolve `{ $unovisMap: name }` markers to lazily-imported TopoJSON data
|
|
16
|
+
* (the map bundles are large — only loaded when a map chart renders) and
|
|
17
|
+
* default `mapFeatureName` to the map's feature collection key. */
|
|
18
|
+
export async function resolveMapMarkers(components) {
|
|
19
|
+
return Promise.all(components.map(async (component) => {
|
|
20
|
+
const marker = component.config.topojson?.$unovisMap;
|
|
21
|
+
if (!marker)
|
|
22
|
+
return component;
|
|
23
|
+
if (!UNOVIS_MAPS.has(marker))
|
|
24
|
+
throw new ChartInputError(`Unknown Unovis map: ${marker}`);
|
|
25
|
+
// Explicit extension: @unovis/ts has no exports map, and ESM does not
|
|
26
|
+
// append .js to bare subpath imports (only the tsx loader tolerates it)
|
|
27
|
+
const maps = await import('@unovis/ts/maps.js');
|
|
28
|
+
const topojson = maps[marker];
|
|
29
|
+
if (!topojson)
|
|
30
|
+
throw new ChartInputError(`Map ${marker} is not available in this @unovis/ts build`);
|
|
31
|
+
return {
|
|
32
|
+
...component,
|
|
33
|
+
config: {
|
|
34
|
+
...component.config,
|
|
35
|
+
topojson,
|
|
36
|
+
mapFeatureName: component.config.mapFeatureName ?? Object.keys(topojson.objects)[0],
|
|
37
|
+
},
|
|
38
|
+
};
|
|
39
|
+
}));
|
|
40
|
+
}
|
|
41
|
+
export async function renderChart(spec, options = {}) {
|
|
42
|
+
const components = await resolveMapMarkers(spec.components);
|
|
43
|
+
const needsAsyncLayout = components.some(c => ASYNC_COMPONENTS.has(c.type));
|
|
44
|
+
return renderToSvg({
|
|
45
|
+
width: spec.width,
|
|
46
|
+
height: spec.height,
|
|
47
|
+
theme: spec.theme,
|
|
48
|
+
title: spec.title,
|
|
49
|
+
legend: spec.legend,
|
|
50
|
+
colors: spec.colors,
|
|
51
|
+
idPrefix: options.idPrefix,
|
|
52
|
+
keepClasses: options.keepClasses,
|
|
53
|
+
}, (ctx) => {
|
|
54
|
+
if (needsAsyncLayout)
|
|
55
|
+
ctx.requireComponentReady();
|
|
56
|
+
const materialized = materializeChart(ctx.unovis, { ...spec, components, width: ctx.width, height: ctx.height }, {
|
|
57
|
+
duration: 0,
|
|
58
|
+
onRenderComplete: ctx.onRenderComplete,
|
|
59
|
+
onComponentComplete: ctx.onComponentReady,
|
|
60
|
+
});
|
|
61
|
+
return materialized.containerType === 'xy'
|
|
62
|
+
? new ctx.unovis.XYContainer(ctx.container, materialized.containerConfig, materialized.data)
|
|
63
|
+
: new ctx.unovis.SingleContainer(ctx.container, materialized.containerConfig, materialized.data);
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
//# sourceMappingURL=renderer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"renderer.js","sourceRoot":"","sources":["../../src/render/renderer.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/** ChartSpec — a JSON-serializable intermediate representation of a chart.
|
|
2
|
+
*
|
|
3
|
+
* Tool inputs are converted to a ChartSpec (recipes), which the renderer
|
|
4
|
+
* materializes into live Unovis containers/components. Keeping the IR
|
|
5
|
+
* serializable enables the `config` output mode and, later, framework code
|
|
6
|
+
* generation. Accessors are descriptors referencing data fields by name —
|
|
7
|
+
* user input is never evaluated as code.
|
|
8
|
+
*/
|
|
9
|
+
export type FieldType = 'number' | 'string' | 'date';
|
|
10
|
+
/** Accessor descriptors (JSON-safe stand-ins for Unovis accessor functions) */
|
|
11
|
+
export type AccessorRef =
|
|
12
|
+
/** d => coerce(d[field]) */
|
|
13
|
+
{
|
|
14
|
+
$field: string;
|
|
15
|
+
as?: FieldType;
|
|
16
|
+
}
|
|
17
|
+
/** (d, i) => i */
|
|
18
|
+
| {
|
|
19
|
+
$index: true;
|
|
20
|
+
}
|
|
21
|
+
/** () => value */
|
|
22
|
+
| {
|
|
23
|
+
$const: unknown;
|
|
24
|
+
}
|
|
25
|
+
/** Multi-line time-aware tick formatter for date scales */
|
|
26
|
+
| {
|
|
27
|
+
$dateTickFormat: true;
|
|
28
|
+
}
|
|
29
|
+
/** Numeric tick formatter with thousands separators */
|
|
30
|
+
| {
|
|
31
|
+
$numTickFormat: true;
|
|
32
|
+
}
|
|
33
|
+
/** (d, i) => values[i % values.length] — e.g. ordinal index → category name */
|
|
34
|
+
| {
|
|
35
|
+
$lookup: (string | number)[];
|
|
36
|
+
}
|
|
37
|
+
/** d => prefix + formatNumber(d[field]) + suffix (empty string for missing values) */
|
|
38
|
+
| {
|
|
39
|
+
$format: {
|
|
40
|
+
field: string;
|
|
41
|
+
prefix?: string;
|
|
42
|
+
suffix?: string;
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
/** d => mapping[String(d[field])] ?? fallback — e.g. category → color */
|
|
46
|
+
| {
|
|
47
|
+
$mapField: {
|
|
48
|
+
field: string;
|
|
49
|
+
mapping: Record<string, string>;
|
|
50
|
+
fallback?: string;
|
|
51
|
+
};
|
|
52
|
+
};
|
|
53
|
+
export declare const isAccessorRef: (value: unknown) => value is AccessorRef;
|
|
54
|
+
/** Component config values: JSON values with AccessorRefs anywhere inside */
|
|
55
|
+
export type SpecConfigValue = unknown;
|
|
56
|
+
export interface ComponentSpec {
|
|
57
|
+
/** Unovis class name, e.g. 'Line', 'StackedBar', 'Donut' */
|
|
58
|
+
type: string;
|
|
59
|
+
config: Record<string, SpecConfigValue>;
|
|
60
|
+
}
|
|
61
|
+
export interface AxisSpec {
|
|
62
|
+
label?: string;
|
|
63
|
+
gridLine?: boolean;
|
|
64
|
+
numTicks?: number;
|
|
65
|
+
tickFormat?: AccessorRef;
|
|
66
|
+
domainLine?: boolean;
|
|
67
|
+
tickTextAngle?: number;
|
|
68
|
+
[key: string]: SpecConfigValue;
|
|
69
|
+
}
|
|
70
|
+
export interface LegendItemSpec {
|
|
71
|
+
name: string;
|
|
72
|
+
/** Resolved during post-processing when omitted (default palette) */
|
|
73
|
+
color?: string;
|
|
74
|
+
/** Palette index used when color is not set */
|
|
75
|
+
paletteIndex?: number;
|
|
76
|
+
}
|
|
77
|
+
export interface ChartSpec {
|
|
78
|
+
container: 'xy' | 'single';
|
|
79
|
+
width: number;
|
|
80
|
+
height: number;
|
|
81
|
+
theme: 'light' | 'dark';
|
|
82
|
+
title?: string;
|
|
83
|
+
/** Extra container config (xy: xDomain, yDirection…; single: sizing…) */
|
|
84
|
+
containerConfig?: Record<string, SpecConfigValue>;
|
|
85
|
+
components: ComponentSpec[];
|
|
86
|
+
xAxis?: AxisSpec;
|
|
87
|
+
yAxis?: AxisSpec;
|
|
88
|
+
/** Custom palette — overrides --vis-colorN for the whole chart */
|
|
89
|
+
colors?: string[];
|
|
90
|
+
legend?: LegendItemSpec[];
|
|
91
|
+
data: unknown;
|
|
92
|
+
}
|
|
93
|
+
export interface RenderWarnings {
|
|
94
|
+
warnings: string[];
|
|
95
|
+
}
|