@opendata-ai/openchart-vanilla 7.11.0 → 8.0.0-rc.2
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-EQ6DYFO7.js → chunk-5EOV5JQZ.js} +431 -538
- package/dist/chunk-5EOV5JQZ.js.map +1 -0
- package/dist/chunk-ENXSUXC5.js +6513 -0
- package/dist/chunk-ENXSUXC5.js.map +1 -0
- package/dist/chunk-W34KN7FD.js +298 -0
- package/dist/chunk-W34KN7FD.js.map +1 -0
- package/dist/index.d.ts +110 -116
- package/dist/index.js +189 -4408
- package/dist/index.js.map +1 -1
- package/dist/mount-CUKQLeqD.d.ts +207 -0
- package/dist/static.js +16 -11
- package/dist/static.js.map +1 -1
- package/dist/story/index.d.ts +277 -0
- package/dist/story/index.js +537 -0
- package/dist/story/index.js.map +1 -0
- package/dist/styles.css +2 -1
- package/dist/styles.css.map +1 -0
- package/package.json +9 -3
- package/src/__test-fixtures__/specs.ts +1 -1
- package/src/__tests__/annotations-render.test.ts +201 -0
- package/src/__tests__/continuous-legend.test.ts +91 -0
- package/src/__tests__/edit-events.test.ts +155 -0
- package/src/__tests__/map.test.ts +293 -0
- package/src/__tests__/mount.test.ts +26 -0
- package/src/__tests__/pattern-fills.test.ts +105 -0
- package/src/__tests__/series-search.test.ts +311 -0
- package/src/__tests__/table-mount.test.ts +34 -0
- package/src/__tests__/tooltip.test.ts +26 -0
- package/src/__tests__/transition.test.ts +196 -11
- package/src/__tests__/you-draw-it.test.ts +273 -0
- package/src/animation-vars.ts +42 -0
- package/src/barlist-renderer.ts +2 -2
- package/src/dom-helpers.ts +17 -0
- package/src/export.ts +33 -16
- package/src/index.ts +7 -0
- package/src/interactions/editing-drags.ts +9 -6
- package/src/interactions/legend-interaction.ts +9 -1
- package/src/interactions/selection.ts +12 -3
- package/src/map-camera.ts +88 -0
- package/src/map-mount.ts +701 -0
- package/src/map-renderer.ts +338 -0
- package/src/map-transition.ts +83 -0
- package/src/mount.ts +212 -26
- package/src/pattern-utils.ts +110 -0
- package/src/renderers/annotations.ts +162 -117
- package/src/renderers/axes.ts +13 -6
- package/src/renderers/brand.ts +7 -2
- package/src/renderers/chrome.ts +32 -10
- package/src/renderers/legend.ts +171 -2
- package/src/renderers/marks.ts +33 -5
- package/src/sankey-renderer.ts +7 -11
- package/src/series-search.ts +392 -0
- package/src/static.ts +13 -8
- package/src/story/__tests__/camera-math.test.ts +145 -0
- package/src/story/__tests__/create-chart-story.test.ts +317 -0
- package/src/story/__tests__/progress-math.test.ts +95 -0
- package/src/story/__tests__/scroll-driver.test.ts +130 -0
- package/src/story/__tests__/tween.test.ts +159 -0
- package/src/story/camera-math.ts +125 -0
- package/src/story/create-chart-story.ts +353 -0
- package/src/story/crossfade.ts +70 -0
- package/src/story/index.ts +39 -0
- package/src/story/progress-math.ts +85 -0
- package/src/story/resolve-camera-target.ts +119 -0
- package/src/story/scroll-driver.ts +206 -0
- package/src/story/story-camera.ts +49 -0
- package/src/story/tween.ts +112 -0
- package/src/story/types.ts +76 -0
- package/src/svg-renderer.ts +60 -29
- package/src/table-mount.ts +12 -3
- package/src/table-renderer.ts +9 -28
- package/src/text-edit-overlay.ts +1 -1
- package/src/theme-tokens.ts +4 -4
- package/src/tilemap-mount.ts +27 -6
- package/src/tilemap-renderer.ts +30 -88
- package/src/tooltip.ts +11 -0
- package/src/transition.ts +376 -33
- package/src/you-draw-it.ts +480 -0
- package/dist/chunk-EQ6DYFO7.js.map +0 -1
|
@@ -1,12 +1,3 @@
|
|
|
1
|
-
// src/svg-ids.ts
|
|
2
|
-
var counter = 0;
|
|
3
|
-
function nextSvgId(prefix) {
|
|
4
|
-
return `${prefix}-${counter++}`;
|
|
5
|
-
}
|
|
6
|
-
function resetSvgIdCounter() {
|
|
7
|
-
counter = 0;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
1
|
// src/renderers/svg-dom.ts
|
|
11
2
|
var SVG_NS = "http://www.w3.org/2000/svg";
|
|
12
3
|
var XLINK_NS = "http://www.w3.org/1999/xlink";
|
|
@@ -35,6 +26,15 @@ function applyTextStyle(el, style) {
|
|
|
35
26
|
}
|
|
36
27
|
}
|
|
37
28
|
|
|
29
|
+
// src/svg-ids.ts
|
|
30
|
+
var counter = 0;
|
|
31
|
+
function nextSvgId(prefix) {
|
|
32
|
+
return `${prefix}-${counter++}`;
|
|
33
|
+
}
|
|
34
|
+
function resetSvgIdCounter() {
|
|
35
|
+
counter = 0;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
38
|
// src/gradient-utils.ts
|
|
39
39
|
import { isGradientDef } from "@opendata-ai/openchart-core";
|
|
40
40
|
var SVG_NS2 = "http://www.w3.org/2000/svg";
|
|
@@ -116,16 +116,79 @@ function resolveMarkFill(fill, gradientMap) {
|
|
|
116
116
|
return id ? `url(#${id})` : "#000000";
|
|
117
117
|
}
|
|
118
118
|
|
|
119
|
+
// src/pattern-utils.ts
|
|
120
|
+
var SVG_NS3 = "http://www.w3.org/2000/svg";
|
|
121
|
+
var TILE = 6;
|
|
122
|
+
var LINE_OPACITY = 0.55;
|
|
123
|
+
function patternKey(p) {
|
|
124
|
+
return `${p.type}|${p.base}|${p.line}`;
|
|
125
|
+
}
|
|
126
|
+
function createPatternElement(p, id) {
|
|
127
|
+
const el = document.createElementNS(SVG_NS3, "pattern");
|
|
128
|
+
el.setAttribute("id", id);
|
|
129
|
+
el.setAttribute("patternUnits", "userSpaceOnUse");
|
|
130
|
+
el.setAttribute("width", String(TILE));
|
|
131
|
+
el.setAttribute("height", String(TILE));
|
|
132
|
+
const bg = document.createElementNS(SVG_NS3, "rect");
|
|
133
|
+
bg.setAttribute("width", String(TILE));
|
|
134
|
+
bg.setAttribute("height", String(TILE));
|
|
135
|
+
bg.setAttribute("fill", p.base);
|
|
136
|
+
el.appendChild(bg);
|
|
137
|
+
if (p.type === "dot") {
|
|
138
|
+
const dot = document.createElementNS(SVG_NS3, "circle");
|
|
139
|
+
dot.setAttribute("cx", String(TILE / 2));
|
|
140
|
+
dot.setAttribute("cy", String(TILE / 2));
|
|
141
|
+
dot.setAttribute("r", "1.2");
|
|
142
|
+
dot.setAttribute("fill", p.line);
|
|
143
|
+
dot.setAttribute("fill-opacity", String(LINE_OPACITY));
|
|
144
|
+
el.appendChild(dot);
|
|
145
|
+
return el;
|
|
146
|
+
}
|
|
147
|
+
const d = p.type === "diagonal" ? `M0,${TILE} l${TILE},-${TILE} M-1.5,1.5 l3,-3 M${TILE - 1.5},${TILE + 1.5} l3,-3` : p.type === "crosshatch" ? `M0,0 l${TILE},${TILE} M${TILE},0 l-${TILE},${TILE}` : `M${TILE / 2},0 V${TILE}`;
|
|
148
|
+
const path = document.createElementNS(SVG_NS3, "path");
|
|
149
|
+
path.setAttribute("d", d);
|
|
150
|
+
path.setAttribute("stroke", p.line);
|
|
151
|
+
path.setAttribute("stroke-width", "1");
|
|
152
|
+
path.setAttribute("stroke-opacity", String(LINE_OPACITY));
|
|
153
|
+
el.appendChild(path);
|
|
154
|
+
return el;
|
|
155
|
+
}
|
|
156
|
+
function buildPatternDefs(marks, defs) {
|
|
157
|
+
const map = /* @__PURE__ */ new Map();
|
|
158
|
+
for (const mark of marks) {
|
|
159
|
+
const pattern = mark.pattern;
|
|
160
|
+
if (!pattern) continue;
|
|
161
|
+
const key = patternKey(pattern);
|
|
162
|
+
if (!map.has(key)) {
|
|
163
|
+
const id = nextSvgId("oc-pattern");
|
|
164
|
+
defs.appendChild(createPatternElement(pattern, id));
|
|
165
|
+
map.set(key, id);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
return map;
|
|
169
|
+
}
|
|
170
|
+
function resolvePatternFill(pattern, patternMap) {
|
|
171
|
+
const id = patternMap.get(patternKey(pattern));
|
|
172
|
+
return id ? `url(#${id})` : pattern.base;
|
|
173
|
+
}
|
|
174
|
+
|
|
119
175
|
// src/renderers/marks.ts
|
|
120
176
|
var currentAnimation;
|
|
121
177
|
var currentGradientMap = /* @__PURE__ */ new Map();
|
|
178
|
+
var currentPatternMap = /* @__PURE__ */ new Map();
|
|
122
179
|
function setMarkRenderState(state) {
|
|
123
180
|
currentAnimation = state.animation;
|
|
124
181
|
currentGradientMap = state.gradientMap;
|
|
182
|
+
currentPatternMap = state.patternMap ?? /* @__PURE__ */ new Map();
|
|
125
183
|
}
|
|
126
184
|
function resetMarkRenderState() {
|
|
127
185
|
currentAnimation = void 0;
|
|
128
186
|
currentGradientMap = /* @__PURE__ */ new Map();
|
|
187
|
+
currentPatternMap = /* @__PURE__ */ new Map();
|
|
188
|
+
}
|
|
189
|
+
function resolveFillOrPattern(fill, pattern) {
|
|
190
|
+
if (pattern) return resolvePatternFill(pattern, currentPatternMap);
|
|
191
|
+
return resolveMarkFill(fill, currentGradientMap);
|
|
129
192
|
}
|
|
130
193
|
function stampAnimationAttrs(el, mark, fallbackIndex) {
|
|
131
194
|
if (mark.key) {
|
|
@@ -198,7 +261,7 @@ function renderAreaMark(mark, index) {
|
|
|
198
261
|
const fill = createSVGElement("path");
|
|
199
262
|
setAttrs(fill, {
|
|
200
263
|
d: mark.path,
|
|
201
|
-
fill:
|
|
264
|
+
fill: resolveFillOrPattern(mark.fill, mark.pattern),
|
|
202
265
|
"fill-opacity": mark.fillOpacity,
|
|
203
266
|
stroke: "none"
|
|
204
267
|
});
|
|
@@ -261,7 +324,7 @@ function renderRectMark(mark, index) {
|
|
|
261
324
|
setAttrs(shapeEl, { rx: mark.cornerRadius, ry: mark.cornerRadius });
|
|
262
325
|
}
|
|
263
326
|
}
|
|
264
|
-
shapeEl.setAttribute("fill",
|
|
327
|
+
shapeEl.setAttribute("fill", resolveFillOrPattern(mark.fill, mark.pattern));
|
|
265
328
|
if (mark.stroke) {
|
|
266
329
|
shapeEl.setAttribute("stroke", mark.stroke);
|
|
267
330
|
}
|
|
@@ -280,7 +343,7 @@ function renderArcMark(mark, index) {
|
|
|
280
343
|
const path = createSVGElement("path");
|
|
281
344
|
setAttrs(path, {
|
|
282
345
|
d: mark.path,
|
|
283
|
-
fill:
|
|
346
|
+
fill: resolveFillOrPattern(mark.fill, mark.pattern),
|
|
284
347
|
stroke: mark.stroke,
|
|
285
348
|
"stroke-width": mark.strokeWidth
|
|
286
349
|
});
|
|
@@ -327,6 +390,9 @@ function renderTextMark(mark, index) {
|
|
|
327
390
|
"font-size": mark.fontSize,
|
|
328
391
|
"text-anchor": mark.textAnchor
|
|
329
392
|
});
|
|
393
|
+
if (mark.dominantBaseline) {
|
|
394
|
+
text.setAttribute("dominant-baseline", mark.dominantBaseline);
|
|
395
|
+
}
|
|
330
396
|
text.style.setProperty("fill", mark.fill);
|
|
331
397
|
if (mark.fontWeight) {
|
|
332
398
|
text.setAttribute("font-weight", String(mark.fontWeight));
|
|
@@ -478,53 +544,99 @@ function renderMarks(parent, layout) {
|
|
|
478
544
|
// src/svg-renderer.ts
|
|
479
545
|
import { clampStaggerDelay } from "@opendata-ai/openchart-engine";
|
|
480
546
|
|
|
547
|
+
// src/animation-vars.ts
|
|
548
|
+
var EASE_VAR_MAP = {
|
|
549
|
+
smooth: "var(--oc-ease-smooth)",
|
|
550
|
+
snappy: "var(--oc-ease-snappy)"
|
|
551
|
+
};
|
|
552
|
+
function stampAnimationVars(el, v) {
|
|
553
|
+
el.style.setProperty("--oc-animation-duration", `${v.duration}ms`);
|
|
554
|
+
el.style.setProperty("--oc-animation-stagger", `${v.stagger}ms`);
|
|
555
|
+
if (v.annotationDelay !== void 0) {
|
|
556
|
+
el.style.setProperty("--oc-annotation-delay", `${v.annotationDelay}ms`);
|
|
557
|
+
}
|
|
558
|
+
if (v.ease !== void 0) {
|
|
559
|
+
el.style.setProperty("--oc-animation-ease", EASE_VAR_MAP[v.ease] || EASE_VAR_MAP.smooth);
|
|
560
|
+
}
|
|
561
|
+
if (v.stackSegmentDuration !== void 0) {
|
|
562
|
+
el.style.setProperty("--oc-stack-segment-duration", `${v.stackSegmentDuration}ms`);
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
|
|
481
566
|
// src/renderers/annotations.ts
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
const
|
|
493
|
-
const
|
|
494
|
-
|
|
495
|
-
|
|
567
|
+
import {
|
|
568
|
+
ARROWHEAD_LENGTH,
|
|
569
|
+
BOLD_SPAN_FONT_WEIGHT,
|
|
570
|
+
computeArrowheadPoints,
|
|
571
|
+
DEFAULT_ANNOTATION_FONT_SIZE,
|
|
572
|
+
DEFAULT_LINE_HEIGHT,
|
|
573
|
+
parseAnnotationSpans
|
|
574
|
+
} from "@opendata-ai/openchart-engine";
|
|
575
|
+
var CONNECTOR_STROKE_WIDTH = 1.25;
|
|
576
|
+
function renderArrowhead(parent, tipX, tipY, tangentX, tangentY, stroke) {
|
|
577
|
+
const head = computeArrowheadPoints(tipX, tipY, tangentX, tangentY);
|
|
578
|
+
const arrow = createSVGElement("polyline");
|
|
579
|
+
arrow.setAttribute("class", "oc-annotation-connector oc-annotation-arrowhead");
|
|
580
|
+
setAttrs(arrow, {
|
|
581
|
+
points: [
|
|
582
|
+
`${head.baseLeft.x},${head.baseLeft.y}`,
|
|
583
|
+
`${head.tip.x},${head.tip.y}`,
|
|
584
|
+
`${head.baseRight.x},${head.baseRight.y}`
|
|
585
|
+
].join(" "),
|
|
586
|
+
fill: "none",
|
|
587
|
+
stroke,
|
|
588
|
+
"stroke-width": CONNECTOR_STROKE_WIDTH,
|
|
589
|
+
"stroke-linecap": "round",
|
|
590
|
+
"stroke-linejoin": "round"
|
|
591
|
+
});
|
|
592
|
+
parent.appendChild(arrow);
|
|
593
|
+
}
|
|
594
|
+
function renderCurvedConnector(parent, from, to, stroke, showArrow, exit) {
|
|
595
|
+
const dx = to.x - from.x;
|
|
596
|
+
const dy = to.y - from.y;
|
|
597
|
+
const vertical = exit ? exit === "vertical" : Math.abs(dy) >= Math.abs(dx);
|
|
598
|
+
const cpx = from.x + dx * (vertical ? 0.2 : 0.6);
|
|
599
|
+
const cpy = from.y + dy * (vertical ? 0.6 : 0.2);
|
|
600
|
+
const tx = to.x - cpx;
|
|
601
|
+
const ty = to.y - cpy;
|
|
496
602
|
const tLen = Math.sqrt(tx * tx + ty * ty) || 1;
|
|
497
|
-
const
|
|
498
|
-
const
|
|
499
|
-
const baseX = to.x - ux * arrowLen;
|
|
500
|
-
const baseY = tipY - uy * arrowLen;
|
|
603
|
+
const endX = showArrow ? to.x - tx / tLen * ARROWHEAD_LENGTH : to.x;
|
|
604
|
+
const endY = showArrow ? to.y - ty / tLen * ARROWHEAD_LENGTH : to.y;
|
|
501
605
|
const path = createSVGElement("path");
|
|
502
606
|
path.setAttribute("class", "oc-annotation-connector");
|
|
503
607
|
setAttrs(path, {
|
|
504
|
-
d: `M ${from.x} ${from.y}
|
|
608
|
+
d: `M ${from.x} ${from.y} Q ${cpx} ${cpy} ${endX} ${endY}`,
|
|
505
609
|
fill: "none",
|
|
506
610
|
stroke,
|
|
507
|
-
"stroke-width":
|
|
611
|
+
"stroke-width": CONNECTOR_STROKE_WIDTH,
|
|
612
|
+
"stroke-linecap": "round"
|
|
508
613
|
});
|
|
509
614
|
parent.appendChild(path);
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
615
|
+
if (showArrow) {
|
|
616
|
+
renderArrowhead(parent, to.x, to.y, tx, ty, stroke);
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
function fillRichText(text, content, x, lineHeight) {
|
|
620
|
+
const lines = content.split("\n");
|
|
621
|
+
for (let i = 0; i < lines.length; i++) {
|
|
622
|
+
const spans = parseAnnotationSpans(lines[i]);
|
|
623
|
+
for (let s = 0; s < spans.length; s++) {
|
|
624
|
+
const tspan = createSVGElement("tspan");
|
|
625
|
+
if (s === 0) {
|
|
626
|
+
setAttrs(tspan, { x, dy: i === 0 ? 0 : lineHeight });
|
|
627
|
+
}
|
|
628
|
+
if (spans[s].bold) {
|
|
629
|
+
tspan.setAttribute("font-weight", String(BOLD_SPAN_FONT_WEIGHT));
|
|
630
|
+
}
|
|
631
|
+
tspan.textContent = spans[s].text;
|
|
632
|
+
text.appendChild(tspan);
|
|
633
|
+
}
|
|
634
|
+
}
|
|
523
635
|
}
|
|
524
636
|
function renderAnnotation(parent, annotation, index, bgColor) {
|
|
525
637
|
const g = createSVGElement("g");
|
|
526
638
|
g.setAttribute("class", `oc-annotation oc-annotation-${annotation.type}`);
|
|
527
|
-
g.setAttribute("data-annotation-index", String(index));
|
|
639
|
+
g.setAttribute("data-annotation-index", String(annotation.specIndex ?? index));
|
|
528
640
|
if (annotation.id) {
|
|
529
641
|
g.setAttribute("data-annotation-id", annotation.id);
|
|
530
642
|
}
|
|
@@ -561,8 +673,8 @@ function renderAnnotation(parent, annotation, index, bgColor) {
|
|
|
561
673
|
g.appendChild(line);
|
|
562
674
|
}
|
|
563
675
|
if (annotation.footnoteIndex != null && annotation.label) {
|
|
564
|
-
const cx = annotation.label.connector?.endpoint?.x ?? annotation.label.connector?.to.x ?? annotation.label.x;
|
|
565
|
-
const cy = annotation.label.connector?.endpoint?.y ?? annotation.label.connector?.to.y ?? annotation.label.y;
|
|
676
|
+
const cx = annotation.label.connector?.endpoint?.x ?? annotation.label.connector?.to.x ?? annotation.dot?.x ?? annotation.label.x;
|
|
677
|
+
const cy = annotation.label.connector?.endpoint?.y ?? annotation.label.connector?.to.y ?? annotation.dot?.y ?? annotation.label.y;
|
|
566
678
|
const r = 8;
|
|
567
679
|
const circle = createSVGElement("circle");
|
|
568
680
|
circle.setAttribute("class", "oc-annotation-footnote-marker");
|
|
@@ -597,7 +709,7 @@ function renderAnnotation(parent, annotation, index, bgColor) {
|
|
|
597
709
|
if (annotation.label.connector) {
|
|
598
710
|
const c = annotation.label.connector;
|
|
599
711
|
if (c.style === "curve") {
|
|
600
|
-
|
|
712
|
+
renderCurvedConnector(g, c.from, c.to, c.stroke, c.arrow, c.exit);
|
|
601
713
|
} else if (c.style === "drop-line") {
|
|
602
714
|
const connector = createSVGElement("line");
|
|
603
715
|
connector.setAttribute("class", "oc-annotation-connector oc-annotation-drop-line");
|
|
@@ -608,10 +720,31 @@ function renderAnnotation(parent, annotation, index, bgColor) {
|
|
|
608
720
|
y2: c.to.y,
|
|
609
721
|
stroke: c.stroke,
|
|
610
722
|
"stroke-width": 1,
|
|
611
|
-
"stroke-opacity": 0.
|
|
723
|
+
"stroke-opacity": 0.7,
|
|
612
724
|
"shape-rendering": "crispEdges"
|
|
613
725
|
});
|
|
614
726
|
g.appendChild(connector);
|
|
727
|
+
} else if (c.arrow) {
|
|
728
|
+
const dx = c.to.x - c.from.x;
|
|
729
|
+
const dy = c.to.y - c.from.y;
|
|
730
|
+
const dist = Math.sqrt(dx * dx + dy * dy) || 1;
|
|
731
|
+
const ux = dx / dist;
|
|
732
|
+
const uy = dy / dist;
|
|
733
|
+
const lineEndX = c.to.x - ux * ARROWHEAD_LENGTH;
|
|
734
|
+
const lineEndY = c.to.y - uy * ARROWHEAD_LENGTH;
|
|
735
|
+
const connector = createSVGElement("line");
|
|
736
|
+
connector.setAttribute("class", "oc-annotation-connector");
|
|
737
|
+
setAttrs(connector, {
|
|
738
|
+
x1: c.from.x,
|
|
739
|
+
y1: c.from.y,
|
|
740
|
+
x2: lineEndX,
|
|
741
|
+
y2: lineEndY,
|
|
742
|
+
stroke: c.stroke,
|
|
743
|
+
"stroke-width": CONNECTOR_STROKE_WIDTH,
|
|
744
|
+
"stroke-linecap": "round"
|
|
745
|
+
});
|
|
746
|
+
g.appendChild(connector);
|
|
747
|
+
renderArrowhead(g, c.to.x, c.to.y, dx, dy, c.stroke);
|
|
615
748
|
} else {
|
|
616
749
|
const connector = createSVGElement("line");
|
|
617
750
|
connector.setAttribute("class", "oc-annotation-connector");
|
|
@@ -621,33 +754,11 @@ function renderAnnotation(parent, annotation, index, bgColor) {
|
|
|
621
754
|
x2: c.to.x,
|
|
622
755
|
y2: c.to.y,
|
|
623
756
|
stroke: c.stroke,
|
|
624
|
-
"stroke-width":
|
|
625
|
-
"stroke-
|
|
757
|
+
"stroke-width": CONNECTOR_STROKE_WIDTH,
|
|
758
|
+
"stroke-linecap": "round"
|
|
626
759
|
});
|
|
627
760
|
g.appendChild(connector);
|
|
628
761
|
}
|
|
629
|
-
if (c.endpoint && c.style !== "curve") {
|
|
630
|
-
const ring = createSVGElement("circle");
|
|
631
|
-
ring.setAttribute("class", "oc-annotation-endpoint-ring");
|
|
632
|
-
setAttrs(ring, {
|
|
633
|
-
cx: c.endpoint.x,
|
|
634
|
-
cy: c.endpoint.y,
|
|
635
|
-
r: 5,
|
|
636
|
-
fill: bgColor ?? "#ffffff",
|
|
637
|
-
stroke: c.stroke,
|
|
638
|
-
"stroke-width": 1.5
|
|
639
|
-
});
|
|
640
|
-
g.appendChild(ring);
|
|
641
|
-
const dot = createSVGElement("circle");
|
|
642
|
-
dot.setAttribute("class", "oc-annotation-endpoint-dot");
|
|
643
|
-
setAttrs(dot, {
|
|
644
|
-
cx: c.endpoint.x,
|
|
645
|
-
cy: c.endpoint.y,
|
|
646
|
-
r: 2,
|
|
647
|
-
fill: c.stroke
|
|
648
|
-
});
|
|
649
|
-
g.appendChild(dot);
|
|
650
|
-
}
|
|
651
762
|
}
|
|
652
763
|
if (annotation.dot) {
|
|
653
764
|
const dot = createSVGElement("circle");
|
|
@@ -666,41 +777,16 @@ function renderAnnotation(parent, annotation, index, bgColor) {
|
|
|
666
777
|
text.setAttribute("class", "oc-annotation-label");
|
|
667
778
|
setAttrs(text, { x: annotation.label.x, y: annotation.label.y });
|
|
668
779
|
applyTextStyle(text, annotation.label.style);
|
|
669
|
-
const
|
|
670
|
-
const
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
if (isMultiLine) {
|
|
674
|
-
for (let i = 0; i < lines.length; i++) {
|
|
675
|
-
const tspan = createSVGElement("tspan");
|
|
676
|
-
setAttrs(tspan, { x: annotation.label.x, dy: i === 0 ? 0 : lineHeight });
|
|
677
|
-
tspan.textContent = lines[i];
|
|
678
|
-
text.appendChild(tspan);
|
|
679
|
-
}
|
|
680
|
-
} else {
|
|
681
|
-
text.textContent = annotation.label.text;
|
|
682
|
-
}
|
|
683
|
-
if (annotation.label.background) {
|
|
780
|
+
const fontSize = annotation.label.style.fontSize ?? DEFAULT_ANNOTATION_FONT_SIZE;
|
|
781
|
+
const lineHeight = fontSize * (annotation.label.style.lineHeight ?? DEFAULT_LINE_HEIGHT);
|
|
782
|
+
fillRichText(text, annotation.label.text, annotation.label.x, lineHeight);
|
|
783
|
+
if (annotation.label.background && annotation.label.bounds) {
|
|
684
784
|
const pad = 3;
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
const b = annotation.label.bounds;
|
|
691
|
-
bgX = b.x - pad;
|
|
692
|
-
bgY = b.y - pad;
|
|
693
|
-
bgW = b.width + pad * 2;
|
|
694
|
-
bgH = b.height + pad * 2;
|
|
695
|
-
} else {
|
|
696
|
-
const charWidth = fontSize * 0.55;
|
|
697
|
-
const maxLineWidth = Math.max(...lines.map((l) => l.length)) * charWidth;
|
|
698
|
-
const totalHeight = lines.length * lineHeight;
|
|
699
|
-
bgX = isMultiLine ? annotation.label.x - maxLineWidth / 2 - pad : annotation.label.x - pad;
|
|
700
|
-
bgY = annotation.label.y - fontSize + (lineHeight - fontSize) / 2 - pad;
|
|
701
|
-
bgW = maxLineWidth + pad * 2;
|
|
702
|
-
bgH = totalHeight + pad * 2;
|
|
703
|
-
}
|
|
785
|
+
const b = annotation.label.bounds;
|
|
786
|
+
const bgX = b.x - pad;
|
|
787
|
+
const bgY = b.y - pad;
|
|
788
|
+
const bgW = b.width + pad * 2;
|
|
789
|
+
const bgH = b.height + pad * 2;
|
|
704
790
|
const bgRect = createSVGElement("rect");
|
|
705
791
|
bgRect.setAttribute("class", "oc-annotation-bg");
|
|
706
792
|
setAttrs(bgRect, {
|
|
@@ -724,7 +810,9 @@ function renderAnnotation(parent, annotation, index, bgColor) {
|
|
|
724
810
|
sub.setAttribute("class", "oc-annotation-subtitle");
|
|
725
811
|
setAttrs(sub, { x: annotation.subtitle.x, y: annotation.subtitle.y });
|
|
726
812
|
applyTextStyle(sub, annotation.subtitle.style);
|
|
727
|
-
|
|
813
|
+
const subFontSize = annotation.subtitle.style.fontSize ?? DEFAULT_ANNOTATION_FONT_SIZE;
|
|
814
|
+
const subLineHeight = subFontSize * (annotation.subtitle.style.lineHeight ?? DEFAULT_LINE_HEIGHT);
|
|
815
|
+
fillRichText(sub, annotation.subtitle.text, annotation.subtitle.x, subLineHeight);
|
|
728
816
|
g.appendChild(sub);
|
|
729
817
|
}
|
|
730
818
|
}
|
|
@@ -811,6 +899,11 @@ function renderAxis(parent, axis, orientation, layout) {
|
|
|
811
899
|
}
|
|
812
900
|
applyTextStyle(label, axis.tickLabelStyle);
|
|
813
901
|
label.textContent = tick.label;
|
|
902
|
+
if (tick.fullLabel) {
|
|
903
|
+
const title = createSVGElement("title");
|
|
904
|
+
title.textContent = tick.fullLabel;
|
|
905
|
+
label.appendChild(title);
|
|
906
|
+
}
|
|
814
907
|
g.appendChild(label);
|
|
815
908
|
} else if (isInlineY) {
|
|
816
909
|
const label = createSVGElement("text");
|
|
@@ -955,16 +1048,14 @@ function renderAxis(parent, axis, orientation, layout) {
|
|
|
955
1048
|
}
|
|
956
1049
|
setAttrs(axisLabel, attrs);
|
|
957
1050
|
} else if (orientation === "x") {
|
|
958
|
-
|
|
1051
|
+
const tickFontSize = axis.tickLabelStyle.fontSize;
|
|
1052
|
+
const tickBand = Math.max(26, 4 + tickFontSize + Math.max(11, Math.ceil(tickFontSize * 0.7)));
|
|
1053
|
+
let titleY = area.y + area.height + tickBand + 9;
|
|
959
1054
|
if (axis.tickAngle && Math.abs(axis.tickAngle) > 10) {
|
|
960
1055
|
const angleRad = Math.abs(axis.tickAngle) * (Math.PI / 180);
|
|
961
1056
|
let maxLabelWidth = 40;
|
|
962
1057
|
for (const tick of axis.ticks) {
|
|
963
|
-
const w = estimateTextWidth(
|
|
964
|
-
tick.label,
|
|
965
|
-
axis.tickLabelStyle.fontSize,
|
|
966
|
-
axis.tickLabelStyle.fontWeight
|
|
967
|
-
);
|
|
1058
|
+
const w = estimateTextWidth(tick.label, tickFontSize, axis.tickLabelStyle.fontWeight);
|
|
968
1059
|
if (w > maxLabelWidth) maxLabelWidth = w;
|
|
969
1060
|
}
|
|
970
1061
|
const rotatedHeight = Math.min(maxLabelWidth * Math.sin(angleRad) + 6, 120);
|
|
@@ -1023,59 +1114,22 @@ function renderAxes(parent, layout) {
|
|
|
1023
1114
|
}
|
|
1024
1115
|
|
|
1025
1116
|
// src/renderers/brand.ts
|
|
1026
|
-
import { BRAND_FONT_SIZE, BRAND_MIN_WIDTH, textAscent as
|
|
1027
|
-
var BRAND_URL = "https://tryopendata.ai";
|
|
1028
|
-
function renderBrand(parent, layout) {
|
|
1029
|
-
if (layout.dimensions.width < BRAND_MIN_WIDTH) return;
|
|
1030
|
-
const { width } = layout.dimensions;
|
|
1031
|
-
const padding = layout.theme.spacing.padding;
|
|
1032
|
-
const rightEdge = width - padding;
|
|
1033
|
-
const fill = layout.theme.colors.axis;
|
|
1034
|
-
const { chrome } = layout;
|
|
1035
|
-
const bottomOffset = chrome.bottomAnchorY ?? layout.area.y + layout.area.height;
|
|
1036
|
-
const firstBottom = chrome.source ?? chrome.byline ?? chrome.footer;
|
|
1037
|
-
const { legend } = layout;
|
|
1038
|
-
const bottomLegendOffset = legend.position === "bottom" && legend.bounds.height > 0 ? legend.bounds.height + 8 : 0;
|
|
1039
|
-
const chromeY = firstBottom ? bottomOffset + firstBottom.y : bottomOffset + layout.theme.spacing.chartToFooter + bottomLegendOffset;
|
|
1040
|
-
const a = createSVGElement("a");
|
|
1041
|
-
a.setAttribute("href", BRAND_URL);
|
|
1042
|
-
a.setAttributeNS(XLINK_NS, "xlink:href", BRAND_URL);
|
|
1043
|
-
a.setAttribute("target", "_blank");
|
|
1044
|
-
a.setAttribute("rel", "noopener");
|
|
1045
|
-
a.setAttribute("class", "oc-chrome-ref");
|
|
1046
|
-
const BRAND_LARGE = 16;
|
|
1047
|
-
const text = createSVGElement("text");
|
|
1048
|
-
setAttrs(text, {
|
|
1049
|
-
x: rightEdge,
|
|
1050
|
-
y: chromeY + textAscent2(BRAND_LARGE),
|
|
1051
|
-
"font-family": layout.theme.fonts.family,
|
|
1052
|
-
"font-size": BRAND_FONT_SIZE,
|
|
1053
|
-
"text-anchor": "end",
|
|
1054
|
-
"fill-opacity": 0.55
|
|
1055
|
-
});
|
|
1056
|
-
text.style.setProperty("fill", fill);
|
|
1057
|
-
const trySpan = createSVGElement("tspan");
|
|
1058
|
-
trySpan.setAttribute("font-weight", "500");
|
|
1059
|
-
trySpan.textContent = "try";
|
|
1060
|
-
text.appendChild(trySpan);
|
|
1061
|
-
const openDataSpan = createSVGElement("tspan");
|
|
1062
|
-
openDataSpan.setAttribute("font-weight", "600");
|
|
1063
|
-
openDataSpan.setAttribute("font-size", String(BRAND_LARGE));
|
|
1064
|
-
openDataSpan.textContent = "OpenData";
|
|
1065
|
-
text.appendChild(openDataSpan);
|
|
1066
|
-
const aiSpan = createSVGElement("tspan");
|
|
1067
|
-
aiSpan.setAttribute("font-weight", "500");
|
|
1068
|
-
aiSpan.textContent = ".ai";
|
|
1069
|
-
text.appendChild(aiSpan);
|
|
1070
|
-
a.appendChild(text);
|
|
1071
|
-
parent.appendChild(a);
|
|
1072
|
-
}
|
|
1117
|
+
import { BRAND_FONT_SIZE, BRAND_MIN_WIDTH, textAscent as textAscent3 } from "@opendata-ai/openchart-core";
|
|
1073
1118
|
|
|
1074
1119
|
// src/renderers/chrome.ts
|
|
1075
|
-
import {
|
|
1120
|
+
import {
|
|
1121
|
+
estimateTextWidth as estimateTextWidth2,
|
|
1122
|
+
FOOTNOTE_LINE_HEIGHT,
|
|
1123
|
+
footnoteBandHeight as footnoteBandHeightFor,
|
|
1124
|
+
textAscent as textAscent2,
|
|
1125
|
+
wrapText
|
|
1126
|
+
} from "@opendata-ai/openchart-core";
|
|
1127
|
+
function footnoteBandHeight(layout) {
|
|
1128
|
+
return footnoteBandHeightFor(layout.chrome.footnotes?.length ?? 0, layout.theme);
|
|
1129
|
+
}
|
|
1076
1130
|
function renderChromeElement(parent, element, className, chromeKey, measureText, uppercase = false) {
|
|
1077
1131
|
const text = createSVGElement("text");
|
|
1078
|
-
setAttrs(text, { x: element.x, y: element.y +
|
|
1132
|
+
setAttrs(text, { x: element.x, y: element.y + textAscent2(element.style.fontSize) });
|
|
1079
1133
|
applyTextStyle(text, element.style);
|
|
1080
1134
|
text.setAttribute("class", className);
|
|
1081
1135
|
text.setAttribute("data-chrome-key", chromeKey);
|
|
@@ -1128,22 +1182,22 @@ function renderChrome(parent, layout) {
|
|
|
1128
1182
|
renderChromeElement(g, chrome.subtitle, "oc-subtitle", "subtitle", measureText);
|
|
1129
1183
|
}
|
|
1130
1184
|
const bottomOffset = layout.chrome.bottomAnchorY ?? layout.area.y + layout.area.height;
|
|
1131
|
-
|
|
1185
|
+
const bandHeight = footnoteBandHeight(layout);
|
|
1132
1186
|
if (chrome.footnotes && chrome.footnotes.length > 0) {
|
|
1133
1187
|
const fontSize = layout.theme.fonts.sizes.small;
|
|
1134
1188
|
const pad = layout.theme.spacing.padding;
|
|
1135
|
-
const lineHeight = fontSize *
|
|
1189
|
+
const lineHeight = fontSize * FOOTNOTE_LINE_HEIGHT;
|
|
1136
1190
|
const style = {
|
|
1137
1191
|
fontFamily: layout.theme.fonts.family,
|
|
1138
1192
|
fontSize,
|
|
1139
1193
|
fontWeight: layout.theme.fonts.weights.normal,
|
|
1140
1194
|
fill: layout.theme.chrome.source.color,
|
|
1141
|
-
lineHeight:
|
|
1195
|
+
lineHeight: FOOTNOTE_LINE_HEIGHT,
|
|
1142
1196
|
textAnchor: "start"
|
|
1143
1197
|
};
|
|
1144
1198
|
for (let i = 0; i < chrome.footnotes.length; i++) {
|
|
1145
1199
|
const f = chrome.footnotes[i];
|
|
1146
|
-
const y = bottomOffset + layout.theme.spacing.chartToFooter +
|
|
1200
|
+
const y = bottomOffset + layout.theme.spacing.chartToFooter + textAscent2(fontSize) + i * lineHeight;
|
|
1147
1201
|
const el = createSVGElement("text");
|
|
1148
1202
|
el.setAttribute("class", "oc-footnotes");
|
|
1149
1203
|
setAttrs(el, { x: pad, y });
|
|
@@ -1151,12 +1205,11 @@ function renderChrome(parent, layout) {
|
|
|
1151
1205
|
el.textContent = `${f.index}. ${f.text}`;
|
|
1152
1206
|
g.appendChild(el);
|
|
1153
1207
|
}
|
|
1154
|
-
footnoteBandHeight = chrome.footnotes.length * lineHeight + 4;
|
|
1155
1208
|
}
|
|
1156
1209
|
if (chrome.source) {
|
|
1157
1210
|
renderChromeElement(
|
|
1158
1211
|
g,
|
|
1159
|
-
{ ...chrome.source, y: bottomOffset + chrome.source.y +
|
|
1212
|
+
{ ...chrome.source, y: bottomOffset + chrome.source.y + bandHeight },
|
|
1160
1213
|
"oc-source",
|
|
1161
1214
|
"source",
|
|
1162
1215
|
measureText
|
|
@@ -1165,7 +1218,7 @@ function renderChrome(parent, layout) {
|
|
|
1165
1218
|
if (chrome.byline) {
|
|
1166
1219
|
renderChromeElement(
|
|
1167
1220
|
g,
|
|
1168
|
-
{ ...chrome.byline, y: bottomOffset + chrome.byline.y +
|
|
1221
|
+
{ ...chrome.byline, y: bottomOffset + chrome.byline.y + bandHeight },
|
|
1169
1222
|
"oc-byline",
|
|
1170
1223
|
"byline",
|
|
1171
1224
|
measureText
|
|
@@ -1174,14 +1227,14 @@ function renderChrome(parent, layout) {
|
|
|
1174
1227
|
if (chrome.footer) {
|
|
1175
1228
|
renderChromeElement(
|
|
1176
1229
|
g,
|
|
1177
|
-
{ ...chrome.footer, y: bottomOffset + chrome.footer.y +
|
|
1230
|
+
{ ...chrome.footer, y: bottomOffset + chrome.footer.y + bandHeight },
|
|
1178
1231
|
"oc-footer",
|
|
1179
1232
|
"footer",
|
|
1180
1233
|
measureText
|
|
1181
1234
|
);
|
|
1182
1235
|
}
|
|
1183
1236
|
if (chrome.brand) {
|
|
1184
|
-
const brandY = bottomOffset + chrome.brand.y +
|
|
1237
|
+
const brandY = bottomOffset + chrome.brand.y + bandHeight;
|
|
1185
1238
|
renderChromeElement(g, { ...chrome.brand, y: brandY }, "oc-brand", "brand", measureText);
|
|
1186
1239
|
const textWidth = estimateTextWidth2(
|
|
1187
1240
|
chrome.brand.text,
|
|
@@ -1198,6 +1251,55 @@ function renderChrome(parent, layout) {
|
|
|
1198
1251
|
parent.appendChild(g);
|
|
1199
1252
|
}
|
|
1200
1253
|
|
|
1254
|
+
// src/renderers/brand.ts
|
|
1255
|
+
var BRAND_URL = "https://tryopendata.ai";
|
|
1256
|
+
function renderBrand(parent, layout) {
|
|
1257
|
+
if (layout.dimensions.width < BRAND_MIN_WIDTH) return;
|
|
1258
|
+
const { width } = layout.dimensions;
|
|
1259
|
+
const padding = layout.theme.spacing.padding;
|
|
1260
|
+
const rightEdge = width - padding;
|
|
1261
|
+
const fill = layout.theme.colors.axis;
|
|
1262
|
+
const { chrome } = layout;
|
|
1263
|
+
const bottomOffset = chrome.bottomAnchorY ?? layout.area.y + layout.area.height;
|
|
1264
|
+
const firstBottom = chrome.source ?? chrome.byline ?? chrome.footer;
|
|
1265
|
+
const { legend } = layout;
|
|
1266
|
+
const bottomLegendOffset = legend.position === "bottom" && legend.bounds.height > 0 ? legend.bounds.height + 8 : 0;
|
|
1267
|
+
const bandHeight = footnoteBandHeight(layout);
|
|
1268
|
+
const chromeY = firstBottom ? bottomOffset + firstBottom.y + bandHeight : bottomOffset + layout.theme.spacing.chartToFooter + bottomLegendOffset + bandHeight;
|
|
1269
|
+
const a = createSVGElement("a");
|
|
1270
|
+
a.setAttribute("href", BRAND_URL);
|
|
1271
|
+
a.setAttributeNS(XLINK_NS, "xlink:href", BRAND_URL);
|
|
1272
|
+
a.setAttribute("target", "_blank");
|
|
1273
|
+
a.setAttribute("rel", "noopener");
|
|
1274
|
+
a.setAttribute("class", "oc-chrome-ref");
|
|
1275
|
+
const BRAND_LARGE = 16;
|
|
1276
|
+
const text = createSVGElement("text");
|
|
1277
|
+
setAttrs(text, {
|
|
1278
|
+
x: rightEdge,
|
|
1279
|
+
y: chromeY + textAscent3(BRAND_LARGE),
|
|
1280
|
+
"font-family": layout.theme.fonts.family,
|
|
1281
|
+
"font-size": BRAND_FONT_SIZE,
|
|
1282
|
+
"text-anchor": "end",
|
|
1283
|
+
"fill-opacity": 0.55
|
|
1284
|
+
});
|
|
1285
|
+
text.style.setProperty("fill", fill);
|
|
1286
|
+
const trySpan = createSVGElement("tspan");
|
|
1287
|
+
trySpan.setAttribute("font-weight", "500");
|
|
1288
|
+
trySpan.textContent = "try";
|
|
1289
|
+
text.appendChild(trySpan);
|
|
1290
|
+
const openDataSpan = createSVGElement("tspan");
|
|
1291
|
+
openDataSpan.setAttribute("font-weight", "600");
|
|
1292
|
+
openDataSpan.setAttribute("font-size", String(BRAND_LARGE));
|
|
1293
|
+
openDataSpan.textContent = "OpenData";
|
|
1294
|
+
text.appendChild(openDataSpan);
|
|
1295
|
+
const aiSpan = createSVGElement("tspan");
|
|
1296
|
+
aiSpan.setAttribute("font-weight", "500");
|
|
1297
|
+
aiSpan.textContent = ".ai";
|
|
1298
|
+
text.appendChild(aiSpan);
|
|
1299
|
+
a.appendChild(text);
|
|
1300
|
+
parent.appendChild(a);
|
|
1301
|
+
}
|
|
1302
|
+
|
|
1201
1303
|
// src/renderers/endpoint-labels.ts
|
|
1202
1304
|
var LEADER_STROKE_WIDTH = 1;
|
|
1203
1305
|
var LEADER_OPACITY = 0.45;
|
|
@@ -1306,7 +1408,125 @@ import { estimateTextWidth as estimateTextWidth3 } from "@opendata-ai/openchart-
|
|
|
1306
1408
|
function isCategorical(legend) {
|
|
1307
1409
|
return !legend.type || legend.type === "categorical";
|
|
1308
1410
|
}
|
|
1411
|
+
function renderContinuousLegend(parent, legend) {
|
|
1412
|
+
if (legend.bounds.width <= 0 || legend.bounds.height <= 0) return;
|
|
1413
|
+
const g = createSVGElement("g");
|
|
1414
|
+
g.setAttribute("class", "oc-legend oc-legend--continuous");
|
|
1415
|
+
g.setAttribute("role", "img");
|
|
1416
|
+
const first = legend.ticks[0];
|
|
1417
|
+
const last = legend.ticks[legend.ticks.length - 1];
|
|
1418
|
+
if (first && last) {
|
|
1419
|
+
g.setAttribute("aria-label", `Color scale from ${first.label} to ${last.label}`);
|
|
1420
|
+
} else {
|
|
1421
|
+
g.setAttribute("aria-label", "Color scale");
|
|
1422
|
+
}
|
|
1423
|
+
if (legend.mode === "gradient") {
|
|
1424
|
+
let defs = parent.querySelector("defs");
|
|
1425
|
+
if (!defs) {
|
|
1426
|
+
defs = createSVGElement("defs");
|
|
1427
|
+
parent.insertBefore(defs, parent.firstChild);
|
|
1428
|
+
}
|
|
1429
|
+
const gradientId = nextSvgId("oc-legend-gradient");
|
|
1430
|
+
const grad = createSVGElement("linearGradient");
|
|
1431
|
+
grad.setAttribute("id", gradientId);
|
|
1432
|
+
setAttrs(grad, { x1: "0%", y1: "0%", x2: "100%", y2: "0%" });
|
|
1433
|
+
for (const stop of legend.colorStops) {
|
|
1434
|
+
const s = createSVGElement("stop");
|
|
1435
|
+
const attrs = {
|
|
1436
|
+
offset: `${stop.offset * 100}%`,
|
|
1437
|
+
"stop-color": stop.color
|
|
1438
|
+
};
|
|
1439
|
+
if (stop.opacity !== void 0) attrs["stop-opacity"] = stop.opacity;
|
|
1440
|
+
setAttrs(s, attrs);
|
|
1441
|
+
grad.appendChild(s);
|
|
1442
|
+
}
|
|
1443
|
+
defs.appendChild(grad);
|
|
1444
|
+
const bar = createSVGElement("rect");
|
|
1445
|
+
bar.setAttribute("class", "oc-legend-gradient-bar");
|
|
1446
|
+
setAttrs(bar, {
|
|
1447
|
+
x: legend.bar.x,
|
|
1448
|
+
y: legend.bar.y,
|
|
1449
|
+
width: legend.bar.width,
|
|
1450
|
+
height: legend.bar.height,
|
|
1451
|
+
rx: 2,
|
|
1452
|
+
ry: 2,
|
|
1453
|
+
fill: `url(#${gradientId})`
|
|
1454
|
+
});
|
|
1455
|
+
g.appendChild(bar);
|
|
1456
|
+
} else {
|
|
1457
|
+
for (const bin of legend.bins) {
|
|
1458
|
+
const rect = createSVGElement("rect");
|
|
1459
|
+
rect.setAttribute("class", "oc-legend-bin");
|
|
1460
|
+
setAttrs(rect, {
|
|
1461
|
+
x: bin.x,
|
|
1462
|
+
y: legend.bar.y,
|
|
1463
|
+
width: bin.width,
|
|
1464
|
+
height: legend.bar.height,
|
|
1465
|
+
fill: bin.color
|
|
1466
|
+
});
|
|
1467
|
+
g.appendChild(rect);
|
|
1468
|
+
}
|
|
1469
|
+
}
|
|
1470
|
+
for (const tick of legend.ticks) {
|
|
1471
|
+
const label = createSVGElement("text");
|
|
1472
|
+
setAttrs(label, { x: tick.x, y: legend.labelY, "text-anchor": tick.anchor });
|
|
1473
|
+
applyTextStyle(label, legend.labelStyle);
|
|
1474
|
+
label.textContent = tick.label;
|
|
1475
|
+
g.appendChild(label);
|
|
1476
|
+
}
|
|
1477
|
+
parent.appendChild(g);
|
|
1478
|
+
}
|
|
1479
|
+
function renderSizeLegend(parent, legend) {
|
|
1480
|
+
if (legend.circles.length === 0) return;
|
|
1481
|
+
const g = createSVGElement("g");
|
|
1482
|
+
g.setAttribute("class", "oc-legend oc-legend--size");
|
|
1483
|
+
g.setAttribute("aria-hidden", "true");
|
|
1484
|
+
const ox = legend.bounds.x;
|
|
1485
|
+
const oy = legend.bounds.y;
|
|
1486
|
+
const labelX = ox + legend.circles[0].cx + legend.circles[0].radius + 8;
|
|
1487
|
+
const leaderX = ox + legend.circles[0].cx + legend.circles[0].radius + 4;
|
|
1488
|
+
for (const circle of legend.circles) {
|
|
1489
|
+
const c = createSVGElement("circle");
|
|
1490
|
+
setAttrs(c, {
|
|
1491
|
+
cx: ox + circle.cx,
|
|
1492
|
+
cy: oy + circle.cy,
|
|
1493
|
+
r: circle.radius,
|
|
1494
|
+
fill: "none",
|
|
1495
|
+
stroke: legend.stroke,
|
|
1496
|
+
"stroke-width": 1
|
|
1497
|
+
});
|
|
1498
|
+
g.appendChild(c);
|
|
1499
|
+
const line = createSVGElement("line");
|
|
1500
|
+
setAttrs(line, {
|
|
1501
|
+
x1: ox + circle.cx,
|
|
1502
|
+
y1: oy + circle.cy - circle.radius,
|
|
1503
|
+
x2: leaderX,
|
|
1504
|
+
y2: oy + circle.cy - circle.radius,
|
|
1505
|
+
stroke: legend.stroke,
|
|
1506
|
+
"stroke-width": 1,
|
|
1507
|
+
"stroke-dasharray": "2 2"
|
|
1508
|
+
});
|
|
1509
|
+
g.appendChild(line);
|
|
1510
|
+
const label = createSVGElement("text");
|
|
1511
|
+
setAttrs(label, {
|
|
1512
|
+
x: labelX,
|
|
1513
|
+
y: oy + circle.labelY
|
|
1514
|
+
});
|
|
1515
|
+
applyTextStyle(label, legend.labelStyle);
|
|
1516
|
+
label.textContent = circle.label;
|
|
1517
|
+
g.appendChild(label);
|
|
1518
|
+
}
|
|
1519
|
+
parent.appendChild(g);
|
|
1520
|
+
}
|
|
1309
1521
|
function renderLegend(parent, legend) {
|
|
1522
|
+
if (legend.type === "continuous") {
|
|
1523
|
+
renderContinuousLegend(parent, legend);
|
|
1524
|
+
return;
|
|
1525
|
+
}
|
|
1526
|
+
if (legend.type === "size") {
|
|
1527
|
+
renderSizeLegend(parent, legend);
|
|
1528
|
+
return;
|
|
1529
|
+
}
|
|
1310
1530
|
if (!isCategorical(legend) || legend.entries.length === 0) return;
|
|
1311
1531
|
const g = createSVGElement("g");
|
|
1312
1532
|
g.setAttribute("class", "oc-legend");
|
|
@@ -1463,10 +1683,6 @@ function renderMetrics(parent, layout) {
|
|
|
1463
1683
|
}
|
|
1464
1684
|
|
|
1465
1685
|
// src/svg-renderer.ts
|
|
1466
|
-
var EASE_VAR_MAP = {
|
|
1467
|
-
smooth: "var(--oc-ease-smooth)",
|
|
1468
|
-
snappy: "var(--oc-ease-snappy)"
|
|
1469
|
-
};
|
|
1470
1686
|
function renderChartSVG(layout, container, opts) {
|
|
1471
1687
|
const { width, height } = layout.dimensions;
|
|
1472
1688
|
const animation = layout.animation;
|
|
@@ -1486,8 +1702,12 @@ function renderChartSVG(layout, container, opts) {
|
|
|
1486
1702
|
"text-rendering": "optimizeLegibility"
|
|
1487
1703
|
});
|
|
1488
1704
|
svg.style.height = `${height}px`;
|
|
1489
|
-
|
|
1490
|
-
|
|
1705
|
+
if (layout.a11y.hidden) {
|
|
1706
|
+
svg.setAttribute("aria-hidden", "true");
|
|
1707
|
+
} else {
|
|
1708
|
+
svg.setAttribute("role", layout.a11y.role);
|
|
1709
|
+
svg.setAttribute("aria-label", layout.a11y.altText);
|
|
1710
|
+
}
|
|
1491
1711
|
if (layout.display === "sparkline") {
|
|
1492
1712
|
svg.setAttribute("data-display", "sparkline");
|
|
1493
1713
|
}
|
|
@@ -1497,11 +1717,6 @@ function renderChartSVG(layout, container, opts) {
|
|
|
1497
1717
|
if (enterPhase) {
|
|
1498
1718
|
const markCount = layout.marks.length;
|
|
1499
1719
|
const stagger = clampStaggerDelay(enterPhase.staggerDelay, markCount);
|
|
1500
|
-
svg.style.setProperty("--oc-animation-duration", `${enterPhase.duration}ms`);
|
|
1501
|
-
svg.style.setProperty("--oc-animation-stagger", `${stagger}ms`);
|
|
1502
|
-
svg.style.setProperty("--oc-annotation-delay", `${animation.annotationDelay}ms`);
|
|
1503
|
-
const easeVar = EASE_VAR_MAP[enterPhase.ease] || EASE_VAR_MAP.smooth;
|
|
1504
|
-
svg.style.setProperty("--oc-animation-ease", easeVar);
|
|
1505
1720
|
let maxSegments = 0;
|
|
1506
1721
|
for (const m of layout.marks) {
|
|
1507
1722
|
if (m.type === "rect") {
|
|
@@ -1511,10 +1726,14 @@ function renderChartSVG(layout, container, opts) {
|
|
|
1511
1726
|
}
|
|
1512
1727
|
}
|
|
1513
1728
|
}
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1729
|
+
const stackSegmentDuration = maxSegments > 0 ? Math.round(enterPhase.duration / maxSegments) : void 0;
|
|
1730
|
+
stampAnimationVars(svg, {
|
|
1731
|
+
duration: enterPhase.duration,
|
|
1732
|
+
stagger,
|
|
1733
|
+
annotationDelay: animation.annotationDelay,
|
|
1734
|
+
ease: enterPhase.ease,
|
|
1735
|
+
stackSegmentDuration
|
|
1736
|
+
});
|
|
1518
1737
|
}
|
|
1519
1738
|
if (layout.display !== "sparkline") {
|
|
1520
1739
|
const bg = createSVGElement("rect");
|
|
@@ -1546,15 +1765,22 @@ function renderChartSVG(layout, container, opts) {
|
|
|
1546
1765
|
clipPath.appendChild(clipRect);
|
|
1547
1766
|
defs.appendChild(clipPath);
|
|
1548
1767
|
const gradientMap = buildGradientDefs(layout.marks, defs);
|
|
1768
|
+
const patternMap = buildPatternDefs(
|
|
1769
|
+
layout.marks,
|
|
1770
|
+
defs
|
|
1771
|
+
);
|
|
1549
1772
|
svg.appendChild(defs);
|
|
1550
|
-
setMarkRenderState({ animation, gradientMap });
|
|
1773
|
+
setMarkRenderState({ animation, gradientMap, patternMap });
|
|
1551
1774
|
try {
|
|
1552
1775
|
if (layout.facet) {
|
|
1553
1776
|
renderFacetedPanels(svg, layout, layout.facet.panels, defs);
|
|
1554
1777
|
} else {
|
|
1555
1778
|
renderAxes(svg, layout);
|
|
1779
|
+
const clipGroup = createSVGElement("g");
|
|
1780
|
+
clipGroup.setAttribute("clip-path", `url(#${clipId})`);
|
|
1556
1781
|
const clippedGroup = createSVGElement("g");
|
|
1557
|
-
clippedGroup.setAttribute("
|
|
1782
|
+
clippedGroup.setAttribute("data-oc-marks-group", "true");
|
|
1783
|
+
clipGroup.appendChild(clippedGroup);
|
|
1558
1784
|
const markLabelsOverlay = renderMarks(clippedGroup, layout);
|
|
1559
1785
|
const hasLineOrAreaWithDataPoints = layout.marks.some(
|
|
1560
1786
|
(m) => (m.type === "line" || m.type === "area") && m.dataPoints && m.dataPoints.length > 0
|
|
@@ -1599,7 +1825,7 @@ function renderChartSVG(layout, container, opts) {
|
|
|
1599
1825
|
dotsGroup.setAttribute("pointer-events", "none");
|
|
1600
1826
|
clippedGroup.appendChild(dotsGroup);
|
|
1601
1827
|
}
|
|
1602
|
-
svg.appendChild(
|
|
1828
|
+
svg.appendChild(clipGroup);
|
|
1603
1829
|
if (markLabelsOverlay) {
|
|
1604
1830
|
svg.appendChild(markLabelsOverlay);
|
|
1605
1831
|
}
|
|
@@ -1622,7 +1848,9 @@ function renderChartSVG(layout, container, opts) {
|
|
|
1622
1848
|
}
|
|
1623
1849
|
}
|
|
1624
1850
|
}
|
|
1625
|
-
|
|
1851
|
+
for (const legend of layout.legends ?? [layout.legend]) {
|
|
1852
|
+
renderLegend(svg, legend);
|
|
1853
|
+
}
|
|
1626
1854
|
renderChrome(svg, layout);
|
|
1627
1855
|
renderMetrics(svg, layout);
|
|
1628
1856
|
if (layout.watermark && !layout.chrome.brand) {
|
|
@@ -1642,9 +1870,9 @@ function renderFacetedPanels(svg, layout, panels, defs) {
|
|
|
1642
1870
|
const bg = createSVGElement("rect");
|
|
1643
1871
|
setAttrs(bg, {
|
|
1644
1872
|
x: panel.area.x,
|
|
1645
|
-
y: panel.
|
|
1873
|
+
y: panel.area.y,
|
|
1646
1874
|
width: panel.area.width,
|
|
1647
|
-
height: panel.area.height
|
|
1875
|
+
height: panel.area.height,
|
|
1648
1876
|
rx: 3,
|
|
1649
1877
|
fill: layout.theme.isDark ? "rgba(255,255,255,0.03)" : "rgba(0,0,0,0.02)"
|
|
1650
1878
|
});
|
|
@@ -1693,355 +1921,20 @@ function renderFacetedPanels(svg, layout, panels, defs) {
|
|
|
1693
1921
|
}
|
|
1694
1922
|
}
|
|
1695
1923
|
|
|
1696
|
-
// src/tilemap-renderer.ts
|
|
1697
|
-
import { textAscent as textAscent4 } from "@opendata-ai/openchart-core";
|
|
1698
|
-
var SVG_NS3 = "http://www.w3.org/2000/svg";
|
|
1699
|
-
var XLINK_NS2 = "http://www.w3.org/1999/xlink";
|
|
1700
|
-
var BRAND_URL2 = "https://tryopendata.ai";
|
|
1701
|
-
var EASE_VAR_MAP2 = {
|
|
1702
|
-
smooth: "var(--oc-ease-smooth)",
|
|
1703
|
-
snappy: "var(--oc-ease-snappy)"
|
|
1704
|
-
};
|
|
1705
|
-
function createSVGElement2(tag) {
|
|
1706
|
-
return document.createElementNS(SVG_NS3, tag);
|
|
1707
|
-
}
|
|
1708
|
-
function setAttrs2(el, attrs) {
|
|
1709
|
-
for (const [key, value] of Object.entries(attrs)) {
|
|
1710
|
-
el.setAttribute(key, String(value));
|
|
1711
|
-
}
|
|
1712
|
-
}
|
|
1713
|
-
function renderChrome2(parent, layout) {
|
|
1714
|
-
const g = createSVGElement2("g");
|
|
1715
|
-
g.setAttribute("class", "oc-chrome");
|
|
1716
|
-
const { chrome } = layout;
|
|
1717
|
-
const bottomOffset = layout.area.y + layout.area.height;
|
|
1718
|
-
if (chrome.title) {
|
|
1719
|
-
const text = createSVGElement2("text");
|
|
1720
|
-
setAttrs2(text, {
|
|
1721
|
-
x: chrome.title.x,
|
|
1722
|
-
y: chrome.title.y + textAscent4(chrome.title.style.fontSize)
|
|
1723
|
-
});
|
|
1724
|
-
text.setAttribute("class", "oc-title");
|
|
1725
|
-
text.setAttribute("font-family", chrome.title.style.fontFamily);
|
|
1726
|
-
text.setAttribute("font-size", String(chrome.title.style.fontSize));
|
|
1727
|
-
text.setAttribute("font-weight", String(chrome.title.style.fontWeight));
|
|
1728
|
-
text.style.setProperty("fill", chrome.title.style.fill);
|
|
1729
|
-
text.textContent = chrome.title.text;
|
|
1730
|
-
g.appendChild(text);
|
|
1731
|
-
}
|
|
1732
|
-
if (chrome.subtitle) {
|
|
1733
|
-
const text = createSVGElement2("text");
|
|
1734
|
-
setAttrs2(text, {
|
|
1735
|
-
x: chrome.subtitle.x,
|
|
1736
|
-
y: chrome.subtitle.y + textAscent4(chrome.subtitle.style.fontSize)
|
|
1737
|
-
});
|
|
1738
|
-
text.setAttribute("class", "oc-subtitle");
|
|
1739
|
-
text.setAttribute("font-family", chrome.subtitle.style.fontFamily);
|
|
1740
|
-
text.setAttribute("font-size", String(chrome.subtitle.style.fontSize));
|
|
1741
|
-
text.setAttribute("font-weight", String(chrome.subtitle.style.fontWeight));
|
|
1742
|
-
text.style.setProperty(
|
|
1743
|
-
"fill",
|
|
1744
|
-
chrome.subtitle.style.fill
|
|
1745
|
-
);
|
|
1746
|
-
text.textContent = chrome.subtitle.text;
|
|
1747
|
-
g.appendChild(text);
|
|
1748
|
-
}
|
|
1749
|
-
if (chrome.source) {
|
|
1750
|
-
const text = createSVGElement2("text");
|
|
1751
|
-
setAttrs2(text, {
|
|
1752
|
-
x: chrome.source.x,
|
|
1753
|
-
y: bottomOffset + chrome.source.y + textAscent4(chrome.source.style.fontSize)
|
|
1754
|
-
});
|
|
1755
|
-
text.setAttribute("class", "oc-source");
|
|
1756
|
-
text.setAttribute("font-family", chrome.source.style.fontFamily);
|
|
1757
|
-
text.setAttribute("font-size", String(chrome.source.style.fontSize));
|
|
1758
|
-
text.setAttribute("font-weight", String(chrome.source.style.fontWeight));
|
|
1759
|
-
text.style.setProperty(
|
|
1760
|
-
"fill",
|
|
1761
|
-
chrome.source.style.fill
|
|
1762
|
-
);
|
|
1763
|
-
text.textContent = chrome.source.text;
|
|
1764
|
-
g.appendChild(text);
|
|
1765
|
-
}
|
|
1766
|
-
if (chrome.byline) {
|
|
1767
|
-
const text = createSVGElement2("text");
|
|
1768
|
-
setAttrs2(text, {
|
|
1769
|
-
x: chrome.byline.x,
|
|
1770
|
-
y: bottomOffset + chrome.byline.y + textAscent4(chrome.byline.style.fontSize)
|
|
1771
|
-
});
|
|
1772
|
-
text.setAttribute("class", "oc-byline");
|
|
1773
|
-
text.setAttribute("font-family", chrome.byline.style.fontFamily);
|
|
1774
|
-
text.setAttribute("font-size", String(chrome.byline.style.fontSize));
|
|
1775
|
-
text.setAttribute("font-weight", String(chrome.byline.style.fontWeight));
|
|
1776
|
-
text.style.setProperty(
|
|
1777
|
-
"fill",
|
|
1778
|
-
chrome.byline.style.fill
|
|
1779
|
-
);
|
|
1780
|
-
text.textContent = chrome.byline.text;
|
|
1781
|
-
g.appendChild(text);
|
|
1782
|
-
}
|
|
1783
|
-
if (chrome.footer) {
|
|
1784
|
-
const text = createSVGElement2("text");
|
|
1785
|
-
setAttrs2(text, {
|
|
1786
|
-
x: chrome.footer.x,
|
|
1787
|
-
y: bottomOffset + chrome.footer.y + textAscent4(chrome.footer.style.fontSize)
|
|
1788
|
-
});
|
|
1789
|
-
text.setAttribute("class", "oc-footer");
|
|
1790
|
-
text.setAttribute("font-family", chrome.footer.style.fontFamily);
|
|
1791
|
-
text.setAttribute("font-size", String(chrome.footer.style.fontSize));
|
|
1792
|
-
text.setAttribute("font-weight", String(chrome.footer.style.fontWeight));
|
|
1793
|
-
text.style.setProperty(
|
|
1794
|
-
"fill",
|
|
1795
|
-
chrome.footer.style.fill
|
|
1796
|
-
);
|
|
1797
|
-
text.textContent = chrome.footer.text;
|
|
1798
|
-
g.appendChild(text);
|
|
1799
|
-
}
|
|
1800
|
-
parent.appendChild(g);
|
|
1801
|
-
}
|
|
1802
|
-
function renderWatermark(parent, layout) {
|
|
1803
|
-
if (layout.width < 480) return;
|
|
1804
|
-
const { width, height } = layout;
|
|
1805
|
-
const { theme } = layout;
|
|
1806
|
-
const padding = theme.spacing.padding;
|
|
1807
|
-
const rightEdge = width - padding;
|
|
1808
|
-
const bottomEdge = height - padding;
|
|
1809
|
-
const fill = theme.colors.axis;
|
|
1810
|
-
const a = createSVGElement2("a");
|
|
1811
|
-
a.setAttribute("href", BRAND_URL2);
|
|
1812
|
-
a.setAttributeNS(XLINK_NS2, "xlink:href", BRAND_URL2);
|
|
1813
|
-
a.setAttribute("target", "_blank");
|
|
1814
|
-
a.setAttribute("rel", "noopener");
|
|
1815
|
-
a.setAttribute("class", "oc-chrome-ref");
|
|
1816
|
-
const text = createSVGElement2("text");
|
|
1817
|
-
setAttrs2(text, {
|
|
1818
|
-
x: rightEdge,
|
|
1819
|
-
y: bottomEdge,
|
|
1820
|
-
"dominant-baseline": "alphabetic",
|
|
1821
|
-
"text-anchor": "end",
|
|
1822
|
-
"font-family": theme.fonts.family,
|
|
1823
|
-
"font-size": 12,
|
|
1824
|
-
"fill-opacity": 0.55
|
|
1825
|
-
});
|
|
1826
|
-
text.style.setProperty("fill", fill);
|
|
1827
|
-
const trySpan = createSVGElement2("tspan");
|
|
1828
|
-
setAttrs2(trySpan, { "font-weight": 500 });
|
|
1829
|
-
trySpan.textContent = "try";
|
|
1830
|
-
const openDataSpan = createSVGElement2("tspan");
|
|
1831
|
-
setAttrs2(openDataSpan, { "font-weight": 600, "font-size": 16 });
|
|
1832
|
-
openDataSpan.textContent = "OpenData";
|
|
1833
|
-
const aiSpan = createSVGElement2("tspan");
|
|
1834
|
-
setAttrs2(aiSpan, { "font-weight": 500 });
|
|
1835
|
-
aiSpan.textContent = ".ai";
|
|
1836
|
-
text.appendChild(trySpan);
|
|
1837
|
-
text.appendChild(openDataSpan);
|
|
1838
|
-
text.appendChild(aiSpan);
|
|
1839
|
-
a.appendChild(text);
|
|
1840
|
-
parent.appendChild(a);
|
|
1841
|
-
}
|
|
1842
|
-
function renderTiles(parent, tiles, animation) {
|
|
1843
|
-
const g = createSVGElement2("g");
|
|
1844
|
-
g.setAttribute("class", "oc-tilemap-tiles");
|
|
1845
|
-
g.setAttribute("role", "list");
|
|
1846
|
-
const tileDelays = [];
|
|
1847
|
-
if (animation?.enter) {
|
|
1848
|
-
const baseStagger = 800 / Math.max(tiles.length, 1);
|
|
1849
|
-
let seed = 17;
|
|
1850
|
-
for (let i = 0; i < tiles.length; i++) {
|
|
1851
|
-
const idx = tiles[i].animationIndex ?? i;
|
|
1852
|
-
seed = seed * 1103515245 + 12345 & 2147483647;
|
|
1853
|
-
const jitter = (seed % 1e3 / 1e3 - 0.5) * 0.8;
|
|
1854
|
-
tileDelays.push(Math.max(0, Math.round(idx * baseStagger * (1 + jitter))));
|
|
1855
|
-
}
|
|
1856
|
-
}
|
|
1857
|
-
for (let i = 0; i < tiles.length; i++) {
|
|
1858
|
-
const tile = tiles[i];
|
|
1859
|
-
const tileGroup = createSVGElement2("g");
|
|
1860
|
-
tileGroup.setAttribute("class", "oc-tilemap-tile");
|
|
1861
|
-
tileGroup.setAttribute("data-state", tile.stateCode);
|
|
1862
|
-
tileGroup.setAttribute("role", "listitem");
|
|
1863
|
-
if (tile.aria?.label) {
|
|
1864
|
-
tileGroup.setAttribute("aria-label", tile.aria.label);
|
|
1865
|
-
}
|
|
1866
|
-
if (animation?.enter) {
|
|
1867
|
-
const idx = tile.animationIndex ?? i;
|
|
1868
|
-
tileGroup.setAttribute("data-animation-index", String(idx));
|
|
1869
|
-
tileGroup.style.setProperty(
|
|
1870
|
-
"--oc-mark-index",
|
|
1871
|
-
String(idx)
|
|
1872
|
-
);
|
|
1873
|
-
tileGroup.style.setProperty(
|
|
1874
|
-
"--oc-tile-delay",
|
|
1875
|
-
`${tileDelays[i]}ms`
|
|
1876
|
-
);
|
|
1877
|
-
}
|
|
1878
|
-
const rect = createSVGElement2("rect");
|
|
1879
|
-
setAttrs2(rect, {
|
|
1880
|
-
x: tile.x,
|
|
1881
|
-
y: tile.y,
|
|
1882
|
-
width: tile.size,
|
|
1883
|
-
height: tile.size,
|
|
1884
|
-
rx: tile.cornerRadius,
|
|
1885
|
-
fill: tile.fill,
|
|
1886
|
-
"fill-opacity": tile.fillOpacity ?? 1,
|
|
1887
|
-
stroke: tile.stroke,
|
|
1888
|
-
"stroke-width": tile.strokeWidth
|
|
1889
|
-
});
|
|
1890
|
-
tileGroup.appendChild(rect);
|
|
1891
|
-
const codeLabel = createSVGElement2("text");
|
|
1892
|
-
setAttrs2(codeLabel, {
|
|
1893
|
-
x: tile.label.x,
|
|
1894
|
-
y: tile.label.y,
|
|
1895
|
-
"text-anchor": "middle",
|
|
1896
|
-
"dominant-baseline": "central",
|
|
1897
|
-
"font-family": tile.label.style.fontFamily,
|
|
1898
|
-
"font-size": tile.label.style.fontSize,
|
|
1899
|
-
"font-weight": tile.label.style.fontWeight
|
|
1900
|
-
});
|
|
1901
|
-
codeLabel.style.setProperty(
|
|
1902
|
-
"fill",
|
|
1903
|
-
tile.label.style.fill
|
|
1904
|
-
);
|
|
1905
|
-
codeLabel.textContent = tile.label.text;
|
|
1906
|
-
tileGroup.appendChild(codeLabel);
|
|
1907
|
-
if (tile.valueLabel.visible && tile.valueLabel.text) {
|
|
1908
|
-
const valueLabel = createSVGElement2("text");
|
|
1909
|
-
setAttrs2(valueLabel, {
|
|
1910
|
-
x: tile.valueLabel.x,
|
|
1911
|
-
y: tile.valueLabel.y,
|
|
1912
|
-
"text-anchor": "middle",
|
|
1913
|
-
"dominant-baseline": "central",
|
|
1914
|
-
"font-family": tile.valueLabel.style.fontFamily,
|
|
1915
|
-
"font-size": tile.valueLabel.style.fontSize,
|
|
1916
|
-
"font-weight": tile.valueLabel.style.fontWeight
|
|
1917
|
-
});
|
|
1918
|
-
valueLabel.style.setProperty(
|
|
1919
|
-
"fill",
|
|
1920
|
-
tile.valueLabel.style.fill
|
|
1921
|
-
);
|
|
1922
|
-
valueLabel.textContent = tile.valueLabel.text;
|
|
1923
|
-
tileGroup.appendChild(valueLabel);
|
|
1924
|
-
}
|
|
1925
|
-
g.appendChild(tileGroup);
|
|
1926
|
-
}
|
|
1927
|
-
parent.appendChild(g);
|
|
1928
|
-
}
|
|
1929
|
-
function renderGradientLegend(parent, layout) {
|
|
1930
|
-
if (!layout.gradientLegend) return;
|
|
1931
|
-
const { gradientLegend } = layout;
|
|
1932
|
-
const g = createSVGElement2("g");
|
|
1933
|
-
g.setAttribute("class", "oc-tilemap-legend");
|
|
1934
|
-
const defs = parent.querySelector("defs") || createSVGElement2("defs");
|
|
1935
|
-
const exists = parent.querySelector("defs");
|
|
1936
|
-
if (!exists) {
|
|
1937
|
-
parent.insertBefore(defs, parent.firstChild);
|
|
1938
|
-
}
|
|
1939
|
-
const gradientId = nextSvgId("oc-tilemap-legend-gradient");
|
|
1940
|
-
const grad = createSVGElement2("linearGradient");
|
|
1941
|
-
grad.id = gradientId;
|
|
1942
|
-
grad.setAttribute("x1", "0%");
|
|
1943
|
-
grad.setAttribute("y1", "0%");
|
|
1944
|
-
grad.setAttribute("x2", "100%");
|
|
1945
|
-
grad.setAttribute("y2", "0%");
|
|
1946
|
-
for (const stop of gradientLegend.colorStops) {
|
|
1947
|
-
const s = createSVGElement2("stop");
|
|
1948
|
-
const attrs = {
|
|
1949
|
-
offset: `${stop.offset * 100}%`,
|
|
1950
|
-
"stop-color": stop.color
|
|
1951
|
-
};
|
|
1952
|
-
if (stop.opacity !== void 0) {
|
|
1953
|
-
attrs["stop-opacity"] = stop.opacity;
|
|
1954
|
-
}
|
|
1955
|
-
setAttrs2(s, attrs);
|
|
1956
|
-
grad.appendChild(s);
|
|
1957
|
-
}
|
|
1958
|
-
defs.appendChild(grad);
|
|
1959
|
-
const barHeight = gradientLegend.bounds.height;
|
|
1960
|
-
const bar = createSVGElement2("rect");
|
|
1961
|
-
setAttrs2(bar, {
|
|
1962
|
-
x: gradientLegend.bounds.x,
|
|
1963
|
-
y: gradientLegend.bounds.y,
|
|
1964
|
-
width: gradientLegend.bounds.width,
|
|
1965
|
-
height: barHeight,
|
|
1966
|
-
rx: barHeight / 2,
|
|
1967
|
-
fill: `url(#${gradientId})`
|
|
1968
|
-
});
|
|
1969
|
-
g.appendChild(bar);
|
|
1970
|
-
const minText = createSVGElement2("text");
|
|
1971
|
-
setAttrs2(minText, {
|
|
1972
|
-
x: gradientLegend.bounds.x,
|
|
1973
|
-
y: gradientLegend.bounds.y + gradientLegend.bounds.height + 14,
|
|
1974
|
-
"text-anchor": "start",
|
|
1975
|
-
"font-family": gradientLegend.labelStyle.fontFamily,
|
|
1976
|
-
"font-size": gradientLegend.labelStyle.fontSize,
|
|
1977
|
-
"font-weight": gradientLegend.labelStyle.fontWeight
|
|
1978
|
-
});
|
|
1979
|
-
minText.style.setProperty(
|
|
1980
|
-
"fill",
|
|
1981
|
-
gradientLegend.labelStyle.fill
|
|
1982
|
-
);
|
|
1983
|
-
minText.textContent = gradientLegend.minLabel;
|
|
1984
|
-
g.appendChild(minText);
|
|
1985
|
-
const maxText = createSVGElement2("text");
|
|
1986
|
-
setAttrs2(maxText, {
|
|
1987
|
-
x: gradientLegend.bounds.x + gradientLegend.bounds.width,
|
|
1988
|
-
y: gradientLegend.bounds.y + gradientLegend.bounds.height + 14,
|
|
1989
|
-
"text-anchor": "end",
|
|
1990
|
-
"font-family": gradientLegend.labelStyle.fontFamily,
|
|
1991
|
-
"font-size": gradientLegend.labelStyle.fontSize,
|
|
1992
|
-
"font-weight": gradientLegend.labelStyle.fontWeight
|
|
1993
|
-
});
|
|
1994
|
-
maxText.style.setProperty(
|
|
1995
|
-
"fill",
|
|
1996
|
-
gradientLegend.labelStyle.fill
|
|
1997
|
-
);
|
|
1998
|
-
maxText.textContent = gradientLegend.maxLabel;
|
|
1999
|
-
g.appendChild(maxText);
|
|
2000
|
-
parent.appendChild(g);
|
|
2001
|
-
}
|
|
2002
|
-
function renderTileMapSVG(layout, opts) {
|
|
2003
|
-
const { width, height, tiles, a11y, watermark, animation } = layout;
|
|
2004
|
-
const animate = opts?.animate && !!animation?.enter;
|
|
2005
|
-
const svg = createSVGElement2("svg");
|
|
2006
|
-
svg.setAttribute("viewBox", `0 0 ${width} ${height}`);
|
|
2007
|
-
svg.setAttribute("role", "img");
|
|
2008
|
-
if (a11y.altText) {
|
|
2009
|
-
svg.setAttribute("aria-label", a11y.altText);
|
|
2010
|
-
}
|
|
2011
|
-
const classes = animate ? "oc-tilemap oc-animate" : "oc-tilemap";
|
|
2012
|
-
svg.setAttribute("class", classes);
|
|
2013
|
-
if (animate && animation?.enter) {
|
|
2014
|
-
const stagger = Math.max(5, Math.round(800 / Math.max(tiles.length, 1)));
|
|
2015
|
-
svg.style.setProperty("--oc-animation-duration", `${animation.enter.duration}ms`);
|
|
2016
|
-
svg.style.setProperty("--oc-animation-stagger", `${stagger}ms`);
|
|
2017
|
-
svg.style.setProperty("--oc-annotation-delay", `${animation.annotationDelay}ms`);
|
|
2018
|
-
const easeVar = EASE_VAR_MAP2[animation.enter.ease] || EASE_VAR_MAP2.smooth;
|
|
2019
|
-
svg.style.setProperty("--oc-animation-ease", easeVar);
|
|
2020
|
-
}
|
|
2021
|
-
const defs = createSVGElement2("defs");
|
|
2022
|
-
svg.appendChild(defs);
|
|
2023
|
-
renderChrome2(svg, layout);
|
|
2024
|
-
renderTiles(svg, tiles, animate ? animation : void 0);
|
|
2025
|
-
if (layout.categoricalLegend) {
|
|
2026
|
-
renderLegend(svg, layout.categoricalLegend);
|
|
2027
|
-
} else {
|
|
2028
|
-
renderGradientLegend(svg, layout);
|
|
2029
|
-
}
|
|
2030
|
-
if (watermark) {
|
|
2031
|
-
renderWatermark(svg, layout);
|
|
2032
|
-
}
|
|
2033
|
-
return svg;
|
|
2034
|
-
}
|
|
2035
|
-
|
|
2036
1924
|
export {
|
|
1925
|
+
stampAnimationVars,
|
|
1926
|
+
SVG_NS,
|
|
1927
|
+
createSVGElement,
|
|
1928
|
+
setAttrs,
|
|
1929
|
+
renderChromeElement,
|
|
1930
|
+
nextSvgId,
|
|
2037
1931
|
resetSvgIdCounter,
|
|
1932
|
+
renderLegend,
|
|
2038
1933
|
buildGradientDefs,
|
|
2039
1934
|
resolveMarkFill,
|
|
2040
|
-
SVG_NS,
|
|
2041
1935
|
registerMarkRenderer,
|
|
2042
1936
|
rectPathWithCorners,
|
|
2043
1937
|
renderSingleMark,
|
|
2044
|
-
renderChartSVG
|
|
2045
|
-
renderTileMapSVG
|
|
1938
|
+
renderChartSVG
|
|
2046
1939
|
};
|
|
2047
|
-
//# sourceMappingURL=chunk-
|
|
1940
|
+
//# sourceMappingURL=chunk-5EOV5JQZ.js.map
|