@opendata-ai/openchart-vanilla 7.7.0 → 7.8.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/dist/{chunk-PSMDJEXK.js → chunk-KYRQV6KR.js} +343 -3
- package/dist/chunk-KYRQV6KR.js.map +1 -0
- package/dist/index.js +14 -357
- package/dist/index.js.map +1 -1
- package/dist/static.d.ts +2 -2
- package/dist/static.js +37 -27
- package/dist/static.js.map +1 -1
- package/package.json +3 -3
- package/src/static.ts +42 -32
- package/src/tilemap-renderer.ts +2 -3
- package/dist/chunk-PSMDJEXK.js.map +0 -1
package/dist/static.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ThemeConfig, DarkMode, ChartSpec, LayerSpec } from '@opendata-ai/openchart-core';
|
|
1
|
+
import { ThemeConfig, DarkMode, ChartSpec, LayerSpec, TileMapSpec } from '@opendata-ai/openchart-core';
|
|
2
2
|
|
|
3
3
|
interface StaticRenderOptions {
|
|
4
4
|
width?: number;
|
|
@@ -24,6 +24,6 @@ interface StaticRenderOptions {
|
|
|
24
24
|
* The entire render pipeline is synchronous; the global swap is safe as long
|
|
25
25
|
* as no code schedules microtasks that outlive the call.
|
|
26
26
|
*/
|
|
27
|
-
declare function renderStaticSVG(spec: ChartSpec | LayerSpec, options?: StaticRenderOptions): string;
|
|
27
|
+
declare function renderStaticSVG(spec: ChartSpec | LayerSpec | TileMapSpec, options?: StaticRenderOptions): string;
|
|
28
28
|
|
|
29
29
|
export { type StaticRenderOptions, renderStaticSVG };
|
package/dist/static.js
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import {
|
|
2
2
|
SVG_NS,
|
|
3
3
|
renderChartSVG,
|
|
4
|
+
renderTileMapSVG,
|
|
4
5
|
resetSvgIdCounter
|
|
5
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-KYRQV6KR.js";
|
|
6
7
|
|
|
7
8
|
// src/static.ts
|
|
8
9
|
import { createRequire } from "module";
|
|
9
|
-
import { adaptForLightLineStroke, isLayerSpec } from "@opendata-ai/openchart-core";
|
|
10
|
-
import { compileChart, compileLayer } from "@opendata-ai/openchart-engine";
|
|
10
|
+
import { adaptForLightLineStroke, isLayerSpec, isTileMapSpec } from "@opendata-ai/openchart-core";
|
|
11
|
+
import { compileChart, compileLayer, compileTileMap } from "@opendata-ai/openchart-engine";
|
|
11
12
|
var esmRequire = createRequire(import.meta.url);
|
|
12
13
|
var cachedWindow;
|
|
13
14
|
function getHappyDomWindow() {
|
|
@@ -120,31 +121,40 @@ function renderStaticSVG(spec, options) {
|
|
|
120
121
|
darkMode,
|
|
121
122
|
watermark: options?.watermark
|
|
122
123
|
};
|
|
123
|
-
let
|
|
124
|
-
|
|
125
|
-
|
|
124
|
+
let svg;
|
|
125
|
+
let themeForStyle;
|
|
126
|
+
if (isTileMapSpec(spec)) {
|
|
127
|
+
const tileMapLayout = compileTileMap(spec, compileOpts);
|
|
128
|
+
svg = renderTileMapSVG(tileMapLayout, { animate: false });
|
|
129
|
+
themeForStyle = tileMapLayout.theme;
|
|
126
130
|
} else {
|
|
127
|
-
layout
|
|
131
|
+
let layout;
|
|
132
|
+
if (isLayerSpec(spec)) {
|
|
133
|
+
layout = compileLayer(spec, compileOpts);
|
|
134
|
+
} else {
|
|
135
|
+
layout = compileChart(spec, compileOpts);
|
|
136
|
+
}
|
|
137
|
+
const container = win.document.createElement("div");
|
|
138
|
+
Object.defineProperty(container, "getBoundingClientRect", {
|
|
139
|
+
value: () => ({
|
|
140
|
+
width,
|
|
141
|
+
height,
|
|
142
|
+
top: 0,
|
|
143
|
+
left: 0,
|
|
144
|
+
right: width,
|
|
145
|
+
bottom: height,
|
|
146
|
+
x: 0,
|
|
147
|
+
y: 0,
|
|
148
|
+
toJSON: () => ({})
|
|
149
|
+
})
|
|
150
|
+
});
|
|
151
|
+
svg = renderChartSVG(layout, container, {
|
|
152
|
+
animate: false,
|
|
153
|
+
crosshair: false
|
|
154
|
+
});
|
|
155
|
+
stripInteractiveElements(svg);
|
|
156
|
+
themeForStyle = layout.theme;
|
|
128
157
|
}
|
|
129
|
-
const container = win.document.createElement("div");
|
|
130
|
-
Object.defineProperty(container, "getBoundingClientRect", {
|
|
131
|
-
value: () => ({
|
|
132
|
-
width,
|
|
133
|
-
height,
|
|
134
|
-
top: 0,
|
|
135
|
-
left: 0,
|
|
136
|
-
right: width,
|
|
137
|
-
bottom: height,
|
|
138
|
-
x: 0,
|
|
139
|
-
y: 0,
|
|
140
|
-
toJSON: () => ({})
|
|
141
|
-
})
|
|
142
|
-
});
|
|
143
|
-
const svg = renderChartSVG(layout, container, {
|
|
144
|
-
animate: false,
|
|
145
|
-
crosshair: false
|
|
146
|
-
});
|
|
147
|
-
stripInteractiveElements(svg);
|
|
148
158
|
const doc = win.document;
|
|
149
159
|
let defs = svg.querySelector("defs");
|
|
150
160
|
if (!defs) {
|
|
@@ -152,7 +162,7 @@ function renderStaticSVG(spec, options) {
|
|
|
152
162
|
svg.insertBefore(defs, svg.firstChild);
|
|
153
163
|
}
|
|
154
164
|
const styleEl = doc.createElementNS(SVG_NS, "style");
|
|
155
|
-
styleEl.textContent = buildThemeStyleBlock(
|
|
165
|
+
styleEl.textContent = buildThemeStyleBlock(themeForStyle);
|
|
156
166
|
defs.insertBefore(styleEl, defs.firstChild);
|
|
157
167
|
const serializer = new win.XMLSerializer();
|
|
158
168
|
return serializer.serializeToString(svg);
|
package/dist/static.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/static.ts"],"sourcesContent":["// Node.js only — do not import from browser code.\nimport { createRequire } from 'node:module';\nimport type {\n ChartLayout,\n ChartSpec,\n CompileOptions,\n DarkMode,\n LayerSpec,\n ResolvedTheme,\n ThemeConfig,\n} from '@opendata-ai/openchart-core';\nimport { adaptForLightLineStroke, isLayerSpec } from '@opendata-ai/openchart-core';\nimport { compileChart, compileLayer } from '@opendata-ai/openchart-engine';\nimport { SVG_NS } from './renderers/svg-dom';\nimport { resetSvgIdCounter } from './svg-ids';\nimport { renderChartSVG } from './svg-renderer';\n\nconst esmRequire = createRequire(import.meta.url);\n\nlet cachedWindow: typeof import('happy-dom').Window | undefined;\nfunction getHappyDomWindow(): typeof import('happy-dom').Window {\n if (cachedWindow) return cachedWindow;\n try {\n ({ Window: cachedWindow } = esmRequire('happy-dom') as typeof import('happy-dom'));\n } catch {\n throw new Error(\n \"renderStaticSVG requires 'happy-dom' as a peer dependency. Install it with: npm add happy-dom\",\n );\n }\n return cachedWindow;\n}\n\nlet rendering = false;\n\nexport interface StaticRenderOptions {\n width?: number;\n height?: number;\n theme?: ThemeConfig;\n /**\n * Dark mode setting. In static rendering `'auto'` resolves to light mode\n * since there is no `matchMedia` to query. Use `'force'` for dark output.\n */\n darkMode?: DarkMode;\n watermark?: boolean;\n}\n\nfunction resolveStaticDarkMode(mode?: DarkMode): boolean {\n if (mode === 'force') return true;\n return false;\n}\n\nfunction buildThemeStyleBlock(theme: ResolvedTheme): string {\n const accent = theme.colors.categorical[0] ?? '#06b6d4';\n const bg =\n theme.colors.background === 'transparent'\n ? theme.isDark\n ? '#09090b'\n : '#ffffff'\n : theme.colors.background;\n\n const props = [\n `--oc-font-family: ${theme.fonts.family}`,\n `--oc-font-mono: ${theme.fonts.mono}`,\n `--oc-title-size: ${theme.chrome.title.fontSize}px`,\n `--oc-title-weight: ${theme.chrome.title.fontWeight}`,\n `--oc-title-tracking: -0.022em`, // sync with tokens.css\n `--oc-subtitle-size: ${theme.chrome.subtitle.fontSize}px`,\n `--oc-subtitle-weight: ${theme.chrome.subtitle.fontWeight}`,\n `--oc-source-size: ${theme.chrome.source.fontSize}px`,\n `--oc-source-weight: ${theme.chrome.source.fontWeight}`,\n `--oc-body-size: ${theme.fonts.sizes.body}px`,\n `--oc-eyebrow-size: ${theme.chrome.eyebrow.fontSize}px`,\n `--oc-eyebrow-weight: ${theme.chrome.eyebrow.fontWeight}`,\n `--oc-eyebrow-tracking: 0.08em`, // sync with tokens.css\n `--oc-bg: ${bg}`,\n `--oc-text: ${theme.colors.text}`,\n `--oc-text-muted: ${theme.colors.axis}`,\n `--oc-text-faint: ${theme.isDark ? '#52525b' : '#d4d4d8'}`,\n `--oc-gridline: ${theme.colors.gridline}`,\n `--oc-axis: ${theme.colors.axis}`,\n `--oc-border-radius: ${theme.borderRadius}px`,\n `--oc-accent: ${accent}`,\n `--oc-accent-strong: ${adaptForLightLineStroke(accent)}`,\n `--oc-positive: ${theme.colors.positive}`,\n `--oc-negative: ${theme.colors.negative}`,\n `--oc-legend-text: ${theme.isDark ? '#d0d6e0' : '#3f3f46'}`,\n `--oc-space-2: ${theme.spacing.chromeGap * 2}px`,\n `--oc-space-4: ${theme.spacing.padding}px`,\n ];\n\n const rules = [\n `svg.oc-chart { ${props.join('; ')}; }`,\n `.oc-chrome { font-family: var(--oc-font-family); }`,\n `.oc-eyebrow { font-size: var(--oc-eyebrow-size); font-weight: var(--oc-eyebrow-weight); letter-spacing: var(--oc-eyebrow-tracking); text-transform: uppercase; fill: var(--oc-accent); }`,\n `.oc-title { font-size: var(--oc-title-size); font-weight: var(--oc-title-weight); letter-spacing: var(--oc-title-tracking); fill: var(--oc-text); }`,\n `.oc-subtitle { font-size: var(--oc-subtitle-size); font-weight: var(--oc-subtitle-weight); fill: var(--oc-text-muted); }`,\n `.oc-source, .oc-byline, .oc-footer { font-size: var(--oc-source-size); font-weight: var(--oc-source-weight); fill: var(--oc-text-muted); }`,\n `.oc-brand { font-size: 11px; font-weight: 510; letter-spacing: 0.02em; fill: var(--oc-text-faint); }`,\n `.oc-brand-dot { fill: var(--oc-accent); }`,\n `.oc-eyebrow-dot { fill: var(--oc-accent); }`,\n `.oc-metrics { font-family: var(--oc-font-family); }`,\n `.oc-metric-label { font-size: 10px; font-weight: 510; letter-spacing: 0.08em; text-transform: uppercase; fill: var(--oc-text-muted); }`,\n `.oc-metric-value { font-size: 22px; font-weight: 510; letter-spacing: -0.01em; fill: var(--oc-text); font-variant-numeric: tabular-nums; }`,\n `.oc-metric-delta-up { fill: var(--oc-positive); font-size: 12px; font-weight: 510; }`,\n `.oc-metric-delta-down { fill: var(--oc-negative); font-size: 12px; font-weight: 510; }`,\n `.oc-axis-tick-inline { font-size: 11px; font-weight: 400; fill: var(--oc-text-muted); }`,\n `.oc-endpoint-labels { font-family: var(--oc-font-family); }`,\n `.oc-endpoint-label { fill: var(--oc-endpoint-label-color, var(--oc-text)); }`,\n `.oc-endpoint-value { fill: var(--oc-endpoint-value-color, var(--oc-text-muted)); }`,\n `.oc-endpoint-leader { stroke: var(--oc-endpoint-leader-color, currentColor); }`,\n `.oc-annotation-subtitle { fill: var(--oc-annotation-subtitle-color, var(--oc-text-muted)); }`,\n `.oc-metric-secondary { fill: var(--oc-positive); font-size: 12px; font-weight: 400; }`,\n `.oc-legend { font-family: var(--oc-font-family); font-size: var(--oc-body-size); }`,\n `.oc-legend-entry { cursor: default; }`,\n `.oc-legend text { fill: var(--oc-legend-text); }`,\n ];\n\n return rules.join('\\n');\n}\n\nfunction stripInteractiveElements(svg: Element): void {\n const selectors = ['[data-voronoi-overlay]', '[data-crosshair]', '[data-snap-dots]'];\n for (const selector of selectors) {\n const els = svg.querySelectorAll(selector);\n for (const el of els) {\n el.parentNode?.removeChild(el);\n }\n }\n}\n\n/**\n * Render a chart spec to a standalone SVG string without a browser DOM.\n *\n * Requires `happy-dom` as a peer dependency (`bun add happy-dom`).\n *\n * Not safe for concurrent invocation: the render pipeline relies on a global\n * SVG ID counter and a temporary global `document` swap, both of which are\n * single-threaded. In a server handling parallel requests, serialize calls\n * through a queue or mutex.\n *\n * The entire render pipeline is synchronous; the global swap is safe as long\n * as no code schedules microtasks that outlive the call.\n */\nexport function renderStaticSVG(\n spec: ChartSpec | LayerSpec,\n options?: StaticRenderOptions,\n): string {\n if (rendering) {\n throw new Error('renderStaticSVG is not reentrant — serialize calls through a queue or mutex');\n }\n rendering = true;\n\n const Window = getHappyDomWindow();\n const win = new Window({ url: 'about:blank' });\n\n const prevDocument = globalThis.document;\n const prevWindow = (globalThis as Record<string, unknown>).window;\n\n try {\n (globalThis as Record<string, unknown>).document = win.document;\n (globalThis as Record<string, unknown>).window = win;\n\n resetSvgIdCounter();\n\n const width = options?.width ?? 640;\n const height = options?.height ?? 420;\n const darkMode = resolveStaticDarkMode(options?.darkMode);\n\n const compileOpts: CompileOptions = {\n width,\n height,\n theme: options?.theme,\n darkMode,\n watermark: options?.watermark,\n };\n\n let layout: ChartLayout;\n if (isLayerSpec(spec)) {\n layout = compileLayer(spec, compileOpts);\n } else {\n layout = compileChart(spec, compileOpts);\n }\n\n const container = win.document.createElement('div');\n Object.defineProperty(container, 'getBoundingClientRect', {\n value: () => ({\n width,\n height,\n top: 0,\n left: 0,\n right: width,\n bottom: height,\n x: 0,\n y: 0,\n toJSON: () => ({}),\n }),\n });\n\n // renderChartSVG appends to container; the container and SVG are owned by\n // the happy-dom Window which is closed in the finally block.\n const svg = renderChartSVG(layout, container as unknown as HTMLElement, {\n animate: false,\n crosshair: false,\n });\n\n stripInteractiveElements(svg);\n\n const doc = win.document as unknown as Document;\n let defs = svg.querySelector('defs');\n if (!defs) {\n defs = doc.createElementNS(SVG_NS, 'defs');\n svg.insertBefore(defs as unknown as Node, svg.firstChild);\n }\n const styleEl = doc.createElementNS(SVG_NS, 'style');\n styleEl.textContent = buildThemeStyleBlock(layout.theme);\n defs.insertBefore(styleEl as unknown as Node, defs.firstChild);\n\n const serializer = new (\n win as unknown as { XMLSerializer: typeof XMLSerializer }\n ).XMLSerializer();\n return serializer.serializeToString(svg as unknown as Node);\n } finally {\n rendering = false;\n if (prevDocument !== undefined) {\n (globalThis as Record<string, unknown>).document = prevDocument;\n } else {\n delete (globalThis as Record<string, unknown>).document;\n }\n if (prevWindow !== undefined) {\n (globalThis as Record<string, unknown>).window = prevWindow;\n } else {\n delete (globalThis as Record<string, unknown>).window;\n }\n win.close();\n }\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/static.ts"],"sourcesContent":["// Node.js only — do not import from browser code.\nimport { createRequire } from 'node:module';\nimport type {\n ChartLayout,\n ChartSpec,\n CompileOptions,\n DarkMode,\n LayerSpec,\n ResolvedTheme,\n ThemeConfig,\n TileMapSpec,\n} from '@opendata-ai/openchart-core';\nimport { adaptForLightLineStroke, isLayerSpec, isTileMapSpec } from '@opendata-ai/openchart-core';\nimport { compileChart, compileLayer, compileTileMap } from '@opendata-ai/openchart-engine';\nimport { SVG_NS } from './renderers/svg-dom';\nimport { resetSvgIdCounter } from './svg-ids';\nimport { renderChartSVG } from './svg-renderer';\nimport { renderTileMapSVG } from './tilemap-renderer';\n\nconst esmRequire = createRequire(import.meta.url);\n\nlet cachedWindow: typeof import('happy-dom').Window | undefined;\nfunction getHappyDomWindow(): typeof import('happy-dom').Window {\n if (cachedWindow) return cachedWindow;\n try {\n ({ Window: cachedWindow } = esmRequire('happy-dom') as typeof import('happy-dom'));\n } catch {\n throw new Error(\n \"renderStaticSVG requires 'happy-dom' as a peer dependency. Install it with: npm add happy-dom\",\n );\n }\n return cachedWindow;\n}\n\nlet rendering = false;\n\nexport interface StaticRenderOptions {\n width?: number;\n height?: number;\n theme?: ThemeConfig;\n /**\n * Dark mode setting. In static rendering `'auto'` resolves to light mode\n * since there is no `matchMedia` to query. Use `'force'` for dark output.\n */\n darkMode?: DarkMode;\n watermark?: boolean;\n}\n\nfunction resolveStaticDarkMode(mode?: DarkMode): boolean {\n if (mode === 'force') return true;\n return false;\n}\n\nfunction buildThemeStyleBlock(theme: ResolvedTheme): string {\n const accent = theme.colors.categorical[0] ?? '#06b6d4';\n const bg =\n theme.colors.background === 'transparent'\n ? theme.isDark\n ? '#09090b'\n : '#ffffff'\n : theme.colors.background;\n\n const props = [\n `--oc-font-family: ${theme.fonts.family}`,\n `--oc-font-mono: ${theme.fonts.mono}`,\n `--oc-title-size: ${theme.chrome.title.fontSize}px`,\n `--oc-title-weight: ${theme.chrome.title.fontWeight}`,\n `--oc-title-tracking: -0.022em`, // sync with tokens.css\n `--oc-subtitle-size: ${theme.chrome.subtitle.fontSize}px`,\n `--oc-subtitle-weight: ${theme.chrome.subtitle.fontWeight}`,\n `--oc-source-size: ${theme.chrome.source.fontSize}px`,\n `--oc-source-weight: ${theme.chrome.source.fontWeight}`,\n `--oc-body-size: ${theme.fonts.sizes.body}px`,\n `--oc-eyebrow-size: ${theme.chrome.eyebrow.fontSize}px`,\n `--oc-eyebrow-weight: ${theme.chrome.eyebrow.fontWeight}`,\n `--oc-eyebrow-tracking: 0.08em`, // sync with tokens.css\n `--oc-bg: ${bg}`,\n `--oc-text: ${theme.colors.text}`,\n `--oc-text-muted: ${theme.colors.axis}`,\n `--oc-text-faint: ${theme.isDark ? '#52525b' : '#d4d4d8'}`,\n `--oc-gridline: ${theme.colors.gridline}`,\n `--oc-axis: ${theme.colors.axis}`,\n `--oc-border-radius: ${theme.borderRadius}px`,\n `--oc-accent: ${accent}`,\n `--oc-accent-strong: ${adaptForLightLineStroke(accent)}`,\n `--oc-positive: ${theme.colors.positive}`,\n `--oc-negative: ${theme.colors.negative}`,\n `--oc-legend-text: ${theme.isDark ? '#d0d6e0' : '#3f3f46'}`,\n `--oc-space-2: ${theme.spacing.chromeGap * 2}px`,\n `--oc-space-4: ${theme.spacing.padding}px`,\n ];\n\n const rules = [\n `svg.oc-chart { ${props.join('; ')}; }`,\n `.oc-chrome { font-family: var(--oc-font-family); }`,\n `.oc-eyebrow { font-size: var(--oc-eyebrow-size); font-weight: var(--oc-eyebrow-weight); letter-spacing: var(--oc-eyebrow-tracking); text-transform: uppercase; fill: var(--oc-accent); }`,\n `.oc-title { font-size: var(--oc-title-size); font-weight: var(--oc-title-weight); letter-spacing: var(--oc-title-tracking); fill: var(--oc-text); }`,\n `.oc-subtitle { font-size: var(--oc-subtitle-size); font-weight: var(--oc-subtitle-weight); fill: var(--oc-text-muted); }`,\n `.oc-source, .oc-byline, .oc-footer { font-size: var(--oc-source-size); font-weight: var(--oc-source-weight); fill: var(--oc-text-muted); }`,\n `.oc-brand { font-size: 11px; font-weight: 510; letter-spacing: 0.02em; fill: var(--oc-text-faint); }`,\n `.oc-brand-dot { fill: var(--oc-accent); }`,\n `.oc-eyebrow-dot { fill: var(--oc-accent); }`,\n `.oc-metrics { font-family: var(--oc-font-family); }`,\n `.oc-metric-label { font-size: 10px; font-weight: 510; letter-spacing: 0.08em; text-transform: uppercase; fill: var(--oc-text-muted); }`,\n `.oc-metric-value { font-size: 22px; font-weight: 510; letter-spacing: -0.01em; fill: var(--oc-text); font-variant-numeric: tabular-nums; }`,\n `.oc-metric-delta-up { fill: var(--oc-positive); font-size: 12px; font-weight: 510; }`,\n `.oc-metric-delta-down { fill: var(--oc-negative); font-size: 12px; font-weight: 510; }`,\n `.oc-axis-tick-inline { font-size: 11px; font-weight: 400; fill: var(--oc-text-muted); }`,\n `.oc-endpoint-labels { font-family: var(--oc-font-family); }`,\n `.oc-endpoint-label { fill: var(--oc-endpoint-label-color, var(--oc-text)); }`,\n `.oc-endpoint-value { fill: var(--oc-endpoint-value-color, var(--oc-text-muted)); }`,\n `.oc-endpoint-leader { stroke: var(--oc-endpoint-leader-color, currentColor); }`,\n `.oc-annotation-subtitle { fill: var(--oc-annotation-subtitle-color, var(--oc-text-muted)); }`,\n `.oc-metric-secondary { fill: var(--oc-positive); font-size: 12px; font-weight: 400; }`,\n `.oc-legend { font-family: var(--oc-font-family); font-size: var(--oc-body-size); }`,\n `.oc-legend-entry { cursor: default; }`,\n `.oc-legend text { fill: var(--oc-legend-text); }`,\n ];\n\n return rules.join('\\n');\n}\n\nfunction stripInteractiveElements(svg: Element): void {\n const selectors = ['[data-voronoi-overlay]', '[data-crosshair]', '[data-snap-dots]'];\n for (const selector of selectors) {\n const els = svg.querySelectorAll(selector);\n for (const el of els) {\n el.parentNode?.removeChild(el);\n }\n }\n}\n\n/**\n * Render a chart spec to a standalone SVG string without a browser DOM.\n *\n * Requires `happy-dom` as a peer dependency (`bun add happy-dom`).\n *\n * Not safe for concurrent invocation: the render pipeline relies on a global\n * SVG ID counter and a temporary global `document` swap, both of which are\n * single-threaded. In a server handling parallel requests, serialize calls\n * through a queue or mutex.\n *\n * The entire render pipeline is synchronous; the global swap is safe as long\n * as no code schedules microtasks that outlive the call.\n */\nexport function renderStaticSVG(\n spec: ChartSpec | LayerSpec | TileMapSpec,\n options?: StaticRenderOptions,\n): string {\n if (rendering) {\n throw new Error('renderStaticSVG is not reentrant — serialize calls through a queue or mutex');\n }\n rendering = true;\n\n const Window = getHappyDomWindow();\n const win = new Window({ url: 'about:blank' });\n\n const prevDocument = globalThis.document;\n const prevWindow = (globalThis as Record<string, unknown>).window;\n\n try {\n (globalThis as Record<string, unknown>).document = win.document;\n (globalThis as Record<string, unknown>).window = win;\n\n resetSvgIdCounter();\n\n const width = options?.width ?? 640;\n const height = options?.height ?? 420;\n const darkMode = resolveStaticDarkMode(options?.darkMode);\n\n const compileOpts: CompileOptions = {\n width,\n height,\n theme: options?.theme,\n darkMode,\n watermark: options?.watermark,\n };\n\n let svg: SVGElement;\n let themeForStyle: ResolvedTheme;\n\n if (isTileMapSpec(spec)) {\n const tileMapLayout = compileTileMap(spec, compileOpts);\n svg = renderTileMapSVG(tileMapLayout, { animate: false });\n themeForStyle = tileMapLayout.theme;\n } else {\n let layout: ChartLayout;\n if (isLayerSpec(spec)) {\n layout = compileLayer(spec, compileOpts);\n } else {\n layout = compileChart(spec, compileOpts);\n }\n\n const container = win.document.createElement('div');\n Object.defineProperty(container, 'getBoundingClientRect', {\n value: () => ({\n width,\n height,\n top: 0,\n left: 0,\n right: width,\n bottom: height,\n x: 0,\n y: 0,\n toJSON: () => ({}),\n }),\n });\n\n svg = renderChartSVG(layout, container as unknown as HTMLElement, {\n animate: false,\n crosshair: false,\n });\n\n stripInteractiveElements(svg);\n themeForStyle = layout.theme;\n }\n\n const doc = win.document as unknown as Document;\n let defs = svg.querySelector('defs');\n if (!defs) {\n defs = doc.createElementNS(SVG_NS, 'defs');\n svg.insertBefore(defs as unknown as Node, svg.firstChild);\n }\n const styleEl = doc.createElementNS(SVG_NS, 'style');\n styleEl.textContent = buildThemeStyleBlock(themeForStyle);\n defs.insertBefore(styleEl as unknown as Node, defs.firstChild);\n\n const serializer = new (\n win as unknown as { XMLSerializer: typeof XMLSerializer }\n ).XMLSerializer();\n return serializer.serializeToString(svg as unknown as Node);\n } finally {\n rendering = false;\n if (prevDocument !== undefined) {\n (globalThis as Record<string, unknown>).document = prevDocument;\n } else {\n delete (globalThis as Record<string, unknown>).document;\n }\n if (prevWindow !== undefined) {\n (globalThis as Record<string, unknown>).window = prevWindow;\n } else {\n delete (globalThis as Record<string, unknown>).window;\n }\n win.close();\n }\n}\n"],"mappings":"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","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@opendata-ai/openchart-vanilla",
|
|
3
|
-
"version": "7.
|
|
3
|
+
"version": "7.8.0",
|
|
4
4
|
"description": "Vanilla JS renderer for openchart: SVG charts, HTML tables, force-directed graphs",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "Riley Hilliard",
|
|
@@ -54,8 +54,8 @@
|
|
|
54
54
|
},
|
|
55
55
|
"dependencies": {
|
|
56
56
|
"@floating-ui/dom": "^1.7.6",
|
|
57
|
-
"@opendata-ai/openchart-core": "7.
|
|
58
|
-
"@opendata-ai/openchart-engine": "7.
|
|
57
|
+
"@opendata-ai/openchart-core": "7.8.0",
|
|
58
|
+
"@opendata-ai/openchart-engine": "7.8.0",
|
|
59
59
|
"d3-force": "^3.0.0",
|
|
60
60
|
"d3-quadtree": "^3.0.1"
|
|
61
61
|
},
|
package/src/static.ts
CHANGED
|
@@ -8,12 +8,14 @@ import type {
|
|
|
8
8
|
LayerSpec,
|
|
9
9
|
ResolvedTheme,
|
|
10
10
|
ThemeConfig,
|
|
11
|
+
TileMapSpec,
|
|
11
12
|
} from '@opendata-ai/openchart-core';
|
|
12
|
-
import { adaptForLightLineStroke, isLayerSpec } from '@opendata-ai/openchart-core';
|
|
13
|
-
import { compileChart, compileLayer } from '@opendata-ai/openchart-engine';
|
|
13
|
+
import { adaptForLightLineStroke, isLayerSpec, isTileMapSpec } from '@opendata-ai/openchart-core';
|
|
14
|
+
import { compileChart, compileLayer, compileTileMap } from '@opendata-ai/openchart-engine';
|
|
14
15
|
import { SVG_NS } from './renderers/svg-dom';
|
|
15
16
|
import { resetSvgIdCounter } from './svg-ids';
|
|
16
17
|
import { renderChartSVG } from './svg-renderer';
|
|
18
|
+
import { renderTileMapSVG } from './tilemap-renderer';
|
|
17
19
|
|
|
18
20
|
const esmRequire = createRequire(import.meta.url);
|
|
19
21
|
|
|
@@ -142,7 +144,7 @@ function stripInteractiveElements(svg: Element): void {
|
|
|
142
144
|
* as no code schedules microtasks that outlive the call.
|
|
143
145
|
*/
|
|
144
146
|
export function renderStaticSVG(
|
|
145
|
-
spec: ChartSpec | LayerSpec,
|
|
147
|
+
spec: ChartSpec | LayerSpec | TileMapSpec,
|
|
146
148
|
options?: StaticRenderOptions,
|
|
147
149
|
): string {
|
|
148
150
|
if (rendering) {
|
|
@@ -174,37 +176,45 @@ export function renderStaticSVG(
|
|
|
174
176
|
watermark: options?.watermark,
|
|
175
177
|
};
|
|
176
178
|
|
|
177
|
-
let
|
|
178
|
-
|
|
179
|
-
|
|
179
|
+
let svg: SVGElement;
|
|
180
|
+
let themeForStyle: ResolvedTheme;
|
|
181
|
+
|
|
182
|
+
if (isTileMapSpec(spec)) {
|
|
183
|
+
const tileMapLayout = compileTileMap(spec, compileOpts);
|
|
184
|
+
svg = renderTileMapSVG(tileMapLayout, { animate: false });
|
|
185
|
+
themeForStyle = tileMapLayout.theme;
|
|
180
186
|
} else {
|
|
181
|
-
layout
|
|
187
|
+
let layout: ChartLayout;
|
|
188
|
+
if (isLayerSpec(spec)) {
|
|
189
|
+
layout = compileLayer(spec, compileOpts);
|
|
190
|
+
} else {
|
|
191
|
+
layout = compileChart(spec, compileOpts);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const container = win.document.createElement('div');
|
|
195
|
+
Object.defineProperty(container, 'getBoundingClientRect', {
|
|
196
|
+
value: () => ({
|
|
197
|
+
width,
|
|
198
|
+
height,
|
|
199
|
+
top: 0,
|
|
200
|
+
left: 0,
|
|
201
|
+
right: width,
|
|
202
|
+
bottom: height,
|
|
203
|
+
x: 0,
|
|
204
|
+
y: 0,
|
|
205
|
+
toJSON: () => ({}),
|
|
206
|
+
}),
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
svg = renderChartSVG(layout, container as unknown as HTMLElement, {
|
|
210
|
+
animate: false,
|
|
211
|
+
crosshair: false,
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
stripInteractiveElements(svg);
|
|
215
|
+
themeForStyle = layout.theme;
|
|
182
216
|
}
|
|
183
217
|
|
|
184
|
-
const container = win.document.createElement('div');
|
|
185
|
-
Object.defineProperty(container, 'getBoundingClientRect', {
|
|
186
|
-
value: () => ({
|
|
187
|
-
width,
|
|
188
|
-
height,
|
|
189
|
-
top: 0,
|
|
190
|
-
left: 0,
|
|
191
|
-
right: width,
|
|
192
|
-
bottom: height,
|
|
193
|
-
x: 0,
|
|
194
|
-
y: 0,
|
|
195
|
-
toJSON: () => ({}),
|
|
196
|
-
}),
|
|
197
|
-
});
|
|
198
|
-
|
|
199
|
-
// renderChartSVG appends to container; the container and SVG are owned by
|
|
200
|
-
// the happy-dom Window which is closed in the finally block.
|
|
201
|
-
const svg = renderChartSVG(layout, container as unknown as HTMLElement, {
|
|
202
|
-
animate: false,
|
|
203
|
-
crosshair: false,
|
|
204
|
-
});
|
|
205
|
-
|
|
206
|
-
stripInteractiveElements(svg);
|
|
207
|
-
|
|
208
218
|
const doc = win.document as unknown as Document;
|
|
209
219
|
let defs = svg.querySelector('defs');
|
|
210
220
|
if (!defs) {
|
|
@@ -212,7 +222,7 @@ export function renderStaticSVG(
|
|
|
212
222
|
svg.insertBefore(defs as unknown as Node, svg.firstChild);
|
|
213
223
|
}
|
|
214
224
|
const styleEl = doc.createElementNS(SVG_NS, 'style');
|
|
215
|
-
styleEl.textContent = buildThemeStyleBlock(
|
|
225
|
+
styleEl.textContent = buildThemeStyleBlock(themeForStyle);
|
|
216
226
|
defs.insertBefore(styleEl as unknown as Node, defs.firstChild);
|
|
217
227
|
|
|
218
228
|
const serializer = new (
|
package/src/tilemap-renderer.ts
CHANGED
|
@@ -13,6 +13,7 @@ import type {
|
|
|
13
13
|
} from '@opendata-ai/openchart-core';
|
|
14
14
|
import { textAscent } from '@opendata-ai/openchart-core';
|
|
15
15
|
import { renderLegend } from './renderers/legend';
|
|
16
|
+
import { nextSvgId } from './svg-ids';
|
|
16
17
|
|
|
17
18
|
const SVG_NS = 'http://www.w3.org/2000/svg';
|
|
18
19
|
const XLINK_NS = 'http://www.w3.org/1999/xlink';
|
|
@@ -23,8 +24,6 @@ const EASE_VAR_MAP: Record<string, string> = {
|
|
|
23
24
|
snappy: 'var(--oc-ease-snappy)',
|
|
24
25
|
};
|
|
25
26
|
|
|
26
|
-
let gradientIdCounter = 0;
|
|
27
|
-
|
|
28
27
|
// ---------------------------------------------------------------------------
|
|
29
28
|
// Helpers
|
|
30
29
|
// ---------------------------------------------------------------------------
|
|
@@ -322,7 +321,7 @@ function renderGradientLegend(parent: SVGElement, layout: TileMapLayout): void {
|
|
|
322
321
|
parent.insertBefore(defs, parent.firstChild);
|
|
323
322
|
}
|
|
324
323
|
|
|
325
|
-
const gradientId =
|
|
324
|
+
const gradientId = nextSvgId('oc-tilemap-legend-gradient');
|
|
326
325
|
const grad = createSVGElement('linearGradient');
|
|
327
326
|
grad.id = gradientId;
|
|
328
327
|
grad.setAttribute('x1', '0%');
|