@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.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 = `oc-grad-${globalGradientCounter++}`;
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-renderer.ts
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 currentAnimation;
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 wrapText(text, fontSize, fontWeight, maxWidth, measureText) {
3459
- if (maxWidth <= 0) return [text];
3460
- const segments = text.split("\n");
3461
- if (segments.length > 1) {
3462
- return segments.flatMap(
3463
- (segment) => segment.length === 0 ? [""] : wrapText(segment, fontSize, fontWeight, maxWidth, measureText)
3464
- );
3465
- }
3466
- if (measureText) {
3467
- const textWidth = measureText(text, fontSize, fontWeight).width;
3468
- if (textWidth <= maxWidth) return [text];
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
- if (current) lines.push(current);
3514
- return lines;
3452
+ return xAxis.label ? 48 : 26;
3515
3453
  }
3516
- function renderChromeElement(parent, element, className, chromeKey, measureText) {
3517
- const text = createSVGElement("text");
3518
- setAttrs(text, { x: element.x, y: element.y });
3519
- applyTextStyle(text, element.style);
3520
- text.setAttribute("class", className);
3521
- text.setAttribute("data-chrome-key", chromeKey);
3522
- const lines = wrapText(
3523
- element.text,
3524
- element.style.fontSize,
3525
- element.style.fontWeight,
3526
- element.maxWidth,
3527
- measureText
3528
- );
3529
- if (lines.length === 1) {
3530
- text.textContent = element.text;
3531
- } else {
3532
- const lineHeight = element.style.fontSize * (element.style.lineHeight ?? 1.3);
3533
- for (let i = 0; i < lines.length; i++) {
3534
- const tspan = createSVGElement("tspan");
3535
- setAttrs(tspan, { x: element.x, dy: i === 0 ? 0 : lineHeight });
3536
- tspan.textContent = lines[i];
3537
- text.appendChild(tspan);
3538
- }
3539
- }
3540
- parent.appendChild(text);
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 renderChrome(parent, layout) {
3498
+ function renderAnnotation(parent, annotation, index2, bgColor) {
3543
3499
  const g = createSVGElement("g");
3544
- g.setAttribute("class", "oc-chrome");
3545
- const { chrome, measureText } = layout;
3546
- if (chrome.title) {
3547
- renderChromeElement(g, chrome.title, "oc-title", "title", measureText);
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 (chrome.footer) {
3573
- renderChromeElement(
3574
- g,
3575
- { ...chrome.footer, y: bottomOffset + chrome.footer.y },
3576
- "oc-footer",
3577
- "footer",
3578
- measureText
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
- parent.appendChild(g);
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-axis-line");
3522
+ line.setAttribute("class", "oc-annotation-line");
3590
3523
  setAttrs(line, {
3591
- x1: axis.start.x,
3592
- y1: axis.start.y,
3593
- x2: axis.end.x,
3594
- y2: axis.end.y,
3595
- stroke: layout.theme.colors.axis,
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
- for (const tick of axis.ticks) {
3601
- if (orientation === "x") {
3602
- const label = createSVGElement("text");
3603
- label.setAttribute("class", "oc-axis-tick");
3604
- if (axis.tickAngle && Math.abs(axis.tickAngle) > 10) {
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
- setAttrs(label, {
3616
- x: tick.position,
3617
- y: area.y + area.height + 14,
3618
- "text-anchor": "middle"
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
- for (const gridline of axis.gridlines) {
3639
- const gl = createSVGElement("line");
3640
- gl.setAttribute("class", "oc-gridline");
3641
- if (orientation === "y") {
3642
- setAttrs(gl, {
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 = estimateTextWidth(
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
- var markRenderers = {};
3711
- function registerMarkRenderer(type, renderer) {
3712
- markRenderers[type] = renderer;
3713
- }
3714
- function renderLineMark(mark, index2) {
3715
- const g = createSVGElement("g");
3716
- g.setAttribute("data-mark-id", `line-${mark.seriesKey ?? index2}`);
3717
- g.setAttribute("class", "oc-mark oc-mark-line");
3718
- stampAnimationAttrs(g, mark, index2);
3719
- if (mark.points.length > 1) {
3720
- const path = createSVGElement("path");
3721
- const d = mark.path ?? mark.points.map((p, i) => `${i === 0 ? "M" : "L"}${p.x},${p.y}`).join(" ");
3722
- setAttrs(path, {
3723
- d,
3724
- fill: "none",
3725
- stroke: mark.stroke,
3726
- "stroke-width": mark.strokeWidth
3727
- });
3728
- if (mark.strokeDasharray) {
3729
- path.setAttribute("stroke-dasharray", mark.strokeDasharray);
3730
- }
3731
- if (mark.opacity != null) {
3732
- path.setAttribute("opacity", String(mark.opacity));
3733
- }
3734
- g.appendChild(path);
3735
- }
3736
- if (mark.label?.visible) {
3737
- const label = createSVGElement("text");
3738
- label.setAttribute("class", "oc-mark-label");
3739
- if (mark.seriesKey) {
3740
- label.setAttribute("data-series", mark.seriesKey);
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
- if (mark.stroke) {
3808
- rect.setAttribute("stroke", mark.stroke);
3809
- }
3810
- if (mark.strokeWidth) {
3811
- rect.setAttribute("stroke-width", String(mark.strokeWidth));
3812
- }
3813
- if (mark.cornerRadius) {
3814
- setAttrs(rect, { rx: mark.cornerRadius, ry: mark.cornerRadius });
3815
- }
3816
- g.appendChild(rect);
3817
- if (mark.label?.visible) {
3818
- const label = createSVGElement("text");
3819
- label.setAttribute("class", "oc-mark-label");
3820
- setAttrs(label, { x: mark.label.x, y: mark.label.y });
3821
- applyTextStyle(label, mark.label.style);
3822
- label.textContent = mark.label.text;
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
- function renderTextMark(mark, index2) {
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.setAttribute("data-mark-id", `textMark-${index2}`);
3875
- text.setAttribute("class", "oc-mark oc-mark-text");
3876
- stampAnimationAttrs(text, mark, index2);
3877
- setAttrs(text, {
3878
- x: mark.x,
3879
- y: mark.y,
3880
- "font-size": mark.fontSize,
3881
- "text-anchor": mark.textAnchor
3882
- });
3883
- text.style.setProperty("fill", mark.fill);
3884
- if (mark.fontWeight) {
3885
- text.setAttribute("font-weight", String(mark.fontWeight));
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
- setAttrs(line, {
3934
- x1: mark.x - half,
3935
- y1: mark.y,
3936
- x2: mark.x + half,
3937
- y2: mark.y,
3938
- stroke: mark.stroke,
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
- if (annotation.label?.visible) {
4088
- if (annotation.label.connector) {
4089
- const c2 = annotation.label.connector;
4090
- if (c2.style === "curve") {
4091
- renderCurvedArrow(g, c2.from, c2.to, c2.stroke);
4092
- } else {
4093
- const connector = createSVGElement("line");
4094
- connector.setAttribute("class", "oc-annotation-connector");
4095
- setAttrs(connector, {
4096
- x1: c2.from.x,
4097
- y1: c2.from.y,
4098
- x2: c2.to.x,
4099
- y2: c2.to.y,
4100
- stroke: c2.stroke,
4101
- "stroke-width": 1,
4102
- "stroke-opacity": 0.5
4103
- });
4104
- g.appendChild(connector);
4105
- }
4106
- }
4107
- const text = createSVGElement("text");
4108
- text.setAttribute("class", "oc-annotation-label");
4109
- setAttrs(text, { x: annotation.label.x, y: annotation.label.y });
4110
- applyTextStyle(text, annotation.label.style);
4111
- const lines = annotation.label.text.split("\n");
4112
- const fontSize = annotation.label.style.fontSize ?? 12;
4113
- const lineHeight = fontSize * (annotation.label.style.lineHeight ?? 1.3);
4114
- const isMultiLine = lines.length > 1;
4115
- if (isMultiLine) {
4116
- text.setAttribute("text-anchor", "middle");
4117
- for (let i = 0; i < lines.length; i++) {
4118
- const tspan = createSVGElement("tspan");
4119
- setAttrs(tspan, { x: annotation.label.x, dy: i === 0 ? 0 : lineHeight });
4120
- tspan.textContent = lines[i];
4121
- text.appendChild(tspan);
4122
- }
4123
- } else {
4124
- text.textContent = annotation.label.text;
4125
- }
4126
- if (annotation.label.background) {
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 = estimateTextWidth(
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, legend.labelStyle);
4242
- label.textContent = entry.label;
4243
- entryG.appendChild(label);
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
- parent.appendChild(g);
4130
+ return g;
4258
4131
  }
4259
- var BRAND_URL = "https://tryopendata.ai";
4260
- var XLINK_NS = "http://www.w3.org/1999/xlink";
4261
- function renderBrand(parent, layout) {
4262
- if (layout.dimensions.width < BRAND_MIN_WIDTH2) return;
4263
- const { width } = layout.dimensions;
4264
- const padding = layout.theme.spacing.padding;
4265
- const rightEdge = width - padding;
4266
- const fill = layout.theme.colors.axis;
4267
- const { chrome } = layout;
4268
- const xAxisExtent = computeXAxisExtent(layout);
4269
- const bottomOffset = layout.area.y + layout.area.height + xAxisExtent;
4270
- const firstBottom = chrome.source ?? chrome.byline ?? chrome.footer;
4271
- const chromeY = firstBottom ? bottomOffset + firstBottom.y : bottomOffset + layout.theme.spacing.chartToFooter;
4272
- const a2 = createSVGElement("a");
4273
- a2.setAttribute("href", BRAND_URL);
4274
- a2.setAttributeNS(XLINK_NS, "xlink:href", BRAND_URL);
4275
- a2.setAttribute("target", "_blank");
4276
- a2.setAttribute("rel", "noopener");
4277
- a2.setAttribute("class", "oc-chrome-ref");
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: rightEdge,
4282
- y: chromeY + BRAND_LARGE,
4283
- "dominant-baseline": "alphabetic",
4284
- "font-family": layout.theme.fonts.family,
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
- const trySpan = createSVGElement("tspan");
4291
- trySpan.setAttribute("font-weight", "500");
4292
- trySpan.textContent = "try";
4293
- text.appendChild(trySpan);
4294
- const openDataSpan = createSVGElement("tspan");
4295
- openDataSpan.setAttribute("font-weight", "600");
4296
- openDataSpan.setAttribute("font-size", String(BRAND_LARGE));
4297
- openDataSpan.textContent = "OpenData";
4298
- text.appendChild(openDataSpan);
4299
- const aiSpan = createSVGElement("tspan");
4300
- aiSpan.setAttribute("font-weight", "500");
4301
- aiSpan.textContent = ".ai";
4302
- text.appendChild(aiSpan);
4303
- a2.appendChild(text);
4304
- parent.appendChild(a2);
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 = `oc-clip-${Math.random().toString(36).slice(2, 8)}`;
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
- currentGradientMap = buildGradientDefs(layout.marks, defs);
4344
+ const gradientMap = buildGradientDefs(layout.marks, defs);
4371
4345
  svg.appendChild(defs);
4372
- renderAxes(svg, layout);
4373
- const clippedGroup = createSVGElement("g");
4374
- clippedGroup.setAttribute("clip-path", `url(#${clipId})`);
4375
- renderMarks(clippedGroup, layout);
4376
- const hasLineOrAreaWithDataPoints = layout.marks.some(
4377
- (m2) => (m2.type === "line" || m2.type === "area") && m2.dataPoints && m2.dataPoints.length > 0
4378
- );
4379
- const hasPointMarks = layout.marks.some((m2) => m2.type === "point");
4380
- if (hasLineOrAreaWithDataPoints && !hasPointMarks) {
4381
- const overlay = createSVGElement("rect");
4382
- setAttrs(overlay, {
4383
- x: layout.area.x,
4384
- y: layout.area.y,
4385
- width: layout.area.width,
4386
- height: layout.area.height,
4387
- fill: "transparent"
4388
- });
4389
- overlay.setAttribute("class", "oc-voronoi-overlay");
4390
- overlay.setAttribute("data-voronoi-overlay", "true");
4391
- clippedGroup.appendChild(overlay);
4392
- }
4393
- svg.appendChild(clippedGroup);
4394
- renderAnnotations(svg, layout);
4395
- renderLegend(svg, layout.legend);
4396
- renderChrome(svg, layout);
4397
- if (layout.watermark) {
4398
- renderBrand(svg, layout);
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
- if (resizeTimer !== null) clearTimeout(resizeTimer);
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 { BRAND_FONT_SIZE as BRAND_FONT_SIZE3, BRAND_MIN_WIDTH as BRAND_MIN_WIDTH3, estimateTextWidth as estimateTextWidth2 } from "@opendata-ai/openchart-core";
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 = estimateTextWidth2(
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 = estimateTextWidth2(
6718
+ const labelWidth = estimateTextWidth4(
6779
6719
  entry.label,
6780
6720
  legend.labelStyle.fontSize,
6781
6721
  legend.labelStyle.fontWeight