@opendata-ai/openchart-vanilla 6.20.0 → 6.22.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/index.d.ts +18 -7
- package/dist/index.js +462 -439
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
- package/src/renderers/annotations.ts +212 -0
- package/src/renderers/axes.ts +164 -0
- package/src/renderers/brand.ts +75 -0
- package/src/renderers/chrome.ts +96 -0
- package/src/renderers/legend.ts +131 -0
- package/src/renderers/marks.ts +427 -0
- package/src/renderers/svg-dom.ts +66 -0
- package/src/svg-renderer.ts +61 -1190
package/dist/index.js
CHANGED
|
@@ -3314,12 +3314,6 @@ function setupTableAnimationCleanup(wrapper) {
|
|
|
3314
3314
|
}
|
|
3315
3315
|
|
|
3316
3316
|
// src/svg-renderer.ts
|
|
3317
|
-
import {
|
|
3318
|
-
BRAND_FONT_SIZE as BRAND_FONT_SIZE2,
|
|
3319
|
-
BRAND_MIN_WIDTH as BRAND_MIN_WIDTH2,
|
|
3320
|
-
estimateTextWidth,
|
|
3321
|
-
wrapText
|
|
3322
|
-
} from "@opendata-ai/openchart-core";
|
|
3323
3317
|
import { clampStaggerDelay } from "@opendata-ai/openchart-engine";
|
|
3324
3318
|
|
|
3325
3319
|
// src/gradient-utils.ts
|
|
@@ -3411,37 +3405,10 @@ function resolveMarkFill(fill, gradientMap) {
|
|
|
3411
3405
|
return id ? `url(#${id})` : "#000000";
|
|
3412
3406
|
}
|
|
3413
3407
|
|
|
3414
|
-
// src/svg-
|
|
3408
|
+
// src/renderers/svg-dom.ts
|
|
3409
|
+
import { estimateTextWidth } from "@opendata-ai/openchart-core";
|
|
3415
3410
|
var SVG_NS2 = "http://www.w3.org/2000/svg";
|
|
3416
|
-
var
|
|
3417
|
-
var currentGradientMap = /* @__PURE__ */ new Map();
|
|
3418
|
-
function stampAnimationAttrs(el, mark, fallbackIndex) {
|
|
3419
|
-
if (!currentAnimation?.enabled) return;
|
|
3420
|
-
const idx = mark.animationIndex ?? fallbackIndex;
|
|
3421
|
-
el.setAttribute("data-animation-index", String(idx));
|
|
3422
|
-
el.style.setProperty("--oc-mark-index", String(idx));
|
|
3423
|
-
}
|
|
3424
|
-
var EASE_VAR_MAP = {
|
|
3425
|
-
smooth: "var(--oc-ease-smooth)",
|
|
3426
|
-
snappy: "var(--oc-ease-snappy)"
|
|
3427
|
-
};
|
|
3428
|
-
function computeXAxisExtent(layout) {
|
|
3429
|
-
const xAxis = layout.axes.x;
|
|
3430
|
-
if (!xAxis) return 0;
|
|
3431
|
-
if (xAxis.tickAngle && Math.abs(xAxis.tickAngle) > 10) {
|
|
3432
|
-
const fontSize = xAxis.tickLabelStyle.fontSize;
|
|
3433
|
-
const fontWeight = xAxis.tickLabelStyle.fontWeight;
|
|
3434
|
-
const angleRad = Math.abs(xAxis.tickAngle) * (Math.PI / 180);
|
|
3435
|
-
let maxLabelWidth = 40;
|
|
3436
|
-
for (const tick of xAxis.ticks) {
|
|
3437
|
-
const w = estimateTextWidth(tick.label, fontSize, fontWeight);
|
|
3438
|
-
if (w > maxLabelWidth) maxLabelWidth = w;
|
|
3439
|
-
}
|
|
3440
|
-
const rotatedHeight = Math.min(maxLabelWidth * Math.sin(angleRad) + 6, 120);
|
|
3441
|
-
return xAxis.label ? rotatedHeight + 20 : rotatedHeight;
|
|
3442
|
-
}
|
|
3443
|
-
return xAxis.label ? 48 : 26;
|
|
3444
|
-
}
|
|
3411
|
+
var XLINK_NS = "http://www.w3.org/1999/xlink";
|
|
3445
3412
|
function createSVGElement(tag) {
|
|
3446
3413
|
return document.createElementNS(SVG_NS2, tag);
|
|
3447
3414
|
}
|
|
@@ -3467,73 +3434,184 @@ function applyTextStyle(el, style) {
|
|
|
3467
3434
|
el.setAttribute("font-variant", style.fontVariant);
|
|
3468
3435
|
}
|
|
3469
3436
|
}
|
|
3470
|
-
function
|
|
3471
|
-
const
|
|
3472
|
-
|
|
3473
|
-
|
|
3474
|
-
|
|
3475
|
-
|
|
3476
|
-
|
|
3477
|
-
|
|
3478
|
-
|
|
3479
|
-
|
|
3480
|
-
|
|
3481
|
-
measureText
|
|
3482
|
-
);
|
|
3483
|
-
if (lines.length === 1) {
|
|
3484
|
-
text.textContent = element.text;
|
|
3485
|
-
} else {
|
|
3486
|
-
const lineHeight = element.style.fontSize * (element.style.lineHeight ?? 1.3);
|
|
3487
|
-
for (let i = 0; i < lines.length; i++) {
|
|
3488
|
-
const tspan = createSVGElement("tspan");
|
|
3489
|
-
setAttrs(tspan, { x: element.x, dy: i === 0 ? 0 : lineHeight });
|
|
3490
|
-
tspan.textContent = lines[i];
|
|
3491
|
-
text.appendChild(tspan);
|
|
3437
|
+
function computeXAxisExtent(layout) {
|
|
3438
|
+
const xAxis = layout.axes.x;
|
|
3439
|
+
if (!xAxis) return 0;
|
|
3440
|
+
if (xAxis.tickAngle && Math.abs(xAxis.tickAngle) > 10) {
|
|
3441
|
+
const fontSize = xAxis.tickLabelStyle.fontSize;
|
|
3442
|
+
const fontWeight = xAxis.tickLabelStyle.fontWeight;
|
|
3443
|
+
const angleRad = Math.abs(xAxis.tickAngle) * (Math.PI / 180);
|
|
3444
|
+
let maxLabelWidth = 40;
|
|
3445
|
+
for (const tick of xAxis.ticks) {
|
|
3446
|
+
const w = estimateTextWidth(tick.label, fontSize, fontWeight);
|
|
3447
|
+
if (w > maxLabelWidth) maxLabelWidth = w;
|
|
3492
3448
|
}
|
|
3449
|
+
const rotatedHeight = Math.min(maxLabelWidth * Math.sin(angleRad) + 6, 120);
|
|
3450
|
+
return xAxis.label ? rotatedHeight + 20 : rotatedHeight;
|
|
3493
3451
|
}
|
|
3494
|
-
|
|
3452
|
+
return xAxis.label ? 48 : 26;
|
|
3495
3453
|
}
|
|
3496
|
-
|
|
3454
|
+
|
|
3455
|
+
// src/renderers/annotations.ts
|
|
3456
|
+
function renderCurvedArrow(parent, from, to, stroke) {
|
|
3457
|
+
const pad = 6;
|
|
3458
|
+
const tipY = to.y - pad;
|
|
3459
|
+
const dy = tipY - from.y;
|
|
3460
|
+
const dist = Math.sqrt((to.x - from.x) ** 2 + dy ** 2) || 1;
|
|
3461
|
+
const arrowLen = 8;
|
|
3462
|
+
const arrowWidth = 4;
|
|
3463
|
+
const bulge = Math.max(dist * 0.4, 35);
|
|
3464
|
+
const cp1x = from.x + bulge;
|
|
3465
|
+
const cp1y = from.y + dy * 0.35;
|
|
3466
|
+
const cp2x = to.x;
|
|
3467
|
+
const cp2y = tipY - Math.abs(dy) * 0.25;
|
|
3468
|
+
const tx = to.x - cp2x;
|
|
3469
|
+
const ty = tipY - cp2y;
|
|
3470
|
+
const tLen = Math.sqrt(tx * tx + ty * ty) || 1;
|
|
3471
|
+
const ux = tx / tLen;
|
|
3472
|
+
const uy = ty / tLen;
|
|
3473
|
+
const baseX = to.x - ux * arrowLen;
|
|
3474
|
+
const baseY = tipY - uy * arrowLen;
|
|
3475
|
+
const path = createSVGElement("path");
|
|
3476
|
+
path.setAttribute("class", "oc-annotation-connector");
|
|
3477
|
+
setAttrs(path, {
|
|
3478
|
+
d: `M ${from.x} ${from.y} C ${cp1x} ${cp1y}, ${cp2x} ${cp2y}, ${baseX} ${baseY}`,
|
|
3479
|
+
fill: "none",
|
|
3480
|
+
stroke,
|
|
3481
|
+
"stroke-width": 1.5
|
|
3482
|
+
});
|
|
3483
|
+
parent.appendChild(path);
|
|
3484
|
+
const px = -uy;
|
|
3485
|
+
const py = ux;
|
|
3486
|
+
const arrow = createSVGElement("polygon");
|
|
3487
|
+
arrow.setAttribute("class", "oc-annotation-connector");
|
|
3488
|
+
setAttrs(arrow, {
|
|
3489
|
+
points: [
|
|
3490
|
+
`${to.x},${tipY}`,
|
|
3491
|
+
`${baseX + px * arrowWidth},${baseY + py * arrowWidth}`,
|
|
3492
|
+
`${baseX - px * arrowWidth},${baseY - py * arrowWidth}`
|
|
3493
|
+
].join(" "),
|
|
3494
|
+
fill: stroke
|
|
3495
|
+
});
|
|
3496
|
+
parent.appendChild(arrow);
|
|
3497
|
+
}
|
|
3498
|
+
function renderAnnotation(parent, annotation, index2, bgColor) {
|
|
3497
3499
|
const g = createSVGElement("g");
|
|
3498
|
-
g.setAttribute("class",
|
|
3499
|
-
|
|
3500
|
-
if (
|
|
3501
|
-
|
|
3500
|
+
g.setAttribute("class", `oc-annotation oc-annotation-${annotation.type}`);
|
|
3501
|
+
g.setAttribute("data-annotation-index", String(index2));
|
|
3502
|
+
if (annotation.id) {
|
|
3503
|
+
g.setAttribute("data-annotation-id", annotation.id);
|
|
3502
3504
|
}
|
|
3503
|
-
if (
|
|
3504
|
-
|
|
3505
|
+
if (annotation.rect) {
|
|
3506
|
+
const rect = createSVGElement("rect");
|
|
3507
|
+
rect.setAttribute("class", "oc-annotation-range");
|
|
3508
|
+
setAttrs(rect, {
|
|
3509
|
+
x: annotation.rect.x,
|
|
3510
|
+
y: annotation.rect.y,
|
|
3511
|
+
width: annotation.rect.width,
|
|
3512
|
+
height: annotation.rect.height
|
|
3513
|
+
});
|
|
3514
|
+
if (annotation.fill) rect.setAttribute("fill", annotation.fill);
|
|
3515
|
+
if (annotation.opacity !== void 0) {
|
|
3516
|
+
rect.setAttribute("fill-opacity", String(annotation.opacity));
|
|
3517
|
+
}
|
|
3518
|
+
g.appendChild(rect);
|
|
3505
3519
|
}
|
|
3506
|
-
|
|
3507
|
-
|
|
3508
|
-
|
|
3509
|
-
|
|
3510
|
-
|
|
3511
|
-
|
|
3512
|
-
|
|
3513
|
-
|
|
3514
|
-
|
|
3515
|
-
);
|
|
3520
|
+
if (annotation.line) {
|
|
3521
|
+
const line = createSVGElement("line");
|
|
3522
|
+
line.setAttribute("class", "oc-annotation-line");
|
|
3523
|
+
setAttrs(line, {
|
|
3524
|
+
x1: annotation.line.start.x,
|
|
3525
|
+
y1: annotation.line.start.y,
|
|
3526
|
+
x2: annotation.line.end.x,
|
|
3527
|
+
y2: annotation.line.end.y,
|
|
3528
|
+
"stroke-width": annotation.strokeWidth ?? 1
|
|
3529
|
+
});
|
|
3530
|
+
if (annotation.stroke) line.setAttribute("stroke", annotation.stroke);
|
|
3531
|
+
if (annotation.strokeDasharray) {
|
|
3532
|
+
line.setAttribute("stroke-dasharray", annotation.strokeDasharray);
|
|
3533
|
+
}
|
|
3534
|
+
g.appendChild(line);
|
|
3516
3535
|
}
|
|
3517
|
-
if (
|
|
3518
|
-
|
|
3519
|
-
|
|
3520
|
-
|
|
3521
|
-
|
|
3522
|
-
|
|
3523
|
-
|
|
3524
|
-
|
|
3536
|
+
if (annotation.label?.visible) {
|
|
3537
|
+
if (annotation.label.connector) {
|
|
3538
|
+
const c2 = annotation.label.connector;
|
|
3539
|
+
if (c2.style === "curve") {
|
|
3540
|
+
renderCurvedArrow(g, c2.from, c2.to, c2.stroke);
|
|
3541
|
+
} else {
|
|
3542
|
+
const connector = createSVGElement("line");
|
|
3543
|
+
connector.setAttribute("class", "oc-annotation-connector");
|
|
3544
|
+
setAttrs(connector, {
|
|
3545
|
+
x1: c2.from.x,
|
|
3546
|
+
y1: c2.from.y,
|
|
3547
|
+
x2: c2.to.x,
|
|
3548
|
+
y2: c2.to.y,
|
|
3549
|
+
stroke: c2.stroke,
|
|
3550
|
+
"stroke-width": 1,
|
|
3551
|
+
"stroke-opacity": 0.5
|
|
3552
|
+
});
|
|
3553
|
+
g.appendChild(connector);
|
|
3554
|
+
}
|
|
3555
|
+
}
|
|
3556
|
+
const text = createSVGElement("text");
|
|
3557
|
+
text.setAttribute("class", "oc-annotation-label");
|
|
3558
|
+
setAttrs(text, { x: annotation.label.x, y: annotation.label.y });
|
|
3559
|
+
applyTextStyle(text, annotation.label.style);
|
|
3560
|
+
const lines = annotation.label.text.split("\n");
|
|
3561
|
+
const fontSize = annotation.label.style.fontSize ?? 12;
|
|
3562
|
+
const lineHeight = fontSize * (annotation.label.style.lineHeight ?? 1.3);
|
|
3563
|
+
const isMultiLine = lines.length > 1;
|
|
3564
|
+
if (isMultiLine) {
|
|
3565
|
+
text.setAttribute("text-anchor", "middle");
|
|
3566
|
+
for (let i = 0; i < lines.length; i++) {
|
|
3567
|
+
const tspan = createSVGElement("tspan");
|
|
3568
|
+
setAttrs(tspan, { x: annotation.label.x, dy: i === 0 ? 0 : lineHeight });
|
|
3569
|
+
tspan.textContent = lines[i];
|
|
3570
|
+
text.appendChild(tspan);
|
|
3571
|
+
}
|
|
3572
|
+
} else {
|
|
3573
|
+
text.textContent = annotation.label.text;
|
|
3574
|
+
}
|
|
3575
|
+
if (annotation.label.background) {
|
|
3576
|
+
const charWidth = fontSize * 0.55;
|
|
3577
|
+
const maxLineWidth = Math.max(...lines.map((l) => l.length)) * charWidth;
|
|
3578
|
+
const totalHeight = lines.length * lineHeight;
|
|
3579
|
+
const pad = 3;
|
|
3580
|
+
const bgX = isMultiLine ? annotation.label.x - maxLineWidth / 2 - pad : annotation.label.x - pad;
|
|
3581
|
+
const bgRect = createSVGElement("rect");
|
|
3582
|
+
bgRect.setAttribute("class", "oc-annotation-bg");
|
|
3583
|
+
setAttrs(bgRect, {
|
|
3584
|
+
x: bgX,
|
|
3585
|
+
y: annotation.label.y - fontSize + (lineHeight - fontSize) / 2 - pad,
|
|
3586
|
+
width: maxLineWidth + pad * 2,
|
|
3587
|
+
height: totalHeight + pad * 2,
|
|
3588
|
+
fill: annotation.label.background,
|
|
3589
|
+
rx: 2
|
|
3590
|
+
});
|
|
3591
|
+
g.appendChild(bgRect);
|
|
3592
|
+
} else if (bgColor) {
|
|
3593
|
+
text.style.paintOrder = "stroke";
|
|
3594
|
+
text.style.stroke = bgColor;
|
|
3595
|
+
text.style.strokeWidth = `${Math.round(fontSize * 0.3)}px`;
|
|
3596
|
+
text.style.strokeLinejoin = "round";
|
|
3597
|
+
}
|
|
3598
|
+
g.appendChild(text);
|
|
3525
3599
|
}
|
|
3526
|
-
|
|
3527
|
-
|
|
3528
|
-
|
|
3529
|
-
|
|
3530
|
-
|
|
3531
|
-
|
|
3532
|
-
|
|
3533
|
-
|
|
3600
|
+
parent.appendChild(g);
|
|
3601
|
+
}
|
|
3602
|
+
function renderAnnotations(parent, layout) {
|
|
3603
|
+
if (layout.annotations.length === 0) return;
|
|
3604
|
+
const g = createSVGElement("g");
|
|
3605
|
+
g.setAttribute("class", "oc-annotations");
|
|
3606
|
+
const bgColor = layout.theme.colors.background;
|
|
3607
|
+
for (let i = 0; i < layout.annotations.length; i++) {
|
|
3608
|
+
renderAnnotation(g, layout.annotations[i], i, bgColor);
|
|
3534
3609
|
}
|
|
3535
3610
|
parent.appendChild(g);
|
|
3536
3611
|
}
|
|
3612
|
+
|
|
3613
|
+
// src/renderers/axes.ts
|
|
3614
|
+
import { estimateTextWidth as estimateTextWidth2 } from "@opendata-ai/openchart-core";
|
|
3537
3615
|
function renderAxis(parent, axis, orientation, layout) {
|
|
3538
3616
|
const g = createSVGElement("g");
|
|
3539
3617
|
g.setAttribute("class", `oc-axis oc-axis-${orientation}`);
|
|
@@ -3626,7 +3704,7 @@ function renderAxis(parent, axis, orientation, layout) {
|
|
|
3626
3704
|
const angleRad = Math.abs(axis.tickAngle) * (Math.PI / 180);
|
|
3627
3705
|
let maxLabelWidth = 40;
|
|
3628
3706
|
for (const tick of axis.ticks) {
|
|
3629
|
-
const w =
|
|
3707
|
+
const w = estimateTextWidth2(
|
|
3630
3708
|
tick.label,
|
|
3631
3709
|
axis.tickLabelStyle.fontSize,
|
|
3632
3710
|
axis.tickLabelStyle.fontWeight
|
|
@@ -3661,9 +3739,255 @@ function renderAxes(parent, layout) {
|
|
|
3661
3739
|
renderAxis(parent, layout.axes.y, "y", layout);
|
|
3662
3740
|
}
|
|
3663
3741
|
}
|
|
3664
|
-
|
|
3665
|
-
|
|
3666
|
-
|
|
3742
|
+
|
|
3743
|
+
// src/renderers/brand.ts
|
|
3744
|
+
import { BRAND_FONT_SIZE as BRAND_FONT_SIZE2, BRAND_MIN_WIDTH as BRAND_MIN_WIDTH2 } from "@opendata-ai/openchart-core";
|
|
3745
|
+
var BRAND_URL = "https://tryopendata.ai";
|
|
3746
|
+
function renderBrand(parent, layout) {
|
|
3747
|
+
if (layout.dimensions.width < BRAND_MIN_WIDTH2) return;
|
|
3748
|
+
const { width } = layout.dimensions;
|
|
3749
|
+
const padding = layout.theme.spacing.padding;
|
|
3750
|
+
const rightEdge = width - padding;
|
|
3751
|
+
const fill = layout.theme.colors.axis;
|
|
3752
|
+
const { chrome } = layout;
|
|
3753
|
+
const xAxisExtent = computeXAxisExtent(layout);
|
|
3754
|
+
const bottomOffset = layout.area.y + layout.area.height + xAxisExtent;
|
|
3755
|
+
const firstBottom = chrome.source ?? chrome.byline ?? chrome.footer;
|
|
3756
|
+
const chromeY = firstBottom ? bottomOffset + firstBottom.y : bottomOffset + layout.theme.spacing.chartToFooter;
|
|
3757
|
+
const a2 = createSVGElement("a");
|
|
3758
|
+
a2.setAttribute("href", BRAND_URL);
|
|
3759
|
+
a2.setAttributeNS(XLINK_NS, "xlink:href", BRAND_URL);
|
|
3760
|
+
a2.setAttribute("target", "_blank");
|
|
3761
|
+
a2.setAttribute("rel", "noopener");
|
|
3762
|
+
a2.setAttribute("class", "oc-chrome-ref");
|
|
3763
|
+
const BRAND_LARGE = 16;
|
|
3764
|
+
const text = createSVGElement("text");
|
|
3765
|
+
setAttrs(text, {
|
|
3766
|
+
x: rightEdge,
|
|
3767
|
+
y: chromeY + BRAND_LARGE,
|
|
3768
|
+
"dominant-baseline": "alphabetic",
|
|
3769
|
+
"font-family": layout.theme.fonts.family,
|
|
3770
|
+
"font-size": BRAND_FONT_SIZE2,
|
|
3771
|
+
"text-anchor": "end",
|
|
3772
|
+
"fill-opacity": 0.55
|
|
3773
|
+
});
|
|
3774
|
+
text.style.setProperty("fill", fill);
|
|
3775
|
+
const trySpan = createSVGElement("tspan");
|
|
3776
|
+
trySpan.setAttribute("font-weight", "500");
|
|
3777
|
+
trySpan.textContent = "try";
|
|
3778
|
+
text.appendChild(trySpan);
|
|
3779
|
+
const openDataSpan = createSVGElement("tspan");
|
|
3780
|
+
openDataSpan.setAttribute("font-weight", "600");
|
|
3781
|
+
openDataSpan.setAttribute("font-size", String(BRAND_LARGE));
|
|
3782
|
+
openDataSpan.textContent = "OpenData";
|
|
3783
|
+
text.appendChild(openDataSpan);
|
|
3784
|
+
const aiSpan = createSVGElement("tspan");
|
|
3785
|
+
aiSpan.setAttribute("font-weight", "500");
|
|
3786
|
+
aiSpan.textContent = ".ai";
|
|
3787
|
+
text.appendChild(aiSpan);
|
|
3788
|
+
a2.appendChild(text);
|
|
3789
|
+
parent.appendChild(a2);
|
|
3790
|
+
}
|
|
3791
|
+
|
|
3792
|
+
// src/renderers/chrome.ts
|
|
3793
|
+
import { wrapText } from "@opendata-ai/openchart-core";
|
|
3794
|
+
function renderChromeElement(parent, element, className, chromeKey, measureText) {
|
|
3795
|
+
const text = createSVGElement("text");
|
|
3796
|
+
setAttrs(text, { x: element.x, y: element.y });
|
|
3797
|
+
applyTextStyle(text, element.style);
|
|
3798
|
+
text.setAttribute("class", className);
|
|
3799
|
+
text.setAttribute("data-chrome-key", chromeKey);
|
|
3800
|
+
const lines = wrapText(
|
|
3801
|
+
element.text,
|
|
3802
|
+
element.style.fontSize,
|
|
3803
|
+
element.style.fontWeight,
|
|
3804
|
+
element.maxWidth,
|
|
3805
|
+
measureText
|
|
3806
|
+
);
|
|
3807
|
+
if (lines.length === 1) {
|
|
3808
|
+
text.textContent = element.text;
|
|
3809
|
+
} else {
|
|
3810
|
+
const lineHeight = element.style.fontSize * (element.style.lineHeight ?? 1.3);
|
|
3811
|
+
for (let i = 0; i < lines.length; i++) {
|
|
3812
|
+
const tspan = createSVGElement("tspan");
|
|
3813
|
+
setAttrs(tspan, { x: element.x, dy: i === 0 ? 0 : lineHeight });
|
|
3814
|
+
tspan.textContent = lines[i];
|
|
3815
|
+
text.appendChild(tspan);
|
|
3816
|
+
}
|
|
3817
|
+
}
|
|
3818
|
+
parent.appendChild(text);
|
|
3819
|
+
}
|
|
3820
|
+
function renderChrome(parent, layout) {
|
|
3821
|
+
const g = createSVGElement("g");
|
|
3822
|
+
g.setAttribute("class", "oc-chrome");
|
|
3823
|
+
const { chrome, measureText } = layout;
|
|
3824
|
+
if (chrome.title) {
|
|
3825
|
+
renderChromeElement(g, chrome.title, "oc-title", "title", measureText);
|
|
3826
|
+
}
|
|
3827
|
+
if (chrome.subtitle) {
|
|
3828
|
+
renderChromeElement(g, chrome.subtitle, "oc-subtitle", "subtitle", measureText);
|
|
3829
|
+
}
|
|
3830
|
+
const xAxisExtent = computeXAxisExtent(layout);
|
|
3831
|
+
const bottomOffset = layout.area.y + layout.area.height + xAxisExtent;
|
|
3832
|
+
if (chrome.source) {
|
|
3833
|
+
renderChromeElement(
|
|
3834
|
+
g,
|
|
3835
|
+
{ ...chrome.source, y: bottomOffset + chrome.source.y },
|
|
3836
|
+
"oc-source",
|
|
3837
|
+
"source",
|
|
3838
|
+
measureText
|
|
3839
|
+
);
|
|
3840
|
+
}
|
|
3841
|
+
if (chrome.byline) {
|
|
3842
|
+
renderChromeElement(
|
|
3843
|
+
g,
|
|
3844
|
+
{ ...chrome.byline, y: bottomOffset + chrome.byline.y },
|
|
3845
|
+
"oc-byline",
|
|
3846
|
+
"byline",
|
|
3847
|
+
measureText
|
|
3848
|
+
);
|
|
3849
|
+
}
|
|
3850
|
+
if (chrome.footer) {
|
|
3851
|
+
renderChromeElement(
|
|
3852
|
+
g,
|
|
3853
|
+
{ ...chrome.footer, y: bottomOffset + chrome.footer.y },
|
|
3854
|
+
"oc-footer",
|
|
3855
|
+
"footer",
|
|
3856
|
+
measureText
|
|
3857
|
+
);
|
|
3858
|
+
}
|
|
3859
|
+
parent.appendChild(g);
|
|
3860
|
+
}
|
|
3861
|
+
|
|
3862
|
+
// src/renderers/legend.ts
|
|
3863
|
+
import { estimateTextWidth as estimateTextWidth3 } from "@opendata-ai/openchart-core";
|
|
3864
|
+
function renderLegend(parent, legend) {
|
|
3865
|
+
if (legend.entries.length === 0) return;
|
|
3866
|
+
const g = createSVGElement("g");
|
|
3867
|
+
g.setAttribute("class", "oc-legend");
|
|
3868
|
+
g.setAttribute("role", "list");
|
|
3869
|
+
g.setAttribute("aria-label", "Chart legend");
|
|
3870
|
+
const isHorizontal = legend.position === "top" || legend.position === "bottom";
|
|
3871
|
+
let offsetX = legend.bounds.x;
|
|
3872
|
+
let offsetY = legend.bounds.y;
|
|
3873
|
+
for (let i = 0; i < legend.entries.length; i++) {
|
|
3874
|
+
const entry = legend.entries[i];
|
|
3875
|
+
if (isHorizontal && i > 0) {
|
|
3876
|
+
const labelWidth = estimateTextWidth3(
|
|
3877
|
+
entry.label,
|
|
3878
|
+
legend.labelStyle.fontSize,
|
|
3879
|
+
legend.labelStyle.fontWeight
|
|
3880
|
+
);
|
|
3881
|
+
const entryWidth = legend.swatchSize + legend.swatchGap + labelWidth + legend.entryGap;
|
|
3882
|
+
if (offsetX + entryWidth > legend.bounds.x + legend.bounds.width) {
|
|
3883
|
+
offsetX = legend.bounds.x;
|
|
3884
|
+
offsetY += legend.swatchSize + 6;
|
|
3885
|
+
}
|
|
3886
|
+
}
|
|
3887
|
+
const entryG = createSVGElement("g");
|
|
3888
|
+
entryG.setAttribute("class", "oc-legend-entry");
|
|
3889
|
+
entryG.setAttribute("role", "listitem");
|
|
3890
|
+
entryG.setAttribute("data-legend-index", String(i));
|
|
3891
|
+
entryG.setAttribute("data-legend-label", entry.label);
|
|
3892
|
+
if (entry.overflow) {
|
|
3893
|
+
entryG.setAttribute("data-legend-overflow", "true");
|
|
3894
|
+
entryG.setAttribute("aria-label", entry.label);
|
|
3895
|
+
entryG.setAttribute("opacity", "0.5");
|
|
3896
|
+
} else {
|
|
3897
|
+
entryG.setAttribute(
|
|
3898
|
+
"aria-label",
|
|
3899
|
+
`${entry.label}: ${entry.active !== false ? "visible" : "hidden"}`
|
|
3900
|
+
);
|
|
3901
|
+
entryG.setAttribute("style", "cursor: pointer");
|
|
3902
|
+
if (entry.active === false) {
|
|
3903
|
+
entryG.setAttribute("opacity", "0.3");
|
|
3904
|
+
}
|
|
3905
|
+
}
|
|
3906
|
+
if (entry.shape === "circle") {
|
|
3907
|
+
const circle = createSVGElement("circle");
|
|
3908
|
+
setAttrs(circle, {
|
|
3909
|
+
cx: offsetX + legend.swatchSize / 2,
|
|
3910
|
+
cy: offsetY + legend.swatchSize / 2,
|
|
3911
|
+
r: legend.swatchSize / 2,
|
|
3912
|
+
fill: entry.color
|
|
3913
|
+
});
|
|
3914
|
+
entryG.appendChild(circle);
|
|
3915
|
+
} else if (entry.shape === "line") {
|
|
3916
|
+
const line = createSVGElement("line");
|
|
3917
|
+
setAttrs(line, {
|
|
3918
|
+
x1: offsetX,
|
|
3919
|
+
y1: offsetY + legend.swatchSize / 2,
|
|
3920
|
+
x2: offsetX + legend.swatchSize,
|
|
3921
|
+
y2: offsetY + legend.swatchSize / 2,
|
|
3922
|
+
stroke: entry.color,
|
|
3923
|
+
"stroke-width": 2
|
|
3924
|
+
});
|
|
3925
|
+
entryG.appendChild(line);
|
|
3926
|
+
const dot = createSVGElement("circle");
|
|
3927
|
+
setAttrs(dot, {
|
|
3928
|
+
cx: offsetX + legend.swatchSize / 2,
|
|
3929
|
+
cy: offsetY + legend.swatchSize / 2,
|
|
3930
|
+
r: 2.5,
|
|
3931
|
+
fill: entry.color
|
|
3932
|
+
});
|
|
3933
|
+
entryG.appendChild(dot);
|
|
3934
|
+
} else {
|
|
3935
|
+
const rect = createSVGElement("rect");
|
|
3936
|
+
setAttrs(rect, {
|
|
3937
|
+
x: offsetX,
|
|
3938
|
+
y: offsetY,
|
|
3939
|
+
width: legend.swatchSize,
|
|
3940
|
+
height: legend.swatchSize,
|
|
3941
|
+
fill: entry.color,
|
|
3942
|
+
rx: 2
|
|
3943
|
+
});
|
|
3944
|
+
entryG.appendChild(rect);
|
|
3945
|
+
}
|
|
3946
|
+
const label = createSVGElement("text");
|
|
3947
|
+
setAttrs(label, {
|
|
3948
|
+
x: offsetX + legend.swatchSize + legend.swatchGap,
|
|
3949
|
+
y: offsetY + legend.swatchSize / 2,
|
|
3950
|
+
"dominant-baseline": "central"
|
|
3951
|
+
});
|
|
3952
|
+
applyTextStyle(label, legend.labelStyle);
|
|
3953
|
+
label.textContent = entry.label;
|
|
3954
|
+
entryG.appendChild(label);
|
|
3955
|
+
g.appendChild(entryG);
|
|
3956
|
+
if (isHorizontal) {
|
|
3957
|
+
const labelWidth = estimateTextWidth3(
|
|
3958
|
+
entry.label,
|
|
3959
|
+
legend.labelStyle.fontSize,
|
|
3960
|
+
legend.labelStyle.fontWeight
|
|
3961
|
+
);
|
|
3962
|
+
const entryWidth = legend.swatchSize + legend.swatchGap + labelWidth + legend.entryGap;
|
|
3963
|
+
offsetX += entryWidth;
|
|
3964
|
+
} else {
|
|
3965
|
+
offsetY += legend.swatchSize + legend.entryGap;
|
|
3966
|
+
}
|
|
3967
|
+
}
|
|
3968
|
+
parent.appendChild(g);
|
|
3969
|
+
}
|
|
3970
|
+
|
|
3971
|
+
// src/renderers/marks.ts
|
|
3972
|
+
var currentAnimation;
|
|
3973
|
+
var currentGradientMap = /* @__PURE__ */ new Map();
|
|
3974
|
+
function setMarkRenderState(state) {
|
|
3975
|
+
currentAnimation = state.animation;
|
|
3976
|
+
currentGradientMap = state.gradientMap;
|
|
3977
|
+
}
|
|
3978
|
+
function resetMarkRenderState() {
|
|
3979
|
+
currentAnimation = void 0;
|
|
3980
|
+
currentGradientMap = /* @__PURE__ */ new Map();
|
|
3981
|
+
}
|
|
3982
|
+
function stampAnimationAttrs(el, mark, fallbackIndex) {
|
|
3983
|
+
if (!currentAnimation?.enabled) return;
|
|
3984
|
+
const idx = mark.animationIndex ?? fallbackIndex;
|
|
3985
|
+
el.setAttribute("data-animation-index", String(idx));
|
|
3986
|
+
el.style.setProperty("--oc-mark-index", String(idx));
|
|
3987
|
+
}
|
|
3988
|
+
var markRenderers = {};
|
|
3989
|
+
function registerMarkRenderer(type, renderer) {
|
|
3990
|
+
markRenderers[type] = renderer;
|
|
3667
3991
|
}
|
|
3668
3992
|
function renderLineMark(mark, index2) {
|
|
3669
3993
|
const g = createSVGElement("g");
|
|
@@ -3948,319 +4272,15 @@ function renderMarks(parent, layout) {
|
|
|
3948
4272
|
}
|
|
3949
4273
|
parent.appendChild(g);
|
|
3950
4274
|
}
|
|
3951
|
-
|
|
3952
|
-
|
|
3953
|
-
|
|
3954
|
-
|
|
3955
|
-
|
|
3956
|
-
|
|
3957
|
-
renderAnnotation(g, layout.annotations[i], i, bgColor);
|
|
3958
|
-
}
|
|
3959
|
-
parent.appendChild(g);
|
|
3960
|
-
}
|
|
3961
|
-
function renderCurvedArrow(parent, from, to, stroke) {
|
|
3962
|
-
const pad = 6;
|
|
3963
|
-
const tipY = to.y - pad;
|
|
3964
|
-
const dy = tipY - from.y;
|
|
3965
|
-
const dist = Math.sqrt((to.x - from.x) ** 2 + dy ** 2) || 1;
|
|
3966
|
-
const arrowLen = 8;
|
|
3967
|
-
const arrowWidth = 4;
|
|
3968
|
-
const bulge = Math.max(dist * 0.4, 35);
|
|
3969
|
-
const cp1x = from.x + bulge;
|
|
3970
|
-
const cp1y = from.y + dy * 0.35;
|
|
3971
|
-
const cp2x = to.x;
|
|
3972
|
-
const cp2y = tipY - Math.abs(dy) * 0.25;
|
|
3973
|
-
const tx = to.x - cp2x;
|
|
3974
|
-
const ty = tipY - cp2y;
|
|
3975
|
-
const tLen = Math.sqrt(tx * tx + ty * ty) || 1;
|
|
3976
|
-
const ux = tx / tLen;
|
|
3977
|
-
const uy = ty / tLen;
|
|
3978
|
-
const baseX = to.x - ux * arrowLen;
|
|
3979
|
-
const baseY = tipY - uy * arrowLen;
|
|
3980
|
-
const path = createSVGElement("path");
|
|
3981
|
-
path.setAttribute("class", "oc-annotation-connector");
|
|
3982
|
-
setAttrs(path, {
|
|
3983
|
-
d: `M ${from.x} ${from.y} C ${cp1x} ${cp1y}, ${cp2x} ${cp2y}, ${baseX} ${baseY}`,
|
|
3984
|
-
fill: "none",
|
|
3985
|
-
stroke,
|
|
3986
|
-
"stroke-width": 1.5
|
|
3987
|
-
});
|
|
3988
|
-
parent.appendChild(path);
|
|
3989
|
-
const px = -uy;
|
|
3990
|
-
const py = ux;
|
|
3991
|
-
const arrow = createSVGElement("polygon");
|
|
3992
|
-
arrow.setAttribute("class", "oc-annotation-connector");
|
|
3993
|
-
setAttrs(arrow, {
|
|
3994
|
-
points: [
|
|
3995
|
-
`${to.x},${tipY}`,
|
|
3996
|
-
`${baseX + px * arrowWidth},${baseY + py * arrowWidth}`,
|
|
3997
|
-
`${baseX - px * arrowWidth},${baseY - py * arrowWidth}`
|
|
3998
|
-
].join(" "),
|
|
3999
|
-
fill: stroke
|
|
4000
|
-
});
|
|
4001
|
-
parent.appendChild(arrow);
|
|
4002
|
-
}
|
|
4003
|
-
function renderAnnotation(parent, annotation, index2, bgColor) {
|
|
4004
|
-
const g = createSVGElement("g");
|
|
4005
|
-
g.setAttribute("class", `oc-annotation oc-annotation-${annotation.type}`);
|
|
4006
|
-
g.setAttribute("data-annotation-index", String(index2));
|
|
4007
|
-
if (annotation.id) {
|
|
4008
|
-
g.setAttribute("data-annotation-id", annotation.id);
|
|
4009
|
-
}
|
|
4010
|
-
if (annotation.rect) {
|
|
4011
|
-
const rect = createSVGElement("rect");
|
|
4012
|
-
rect.setAttribute("class", "oc-annotation-range");
|
|
4013
|
-
setAttrs(rect, {
|
|
4014
|
-
x: annotation.rect.x,
|
|
4015
|
-
y: annotation.rect.y,
|
|
4016
|
-
width: annotation.rect.width,
|
|
4017
|
-
height: annotation.rect.height
|
|
4018
|
-
});
|
|
4019
|
-
if (annotation.fill) rect.setAttribute("fill", annotation.fill);
|
|
4020
|
-
if (annotation.opacity !== void 0) {
|
|
4021
|
-
rect.setAttribute("fill-opacity", String(annotation.opacity));
|
|
4022
|
-
}
|
|
4023
|
-
g.appendChild(rect);
|
|
4024
|
-
}
|
|
4025
|
-
if (annotation.line) {
|
|
4026
|
-
const line = createSVGElement("line");
|
|
4027
|
-
line.setAttribute("class", "oc-annotation-line");
|
|
4028
|
-
setAttrs(line, {
|
|
4029
|
-
x1: annotation.line.start.x,
|
|
4030
|
-
y1: annotation.line.start.y,
|
|
4031
|
-
x2: annotation.line.end.x,
|
|
4032
|
-
y2: annotation.line.end.y,
|
|
4033
|
-
"stroke-width": annotation.strokeWidth ?? 1
|
|
4034
|
-
});
|
|
4035
|
-
if (annotation.stroke) line.setAttribute("stroke", annotation.stroke);
|
|
4036
|
-
if (annotation.strokeDasharray) {
|
|
4037
|
-
line.setAttribute("stroke-dasharray", annotation.strokeDasharray);
|
|
4038
|
-
}
|
|
4039
|
-
g.appendChild(line);
|
|
4040
|
-
}
|
|
4041
|
-
if (annotation.label?.visible) {
|
|
4042
|
-
if (annotation.label.connector) {
|
|
4043
|
-
const c2 = annotation.label.connector;
|
|
4044
|
-
if (c2.style === "curve") {
|
|
4045
|
-
renderCurvedArrow(g, c2.from, c2.to, c2.stroke);
|
|
4046
|
-
} else {
|
|
4047
|
-
const connector = createSVGElement("line");
|
|
4048
|
-
connector.setAttribute("class", "oc-annotation-connector");
|
|
4049
|
-
setAttrs(connector, {
|
|
4050
|
-
x1: c2.from.x,
|
|
4051
|
-
y1: c2.from.y,
|
|
4052
|
-
x2: c2.to.x,
|
|
4053
|
-
y2: c2.to.y,
|
|
4054
|
-
stroke: c2.stroke,
|
|
4055
|
-
"stroke-width": 1,
|
|
4056
|
-
"stroke-opacity": 0.5
|
|
4057
|
-
});
|
|
4058
|
-
g.appendChild(connector);
|
|
4059
|
-
}
|
|
4060
|
-
}
|
|
4061
|
-
const text = createSVGElement("text");
|
|
4062
|
-
text.setAttribute("class", "oc-annotation-label");
|
|
4063
|
-
setAttrs(text, { x: annotation.label.x, y: annotation.label.y });
|
|
4064
|
-
applyTextStyle(text, annotation.label.style);
|
|
4065
|
-
const lines = annotation.label.text.split("\n");
|
|
4066
|
-
const fontSize = annotation.label.style.fontSize ?? 12;
|
|
4067
|
-
const lineHeight = fontSize * (annotation.label.style.lineHeight ?? 1.3);
|
|
4068
|
-
const isMultiLine = lines.length > 1;
|
|
4069
|
-
if (isMultiLine) {
|
|
4070
|
-
text.setAttribute("text-anchor", "middle");
|
|
4071
|
-
for (let i = 0; i < lines.length; i++) {
|
|
4072
|
-
const tspan = createSVGElement("tspan");
|
|
4073
|
-
setAttrs(tspan, { x: annotation.label.x, dy: i === 0 ? 0 : lineHeight });
|
|
4074
|
-
tspan.textContent = lines[i];
|
|
4075
|
-
text.appendChild(tspan);
|
|
4076
|
-
}
|
|
4077
|
-
} else {
|
|
4078
|
-
text.textContent = annotation.label.text;
|
|
4079
|
-
}
|
|
4080
|
-
if (annotation.label.background) {
|
|
4081
|
-
const charWidth = fontSize * 0.55;
|
|
4082
|
-
const maxLineWidth = Math.max(...lines.map((l) => l.length)) * charWidth;
|
|
4083
|
-
const totalHeight = lines.length * lineHeight;
|
|
4084
|
-
const pad = 3;
|
|
4085
|
-
const bgX = isMultiLine ? annotation.label.x - maxLineWidth / 2 - pad : annotation.label.x - pad;
|
|
4086
|
-
const bgRect = createSVGElement("rect");
|
|
4087
|
-
bgRect.setAttribute("class", "oc-annotation-bg");
|
|
4088
|
-
setAttrs(bgRect, {
|
|
4089
|
-
x: bgX,
|
|
4090
|
-
y: annotation.label.y - fontSize + (lineHeight - fontSize) / 2 - pad,
|
|
4091
|
-
width: maxLineWidth + pad * 2,
|
|
4092
|
-
height: totalHeight + pad * 2,
|
|
4093
|
-
fill: annotation.label.background,
|
|
4094
|
-
rx: 2
|
|
4095
|
-
});
|
|
4096
|
-
g.appendChild(bgRect);
|
|
4097
|
-
} else if (bgColor) {
|
|
4098
|
-
text.style.paintOrder = "stroke";
|
|
4099
|
-
text.style.stroke = bgColor;
|
|
4100
|
-
text.style.strokeWidth = `${Math.round(fontSize * 0.3)}px`;
|
|
4101
|
-
text.style.strokeLinejoin = "round";
|
|
4102
|
-
}
|
|
4103
|
-
g.appendChild(text);
|
|
4104
|
-
}
|
|
4105
|
-
parent.appendChild(g);
|
|
4106
|
-
}
|
|
4107
|
-
function renderLegend(parent, legend) {
|
|
4108
|
-
if (legend.entries.length === 0) return;
|
|
4109
|
-
const g = createSVGElement("g");
|
|
4110
|
-
g.setAttribute("class", "oc-legend");
|
|
4111
|
-
g.setAttribute("role", "list");
|
|
4112
|
-
g.setAttribute("aria-label", "Chart legend");
|
|
4113
|
-
const isHorizontal = legend.position === "top" || legend.position === "bottom";
|
|
4114
|
-
let offsetX = legend.bounds.x;
|
|
4115
|
-
let offsetY = legend.bounds.y;
|
|
4116
|
-
for (let i = 0; i < legend.entries.length; i++) {
|
|
4117
|
-
const entry = legend.entries[i];
|
|
4118
|
-
if (isHorizontal && i > 0) {
|
|
4119
|
-
const labelWidth = estimateTextWidth(
|
|
4120
|
-
entry.label,
|
|
4121
|
-
legend.labelStyle.fontSize,
|
|
4122
|
-
legend.labelStyle.fontWeight
|
|
4123
|
-
);
|
|
4124
|
-
const entryWidth = legend.swatchSize + legend.swatchGap + labelWidth + legend.entryGap;
|
|
4125
|
-
if (offsetX + entryWidth > legend.bounds.x + legend.bounds.width) {
|
|
4126
|
-
offsetX = legend.bounds.x;
|
|
4127
|
-
offsetY += legend.swatchSize + 6;
|
|
4128
|
-
}
|
|
4129
|
-
}
|
|
4130
|
-
const entryG = createSVGElement("g");
|
|
4131
|
-
entryG.setAttribute("class", "oc-legend-entry");
|
|
4132
|
-
entryG.setAttribute("role", "listitem");
|
|
4133
|
-
entryG.setAttribute("data-legend-index", String(i));
|
|
4134
|
-
entryG.setAttribute("data-legend-label", entry.label);
|
|
4135
|
-
if (entry.overflow) {
|
|
4136
|
-
entryG.setAttribute("data-legend-overflow", "true");
|
|
4137
|
-
entryG.setAttribute("aria-label", entry.label);
|
|
4138
|
-
entryG.setAttribute("opacity", "0.5");
|
|
4139
|
-
} else {
|
|
4140
|
-
entryG.setAttribute(
|
|
4141
|
-
"aria-label",
|
|
4142
|
-
`${entry.label}: ${entry.active !== false ? "visible" : "hidden"}`
|
|
4143
|
-
);
|
|
4144
|
-
entryG.setAttribute("style", "cursor: pointer");
|
|
4145
|
-
if (entry.active === false) {
|
|
4146
|
-
entryG.setAttribute("opacity", "0.3");
|
|
4147
|
-
}
|
|
4148
|
-
}
|
|
4149
|
-
if (entry.shape === "circle") {
|
|
4150
|
-
const circle = createSVGElement("circle");
|
|
4151
|
-
setAttrs(circle, {
|
|
4152
|
-
cx: offsetX + legend.swatchSize / 2,
|
|
4153
|
-
cy: offsetY + legend.swatchSize / 2,
|
|
4154
|
-
r: legend.swatchSize / 2,
|
|
4155
|
-
fill: entry.color
|
|
4156
|
-
});
|
|
4157
|
-
entryG.appendChild(circle);
|
|
4158
|
-
} else if (entry.shape === "line") {
|
|
4159
|
-
const line = createSVGElement("line");
|
|
4160
|
-
setAttrs(line, {
|
|
4161
|
-
x1: offsetX,
|
|
4162
|
-
y1: offsetY + legend.swatchSize / 2,
|
|
4163
|
-
x2: offsetX + legend.swatchSize,
|
|
4164
|
-
y2: offsetY + legend.swatchSize / 2,
|
|
4165
|
-
stroke: entry.color,
|
|
4166
|
-
"stroke-width": 2
|
|
4167
|
-
});
|
|
4168
|
-
entryG.appendChild(line);
|
|
4169
|
-
const dot = createSVGElement("circle");
|
|
4170
|
-
setAttrs(dot, {
|
|
4171
|
-
cx: offsetX + legend.swatchSize / 2,
|
|
4172
|
-
cy: offsetY + legend.swatchSize / 2,
|
|
4173
|
-
r: 2.5,
|
|
4174
|
-
fill: entry.color
|
|
4175
|
-
});
|
|
4176
|
-
entryG.appendChild(dot);
|
|
4177
|
-
} else {
|
|
4178
|
-
const rect = createSVGElement("rect");
|
|
4179
|
-
setAttrs(rect, {
|
|
4180
|
-
x: offsetX,
|
|
4181
|
-
y: offsetY,
|
|
4182
|
-
width: legend.swatchSize,
|
|
4183
|
-
height: legend.swatchSize,
|
|
4184
|
-
fill: entry.color,
|
|
4185
|
-
rx: 2
|
|
4186
|
-
});
|
|
4187
|
-
entryG.appendChild(rect);
|
|
4188
|
-
}
|
|
4189
|
-
const label = createSVGElement("text");
|
|
4190
|
-
setAttrs(label, {
|
|
4191
|
-
x: offsetX + legend.swatchSize + legend.swatchGap,
|
|
4192
|
-
y: offsetY + legend.swatchSize / 2,
|
|
4193
|
-
"dominant-baseline": "central"
|
|
4194
|
-
});
|
|
4195
|
-
applyTextStyle(label, legend.labelStyle);
|
|
4196
|
-
label.textContent = entry.label;
|
|
4197
|
-
entryG.appendChild(label);
|
|
4198
|
-
g.appendChild(entryG);
|
|
4199
|
-
if (isHorizontal) {
|
|
4200
|
-
const labelWidth = estimateTextWidth(
|
|
4201
|
-
entry.label,
|
|
4202
|
-
legend.labelStyle.fontSize,
|
|
4203
|
-
legend.labelStyle.fontWeight
|
|
4204
|
-
);
|
|
4205
|
-
const entryWidth = legend.swatchSize + legend.swatchGap + labelWidth + legend.entryGap;
|
|
4206
|
-
offsetX += entryWidth;
|
|
4207
|
-
} else {
|
|
4208
|
-
offsetY += legend.swatchSize + legend.entryGap;
|
|
4209
|
-
}
|
|
4210
|
-
}
|
|
4211
|
-
parent.appendChild(g);
|
|
4212
|
-
}
|
|
4213
|
-
var BRAND_URL = "https://tryopendata.ai";
|
|
4214
|
-
var XLINK_NS = "http://www.w3.org/1999/xlink";
|
|
4215
|
-
function renderBrand(parent, layout) {
|
|
4216
|
-
if (layout.dimensions.width < BRAND_MIN_WIDTH2) return;
|
|
4217
|
-
const { width } = layout.dimensions;
|
|
4218
|
-
const padding = layout.theme.spacing.padding;
|
|
4219
|
-
const rightEdge = width - padding;
|
|
4220
|
-
const fill = layout.theme.colors.axis;
|
|
4221
|
-
const { chrome } = layout;
|
|
4222
|
-
const xAxisExtent = computeXAxisExtent(layout);
|
|
4223
|
-
const bottomOffset = layout.area.y + layout.area.height + xAxisExtent;
|
|
4224
|
-
const firstBottom = chrome.source ?? chrome.byline ?? chrome.footer;
|
|
4225
|
-
const chromeY = firstBottom ? bottomOffset + firstBottom.y : bottomOffset + layout.theme.spacing.chartToFooter;
|
|
4226
|
-
const a2 = createSVGElement("a");
|
|
4227
|
-
a2.setAttribute("href", BRAND_URL);
|
|
4228
|
-
a2.setAttributeNS(XLINK_NS, "xlink:href", BRAND_URL);
|
|
4229
|
-
a2.setAttribute("target", "_blank");
|
|
4230
|
-
a2.setAttribute("rel", "noopener");
|
|
4231
|
-
a2.setAttribute("class", "oc-chrome-ref");
|
|
4232
|
-
const BRAND_LARGE = 16;
|
|
4233
|
-
const text = createSVGElement("text");
|
|
4234
|
-
setAttrs(text, {
|
|
4235
|
-
x: rightEdge,
|
|
4236
|
-
y: chromeY + BRAND_LARGE,
|
|
4237
|
-
"dominant-baseline": "alphabetic",
|
|
4238
|
-
"font-family": layout.theme.fonts.family,
|
|
4239
|
-
"font-size": BRAND_FONT_SIZE2,
|
|
4240
|
-
"text-anchor": "end",
|
|
4241
|
-
"fill-opacity": 0.55
|
|
4242
|
-
});
|
|
4243
|
-
text.style.setProperty("fill", fill);
|
|
4244
|
-
const trySpan = createSVGElement("tspan");
|
|
4245
|
-
trySpan.setAttribute("font-weight", "500");
|
|
4246
|
-
trySpan.textContent = "try";
|
|
4247
|
-
text.appendChild(trySpan);
|
|
4248
|
-
const openDataSpan = createSVGElement("tspan");
|
|
4249
|
-
openDataSpan.setAttribute("font-weight", "600");
|
|
4250
|
-
openDataSpan.setAttribute("font-size", String(BRAND_LARGE));
|
|
4251
|
-
openDataSpan.textContent = "OpenData";
|
|
4252
|
-
text.appendChild(openDataSpan);
|
|
4253
|
-
const aiSpan = createSVGElement("tspan");
|
|
4254
|
-
aiSpan.setAttribute("font-weight", "500");
|
|
4255
|
-
aiSpan.textContent = ".ai";
|
|
4256
|
-
text.appendChild(aiSpan);
|
|
4257
|
-
a2.appendChild(text);
|
|
4258
|
-
parent.appendChild(a2);
|
|
4259
|
-
}
|
|
4275
|
+
|
|
4276
|
+
// src/svg-renderer.ts
|
|
4277
|
+
var EASE_VAR_MAP = {
|
|
4278
|
+
smooth: "var(--oc-ease-smooth)",
|
|
4279
|
+
snappy: "var(--oc-ease-snappy)"
|
|
4280
|
+
};
|
|
4260
4281
|
function renderChartSVG(layout, container, opts) {
|
|
4261
4282
|
const { width, height } = layout.dimensions;
|
|
4262
4283
|
const animation = layout.animation;
|
|
4263
|
-
currentAnimation = animation;
|
|
4264
4284
|
const svg = createSVGElement("svg");
|
|
4265
4285
|
setAttrs(svg, {
|
|
4266
4286
|
viewBox: `0 0 ${width} ${height}`,
|
|
@@ -4321,38 +4341,41 @@ function renderChartSVG(layout, container, opts) {
|
|
|
4321
4341
|
});
|
|
4322
4342
|
clipPath.appendChild(clipRect);
|
|
4323
4343
|
defs.appendChild(clipPath);
|
|
4324
|
-
|
|
4344
|
+
const gradientMap = buildGradientDefs(layout.marks, defs);
|
|
4325
4345
|
svg.appendChild(defs);
|
|
4326
|
-
|
|
4327
|
-
|
|
4328
|
-
|
|
4329
|
-
|
|
4330
|
-
|
|
4331
|
-
(
|
|
4332
|
-
|
|
4333
|
-
|
|
4334
|
-
|
|
4335
|
-
const
|
|
4336
|
-
|
|
4337
|
-
|
|
4338
|
-
|
|
4339
|
-
|
|
4340
|
-
|
|
4341
|
-
|
|
4342
|
-
|
|
4343
|
-
|
|
4344
|
-
|
|
4345
|
-
|
|
4346
|
-
|
|
4347
|
-
|
|
4348
|
-
|
|
4349
|
-
|
|
4350
|
-
|
|
4351
|
-
|
|
4352
|
-
|
|
4346
|
+
setMarkRenderState({ animation, gradientMap });
|
|
4347
|
+
try {
|
|
4348
|
+
renderAxes(svg, layout);
|
|
4349
|
+
const clippedGroup = createSVGElement("g");
|
|
4350
|
+
clippedGroup.setAttribute("clip-path", `url(#${clipId})`);
|
|
4351
|
+
renderMarks(clippedGroup, layout);
|
|
4352
|
+
const hasLineOrAreaWithDataPoints = layout.marks.some(
|
|
4353
|
+
(m2) => (m2.type === "line" || m2.type === "area") && m2.dataPoints && m2.dataPoints.length > 0
|
|
4354
|
+
);
|
|
4355
|
+
const hasPointMarks = layout.marks.some((m2) => m2.type === "point");
|
|
4356
|
+
if (hasLineOrAreaWithDataPoints && !hasPointMarks) {
|
|
4357
|
+
const overlay = createSVGElement("rect");
|
|
4358
|
+
setAttrs(overlay, {
|
|
4359
|
+
x: layout.area.x,
|
|
4360
|
+
y: layout.area.y,
|
|
4361
|
+
width: layout.area.width,
|
|
4362
|
+
height: layout.area.height,
|
|
4363
|
+
fill: "transparent"
|
|
4364
|
+
});
|
|
4365
|
+
overlay.setAttribute("class", "oc-voronoi-overlay");
|
|
4366
|
+
overlay.setAttribute("data-voronoi-overlay", "true");
|
|
4367
|
+
clippedGroup.appendChild(overlay);
|
|
4368
|
+
}
|
|
4369
|
+
svg.appendChild(clippedGroup);
|
|
4370
|
+
renderAnnotations(svg, layout);
|
|
4371
|
+
renderLegend(svg, layout.legend);
|
|
4372
|
+
renderChrome(svg, layout);
|
|
4373
|
+
if (layout.watermark) {
|
|
4374
|
+
renderBrand(svg, layout);
|
|
4375
|
+
}
|
|
4376
|
+
} finally {
|
|
4377
|
+
resetMarkRenderState();
|
|
4353
4378
|
}
|
|
4354
|
-
currentAnimation = void 0;
|
|
4355
|
-
currentGradientMap = /* @__PURE__ */ new Map();
|
|
4356
4379
|
container.appendChild(svg);
|
|
4357
4380
|
return svg;
|
|
4358
4381
|
}
|
|
@@ -6482,7 +6505,7 @@ import { compileSankey } from "@opendata-ai/openchart-engine";
|
|
|
6482
6505
|
import {
|
|
6483
6506
|
BRAND_FONT_SIZE as BRAND_FONT_SIZE3,
|
|
6484
6507
|
BRAND_MIN_WIDTH as BRAND_MIN_WIDTH3,
|
|
6485
|
-
estimateTextWidth as
|
|
6508
|
+
estimateTextWidth as estimateTextWidth4,
|
|
6486
6509
|
wrapText as wrapText2
|
|
6487
6510
|
} from "@opendata-ai/openchart-core";
|
|
6488
6511
|
import { clampStaggerDelay as clampStaggerDelay2 } from "@opendata-ai/openchart-engine";
|
|
@@ -6641,7 +6664,7 @@ function renderLegend2(parent, legend) {
|
|
|
6641
6664
|
for (let i = 0; i < legend.entries.length; i++) {
|
|
6642
6665
|
const entry = legend.entries[i];
|
|
6643
6666
|
if (isHorizontal && i > 0) {
|
|
6644
|
-
const labelWidth =
|
|
6667
|
+
const labelWidth = estimateTextWidth4(
|
|
6645
6668
|
entry.label,
|
|
6646
6669
|
legend.labelStyle.fontSize,
|
|
6647
6670
|
legend.labelStyle.fontWeight
|
|
@@ -6692,7 +6715,7 @@ function renderLegend2(parent, legend) {
|
|
|
6692
6715
|
entryG.appendChild(label);
|
|
6693
6716
|
g.appendChild(entryG);
|
|
6694
6717
|
if (isHorizontal) {
|
|
6695
|
-
const labelWidth =
|
|
6718
|
+
const labelWidth = estimateTextWidth4(
|
|
6696
6719
|
entry.label,
|
|
6697
6720
|
legend.labelStyle.fontSize,
|
|
6698
6721
|
legend.labelStyle.fontWeight
|