@opendata-ai/openchart-vanilla 6.19.3 → 6.21.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 +718 -778
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
- package/src/__tests__/gradient-ids.test.ts +159 -0
- package/src/__tests__/resize-timing.test.ts +184 -0
- package/src/gradient-utils.ts +6 -8
- package/src/mount.ts +4 -11
- 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/sankey-renderer.ts +6 -43
- package/src/svg-ids.ts +18 -0
- package/src/svg-renderer.ts +64 -1269
package/dist/index.js
CHANGED
|
@@ -3314,11 +3314,18 @@ function setupTableAnimationCleanup(wrapper) {
|
|
|
3314
3314
|
}
|
|
3315
3315
|
|
|
3316
3316
|
// src/svg-renderer.ts
|
|
3317
|
-
import { BRAND_FONT_SIZE as BRAND_FONT_SIZE2, BRAND_MIN_WIDTH as BRAND_MIN_WIDTH2, estimateTextWidth } from "@opendata-ai/openchart-core";
|
|
3318
3317
|
import { clampStaggerDelay } from "@opendata-ai/openchart-engine";
|
|
3319
3318
|
|
|
3320
3319
|
// src/gradient-utils.ts
|
|
3321
3320
|
import { isGradientDef } from "@opendata-ai/openchart-core";
|
|
3321
|
+
|
|
3322
|
+
// src/svg-ids.ts
|
|
3323
|
+
var counter = 0;
|
|
3324
|
+
function nextSvgId(prefix) {
|
|
3325
|
+
return `${prefix}-${counter++}`;
|
|
3326
|
+
}
|
|
3327
|
+
|
|
3328
|
+
// src/gradient-utils.ts
|
|
3322
3329
|
var SVG_NS = "http://www.w3.org/2000/svg";
|
|
3323
3330
|
function gradientKey(def) {
|
|
3324
3331
|
return sortedStringify(def);
|
|
@@ -3375,7 +3382,6 @@ function appendStop(parent, stop) {
|
|
|
3375
3382
|
}
|
|
3376
3383
|
parent.appendChild(stopEl);
|
|
3377
3384
|
}
|
|
3378
|
-
var globalGradientCounter = 0;
|
|
3379
3385
|
function buildGradientDefs(marks, defs) {
|
|
3380
3386
|
const map = /* @__PURE__ */ new Map();
|
|
3381
3387
|
for (const mark of marks) {
|
|
@@ -3383,7 +3389,7 @@ function buildGradientDefs(marks, defs) {
|
|
|
3383
3389
|
if (fill && isGradientDef(fill)) {
|
|
3384
3390
|
const key = gradientKey(fill);
|
|
3385
3391
|
if (!map.has(key)) {
|
|
3386
|
-
const id =
|
|
3392
|
+
const id = nextSvgId("oc-grad");
|
|
3387
3393
|
const el = createGradientElement(fill, id);
|
|
3388
3394
|
defs.appendChild(el);
|
|
3389
3395
|
map.set(key, id);
|
|
@@ -3399,37 +3405,10 @@ function resolveMarkFill(fill, gradientMap) {
|
|
|
3399
3405
|
return id ? `url(#${id})` : "#000000";
|
|
3400
3406
|
}
|
|
3401
3407
|
|
|
3402
|
-
// src/svg-
|
|
3408
|
+
// src/renderers/svg-dom.ts
|
|
3409
|
+
import { estimateTextWidth } from "@opendata-ai/openchart-core";
|
|
3403
3410
|
var SVG_NS2 = "http://www.w3.org/2000/svg";
|
|
3404
|
-
var
|
|
3405
|
-
var currentGradientMap = /* @__PURE__ */ new Map();
|
|
3406
|
-
function stampAnimationAttrs(el, mark, fallbackIndex) {
|
|
3407
|
-
if (!currentAnimation?.enabled) return;
|
|
3408
|
-
const idx = mark.animationIndex ?? fallbackIndex;
|
|
3409
|
-
el.setAttribute("data-animation-index", String(idx));
|
|
3410
|
-
el.style.setProperty("--oc-mark-index", String(idx));
|
|
3411
|
-
}
|
|
3412
|
-
var EASE_VAR_MAP = {
|
|
3413
|
-
smooth: "var(--oc-ease-smooth)",
|
|
3414
|
-
snappy: "var(--oc-ease-snappy)"
|
|
3415
|
-
};
|
|
3416
|
-
function computeXAxisExtent(layout) {
|
|
3417
|
-
const xAxis = layout.axes.x;
|
|
3418
|
-
if (!xAxis) return 0;
|
|
3419
|
-
if (xAxis.tickAngle && Math.abs(xAxis.tickAngle) > 10) {
|
|
3420
|
-
const fontSize = xAxis.tickLabelStyle.fontSize;
|
|
3421
|
-
const fontWeight = xAxis.tickLabelStyle.fontWeight;
|
|
3422
|
-
const angleRad = Math.abs(xAxis.tickAngle) * (Math.PI / 180);
|
|
3423
|
-
let maxLabelWidth = 40;
|
|
3424
|
-
for (const tick of xAxis.ticks) {
|
|
3425
|
-
const w = estimateTextWidth(tick.label, fontSize, fontWeight);
|
|
3426
|
-
if (w > maxLabelWidth) maxLabelWidth = w;
|
|
3427
|
-
}
|
|
3428
|
-
const rotatedHeight = Math.min(maxLabelWidth * Math.sin(angleRad) + 6, 120);
|
|
3429
|
-
return xAxis.label ? rotatedHeight + 20 : rotatedHeight;
|
|
3430
|
-
}
|
|
3431
|
-
return xAxis.label ? 48 : 26;
|
|
3432
|
-
}
|
|
3411
|
+
var XLINK_NS = "http://www.w3.org/1999/xlink";
|
|
3433
3412
|
function createSVGElement(tag) {
|
|
3434
3413
|
return document.createElementNS(SVG_NS2, tag);
|
|
3435
3414
|
}
|
|
@@ -3455,191 +3434,244 @@ function applyTextStyle(el, style) {
|
|
|
3455
3434
|
el.setAttribute("font-variant", style.fontVariant);
|
|
3456
3435
|
}
|
|
3457
3436
|
}
|
|
3458
|
-
function
|
|
3459
|
-
|
|
3460
|
-
|
|
3461
|
-
if (
|
|
3462
|
-
|
|
3463
|
-
|
|
3464
|
-
);
|
|
3465
|
-
|
|
3466
|
-
|
|
3467
|
-
|
|
3468
|
-
|
|
3469
|
-
const words2 = text.split(" ");
|
|
3470
|
-
const lines2 = [];
|
|
3471
|
-
let current2 = "";
|
|
3472
|
-
for (const word of words2) {
|
|
3473
|
-
const candidate = current2 ? `${current2} ${word}` : word;
|
|
3474
|
-
const candidateWidth = measureText(candidate, fontSize, fontWeight).width;
|
|
3475
|
-
if (candidateWidth > maxWidth && current2) {
|
|
3476
|
-
lines2.push(current2);
|
|
3477
|
-
current2 = word;
|
|
3478
|
-
} else {
|
|
3479
|
-
current2 = candidate;
|
|
3480
|
-
}
|
|
3481
|
-
}
|
|
3482
|
-
if (current2) lines2.push(current2);
|
|
3483
|
-
return lines2;
|
|
3484
|
-
}
|
|
3485
|
-
const AVG_CHAR_WIDTH = 0.57;
|
|
3486
|
-
const WEIGHT_FACTORS = {
|
|
3487
|
-
100: 0.9,
|
|
3488
|
-
200: 0.92,
|
|
3489
|
-
300: 0.95,
|
|
3490
|
-
400: 1,
|
|
3491
|
-
500: 1.02,
|
|
3492
|
-
600: 1.05,
|
|
3493
|
-
700: 1.08,
|
|
3494
|
-
800: 1.1,
|
|
3495
|
-
900: 1.12
|
|
3496
|
-
};
|
|
3497
|
-
const weightFactor = WEIGHT_FACTORS[fontWeight] ?? 1;
|
|
3498
|
-
const charWidth = fontSize * AVG_CHAR_WIDTH * weightFactor;
|
|
3499
|
-
const maxChars = Math.floor(maxWidth / charWidth);
|
|
3500
|
-
if (text.length <= maxChars) return [text];
|
|
3501
|
-
const words = text.split(" ");
|
|
3502
|
-
const lines = [];
|
|
3503
|
-
let current = "";
|
|
3504
|
-
for (const word of words) {
|
|
3505
|
-
const candidate = current ? `${current} ${word}` : word;
|
|
3506
|
-
if (candidate.length > maxChars && current) {
|
|
3507
|
-
lines.push(current);
|
|
3508
|
-
current = word;
|
|
3509
|
-
} else {
|
|
3510
|
-
current = candidate;
|
|
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;
|
|
3511
3448
|
}
|
|
3449
|
+
const rotatedHeight = Math.min(maxLabelWidth * Math.sin(angleRad) + 6, 120);
|
|
3450
|
+
return xAxis.label ? rotatedHeight + 20 : rotatedHeight;
|
|
3512
3451
|
}
|
|
3513
|
-
|
|
3514
|
-
return lines;
|
|
3452
|
+
return xAxis.label ? 48 : 26;
|
|
3515
3453
|
}
|
|
3516
|
-
|
|
3517
|
-
|
|
3518
|
-
|
|
3519
|
-
|
|
3520
|
-
|
|
3521
|
-
|
|
3522
|
-
const
|
|
3523
|
-
|
|
3524
|
-
|
|
3525
|
-
|
|
3526
|
-
|
|
3527
|
-
|
|
3528
|
-
|
|
3529
|
-
|
|
3530
|
-
|
|
3531
|
-
|
|
3532
|
-
|
|
3533
|
-
|
|
3534
|
-
|
|
3535
|
-
|
|
3536
|
-
|
|
3537
|
-
|
|
3538
|
-
|
|
3539
|
-
|
|
3540
|
-
|
|
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);
|
|
3541
3497
|
}
|
|
3542
|
-
function
|
|
3498
|
+
function renderAnnotation(parent, annotation, index2, bgColor) {
|
|
3543
3499
|
const g = createSVGElement("g");
|
|
3544
|
-
g.setAttribute("class",
|
|
3545
|
-
|
|
3546
|
-
if (
|
|
3547
|
-
|
|
3548
|
-
}
|
|
3549
|
-
if (chrome.subtitle) {
|
|
3550
|
-
renderChromeElement(g, chrome.subtitle, "oc-subtitle", "subtitle", measureText);
|
|
3551
|
-
}
|
|
3552
|
-
const xAxisExtent = computeXAxisExtent(layout);
|
|
3553
|
-
const bottomOffset = layout.area.y + layout.area.height + xAxisExtent;
|
|
3554
|
-
if (chrome.source) {
|
|
3555
|
-
renderChromeElement(
|
|
3556
|
-
g,
|
|
3557
|
-
{ ...chrome.source, y: bottomOffset + chrome.source.y },
|
|
3558
|
-
"oc-source",
|
|
3559
|
-
"source",
|
|
3560
|
-
measureText
|
|
3561
|
-
);
|
|
3562
|
-
}
|
|
3563
|
-
if (chrome.byline) {
|
|
3564
|
-
renderChromeElement(
|
|
3565
|
-
g,
|
|
3566
|
-
{ ...chrome.byline, y: bottomOffset + chrome.byline.y },
|
|
3567
|
-
"oc-byline",
|
|
3568
|
-
"byline",
|
|
3569
|
-
measureText
|
|
3570
|
-
);
|
|
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);
|
|
3571
3504
|
}
|
|
3572
|
-
if (
|
|
3573
|
-
|
|
3574
|
-
|
|
3575
|
-
|
|
3576
|
-
|
|
3577
|
-
|
|
3578
|
-
|
|
3579
|
-
|
|
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);
|
|
3580
3519
|
}
|
|
3581
|
-
|
|
3582
|
-
}
|
|
3583
|
-
function renderAxis(parent, axis, orientation, layout) {
|
|
3584
|
-
const g = createSVGElement("g");
|
|
3585
|
-
g.setAttribute("class", `oc-axis oc-axis-${orientation}`);
|
|
3586
|
-
const { area } = layout;
|
|
3587
|
-
if (orientation === "x") {
|
|
3520
|
+
if (annotation.line) {
|
|
3588
3521
|
const line = createSVGElement("line");
|
|
3589
|
-
line.setAttribute("class", "oc-
|
|
3522
|
+
line.setAttribute("class", "oc-annotation-line");
|
|
3590
3523
|
setAttrs(line, {
|
|
3591
|
-
x1:
|
|
3592
|
-
y1:
|
|
3593
|
-
x2:
|
|
3594
|
-
y2:
|
|
3595
|
-
stroke:
|
|
3596
|
-
"stroke-width": 1
|
|
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
|
|
3597
3529
|
});
|
|
3530
|
+
if (annotation.stroke) line.setAttribute("stroke", annotation.stroke);
|
|
3531
|
+
if (annotation.strokeDasharray) {
|
|
3532
|
+
line.setAttribute("stroke-dasharray", annotation.strokeDasharray);
|
|
3533
|
+
}
|
|
3598
3534
|
g.appendChild(line);
|
|
3599
3535
|
}
|
|
3600
|
-
|
|
3601
|
-
if (
|
|
3602
|
-
const
|
|
3603
|
-
|
|
3604
|
-
|
|
3605
|
-
const labelX = tick.position;
|
|
3606
|
-
const labelY = area.y + area.height + 6;
|
|
3607
|
-
setAttrs(label, {
|
|
3608
|
-
x: labelX,
|
|
3609
|
-
y: labelY,
|
|
3610
|
-
"text-anchor": axis.tickAngle < 0 ? "end" : "start",
|
|
3611
|
-
"dominant-baseline": "central",
|
|
3612
|
-
transform: `rotate(${axis.tickAngle}, ${labelX}, ${labelY})`
|
|
3613
|
-
});
|
|
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);
|
|
3614
3541
|
} else {
|
|
3615
|
-
|
|
3616
|
-
|
|
3617
|
-
|
|
3618
|
-
|
|
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
|
|
3619
3552
|
});
|
|
3553
|
+
g.appendChild(connector);
|
|
3620
3554
|
}
|
|
3621
|
-
applyTextStyle(label, axis.tickLabelStyle);
|
|
3622
|
-
label.textContent = tick.label;
|
|
3623
|
-
g.appendChild(label);
|
|
3624
|
-
} else {
|
|
3625
|
-
const label = createSVGElement("text");
|
|
3626
|
-
label.setAttribute("class", "oc-axis-tick");
|
|
3627
|
-
setAttrs(label, {
|
|
3628
|
-
x: area.x - 6,
|
|
3629
|
-
y: tick.position,
|
|
3630
|
-
"text-anchor": "end",
|
|
3631
|
-
"dominant-baseline": "central"
|
|
3632
|
-
});
|
|
3633
|
-
applyTextStyle(label, axis.tickLabelStyle);
|
|
3634
|
-
label.textContent = tick.label;
|
|
3635
|
-
g.appendChild(label);
|
|
3636
3555
|
}
|
|
3637
|
-
|
|
3638
|
-
|
|
3639
|
-
|
|
3640
|
-
|
|
3641
|
-
|
|
3642
|
-
|
|
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);
|
|
3599
|
+
}
|
|
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);
|
|
3609
|
+
}
|
|
3610
|
+
parent.appendChild(g);
|
|
3611
|
+
}
|
|
3612
|
+
|
|
3613
|
+
// src/renderers/axes.ts
|
|
3614
|
+
import { estimateTextWidth as estimateTextWidth2 } from "@opendata-ai/openchart-core";
|
|
3615
|
+
function renderAxis(parent, axis, orientation, layout) {
|
|
3616
|
+
const g = createSVGElement("g");
|
|
3617
|
+
g.setAttribute("class", `oc-axis oc-axis-${orientation}`);
|
|
3618
|
+
const { area } = layout;
|
|
3619
|
+
if (orientation === "x") {
|
|
3620
|
+
const line = createSVGElement("line");
|
|
3621
|
+
line.setAttribute("class", "oc-axis-line");
|
|
3622
|
+
setAttrs(line, {
|
|
3623
|
+
x1: axis.start.x,
|
|
3624
|
+
y1: axis.start.y,
|
|
3625
|
+
x2: axis.end.x,
|
|
3626
|
+
y2: axis.end.y,
|
|
3627
|
+
stroke: layout.theme.colors.axis,
|
|
3628
|
+
"stroke-width": 1
|
|
3629
|
+
});
|
|
3630
|
+
g.appendChild(line);
|
|
3631
|
+
}
|
|
3632
|
+
for (const tick of axis.ticks) {
|
|
3633
|
+
if (orientation === "x") {
|
|
3634
|
+
const label = createSVGElement("text");
|
|
3635
|
+
label.setAttribute("class", "oc-axis-tick");
|
|
3636
|
+
if (axis.tickAngle && Math.abs(axis.tickAngle) > 10) {
|
|
3637
|
+
const labelX = tick.position;
|
|
3638
|
+
const labelY = area.y + area.height + 6;
|
|
3639
|
+
setAttrs(label, {
|
|
3640
|
+
x: labelX,
|
|
3641
|
+
y: labelY,
|
|
3642
|
+
"text-anchor": axis.tickAngle < 0 ? "end" : "start",
|
|
3643
|
+
"dominant-baseline": "central",
|
|
3644
|
+
transform: `rotate(${axis.tickAngle}, ${labelX}, ${labelY})`
|
|
3645
|
+
});
|
|
3646
|
+
} else {
|
|
3647
|
+
setAttrs(label, {
|
|
3648
|
+
x: tick.position,
|
|
3649
|
+
y: area.y + area.height + 14,
|
|
3650
|
+
"text-anchor": "middle"
|
|
3651
|
+
});
|
|
3652
|
+
}
|
|
3653
|
+
applyTextStyle(label, axis.tickLabelStyle);
|
|
3654
|
+
label.textContent = tick.label;
|
|
3655
|
+
g.appendChild(label);
|
|
3656
|
+
} else {
|
|
3657
|
+
const label = createSVGElement("text");
|
|
3658
|
+
label.setAttribute("class", "oc-axis-tick");
|
|
3659
|
+
setAttrs(label, {
|
|
3660
|
+
x: area.x - 6,
|
|
3661
|
+
y: tick.position,
|
|
3662
|
+
"text-anchor": "end",
|
|
3663
|
+
"dominant-baseline": "central"
|
|
3664
|
+
});
|
|
3665
|
+
applyTextStyle(label, axis.tickLabelStyle);
|
|
3666
|
+
label.textContent = tick.label;
|
|
3667
|
+
g.appendChild(label);
|
|
3668
|
+
}
|
|
3669
|
+
}
|
|
3670
|
+
for (const gridline of axis.gridlines) {
|
|
3671
|
+
const gl = createSVGElement("line");
|
|
3672
|
+
gl.setAttribute("class", "oc-gridline");
|
|
3673
|
+
if (orientation === "y") {
|
|
3674
|
+
setAttrs(gl, {
|
|
3643
3675
|
x1: area.x,
|
|
3644
3676
|
y1: gridline.position,
|
|
3645
3677
|
x2: area.x + area.width,
|
|
@@ -3672,7 +3704,7 @@ function renderAxis(parent, axis, orientation, layout) {
|
|
|
3672
3704
|
const angleRad = Math.abs(axis.tickAngle) * (Math.PI / 180);
|
|
3673
3705
|
let maxLabelWidth = 40;
|
|
3674
3706
|
for (const tick of axis.ticks) {
|
|
3675
|
-
const w =
|
|
3707
|
+
const w = estimateTextWidth2(
|
|
3676
3708
|
tick.label,
|
|
3677
3709
|
axis.tickLabelStyle.fontSize,
|
|
3678
3710
|
axis.tickLabelStyle.fontWeight
|
|
@@ -3707,449 +3739,128 @@ function renderAxes(parent, layout) {
|
|
|
3707
3739
|
renderAxis(parent, layout.axes.y, "y", layout);
|
|
3708
3740
|
}
|
|
3709
3741
|
}
|
|
3710
|
-
|
|
3711
|
-
|
|
3712
|
-
|
|
3713
|
-
|
|
3714
|
-
function
|
|
3715
|
-
|
|
3716
|
-
|
|
3717
|
-
|
|
3718
|
-
|
|
3719
|
-
|
|
3720
|
-
|
|
3721
|
-
|
|
3722
|
-
|
|
3723
|
-
|
|
3724
|
-
|
|
3725
|
-
|
|
3726
|
-
|
|
3727
|
-
|
|
3728
|
-
|
|
3729
|
-
|
|
3730
|
-
|
|
3731
|
-
|
|
3732
|
-
|
|
3733
|
-
|
|
3734
|
-
|
|
3735
|
-
|
|
3736
|
-
|
|
3737
|
-
|
|
3738
|
-
|
|
3739
|
-
|
|
3740
|
-
|
|
3741
|
-
}
|
|
3742
|
-
setAttrs(label, { x: mark.label.x, y: mark.label.y });
|
|
3743
|
-
applyTextStyle(label, mark.label.style);
|
|
3744
|
-
label.textContent = mark.label.text;
|
|
3745
|
-
g.appendChild(label);
|
|
3746
|
-
if (mark.label.connector) {
|
|
3747
|
-
const connector = createSVGElement("line");
|
|
3748
|
-
connector.setAttribute("class", "oc-mark-connector");
|
|
3749
|
-
setAttrs(connector, {
|
|
3750
|
-
x1: mark.label.connector.from.x,
|
|
3751
|
-
y1: mark.label.connector.from.y,
|
|
3752
|
-
x2: mark.label.connector.to.x,
|
|
3753
|
-
y2: mark.label.connector.to.y,
|
|
3754
|
-
stroke: mark.label.connector.stroke,
|
|
3755
|
-
"stroke-width": 1,
|
|
3756
|
-
"stroke-opacity": 0.5
|
|
3757
|
-
});
|
|
3758
|
-
g.appendChild(connector);
|
|
3759
|
-
}
|
|
3760
|
-
}
|
|
3761
|
-
return g;
|
|
3762
|
-
}
|
|
3763
|
-
function renderAreaMark(mark, index2) {
|
|
3764
|
-
const g = createSVGElement("g");
|
|
3765
|
-
g.setAttribute("data-mark-id", `area-${mark.seriesKey ?? index2}`);
|
|
3766
|
-
g.setAttribute("class", "oc-mark oc-mark-area");
|
|
3767
|
-
stampAnimationAttrs(g, mark, index2);
|
|
3768
|
-
if (mark.path) {
|
|
3769
|
-
const fill = createSVGElement("path");
|
|
3770
|
-
setAttrs(fill, {
|
|
3771
|
-
d: mark.path,
|
|
3772
|
-
fill: resolveMarkFill(mark.fill, currentGradientMap),
|
|
3773
|
-
"fill-opacity": mark.fillOpacity,
|
|
3774
|
-
stroke: "none"
|
|
3775
|
-
});
|
|
3776
|
-
g.appendChild(fill);
|
|
3777
|
-
if (mark.stroke && mark.topPath) {
|
|
3778
|
-
const strokePath = createSVGElement("path");
|
|
3779
|
-
strokePath.setAttribute("class", "oc-area-top");
|
|
3780
|
-
setAttrs(strokePath, {
|
|
3781
|
-
d: mark.topPath,
|
|
3782
|
-
fill: "none",
|
|
3783
|
-
stroke: mark.stroke,
|
|
3784
|
-
"stroke-width": mark.strokeWidth ?? 1
|
|
3785
|
-
});
|
|
3786
|
-
g.appendChild(strokePath);
|
|
3787
|
-
}
|
|
3788
|
-
}
|
|
3789
|
-
return g;
|
|
3790
|
-
}
|
|
3791
|
-
function renderRectMark(mark, index2) {
|
|
3792
|
-
const g = createSVGElement("g");
|
|
3793
|
-
g.setAttribute("data-mark-id", `rect-${index2}`);
|
|
3794
|
-
g.setAttribute("class", "oc-mark oc-mark-rect");
|
|
3795
|
-
stampAnimationAttrs(g, mark, index2);
|
|
3796
|
-
if (currentAnimation?.enabled && mark.orient === "horizontal") {
|
|
3797
|
-
g.setAttribute("data-orient", "horizontal");
|
|
3798
|
-
}
|
|
3799
|
-
const rect = createSVGElement("rect");
|
|
3800
|
-
setAttrs(rect, {
|
|
3801
|
-
x: mark.x,
|
|
3802
|
-
y: mark.y,
|
|
3803
|
-
width: mark.width,
|
|
3804
|
-
height: mark.height,
|
|
3805
|
-
fill: resolveMarkFill(mark.fill, currentGradientMap)
|
|
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
|
|
3806
3773
|
});
|
|
3807
|
-
|
|
3808
|
-
|
|
3809
|
-
|
|
3810
|
-
|
|
3811
|
-
|
|
3812
|
-
|
|
3813
|
-
|
|
3814
|
-
|
|
3815
|
-
|
|
3816
|
-
|
|
3817
|
-
|
|
3818
|
-
|
|
3819
|
-
|
|
3820
|
-
|
|
3821
|
-
|
|
3822
|
-
|
|
3823
|
-
g.appendChild(label);
|
|
3824
|
-
}
|
|
3825
|
-
return g;
|
|
3826
|
-
}
|
|
3827
|
-
function renderArcMark(mark, index2) {
|
|
3828
|
-
const g = createSVGElement("g");
|
|
3829
|
-
g.setAttribute("data-mark-id", `arc-${index2}`);
|
|
3830
|
-
g.setAttribute("class", "oc-mark oc-mark-arc");
|
|
3831
|
-
g.setAttribute("transform", `translate(${mark.center.x},${mark.center.y})`);
|
|
3832
|
-
stampAnimationAttrs(g, mark, index2);
|
|
3833
|
-
const path = createSVGElement("path");
|
|
3834
|
-
setAttrs(path, {
|
|
3835
|
-
d: mark.path,
|
|
3836
|
-
fill: resolveMarkFill(mark.fill, currentGradientMap),
|
|
3837
|
-
stroke: mark.stroke,
|
|
3838
|
-
"stroke-width": mark.strokeWidth
|
|
3839
|
-
});
|
|
3840
|
-
g.appendChild(path);
|
|
3841
|
-
if (mark.label?.visible) {
|
|
3842
|
-
const label = createSVGElement("text");
|
|
3843
|
-
label.setAttribute("class", "oc-mark-label");
|
|
3844
|
-
setAttrs(label, {
|
|
3845
|
-
x: mark.label.x - mark.center.x,
|
|
3846
|
-
y: mark.label.y - mark.center.y
|
|
3847
|
-
});
|
|
3848
|
-
applyTextStyle(label, mark.label.style);
|
|
3849
|
-
label.textContent = mark.label.text;
|
|
3850
|
-
g.appendChild(label);
|
|
3851
|
-
}
|
|
3852
|
-
return g;
|
|
3853
|
-
}
|
|
3854
|
-
function renderPointMark(mark, index2) {
|
|
3855
|
-
const circle = createSVGElement("circle");
|
|
3856
|
-
circle.setAttribute("data-mark-id", `point-${index2}`);
|
|
3857
|
-
circle.setAttribute("class", "oc-mark oc-mark-point");
|
|
3858
|
-
stampAnimationAttrs(circle, mark, index2);
|
|
3859
|
-
setAttrs(circle, {
|
|
3860
|
-
cx: mark.cx,
|
|
3861
|
-
cy: mark.cy,
|
|
3862
|
-
r: mark.r,
|
|
3863
|
-
fill: resolveMarkFill(mark.fill, currentGradientMap),
|
|
3864
|
-
stroke: mark.stroke,
|
|
3865
|
-
"stroke-width": mark.strokeWidth
|
|
3866
|
-
});
|
|
3867
|
-
if (mark.fillOpacity !== void 0) {
|
|
3868
|
-
circle.setAttribute("fill-opacity", String(mark.fillOpacity));
|
|
3869
|
-
}
|
|
3870
|
-
return circle;
|
|
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);
|
|
3871
3790
|
}
|
|
3872
|
-
|
|
3791
|
+
|
|
3792
|
+
// src/renderers/chrome.ts
|
|
3793
|
+
import { wrapText } from "@opendata-ai/openchart-core";
|
|
3794
|
+
function renderChromeElement(parent, element, className, chromeKey, measureText) {
|
|
3873
3795
|
const text = createSVGElement("text");
|
|
3874
|
-
text.
|
|
3875
|
-
text
|
|
3876
|
-
|
|
3877
|
-
|
|
3878
|
-
|
|
3879
|
-
|
|
3880
|
-
|
|
3881
|
-
|
|
3882
|
-
|
|
3883
|
-
|
|
3884
|
-
|
|
3885
|
-
|
|
3886
|
-
|
|
3887
|
-
if (mark.fontFamily) {
|
|
3888
|
-
text.setAttribute("font-family", mark.fontFamily);
|
|
3889
|
-
}
|
|
3890
|
-
if (mark.angle) {
|
|
3891
|
-
text.setAttribute("transform", `rotate(${mark.angle}, ${mark.x}, ${mark.y})`);
|
|
3892
|
-
}
|
|
3893
|
-
text.textContent = mark.text;
|
|
3894
|
-
return text;
|
|
3895
|
-
}
|
|
3896
|
-
function renderRuleMark(mark, index2) {
|
|
3897
|
-
const line = createSVGElement("line");
|
|
3898
|
-
line.setAttribute("data-mark-id", `rule-${index2}`);
|
|
3899
|
-
line.setAttribute("class", "oc-mark oc-mark-rule");
|
|
3900
|
-
stampAnimationAttrs(line, mark, index2);
|
|
3901
|
-
setAttrs(line, {
|
|
3902
|
-
x1: mark.x1,
|
|
3903
|
-
y1: mark.y1,
|
|
3904
|
-
x2: mark.x2,
|
|
3905
|
-
y2: mark.y2,
|
|
3906
|
-
stroke: mark.stroke,
|
|
3907
|
-
"stroke-width": mark.strokeWidth
|
|
3908
|
-
});
|
|
3909
|
-
if (mark.strokeDasharray) {
|
|
3910
|
-
line.setAttribute("stroke-dasharray", mark.strokeDasharray);
|
|
3911
|
-
}
|
|
3912
|
-
if (mark.opacity != null) {
|
|
3913
|
-
line.setAttribute("opacity", String(mark.opacity));
|
|
3914
|
-
}
|
|
3915
|
-
return line;
|
|
3916
|
-
}
|
|
3917
|
-
function renderTickMark(mark, index2) {
|
|
3918
|
-
const line = createSVGElement("line");
|
|
3919
|
-
line.setAttribute("data-mark-id", `tick-${index2}`);
|
|
3920
|
-
line.setAttribute("class", "oc-mark oc-mark-tick");
|
|
3921
|
-
stampAnimationAttrs(line, mark, index2);
|
|
3922
|
-
const half = mark.length / 2;
|
|
3923
|
-
if (mark.orient === "vertical") {
|
|
3924
|
-
setAttrs(line, {
|
|
3925
|
-
x1: mark.x,
|
|
3926
|
-
y1: mark.y - half,
|
|
3927
|
-
x2: mark.x,
|
|
3928
|
-
y2: mark.y + half,
|
|
3929
|
-
stroke: mark.stroke,
|
|
3930
|
-
"stroke-width": mark.strokeWidth
|
|
3931
|
-
});
|
|
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;
|
|
3932
3809
|
} else {
|
|
3933
|
-
|
|
3934
|
-
|
|
3935
|
-
|
|
3936
|
-
|
|
3937
|
-
|
|
3938
|
-
|
|
3939
|
-
"stroke-width": mark.strokeWidth
|
|
3940
|
-
});
|
|
3941
|
-
}
|
|
3942
|
-
if (mark.opacity != null) {
|
|
3943
|
-
line.setAttribute("opacity", String(mark.opacity));
|
|
3944
|
-
}
|
|
3945
|
-
return line;
|
|
3946
|
-
}
|
|
3947
|
-
registerMarkRenderer("line", renderLineMark);
|
|
3948
|
-
registerMarkRenderer("area", renderAreaMark);
|
|
3949
|
-
registerMarkRenderer("rect", renderRectMark);
|
|
3950
|
-
registerMarkRenderer("arc", renderArcMark);
|
|
3951
|
-
registerMarkRenderer("point", renderPointMark);
|
|
3952
|
-
registerMarkRenderer("textMark", renderTextMark);
|
|
3953
|
-
registerMarkRenderer("rule", renderRuleMark);
|
|
3954
|
-
registerMarkRenderer("tick", renderTickMark);
|
|
3955
|
-
function getMarkSeries(mark) {
|
|
3956
|
-
if (mark.type === "line" || mark.type === "area") {
|
|
3957
|
-
return mark.seriesKey;
|
|
3958
|
-
}
|
|
3959
|
-
if (mark.type === "arc") {
|
|
3960
|
-
return mark.aria.label.split(":")[0]?.trim();
|
|
3961
|
-
}
|
|
3962
|
-
if (mark.aria?.label) {
|
|
3963
|
-
const beforeColon = mark.aria.label.split(":")[0]?.trim();
|
|
3964
|
-
if (beforeColon) return beforeColon;
|
|
3965
|
-
}
|
|
3966
|
-
return void 0;
|
|
3967
|
-
}
|
|
3968
|
-
function renderMarks(parent, layout) {
|
|
3969
|
-
const g = createSVGElement("g");
|
|
3970
|
-
g.setAttribute("class", "oc-marks");
|
|
3971
|
-
for (let i = 0; i < layout.marks.length; i++) {
|
|
3972
|
-
const mark = layout.marks[i];
|
|
3973
|
-
const renderer = markRenderers[mark.type];
|
|
3974
|
-
if (!renderer) continue;
|
|
3975
|
-
const el = renderer(mark, i);
|
|
3976
|
-
if (mark.aria?.label) {
|
|
3977
|
-
el.setAttribute("aria-label", mark.aria.label);
|
|
3978
|
-
}
|
|
3979
|
-
const series = getMarkSeries(mark);
|
|
3980
|
-
if (series) {
|
|
3981
|
-
el.setAttribute("data-series", series);
|
|
3982
|
-
}
|
|
3983
|
-
if (currentAnimation?.enabled && mark.type === "rect") {
|
|
3984
|
-
const rect = mark;
|
|
3985
|
-
if (rect.stackGroup && rect.stackPos !== void 0) {
|
|
3986
|
-
el.setAttribute("data-stack-pos", String(rect.stackPos));
|
|
3987
|
-
el.style.setProperty(
|
|
3988
|
-
"--oc-stack-pos",
|
|
3989
|
-
String(rect.stackPos)
|
|
3990
|
-
);
|
|
3991
|
-
}
|
|
3992
|
-
}
|
|
3993
|
-
g.appendChild(el);
|
|
3994
|
-
}
|
|
3995
|
-
parent.appendChild(g);
|
|
3996
|
-
}
|
|
3997
|
-
function renderAnnotations(parent, layout) {
|
|
3998
|
-
if (layout.annotations.length === 0) return;
|
|
3999
|
-
const g = createSVGElement("g");
|
|
4000
|
-
g.setAttribute("class", "oc-annotations");
|
|
4001
|
-
const bgColor = layout.theme.colors.background;
|
|
4002
|
-
for (let i = 0; i < layout.annotations.length; i++) {
|
|
4003
|
-
renderAnnotation(g, layout.annotations[i], i, bgColor);
|
|
4004
|
-
}
|
|
4005
|
-
parent.appendChild(g);
|
|
4006
|
-
}
|
|
4007
|
-
function renderCurvedArrow(parent, from, to, stroke) {
|
|
4008
|
-
const pad = 6;
|
|
4009
|
-
const tipY = to.y - pad;
|
|
4010
|
-
const dy = tipY - from.y;
|
|
4011
|
-
const dist = Math.sqrt((to.x - from.x) ** 2 + dy ** 2) || 1;
|
|
4012
|
-
const arrowLen = 8;
|
|
4013
|
-
const arrowWidth = 4;
|
|
4014
|
-
const bulge = Math.max(dist * 0.4, 35);
|
|
4015
|
-
const cp1x = from.x + bulge;
|
|
4016
|
-
const cp1y = from.y + dy * 0.35;
|
|
4017
|
-
const cp2x = to.x;
|
|
4018
|
-
const cp2y = tipY - Math.abs(dy) * 0.25;
|
|
4019
|
-
const tx = to.x - cp2x;
|
|
4020
|
-
const ty = tipY - cp2y;
|
|
4021
|
-
const tLen = Math.sqrt(tx * tx + ty * ty) || 1;
|
|
4022
|
-
const ux = tx / tLen;
|
|
4023
|
-
const uy = ty / tLen;
|
|
4024
|
-
const baseX = to.x - ux * arrowLen;
|
|
4025
|
-
const baseY = tipY - uy * arrowLen;
|
|
4026
|
-
const path = createSVGElement("path");
|
|
4027
|
-
path.setAttribute("class", "oc-annotation-connector");
|
|
4028
|
-
setAttrs(path, {
|
|
4029
|
-
d: `M ${from.x} ${from.y} C ${cp1x} ${cp1y}, ${cp2x} ${cp2y}, ${baseX} ${baseY}`,
|
|
4030
|
-
fill: "none",
|
|
4031
|
-
stroke,
|
|
4032
|
-
"stroke-width": 1.5
|
|
4033
|
-
});
|
|
4034
|
-
parent.appendChild(path);
|
|
4035
|
-
const px = -uy;
|
|
4036
|
-
const py = ux;
|
|
4037
|
-
const arrow = createSVGElement("polygon");
|
|
4038
|
-
arrow.setAttribute("class", "oc-annotation-connector");
|
|
4039
|
-
setAttrs(arrow, {
|
|
4040
|
-
points: [
|
|
4041
|
-
`${to.x},${tipY}`,
|
|
4042
|
-
`${baseX + px * arrowWidth},${baseY + py * arrowWidth}`,
|
|
4043
|
-
`${baseX - px * arrowWidth},${baseY - py * arrowWidth}`
|
|
4044
|
-
].join(" "),
|
|
4045
|
-
fill: stroke
|
|
4046
|
-
});
|
|
4047
|
-
parent.appendChild(arrow);
|
|
4048
|
-
}
|
|
4049
|
-
function renderAnnotation(parent, annotation, index2, bgColor) {
|
|
4050
|
-
const g = createSVGElement("g");
|
|
4051
|
-
g.setAttribute("class", `oc-annotation oc-annotation-${annotation.type}`);
|
|
4052
|
-
g.setAttribute("data-annotation-index", String(index2));
|
|
4053
|
-
if (annotation.id) {
|
|
4054
|
-
g.setAttribute("data-annotation-id", annotation.id);
|
|
4055
|
-
}
|
|
4056
|
-
if (annotation.rect) {
|
|
4057
|
-
const rect = createSVGElement("rect");
|
|
4058
|
-
rect.setAttribute("class", "oc-annotation-range");
|
|
4059
|
-
setAttrs(rect, {
|
|
4060
|
-
x: annotation.rect.x,
|
|
4061
|
-
y: annotation.rect.y,
|
|
4062
|
-
width: annotation.rect.width,
|
|
4063
|
-
height: annotation.rect.height
|
|
4064
|
-
});
|
|
4065
|
-
if (annotation.fill) rect.setAttribute("fill", annotation.fill);
|
|
4066
|
-
if (annotation.opacity !== void 0) {
|
|
4067
|
-
rect.setAttribute("fill-opacity", String(annotation.opacity));
|
|
4068
|
-
}
|
|
4069
|
-
g.appendChild(rect);
|
|
4070
|
-
}
|
|
4071
|
-
if (annotation.line) {
|
|
4072
|
-
const line = createSVGElement("line");
|
|
4073
|
-
line.setAttribute("class", "oc-annotation-line");
|
|
4074
|
-
setAttrs(line, {
|
|
4075
|
-
x1: annotation.line.start.x,
|
|
4076
|
-
y1: annotation.line.start.y,
|
|
4077
|
-
x2: annotation.line.end.x,
|
|
4078
|
-
y2: annotation.line.end.y,
|
|
4079
|
-
"stroke-width": annotation.strokeWidth ?? 1
|
|
4080
|
-
});
|
|
4081
|
-
if (annotation.stroke) line.setAttribute("stroke", annotation.stroke);
|
|
4082
|
-
if (annotation.strokeDasharray) {
|
|
4083
|
-
line.setAttribute("stroke-dasharray", annotation.strokeDasharray);
|
|
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);
|
|
4084
3816
|
}
|
|
4085
|
-
g.appendChild(line);
|
|
4086
3817
|
}
|
|
4087
|
-
|
|
4088
|
-
|
|
4089
|
-
|
|
4090
|
-
|
|
4091
|
-
|
|
4092
|
-
|
|
4093
|
-
|
|
4094
|
-
|
|
4095
|
-
|
|
4096
|
-
|
|
4097
|
-
|
|
4098
|
-
|
|
4099
|
-
|
|
4100
|
-
|
|
4101
|
-
|
|
4102
|
-
|
|
4103
|
-
|
|
4104
|
-
|
|
4105
|
-
|
|
4106
|
-
|
|
4107
|
-
|
|
4108
|
-
|
|
4109
|
-
|
|
4110
|
-
|
|
4111
|
-
|
|
4112
|
-
|
|
4113
|
-
|
|
4114
|
-
|
|
4115
|
-
|
|
4116
|
-
|
|
4117
|
-
|
|
4118
|
-
|
|
4119
|
-
|
|
4120
|
-
|
|
4121
|
-
|
|
4122
|
-
}
|
|
4123
|
-
|
|
4124
|
-
|
|
4125
|
-
|
|
4126
|
-
|
|
4127
|
-
const charWidth = fontSize * 0.55;
|
|
4128
|
-
const maxLineWidth = Math.max(...lines.map((l) => l.length)) * charWidth;
|
|
4129
|
-
const totalHeight = lines.length * lineHeight;
|
|
4130
|
-
const pad = 3;
|
|
4131
|
-
const bgX = isMultiLine ? annotation.label.x - maxLineWidth / 2 - pad : annotation.label.x - pad;
|
|
4132
|
-
const bgRect = createSVGElement("rect");
|
|
4133
|
-
bgRect.setAttribute("class", "oc-annotation-bg");
|
|
4134
|
-
setAttrs(bgRect, {
|
|
4135
|
-
x: bgX,
|
|
4136
|
-
y: annotation.label.y - fontSize + (lineHeight - fontSize) / 2 - pad,
|
|
4137
|
-
width: maxLineWidth + pad * 2,
|
|
4138
|
-
height: totalHeight + pad * 2,
|
|
4139
|
-
fill: annotation.label.background,
|
|
4140
|
-
rx: 2
|
|
4141
|
-
});
|
|
4142
|
-
g.appendChild(bgRect);
|
|
4143
|
-
} else if (bgColor) {
|
|
4144
|
-
text.style.paintOrder = "stroke";
|
|
4145
|
-
text.style.stroke = bgColor;
|
|
4146
|
-
text.style.strokeWidth = `${Math.round(fontSize * 0.3)}px`;
|
|
4147
|
-
text.style.strokeLinejoin = "round";
|
|
4148
|
-
}
|
|
4149
|
-
g.appendChild(text);
|
|
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
|
+
);
|
|
4150
3858
|
}
|
|
4151
3859
|
parent.appendChild(g);
|
|
4152
3860
|
}
|
|
3861
|
+
|
|
3862
|
+
// src/renderers/legend.ts
|
|
3863
|
+
import { estimateTextWidth as estimateTextWidth3 } from "@opendata-ai/openchart-core";
|
|
4153
3864
|
function renderLegend(parent, legend) {
|
|
4154
3865
|
if (legend.entries.length === 0) return;
|
|
4155
3866
|
const g = createSVGElement("g");
|
|
@@ -4162,7 +3873,7 @@ function renderLegend(parent, legend) {
|
|
|
4162
3873
|
for (let i = 0; i < legend.entries.length; i++) {
|
|
4163
3874
|
const entry = legend.entries[i];
|
|
4164
3875
|
if (isHorizontal && i > 0) {
|
|
4165
|
-
const labelWidth =
|
|
3876
|
+
const labelWidth = estimateTextWidth3(
|
|
4166
3877
|
entry.label,
|
|
4167
3878
|
legend.labelStyle.fontSize,
|
|
4168
3879
|
legend.labelStyle.fontWeight
|
|
@@ -4234,79 +3945,342 @@ function renderLegend(parent, legend) {
|
|
|
4234
3945
|
}
|
|
4235
3946
|
const label = createSVGElement("text");
|
|
4236
3947
|
setAttrs(label, {
|
|
4237
|
-
x: offsetX + legend.swatchSize + legend.swatchGap,
|
|
4238
|
-
y: offsetY + legend.swatchSize / 2,
|
|
4239
|
-
"dominant-baseline": "central"
|
|
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;
|
|
3991
|
+
}
|
|
3992
|
+
function renderLineMark(mark, index2) {
|
|
3993
|
+
const g = createSVGElement("g");
|
|
3994
|
+
g.setAttribute("data-mark-id", `line-${mark.seriesKey ?? index2}`);
|
|
3995
|
+
g.setAttribute("class", "oc-mark oc-mark-line");
|
|
3996
|
+
stampAnimationAttrs(g, mark, index2);
|
|
3997
|
+
if (mark.points.length > 1) {
|
|
3998
|
+
const path = createSVGElement("path");
|
|
3999
|
+
const d = mark.path ?? mark.points.map((p, i) => `${i === 0 ? "M" : "L"}${p.x},${p.y}`).join(" ");
|
|
4000
|
+
setAttrs(path, {
|
|
4001
|
+
d,
|
|
4002
|
+
fill: "none",
|
|
4003
|
+
stroke: mark.stroke,
|
|
4004
|
+
"stroke-width": mark.strokeWidth
|
|
4005
|
+
});
|
|
4006
|
+
if (mark.strokeDasharray) {
|
|
4007
|
+
path.setAttribute("stroke-dasharray", mark.strokeDasharray);
|
|
4008
|
+
}
|
|
4009
|
+
if (mark.opacity != null) {
|
|
4010
|
+
path.setAttribute("opacity", String(mark.opacity));
|
|
4011
|
+
}
|
|
4012
|
+
g.appendChild(path);
|
|
4013
|
+
}
|
|
4014
|
+
if (mark.label?.visible) {
|
|
4015
|
+
const label = createSVGElement("text");
|
|
4016
|
+
label.setAttribute("class", "oc-mark-label");
|
|
4017
|
+
if (mark.seriesKey) {
|
|
4018
|
+
label.setAttribute("data-series", mark.seriesKey);
|
|
4019
|
+
}
|
|
4020
|
+
setAttrs(label, { x: mark.label.x, y: mark.label.y });
|
|
4021
|
+
applyTextStyle(label, mark.label.style);
|
|
4022
|
+
label.textContent = mark.label.text;
|
|
4023
|
+
g.appendChild(label);
|
|
4024
|
+
if (mark.label.connector) {
|
|
4025
|
+
const connector = createSVGElement("line");
|
|
4026
|
+
connector.setAttribute("class", "oc-mark-connector");
|
|
4027
|
+
setAttrs(connector, {
|
|
4028
|
+
x1: mark.label.connector.from.x,
|
|
4029
|
+
y1: mark.label.connector.from.y,
|
|
4030
|
+
x2: mark.label.connector.to.x,
|
|
4031
|
+
y2: mark.label.connector.to.y,
|
|
4032
|
+
stroke: mark.label.connector.stroke,
|
|
4033
|
+
"stroke-width": 1,
|
|
4034
|
+
"stroke-opacity": 0.5
|
|
4035
|
+
});
|
|
4036
|
+
g.appendChild(connector);
|
|
4037
|
+
}
|
|
4038
|
+
}
|
|
4039
|
+
return g;
|
|
4040
|
+
}
|
|
4041
|
+
function renderAreaMark(mark, index2) {
|
|
4042
|
+
const g = createSVGElement("g");
|
|
4043
|
+
g.setAttribute("data-mark-id", `area-${mark.seriesKey ?? index2}`);
|
|
4044
|
+
g.setAttribute("class", "oc-mark oc-mark-area");
|
|
4045
|
+
stampAnimationAttrs(g, mark, index2);
|
|
4046
|
+
if (mark.path) {
|
|
4047
|
+
const fill = createSVGElement("path");
|
|
4048
|
+
setAttrs(fill, {
|
|
4049
|
+
d: mark.path,
|
|
4050
|
+
fill: resolveMarkFill(mark.fill, currentGradientMap),
|
|
4051
|
+
"fill-opacity": mark.fillOpacity,
|
|
4052
|
+
stroke: "none"
|
|
4053
|
+
});
|
|
4054
|
+
g.appendChild(fill);
|
|
4055
|
+
if (mark.stroke && mark.topPath) {
|
|
4056
|
+
const strokePath = createSVGElement("path");
|
|
4057
|
+
strokePath.setAttribute("class", "oc-area-top");
|
|
4058
|
+
setAttrs(strokePath, {
|
|
4059
|
+
d: mark.topPath,
|
|
4060
|
+
fill: "none",
|
|
4061
|
+
stroke: mark.stroke,
|
|
4062
|
+
"stroke-width": mark.strokeWidth ?? 1
|
|
4063
|
+
});
|
|
4064
|
+
g.appendChild(strokePath);
|
|
4065
|
+
}
|
|
4066
|
+
}
|
|
4067
|
+
return g;
|
|
4068
|
+
}
|
|
4069
|
+
function renderRectMark(mark, index2) {
|
|
4070
|
+
const g = createSVGElement("g");
|
|
4071
|
+
g.setAttribute("data-mark-id", `rect-${index2}`);
|
|
4072
|
+
g.setAttribute("class", "oc-mark oc-mark-rect");
|
|
4073
|
+
stampAnimationAttrs(g, mark, index2);
|
|
4074
|
+
if (currentAnimation?.enabled && mark.orient === "horizontal") {
|
|
4075
|
+
g.setAttribute("data-orient", "horizontal");
|
|
4076
|
+
}
|
|
4077
|
+
const rect = createSVGElement("rect");
|
|
4078
|
+
setAttrs(rect, {
|
|
4079
|
+
x: mark.x,
|
|
4080
|
+
y: mark.y,
|
|
4081
|
+
width: mark.width,
|
|
4082
|
+
height: mark.height,
|
|
4083
|
+
fill: resolveMarkFill(mark.fill, currentGradientMap)
|
|
4084
|
+
});
|
|
4085
|
+
if (mark.stroke) {
|
|
4086
|
+
rect.setAttribute("stroke", mark.stroke);
|
|
4087
|
+
}
|
|
4088
|
+
if (mark.strokeWidth) {
|
|
4089
|
+
rect.setAttribute("stroke-width", String(mark.strokeWidth));
|
|
4090
|
+
}
|
|
4091
|
+
if (mark.cornerRadius) {
|
|
4092
|
+
setAttrs(rect, { rx: mark.cornerRadius, ry: mark.cornerRadius });
|
|
4093
|
+
}
|
|
4094
|
+
g.appendChild(rect);
|
|
4095
|
+
if (mark.label?.visible) {
|
|
4096
|
+
const label = createSVGElement("text");
|
|
4097
|
+
label.setAttribute("class", "oc-mark-label");
|
|
4098
|
+
setAttrs(label, { x: mark.label.x, y: mark.label.y });
|
|
4099
|
+
applyTextStyle(label, mark.label.style);
|
|
4100
|
+
label.textContent = mark.label.text;
|
|
4101
|
+
g.appendChild(label);
|
|
4102
|
+
}
|
|
4103
|
+
return g;
|
|
4104
|
+
}
|
|
4105
|
+
function renderArcMark(mark, index2) {
|
|
4106
|
+
const g = createSVGElement("g");
|
|
4107
|
+
g.setAttribute("data-mark-id", `arc-${index2}`);
|
|
4108
|
+
g.setAttribute("class", "oc-mark oc-mark-arc");
|
|
4109
|
+
g.setAttribute("transform", `translate(${mark.center.x},${mark.center.y})`);
|
|
4110
|
+
stampAnimationAttrs(g, mark, index2);
|
|
4111
|
+
const path = createSVGElement("path");
|
|
4112
|
+
setAttrs(path, {
|
|
4113
|
+
d: mark.path,
|
|
4114
|
+
fill: resolveMarkFill(mark.fill, currentGradientMap),
|
|
4115
|
+
stroke: mark.stroke,
|
|
4116
|
+
"stroke-width": mark.strokeWidth
|
|
4117
|
+
});
|
|
4118
|
+
g.appendChild(path);
|
|
4119
|
+
if (mark.label?.visible) {
|
|
4120
|
+
const label = createSVGElement("text");
|
|
4121
|
+
label.setAttribute("class", "oc-mark-label");
|
|
4122
|
+
setAttrs(label, {
|
|
4123
|
+
x: mark.label.x - mark.center.x,
|
|
4124
|
+
y: mark.label.y - mark.center.y
|
|
4240
4125
|
});
|
|
4241
|
-
applyTextStyle(label,
|
|
4242
|
-
label.textContent =
|
|
4243
|
-
|
|
4244
|
-
g.appendChild(entryG);
|
|
4245
|
-
if (isHorizontal) {
|
|
4246
|
-
const labelWidth = estimateTextWidth(
|
|
4247
|
-
entry.label,
|
|
4248
|
-
legend.labelStyle.fontSize,
|
|
4249
|
-
legend.labelStyle.fontWeight
|
|
4250
|
-
);
|
|
4251
|
-
const entryWidth = legend.swatchSize + legend.swatchGap + labelWidth + legend.entryGap;
|
|
4252
|
-
offsetX += entryWidth;
|
|
4253
|
-
} else {
|
|
4254
|
-
offsetY += legend.swatchSize + legend.entryGap;
|
|
4255
|
-
}
|
|
4126
|
+
applyTextStyle(label, mark.label.style);
|
|
4127
|
+
label.textContent = mark.label.text;
|
|
4128
|
+
g.appendChild(label);
|
|
4256
4129
|
}
|
|
4257
|
-
|
|
4130
|
+
return g;
|
|
4258
4131
|
}
|
|
4259
|
-
|
|
4260
|
-
|
|
4261
|
-
|
|
4262
|
-
|
|
4263
|
-
|
|
4264
|
-
|
|
4265
|
-
|
|
4266
|
-
|
|
4267
|
-
|
|
4268
|
-
|
|
4269
|
-
|
|
4270
|
-
|
|
4271
|
-
|
|
4272
|
-
|
|
4273
|
-
|
|
4274
|
-
|
|
4275
|
-
|
|
4276
|
-
|
|
4277
|
-
|
|
4278
|
-
const BRAND_LARGE = 16;
|
|
4132
|
+
function renderPointMark(mark, index2) {
|
|
4133
|
+
const circle = createSVGElement("circle");
|
|
4134
|
+
circle.setAttribute("data-mark-id", `point-${index2}`);
|
|
4135
|
+
circle.setAttribute("class", "oc-mark oc-mark-point");
|
|
4136
|
+
stampAnimationAttrs(circle, mark, index2);
|
|
4137
|
+
setAttrs(circle, {
|
|
4138
|
+
cx: mark.cx,
|
|
4139
|
+
cy: mark.cy,
|
|
4140
|
+
r: mark.r,
|
|
4141
|
+
fill: resolveMarkFill(mark.fill, currentGradientMap),
|
|
4142
|
+
stroke: mark.stroke,
|
|
4143
|
+
"stroke-width": mark.strokeWidth
|
|
4144
|
+
});
|
|
4145
|
+
if (mark.fillOpacity !== void 0) {
|
|
4146
|
+
circle.setAttribute("fill-opacity", String(mark.fillOpacity));
|
|
4147
|
+
}
|
|
4148
|
+
return circle;
|
|
4149
|
+
}
|
|
4150
|
+
function renderTextMark(mark, index2) {
|
|
4279
4151
|
const text = createSVGElement("text");
|
|
4152
|
+
text.setAttribute("data-mark-id", `textMark-${index2}`);
|
|
4153
|
+
text.setAttribute("class", "oc-mark oc-mark-text");
|
|
4154
|
+
stampAnimationAttrs(text, mark, index2);
|
|
4280
4155
|
setAttrs(text, {
|
|
4281
|
-
x:
|
|
4282
|
-
y:
|
|
4283
|
-
"
|
|
4284
|
-
"
|
|
4285
|
-
"font-size": BRAND_FONT_SIZE2,
|
|
4286
|
-
"text-anchor": "end",
|
|
4287
|
-
"fill-opacity": 0.55
|
|
4156
|
+
x: mark.x,
|
|
4157
|
+
y: mark.y,
|
|
4158
|
+
"font-size": mark.fontSize,
|
|
4159
|
+
"text-anchor": mark.textAnchor
|
|
4288
4160
|
});
|
|
4289
|
-
text.style.setProperty("fill", fill);
|
|
4290
|
-
|
|
4291
|
-
|
|
4292
|
-
|
|
4293
|
-
|
|
4294
|
-
|
|
4295
|
-
|
|
4296
|
-
|
|
4297
|
-
|
|
4298
|
-
|
|
4299
|
-
|
|
4300
|
-
|
|
4301
|
-
|
|
4302
|
-
|
|
4303
|
-
|
|
4304
|
-
|
|
4161
|
+
text.style.setProperty("fill", mark.fill);
|
|
4162
|
+
if (mark.fontWeight) {
|
|
4163
|
+
text.setAttribute("font-weight", String(mark.fontWeight));
|
|
4164
|
+
}
|
|
4165
|
+
if (mark.fontFamily) {
|
|
4166
|
+
text.setAttribute("font-family", mark.fontFamily);
|
|
4167
|
+
}
|
|
4168
|
+
if (mark.angle) {
|
|
4169
|
+
text.setAttribute("transform", `rotate(${mark.angle}, ${mark.x}, ${mark.y})`);
|
|
4170
|
+
}
|
|
4171
|
+
text.textContent = mark.text;
|
|
4172
|
+
return text;
|
|
4173
|
+
}
|
|
4174
|
+
function renderRuleMark(mark, index2) {
|
|
4175
|
+
const line = createSVGElement("line");
|
|
4176
|
+
line.setAttribute("data-mark-id", `rule-${index2}`);
|
|
4177
|
+
line.setAttribute("class", "oc-mark oc-mark-rule");
|
|
4178
|
+
stampAnimationAttrs(line, mark, index2);
|
|
4179
|
+
setAttrs(line, {
|
|
4180
|
+
x1: mark.x1,
|
|
4181
|
+
y1: mark.y1,
|
|
4182
|
+
x2: mark.x2,
|
|
4183
|
+
y2: mark.y2,
|
|
4184
|
+
stroke: mark.stroke,
|
|
4185
|
+
"stroke-width": mark.strokeWidth
|
|
4186
|
+
});
|
|
4187
|
+
if (mark.strokeDasharray) {
|
|
4188
|
+
line.setAttribute("stroke-dasharray", mark.strokeDasharray);
|
|
4189
|
+
}
|
|
4190
|
+
if (mark.opacity != null) {
|
|
4191
|
+
line.setAttribute("opacity", String(mark.opacity));
|
|
4192
|
+
}
|
|
4193
|
+
return line;
|
|
4194
|
+
}
|
|
4195
|
+
function renderTickMark(mark, index2) {
|
|
4196
|
+
const line = createSVGElement("line");
|
|
4197
|
+
line.setAttribute("data-mark-id", `tick-${index2}`);
|
|
4198
|
+
line.setAttribute("class", "oc-mark oc-mark-tick");
|
|
4199
|
+
stampAnimationAttrs(line, mark, index2);
|
|
4200
|
+
const half = mark.length / 2;
|
|
4201
|
+
if (mark.orient === "vertical") {
|
|
4202
|
+
setAttrs(line, {
|
|
4203
|
+
x1: mark.x,
|
|
4204
|
+
y1: mark.y - half,
|
|
4205
|
+
x2: mark.x,
|
|
4206
|
+
y2: mark.y + half,
|
|
4207
|
+
stroke: mark.stroke,
|
|
4208
|
+
"stroke-width": mark.strokeWidth
|
|
4209
|
+
});
|
|
4210
|
+
} else {
|
|
4211
|
+
setAttrs(line, {
|
|
4212
|
+
x1: mark.x - half,
|
|
4213
|
+
y1: mark.y,
|
|
4214
|
+
x2: mark.x + half,
|
|
4215
|
+
y2: mark.y,
|
|
4216
|
+
stroke: mark.stroke,
|
|
4217
|
+
"stroke-width": mark.strokeWidth
|
|
4218
|
+
});
|
|
4219
|
+
}
|
|
4220
|
+
if (mark.opacity != null) {
|
|
4221
|
+
line.setAttribute("opacity", String(mark.opacity));
|
|
4222
|
+
}
|
|
4223
|
+
return line;
|
|
4224
|
+
}
|
|
4225
|
+
registerMarkRenderer("line", renderLineMark);
|
|
4226
|
+
registerMarkRenderer("area", renderAreaMark);
|
|
4227
|
+
registerMarkRenderer("rect", renderRectMark);
|
|
4228
|
+
registerMarkRenderer("arc", renderArcMark);
|
|
4229
|
+
registerMarkRenderer("point", renderPointMark);
|
|
4230
|
+
registerMarkRenderer("textMark", renderTextMark);
|
|
4231
|
+
registerMarkRenderer("rule", renderRuleMark);
|
|
4232
|
+
registerMarkRenderer("tick", renderTickMark);
|
|
4233
|
+
function getMarkSeries(mark) {
|
|
4234
|
+
if (mark.type === "line" || mark.type === "area") {
|
|
4235
|
+
return mark.seriesKey;
|
|
4236
|
+
}
|
|
4237
|
+
if (mark.type === "arc") {
|
|
4238
|
+
return mark.aria.label.split(":")[0]?.trim();
|
|
4239
|
+
}
|
|
4240
|
+
if (mark.aria?.label) {
|
|
4241
|
+
const beforeColon = mark.aria.label.split(":")[0]?.trim();
|
|
4242
|
+
if (beforeColon) return beforeColon;
|
|
4243
|
+
}
|
|
4244
|
+
return void 0;
|
|
4245
|
+
}
|
|
4246
|
+
function renderMarks(parent, layout) {
|
|
4247
|
+
const g = createSVGElement("g");
|
|
4248
|
+
g.setAttribute("class", "oc-marks");
|
|
4249
|
+
for (let i = 0; i < layout.marks.length; i++) {
|
|
4250
|
+
const mark = layout.marks[i];
|
|
4251
|
+
const renderer = markRenderers[mark.type];
|
|
4252
|
+
if (!renderer) continue;
|
|
4253
|
+
const el = renderer(mark, i);
|
|
4254
|
+
if (mark.aria?.label) {
|
|
4255
|
+
el.setAttribute("aria-label", mark.aria.label);
|
|
4256
|
+
}
|
|
4257
|
+
const series = getMarkSeries(mark);
|
|
4258
|
+
if (series) {
|
|
4259
|
+
el.setAttribute("data-series", series);
|
|
4260
|
+
}
|
|
4261
|
+
if (currentAnimation?.enabled && mark.type === "rect") {
|
|
4262
|
+
const rect = mark;
|
|
4263
|
+
if (rect.stackGroup && rect.stackPos !== void 0) {
|
|
4264
|
+
el.setAttribute("data-stack-pos", String(rect.stackPos));
|
|
4265
|
+
el.style.setProperty(
|
|
4266
|
+
"--oc-stack-pos",
|
|
4267
|
+
String(rect.stackPos)
|
|
4268
|
+
);
|
|
4269
|
+
}
|
|
4270
|
+
}
|
|
4271
|
+
g.appendChild(el);
|
|
4272
|
+
}
|
|
4273
|
+
parent.appendChild(g);
|
|
4305
4274
|
}
|
|
4275
|
+
|
|
4276
|
+
// src/svg-renderer.ts
|
|
4277
|
+
var EASE_VAR_MAP = {
|
|
4278
|
+
smooth: "var(--oc-ease-smooth)",
|
|
4279
|
+
snappy: "var(--oc-ease-snappy)"
|
|
4280
|
+
};
|
|
4306
4281
|
function renderChartSVG(layout, container, opts) {
|
|
4307
4282
|
const { width, height } = layout.dimensions;
|
|
4308
4283
|
const animation = layout.animation;
|
|
4309
|
-
currentAnimation = animation;
|
|
4310
4284
|
const svg = createSVGElement("svg");
|
|
4311
4285
|
setAttrs(svg, {
|
|
4312
4286
|
viewBox: `0 0 ${width} ${height}`,
|
|
@@ -4354,7 +4328,7 @@ function renderChartSVG(layout, container, opts) {
|
|
|
4354
4328
|
fill: layout.theme.colors.background
|
|
4355
4329
|
});
|
|
4356
4330
|
svg.appendChild(bg);
|
|
4357
|
-
const clipId =
|
|
4331
|
+
const clipId = nextSvgId("oc-clip");
|
|
4358
4332
|
const defs = createSVGElement("defs");
|
|
4359
4333
|
const clipPath = createSVGElement("clipPath");
|
|
4360
4334
|
clipPath.setAttribute("id", clipId);
|
|
@@ -4367,38 +4341,41 @@ function renderChartSVG(layout, container, opts) {
|
|
|
4367
4341
|
});
|
|
4368
4342
|
clipPath.appendChild(clipRect);
|
|
4369
4343
|
defs.appendChild(clipPath);
|
|
4370
|
-
|
|
4344
|
+
const gradientMap = buildGradientDefs(layout.marks, defs);
|
|
4371
4345
|
svg.appendChild(defs);
|
|
4372
|
-
|
|
4373
|
-
|
|
4374
|
-
|
|
4375
|
-
|
|
4376
|
-
|
|
4377
|
-
(
|
|
4378
|
-
|
|
4379
|
-
|
|
4380
|
-
|
|
4381
|
-
const
|
|
4382
|
-
|
|
4383
|
-
|
|
4384
|
-
|
|
4385
|
-
|
|
4386
|
-
|
|
4387
|
-
|
|
4388
|
-
|
|
4389
|
-
|
|
4390
|
-
|
|
4391
|
-
|
|
4392
|
-
|
|
4393
|
-
|
|
4394
|
-
|
|
4395
|
-
|
|
4396
|
-
|
|
4397
|
-
|
|
4398
|
-
|
|
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();
|
|
4399
4378
|
}
|
|
4400
|
-
currentAnimation = void 0;
|
|
4401
|
-
currentGradientMap = /* @__PURE__ */ new Map();
|
|
4402
4379
|
container.appendChild(svg);
|
|
4403
4380
|
return svg;
|
|
4404
4381
|
}
|
|
@@ -5680,7 +5657,6 @@ function createChart(container, spec, options) {
|
|
|
5680
5657
|
let destroyed = false;
|
|
5681
5658
|
let isDragging = false;
|
|
5682
5659
|
let pendingRender = false;
|
|
5683
|
-
let resizeTimer = null;
|
|
5684
5660
|
let isFirstRender = true;
|
|
5685
5661
|
let cleanupAnimations = null;
|
|
5686
5662
|
let pendingResize = false;
|
|
@@ -6109,10 +6085,6 @@ function createChart(container, spec, options) {
|
|
|
6109
6085
|
pendingResize = false;
|
|
6110
6086
|
}
|
|
6111
6087
|
cancelAnimations(svgElement);
|
|
6112
|
-
if (resizeTimer !== null) {
|
|
6113
|
-
clearTimeout(resizeTimer);
|
|
6114
|
-
resizeTimer = null;
|
|
6115
|
-
}
|
|
6116
6088
|
if (cleanupTooltipEvents) {
|
|
6117
6089
|
cleanupTooltipEvents();
|
|
6118
6090
|
cleanupTooltipEvents = null;
|
|
@@ -6178,11 +6150,7 @@ function createChart(container, spec, options) {
|
|
|
6178
6150
|
render();
|
|
6179
6151
|
if (options?.responsive !== false) {
|
|
6180
6152
|
disconnectResize = observeResize(container, () => {
|
|
6181
|
-
|
|
6182
|
-
resizeTimer = setTimeout(() => {
|
|
6183
|
-
resizeTimer = null;
|
|
6184
|
-
resize();
|
|
6185
|
-
}, 100);
|
|
6153
|
+
resize();
|
|
6186
6154
|
});
|
|
6187
6155
|
}
|
|
6188
6156
|
return {
|
|
@@ -6534,7 +6502,12 @@ function renderCell(cell) {
|
|
|
6534
6502
|
import { compileSankey } from "@opendata-ai/openchart-engine";
|
|
6535
6503
|
|
|
6536
6504
|
// src/sankey-renderer.ts
|
|
6537
|
-
import {
|
|
6505
|
+
import {
|
|
6506
|
+
BRAND_FONT_SIZE as BRAND_FONT_SIZE3,
|
|
6507
|
+
BRAND_MIN_WIDTH as BRAND_MIN_WIDTH3,
|
|
6508
|
+
estimateTextWidth as estimateTextWidth4,
|
|
6509
|
+
wrapText as wrapText2
|
|
6510
|
+
} from "@opendata-ai/openchart-core";
|
|
6538
6511
|
import { clampStaggerDelay as clampStaggerDelay2 } from "@opendata-ai/openchart-engine";
|
|
6539
6512
|
var SVG_NS3 = "http://www.w3.org/2000/svg";
|
|
6540
6513
|
var XLINK_NS2 = "http://www.w3.org/1999/xlink";
|
|
@@ -6574,39 +6547,6 @@ function stampAnimationAttrs2(el, mark, fallbackIndex, animation) {
|
|
|
6574
6547
|
el.setAttribute("data-animation-index", String(idx));
|
|
6575
6548
|
el.style.setProperty("--oc-mark-index", String(idx));
|
|
6576
6549
|
}
|
|
6577
|
-
function wrapText2(text, fontSize, fontWeight, maxWidth) {
|
|
6578
|
-
if (maxWidth <= 0) return [text];
|
|
6579
|
-
const AVG_CHAR_WIDTH = 0.57;
|
|
6580
|
-
const WEIGHT_FACTORS = {
|
|
6581
|
-
100: 0.9,
|
|
6582
|
-
200: 0.92,
|
|
6583
|
-
300: 0.95,
|
|
6584
|
-
400: 1,
|
|
6585
|
-
500: 1.02,
|
|
6586
|
-
600: 1.05,
|
|
6587
|
-
700: 1.08,
|
|
6588
|
-
800: 1.1,
|
|
6589
|
-
900: 1.12
|
|
6590
|
-
};
|
|
6591
|
-
const weightFactor = WEIGHT_FACTORS[fontWeight] ?? 1;
|
|
6592
|
-
const charWidth = fontSize * AVG_CHAR_WIDTH * weightFactor;
|
|
6593
|
-
const maxChars = Math.floor(maxWidth / charWidth);
|
|
6594
|
-
if (text.length <= maxChars) return [text];
|
|
6595
|
-
const words = text.split(" ");
|
|
6596
|
-
const lines = [];
|
|
6597
|
-
let current = "";
|
|
6598
|
-
for (const word of words) {
|
|
6599
|
-
const candidate = current ? `${current} ${word}` : word;
|
|
6600
|
-
if (candidate.length > maxChars && current) {
|
|
6601
|
-
lines.push(current);
|
|
6602
|
-
current = word;
|
|
6603
|
-
} else {
|
|
6604
|
-
current = candidate;
|
|
6605
|
-
}
|
|
6606
|
-
}
|
|
6607
|
-
if (current) lines.push(current);
|
|
6608
|
-
return lines;
|
|
6609
|
-
}
|
|
6610
6550
|
function renderChromeElement2(parent, element, className, chromeKey) {
|
|
6611
6551
|
const text = createSVGElement2("text");
|
|
6612
6552
|
setAttrs2(text, { x: element.x, y: element.y });
|
|
@@ -6724,7 +6664,7 @@ function renderLegend2(parent, legend) {
|
|
|
6724
6664
|
for (let i = 0; i < legend.entries.length; i++) {
|
|
6725
6665
|
const entry = legend.entries[i];
|
|
6726
6666
|
if (isHorizontal && i > 0) {
|
|
6727
|
-
const labelWidth =
|
|
6667
|
+
const labelWidth = estimateTextWidth4(
|
|
6728
6668
|
entry.label,
|
|
6729
6669
|
legend.labelStyle.fontSize,
|
|
6730
6670
|
legend.labelStyle.fontWeight
|
|
@@ -6775,7 +6715,7 @@ function renderLegend2(parent, legend) {
|
|
|
6775
6715
|
entryG.appendChild(label);
|
|
6776
6716
|
g.appendChild(entryG);
|
|
6777
6717
|
if (isHorizontal) {
|
|
6778
|
-
const labelWidth =
|
|
6718
|
+
const labelWidth = estimateTextWidth4(
|
|
6779
6719
|
entry.label,
|
|
6780
6720
|
legend.labelStyle.fontSize,
|
|
6781
6721
|
legend.labelStyle.fontWeight
|