@opendata-ai/openchart-vanilla 8.3.0 → 8.4.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-PEIX32S3.js → chunk-45XZOWYC.js} +497 -52
- package/dist/chunk-45XZOWYC.js.map +1 -0
- package/dist/{chunk-DCODCLWI.js → chunk-FETYAVTZ.js} +3 -3
- package/dist/{chunk-RM5XS6NR.js → chunk-HGRNXLDF.js} +16 -11
- package/dist/chunk-HGRNXLDF.js.map +1 -0
- package/dist/{chunk-737VNBU6.js → chunk-LNPRDSK3.js} +37 -5
- package/dist/chunk-LNPRDSK3.js.map +1 -0
- package/dist/{chunk-T32UHMZA.js → chunk-UOB422LY.js} +2 -2
- package/dist/{chunk-P32LUJFK.js → chunk-YGYMB7KB.js} +118 -16
- package/dist/chunk-YGYMB7KB.js.map +1 -0
- package/dist/export-gif.js +3 -3
- package/dist/index.d.ts +6 -1
- package/dist/index.js +306 -103
- package/dist/index.js.map +1 -1
- package/dist/simulation-worker.js +67 -67
- package/dist/static.js +3 -3
- package/dist/story/index.js +4 -4
- package/dist/styles.css +1 -1
- package/dist/styles.css.map +1 -1
- package/package.json +3 -3
- package/dist/chunk-737VNBU6.js.map +0 -1
- package/dist/chunk-P32LUJFK.js.map +0 -1
- package/dist/chunk-PEIX32S3.js.map +0 -1
- package/dist/chunk-RM5XS6NR.js.map +0 -1
- /package/dist/{chunk-DCODCLWI.js.map → chunk-FETYAVTZ.js.map} +0 -0
- /package/dist/{chunk-T32UHMZA.js.map → chunk-UOB422LY.js.map} +0 -0
|
@@ -5,10 +5,10 @@ import {
|
|
|
5
5
|
getSVGBackgroundColor,
|
|
6
6
|
getSVGDimensions,
|
|
7
7
|
rasterizeSVGToCanvas
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-UOB422LY.js";
|
|
9
9
|
import {
|
|
10
10
|
injectThemeStyleBlock
|
|
11
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-HGRNXLDF.js";
|
|
12
12
|
|
|
13
13
|
// src/gif-encode.ts
|
|
14
14
|
function readCanvasSRGB(canvas) {
|
|
@@ -231,4 +231,4 @@ export {
|
|
|
231
231
|
applyFrameState,
|
|
232
232
|
exportGIF
|
|
233
233
|
};
|
|
234
|
-
//# sourceMappingURL=chunk-
|
|
234
|
+
//# sourceMappingURL=chunk-FETYAVTZ.js.map
|
|
@@ -30,7 +30,7 @@ function applyTextStyle(el, style) {
|
|
|
30
30
|
import { adaptForLightLineStroke, cssTokenDefault } from "@opendata-ai/openchart-core";
|
|
31
31
|
function buildThemeStyleBlock(theme) {
|
|
32
32
|
const accent = theme.colors.categorical[0] ?? cssTokenDefault("--oc-accent", "light");
|
|
33
|
-
const bg = theme.colors.
|
|
33
|
+
const bg = theme.colors.neutral.surface;
|
|
34
34
|
const props = [
|
|
35
35
|
`--oc-font-family: ${theme.fonts.family}`,
|
|
36
36
|
`--oc-font-mono: ${theme.fonts.mono}`,
|
|
@@ -48,9 +48,12 @@ function buildThemeStyleBlock(theme) {
|
|
|
48
48
|
`--oc-bg: ${bg}`,
|
|
49
49
|
`--oc-text: ${theme.colors.text}`,
|
|
50
50
|
`--oc-text-muted: ${theme.colors.axis}`,
|
|
51
|
-
`--oc-text-
|
|
51
|
+
`--oc-text-secondary: ${theme.colors.neutral.secondary}`,
|
|
52
|
+
`--oc-text-faint: ${theme.colors.neutral.faint}`,
|
|
53
|
+
`--oc-border: ${theme.colors.neutral.border}`,
|
|
52
54
|
`--oc-gridline: ${theme.colors.gridline}`,
|
|
53
|
-
|
|
55
|
+
// Hairline, not the tick-label ink.
|
|
56
|
+
`--oc-axis: ${theme.colors.hairline}`,
|
|
54
57
|
`--oc-border-radius: ${theme.borderRadius}px`,
|
|
55
58
|
`--oc-accent: ${accent}`,
|
|
56
59
|
`--oc-accent-strong: ${adaptForLightLineStroke(accent)}`,
|
|
@@ -61,27 +64,29 @@ function buildThemeStyleBlock(theme) {
|
|
|
61
64
|
`--oc-space-4: ${theme.spacing.padding}px`
|
|
62
65
|
];
|
|
63
66
|
const rules = [
|
|
64
|
-
|
|
67
|
+
// Tabular figures everywhere in the chart's own text: axis ticks, value
|
|
68
|
+
// labels and legend entries all line up column-wise.
|
|
69
|
+
`svg.oc-chart { ${props.join("; ")}; font-variant-numeric: tabular-nums; }`,
|
|
65
70
|
`.oc-chrome { font-family: var(--oc-font-family); }`,
|
|
66
71
|
`.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); }`,
|
|
67
72
|
`.oc-title { font-size: var(--oc-title-size); font-weight: var(--oc-title-weight); letter-spacing: var(--oc-title-tracking); fill: var(--oc-text); }`,
|
|
68
73
|
`.oc-subtitle { font-size: var(--oc-subtitle-size); font-weight: var(--oc-subtitle-weight); fill: var(--oc-text-muted); }`,
|
|
69
74
|
`.oc-source, .oc-byline, .oc-footer { font-size: var(--oc-source-size); font-weight: var(--oc-source-weight); fill: var(--oc-text-muted); }`,
|
|
70
|
-
`.oc-brand { font-size: 11px; font-weight:
|
|
75
|
+
`.oc-brand { font-size: 11px; font-weight: 500; letter-spacing: 0.02em; fill: var(--oc-text-faint); }`,
|
|
71
76
|
`.oc-brand-dot { fill: var(--oc-accent); }`,
|
|
72
77
|
`.oc-eyebrow-dot { fill: var(--oc-accent); }`,
|
|
73
78
|
`.oc-metrics { font-family: var(--oc-font-family); }`,
|
|
74
|
-
`.oc-metric-label { font-size:
|
|
75
|
-
`.oc-metric-value { font-size: 22px; font-weight:
|
|
76
|
-
`.oc-metric-delta-up { fill: var(--oc-positive); font-size: 12px; font-weight:
|
|
77
|
-
`.oc-metric-delta-down { fill: var(--oc-negative); font-size: 12px; font-weight:
|
|
79
|
+
`.oc-metric-label { font-size: 11px; font-weight: 500; letter-spacing: 0.06em; text-transform: uppercase; fill: var(--oc-text-muted); }`,
|
|
80
|
+
`.oc-metric-value { font-size: 22px; font-weight: 600; letter-spacing: -0.02em; fill: var(--oc-text); font-variant-numeric: tabular-nums; }`,
|
|
81
|
+
`.oc-metric-delta-up { fill: var(--oc-positive); font-size: 12px; font-weight: 500; }`,
|
|
82
|
+
`.oc-metric-delta-down { fill: var(--oc-negative); font-size: 12px; font-weight: 500; }`,
|
|
78
83
|
`.oc-axis-tick-inline { font-size: 11px; font-weight: 400; fill: var(--oc-text-muted); }`,
|
|
79
84
|
`.oc-endpoint-labels { font-family: var(--oc-font-family); }`,
|
|
80
85
|
`.oc-endpoint-label { fill: var(--oc-endpoint-label-color, var(--oc-text)); }`,
|
|
81
86
|
`.oc-endpoint-value { fill: var(--oc-endpoint-value-color, var(--oc-text-muted)); }`,
|
|
82
87
|
`.oc-endpoint-leader { stroke: var(--oc-endpoint-leader-color, currentColor); }`,
|
|
83
88
|
`.oc-annotation-subtitle { fill: var(--oc-annotation-subtitle-color, var(--oc-text-muted)); }`,
|
|
84
|
-
`.oc-metric-secondary { fill: var(--oc-
|
|
89
|
+
`.oc-metric-secondary { fill: var(--oc-text-muted); font-size: 12px; font-weight: 400; }`,
|
|
85
90
|
`.oc-legend { font-family: var(--oc-font-family); font-size: var(--oc-body-size); }`,
|
|
86
91
|
`.oc-legend-entry { cursor: default; }`,
|
|
87
92
|
`.oc-legend text { fill: var(--oc-legend-text); }`
|
|
@@ -110,4 +115,4 @@ export {
|
|
|
110
115
|
buildThemeStyleBlock,
|
|
111
116
|
injectThemeStyleBlock
|
|
112
117
|
};
|
|
113
|
-
//# sourceMappingURL=chunk-
|
|
118
|
+
//# sourceMappingURL=chunk-HGRNXLDF.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/renderers/svg-dom.ts","../src/theme-style-block.ts"],"sourcesContent":["/**\n * Shared SVG DOM helpers used across the per-concern renderers.\n *\n * Pure, stateless utilities. No layout/theme knowledge.\n */\n\nimport type { TextStyle } from '@opendata-ai/openchart-core';\n\nexport const SVG_NS = 'http://www.w3.org/2000/svg';\nexport const XLINK_NS = 'http://www.w3.org/1999/xlink';\n\nexport function createSVGElement(tag: string): SVGElement {\n return document.createElementNS(SVG_NS, tag);\n}\n\nexport function setAttrs(el: SVGElement, attrs: Record<string, string | number>): void {\n for (const [key, value] of Object.entries(attrs)) {\n el.setAttribute(key, String(value));\n }\n}\n\nexport function applyTextStyle(el: SVGElement, style: TextStyle): void {\n // Use inline styles so engine-computed values take priority over CSS class\n // defaults (e.g. .oc-title { font-size: var(--oc-title-size) } would otherwise\n // override the responsive scaling applied by the chrome layout).\n const inline = (el as SVGElement & ElementCSSInlineStyle).style;\n inline.setProperty('fill', style.fill);\n inline.setProperty('font-size', `${style.fontSize}px`);\n inline.setProperty('font-weight', String(style.fontWeight));\n inline.setProperty('font-family', style.fontFamily);\n if (style.textAnchor) {\n el.setAttribute('text-anchor', style.textAnchor);\n }\n if (style.dominantBaseline) {\n el.setAttribute('dominant-baseline', style.dominantBaseline);\n }\n if (style.fontVariant) {\n el.setAttribute('font-variant', style.fontVariant);\n }\n}\n","/**\n * Theme `<style>` block for self-contained SVG export.\n *\n * Charts render most fills as inline SVG attributes, but a handful of chrome\n * elements (metric cells, the brand watermark dot, legend text, endpoint labels)\n * take their fill from CSS classes in `chrome.css` via `--oc-*` variables. That\n * works on-screen because the live SVG inherits the page stylesheet — but a\n * serialized/rasterized export (PNG/JPG/GIF) is detached from the page, so those\n * class-based fills vanish. This module builds a `<style>` block that resolves\n * every such rule against a concrete `ResolvedTheme`, so injecting it into an\n * export clone makes the SVG fully self-contained.\n *\n * Both the headless renderer (`static.ts`) and the browser export path use this,\n * so class-based styling round-trips identically no matter which path produced\n * the SVG. Browser-safe (no Node imports) so the browser exporters can import it.\n */\n\nimport type { ResolvedTheme } from '@opendata-ai/openchart-core';\nimport { adaptForLightLineStroke, cssTokenDefault } from '@opendata-ai/openchart-core';\nimport { SVG_NS } from './renderers/svg-dom';\n\n/**\n * Build the theme `<style>` block CSS text for a resolved theme. Sets the\n * `--oc-*` custom properties on `svg.oc-chart` and defines every class-based\n * chrome rule against them.\n */\nexport function buildThemeStyleBlock(theme: ResolvedTheme): string {\n const accent = theme.colors.categorical[0] ?? cssTokenDefault('--oc-accent', 'light');\n const bg = theme.colors.neutral.surface;\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: ${cssTokenDefault('--oc-title-tracking', 'light')}`,\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: ${cssTokenDefault('--oc-eyebrow-tracking', 'light')}`,\n `--oc-bg: ${bg}`,\n `--oc-text: ${theme.colors.text}`,\n `--oc-text-muted: ${theme.colors.axis}`,\n `--oc-text-secondary: ${theme.colors.neutral.secondary}`,\n `--oc-text-faint: ${theme.colors.neutral.faint}`,\n `--oc-border: ${theme.colors.neutral.border}`,\n `--oc-gridline: ${theme.colors.gridline}`,\n // Hairline, not the tick-label ink.\n `--oc-axis: ${theme.colors.hairline}`,\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 ? cssTokenDefault('--oc-legend-text', 'dark') : cssTokenDefault('--oc-legend-text', 'light')}`,\n `--oc-space-2: ${theme.spacing.chromeGap * 2}px`,\n `--oc-space-4: ${theme.spacing.padding}px`,\n ];\n\n const rules = [\n // Tabular figures everywhere in the chart's own text: axis ticks, value\n // labels and legend entries all line up column-wise.\n `svg.oc-chart { ${props.join('; ')}; font-variant-numeric: tabular-nums; }`,\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: 500; 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: 11px; font-weight: 500; letter-spacing: 0.06em; text-transform: uppercase; fill: var(--oc-text-muted); }`,\n `.oc-metric-value { font-size: 22px; font-weight: 600; letter-spacing: -0.02em; fill: var(--oc-text); font-variant-numeric: tabular-nums; }`,\n `.oc-metric-delta-up { fill: var(--oc-positive); font-size: 12px; font-weight: 500; }`,\n `.oc-metric-delta-down { fill: var(--oc-negative); font-size: 12px; font-weight: 500; }`,\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-text-muted); 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\n/**\n * Inject the theme style block into an SVG clone's `<defs>` so class-based fills\n * survive serialization. Idempotent-ish: adds one `<style data-oc-theme>` at the\n * front of `<defs>`; call once per clone before serializing. No-op if `theme` is\n * undefined (nothing to resolve against).\n */\nexport function injectThemeStyleBlock(svg: SVGElement, theme: ResolvedTheme | undefined): void {\n if (!theme) return;\n let defs = svg.querySelector('defs');\n if (!defs) {\n defs = document.createElementNS(SVG_NS, 'defs');\n svg.insertBefore(defs, svg.firstChild);\n }\n const style = document.createElementNS(SVG_NS, 'style');\n style.setAttribute('data-oc-theme', '');\n style.textContent = buildThemeStyleBlock(theme);\n defs.insertBefore(style, defs.firstChild);\n}\n"],"mappings":"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","names":[]}
|
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
renderChromeElement,
|
|
4
4
|
renderLegend,
|
|
5
5
|
stampAnimationVars
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-YGYMB7KB.js";
|
|
7
7
|
|
|
8
8
|
// src/tilemap-renderer.ts
|
|
9
9
|
var SVG_NS = "http://www.w3.org/2000/svg";
|
|
@@ -154,7 +154,10 @@ function renderTiles(parent, tiles, animation) {
|
|
|
154
154
|
"dominant-baseline": "central",
|
|
155
155
|
"font-family": tile.label.style.fontFamily,
|
|
156
156
|
"font-size": tile.label.style.fontSize,
|
|
157
|
-
"font-weight": tile.label.style.fontWeight
|
|
157
|
+
"font-weight": tile.label.style.fontWeight,
|
|
158
|
+
// A hair of tracking is what keeps a two-letter code legible at 8px now
|
|
159
|
+
// that it is set at 600 instead of 700.
|
|
160
|
+
"letter-spacing": "0.02em"
|
|
158
161
|
});
|
|
159
162
|
codeLabel.style.setProperty(
|
|
160
163
|
"fill",
|
|
@@ -221,8 +224,8 @@ function renderGradientLegend(parent, layout) {
|
|
|
221
224
|
y: gradientLegend.bounds.y,
|
|
222
225
|
width: gradientLegend.bounds.width,
|
|
223
226
|
height: barHeight,
|
|
224
|
-
|
|
225
|
-
|
|
227
|
+
fill: `url(#${gradientId})`,
|
|
228
|
+
"shape-rendering": "crispEdges"
|
|
226
229
|
});
|
|
227
230
|
g.appendChild(bar);
|
|
228
231
|
const minText = createSVGElement("text");
|
|
@@ -255,6 +258,35 @@ function renderGradientLegend(parent, layout) {
|
|
|
255
258
|
);
|
|
256
259
|
maxText.textContent = gradientLegend.maxLabel;
|
|
257
260
|
g.appendChild(maxText);
|
|
261
|
+
if (gradientLegend.noData) {
|
|
262
|
+
const nd = gradientLegend.noData;
|
|
263
|
+
const swatch = createSVGElement("rect");
|
|
264
|
+
swatch.setAttribute("class", "oc-legend-nodata");
|
|
265
|
+
setAttrs(swatch, {
|
|
266
|
+
x: nd.x,
|
|
267
|
+
y: nd.y,
|
|
268
|
+
width: nd.size,
|
|
269
|
+
height: nd.size,
|
|
270
|
+
fill: nd.fill,
|
|
271
|
+
"shape-rendering": "crispEdges"
|
|
272
|
+
});
|
|
273
|
+
g.appendChild(swatch);
|
|
274
|
+
const ndLabel = createSVGElement("text");
|
|
275
|
+
setAttrs(ndLabel, {
|
|
276
|
+
x: nd.labelX,
|
|
277
|
+
y: nd.labelY,
|
|
278
|
+
"text-anchor": "start",
|
|
279
|
+
"font-family": gradientLegend.labelStyle.fontFamily,
|
|
280
|
+
"font-size": gradientLegend.labelStyle.fontSize,
|
|
281
|
+
"font-weight": gradientLegend.labelStyle.fontWeight
|
|
282
|
+
});
|
|
283
|
+
ndLabel.style.setProperty(
|
|
284
|
+
"fill",
|
|
285
|
+
gradientLegend.labelStyle.fill
|
|
286
|
+
);
|
|
287
|
+
ndLabel.textContent = nd.label;
|
|
288
|
+
g.appendChild(ndLabel);
|
|
289
|
+
}
|
|
258
290
|
parent.appendChild(g);
|
|
259
291
|
}
|
|
260
292
|
function renderTileMapSVG(layout, opts) {
|
|
@@ -295,4 +327,4 @@ function renderTileMapSVG(layout, opts) {
|
|
|
295
327
|
export {
|
|
296
328
|
renderTileMapSVG
|
|
297
329
|
};
|
|
298
|
-
//# sourceMappingURL=chunk-
|
|
330
|
+
//# sourceMappingURL=chunk-LNPRDSK3.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/tilemap-renderer.ts"],"sourcesContent":["/**\n * TileMap SVG renderer: converts a TileMapLayout into SVG DOM elements.\n *\n * Creates an <svg> with tile rectangles, state code labels, value labels,\n * gradient legend, and chrome. All styling via inline SVG attributes from\n * layout data. Animation is pure CSS, driven by data attributes.\n */\n\nimport type {\n ResolvedAnimation,\n TileMapLayout,\n TileMapTileMark,\n} from '@opendata-ai/openchart-core';\nimport { stampAnimationVars } from './animation-vars';\nimport { renderChromeElement } from './renderers/chrome';\nimport { renderLegend } from './renderers/legend';\nimport { nextSvgId } from './svg-ids';\n\nconst SVG_NS = 'http://www.w3.org/2000/svg';\nconst XLINK_NS = 'http://www.w3.org/1999/xlink';\nconst BRAND_URL = 'https://tryopendata.ai';\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nfunction createSVGElement(tag: string): SVGElement {\n return document.createElementNS(SVG_NS, tag);\n}\n\nfunction setAttrs(el: SVGElement, attrs: Record<string, string | number>): void {\n for (const [key, value] of Object.entries(attrs)) {\n el.setAttribute(key, String(value));\n }\n}\n\n// ---------------------------------------------------------------------------\n// Chrome rendering (delegates to the shared helper for text wrapping)\n// ---------------------------------------------------------------------------\n\nfunction renderChrome(parent: SVGElement, layout: TileMapLayout): void {\n const g = createSVGElement('g');\n g.setAttribute('class', 'oc-chrome');\n\n const { chrome, measureText } = layout;\n const bottomOffset = layout.area.y + layout.area.height;\n\n if (chrome.title) {\n renderChromeElement(g, chrome.title, 'oc-title', 'title', measureText);\n }\n if (chrome.subtitle) {\n renderChromeElement(g, chrome.subtitle, 'oc-subtitle', 'subtitle', measureText);\n }\n if (chrome.source) {\n renderChromeElement(\n g,\n { ...chrome.source, y: bottomOffset + chrome.source.y },\n 'oc-source',\n 'source',\n measureText,\n );\n }\n if (chrome.byline) {\n renderChromeElement(\n g,\n { ...chrome.byline, y: bottomOffset + chrome.byline.y },\n 'oc-byline',\n 'byline',\n measureText,\n );\n }\n if (chrome.footer) {\n renderChromeElement(\n g,\n { ...chrome.footer, y: bottomOffset + chrome.footer.y },\n 'oc-footer',\n 'footer',\n measureText,\n );\n }\n\n parent.appendChild(g);\n}\n\n// ---------------------------------------------------------------------------\n// Watermark rendering\n// ---------------------------------------------------------------------------\n\nfunction renderWatermark(parent: SVGElement, layout: TileMapLayout): void {\n if (layout.width < 480) return; // Don't render if too narrow\n\n const { width, height } = layout;\n const { theme } = layout;\n const padding = theme.spacing.padding;\n const rightEdge = width - padding;\n const bottomEdge = height - padding;\n const fill = theme.colors.axis;\n\n const a = createSVGElement('a');\n a.setAttribute('href', BRAND_URL);\n a.setAttributeNS(XLINK_NS, 'xlink:href', BRAND_URL);\n a.setAttribute('target', '_blank');\n a.setAttribute('rel', 'noopener');\n a.setAttribute('class', 'oc-chrome-ref');\n\n const text = createSVGElement('text');\n setAttrs(text, {\n x: rightEdge,\n y: bottomEdge,\n 'dominant-baseline': 'alphabetic',\n 'text-anchor': 'end',\n 'font-family': theme.fonts.family,\n 'font-size': 12,\n 'fill-opacity': 0.55,\n });\n (text as SVGElement & ElementCSSInlineStyle).style.setProperty('fill', fill);\n\n const trySpan = createSVGElement('tspan');\n setAttrs(trySpan, { 'font-weight': 500 });\n trySpan.textContent = 'try';\n\n const openDataSpan = createSVGElement('tspan');\n setAttrs(openDataSpan, { 'font-weight': 600, 'font-size': 16 });\n openDataSpan.textContent = 'OpenData';\n\n const aiSpan = createSVGElement('tspan');\n setAttrs(aiSpan, { 'font-weight': 500 });\n aiSpan.textContent = '.ai';\n\n text.appendChild(trySpan);\n text.appendChild(openDataSpan);\n text.appendChild(aiSpan);\n a.appendChild(text);\n parent.appendChild(a);\n}\n\n// ---------------------------------------------------------------------------\n// Tiles rendering\n// ---------------------------------------------------------------------------\n\nfunction renderTiles(\n parent: SVGElement,\n tiles: TileMapTileMark[],\n animation?: ResolvedAnimation,\n): void {\n const g = createSVGElement('g');\n g.setAttribute('class', 'oc-tilemap-tiles');\n g.setAttribute('role', 'list');\n\n // Compute per-tile delays with jitter for organic feel.\n // Base delay spreads tiles across ~800ms window, jitter adds +-40% variation\n // so some tiles pop in clusters while others have longer gaps.\n const tileDelays: number[] = [];\n if (animation?.enter) {\n const baseStagger = 800 / Math.max(tiles.length, 1);\n let seed = 17;\n for (let i = 0; i < tiles.length; i++) {\n const idx = tiles[i].animationIndex ?? i;\n seed = (seed * 1103515245 + 12345) & 0x7fffffff;\n const jitter = ((seed % 1000) / 1000 - 0.5) * 0.8;\n tileDelays.push(Math.max(0, Math.round(idx * baseStagger * (1 + jitter))));\n }\n }\n\n for (let i = 0; i < tiles.length; i++) {\n const tile = tiles[i];\n const tileGroup = createSVGElement('g');\n tileGroup.setAttribute('class', 'oc-tilemap-tile');\n tileGroup.setAttribute('data-state', tile.stateCode);\n tileGroup.setAttribute('role', 'listitem');\n if (tile.aria?.label) {\n tileGroup.setAttribute('aria-label', tile.aria.label);\n }\n\n if (animation?.enter) {\n const idx = tile.animationIndex ?? i;\n tileGroup.setAttribute('data-animation-index', String(idx));\n (tileGroup as SVGElement & ElementCSSInlineStyle).style.setProperty(\n '--oc-mark-index',\n String(idx),\n );\n (tileGroup as SVGElement & ElementCSSInlineStyle).style.setProperty(\n '--oc-tile-delay',\n `${tileDelays[i]}ms`,\n );\n }\n\n // Tile background rect\n const rect = createSVGElement('rect');\n setAttrs(rect, {\n x: tile.x,\n y: tile.y,\n width: tile.size,\n height: tile.size,\n rx: tile.cornerRadius,\n fill: tile.fill,\n 'fill-opacity': tile.fillOpacity ?? 1,\n stroke: tile.stroke,\n 'stroke-width': tile.strokeWidth,\n });\n tileGroup.appendChild(rect);\n\n // State code label\n const codeLabel = createSVGElement('text');\n setAttrs(codeLabel, {\n x: tile.label.x,\n y: tile.label.y,\n 'text-anchor': 'middle',\n 'dominant-baseline': 'central',\n 'font-family': tile.label.style.fontFamily,\n 'font-size': tile.label.style.fontSize,\n 'font-weight': tile.label.style.fontWeight,\n // A hair of tracking is what keeps a two-letter code legible at 8px now\n // that it is set at 600 instead of 700.\n 'letter-spacing': '0.02em',\n });\n (codeLabel as SVGElement & ElementCSSInlineStyle).style.setProperty(\n 'fill',\n tile.label.style.fill,\n );\n codeLabel.textContent = tile.label.text;\n tileGroup.appendChild(codeLabel);\n\n // Value label (if visible)\n if (tile.valueLabel.visible && tile.valueLabel.text) {\n const valueLabel = createSVGElement('text');\n setAttrs(valueLabel, {\n x: tile.valueLabel.x,\n y: tile.valueLabel.y,\n 'text-anchor': 'middle',\n 'dominant-baseline': 'central',\n 'font-family': tile.valueLabel.style.fontFamily,\n 'font-size': tile.valueLabel.style.fontSize,\n 'font-weight': tile.valueLabel.style.fontWeight,\n });\n (valueLabel as SVGElement & ElementCSSInlineStyle).style.setProperty(\n 'fill',\n tile.valueLabel.style.fill,\n );\n valueLabel.textContent = tile.valueLabel.text;\n tileGroup.appendChild(valueLabel);\n }\n\n g.appendChild(tileGroup);\n }\n\n parent.appendChild(g);\n}\n\n// ---------------------------------------------------------------------------\n// Gradient legend rendering\n// ---------------------------------------------------------------------------\n\nfunction renderGradientLegend(parent: SVGElement, layout: TileMapLayout): void {\n if (!layout.gradientLegend) return;\n\n const { gradientLegend } = layout;\n const g = createSVGElement('g');\n g.setAttribute('class', 'oc-tilemap-legend');\n\n // Build linear gradient in defs\n const defs = parent.querySelector('defs') || createSVGElement('defs');\n const exists = parent.querySelector('defs');\n if (!exists) {\n parent.insertBefore(defs, parent.firstChild);\n }\n\n const gradientId = nextSvgId('oc-tilemap-legend-gradient');\n const grad = createSVGElement('linearGradient');\n grad.id = gradientId;\n grad.setAttribute('x1', '0%');\n grad.setAttribute('y1', '0%');\n grad.setAttribute('x2', '100%');\n grad.setAttribute('y2', '0%');\n\n for (const stop of gradientLegend.colorStops) {\n const s = createSVGElement('stop');\n const attrs: Record<string, string | number> = {\n offset: `${stop.offset * 100}%`,\n 'stop-color': stop.color,\n };\n if (stop.opacity !== undefined) {\n attrs['stop-opacity'] = stop.opacity;\n }\n setAttrs(s, attrs);\n grad.appendChild(s);\n }\n\n (defs as SVGElement).appendChild(grad);\n\n // Gradient bar: square-cornered, so it reads as a color scale rather than a\n // pill-shaped UI control.\n const barHeight = gradientLegend.bounds.height;\n const bar = createSVGElement('rect');\n setAttrs(bar, {\n x: gradientLegend.bounds.x,\n y: gradientLegend.bounds.y,\n width: gradientLegend.bounds.width,\n height: barHeight,\n fill: `url(#${gradientId})`,\n 'shape-rendering': 'crispEdges',\n });\n g.appendChild(bar);\n\n // Min label\n const minText = createSVGElement('text');\n setAttrs(minText, {\n x: gradientLegend.bounds.x,\n y: gradientLegend.bounds.y + gradientLegend.bounds.height + 14,\n 'text-anchor': 'start',\n 'font-family': gradientLegend.labelStyle.fontFamily,\n 'font-size': gradientLegend.labelStyle.fontSize,\n 'font-weight': gradientLegend.labelStyle.fontWeight,\n });\n (minText as SVGElement & ElementCSSInlineStyle).style.setProperty(\n 'fill',\n gradientLegend.labelStyle.fill,\n );\n minText.textContent = gradientLegend.minLabel;\n g.appendChild(minText);\n\n // Max label\n const maxText = createSVGElement('text');\n setAttrs(maxText, {\n x: gradientLegend.bounds.x + gradientLegend.bounds.width,\n y: gradientLegend.bounds.y + gradientLegend.bounds.height + 14,\n 'text-anchor': 'end',\n 'font-family': gradientLegend.labelStyle.fontFamily,\n 'font-size': gradientLegend.labelStyle.fontSize,\n 'font-weight': gradientLegend.labelStyle.fontWeight,\n });\n (maxText as SVGElement & ElementCSSInlineStyle).style.setProperty(\n 'fill',\n gradientLegend.labelStyle.fill,\n );\n maxText.textContent = gradientLegend.maxLabel;\n g.appendChild(maxText);\n\n // Detached \"no data\" swatch, keyed beside the ramp rather than at its low end.\n if (gradientLegend.noData) {\n const nd = gradientLegend.noData;\n const swatch = createSVGElement('rect');\n swatch.setAttribute('class', 'oc-legend-nodata');\n setAttrs(swatch, {\n x: nd.x,\n y: nd.y,\n width: nd.size,\n height: nd.size,\n fill: nd.fill,\n 'shape-rendering': 'crispEdges',\n });\n g.appendChild(swatch);\n\n const ndLabel = createSVGElement('text');\n setAttrs(ndLabel, {\n x: nd.labelX,\n y: nd.labelY,\n 'text-anchor': 'start',\n 'font-family': gradientLegend.labelStyle.fontFamily,\n 'font-size': gradientLegend.labelStyle.fontSize,\n 'font-weight': gradientLegend.labelStyle.fontWeight,\n });\n (ndLabel as SVGElement & ElementCSSInlineStyle).style.setProperty(\n 'fill',\n gradientLegend.labelStyle.fill,\n );\n ndLabel.textContent = nd.label;\n g.appendChild(ndLabel);\n }\n\n parent.appendChild(g);\n}\n\n// ---------------------------------------------------------------------------\n// Public API\n// ---------------------------------------------------------------------------\n\n/**\n * Render a TileMapLayout to an SVG element.\n */\nexport function renderTileMapSVG(\n layout: TileMapLayout,\n opts?: { animate?: boolean },\n): SVGSVGElement {\n const { width, height, tiles, a11y, watermark, animation } = layout;\n const animate = opts?.animate && !!animation?.enter;\n\n const svg = createSVGElement('svg') as SVGSVGElement;\n svg.setAttribute('viewBox', `0 0 ${width} ${height}`);\n svg.setAttribute('role', 'img');\n if (a11y.altText) {\n svg.setAttribute('aria-label', a11y.altText);\n }\n\n const classes = animate ? 'oc-tilemap oc-animate' : 'oc-tilemap';\n svg.setAttribute('class', classes);\n\n if (animate && animation?.enter) {\n // Target ~1s total: stagger window ~800ms + per-tile pop ~200ms\n const stagger = Math.max(5, Math.round(800 / Math.max(tiles.length, 1)));\n stampAnimationVars(svg, {\n duration: animation.enter.duration,\n stagger,\n annotationDelay: animation.annotationDelay,\n ease: animation.enter.ease,\n });\n }\n\n // Empty defs element (will be filled by gradient legend)\n const defs = createSVGElement('defs');\n svg.appendChild(defs);\n\n // Render chrome\n renderChrome(svg, layout);\n\n // Render tiles\n renderTiles(svg, tiles, animate ? animation : undefined);\n\n // Render legend (gradient for quantitative, categorical for nominal)\n if (layout.categoricalLegend) {\n renderLegend(svg, layout.categoricalLegend);\n } else {\n renderGradientLegend(svg, layout);\n }\n\n // Render watermark\n if (watermark) {\n renderWatermark(svg, layout);\n }\n\n return svg;\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
injectThemeStyleBlock
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-HGRNXLDF.js";
|
|
4
4
|
|
|
5
5
|
// src/export.ts
|
|
6
6
|
function getSVGDimensions(svg) {
|
|
@@ -279,4 +279,4 @@ export {
|
|
|
279
279
|
exportJPG,
|
|
280
280
|
exportCSV
|
|
281
281
|
};
|
|
282
|
-
//# sourceMappingURL=chunk-
|
|
282
|
+
//# sourceMappingURL=chunk-UOB422LY.js.map
|
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
applyTextStyle,
|
|
5
5
|
createSVGElement,
|
|
6
6
|
setAttrs
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-HGRNXLDF.js";
|
|
8
8
|
|
|
9
9
|
// src/svg-ids.ts
|
|
10
10
|
var counter = 0;
|
|
@@ -195,7 +195,11 @@ function renderLineMark(mark, index) {
|
|
|
195
195
|
d,
|
|
196
196
|
fill: "none",
|
|
197
197
|
stroke: mark.stroke,
|
|
198
|
-
"stroke-width": mark.strokeWidth
|
|
198
|
+
"stroke-width": mark.strokeWidth,
|
|
199
|
+
// Round joins/caps: at 2px a mitre on a sharp data spike renders as a
|
|
200
|
+
// spur past the vertex, and a butt cap leaves a squared-off line end.
|
|
201
|
+
"stroke-linejoin": "round",
|
|
202
|
+
"stroke-linecap": "round"
|
|
199
203
|
});
|
|
200
204
|
if (mark.strokeDasharray) {
|
|
201
205
|
path.setAttribute("stroke-dasharray", mark.strokeDasharray);
|
|
@@ -253,7 +257,9 @@ function renderAreaMark(mark, index) {
|
|
|
253
257
|
d: mark.topPath,
|
|
254
258
|
fill: "none",
|
|
255
259
|
stroke: mark.stroke,
|
|
256
|
-
"stroke-width": mark.strokeWidth ?? 1
|
|
260
|
+
"stroke-width": mark.strokeWidth ?? 1,
|
|
261
|
+
"stroke-linejoin": "round",
|
|
262
|
+
"stroke-linecap": "round"
|
|
257
263
|
});
|
|
258
264
|
g.appendChild(strokePath);
|
|
259
265
|
}
|
|
@@ -311,6 +317,9 @@ function renderRectMark(mark, index) {
|
|
|
311
317
|
if (mark.strokeWidth) {
|
|
312
318
|
shapeEl.setAttribute("stroke-width", String(mark.strokeWidth));
|
|
313
319
|
}
|
|
320
|
+
if (mark.shapeRendering) {
|
|
321
|
+
shapeEl.setAttribute("shape-rendering", mark.shapeRendering);
|
|
322
|
+
}
|
|
314
323
|
g.appendChild(shapeEl);
|
|
315
324
|
return g;
|
|
316
325
|
}
|
|
@@ -402,6 +411,9 @@ function renderRuleMark(mark, index) {
|
|
|
402
411
|
if (mark.strokeDasharray) {
|
|
403
412
|
line.setAttribute("stroke-dasharray", mark.strokeDasharray);
|
|
404
413
|
}
|
|
414
|
+
if (mark.strokeOpacity != null) {
|
|
415
|
+
line.setAttribute("stroke-opacity", String(mark.strokeOpacity));
|
|
416
|
+
}
|
|
405
417
|
if (mark.opacity != null) {
|
|
406
418
|
line.setAttribute("opacity", String(mark.opacity));
|
|
407
419
|
}
|
|
@@ -454,6 +466,10 @@ function getMarkSeries(mark) {
|
|
|
454
466
|
if (mark.type === "line" || mark.type === "area") {
|
|
455
467
|
return mark.seriesKey;
|
|
456
468
|
}
|
|
469
|
+
const explicitSeries = mark.seriesKey;
|
|
470
|
+
if (explicitSeries) {
|
|
471
|
+
return explicitSeries;
|
|
472
|
+
}
|
|
457
473
|
if (mark.type === "arc") {
|
|
458
474
|
const label = mark.aria.label;
|
|
459
475
|
if (!label) return void 0;
|
|
@@ -819,7 +835,6 @@ function renderAnnotations(parent, layout) {
|
|
|
819
835
|
import {
|
|
820
836
|
axisTitleOffset,
|
|
821
837
|
estimateTextWidth,
|
|
822
|
-
getAxisTitleOffset,
|
|
823
838
|
TICK_LABEL_OFFSET,
|
|
824
839
|
textAscent
|
|
825
840
|
} from "@opendata-ai/openchart-core";
|
|
@@ -1053,7 +1068,19 @@ function renderAxis(parent, axis, orientation, layout, opts) {
|
|
|
1053
1068
|
"text-anchor": "middle"
|
|
1054
1069
|
});
|
|
1055
1070
|
} else if (isRight) {
|
|
1056
|
-
const
|
|
1071
|
+
const maxTickLabelWidth = axis.ticks.reduce((max, t) => {
|
|
1072
|
+
const w = estimateTextWidth(
|
|
1073
|
+
t.label,
|
|
1074
|
+
axis.tickLabelStyle.fontSize,
|
|
1075
|
+
axis.tickLabelStyle.fontWeight ?? 400
|
|
1076
|
+
);
|
|
1077
|
+
return Math.max(max, w);
|
|
1078
|
+
}, 0);
|
|
1079
|
+
const titleOffset = axisTitleOffset(
|
|
1080
|
+
maxTickLabelWidth,
|
|
1081
|
+
axis.labelStyle.fontSize,
|
|
1082
|
+
layout.dimensions.width
|
|
1083
|
+
);
|
|
1057
1084
|
const titleX = area.x + area.width + titleOffset;
|
|
1058
1085
|
setAttrs(axisLabel, {
|
|
1059
1086
|
x: titleX,
|
|
@@ -1161,7 +1188,22 @@ function renderChrome(parent, layout) {
|
|
|
1161
1188
|
const g = createSVGElement("g");
|
|
1162
1189
|
g.setAttribute("class", "oc-chrome");
|
|
1163
1190
|
const { chrome, measureText } = layout;
|
|
1164
|
-
if (chrome.
|
|
1191
|
+
if (chrome.rule) {
|
|
1192
|
+
const rule = createSVGElement("rect");
|
|
1193
|
+
rule.setAttribute("class", "oc-chrome-rule");
|
|
1194
|
+
setAttrs(rule, {
|
|
1195
|
+
x: chrome.rule.x,
|
|
1196
|
+
y: chrome.rule.y,
|
|
1197
|
+
width: chrome.rule.width,
|
|
1198
|
+
height: chrome.rule.thickness
|
|
1199
|
+
});
|
|
1200
|
+
rule.setAttribute("fill", chrome.rule.color);
|
|
1201
|
+
rule.setAttribute("shape-rendering", "crispEdges");
|
|
1202
|
+
g.appendChild(rule);
|
|
1203
|
+
}
|
|
1204
|
+
if (chrome.eyebrow && chrome.rule) {
|
|
1205
|
+
renderChromeElement(g, chrome.eyebrow, "oc-eyebrow", "eyebrow", measureText, true);
|
|
1206
|
+
} else if (chrome.eyebrow) {
|
|
1165
1207
|
const eyebrow = chrome.eyebrow;
|
|
1166
1208
|
const dotR = 3;
|
|
1167
1209
|
const dotGap = 8;
|
|
@@ -1386,6 +1428,8 @@ function renderEndpointLabels(parent, layout) {
|
|
|
1386
1428
|
|
|
1387
1429
|
// src/renderers/legend.ts
|
|
1388
1430
|
import { estimateTextWidth as estimateTextWidth2 } from "@opendata-ai/openchart-core";
|
|
1431
|
+
var LEGEND_HIT_HEIGHT = 24;
|
|
1432
|
+
var LEGEND_HIT_PAD = 4;
|
|
1389
1433
|
function isCategorical(legend) {
|
|
1390
1434
|
return !legend.type || legend.type === "categorical";
|
|
1391
1435
|
}
|
|
@@ -1435,7 +1479,8 @@ function renderContinuousLegend(parent, legend) {
|
|
|
1435
1479
|
});
|
|
1436
1480
|
g.appendChild(bar);
|
|
1437
1481
|
} else {
|
|
1438
|
-
for (
|
|
1482
|
+
for (let i = 0; i < legend.bins.length; i++) {
|
|
1483
|
+
const bin = legend.bins[i];
|
|
1439
1484
|
const rect = createSVGElement("rect");
|
|
1440
1485
|
rect.setAttribute("class", "oc-legend-bin");
|
|
1441
1486
|
setAttrs(rect, {
|
|
@@ -1443,11 +1488,21 @@ function renderContinuousLegend(parent, legend) {
|
|
|
1443
1488
|
y: legend.bar.y,
|
|
1444
1489
|
width: bin.width,
|
|
1445
1490
|
height: legend.bar.height,
|
|
1446
|
-
fill: bin.color
|
|
1491
|
+
fill: bin.color,
|
|
1492
|
+
"shape-rendering": "crispEdges"
|
|
1447
1493
|
});
|
|
1494
|
+
rect.setAttribute("data-bin-index", String(i));
|
|
1448
1495
|
g.appendChild(rect);
|
|
1449
1496
|
}
|
|
1450
1497
|
}
|
|
1498
|
+
if (legend.title && legend.titleY !== void 0) {
|
|
1499
|
+
const title = createSVGElement("text");
|
|
1500
|
+
title.setAttribute("class", "oc-legend-title");
|
|
1501
|
+
setAttrs(title, { x: legend.bar.x, y: legend.titleY, "text-anchor": "start" });
|
|
1502
|
+
applyTextStyle(title, legend.titleStyle ?? legend.labelStyle);
|
|
1503
|
+
title.textContent = legend.title;
|
|
1504
|
+
g.appendChild(title);
|
|
1505
|
+
}
|
|
1451
1506
|
for (const tick of legend.ticks) {
|
|
1452
1507
|
const label = createSVGElement("text");
|
|
1453
1508
|
setAttrs(label, { x: tick.x, y: legend.labelY, "text-anchor": tick.anchor });
|
|
@@ -1455,6 +1510,28 @@ function renderContinuousLegend(parent, legend) {
|
|
|
1455
1510
|
label.textContent = tick.label;
|
|
1456
1511
|
g.appendChild(label);
|
|
1457
1512
|
}
|
|
1513
|
+
if (legend.noData) {
|
|
1514
|
+
const swatch = createSVGElement("rect");
|
|
1515
|
+
swatch.setAttribute("class", "oc-legend-nodata");
|
|
1516
|
+
setAttrs(swatch, {
|
|
1517
|
+
x: legend.noData.x,
|
|
1518
|
+
y: legend.noData.y,
|
|
1519
|
+
width: legend.noData.size,
|
|
1520
|
+
height: legend.noData.size,
|
|
1521
|
+
fill: legend.noData.fill,
|
|
1522
|
+
"shape-rendering": "crispEdges"
|
|
1523
|
+
});
|
|
1524
|
+
g.appendChild(swatch);
|
|
1525
|
+
const label = createSVGElement("text");
|
|
1526
|
+
setAttrs(label, {
|
|
1527
|
+
x: legend.noData.labelX,
|
|
1528
|
+
y: legend.noData.labelY,
|
|
1529
|
+
"text-anchor": "start"
|
|
1530
|
+
});
|
|
1531
|
+
applyTextStyle(label, legend.labelStyle);
|
|
1532
|
+
label.textContent = legend.noData.label;
|
|
1533
|
+
g.appendChild(label);
|
|
1534
|
+
}
|
|
1458
1535
|
parent.appendChild(g);
|
|
1459
1536
|
}
|
|
1460
1537
|
function renderSizeLegend(parent, legend) {
|
|
@@ -1466,6 +1543,14 @@ function renderSizeLegend(parent, legend) {
|
|
|
1466
1543
|
const oy = legend.bounds.y;
|
|
1467
1544
|
const labelX = ox + legend.circles[0].cx + legend.circles[0].radius + 8;
|
|
1468
1545
|
const leaderX = ox + legend.circles[0].cx + legend.circles[0].radius + 4;
|
|
1546
|
+
if (legend.title && legend.titleY !== void 0) {
|
|
1547
|
+
const title = createSVGElement("text");
|
|
1548
|
+
title.setAttribute("class", "oc-legend-title");
|
|
1549
|
+
setAttrs(title, { x: ox, y: oy + legend.titleY, "text-anchor": "start" });
|
|
1550
|
+
applyTextStyle(title, legend.titleStyle ?? legend.labelStyle);
|
|
1551
|
+
title.textContent = legend.title;
|
|
1552
|
+
g.appendChild(title);
|
|
1553
|
+
}
|
|
1469
1554
|
for (const circle of legend.circles) {
|
|
1470
1555
|
const c = createSVGElement("circle");
|
|
1471
1556
|
setAttrs(c, {
|
|
@@ -1511,7 +1596,7 @@ function renderLegend(parent, legend) {
|
|
|
1511
1596
|
if (!isCategorical(legend) || legend.entries.length === 0) return;
|
|
1512
1597
|
const g = createSVGElement("g");
|
|
1513
1598
|
g.setAttribute("class", "oc-legend");
|
|
1514
|
-
g.setAttribute("role", "
|
|
1599
|
+
g.setAttribute("role", "group");
|
|
1515
1600
|
g.setAttribute("aria-label", "Chart legend");
|
|
1516
1601
|
const isHorizontal = legend.position === "top" || legend.position === "top-left" || legend.position === "bottom";
|
|
1517
1602
|
const positions = "entryPositions" in legend ? legend.entryPositions : void 0;
|
|
@@ -1537,7 +1622,6 @@ function renderLegend(parent, legend) {
|
|
|
1537
1622
|
}
|
|
1538
1623
|
const entryG = createSVGElement("g");
|
|
1539
1624
|
entryG.setAttribute("class", "oc-legend-entry");
|
|
1540
|
-
entryG.setAttribute("role", "listitem");
|
|
1541
1625
|
entryG.setAttribute("data-legend-index", String(i));
|
|
1542
1626
|
entryG.setAttribute("data-legend-label", entry.label);
|
|
1543
1627
|
if (entry.overflow) {
|
|
@@ -1545,14 +1629,32 @@ function renderLegend(parent, legend) {
|
|
|
1545
1629
|
entryG.setAttribute("aria-label", entry.label);
|
|
1546
1630
|
entryG.setAttribute("opacity", "0.5");
|
|
1547
1631
|
} else {
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
);
|
|
1632
|
+
const visible = entry.active !== false;
|
|
1633
|
+
entryG.setAttribute("role", "button");
|
|
1634
|
+
entryG.setAttribute("tabindex", "0");
|
|
1635
|
+
entryG.setAttribute("aria-pressed", String(visible));
|
|
1636
|
+
entryG.setAttribute("data-legend-active", String(visible));
|
|
1637
|
+
entryG.setAttribute("aria-label", `${entry.label}: ${visible ? "visible" : "hidden"}`);
|
|
1552
1638
|
entryG.setAttribute("style", "cursor: pointer");
|
|
1553
|
-
if (
|
|
1639
|
+
if (!visible) {
|
|
1554
1640
|
entryG.setAttribute("opacity", "0.3");
|
|
1555
1641
|
}
|
|
1642
|
+
const labelWidth = estimateTextWidth2(
|
|
1643
|
+
entry.label,
|
|
1644
|
+
legend.labelStyle.fontSize,
|
|
1645
|
+
legend.labelStyle.fontWeight
|
|
1646
|
+
);
|
|
1647
|
+
const hit = createSVGElement("rect");
|
|
1648
|
+
hit.setAttribute("class", "oc-legend-hit");
|
|
1649
|
+
setAttrs(hit, {
|
|
1650
|
+
x: offsetX - LEGEND_HIT_PAD,
|
|
1651
|
+
y: offsetY + legend.swatchSize / 2 - LEGEND_HIT_HEIGHT / 2,
|
|
1652
|
+
width: legend.swatchSize + legend.swatchGap + labelWidth + LEGEND_HIT_PAD * 2,
|
|
1653
|
+
height: LEGEND_HIT_HEIGHT,
|
|
1654
|
+
rx: 2,
|
|
1655
|
+
fill: "transparent"
|
|
1656
|
+
});
|
|
1657
|
+
entryG.appendChild(hit);
|
|
1556
1658
|
}
|
|
1557
1659
|
const midX = offsetX + legend.swatchSize / 2;
|
|
1558
1660
|
const midY = offsetY + legend.swatchSize / 2;
|
|
@@ -1941,4 +2043,4 @@ export {
|
|
|
1941
2043
|
renderSingleMark,
|
|
1942
2044
|
renderChartSVG
|
|
1943
2045
|
};
|
|
1944
|
-
//# sourceMappingURL=chunk-
|
|
2046
|
+
//# sourceMappingURL=chunk-YGYMB7KB.js.map
|