@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,15 @@
|
|
|
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 const isAccessorRef = (value) => {
|
|
10
|
+
if (typeof value !== 'object' || value === null || Array.isArray(value))
|
|
11
|
+
return false;
|
|
12
|
+
const keys = Object.keys(value);
|
|
13
|
+
return keys.some(k => ['$field', '$index', '$const', '$dateTickFormat', '$numTickFormat', '$lookup', '$format', '$mapField'].includes(k));
|
|
14
|
+
};
|
|
15
|
+
//# sourceMappingURL=spec.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"spec.js","sourceRoot":"","sources":["../../src/render/spec.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAuBH,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC,KAAc,EAAwB,EAAE;IACpE,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;QAAE,OAAO,KAAK,CAAA;IACrF,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;IAC/B,OAAO,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,QAAQ,EAAE,QAAQ,EAAE,QAAQ,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,SAAS,EAAE,SAAS,EAAE,WAAW,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAA;AAC3I,CAAC,CAAA"}
|
package/dist/server.d.ts
ADDED
package/dist/server.js
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { join, dirname } from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
5
|
+
import { registerTools, WIDGET_URI } from './tools/register.js';
|
|
6
|
+
import { buildEmbedDocument } from './html/document.js';
|
|
7
|
+
export function getPackageVersion() {
|
|
8
|
+
try {
|
|
9
|
+
const pkgPath = join(dirname(fileURLToPath(import.meta.url)), '..', 'package.json');
|
|
10
|
+
return JSON.parse(readFileSync(pkgPath, 'utf8')).version ?? '0.0.0';
|
|
11
|
+
}
|
|
12
|
+
catch {
|
|
13
|
+
return '0.0.0';
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
export function buildServer(options = {}) {
|
|
17
|
+
const server = new McpServer({
|
|
18
|
+
name: 'unovis',
|
|
19
|
+
version: getPackageVersion(),
|
|
20
|
+
});
|
|
21
|
+
// eslint-disable-next-line @typescript-eslint/no-use-before-define
|
|
22
|
+
registerWidgetResource(server);
|
|
23
|
+
registerTools(server, options);
|
|
24
|
+
return server;
|
|
25
|
+
}
|
|
26
|
+
/** The interactive chart widget, served as an MCP UI resource.
|
|
27
|
+
*
|
|
28
|
+
* Clients that support embedded UI resources fetch this once and drive it with
|
|
29
|
+
* the spec from a tool call's structuredContent; the same document works as a
|
|
30
|
+
* plain iframe (see the embed protocol in src/widget/entry.ts). */
|
|
31
|
+
function registerWidgetResource(server) {
|
|
32
|
+
server.registerResource('chart-widget', WIDGET_URI, {
|
|
33
|
+
title: 'Unovis chart widget',
|
|
34
|
+
description: 'Interactive chart renderer. Send it a chart spec to render.',
|
|
35
|
+
mimeType: 'text/html+skybridge',
|
|
36
|
+
}, async () => ({
|
|
37
|
+
contents: [{
|
|
38
|
+
uri: WIDGET_URI,
|
|
39
|
+
mimeType: 'text/html+skybridge',
|
|
40
|
+
text: buildEmbedDocument(),
|
|
41
|
+
}],
|
|
42
|
+
}));
|
|
43
|
+
}
|
|
44
|
+
//# sourceMappingURL=server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA;AACtC,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AACzC,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAA;AAExC,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAA;AAEnE,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAA;AAE/D,OAAO,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAA;AAEvD,MAAM,UAAU,iBAAiB;IAC/B,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,EAAE,cAAc,CAAC,CAAA;QACnF,OAAO,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC,CAAC,OAAO,IAAI,OAAO,CAAA;IACrE,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,OAAO,CAAA;IAChB,CAAC;AACH,CAAC;AAED,MAAM,UAAU,WAAW,CAAE,UAA6B,EAAE;IAC1D,MAAM,MAAM,GAAG,IAAI,SAAS,CAAC;QAC3B,IAAI,EAAE,QAAQ;QACd,OAAO,EAAE,iBAAiB,EAAE;KAC7B,CAAC,CAAA;IACF,mEAAmE;IACnE,sBAAsB,CAAC,MAAM,CAAC,CAAA;IAC9B,aAAa,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IAC9B,OAAO,MAAM,CAAA;AACf,CAAC;AAED;;;;mEAImE;AACnE,SAAS,sBAAsB,CAAE,MAAiB;IAChD,MAAM,CAAC,gBAAgB,CACrB,cAAc,EACd,UAAU,EACV;QACE,KAAK,EAAE,qBAAqB;QAC5B,WAAW,EAAE,6DAA6D;QAC1E,QAAQ,EAAE,qBAAqB;KAChC,EACD,KAAK,IAAI,EAAE,CAAC,CAAC;QACX,QAAQ,EAAE,CAAC;gBACT,GAAG,EAAE,UAAU;gBACf,QAAQ,EAAE,qBAAqB;gBAC/B,IAAI,EAAE,kBAAkB,EAAE;aAC3B,CAAC;KACH,CAAC,CACH,CAAA;AACH,CAAC"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/** Harvest CSS rules from emotion's injected <style> tags.
|
|
2
|
+
*
|
|
3
|
+
* Emotion may run in "speedy" mode where rules are inserted through the
|
|
4
|
+
* CSSOM (`sheet.insertRule`) and the <style> tags have empty text content —
|
|
5
|
+
* so rules are read from `sheet.cssRules` first, falling back to textContent.
|
|
6
|
+
*/
|
|
7
|
+
export interface CollectedRule {
|
|
8
|
+
selector: string;
|
|
9
|
+
/** Declaration block text (without braces) */
|
|
10
|
+
block: string;
|
|
11
|
+
/** Full rule text */
|
|
12
|
+
cssText: string;
|
|
13
|
+
}
|
|
14
|
+
export declare function collectCssRules(document: Document): CollectedRule[];
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/** Harvest CSS rules from emotion's injected <style> tags.
|
|
2
|
+
*
|
|
3
|
+
* Emotion may run in "speedy" mode where rules are inserted through the
|
|
4
|
+
* CSSOM (`sheet.insertRule`) and the <style> tags have empty text content —
|
|
5
|
+
* so rules are read from `sheet.cssRules` first, falling back to textContent.
|
|
6
|
+
*/
|
|
7
|
+
export function collectCssRules(document) {
|
|
8
|
+
const rules = [];
|
|
9
|
+
for (const styleEl of Array.from(document.querySelectorAll('style'))) {
|
|
10
|
+
const sheet = styleEl.sheet;
|
|
11
|
+
if (sheet) {
|
|
12
|
+
let cssRules;
|
|
13
|
+
try {
|
|
14
|
+
cssRules = sheet.cssRules;
|
|
15
|
+
}
|
|
16
|
+
catch { /* cross-origin or detached sheets */ }
|
|
17
|
+
if (cssRules && cssRules.length) {
|
|
18
|
+
// eslint-disable-next-line @typescript-eslint/no-use-before-define
|
|
19
|
+
for (const rule of Array.from(cssRules))
|
|
20
|
+
collectRule(rule, rules);
|
|
21
|
+
continue;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
const text = styleEl.textContent;
|
|
25
|
+
if (text) {
|
|
26
|
+
for (const match of text.matchAll(/([^{}]+)\{([^{}]*)\}/g)) {
|
|
27
|
+
rules.push({ selector: match[1].trim(), block: match[2].trim(), cssText: match[0] });
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return rules;
|
|
32
|
+
}
|
|
33
|
+
function collectRule(rule, out) {
|
|
34
|
+
// CSSStyleRule
|
|
35
|
+
const styleRule = rule;
|
|
36
|
+
if (styleRule.selectorText !== undefined) {
|
|
37
|
+
const cssText = rule.cssText || '';
|
|
38
|
+
const blockMatch = /\{([\s\S]*)\}/.exec(cssText);
|
|
39
|
+
out.push({
|
|
40
|
+
selector: styleRule.selectorText,
|
|
41
|
+
block: blockMatch ? blockMatch[1].trim() : '',
|
|
42
|
+
cssText,
|
|
43
|
+
});
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
// Grouping rules (@media, @supports) — recurse
|
|
47
|
+
const grouping = rule;
|
|
48
|
+
if (grouping.cssRules) {
|
|
49
|
+
for (const child of Array.from(grouping.cssRules))
|
|
50
|
+
collectRule(child, out);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
//# sourceMappingURL=collect-css.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"collect-css.js","sourceRoot":"","sources":["../../src/svg/collect-css.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAUH,MAAM,UAAU,eAAe,CAAE,QAAkB;IACjD,MAAM,KAAK,GAAoB,EAAE,CAAA;IAEjC,KAAK,MAAM,OAAO,IAAI,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,gBAAgB,CAAC,OAAO,CAAC,CAAC,EAAE,CAAC;QACrE,MAAM,KAAK,GAAI,OAA4B,CAAC,KAAK,CAAA;QACjD,IAAI,KAAK,EAAE,CAAC;YACV,IAAI,QAAiC,CAAA;YACrC,IAAI,CAAC;gBACH,QAAQ,GAAG,KAAK,CAAC,QAAQ,CAAA;YAC3B,CAAC;YAAC,MAAM,CAAC,CAAC,qCAAqC,CAAC,CAAC;YACjD,IAAI,QAAQ,IAAI,QAAQ,CAAC,MAAM,EAAE,CAAC;gBAChC,mEAAmE;gBACnE,KAAK,MAAM,IAAI,IAAI,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC;oBAAE,WAAW,CAAC,IAAI,EAAE,KAAK,CAAC,CAAA;gBACjE,SAAQ;YACV,CAAC;QACH,CAAC;QACD,MAAM,IAAI,GAAG,OAAO,CAAC,WAAW,CAAA;QAChC,IAAI,IAAI,EAAE,CAAC;YACT,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,QAAQ,CAAC,uBAAuB,CAAC,EAAE,CAAC;gBAC3D,KAAK,CAAC,IAAI,CAAC,EAAE,QAAQ,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,EAAE,OAAO,EAAE,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC,CAAA;YACtF,CAAC;QACH,CAAC;IACH,CAAC;IACD,OAAO,KAAK,CAAA;AACd,CAAC;AAED,SAAS,WAAW,CAAE,IAAa,EAAE,GAAoB;IACvD,eAAe;IACf,MAAM,SAAS,GAAG,IAAoB,CAAA;IACtC,IAAI,SAAS,CAAC,YAAY,KAAK,SAAS,EAAE,CAAC;QACzC,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,IAAI,EAAE,CAAA;QAClC,MAAM,UAAU,GAAG,eAAe,CAAC,IAAI,CAAC,OAAO,CAAC,CAAA;QAChD,GAAG,CAAC,IAAI,CAAC;YACP,QAAQ,EAAE,SAAS,CAAC,YAAY;YAChC,KAAK,EAAE,UAAU,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE;YAC7C,OAAO;SACR,CAAC,CAAA;QACF,OAAM;IACR,CAAC;IACD,+CAA+C;IAC/C,MAAM,QAAQ,GAAG,IAAuB,CAAA;IACxC,IAAI,QAAQ,CAAC,QAAQ,EAAE,CAAC;QACtB,KAAK,MAAM,KAAK,IAAI,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC;YAAE,WAAW,CAAC,KAAK,EAAE,GAAG,CAAC,CAAA;IAC5E,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/** CSS custom-property baking for standalone SVG output.
|
|
2
|
+
*
|
|
3
|
+
* The rendered DOM references theme values as `var(--vis-*)` (in inline
|
|
4
|
+
* styles and presentation attributes). Standalone SVG viewers — and PNG
|
|
5
|
+
* rasterizers — can't be relied on to support custom properties, so every
|
|
6
|
+
* `var()` reference is resolved to a literal value.
|
|
7
|
+
*/
|
|
8
|
+
import type { VarMaps } from '../env/computed-style.js';
|
|
9
|
+
export interface VarContext {
|
|
10
|
+
maps: VarMaps;
|
|
11
|
+
theme: 'light' | 'dark';
|
|
12
|
+
/** Highest-priority overrides (e.g. a custom palette) */
|
|
13
|
+
overrides?: Map<string, string>;
|
|
14
|
+
}
|
|
15
|
+
export declare function lookupVar(name: string, el: Element | null, ctx: VarContext): string | undefined;
|
|
16
|
+
export declare function substituteVarsForElement(value: string, el: Element | null, ctx: VarContext, depth?: number): string;
|
|
17
|
+
export declare function bakeCssVars(svg: SVGSVGElement, ctx: VarContext): void;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
const VAR_PATTERN = /var\(\s*(--[\w-]+)\s*(?:,\s*([^()]*(?:\([^()]*\)[^()]*)*))?\)/;
|
|
2
|
+
export function lookupVar(name, el, ctx) {
|
|
3
|
+
// Inline definitions on the element chain win (matches browser behavior)
|
|
4
|
+
let node = el;
|
|
5
|
+
while (node) {
|
|
6
|
+
const inline = node.style?.getPropertyValue(name);
|
|
7
|
+
if (inline)
|
|
8
|
+
return inline;
|
|
9
|
+
node = node.parentElement;
|
|
10
|
+
}
|
|
11
|
+
if (ctx.overrides?.has(name))
|
|
12
|
+
return ctx.overrides.get(name);
|
|
13
|
+
if (ctx.theme === 'dark' && ctx.maps.dark.has(name))
|
|
14
|
+
return ctx.maps.dark.get(name);
|
|
15
|
+
return ctx.maps.light.get(name);
|
|
16
|
+
}
|
|
17
|
+
export function substituteVarsForElement(value, el, ctx, depth = 0) {
|
|
18
|
+
if (depth > 10)
|
|
19
|
+
return value;
|
|
20
|
+
let result = value;
|
|
21
|
+
let match;
|
|
22
|
+
while ((match = VAR_PATTERN.exec(result))) {
|
|
23
|
+
const [full, name, fallback] = match;
|
|
24
|
+
const resolvedRaw = lookupVar(name, el, ctx);
|
|
25
|
+
const replacement = resolvedRaw !== undefined
|
|
26
|
+
? substituteVarsForElement(resolvedRaw, el, ctx, depth + 1)
|
|
27
|
+
: (fallback !== undefined ? substituteVarsForElement(fallback.trim(), el, ctx, depth + 1) : '');
|
|
28
|
+
result = result.slice(0, match.index) + replacement + result.slice(match.index + full.length);
|
|
29
|
+
}
|
|
30
|
+
return result;
|
|
31
|
+
}
|
|
32
|
+
/** Attributes that commonly carry var() references or url() functions */
|
|
33
|
+
const PAINT_ATTRS = ['fill', 'stroke', 'color', 'stop-color', 'marker', 'marker-start', 'marker-mid', 'marker-end', 'mask', 'filter', 'stroke-dasharray', 'flood-color'];
|
|
34
|
+
export function bakeCssVars(svg, ctx) {
|
|
35
|
+
const elements = [svg, ...Array.from(svg.querySelectorAll('*'))];
|
|
36
|
+
for (const el of elements) {
|
|
37
|
+
const style = el.style;
|
|
38
|
+
if (style && style.length !== undefined) {
|
|
39
|
+
const props = Array.from({ length: style.length }, (_, i) => style.item(i));
|
|
40
|
+
for (const prop of props) {
|
|
41
|
+
const value = style.getPropertyValue(prop);
|
|
42
|
+
if (!value)
|
|
43
|
+
continue;
|
|
44
|
+
if (prop.startsWith('--'))
|
|
45
|
+
continue; // custom-prop definitions are dropped below
|
|
46
|
+
if (value.includes('var(')) {
|
|
47
|
+
style.setProperty(prop, substituteVarsForElement(value, el, ctx), style.getPropertyPriority(prop));
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
// Drop custom-property definitions after all references were resolved
|
|
51
|
+
for (const prop of props) {
|
|
52
|
+
if (prop.startsWith('--'))
|
|
53
|
+
style.removeProperty(prop);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
for (const attr of PAINT_ATTRS) {
|
|
57
|
+
const value = el.getAttribute(attr);
|
|
58
|
+
if (value && value.includes('var(')) {
|
|
59
|
+
el.setAttribute(attr, substituteVarsForElement(value, el, ctx));
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
//# sourceMappingURL=css-vars.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"css-vars.js","sourceRoot":"","sources":["../../src/svg/css-vars.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { VarContext } from './css-vars.js';
|
|
2
|
+
import type { LegendItemSpec } from '../render/spec.js';
|
|
3
|
+
export interface HeaderSpec {
|
|
4
|
+
title?: string;
|
|
5
|
+
legend?: LegendItemSpec[];
|
|
6
|
+
fontFamily: string;
|
|
7
|
+
}
|
|
8
|
+
export declare function renderHeader(svg: SVGSVGElement, document: Document, header: HeaderSpec, ctx: VarContext, width: number): number;
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/** Title and legend synthesis.
|
|
2
|
+
*
|
|
3
|
+
* Unovis containers have no title concept, and its legends (BulletLegend) are
|
|
4
|
+
* HTML components rendered outside the SVG — neither can appear in standalone
|
|
5
|
+
* SVG output. Both are synthesized here: the chart content is wrapped in a
|
|
6
|
+
* translated group and a header block (title + swatch legend) is drawn above.
|
|
7
|
+
*/
|
|
8
|
+
import { measureTextWidth } from '../env/canvas.js';
|
|
9
|
+
import { substituteVarsForElement } from './css-vars.js';
|
|
10
|
+
const SVG_NS = 'http://www.w3.org/2000/svg';
|
|
11
|
+
const TITLE_FONT_SIZE = 16;
|
|
12
|
+
const TITLE_ROW_HEIGHT = 28;
|
|
13
|
+
const LEGEND_FONT_SIZE = 12;
|
|
14
|
+
const LEGEND_ROW_HEIGHT = 22;
|
|
15
|
+
const LEGEND_SWATCH = 10;
|
|
16
|
+
const LEGEND_GAP = 16;
|
|
17
|
+
const PADDING_X = 16;
|
|
18
|
+
const PADDING_TOP = 12;
|
|
19
|
+
const PADDING_BOTTOM = 4;
|
|
20
|
+
export function renderHeader(svg, document, header, ctx, width) {
|
|
21
|
+
if (!header.title && !header.legend?.length)
|
|
22
|
+
return 0;
|
|
23
|
+
const dark = ctx.theme === 'dark';
|
|
24
|
+
const titleColor = dark ? '#f2f4f9' : '#1f2937';
|
|
25
|
+
const labelColor = dark ? '#c8ced9' : '#4b5563';
|
|
26
|
+
const font = (size) => `${size}px ${header.fontFamily}`;
|
|
27
|
+
// Lay out legend items with wrapping
|
|
28
|
+
const items = [];
|
|
29
|
+
let legendRows = 0;
|
|
30
|
+
if (header.legend?.length) {
|
|
31
|
+
let x = PADDING_X;
|
|
32
|
+
let row = 0;
|
|
33
|
+
for (const item of header.legend) {
|
|
34
|
+
const color = item.color ?? substituteVarsForElement(`var(--vis-color${(item.paletteIndex ?? 0) % 6})`, null, ctx);
|
|
35
|
+
const labelWidth = measureTextWidth(item.name, font(LEGEND_FONT_SIZE));
|
|
36
|
+
const itemWidth = LEGEND_SWATCH + 6 + labelWidth;
|
|
37
|
+
if (x + itemWidth > width - PADDING_X && x > PADDING_X) {
|
|
38
|
+
row += 1;
|
|
39
|
+
x = PADDING_X;
|
|
40
|
+
}
|
|
41
|
+
items.push({ name: item.name, color, x, row, labelWidth });
|
|
42
|
+
x += itemWidth + LEGEND_GAP;
|
|
43
|
+
}
|
|
44
|
+
legendRows = row + 1;
|
|
45
|
+
}
|
|
46
|
+
const headerHeight = PADDING_TOP + (header.title ? TITLE_ROW_HEIGHT : 0) + legendRows * LEGEND_ROW_HEIGHT + PADDING_BOTTOM;
|
|
47
|
+
// The caller wraps the chart content and offsets it below the header
|
|
48
|
+
const headerGroup = document.createElementNS(SVG_NS, 'g');
|
|
49
|
+
svg.insertBefore(headerGroup, svg.firstChild);
|
|
50
|
+
if (header.title) {
|
|
51
|
+
const titleEl = document.createElementNS(SVG_NS, 'text');
|
|
52
|
+
titleEl.setAttribute('x', String(PADDING_X));
|
|
53
|
+
titleEl.setAttribute('y', String(PADDING_TOP + TITLE_FONT_SIZE));
|
|
54
|
+
titleEl.setAttribute('font-size', String(TITLE_FONT_SIZE));
|
|
55
|
+
titleEl.setAttribute('font-weight', '600');
|
|
56
|
+
titleEl.setAttribute('fill', titleColor);
|
|
57
|
+
titleEl.textContent = header.title;
|
|
58
|
+
headerGroup.appendChild(titleEl);
|
|
59
|
+
}
|
|
60
|
+
const legendOffsetY = PADDING_TOP + (header.title ? TITLE_ROW_HEIGHT : 0);
|
|
61
|
+
for (const item of items) {
|
|
62
|
+
const y = legendOffsetY + item.row * LEGEND_ROW_HEIGHT + (LEGEND_ROW_HEIGHT - LEGEND_SWATCH) / 2;
|
|
63
|
+
const swatch = document.createElementNS(SVG_NS, 'rect');
|
|
64
|
+
swatch.setAttribute('x', String(item.x));
|
|
65
|
+
swatch.setAttribute('y', String(y));
|
|
66
|
+
swatch.setAttribute('width', String(LEGEND_SWATCH));
|
|
67
|
+
swatch.setAttribute('height', String(LEGEND_SWATCH));
|
|
68
|
+
swatch.setAttribute('rx', '2');
|
|
69
|
+
swatch.setAttribute('fill', item.color);
|
|
70
|
+
headerGroup.appendChild(swatch);
|
|
71
|
+
const label = document.createElementNS(SVG_NS, 'text');
|
|
72
|
+
label.setAttribute('x', String(item.x + LEGEND_SWATCH + 6));
|
|
73
|
+
label.setAttribute('y', String(y + LEGEND_SWATCH - 1));
|
|
74
|
+
label.setAttribute('font-size', String(LEGEND_FONT_SIZE));
|
|
75
|
+
label.setAttribute('fill', labelColor);
|
|
76
|
+
label.textContent = item.name;
|
|
77
|
+
headerGroup.appendChild(label);
|
|
78
|
+
}
|
|
79
|
+
return headerHeight;
|
|
80
|
+
}
|
|
81
|
+
//# sourceMappingURL=header.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"header.js","sourceRoot":"","sources":["../../src/svg/header.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAA;AACnD,OAAO,EAAE,wBAAwB,EAAE,MAAM,eAAe,CAAA;AAIxD,MAAM,MAAM,GAAG,4BAA4B,CAAA;AAE3C,MAAM,eAAe,GAAG,EAAE,CAAA;AAC1B,MAAM,gBAAgB,GAAG,EAAE,CAAA;AAC3B,MAAM,gBAAgB,GAAG,EAAE,CAAA;AAC3B,MAAM,iBAAiB,GAAG,EAAE,CAAA;AAC5B,MAAM,aAAa,GAAG,EAAE,CAAA;AACxB,MAAM,UAAU,GAAG,EAAE,CAAA;AACrB,MAAM,SAAS,GAAG,EAAE,CAAA;AACpB,MAAM,WAAW,GAAG,EAAE,CAAA;AACtB,MAAM,cAAc,GAAG,CAAC,CAAA;AAgBxB,MAAM,UAAU,YAAY,CAAE,GAAkB,EAAE,QAAkB,EAAE,MAAkB,EAAE,GAAe,EAAE,KAAa;IACtH,IAAI,CAAC,MAAM,CAAC,KAAK,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM;QAAE,OAAO,CAAC,CAAA;IAErD,MAAM,IAAI,GAAG,GAAG,CAAC,KAAK,KAAK,MAAM,CAAA;IACjC,MAAM,UAAU,GAAG,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,SAAS,CAAA;IAC/C,MAAM,UAAU,GAAG,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,SAAS,CAAA;IAC/C,MAAM,IAAI,GAAG,CAAC,IAAY,EAAU,EAAE,CAAC,GAAG,IAAI,MAAM,MAAM,CAAC,UAAU,EAAE,CAAA;IAEvE,qCAAqC;IACrC,MAAM,KAAK,GAAuB,EAAE,CAAA;IACpC,IAAI,UAAU,GAAG,CAAC,CAAA;IAClB,IAAI,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC;QAC1B,IAAI,CAAC,GAAG,SAAS,CAAA;QACjB,IAAI,GAAG,GAAG,CAAC,CAAA;QACX,KAAK,MAAM,IAAI,IAAI,MAAM,CAAC,MAAM,EAAE,CAAC;YACjC,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,IAAI,wBAAwB,CAAC,kBAAkB,CAAC,IAAI,CAAC,YAAY,IAAI,CAAC,CAAC,GAAG,CAAC,GAAG,EAAE,IAAI,EAAE,GAAG,CAAC,CAAA;YAClH,MAAM,UAAU,GAAG,gBAAgB,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,gBAAgB,CAAC,CAAC,CAAA;YACtE,MAAM,SAAS,GAAG,aAAa,GAAG,CAAC,GAAG,UAAU,CAAA;YAChD,IAAI,CAAC,GAAG,SAAS,GAAG,KAAK,GAAG,SAAS,IAAI,CAAC,GAAG,SAAS,EAAE,CAAC;gBACvD,GAAG,IAAI,CAAC,CAAA;gBACR,CAAC,GAAG,SAAS,CAAA;YACf,CAAC;YACD,KAAK,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,KAAK,EAAE,CAAC,EAAE,GAAG,EAAE,UAAU,EAAE,CAAC,CAAA;YAC1D,CAAC,IAAI,SAAS,GAAG,UAAU,CAAA;QAC7B,CAAC;QACD,UAAU,GAAG,GAAG,GAAG,CAAC,CAAA;IACtB,CAAC;IAED,MAAM,YAAY,GAAG,WAAW,GAAG,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,gBAAgB,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,UAAU,GAAG,iBAAiB,GAAG,cAAc,CAAA;IAE1H,qEAAqE;IACrE,MAAM,WAAW,GAAG,QAAQ,CAAC,eAAe,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;IACzD,GAAG,CAAC,YAAY,CAAC,WAAW,EAAE,GAAG,CAAC,UAAU,CAAC,CAAA;IAE7C,IAAI,MAAM,CAAC,KAAK,EAAE,CAAC;QACjB,MAAM,OAAO,GAAG,QAAQ,CAAC,eAAe,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;QACxD,OAAO,CAAC,YAAY,CAAC,GAAG,EAAE,MAAM,CAAC,SAAS,CAAC,CAAC,CAAA;QAC5C,OAAO,CAAC,YAAY,CAAC,GAAG,EAAE,MAAM,CAAC,WAAW,GAAG,eAAe,CAAC,CAAC,CAAA;QAChE,OAAO,CAAC,YAAY,CAAC,WAAW,EAAE,MAAM,CAAC,eAAe,CAAC,CAAC,CAAA;QAC1D,OAAO,CAAC,YAAY,CAAC,aAAa,EAAE,KAAK,CAAC,CAAA;QAC1C,OAAO,CAAC,YAAY,CAAC,MAAM,EAAE,UAAU,CAAC,CAAA;QACxC,OAAO,CAAC,WAAW,GAAG,MAAM,CAAC,KAAK,CAAA;QAClC,WAAW,CAAC,WAAW,CAAC,OAAO,CAAC,CAAA;IAClC,CAAC;IAED,MAAM,aAAa,GAAG,WAAW,GAAG,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,gBAAgB,CAAC,CAAC,CAAC,CAAC,CAAC,CAAA;IACzE,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,MAAM,CAAC,GAAG,aAAa,GAAG,IAAI,CAAC,GAAG,GAAG,iBAAiB,GAAG,CAAC,iBAAiB,GAAG,aAAa,CAAC,GAAG,CAAC,CAAA;QAChG,MAAM,MAAM,GAAG,QAAQ,CAAC,eAAe,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;QACvD,MAAM,CAAC,YAAY,CAAC,GAAG,EAAE,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAA;QACxC,MAAM,CAAC,YAAY,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,CAAA;QACnC,MAAM,CAAC,YAAY,CAAC,OAAO,EAAE,MAAM,CAAC,aAAa,CAAC,CAAC,CAAA;QACnD,MAAM,CAAC,YAAY,CAAC,QAAQ,EAAE,MAAM,CAAC,aAAa,CAAC,CAAC,CAAA;QACpD,MAAM,CAAC,YAAY,CAAC,IAAI,EAAE,GAAG,CAAC,CAAA;QAC9B,MAAM,CAAC,YAAY,CAAC,MAAM,EAAE,IAAI,CAAC,KAAK,CAAC,CAAA;QACvC,WAAW,CAAC,WAAW,CAAC,MAAM,CAAC,CAAA;QAE/B,MAAM,KAAK,GAAG,QAAQ,CAAC,eAAe,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;QACtD,KAAK,CAAC,YAAY,CAAC,GAAG,EAAE,MAAM,CAAC,IAAI,CAAC,CAAC,GAAG,aAAa,GAAG,CAAC,CAAC,CAAC,CAAA;QAC3D,KAAK,CAAC,YAAY,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC,GAAG,aAAa,GAAG,CAAC,CAAC,CAAC,CAAA;QACtD,KAAK,CAAC,YAAY,CAAC,WAAW,EAAE,MAAM,CAAC,gBAAgB,CAAC,CAAC,CAAA;QACzD,KAAK,CAAC,YAAY,CAAC,MAAM,EAAE,UAAU,CAAC,CAAA;QACtC,KAAK,CAAC,WAAW,GAAG,IAAI,CAAC,IAAI,CAAA;QAC7B,WAAW,CAAC,WAAW,CAAC,KAAK,CAAC,CAAA;IAChC,CAAC;IAED,OAAO,YAAY,CAAA;AACrB,CAAC"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/** Id rewriting and url() reference normalization.
|
|
2
|
+
*
|
|
3
|
+
* Rendered charts contain random `guid()` ids (clip paths, filters) and
|
|
4
|
+
* absolute references like `url(http://localhost/#saturate)` (built from
|
|
5
|
+
* `window.location.href` in the XYContainer constructor). For standalone,
|
|
6
|
+
* embeddable SVG we rewrite ids with a deterministic prefix and strip the
|
|
7
|
+
* base URL from all references.
|
|
8
|
+
*/
|
|
9
|
+
/** Copy referenced defs living outside the chart SVG (e.g. globally injected
|
|
10
|
+
* pattern defs) into the chart's own defs so the output is self-contained. */
|
|
11
|
+
export declare function importExternalDefs(svg: SVGSVGElement, document: Document): void;
|
|
12
|
+
export declare function rewriteIds(svg: SVGSVGElement, prefix: string): void;
|
package/dist/svg/ids.js
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/** Id rewriting and url() reference normalization.
|
|
2
|
+
*
|
|
3
|
+
* Rendered charts contain random `guid()` ids (clip paths, filters) and
|
|
4
|
+
* absolute references like `url(http://localhost/#saturate)` (built from
|
|
5
|
+
* `window.location.href` in the XYContainer constructor). For standalone,
|
|
6
|
+
* embeddable SVG we rewrite ids with a deterministic prefix and strip the
|
|
7
|
+
* base URL from all references.
|
|
8
|
+
*/
|
|
9
|
+
const URL_REF = /url\(["']?([^"')]+)["']?\)/g;
|
|
10
|
+
const extractRefId = (raw) => {
|
|
11
|
+
const hashIndex = raw.indexOf('#');
|
|
12
|
+
if (hashIndex === -1)
|
|
13
|
+
return undefined;
|
|
14
|
+
return raw.slice(hashIndex + 1);
|
|
15
|
+
};
|
|
16
|
+
/** Copy referenced defs living outside the chart SVG (e.g. globally injected
|
|
17
|
+
* pattern defs) into the chart's own defs so the output is self-contained. */
|
|
18
|
+
export function importExternalDefs(svg, document) {
|
|
19
|
+
const referencedIds = new Set();
|
|
20
|
+
for (const el of [svg, ...Array.from(svg.querySelectorAll('*'))]) {
|
|
21
|
+
for (const attr of Array.from(el.attributes)) {
|
|
22
|
+
for (const match of attr.value.matchAll(URL_REF)) {
|
|
23
|
+
const id = extractRefId(match[1]);
|
|
24
|
+
if (id)
|
|
25
|
+
referencedIds.add(id);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
const href = el.getAttribute('href') ?? el.getAttribute('xlink:href');
|
|
29
|
+
if (href?.startsWith('#'))
|
|
30
|
+
referencedIds.add(href.slice(1));
|
|
31
|
+
}
|
|
32
|
+
for (const id of referencedIds) {
|
|
33
|
+
if (svg.querySelector(`[id="${id}"]`))
|
|
34
|
+
continue;
|
|
35
|
+
const external = document.getElementById(id);
|
|
36
|
+
if (!external)
|
|
37
|
+
continue;
|
|
38
|
+
let defs = svg.querySelector(':scope > defs');
|
|
39
|
+
if (!defs) {
|
|
40
|
+
defs = document.createElementNS('http://www.w3.org/2000/svg', 'defs');
|
|
41
|
+
svg.prepend(defs);
|
|
42
|
+
}
|
|
43
|
+
defs.appendChild(external.cloneNode(true));
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
export function rewriteIds(svg, prefix) {
|
|
47
|
+
const idMap = new Map();
|
|
48
|
+
let counter = 0;
|
|
49
|
+
for (const el of Array.from(svg.querySelectorAll('[id]'))) {
|
|
50
|
+
const oldId = el.getAttribute('id');
|
|
51
|
+
const newId = `${prefix}${counter++}`;
|
|
52
|
+
idMap.set(oldId, newId);
|
|
53
|
+
el.setAttribute('id', newId);
|
|
54
|
+
}
|
|
55
|
+
const rewriteValue = (value) => {
|
|
56
|
+
let result = value.replace(URL_REF, (_match, ref) => {
|
|
57
|
+
const id = extractRefId(ref);
|
|
58
|
+
if (id === undefined)
|
|
59
|
+
return `url(${ref})`; // external URL without fragment — drop quotes consistently
|
|
60
|
+
const mapped = idMap.get(id) ?? id;
|
|
61
|
+
return `url(#${mapped})`;
|
|
62
|
+
});
|
|
63
|
+
if (result.startsWith('#')) {
|
|
64
|
+
const mapped = idMap.get(result.slice(1));
|
|
65
|
+
if (mapped)
|
|
66
|
+
result = `#${mapped}`;
|
|
67
|
+
}
|
|
68
|
+
return result;
|
|
69
|
+
};
|
|
70
|
+
for (const el of [svg, ...Array.from(svg.querySelectorAll('*'))]) {
|
|
71
|
+
for (const attr of Array.from(el.attributes)) {
|
|
72
|
+
if (attr.name === 'id')
|
|
73
|
+
continue;
|
|
74
|
+
if (attr.value.includes('url(') || attr.value.startsWith('#')) {
|
|
75
|
+
const rewritten = rewriteValue(attr.value);
|
|
76
|
+
if (rewritten !== attr.value)
|
|
77
|
+
el.setAttribute(attr.name, rewritten);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
const style = el.style;
|
|
81
|
+
if (style && style.length) {
|
|
82
|
+
for (let i = 0; i < style.length; i++) {
|
|
83
|
+
const prop = style.item(i);
|
|
84
|
+
const value = style.getPropertyValue(prop);
|
|
85
|
+
if (value.includes('url(')) {
|
|
86
|
+
const rewritten = rewriteValue(value);
|
|
87
|
+
if (rewritten !== value)
|
|
88
|
+
style.setProperty(prop, rewritten);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
//# sourceMappingURL=ids.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ids.js","sourceRoot":"","sources":["../../src/svg/ids.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { LegendItemSpec } from '../render/spec.js';
|
|
2
|
+
import type { VarMaps } from '../env/computed-style.js';
|
|
3
|
+
/** What the output frame needs to know about a chart — deliberately not a
|
|
4
|
+
* ChartSpec, so the SVG layer stays independent of the spec format. */
|
|
5
|
+
export interface SvgFrame {
|
|
6
|
+
width: number;
|
|
7
|
+
height: number;
|
|
8
|
+
theme?: 'light' | 'dark';
|
|
9
|
+
/** Rendered as a heading above the chart */
|
|
10
|
+
title?: string;
|
|
11
|
+
/** Rendered as swatch + label rows under the title */
|
|
12
|
+
legend?: LegendItemSpec[];
|
|
13
|
+
/** Palette overriding --vis-colorN */
|
|
14
|
+
colors?: string[];
|
|
15
|
+
}
|
|
16
|
+
export interface FinalizeContext {
|
|
17
|
+
document: Document;
|
|
18
|
+
frame: SvgFrame;
|
|
19
|
+
varMaps: VarMaps;
|
|
20
|
+
/** Deterministic id prefix (tests); random per render by default */
|
|
21
|
+
idPrefix?: string;
|
|
22
|
+
/** Keep emotion class attributes and skip style inlining (debug) */
|
|
23
|
+
keepClasses?: boolean;
|
|
24
|
+
/** Frame padding; defaults to CHART_PADDING */
|
|
25
|
+
padding?: {
|
|
26
|
+
top: number;
|
|
27
|
+
right: number;
|
|
28
|
+
bottom: number;
|
|
29
|
+
left: number;
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
/** Frame around the chart so content never touches the image edges.
|
|
33
|
+
* The requested width/height are the final image size — the renderer draws
|
|
34
|
+
* the chart at the inner size and the content is offset here. */
|
|
35
|
+
export declare const CHART_PADDING: {
|
|
36
|
+
top: number;
|
|
37
|
+
right: number;
|
|
38
|
+
bottom: number;
|
|
39
|
+
left: number;
|
|
40
|
+
};
|
|
41
|
+
export declare function finalizeSvg(svg: SVGSVGElement, ctx: FinalizeContext): string;
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
/** Turn a rendered chart's live SVG element into a standalone SVG string.
|
|
2
|
+
*
|
|
3
|
+
* The rendered DOM depends on the page context: emotion classes reference
|
|
4
|
+
* stylesheet rules, colors are `var(--vis-*)` references, url(#) refs may be
|
|
5
|
+
* absolute, ids are random guids. This pipeline makes the SVG self-contained:
|
|
6
|
+
*
|
|
7
|
+
* 1. import externally-injected defs that the chart references
|
|
8
|
+
* 2. inline matched stylesheet declarations as inline styles (classes are
|
|
9
|
+
* dropped afterwards — survives sanitizers, <img> embedding, rasterizers)
|
|
10
|
+
* 3. bake every var() reference into a literal value (theme-aware)
|
|
11
|
+
* 4. synthesize the title/legend header
|
|
12
|
+
* 5. rewrite ids with a deterministic prefix and normalize url() refs
|
|
13
|
+
* 6. root hygiene: dimensions, viewBox, role, font-family, cleanup
|
|
14
|
+
*/
|
|
15
|
+
import { collectCssRules } from './collect-css.js';
|
|
16
|
+
import { bakeCssVars, substituteVarsForElement } from './css-vars.js';
|
|
17
|
+
import { importExternalDefs, rewriteIds } from './ids.js';
|
|
18
|
+
import { renderHeader } from './header.js';
|
|
19
|
+
const PSEUDO_SELECTOR = /:(hover|focus|active|visited|checked|disabled)|::/;
|
|
20
|
+
/** Frame around the chart so content never touches the image edges.
|
|
21
|
+
* The requested width/height are the final image size — the renderer draws
|
|
22
|
+
* the chart at the inner size and the content is offset here. */
|
|
23
|
+
export const CHART_PADDING = { top: 12, right: 16, bottom: 16, left: 16 };
|
|
24
|
+
export function finalizeSvg(svg, ctx) {
|
|
25
|
+
const { document, frame } = ctx;
|
|
26
|
+
const spec = frame;
|
|
27
|
+
const padding = ctx.padding ?? CHART_PADDING;
|
|
28
|
+
const overrides = new Map();
|
|
29
|
+
spec.colors?.forEach((color, i) => {
|
|
30
|
+
overrides.set(`--vis-color${i}`, color);
|
|
31
|
+
overrides.set(`--vis-dark-color${i}`, color);
|
|
32
|
+
});
|
|
33
|
+
const varCtx = { maps: ctx.varMaps, theme: spec.theme ?? 'light', overrides };
|
|
34
|
+
importExternalDefs(svg, document);
|
|
35
|
+
// eslint-disable-next-line @typescript-eslint/no-use-before-define
|
|
36
|
+
if (!ctx.keepClasses)
|
|
37
|
+
inlineStyles(svg, document, varCtx);
|
|
38
|
+
bakeCssVars(svg, varCtx);
|
|
39
|
+
const fontFamily = substituteVarsForElement('var(--vis-font-family)', null, varCtx) ||
|
|
40
|
+
'Inter, Arial, "Helvetica Neue", Helvetica, sans-serif';
|
|
41
|
+
// Frame the chart: wrap the rendered content and offset it by the padding
|
|
42
|
+
// (and below the synthesized header). width/height stay the requested size.
|
|
43
|
+
const width = spec.width;
|
|
44
|
+
const contentGroup = document.createElementNS('http://www.w3.org/2000/svg', 'g');
|
|
45
|
+
while (svg.firstChild)
|
|
46
|
+
contentGroup.appendChild(svg.firstChild);
|
|
47
|
+
svg.appendChild(contentGroup);
|
|
48
|
+
const headerHeight = renderHeader(svg, document, {
|
|
49
|
+
title: spec.title,
|
|
50
|
+
legend: spec.legend,
|
|
51
|
+
fontFamily,
|
|
52
|
+
}, varCtx, width);
|
|
53
|
+
contentGroup.setAttribute('transform', `translate(${padding.left},${headerHeight + padding.top})`);
|
|
54
|
+
rewriteIds(svg, ctx.idPrefix ?? `uv${Math.random().toString(36).slice(2, 6)}-`);
|
|
55
|
+
// Root hygiene
|
|
56
|
+
const totalHeight = spec.height + headerHeight;
|
|
57
|
+
svg.setAttribute('width', String(width));
|
|
58
|
+
svg.setAttribute('height', String(totalHeight));
|
|
59
|
+
svg.setAttribute('viewBox', `0 0 ${width} ${totalHeight}`);
|
|
60
|
+
svg.setAttribute('xmlns', 'http://www.w3.org/2000/svg');
|
|
61
|
+
svg.removeAttribute('aria-hidden');
|
|
62
|
+
svg.setAttribute('role', 'img');
|
|
63
|
+
svg.style.removeProperty('display');
|
|
64
|
+
svg.style.setProperty('font-family', fontFamily);
|
|
65
|
+
if (spec.theme === 'dark')
|
|
66
|
+
svg.style.setProperty('background-color', '#292b34');
|
|
67
|
+
if (spec.title) {
|
|
68
|
+
const titleEl = document.createElementNS('http://www.w3.org/2000/svg', 'title');
|
|
69
|
+
titleEl.textContent = spec.title;
|
|
70
|
+
svg.prepend(titleEl);
|
|
71
|
+
}
|
|
72
|
+
if (!ctx.keepClasses) {
|
|
73
|
+
for (const el of [svg, ...Array.from(svg.querySelectorAll('*'))]) {
|
|
74
|
+
el.removeAttribute('class');
|
|
75
|
+
el.removeAttribute('animating');
|
|
76
|
+
if (el.getAttribute('style') === '')
|
|
77
|
+
el.removeAttribute('style');
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return new ctx.document.defaultView.XMLSerializer().serializeToString(svg);
|
|
81
|
+
}
|
|
82
|
+
/** Apply matched stylesheet declarations as inline styles.
|
|
83
|
+
* jsdom's querySelectorAll does the selector matching; declarations never
|
|
84
|
+
* override author inline styles, while later rules override earlier ones.
|
|
85
|
+
* var() references are resolved at write time — jsdom's typed CSS properties
|
|
86
|
+
* (e.g. opacity) would otherwise mangle them into "NaN". */
|
|
87
|
+
function inlineStyles(svg, document, varCtx) {
|
|
88
|
+
const ruleApplied = new WeakMap();
|
|
89
|
+
for (const rule of collectCssRules(document)) {
|
|
90
|
+
if (rule.selector.includes(':root') || PSEUDO_SELECTOR.test(rule.selector))
|
|
91
|
+
continue;
|
|
92
|
+
let matches;
|
|
93
|
+
try {
|
|
94
|
+
matches = [
|
|
95
|
+
...(svg.matches(rule.selector) ? [svg] : []),
|
|
96
|
+
...Array.from(svg.querySelectorAll(rule.selector)),
|
|
97
|
+
];
|
|
98
|
+
}
|
|
99
|
+
catch {
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
if (!matches.length)
|
|
103
|
+
continue;
|
|
104
|
+
const declarations = [];
|
|
105
|
+
for (const match of rule.block.matchAll(/(--[\w-]+|[a-zA-Z-]+)\s*:\s*([^;]+)/g)) {
|
|
106
|
+
const prop = match[1].trim();
|
|
107
|
+
if (prop === 'label')
|
|
108
|
+
continue; // emotion debug labels
|
|
109
|
+
declarations.push([prop, match[2].trim()]);
|
|
110
|
+
}
|
|
111
|
+
if (!declarations.length)
|
|
112
|
+
continue;
|
|
113
|
+
for (const el of matches) {
|
|
114
|
+
const style = el.style;
|
|
115
|
+
if (!style)
|
|
116
|
+
continue;
|
|
117
|
+
let applied = ruleApplied.get(el);
|
|
118
|
+
if (!applied) {
|
|
119
|
+
applied = new Set();
|
|
120
|
+
ruleApplied.set(el, applied);
|
|
121
|
+
}
|
|
122
|
+
for (const [prop, value] of declarations) {
|
|
123
|
+
const existing = style.getPropertyValue(prop);
|
|
124
|
+
if (existing && !applied.has(prop))
|
|
125
|
+
continue; // author inline style wins
|
|
126
|
+
const resolved = !prop.startsWith('--') && value.includes('var(')
|
|
127
|
+
? substituteVarsForElement(value, el, varCtx)
|
|
128
|
+
: value;
|
|
129
|
+
style.setProperty(prop, resolved);
|
|
130
|
+
applied.add(prop);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
//# sourceMappingURL=postprocess.js.map
|