pptx-react-viewer 1.18.0 → 1.19.1

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.
Files changed (99) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/dist/{Model3DScene-GVWQ6SOM.js → Model3DScene-3RYDRQOD.js} +3 -3
  3. package/dist/Model3DScene-3RYDRQOD.js.br +0 -0
  4. package/dist/Model3DScene-3RYDRQOD.js.gz +0 -0
  5. package/dist/{Model3DScene-JGVLIDLD.mjs → Model3DScene-SLGODOXT.mjs} +2 -2
  6. package/dist/Model3DScene-SLGODOXT.mjs.br +0 -0
  7. package/dist/Model3DScene-SLGODOXT.mjs.gz +0 -0
  8. package/dist/PowerPointViewer-C4d7SKZf.d.ts.map +1 -1
  9. package/dist/{SurfaceChart3DScene-ZOTOHOIE.js → SurfaceChart3DScene-3UZ5HZ2W.js} +3 -3
  10. package/dist/SurfaceChart3DScene-3UZ5HZ2W.js.br +0 -0
  11. package/dist/SurfaceChart3DScene-3UZ5HZ2W.js.gz +0 -0
  12. package/dist/{SurfaceChart3DScene-DBWSDSKI.mjs → SurfaceChart3DScene-YL4QD3O5.mjs} +2 -2
  13. package/dist/SurfaceChart3DScene-YL4QD3O5.mjs.br +0 -0
  14. package/dist/SurfaceChart3DScene-YL4QD3O5.mjs.gz +0 -0
  15. package/dist/{chunk-Z4MDCPAY.mjs → chunk-3TRQKGBU.mjs} +26 -731
  16. package/dist/chunk-3TRQKGBU.mjs.br +0 -0
  17. package/dist/chunk-3TRQKGBU.mjs.gz +0 -0
  18. package/dist/{chunk-SBNTEQUA.mjs → chunk-3ZGCM6RC.mjs} +20269 -12232
  19. package/dist/chunk-3ZGCM6RC.mjs.br +0 -0
  20. package/dist/chunk-3ZGCM6RC.mjs.gz +0 -0
  21. package/dist/{chunk-73ST7AC6.js → chunk-7JYQRDRI.js} +6962 -6661
  22. package/dist/chunk-7JYQRDRI.js.br +0 -0
  23. package/dist/chunk-7JYQRDRI.js.gz +0 -0
  24. package/dist/{chunk-EXDUEXTO.js → chunk-AD3EAEY6.js} +20282 -12231
  25. package/dist/chunk-AD3EAEY6.js.br +0 -0
  26. package/dist/chunk-AD3EAEY6.js.gz +0 -0
  27. package/dist/{chunk-HBXPK572.js → chunk-G5GATDE4.js} +2969 -746
  28. package/dist/chunk-G5GATDE4.js.br +0 -0
  29. package/dist/chunk-G5GATDE4.js.gz +0 -0
  30. package/dist/{chunk-JDRC3PSY.js → chunk-H5V24JVW.js} +470 -1186
  31. package/dist/chunk-H5V24JVW.js.br +0 -0
  32. package/dist/chunk-H5V24JVW.js.gz +0 -0
  33. package/dist/{chunk-KMLIWCNL.mjs → chunk-TG2ZYU7Y.mjs} +5782 -5480
  34. package/dist/chunk-TG2ZYU7Y.mjs.br +0 -0
  35. package/dist/chunk-TG2ZYU7Y.mjs.gz +0 -0
  36. package/dist/{chunk-HHTZQHKZ.mjs → chunk-VOU7OCQM.mjs} +2922 -722
  37. package/dist/chunk-VOU7OCQM.mjs.br +0 -0
  38. package/dist/chunk-VOU7OCQM.mjs.gz +0 -0
  39. package/dist/dist-3YRZ26TL.mjs +2 -0
  40. package/dist/dist-3YRZ26TL.mjs.br +0 -0
  41. package/dist/dist-3YRZ26TL.mjs.gz +0 -0
  42. package/dist/{dist-KRRXYUGI.js → dist-M2P6HBG4.js} +602 -546
  43. package/dist/dist-M2P6HBG4.js.br +0 -0
  44. package/dist/dist-M2P6HBG4.js.gz +0 -0
  45. package/dist/hooks-unstable.d.ts +523 -49
  46. package/dist/hooks-unstable.d.ts.map +1 -1
  47. package/dist/hooks-unstable.js +77 -77
  48. package/dist/hooks-unstable.js.br +0 -0
  49. package/dist/hooks-unstable.js.gz +0 -0
  50. package/dist/hooks-unstable.mjs +3 -3
  51. package/dist/hooks-unstable.mjs.br +0 -0
  52. package/dist/hooks-unstable.mjs.gz +0 -0
  53. package/dist/index.d.ts +417 -21
  54. package/dist/index.js +18 -18
  55. package/dist/index.js.br +0 -0
  56. package/dist/index.js.gz +0 -0
  57. package/dist/index.mjs +4 -4
  58. package/dist/index.mjs.br +0 -0
  59. package/dist/index.mjs.gz +0 -0
  60. package/dist/pptx-viewer.css +2 -2
  61. package/dist/pptx-viewer.css.br +0 -0
  62. package/dist/pptx-viewer.css.gz +0 -0
  63. package/dist/viewer/index.d.ts +509 -27
  64. package/dist/viewer/index.js +20 -20
  65. package/dist/viewer/index.js.br +0 -0
  66. package/dist/viewer/index.js.gz +0 -0
  67. package/dist/viewer/index.mjs +4 -4
  68. package/dist/viewer/index.mjs.br +0 -0
  69. package/dist/viewer/index.mjs.gz +0 -0
  70. package/package.json +12 -12
  71. package/dist/Model3DScene-GVWQ6SOM.js.br +0 -0
  72. package/dist/Model3DScene-GVWQ6SOM.js.gz +0 -0
  73. package/dist/Model3DScene-JGVLIDLD.mjs.br +0 -0
  74. package/dist/Model3DScene-JGVLIDLD.mjs.gz +0 -0
  75. package/dist/SurfaceChart3DScene-DBWSDSKI.mjs.br +0 -0
  76. package/dist/SurfaceChart3DScene-DBWSDSKI.mjs.gz +0 -0
  77. package/dist/SurfaceChart3DScene-ZOTOHOIE.js.br +0 -0
  78. package/dist/SurfaceChart3DScene-ZOTOHOIE.js.gz +0 -0
  79. package/dist/chunk-73ST7AC6.js.br +0 -0
  80. package/dist/chunk-73ST7AC6.js.gz +0 -0
  81. package/dist/chunk-EXDUEXTO.js.br +0 -0
  82. package/dist/chunk-EXDUEXTO.js.gz +0 -0
  83. package/dist/chunk-HBXPK572.js.br +0 -0
  84. package/dist/chunk-HBXPK572.js.gz +0 -0
  85. package/dist/chunk-HHTZQHKZ.mjs.br +0 -0
  86. package/dist/chunk-HHTZQHKZ.mjs.gz +0 -0
  87. package/dist/chunk-JDRC3PSY.js.br +0 -0
  88. package/dist/chunk-JDRC3PSY.js.gz +0 -0
  89. package/dist/chunk-KMLIWCNL.mjs.br +0 -0
  90. package/dist/chunk-KMLIWCNL.mjs.gz +0 -0
  91. package/dist/chunk-SBNTEQUA.mjs.br +0 -0
  92. package/dist/chunk-SBNTEQUA.mjs.gz +0 -0
  93. package/dist/chunk-Z4MDCPAY.mjs.br +0 -0
  94. package/dist/chunk-Z4MDCPAY.mjs.gz +0 -0
  95. package/dist/dist-KRRXYUGI.js.br +0 -0
  96. package/dist/dist-KRRXYUGI.js.gz +0 -0
  97. package/dist/dist-N6PAPXK6.mjs +0 -2
  98. package/dist/dist-N6PAPXK6.mjs.br +0 -0
  99. package/dist/dist-N6PAPXK6.mjs.gz +0 -0
@@ -1,4 +1,4 @@
1
- import { MIN_ELEMENT_SIZE, guideEmuToPx, THEME_COLOR_SCHEME_KEYS, hasShapeProperties, hasTextProperties, createChartElement, getShapeClipPath, getAdjustmentAwareShapeClipPath, getShapeClipPathFromPreset, getCloudPathForRendering, hslToRgb, PRESET_COLOR_MAP, isImageLikeElement, applyDrawingColorTransforms } from './chunk-SBNTEQUA.mjs';
1
+ import { MIN_ELEMENT_SIZE, guideEmuToPx, THEME_COLOR_SCHEME_KEYS, hasShapeProperties, hasTextProperties, createChartElement, getShapeClipPath, getAdjustmentAwareShapeClipPath, getShapeClipPathFromPreset, getCloudPathForRendering, hslToRgb, PRESET_COLOR_MAP, isImageLikeElement, applyDrawingColorTransforms } from './chunk-3ZGCM6RC.mjs';
2
2
  import DOMPurify from 'dompurify';
3
3
 
4
4
  var defaultThemeColors = {
@@ -196,6 +196,18 @@ function collectImagePaths(slides) {
196
196
  });
197
197
  }
198
198
  }
199
+ if (el.type === "model3d") {
200
+ const model = el;
201
+ if (model.modelPath && !model.modelData && !isExternalUrl(model.modelPath)) {
202
+ paths.add(model.modelPath);
203
+ refs.push({ element: el, field: "modelData", path: model.modelPath });
204
+ }
205
+ const posterNeedsResolution = model.posterImage ? !isExternalUrl(model.posterImage) : !model.imageData;
206
+ if (model.imagePath && posterNeedsResolution && !isExternalUrl(model.imagePath)) {
207
+ paths.add(model.imagePath);
208
+ refs.push({ element: el, field: "posterImage", path: model.imagePath });
209
+ }
210
+ }
199
211
  if (el.type === "group" && el.children?.length) {
200
212
  walkElements(el.children);
201
213
  }
@@ -447,7 +459,7 @@ function getPatternSvg(preset, fg, bg) {
447
459
  const svg = (w, h, inner) => `<svg xmlns="${xmlns}" width="${w}" height="${h}">${inner}</svg>`;
448
460
  const rect = (x, y, w, h, fill) => `<rect x="${x}" y="${y}" width="${w}" height="${h}" fill="${fill}"/>`;
449
461
  const bgRect = (w, h) => rect(0, 0, w, h, bg);
450
- const line = (x1, y1, x2, y2, stroke, sw, extra = "") => `<line x1="${x1}" y1="${y1}" x2="${x2}" y2="${y2}" stroke="${stroke}" stroke-width="${sw}"${extra}/>`;
462
+ const line2 = (x1, y1, x2, y2, stroke, sw, extra = "") => `<line x1="${x1}" y1="${y1}" x2="${x2}" y2="${y2}" stroke="${stroke}" stroke-width="${sw}"${extra}/>`;
451
463
  const circle = (cx, cy, r, fill) => `<circle cx="${cx}" cy="${cy}" r="${r}" fill="${fill}"/>`;
452
464
  switch (preset) {
453
465
  // ── Percentage fills ──────────────────────────────────────────────
@@ -526,61 +538,61 @@ function getPatternSvg(preset, fg, bg) {
526
538
  return svg(
527
539
  s,
528
540
  s,
529
- bgRect(s, s) + line(0, 0, s, s, fg, 2) + line(-s, 0, 0, s, fg, 2) + line(s, 0, s * 2, s, fg, 2)
541
+ bgRect(s, s) + line2(0, 0, s, s, fg, 2) + line2(-s, 0, 0, s, fg, 2) + line2(s, 0, s * 2, s, fg, 2)
530
542
  );
531
543
  case "ltDnDiag":
532
544
  return svg(
533
545
  s,
534
546
  s,
535
- bgRect(s, s) + line(0, 0, s, s, fg, 1) + line(-s, 0, 0, s, fg, 1) + line(s, 0, s * 2, s, fg, 1)
547
+ bgRect(s, s) + line2(0, 0, s, s, fg, 1) + line2(-s, 0, 0, s, fg, 1) + line2(s, 0, s * 2, s, fg, 1)
536
548
  );
537
549
  case "dkDnDiag":
538
550
  return svg(
539
551
  s,
540
552
  s,
541
- bgRect(s, s) + line(0, 0, s, s, fg, 3) + line(-s, 0, 0, s, fg, 3) + line(s, 0, s * 2, s, fg, 3)
553
+ bgRect(s, s) + line2(0, 0, s, s, fg, 3) + line2(-s, 0, 0, s, fg, 3) + line2(s, 0, s * 2, s, fg, 3)
542
554
  );
543
555
  case "wdDnDiag":
544
556
  return svg(
545
557
  12,
546
558
  12,
547
- bgRect(12, 12) + line(0, 0, 12, 12, fg, 4) + line(-12, 0, 0, 12, fg, 4) + line(12, 0, 24, 12, fg, 4)
559
+ bgRect(12, 12) + line2(0, 0, 12, 12, fg, 4) + line2(-12, 0, 0, 12, fg, 4) + line2(12, 0, 24, 12, fg, 4)
548
560
  );
549
561
  case "dashDnDiag":
550
- return svg(s, s, bgRect(s, s) + line(0, 0, s, s, fg, 1, ' stroke-dasharray="4,4"'));
562
+ return svg(s, s, bgRect(s, s) + line2(0, 0, s, s, fg, 1, ' stroke-dasharray="4,4"'));
551
563
  // ── Diagonal lines (up) ──────────────────────────────────────────
552
564
  case "upDiag":
553
565
  return svg(
554
566
  s,
555
567
  s,
556
- bgRect(s, s) + line(0, s, s, 0, fg, 2) + line(-s, s, 0, 0, fg, 2) + line(s, s, s * 2, 0, fg, 2)
568
+ bgRect(s, s) + line2(0, s, s, 0, fg, 2) + line2(-s, s, 0, 0, fg, 2) + line2(s, s, s * 2, 0, fg, 2)
557
569
  );
558
570
  case "ltUpDiag":
559
571
  return svg(
560
572
  s,
561
573
  s,
562
- bgRect(s, s) + line(0, s, s, 0, fg, 1) + line(-s, s, 0, 0, fg, 1) + line(s, s, s * 2, 0, fg, 1)
574
+ bgRect(s, s) + line2(0, s, s, 0, fg, 1) + line2(-s, s, 0, 0, fg, 1) + line2(s, s, s * 2, 0, fg, 1)
563
575
  );
564
576
  case "dkUpDiag":
565
577
  return svg(
566
578
  s,
567
579
  s,
568
- bgRect(s, s) + line(0, s, s, 0, fg, 3) + line(-s, s, 0, 0, fg, 3) + line(s, s, s * 2, 0, fg, 3)
580
+ bgRect(s, s) + line2(0, s, s, 0, fg, 3) + line2(-s, s, 0, 0, fg, 3) + line2(s, s, s * 2, 0, fg, 3)
569
581
  );
570
582
  case "wdUpDiag":
571
583
  return svg(
572
584
  12,
573
585
  12,
574
- bgRect(12, 12) + line(0, 12, 12, 0, fg, 4) + line(-12, 12, 0, 0, fg, 4) + line(12, 12, 24, 0, fg, 4)
586
+ bgRect(12, 12) + line2(0, 12, 12, 0, fg, 4) + line2(-12, 12, 0, 0, fg, 4) + line2(12, 12, 24, 0, fg, 4)
575
587
  );
576
588
  case "dashUpDiag":
577
- return svg(s, s, bgRect(s, s) + line(0, s, s, 0, fg, 1, ' stroke-dasharray="4,4"'));
589
+ return svg(s, s, bgRect(s, s) + line2(0, s, s, 0, fg, 1, ' stroke-dasharray="4,4"'));
578
590
  // ── Diagonal cross ───────────────────────────────────────────────
579
591
  case "diagCross":
580
592
  return svg(
581
593
  s,
582
594
  s,
583
- bgRect(s, s) + line(0, 0, s, s, fg, 1) + line(-s, 0, 0, s, fg, 1) + line(s, 0, s * 2, s, fg, 1) + line(0, s, s, 0, fg, 1) + line(-s, s, 0, 0, fg, 1) + line(s, s, s * 2, 0, fg, 1)
595
+ bgRect(s, s) + line2(0, 0, s, s, fg, 1) + line2(-s, 0, 0, s, fg, 1) + line2(s, 0, s * 2, s, fg, 1) + line2(0, s, s, 0, fg, 1) + line2(-s, s, 0, 0, fg, 1) + line2(s, s, s * 2, 0, fg, 1)
584
596
  );
585
597
  // ── Checkerboard ─────────────────────────────────────────────────
586
598
  case "smCheck":
@@ -622,7 +634,7 @@ function getPatternSvg(preset, fg, bg) {
622
634
  return svg(
623
635
  s,
624
636
  s,
625
- bgRect(s, s) + line(0, 4, 4, 0, fg, 1) + line(4, s, s, 4, fg, 1) + line(0, s, s, 0, fg, 1)
637
+ bgRect(s, s) + line2(0, 4, 4, 0, fg, 1) + line2(4, s, s, 4, fg, 1) + line2(0, s, s, 0, fg, 1)
626
638
  );
627
639
  // ── Diamond ──────────────────────────────────────────────────────
628
640
  case "solidDmnd":
@@ -631,7 +643,7 @@ function getPatternSvg(preset, fg, bg) {
631
643
  return svg(
632
644
  s,
633
645
  s,
634
- bgRect(s, s) + line(4, 0, s, 4, fg, 1) + line(s, 4, 4, s, fg, 1) + line(4, s, 0, 4, fg, 1) + line(0, 4, 4, 0, fg, 1)
646
+ bgRect(s, s) + line2(4, 0, s, 4, fg, 1) + line2(s, 4, 4, s, fg, 1) + line2(4, s, 0, 4, fg, 1) + line2(0, 4, 4, 0, fg, 1)
635
647
  );
636
648
  case "dotDmnd":
637
649
  return svg(
@@ -656,21 +668,21 @@ function getPatternSvg(preset, fg, bg) {
656
668
  return svg(
657
669
  s,
658
670
  s,
659
- bgRect(s, s) + line(0, 0, 4, 4, fg, 1.5) + line(4, 4, 0, s, fg, 1.5) + line(s, 0, 4, 4, fg, 1.5) + line(4, 4, s, s, fg, 1.5) + line(4, 0, s, 4, fg, 1.5) + line(0, 4, 4, s, fg, 1.5)
671
+ bgRect(s, s) + line2(0, 0, 4, 4, fg, 1.5) + line2(4, 4, 0, s, fg, 1.5) + line2(s, 0, 4, 4, fg, 1.5) + line2(4, 4, s, s, fg, 1.5) + line2(4, 0, s, 4, fg, 1.5) + line2(0, 4, 4, s, fg, 1.5)
660
672
  );
661
673
  // ── Divot ────────────────────────────────────────────────────────
662
674
  case "divot":
663
675
  return svg(
664
676
  s,
665
677
  s,
666
- bgRect(s, s) + line(2, 1, 2, 3, fg, 1) + line(1, 2, 3, 2, fg, 1) + line(6, 5, 6, 7, fg, 1) + line(5, 6, 7, 6, fg, 1)
678
+ bgRect(s, s) + line2(2, 1, 2, 3, fg, 1) + line2(1, 2, 3, 2, fg, 1) + line2(6, 5, 6, 7, fg, 1) + line2(5, 6, 7, 6, fg, 1)
667
679
  );
668
680
  // ── Shingle ──────────────────────────────────────────────────────
669
681
  case "shingle":
670
682
  return svg(
671
683
  s,
672
684
  s,
673
- bgRect(s, s) + line(0, 0, s, s, fg, 1) + line(0, s, 4, 4, fg, 1) + rect(0, 7, s, 1, fg)
685
+ bgRect(s, s) + line2(0, 0, s, s, fg, 1) + line2(0, s, 4, 4, fg, 1) + rect(0, 7, s, 1, fg)
674
686
  );
675
687
  // ── Wave ─────────────────────────────────────────────────────────
676
688
  case "wave":
@@ -1357,11 +1369,33 @@ function buildReflectionCssValue(distance, startOpacity, endOpacity, fadeLength,
1357
1369
  function getDuotoneFilterId(elementId) {
1358
1370
  return `dag-duotone-${elementId}`;
1359
1371
  }
1360
- function clamp(v, lo, hi) {
1372
+ function clamp(value, minimum, maximum) {
1373
+ return Math.min(maximum, Math.max(minimum, value));
1374
+ }
1375
+ function buildImageBiLevelTable(threshold) {
1376
+ const cutoff = clamp(threshold, 0, 100);
1377
+ return Array.from({ length: 101 }, (_, index) => index >= cutoff ? "1" : "0").join(" ");
1378
+ }
1379
+ function buildImageLuminanceTransfer(amount) {
1380
+ const normalized = clamp(amount / 100, -1, 1);
1381
+ return normalized >= 0 ? { slope: 1 - normalized, intercept: normalized } : { slope: 1 + normalized, intercept: 0 };
1382
+ }
1383
+ function clamp2(v, lo, hi) {
1361
1384
  return v < lo ? lo : v > hi ? hi : v;
1362
1385
  }
1363
1386
  function normalizeRadius01(radius) {
1364
- return clamp(radius / 100, 0, 1);
1387
+ return clamp2(radius / 100, 0, 1);
1388
+ }
1389
+ function hexToRgbUnit2(hex) {
1390
+ const clean = hex.replace(/^#/u, "");
1391
+ const r = parseInt(clean.substring(0, 2), 16) / 255;
1392
+ const g = parseInt(clean.substring(2, 4), 16) / 255;
1393
+ const b = parseInt(clean.substring(4, 6), 16) / 255;
1394
+ return {
1395
+ r: Number.isFinite(r) ? r : 0,
1396
+ g: Number.isFinite(g) ? g : 0,
1397
+ b: Number.isFinite(b) ? b : 0
1398
+ };
1365
1399
  }
1366
1400
  function getEffects(element) {
1367
1401
  if (!isImageLikeElement(element)) {
@@ -1452,10 +1486,6 @@ function getImageFilterCss(element, options) {
1452
1486
  filters.push(buildSimpleArtisticCss(effects.artisticEffect, radius));
1453
1487
  }
1454
1488
  }
1455
- if (typeof effects.biLevel === "number") {
1456
- const thresh = clamp(effects.biLevel, 0, 100);
1457
- filters.push(`grayscale(100%) contrast(1000%) brightness(${thresh}%)`);
1458
- }
1459
1489
  return filters.length > 0 ? filters.join(" ") : void 0;
1460
1490
  }
1461
1491
  var getImageEffectsFilter = getImageFilterCss;
@@ -1526,10 +1556,257 @@ function getImageEffectsOpacity(element) {
1526
1556
  return void 0;
1527
1557
  }
1528
1558
  if (typeof effects.alphaModFix === "number") {
1529
- return clamp(effects.alphaModFix / 100, 0, 1);
1559
+ return clamp2(effects.alphaModFix / 100, 0, 1);
1530
1560
  }
1531
1561
  return void 0;
1532
1562
  }
1563
+ var GRAYSCALE_LUMINANCE_MATRIX = [
1564
+ 0.2126,
1565
+ 0.7152,
1566
+ 0.0722,
1567
+ 0,
1568
+ 0,
1569
+ 0.2126,
1570
+ 0.7152,
1571
+ 0.0722,
1572
+ 0,
1573
+ 0,
1574
+ 0.2126,
1575
+ 0.7152,
1576
+ 0.0722,
1577
+ 0,
1578
+ 0,
1579
+ 0,
1580
+ 0,
1581
+ 0,
1582
+ 1,
1583
+ 0
1584
+ ].join(" ");
1585
+ function buildDuotoneFilterMarkup(color1, color2) {
1586
+ const c1 = hexToRgbUnit2(color1);
1587
+ const c2 = hexToRgbUnit2(color2);
1588
+ return `<feColorMatrix type="matrix" values="${GRAYSCALE_LUMINANCE_MATRIX}"/><feComponentTransfer><feFuncR type="linear" slope="${c2.r - c1.r}" intercept="${c1.r}"/><feFuncG type="linear" slope="${c2.g - c1.g}" intercept="${c1.g}"/><feFuncB type="linear" slope="${c2.b - c1.b}" intercept="${c1.b}"/></feComponentTransfer>`;
1589
+ }
1590
+ function getDuotoneImageFilter(element, elementId = isImageLikeElement(element) ? element.id : "") {
1591
+ const effects = getEffects(element);
1592
+ if (!effects?.duotone) {
1593
+ return void 0;
1594
+ }
1595
+ const id = getImageDuotoneFilterId(elementId);
1596
+ return {
1597
+ id,
1598
+ cssReference: `url(#${id})`,
1599
+ filterMarkup: buildDuotoneFilterMarkup(effects.duotone.color1, effects.duotone.color2)
1600
+ };
1601
+ }
1602
+ function buildImageAlphaFilterMarkup(effects) {
1603
+ const parts = [];
1604
+ let resultIdx = 0;
1605
+ let inputRef = "SourceGraphic";
1606
+ const next = (build) => {
1607
+ const output = `r${resultIdx++}`;
1608
+ parts.push(build(inputRef, output));
1609
+ inputRef = output;
1610
+ };
1611
+ if (typeof effects.alphaModFix === "number") {
1612
+ const mul = clamp2(effects.alphaModFix / 100, 0, 1);
1613
+ next(
1614
+ (inp, out) => `<feColorMatrix in="${inp}" result="${out}" type="matrix" values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 ${mul} 0"/>`
1615
+ );
1616
+ }
1617
+ if (effects.alphaInv) {
1618
+ next(
1619
+ (inp, out) => `<feComponentTransfer in="${inp}" result="${out}"><feFuncA type="linear" slope="-1" intercept="1"/></feComponentTransfer>`
1620
+ );
1621
+ }
1622
+ if (effects.alphaCeiling) {
1623
+ next(
1624
+ (inp, out) => `<feComponentTransfer in="${inp}" result="${out}"><feFuncA type="discrete" tableValues="0 1 1 1 1 1 1 1 1 1"/></feComponentTransfer>`
1625
+ );
1626
+ }
1627
+ if (effects.alphaFloor) {
1628
+ next(
1629
+ (inp, out) => `<feComponentTransfer in="${inp}" result="${out}"><feFuncA type="discrete" tableValues="0 0 0 0 0 0 0 0 0 1"/></feComponentTransfer>`
1630
+ );
1631
+ }
1632
+ if (typeof effects.alphaRepl === "number") {
1633
+ const a = clamp2(effects.alphaRepl / 100, 0, 1);
1634
+ next(
1635
+ (inp, out) => `<feComponentTransfer in="${inp}" result="${out}"><feFuncA type="linear" slope="0" intercept="${a}"/></feComponentTransfer>`
1636
+ );
1637
+ }
1638
+ if (typeof effects.alphaBiLevel === "number") {
1639
+ const t = clamp2(effects.alphaBiLevel / 100, 0, 1);
1640
+ const table = Array.from({ length: 10 }, (_, i) => i / 10 >= t ? "1" : "0").join(" ");
1641
+ next(
1642
+ (inp, out) => `<feComponentTransfer in="${inp}" result="${out}"><feFuncA type="discrete" tableValues="${table}"/></feComponentTransfer>`
1643
+ );
1644
+ }
1645
+ if (typeof effects.biLevel === "number") {
1646
+ next((inp, out) => `<feColorMatrix in="${inp}" result="${out}" type="saturate" values="0"/>`);
1647
+ const tbl = buildImageBiLevelTable(effects.biLevel);
1648
+ next(
1649
+ (inp, out) => `<feComponentTransfer in="${inp}" result="${out}"><feFuncR type="discrete" tableValues="${tbl}"/><feFuncG type="discrete" tableValues="${tbl}"/><feFuncB type="discrete" tableValues="${tbl}"/></feComponentTransfer>`
1650
+ );
1651
+ }
1652
+ if (effects.lum && (typeof effects.lum.bright === "number" || typeof effects.lum.contrast === "number")) {
1653
+ const b = (effects.lum.bright ?? 0) / 100;
1654
+ const c = 1 + (effects.lum.contrast ?? 0) / 100;
1655
+ const slope = c;
1656
+ const intercept = b + (1 - c) / 2;
1657
+ next(
1658
+ (inp, out) => `<feComponentTransfer in="${inp}" result="${out}"><feFuncR type="linear" slope="${slope}" intercept="${intercept}"/><feFuncG type="linear" slope="${slope}" intercept="${intercept}"/><feFuncB type="linear" slope="${slope}" intercept="${intercept}"/></feComponentTransfer>`
1659
+ );
1660
+ }
1661
+ if (effects.hsl && typeof effects.hsl.sat === "number") {
1662
+ const v = clamp2(1 + effects.hsl.sat / 100, 0, 2);
1663
+ next(
1664
+ (inp, out) => `<feColorMatrix in="${inp}" result="${out}" type="saturate" values="${v}"/>`
1665
+ );
1666
+ }
1667
+ if (effects.hsl && typeof effects.hsl.lum === "number") {
1668
+ const { slope, intercept } = buildImageLuminanceTransfer(effects.hsl.lum);
1669
+ next(
1670
+ (inp, out) => `<feComponentTransfer in="${inp}" result="${out}"><feFuncR type="linear" slope="${slope}" intercept="${intercept}"/><feFuncG type="linear" slope="${slope}" intercept="${intercept}"/><feFuncB type="linear" slope="${slope}" intercept="${intercept}"/></feComponentTransfer>`
1671
+ );
1672
+ }
1673
+ if (effects.tint && typeof effects.tint.hue === "number") {
1674
+ next(
1675
+ (inp, out) => `<feColorMatrix in="${inp}" result="${out}" type="hueRotate" values="${effects.tint.hue}"/>`
1676
+ );
1677
+ }
1678
+ if (effects.tint && typeof effects.tint.amt === "number") {
1679
+ const { slope, intercept } = buildImageLuminanceTransfer(effects.tint.amt);
1680
+ next(
1681
+ (inp, out) => `<feComponentTransfer in="${inp}" result="${out}"><feFuncR type="linear" slope="${slope}" intercept="${intercept}"/><feFuncG type="linear" slope="${slope}" intercept="${intercept}"/><feFuncB type="linear" slope="${slope}" intercept="${intercept}"/></feComponentTransfer>`
1682
+ );
1683
+ }
1684
+ if (effects.clrRepl) {
1685
+ const c = hexToRgbUnit2(effects.clrRepl.color);
1686
+ next(
1687
+ (inp, out) => `<feColorMatrix in="${inp}" result="${out}" type="matrix" values="0 0 0 0 ${c.r} 0 0 0 0 ${c.g} 0 0 0 0 ${c.b} 0 0 0 1 0"/>`
1688
+ );
1689
+ }
1690
+ return parts.length > 0 ? parts.join("") : void 0;
1691
+ }
1692
+ function getImageAlphaFilter(element, elementId = isImageLikeElement(element) ? element.id : "") {
1693
+ const effects = getEffects(element);
1694
+ if (!effects || !hasAdvancedImageAlphaEffects(element)) {
1695
+ return void 0;
1696
+ }
1697
+ const markup = buildImageAlphaFilterMarkup(effects);
1698
+ if (!markup) {
1699
+ return void 0;
1700
+ }
1701
+ const id = getImageAlphaFilterId(elementId);
1702
+ return { id, cssReference: `url(#${id})`, filterMarkup: markup };
1703
+ }
1704
+ function buildDiscreteTable(steps) {
1705
+ const values = [];
1706
+ for (let i = 0; i < steps; i++) {
1707
+ const v = Math.round(i / (steps - 1) * 100) / 100;
1708
+ const count = Math.ceil(256 / steps);
1709
+ for (let j = 0; j < count; j++) {
1710
+ values.push(v);
1711
+ }
1712
+ }
1713
+ return values.slice(0, 256).join(" ");
1714
+ }
1715
+ function buildArtisticFilterMarkup(effectName, radius) {
1716
+ const normalizedRadius = clamp2(radius, 0, 100) / 100;
1717
+ switch (effectName) {
1718
+ case "artisticFilmGrain":
1719
+ case "filmGrain": {
1720
+ const freq = 0.5 + normalizedRadius * 0.5;
1721
+ const opacity = 0.15 + normalizedRadius * 0.25;
1722
+ return `<feTurbulence type="fractalNoise" baseFrequency="${freq}" numOctaves="4" seed="1" stitchTiles="stitch" result="grain"/><feColorMatrix in="grain" type="saturate" values="0" result="grainGray"/><feBlend in="SourceGraphic" in2="grainGray" mode="overlay"/><feComponentTransfer><feFuncR type="linear" slope="${1 + opacity * 0.3}" intercept="${opacity * 0.02}"/><feFuncG type="linear" slope="${1 + opacity * 0.3}" intercept="${opacity * 0.02}"/><feFuncB type="linear" slope="${1 + opacity * 0.3}" intercept="${opacity * 0.02}"/></feComponentTransfer>`;
1723
+ }
1724
+ case "artisticCutout":
1725
+ case "cutout": {
1726
+ const steps = Math.max(2, Math.round(4 + normalizedRadius * 4));
1727
+ const tableValues = buildDiscreteTable(steps);
1728
+ return `<feComponentTransfer><feFuncR type="discrete" tableValues="${tableValues}"/><feFuncG type="discrete" tableValues="${tableValues}"/><feFuncB type="discrete" tableValues="${tableValues}"/></feComponentTransfer>`;
1729
+ }
1730
+ case "artisticCement":
1731
+ case "cement": {
1732
+ const freq = 1.5 + normalizedRadius * 2.5;
1733
+ const desat = Math.max(0, 0.4 - normalizedRadius * 0.3);
1734
+ return `<feColorMatrix type="saturate" values="${desat}" result="desat"/><feTurbulence type="fractalNoise" baseFrequency="${freq}" numOctaves="5" seed="2" stitchTiles="stitch" result="cementNoise"/><feColorMatrix in="cementNoise" type="saturate" values="0" result="cementGray"/><feBlend in="desat" in2="cementGray" mode="multiply"/><feComponentTransfer><feFuncR type="linear" slope="1.2" intercept="0"/><feFuncG type="linear" slope="1.2" intercept="0"/><feFuncB type="linear" slope="1.2" intercept="0"/></feComponentTransfer>`;
1735
+ }
1736
+ case "artisticTexturizer":
1737
+ case "texturizer": {
1738
+ const freq = 2 + normalizedRadius * 3;
1739
+ return `<feTurbulence type="fractalNoise" baseFrequency="${freq}" numOctaves="3" seed="3" stitchTiles="stitch" result="texNoise"/><feColorMatrix in="texNoise" type="saturate" values="0" result="texGray"/><feBlend in="SourceGraphic" in2="texGray" mode="overlay"/><feComponentTransfer><feFuncR type="linear" slope="1.1" intercept="0.02"/><feFuncG type="linear" slope="1.1" intercept="0.02"/><feFuncB type="linear" slope="1.1" intercept="0.02"/></feComponentTransfer>`;
1740
+ }
1741
+ case "artisticCrisscrossEtching":
1742
+ case "crisscrossEtching": {
1743
+ const freq = 0.08 + normalizedRadius * 0.12;
1744
+ return `<feColorMatrix type="saturate" values="0" result="gray"/><feTurbulence type="turbulence" baseFrequency="${freq} ${freq * 3}" numOctaves="1" seed="7" result="lines"/><feColorMatrix in="lines" type="saturate" values="0" result="linesGray"/><feBlend in="gray" in2="linesGray" mode="multiply"/><feComponentTransfer><feFuncR type="linear" slope="1.3" intercept="-0.05"/><feFuncG type="linear" slope="1.3" intercept="-0.05"/><feFuncB type="linear" slope="1.3" intercept="-0.05"/></feComponentTransfer>`;
1745
+ }
1746
+ case "artisticMosaic":
1747
+ case "artisticMosaicBubbles":
1748
+ case "mosaicBubbles":
1749
+ case "mosaic": {
1750
+ const blurAmount = Math.max(2, Math.round(radius * 0.8));
1751
+ const freq = 0.02 + normalizedRadius * 0.03;
1752
+ return `<feGaussianBlur in="SourceGraphic" stdDeviation="${blurAmount}" result="blurred"/><feTurbulence type="turbulence" baseFrequency="${freq}" numOctaves="1" seed="4" result="cells"/><feDisplacementMap in="blurred" in2="cells" scale="${blurAmount * 0.5}" xChannelSelector="R" yChannelSelector="G" result="mosaic"/><feComponentTransfer in="mosaic"><feFuncR type="linear" slope="1.05" intercept="0"/><feFuncG type="linear" slope="1.05" intercept="0"/><feFuncB type="linear" slope="1.05" intercept="0"/></feComponentTransfer>`;
1753
+ }
1754
+ case "artisticGlowEdges":
1755
+ case "glowEdges":
1756
+ case "glow_edges": {
1757
+ const edgeBlur = Math.max(1, Math.round(normalizedRadius * 3));
1758
+ return `<feColorMatrix type="matrix" values="-1 0 0 0 1 0 -1 0 0 1 0 0 -1 0 1 0 0 0 1 0" result="inverted"/><feGaussianBlur in="inverted" stdDeviation="${edgeBlur}" result="blurredInv"/><feBlend in="SourceGraphic" in2="blurredInv" mode="screen"/><feComponentTransfer><feFuncR type="linear" slope="2" intercept="-0.3"/><feFuncG type="linear" slope="2" intercept="-0.3"/><feFuncB type="linear" slope="2" intercept="-0.3"/></feComponentTransfer>`;
1759
+ }
1760
+ case "artisticChalkSketch":
1761
+ case "chalkSketch":
1762
+ case "chalk": {
1763
+ const freq = 1 + normalizedRadius * 2;
1764
+ const desat = Math.max(0, 0.2 - normalizedRadius * 0.2);
1765
+ return `<feColorMatrix type="saturate" values="${desat}" result="gray"/><feTurbulence type="fractalNoise" baseFrequency="${freq}" numOctaves="4" seed="5" stitchTiles="stitch" result="chalkTex"/><feColorMatrix in="chalkTex" type="saturate" values="0" result="chalkGray"/><feBlend in="gray" in2="chalkGray" mode="overlay"/><feComponentTransfer><feFuncR type="linear" slope="1.5" intercept="0.05"/><feFuncG type="linear" slope="1.5" intercept="0.05"/><feFuncB type="linear" slope="1.5" intercept="0.05"/></feComponentTransfer>`;
1766
+ }
1767
+ case "artisticPencilSketch":
1768
+ case "pencilSketch": {
1769
+ const blurAmt = Math.max(3, Math.round(4 + normalizedRadius * 8));
1770
+ return `<feColorMatrix type="saturate" values="0" result="gray"/><feColorMatrix in="gray" type="matrix" values="-1 0 0 0 1 0 -1 0 0 1 0 0 -1 0 1 0 0 0 1 0" result="invGray"/><feGaussianBlur in="invGray" stdDeviation="${blurAmt}" result="blurInv"/><feBlend in="blurInv" in2="gray" mode="screen" result="sketch"/><feComponentTransfer in="sketch"><feFuncR type="linear" slope="1.6" intercept="-0.15"/><feFuncG type="linear" slope="1.6" intercept="-0.15"/><feFuncB type="linear" slope="1.6" intercept="-0.15"/></feComponentTransfer>`;
1771
+ }
1772
+ case "artisticPencilGrayscale":
1773
+ case "pencilGrayscale":
1774
+ case "grayPencil": {
1775
+ const sharpenBlur = Math.max(0.5, 1 + normalizedRadius * 2);
1776
+ return `<feColorMatrix type="saturate" values="0" result="gray"/><feGaussianBlur in="gray" stdDeviation="${sharpenBlur}" result="grayBlur"/><feComposite in="gray" in2="grayBlur" operator="arithmetic" k1="0" k2="1.5" k3="-0.5" k4="0"/>`;
1777
+ }
1778
+ default:
1779
+ return void 0;
1780
+ }
1781
+ }
1782
+ function getArtisticImageFilter(element, elementId = isImageLikeElement(element) ? element.id : "") {
1783
+ const effects = getEffects(element);
1784
+ if (!effects?.artisticEffect || !needsSvgArtisticFilter(effects.artisticEffect)) {
1785
+ return void 0;
1786
+ }
1787
+ const markup = buildArtisticFilterMarkup(effects.artisticEffect, effects.artisticRadius ?? 5);
1788
+ if (!markup) {
1789
+ return void 0;
1790
+ }
1791
+ const id = getArtisticFilterId(elementId);
1792
+ return { id, cssReference: `url(#${id})`, filterMarkup: markup };
1793
+ }
1794
+ function getImageSvgFilters(element) {
1795
+ const defs = [];
1796
+ const duotone = getDuotoneImageFilter(element);
1797
+ if (duotone) {
1798
+ defs.push({ id: duotone.id, markup: duotone.filterMarkup });
1799
+ }
1800
+ const alpha = getImageAlphaFilter(element);
1801
+ if (alpha) {
1802
+ defs.push({ id: alpha.id, markup: alpha.filterMarkup });
1803
+ }
1804
+ const artistic = getArtisticImageFilter(element);
1805
+ if (artistic) {
1806
+ defs.push({ id: artistic.id, markup: artistic.filterMarkup });
1807
+ }
1808
+ return defs;
1809
+ }
1533
1810
  var NONE_PRESETS = /* @__PURE__ */ new Set(["textNoShape", "textPlain"]);
1534
1811
  var PATH_PRESETS = /* @__PURE__ */ new Set([
1535
1812
  "textArchUp",
@@ -3284,8 +3561,8 @@ function hexToRgb(hex) {
3284
3561
  ];
3285
3562
  }
3286
3563
  function rgbToHex(r, g, b) {
3287
- const clamp3 = (v) => Math.max(0, Math.min(255, Math.round(v)));
3288
- return `#${clamp3(r).toString(16).padStart(2, "0")}${clamp3(g).toString(16).padStart(2, "0")}${clamp3(b).toString(16).padStart(2, "0")}`;
3564
+ const clamp7 = (v) => Math.max(0, Math.min(255, Math.round(v)));
3565
+ return `#${clamp7(r).toString(16).padStart(2, "0")}${clamp7(g).toString(16).padStart(2, "0")}${clamp7(b).toString(16).padStart(2, "0")}`;
3289
3566
  }
3290
3567
  function tint(hex, amount) {
3291
3568
  const [r, g, b] = hexToRgb(hex);
@@ -3397,14 +3674,17 @@ function computeValueRange(series) {
3397
3674
  }
3398
3675
  function valueToY(val2, range, topY, bottomY) {
3399
3676
  const usable = bottomY - topY;
3677
+ let ratio;
3400
3678
  if (range.logScale && range.logBase) {
3401
3679
  const base = range.logBase;
3402
3680
  const clampedVal = Math.max(val2, range.min);
3403
3681
  const logVal = Math.log(clampedVal) / Math.log(base);
3404
3682
  const logMin = Math.log(range.min) / Math.log(base);
3405
- return bottomY - (logVal - logMin) / range.span * usable;
3683
+ ratio = (logVal - logMin) / range.span;
3684
+ } else {
3685
+ ratio = (val2 - range.min) / range.span;
3406
3686
  }
3407
- return bottomY - (val2 - range.min) / range.span * usable;
3687
+ return range.reverseOrder ? topY + ratio * usable : bottomY - ratio * usable;
3408
3688
  }
3409
3689
  function formatAxisValue(val2) {
3410
3690
  if (Math.abs(val2) >= 1e6) {
@@ -3462,8 +3742,9 @@ function computeLayout(elementWidth, elementHeight, style, hasAxes, legendPos, o
3462
3742
  svgHeight
3463
3743
  };
3464
3744
  }
3745
+ var LOG_EXPONENT_TOLERANCE = 1e-12;
3465
3746
  function computeLogValueRange(series, logBase) {
3466
- const allValues = series.flatMap((s) => s.values).filter((v) => v > 0);
3747
+ const allValues = series.flatMap((item) => item.values).filter((value) => value > 0);
3467
3748
  if (allValues.length === 0) {
3468
3749
  return { min: 1, max: logBase, span: 1, logScale: true, logBase };
3469
3750
  }
@@ -3473,15 +3754,34 @@ function computeLogValueRange(series, logBase) {
3473
3754
  const logMax = Math.ceil(Math.log(dataMax) / Math.log(logBase));
3474
3755
  const min = logBase ** logMin;
3475
3756
  const max = logBase ** Math.max(logMax, logMin + 1);
3476
- const logSpan = Math.log(max) / Math.log(logBase) - Math.log(min) / Math.log(logBase);
3477
3757
  return {
3478
3758
  min,
3479
3759
  max,
3480
- span: Math.max(logSpan, 1),
3760
+ span: Math.max(Math.log(max) / Math.log(logBase) - Math.log(min) / Math.log(logBase), 1),
3481
3761
  logScale: true,
3482
3762
  logBase
3483
3763
  };
3484
3764
  }
3765
+ function computeValueRangeForAxis(series, axis) {
3766
+ const requestedLogBase = axis?.logBase ?? 10;
3767
+ const logBase = axis?.logScale && Number.isFinite(requestedLogBase) && requestedLogBase > 1 ? requestedLogBase : axis?.logScale ? 10 : void 0;
3768
+ const automatic = logBase ? computeLogValueRange(series, logBase) : computeValueRange(series);
3769
+ const validMin = typeof axis?.min === "number" && Number.isFinite(axis.min) && (!logBase || axis.min > 0) ? axis.min : void 0;
3770
+ const validMax = typeof axis?.max === "number" && Number.isFinite(axis.max) && (!logBase || axis.max > 0) ? axis.max : void 0;
3771
+ const min = validMin ?? automatic.min;
3772
+ let max = validMax ?? automatic.max;
3773
+ if (max <= min) {
3774
+ max = logBase ? min * logBase : min + 1;
3775
+ }
3776
+ const span = logBase ? Math.log(max) / Math.log(logBase) - Math.log(min) / Math.log(logBase) : max - min;
3777
+ return {
3778
+ min,
3779
+ max,
3780
+ span: Math.max(span, Number.EPSILON),
3781
+ ...logBase ? { logScale: true, logBase } : {},
3782
+ ...axis?.orientation === "maxMin" ? { reverseOrder: true } : {}
3783
+ };
3784
+ }
3485
3785
  function generateLogTicks(range) {
3486
3786
  if (!range.logScale || !range.logBase) {
3487
3787
  return [];
@@ -3489,21 +3789,56 @@ function generateLogTicks(range) {
3489
3789
  const base = range.logBase;
3490
3790
  const logMin = Math.log(range.min) / Math.log(base);
3491
3791
  const logMax = Math.log(range.max) / Math.log(base);
3492
- const ticks = [];
3493
- for (let exp = Math.round(logMin); exp <= Math.round(logMax); exp++) {
3494
- ticks.push(base ** exp);
3792
+ const firstExponent = Math.ceil(logMin - LOG_EXPONENT_TOLERANCE);
3793
+ const lastExponent = Math.floor(logMax + LOG_EXPONENT_TOLERANCE);
3794
+ if (lastExponent < firstExponent) {
3795
+ return range.min === range.max ? [range.min] : [range.min, range.max];
3495
3796
  }
3496
- return ticks;
3797
+ return Array.from(
3798
+ { length: lastExponent - firstExponent + 1 },
3799
+ (_unused, index) => base ** (firstExponent + index)
3800
+ );
3497
3801
  }
3498
- function findLogAxis(axes) {
3499
- return axes?.find((a) => a.axisType === "valAx" && a.logScale);
3802
+ function generateIntervalTicks(range, unit) {
3803
+ if (!Number.isFinite(unit) || unit <= 0 || range.logScale) {
3804
+ return [];
3805
+ }
3806
+ const tolerance = Math.max(1, Math.abs(range.min), Math.abs(range.max)) * 1e-12;
3807
+ const first = Math.ceil((range.min - tolerance) / unit);
3808
+ const last = Math.floor((range.max + tolerance) / unit);
3809
+ const count = Math.min(Math.max(last - first + 1, 0), 2e3);
3810
+ return Array.from({ length: count }, (_, index) => (first + index) * unit);
3500
3811
  }
3501
- function computeValueRangeForChart(series, axes) {
3502
- const logAxis = findLogAxis(axes);
3503
- if (logAxis?.logBase) {
3504
- return computeLogValueRange(series, logAxis.logBase);
3812
+ function generateAxisTicks(range, axis, defaultIntervals) {
3813
+ if (range.logScale && range.logBase) {
3814
+ return generateLogTicks(range);
3815
+ }
3816
+ const explicit = generateIntervalTicks(range, axis?.majorUnit ?? 0);
3817
+ if (explicit.length > 0) {
3818
+ return explicit;
3819
+ }
3820
+ return Array.from(
3821
+ { length: defaultIntervals + 1 },
3822
+ (_, index) => range.min + range.span / defaultIntervals * index
3823
+ );
3824
+ }
3825
+ function generateMinorAxisTicks(range, axis) {
3826
+ const minor = generateIntervalTicks(range, axis?.minorUnit ?? 0);
3827
+ if (minor.length === 0) {
3828
+ return [];
3505
3829
  }
3506
- return computeValueRange(series);
3830
+ const majorUnit = axis?.majorUnit;
3831
+ if (!majorUnit || !Number.isFinite(majorUnit) || majorUnit <= 0) {
3832
+ return minor;
3833
+ }
3834
+ const tolerance = Math.max(1, Math.abs(range.min), Math.abs(range.max)) * 1e-10;
3835
+ return minor.filter(
3836
+ (value) => Math.abs(value / majorUnit - Math.round(value / majorUnit)) > tolerance
3837
+ );
3838
+ }
3839
+ function computeValueRangeForChart(series, axes) {
3840
+ const primaryAxis = axes?.find((axis) => axis.axisType === "valAx" && axis.axPos === "l") ?? axes?.find((axis) => axis.axisType === "valAx" && axis.axPos !== "r");
3841
+ return computeValueRangeForAxis(series, primaryAxis);
3507
3842
  }
3508
3843
  var DISPLAY_UNIT_DIVISORS = {
3509
3844
  hundreds: 100,
@@ -3589,6 +3924,13 @@ function getSecondaryValueAxisId(axes) {
3589
3924
  const ax = getSecondaryValueAxis(axes);
3590
3925
  return ax?.axisId;
3591
3926
  }
3927
+ function getPrimaryValueAxisId(axes) {
3928
+ if (!axes) {
3929
+ return void 0;
3930
+ }
3931
+ const primary = axes.find((a) => a.axisType === "valAx" && a.axPos === "l");
3932
+ return primary?.axisId ?? axes.find((a) => a.axisType === "valAx")?.axisId;
3933
+ }
3592
3934
  function isSeriesOnSecondaryAxis(series, axes) {
3593
3935
  if (!axes) {
3594
3936
  return false;
@@ -3614,41 +3956,199 @@ function splitSeriesByAxis(series, axes) {
3614
3956
  }
3615
3957
  return { primary, secondary };
3616
3958
  }
3959
+ function clamp3(value, min, max) {
3960
+ return Math.min(Math.max(value, min), max);
3961
+ }
3962
+ function horizontalAxisY(valueAxis, range, layout, fallback) {
3963
+ if (valueAxis?.crosses === "max") {
3964
+ return layout.plotTop;
3965
+ }
3966
+ if (valueAxis?.crosses === "min") {
3967
+ return layout.plotBottom;
3968
+ }
3969
+ const crossingValue = valueAxis?.crossesAt ?? 0;
3970
+ if (valueAxis?.crossesAt !== void 0 || valueAxis?.crosses === "autoZero") {
3971
+ return valueToY2(
3972
+ clamp3(crossingValue, range.min, range.max),
3973
+ range,
3974
+ layout.plotTop,
3975
+ layout.plotBottom
3976
+ );
3977
+ }
3978
+ return fallback === "top" ? layout.plotTop : layout.plotBottom;
3979
+ }
3980
+ function verticalAxisX(categoryAxis, categoryCount, layout, fallback, dateValues) {
3981
+ if (categoryAxis?.crosses === "max") {
3982
+ return layout.plotRight;
3983
+ }
3984
+ if (categoryAxis?.crosses === "min") {
3985
+ return layout.plotLeft;
3986
+ }
3987
+ if (categoryAxis?.crossesAt !== void 0) {
3988
+ if (categoryAxis.axisType === "dateAx" && dateValues?.length) {
3989
+ const min = categoryAxis.min ?? Math.min(...dateValues);
3990
+ const max = categoryAxis.max ?? Math.max(...dateValues);
3991
+ const ratio2 = max === min ? 0.5 : (clamp3(categoryAxis.crossesAt, min, max) - min) / (max - min);
3992
+ return layout.plotLeft + ratio2 * layout.plotWidth;
3993
+ }
3994
+ const index = clamp3(categoryAxis.crossesAt - 1, 0, Math.max(categoryCount - 1, 0));
3995
+ const ratio = categoryCount <= 1 ? 0.5 : index / (categoryCount - 1);
3996
+ return layout.plotLeft + ratio * layout.plotWidth;
3997
+ }
3998
+ return fallback === "right" ? layout.plotRight : layout.plotLeft;
3999
+ }
4000
+ var DEFAULT_COLOR2 = "#64748b";
4001
+ function dashArray(style, width) {
4002
+ if (!style || style === "solid") {
4003
+ return void 0;
4004
+ }
4005
+ const unit = Math.max(width, 1);
4006
+ if (style === "dot" || style === "sysDot") {
4007
+ return `${unit} ${unit * 2}`;
4008
+ }
4009
+ if (style === "lgDash") {
4010
+ return `${unit * 6} ${unit * 3}`;
4011
+ }
4012
+ return `${unit * 3} ${unit * 2}`;
4013
+ }
4014
+ function chartAxisTextStyle(axis, defaultFontSize = 8) {
4015
+ return {
4016
+ fontSize: axis?.fontSize ?? defaultFontSize,
4017
+ fill: axis?.fontColor ?? DEFAULT_COLOR2,
4018
+ ...axis?.fontBold !== void 0 ? { fontWeight: axis.fontBold ? "bold" : "normal" } : {},
4019
+ ...axis?.fontFamily ? { fontFamily: axis.fontFamily } : {}
4020
+ };
4021
+ }
4022
+ function chartLineStyle(shape, fallbackColor = DEFAULT_COLOR2, fallbackWidth = 1) {
4023
+ const strokeWidth = shape?.strokeWidth ?? fallbackWidth;
4024
+ return {
4025
+ stroke: shape?.strokeColor ?? fallbackColor,
4026
+ strokeWidth,
4027
+ ...dashArray(shape?.strokeDashStyle, strokeWidth) ? { dashArray: dashArray(shape?.strokeDashStyle, strokeWidth) } : {}
4028
+ };
4029
+ }
4030
+ function buildVerticalTickMark(axisX, y, placement, side, length, shape) {
4031
+ if (!placement || placement === "none") {
4032
+ return void 0;
4033
+ }
4034
+ const inward = side === "left" ? 1 : -1;
4035
+ const startOffset = placement === "cross" ? -inward * length : 0;
4036
+ const endOffset = placement === "out" ? -inward * length : inward * length;
4037
+ return {
4038
+ kind: "line",
4039
+ x1: axisX + startOffset,
4040
+ y1: y,
4041
+ x2: axisX + endOffset,
4042
+ y2: y,
4043
+ ...chartLineStyle(shape)
4044
+ };
4045
+ }
4046
+ function buildVerticalAxisLine(axis, axisX, layout) {
4047
+ return axis?.spPr ? {
4048
+ kind: "line",
4049
+ x1: axisX,
4050
+ y1: layout.plotTop,
4051
+ x2: axisX,
4052
+ y2: layout.plotBottom,
4053
+ ...chartLineStyle(axis.spPr)
4054
+ } : void 0;
4055
+ }
4056
+ function buildStyledGridline(y, layout, shape, fallbackColor, fallbackWidth, fallbackDash, opacity) {
4057
+ const style = chartLineStyle(shape, fallbackColor, fallbackWidth);
4058
+ return {
4059
+ kind: "line",
4060
+ x1: layout.plotLeft,
4061
+ y1: y,
4062
+ x2: layout.plotRight,
4063
+ y2: y,
4064
+ ...style,
4065
+ ...(shape ? style.dashArray : fallbackDash) ? { dashArray: shape ? style.dashArray : fallbackDash } : {},
4066
+ ...opacity !== void 0 ? { opacity } : {}
4067
+ };
4068
+ }
3617
4069
  var GRIDLINE_COLOR = "#e2e8f0";
3618
- var AXIS_LABEL_COLOR = "#64748b";
3619
4070
  var SECONDARY_GRID_COLOR = "#e2e8f0";
3620
4071
  var TICK_COUNT = 5;
4072
+ var MAJOR_TICK_LENGTH = 4;
4073
+ var MINOR_TICK_LENGTH = 2.5;
4074
+ function valueAxisLabelPlacement(layout, position, defaultSide, axisX) {
4075
+ if (!position || position === "nextTo") {
4076
+ return defaultSide === "left" ? { x: axisX - 4, textAnchor: "end" } : { x: axisX + 4, textAnchor: "start" };
4077
+ }
4078
+ const side = position === "high" ? "right" : position === "low" ? "left" : defaultSide;
4079
+ return side === "left" ? { x: layout.plotLeft - 4, textAnchor: "end" } : { x: layout.plotRight + 4, textAnchor: "start" };
4080
+ }
3621
4081
  function formatTick(val2, axis) {
3622
4082
  if (axis?.displayUnits) {
3623
4083
  return formatAxisValueWithUnits(val2, axis);
3624
4084
  }
3625
4085
  return formatAxisValue2(val2);
3626
4086
  }
3627
- function buildPrimaryAxis(range, layout, axis) {
4087
+ function buildPrimaryAxis(range, layout, axis, axisX = layout.plotLeft) {
3628
4088
  const gridlines = [];
3629
4089
  const axisLabels = [];
3630
- const tickVals = range.logScale && range.logBase ? generateLogTicks(range) : Array.from({ length: TICK_COUNT + 1 }, (_, i) => range.min + range.span / TICK_COUNT * i);
4090
+ const tickVals = generateAxisTicks(range, axis, TICK_COUNT);
4091
+ const minorTickVals = generateMinorAxisTicks(range, axis);
4092
+ const axisLine = buildVerticalAxisLine(axis, axisX, layout);
4093
+ if (axisLine) {
4094
+ gridlines.push(axisLine);
4095
+ }
4096
+ if (axis?.minorGridlines) {
4097
+ for (const val2 of minorTickVals) {
4098
+ const y = valueToY2(val2, range, layout.plotTop, layout.plotBottom);
4099
+ gridlines.push(
4100
+ buildStyledGridline(y, layout, axis.minorGridlinesSpPr, GRIDLINE_COLOR, 0.5, "1 2", 0.5)
4101
+ );
4102
+ }
4103
+ }
4104
+ for (const val2 of minorTickVals) {
4105
+ const y = valueToY2(val2, range, layout.plotTop, layout.plotBottom);
4106
+ const tick = buildVerticalTickMark(
4107
+ axisX,
4108
+ y,
4109
+ axis?.minorTickMark,
4110
+ "left",
4111
+ MINOR_TICK_LENGTH,
4112
+ axis?.spPr
4113
+ );
4114
+ if (tick) {
4115
+ gridlines.push(tick);
4116
+ }
4117
+ }
3631
4118
  for (const val2 of tickVals) {
3632
4119
  const y = valueToY2(val2, range, layout.plotTop, layout.plotBottom);
3633
- gridlines.push({
3634
- kind: "line",
3635
- x1: layout.plotLeft,
3636
- y1: y,
3637
- x2: layout.plotRight,
3638
- y2: y,
3639
- stroke: GRIDLINE_COLOR,
3640
- strokeWidth: 1
3641
- });
3642
- axisLabels.push({
3643
- kind: "text",
3644
- x: layout.plotLeft - 4,
4120
+ gridlines.push(
4121
+ buildStyledGridline(
4122
+ y,
4123
+ layout,
4124
+ axis?.majorGridlinesSpPr,
4125
+ GRIDLINE_COLOR,
4126
+ 1,
4127
+ void 0,
4128
+ void 0
4129
+ )
4130
+ );
4131
+ const tick = buildVerticalTickMark(
4132
+ axisX,
3645
4133
  y,
3646
- text: formatTick(val2, axis),
3647
- fontSize: 8,
3648
- fill: AXIS_LABEL_COLOR,
3649
- textAnchor: "end",
3650
- dominantBaseline: "central"
3651
- });
4134
+ axis?.majorTickMark,
4135
+ "left",
4136
+ MAJOR_TICK_LENGTH,
4137
+ axis?.spPr
4138
+ );
4139
+ if (tick) {
4140
+ gridlines.push(tick);
4141
+ }
4142
+ if (axis?.tickLblPos !== "none") {
4143
+ axisLabels.push({
4144
+ kind: "text",
4145
+ ...valueAxisLabelPlacement(layout, axis?.tickLblPos, "left", axisX),
4146
+ y,
4147
+ text: formatTick(val2, axis),
4148
+ ...chartAxisTextStyle(axis),
4149
+ dominantBaseline: "central"
4150
+ });
4151
+ }
3652
4152
  }
3653
4153
  if (axis?.displayUnits) {
3654
4154
  const unitLabel = getDisplayUnitLabel(axis.displayUnits, axis.displayUnitsLabel);
@@ -3660,8 +4160,7 @@ function buildPrimaryAxis(range, layout, axis) {
3660
4160
  x: labelX,
3661
4161
  y: midY,
3662
4162
  text: unitLabel,
3663
- fontSize: 9,
3664
- fill: AXIS_LABEL_COLOR,
4163
+ ...chartAxisTextStyle(axis, 9),
3665
4164
  textAnchor: "middle",
3666
4165
  transform: `rotate(-90, ${labelX}, ${midY})`
3667
4166
  });
@@ -3669,37 +4168,81 @@ function buildPrimaryAxis(range, layout, axis) {
3669
4168
  }
3670
4169
  return { gridlines, axisLabels };
3671
4170
  }
3672
- function buildSecondaryAxis(range, layout, axis) {
4171
+ function buildSecondaryAxis(range, layout, axis, axisX = layout.plotRight) {
3673
4172
  const gridlines = [];
3674
4173
  const axisLabels = [];
3675
- const fontSize = axis?.fontSize ?? 8;
3676
- const fontColor = axis?.fontColor ?? AXIS_LABEL_COLOR;
3677
- const fontWeight = axis?.fontBold ? "bold" : "normal";
3678
- for (let i = 0; i <= TICK_COUNT - 1; i++) {
3679
- const val2 = range.min + range.span / (TICK_COUNT - 1) * i;
4174
+ const textStyle = chartAxisTextStyle(axis);
4175
+ const captionStyle = chartAxisTextStyle(axis, 9);
4176
+ const axisLine = buildVerticalAxisLine(axis, axisX, layout);
4177
+ if (axisLine) {
4178
+ gridlines.push(axisLine);
4179
+ }
4180
+ const tickValues = generateAxisTicks(range, axis, TICK_COUNT - 1);
4181
+ const minorTickValues = generateMinorAxisTicks(range, axis);
4182
+ if (axis?.minorGridlines) {
4183
+ for (const val2 of minorTickValues) {
4184
+ const y = valueToY2(val2, range, layout.plotTop, layout.plotBottom);
4185
+ gridlines.push(
4186
+ buildStyledGridline(
4187
+ y,
4188
+ layout,
4189
+ axis.minorGridlinesSpPr,
4190
+ SECONDARY_GRID_COLOR,
4191
+ 0.5,
4192
+ "1 2",
4193
+ 0.35
4194
+ )
4195
+ );
4196
+ }
4197
+ }
4198
+ for (const val2 of minorTickValues) {
3680
4199
  const y = valueToY2(val2, range, layout.plotTop, layout.plotBottom);
3681
- gridlines.push({
3682
- kind: "line",
3683
- x1: layout.plotLeft,
3684
- y1: y,
3685
- x2: layout.plotRight,
3686
- y2: y,
3687
- stroke: SECONDARY_GRID_COLOR,
3688
- strokeWidth: 0.5,
3689
- dashArray: "2 3",
3690
- opacity: 0.5
3691
- });
3692
- axisLabels.push({
3693
- kind: "text",
3694
- x: layout.plotRight + 4,
4200
+ const tick = buildVerticalTickMark(
4201
+ axisX,
3695
4202
  y,
3696
- text: formatTick(val2, axis),
3697
- fontSize,
3698
- fill: fontColor,
3699
- textAnchor: "start",
3700
- fontWeight,
3701
- dominantBaseline: "central"
3702
- });
4203
+ axis?.minorTickMark,
4204
+ "right",
4205
+ MINOR_TICK_LENGTH,
4206
+ axis?.spPr
4207
+ );
4208
+ if (tick) {
4209
+ gridlines.push(tick);
4210
+ }
4211
+ }
4212
+ for (const val2 of tickValues) {
4213
+ const y = valueToY2(val2, range, layout.plotTop, layout.plotBottom);
4214
+ gridlines.push(
4215
+ buildStyledGridline(
4216
+ y,
4217
+ layout,
4218
+ axis?.majorGridlinesSpPr,
4219
+ SECONDARY_GRID_COLOR,
4220
+ 0.5,
4221
+ "2 3",
4222
+ 0.5
4223
+ )
4224
+ );
4225
+ const tick = buildVerticalTickMark(
4226
+ axisX,
4227
+ y,
4228
+ axis?.majorTickMark,
4229
+ "right",
4230
+ MAJOR_TICK_LENGTH,
4231
+ axis?.spPr
4232
+ );
4233
+ if (tick) {
4234
+ gridlines.push(tick);
4235
+ }
4236
+ if (axis?.tickLblPos !== "none") {
4237
+ axisLabels.push({
4238
+ kind: "text",
4239
+ ...valueAxisLabelPlacement(layout, axis?.tickLblPos, "right", axisX),
4240
+ y,
4241
+ text: formatTick(val2, axis),
4242
+ ...textStyle,
4243
+ dominantBaseline: "central"
4244
+ });
4245
+ }
3703
4246
  }
3704
4247
  if (axis?.titleText) {
3705
4248
  const titleX = layout.plotRight + 36;
@@ -3709,8 +4252,7 @@ function buildSecondaryAxis(range, layout, axis) {
3709
4252
  x: titleX,
3710
4253
  y: midY,
3711
4254
  text: axis.titleText,
3712
- fontSize: 9,
3713
- fill: fontColor,
4255
+ ...captionStyle,
3714
4256
  textAnchor: "middle",
3715
4257
  transform: `rotate(-90, ${titleX}, ${midY})`
3716
4258
  });
@@ -3725,8 +4267,7 @@ function buildSecondaryAxis(range, layout, axis) {
3725
4267
  x: labelX,
3726
4268
  y: midY,
3727
4269
  text: unitLabel,
3728
- fontSize: 9,
3729
- fill: fontColor,
4270
+ ...captionStyle,
3730
4271
  textAnchor: "middle",
3731
4272
  transform: `rotate(-90, ${labelX}, ${midY})`
3732
4273
  });
@@ -3734,13 +4275,60 @@ function buildSecondaryAxis(range, layout, axis) {
3734
4275
  }
3735
4276
  return { gridlines, axisLabels };
3736
4277
  }
4278
+ function hasRicherAxisFeatures(chartData) {
4279
+ return Boolean(
4280
+ chartData.dataTable || chartData.axes?.some(
4281
+ (axis) => axis.crosses !== void 0 || axis.crossesAt !== void 0 || axis.crossBetween !== void 0 || axis.axisType === "valAx" && Boolean(
4282
+ axis.logScale || axis.displayUnits || axis.axPos === "r" || axis.orientation === "maxMin" || axis.majorUnit !== void 0 || axis.minorUnit !== void 0 || axis.minorGridlines || axis.majorTickMark !== void 0 || axis.minorTickMark !== void 0 || axis.tickLblPos !== void 0
4283
+ )
4284
+ )
4285
+ );
4286
+ }
4287
+ function buildCartesianAxes(chartData, layout, primaryRange, secondaryRange, categoryCount) {
4288
+ if (!hasRicherAxisFeatures(chartData)) {
4289
+ const { gridlines, axisLabels } = buildGridlinesAndLabels(primaryRange, layout);
4290
+ return { gridlines, axisLabels, secondaryGridlines: void 0, secondaryAxisLabels: void 0 };
4291
+ }
4292
+ const primaryAxis = chartData.axes?.find((axis) => axis.axisType === "valAx" && axis.axPos !== "r") ?? chartData.axes?.find((axis) => axis.axisType === "valAx");
4293
+ const categoryAxisFor = (axisId) => chartData.axes?.find(
4294
+ (axis) => (axis.axisType === "catAx" || axis.axisType === "dateAx") && axis.axisId === axisId
4295
+ );
4296
+ const axisX = (axisId, fallback) => verticalAxisX(
4297
+ categoryAxisFor(axisId),
4298
+ categoryCount,
4299
+ layout,
4300
+ fallback,
4301
+ chartData.dateCategories?.values
4302
+ );
4303
+ const primary = buildPrimaryAxis(
4304
+ primaryRange,
4305
+ layout,
4306
+ primaryAxis,
4307
+ axisX(primaryAxis?.crossAxisId, "left")
4308
+ );
4309
+ if (!secondaryRange) {
4310
+ return { ...primary, secondaryGridlines: void 0, secondaryAxisLabels: void 0 };
4311
+ }
4312
+ const secondaryAxis = getSecondaryValueAxis(chartData.axes);
4313
+ const secondary = buildSecondaryAxis(
4314
+ secondaryRange,
4315
+ layout,
4316
+ secondaryAxis,
4317
+ axisX(secondaryAxis?.crossAxisId, "right")
4318
+ );
4319
+ return {
4320
+ ...primary,
4321
+ secondaryGridlines: secondary.gridlines,
4322
+ secondaryAxisLabels: secondary.axisLabels
4323
+ };
4324
+ }
3737
4325
  function categoryTotals(series, catCount) {
3738
4326
  return Array.from(
3739
4327
  { length: catCount },
3740
4328
  (_, ci) => series.reduce((sum, s) => sum + Math.abs(s.values[ci] ?? 0), 0)
3741
4329
  );
3742
4330
  }
3743
- function buildBars(chartData, catCount, layout, primaryRange, secondaryRange, secondaryIdx, grouping) {
4331
+ function buildBars(chartData, catCount, layout, primaryRange, secondaryRange, secondaryIdx, grouping, sourceIndices2) {
3744
4332
  const primitives = [];
3745
4333
  const dataLabels = [];
3746
4334
  const series = chartData.series;
@@ -3751,10 +4339,11 @@ function buildBars(chartData, catCount, layout, primaryRange, secondaryRange, se
3751
4339
  const barGroupWidth2 = layout.plotWidth / Math.max(catCount, 1);
3752
4340
  const singleBarWidth = barGroupWidth2 * 0.7 / seriesCount;
3753
4341
  const groupOffset = (barGroupWidth2 - singleBarWidth * seriesCount) / 2;
3754
- for (let ci = 0; ci < catCount; ci++) {
4342
+ for (let displayIndex = 0; displayIndex < catCount; displayIndex++) {
4343
+ const sourceIndex = sourceIndices2[displayIndex] ?? displayIndex;
3755
4344
  for (let si = 0; si < series.length; si++) {
3756
- const val2 = series[si].values[ci] ?? 0;
3757
- const x = layout.plotLeft + barGroupWidth2 * ci + groupOffset + singleBarWidth * si;
4345
+ const val2 = series[si].values[sourceIndex] ?? 0;
4346
+ const x = layout.plotLeft + barGroupWidth2 * displayIndex + groupOffset + singleBarWidth * si;
3758
4347
  const activeRange = secondaryIdx.has(si) && secondaryRange ? secondaryRange : primaryRange;
3759
4348
  const zeroY = valueToY2(0, activeRange, layout.plotTop, layout.plotBottom);
3760
4349
  const valY = valueToY2(val2, activeRange, layout.plotTop, layout.plotBottom);
@@ -3768,7 +4357,7 @@ function buildBars(chartData, catCount, layout, primaryRange, secondaryRange, se
3768
4357
  h,
3769
4358
  fill: seriesColor2(series[si], si, palette),
3770
4359
  rx: 1,
3771
- part: { role: "dataPoint", seriesIndex: si, pointIndex: ci }
4360
+ part: { role: "dataPoint", seriesIndex: si, pointIndex: sourceIndex }
3772
4361
  });
3773
4362
  if (showLabels) {
3774
4363
  dataLabels.push({
@@ -3786,7 +4375,11 @@ function buildBars(chartData, catCount, layout, primaryRange, secondaryRange, se
3786
4375
  return { primitives, dataLabels };
3787
4376
  }
3788
4377
  if (grouping === "stacked") {
3789
- const rects = computeStackedBarRects(series, catCount, layout, primaryRange, palette);
4378
+ const displaySeries2 = series.map((entry) => ({
4379
+ ...entry,
4380
+ values: sourceIndices2.map((sourceIndex) => entry.values[sourceIndex] ?? 0)
4381
+ }));
4382
+ const rects = computeStackedBarRects(displaySeries2, catCount, layout, primaryRange, palette);
3790
4383
  for (const r of rects) {
3791
4384
  primitives.push({
3792
4385
  kind: "rect",
@@ -3796,24 +4389,33 @@ function buildBars(chartData, catCount, layout, primaryRange, secondaryRange, se
3796
4389
  h: r.h,
3797
4390
  fill: r.fill,
3798
4391
  rx: 1,
3799
- part: r.seriesIndex !== void 0 && r.pointIndex !== void 0 ? { role: "dataPoint", seriesIndex: r.seriesIndex, pointIndex: r.pointIndex } : void 0
4392
+ part: r.seriesIndex !== void 0 && r.pointIndex !== void 0 ? {
4393
+ role: "dataPoint",
4394
+ seriesIndex: r.seriesIndex,
4395
+ pointIndex: sourceIndices2[r.pointIndex] ?? r.pointIndex
4396
+ } : void 0
3800
4397
  });
3801
4398
  }
3802
4399
  if (showLabels) {
3803
- pushClusteredStackedLabels(series, catCount, layout, primaryRange, dataLabels);
4400
+ pushClusteredStackedLabels(series, sourceIndices2, catCount, layout, primaryRange, dataLabels);
3804
4401
  }
3805
4402
  return { primitives, dataLabels };
3806
4403
  }
3807
4404
  const barGroupWidth = layout.plotWidth / Math.max(catCount, 1);
3808
4405
  const barW = barGroupWidth * 0.6;
3809
4406
  const barOffset = (barGroupWidth - barW) / 2;
3810
- const totals = categoryTotals(series, catCount);
4407
+ const displaySeries = series.map((entry) => ({
4408
+ ...entry,
4409
+ values: sourceIndices2.map((sourceIndex) => entry.values[sourceIndex] ?? 0)
4410
+ }));
4411
+ const totals = categoryTotals(displaySeries, catCount);
3811
4412
  for (let ci = 0; ci < catCount; ci++) {
3812
4413
  let posRunning = 0;
3813
4414
  let negRunning = 0;
3814
4415
  const catTotal = totals[ci] || 1;
3815
4416
  for (let si = 0; si < series.length; si++) {
3816
- const rawVal = series[si].values[ci] ?? 0;
4417
+ const sourceIndex = sourceIndices2[ci] ?? ci;
4418
+ const rawVal = series[si].values[sourceIndex] ?? 0;
3817
4419
  const val2 = catTotal > 0 ? rawVal / catTotal * 100 : 0;
3818
4420
  const isNeg = val2 < 0;
3819
4421
  const base = isNeg ? negRunning : posRunning;
@@ -3830,7 +4432,7 @@ function buildBars(chartData, catCount, layout, primaryRange, secondaryRange, se
3830
4432
  w: barW,
3831
4433
  h,
3832
4434
  fill: seriesColor2(series[si], si, palette),
3833
- part: { role: "dataPoint", seriesIndex: si, pointIndex: ci }
4435
+ part: { role: "dataPoint", seriesIndex: si, pointIndex: sourceIndex }
3834
4436
  });
3835
4437
  if (showLabels && Math.abs(val2) > 0) {
3836
4438
  dataLabels.push({
@@ -3853,14 +4455,15 @@ function buildBars(chartData, catCount, layout, primaryRange, secondaryRange, se
3853
4455
  }
3854
4456
  return { primitives, dataLabels };
3855
4457
  }
3856
- function pushClusteredStackedLabels(series, catCount, layout, range, dataLabels) {
4458
+ function pushClusteredStackedLabels(series, sourceIndices2, catCount, layout, range, dataLabels) {
3857
4459
  const barGroupWidth = layout.plotWidth / catCount;
3858
4460
  const seriesCount = Math.max(series.length, 1);
3859
4461
  const singleBarWidth = barGroupWidth * 0.7 / seriesCount;
3860
4462
  const groupOffset = (barGroupWidth - singleBarWidth * seriesCount) / 2;
3861
4463
  for (let ci = 0; ci < catCount; ci++) {
4464
+ const sourceIndex = sourceIndices2[ci] ?? ci;
3862
4465
  for (let si = 0; si < series.length; si++) {
3863
- const val2 = series[si].values[ci] ?? 0;
4466
+ const val2 = series[si].values[sourceIndex] ?? 0;
3864
4467
  const x = layout.plotLeft + barGroupWidth * ci + groupOffset + singleBarWidth * si + singleBarWidth / 2;
3865
4468
  const zeroY = valueToY2(0, range, layout.plotTop, layout.plotBottom);
3866
4469
  const valY = valueToY2(val2, range, layout.plotTop, layout.plotBottom);
@@ -3877,7 +4480,7 @@ function pushClusteredStackedLabels(series, catCount, layout, range, dataLabels)
3877
4480
  }
3878
4481
  }
3879
4482
  }
3880
- function buildLines(chartData, catCount, layout, primaryRange, secondaryRange, secondaryIdx) {
4483
+ function buildLines(chartData, catCount, layout, primaryRange, secondaryRange, secondaryIdx, sourceIndices2, xPositions) {
3881
4484
  const primitives = [];
3882
4485
  const dataLabels = [];
3883
4486
  const showLabels = chartData.style?.hasDataLabels;
@@ -3887,7 +4490,13 @@ function buildLines(chartData, catCount, layout, primaryRange, secondaryRange, s
3887
4490
  continue;
3888
4491
  }
3889
4492
  const activeRange = secondaryIdx.has(si) && secondaryRange ? secondaryRange : primaryRange;
3890
- const pts = computeLinePoints(series.values, catCount, layout, activeRange);
4493
+ const displayValues = sourceIndices2.map((sourceIndex) => series.values[sourceIndex] ?? 0);
4494
+ const pts = computeLinePoints(displayValues, catCount, layout, activeRange).map(
4495
+ (point, index) => ({
4496
+ ...point,
4497
+ x: xPositions?.[index] ?? point.x
4498
+ })
4499
+ );
3891
4500
  const c = seriesColor2(series, si, chartData.colorPalette);
3892
4501
  primitives.push({
3893
4502
  kind: "polyline",
@@ -3897,19 +4506,20 @@ function buildLines(chartData, catCount, layout, primaryRange, secondaryRange, s
3897
4506
  fill: "none",
3898
4507
  part: { role: "series", seriesIndex: si }
3899
4508
  });
3900
- pts.forEach((pt, vi) => {
4509
+ pts.forEach((pt, displayIndex) => {
4510
+ const sourceIndex = sourceIndices2[displayIndex] ?? displayIndex;
3901
4511
  primitives.push({
3902
4512
  kind: "circle",
3903
4513
  cx: pt.x,
3904
4514
  cy: pt.y,
3905
4515
  r: 2.5,
3906
4516
  fill: c,
3907
- part: { role: "dataPoint", seriesIndex: si, pointIndex: vi }
4517
+ part: { role: "dataPoint", seriesIndex: si, pointIndex: sourceIndex }
3908
4518
  });
3909
4519
  });
3910
4520
  if (showLabels) {
3911
- series.values.forEach((val2, vi) => {
3912
- const pt = pts[vi];
4521
+ displayValues.forEach((val2, displayIndex) => {
4522
+ const pt = pts[displayIndex];
3913
4523
  if (!pt) {
3914
4524
  return;
3915
4525
  }
@@ -3927,7 +4537,7 @@ function buildLines(chartData, catCount, layout, primaryRange, secondaryRange, s
3927
4537
  }
3928
4538
  return { primitives, dataLabels };
3929
4539
  }
3930
- function buildAreas(chartData, catCount, layout, range) {
4540
+ function buildAreas(chartData, catCount, layout, range, sourceIndices2, xPositions) {
3931
4541
  const primitives = [];
3932
4542
  const dataLabels = [];
3933
4543
  const showLabels = chartData.style?.hasDataLabels;
@@ -3937,7 +4547,11 @@ function buildAreas(chartData, catCount, layout, range) {
3937
4547
  if (series.values.length === 0) {
3938
4548
  continue;
3939
4549
  }
3940
- const pts = computeLinePoints(series.values, catCount, layout, range);
4550
+ const displayValues = sourceIndices2.map((sourceIndex) => series.values[sourceIndex] ?? 0);
4551
+ const pts = computeLinePoints(displayValues, catCount, layout, range).map((point, index) => ({
4552
+ ...point,
4553
+ x: xPositions?.[index] ?? point.x
4554
+ }));
3941
4555
  const c = seriesColor2(series, si, chartData.colorPalette);
3942
4556
  const lineStr = linePointsToSvgString(pts);
3943
4557
  const firstPt = pts[0];
@@ -3945,7 +4559,7 @@ function buildAreas(chartData, catCount, layout, range) {
3945
4559
  if (firstPt && lastPt) {
3946
4560
  primitives.push({
3947
4561
  kind: "polyline",
3948
- points: `${layout.plotLeft.toFixed(2)},${baselineY.toFixed(2)} ${lineStr} ${lastPt.x.toFixed(2)},${baselineY.toFixed(2)}`,
4562
+ points: `${firstPt.x.toFixed(2)},${baselineY.toFixed(2)} ${lineStr} ${lastPt.x.toFixed(2)},${baselineY.toFixed(2)}`,
3949
4563
  stroke: "none",
3950
4564
  strokeWidth: 0,
3951
4565
  fill: c,
@@ -3961,9 +4575,23 @@ function buildAreas(chartData, catCount, layout, range) {
3961
4575
  fill: "none",
3962
4576
  part: { role: "series", seriesIndex: si }
3963
4577
  });
4578
+ pts.forEach((pt, displayIndex) => {
4579
+ primitives.push({
4580
+ kind: "circle",
4581
+ cx: pt.x,
4582
+ cy: pt.y,
4583
+ r: 2,
4584
+ fill: c,
4585
+ part: {
4586
+ role: "dataPoint",
4587
+ seriesIndex: si,
4588
+ pointIndex: sourceIndices2[displayIndex] ?? displayIndex
4589
+ }
4590
+ });
4591
+ });
3964
4592
  if (showLabels) {
3965
- series.values.forEach((val2, vi) => {
3966
- const pt = pts[vi];
4593
+ displayValues.forEach((val2, displayIndex) => {
4594
+ const pt = pts[displayIndex];
3967
4595
  if (!pt) {
3968
4596
  return;
3969
4597
  }
@@ -3987,10 +4615,11 @@ function buildScatter(chartData, layout, range) {
3987
4615
  const showLabels = chartData.style?.hasDataLabels;
3988
4616
  const allIndices = chartData.series.flatMap((s) => s.values.map((_, i) => i));
3989
4617
  const maxXIndex = Math.max(1, ...allIndices);
4618
+ const xValues = chartData.categories.map(Number);
3990
4619
  for (let si = 0; si < chartData.series.length; si++) {
3991
4620
  const series = chartData.series[si];
3992
4621
  const c = seriesColor2(series, si, chartData.colorPalette);
3993
- const dots = computeScatterDots(series.values, maxXIndex, layout, range);
4622
+ const dots = computeScatterDots(series.values, maxXIndex, layout, range, xValues);
3994
4623
  dots.forEach((dot, vi) => {
3995
4624
  primitives.push({
3996
4625
  kind: "circle",
@@ -4028,6 +4657,7 @@ function buildBubbles(chartData, layout, range) {
4028
4657
  const showLabels = chartData.style?.hasDataLabels;
4029
4658
  const allIndices = chartData.series.flatMap((s) => s.values.map((_, i) => i));
4030
4659
  const maxXIndex = Math.max(1, ...allIndices);
4660
+ const xValues = chartData.categories.map(Number);
4031
4661
  const sizeSeries = chartData.series.length >= 3 ? chartData.series[2] : void 0;
4032
4662
  const maxBubble = sizeSeries ? Math.max(1, ...sizeSeries.values.map((v) => Math.abs(v))) : 1;
4033
4663
  const medianRadius = Math.min(layout.plotWidth, layout.plotHeight) * 0.04;
@@ -4035,7 +4665,7 @@ function buildBubbles(chartData, layout, range) {
4035
4665
  for (let si = 0; si < pointSeries.length; si++) {
4036
4666
  const series = pointSeries[si];
4037
4667
  const c = seriesColor2(series, si, chartData.colorPalette);
4038
- const dots = computeScatterDots(series.values, maxXIndex, layout, range);
4668
+ const dots = computeScatterDots(series.values, maxXIndex, layout, range, xValues);
4039
4669
  dots.forEach((dot, vi) => {
4040
4670
  const r = computeBubbleRadius(sizeSeries?.values[vi], maxBubble, medianRadius);
4041
4671
  primitives.push({
@@ -4068,12 +4698,527 @@ function buildBubbles(chartData, layout, range) {
4068
4698
  }
4069
4699
  return { primitives, dataLabels };
4070
4700
  }
4071
- function xToPixel2(xVal, catCount, layout, mode) {
4072
- if (mode === "bar") {
4073
- const slotWidth = layout.plotWidth / Math.max(catCount, 1);
4074
- return layout.plotLeft + slotWidth * xVal + slotWidth / 2;
4701
+ function categoryX(displayIndex, count, layout, spacing) {
4702
+ if (spacing === "bar") {
4703
+ return layout.plotLeft + layout.plotWidth / Math.max(count, 1) * (displayIndex + 0.5);
4075
4704
  }
4076
- const maxIdx = Math.max(catCount - 1, 1);
4705
+ return count > 1 ? layout.plotLeft + layout.plotWidth / (count - 1) * displayIndex : layout.plotLeft + layout.plotWidth / 2;
4706
+ }
4707
+ function groupedLabels(values) {
4708
+ const groups = [];
4709
+ values.forEach((text, index) => {
4710
+ const previous = groups[groups.length - 1];
4711
+ if (previous?.text === text) {
4712
+ previous.end = index;
4713
+ } else {
4714
+ groups.push({ text, start: index, end: index });
4715
+ }
4716
+ });
4717
+ return groups;
4718
+ }
4719
+ function buildMultiLevelCategoryLabels(categoryLabels, categoryLevels, sourceIndices2, layout, spacing, axis, labelY, labelsAbove, offset) {
4720
+ const sourceLevels = categoryLevels?.length ? categoryLevels : [categoryLabels];
4721
+ const levels = axis?.noMultiLevelLabels ? sourceLevels.slice(0, 1) : sourceLevels;
4722
+ const skip = Math.max(1, axis?.tickLabelSkip ?? 1);
4723
+ const textAnchor = axis?.labelAlignment === "l" ? "start" : axis?.labelAlignment === "r" ? "end" : "middle";
4724
+ const direction = labelsAbove ? -1 : 1;
4725
+ const bandHeight = Math.max(axis?.fontSize ?? 8, 8) + 4;
4726
+ return levels.flatMap((level, levelIndex) => {
4727
+ const values = sourceIndices2.map((sourceIndex) => level[sourceIndex] ?? "");
4728
+ return groupedLabels(values).flatMap((group) => {
4729
+ if (!group.text || group.start % skip !== 0) {
4730
+ return [];
4731
+ }
4732
+ const startX = categoryX(group.start, sourceIndices2.length, layout, spacing);
4733
+ const endX = categoryX(group.end, sourceIndices2.length, layout, spacing);
4734
+ return [
4735
+ {
4736
+ kind: "text",
4737
+ x: (startX + endX) / 2,
4738
+ y: labelY + direction * (offset + levelIndex * bandHeight),
4739
+ text: group.text,
4740
+ textAnchor,
4741
+ ...chartAxisTextStyle(axis)
4742
+ }
4743
+ ];
4744
+ });
4745
+ });
4746
+ }
4747
+ function primaryCategoryAxis(axes) {
4748
+ const categoryAxes = axes?.filter(
4749
+ (axis) => axis.axisType === "catAx" || axis.axisType === "dateAx"
4750
+ );
4751
+ return categoryAxes?.find((axis) => axis.axPos !== "t") ?? categoryAxes?.[0];
4752
+ }
4753
+ function tickLine(x, y, placement, topAxis, length, axis) {
4754
+ if (!placement || placement === "none") {
4755
+ return void 0;
4756
+ }
4757
+ const inward = topAxis ? length : -length;
4758
+ const outward = -inward;
4759
+ const [start, end] = placement === "cross" ? [-length, length] : placement === "in" ? [0, inward] : [0, outward];
4760
+ return {
4761
+ kind: "line",
4762
+ x1: x,
4763
+ y1: y + start,
4764
+ x2: x,
4765
+ y2: y + end,
4766
+ ...chartLineStyle(axis.spPr)
4767
+ };
4768
+ }
4769
+ function buildTickMarks(axis, sourceCount, layout, spacing, axisY) {
4770
+ if (!axis || axis.deleted) {
4771
+ return [];
4772
+ }
4773
+ const result = [];
4774
+ const topAxis = axis.axPos === "t";
4775
+ const y = axisY ?? (topAxis ? layout.plotTop : layout.plotBottom);
4776
+ if (axis.spPr) {
4777
+ result.push({
4778
+ kind: "line",
4779
+ x1: layout.plotLeft,
4780
+ y1: y,
4781
+ x2: layout.plotRight,
4782
+ y2: y,
4783
+ ...chartLineStyle(axis.spPr)
4784
+ });
4785
+ }
4786
+ const majorSkip = Math.max(1, axis.tickMarkSkip ?? 1);
4787
+ for (let displayIndex = 0; displayIndex < sourceCount; displayIndex += majorSkip) {
4788
+ const x = categoryX(displayIndex, sourceCount, layout, spacing);
4789
+ const major = tickLine(x, y, axis.majorTickMark, topAxis, 4, axis);
4790
+ if (major) {
4791
+ result.push(major);
4792
+ }
4793
+ if (axis.minorTickMark !== void 0 && displayIndex + majorSkip < sourceCount) {
4794
+ const nextX = categoryX(displayIndex + majorSkip, sourceCount, layout, spacing);
4795
+ const minor = tickLine((x + nextX) / 2, y, axis.minorTickMark, topAxis, 2.5, axis);
4796
+ if (minor) {
4797
+ result.push(minor);
4798
+ }
4799
+ }
4800
+ }
4801
+ return result;
4802
+ }
4803
+ function buildCategoryAxisPlan(categoryLabels, layout, spacing, axes, selectedAxis, axisY, categoryLevels) {
4804
+ const axis = selectedAxis ?? primaryCategoryAxis(axes);
4805
+ const sourceIndices2 = categoryLabels.map((_label, index) => index);
4806
+ if (axis?.orientation === "maxMin") {
4807
+ sourceIndices2.reverse();
4808
+ }
4809
+ if (axis?.deleted || axis?.tickLblPos === "none") {
4810
+ return {
4811
+ axis,
4812
+ sourceIndices: sourceIndices2,
4813
+ labels: [],
4814
+ tickMarks: buildTickMarks(axis, sourceIndices2.length, layout, spacing, axisY)
4815
+ };
4816
+ }
4817
+ const topAxis = axis?.axPos === "t";
4818
+ const high = axis?.tickLblPos === "high";
4819
+ const low = axis?.tickLblPos === "low";
4820
+ const labelsAbove = high || !low && topAxis;
4821
+ const offset = 4 + 8 * ((axis?.labelOffset ?? 100) / 100);
4822
+ const labelY = high ? layout.plotTop : low ? layout.plotBottom : axisY ?? (topAxis ? layout.plotTop : layout.plotBottom);
4823
+ const labels = buildMultiLevelCategoryLabels(
4824
+ categoryLabels,
4825
+ categoryLevels,
4826
+ sourceIndices2,
4827
+ layout,
4828
+ spacing,
4829
+ axis,
4830
+ labelY,
4831
+ labelsAbove,
4832
+ offset
4833
+ );
4834
+ return {
4835
+ axis,
4836
+ sourceIndices: sourceIndices2,
4837
+ labels,
4838
+ tickMarks: buildTickMarks(axis, sourceIndices2.length, layout, spacing, axisY)
4839
+ };
4840
+ }
4841
+ function chartDataInCategoryOrder(chartData, sourceIndices2) {
4842
+ if (sourceIndices2.every((sourceIndex, displayIndex) => sourceIndex === displayIndex)) {
4843
+ return chartData;
4844
+ }
4845
+ return {
4846
+ ...chartData,
4847
+ categories: sourceIndices2.map((sourceIndex) => chartData.categories[sourceIndex] ?? ""),
4848
+ categoryLevels: chartData.categoryLevels?.map(
4849
+ (level) => sourceIndices2.map((sourceIndex) => level[sourceIndex] ?? "")
4850
+ ),
4851
+ series: chartData.series.map((series) => ({
4852
+ ...series,
4853
+ values: sourceIndices2.map((sourceIndex) => series.values[sourceIndex] ?? 0),
4854
+ errBars: series.errBars?.map((errorBars) => ({
4855
+ ...errorBars,
4856
+ customPlus: errorBars.customPlus ? sourceIndices2.map((sourceIndex) => errorBars.customPlus?.[sourceIndex] ?? 0) : void 0,
4857
+ customMinus: errorBars.customMinus ? sourceIndices2.map((sourceIndex) => errorBars.customMinus?.[sourceIndex] ?? 0) : void 0
4858
+ }))
4859
+ }))
4860
+ };
4861
+ }
4862
+ var DAY_MS = 864e5;
4863
+ function excelSerialToDate(serial, date1904 = false) {
4864
+ const epoch = date1904 ? Date.UTC(1904, 0, 1) : Date.UTC(1899, 11, serial < 60 ? 31 : 30);
4865
+ return new Date(epoch + serial * DAY_MS);
4866
+ }
4867
+ function primaryDateAxis(chartData) {
4868
+ return chartData.axes?.find((axis) => axis.axisType === "dateAx" && axis.axPos !== "t") ?? chartData.axes?.find((axis) => axis.axisType === "dateAx");
4869
+ }
4870
+ function serialToX(serial, min, max, layout) {
4871
+ const ratio = max === min ? 0.5 : (serial - min) / (max - min);
4872
+ return layout.plotLeft + ratio * layout.plotWidth;
4873
+ }
4874
+ function addUnit(date, amount, unit) {
4875
+ const next = new Date(date);
4876
+ if (unit === "days") {
4877
+ next.setUTCDate(next.getUTCDate() + amount);
4878
+ } else if (unit === "months") {
4879
+ next.setUTCMonth(next.getUTCMonth() + amount);
4880
+ } else {
4881
+ next.setUTCFullYear(next.getUTCFullYear() + amount);
4882
+ }
4883
+ return next;
4884
+ }
4885
+ function formatDate(date, unit, format) {
4886
+ const month = date.toLocaleString("en-US", { month: "short", timeZone: "UTC" });
4887
+ const year = String(date.getUTCFullYear());
4888
+ const day = String(date.getUTCDate());
4889
+ if (format?.toLowerCase().includes("yyyy")) {
4890
+ return format.toLowerCase().includes("d") ? `${day} ${month} ${year}` : `${month} ${year}`;
4891
+ }
4892
+ if (unit === "years") {
4893
+ return year;
4894
+ }
4895
+ if (unit === "months") {
4896
+ return `${month} ${year}`;
4897
+ }
4898
+ return `${day} ${month}`;
4899
+ }
4900
+ function tickLine2(x, y, axis, minor) {
4901
+ const placement = minor ? axis.minorTickMark : axis.majorTickMark;
4902
+ if (!placement || placement === "none") {
4903
+ return void 0;
4904
+ }
4905
+ const length = minor ? 2.5 : 4;
4906
+ const direction = axis.axPos === "t" ? 1 : -1;
4907
+ const inward = direction * length;
4908
+ const outward = -inward;
4909
+ const [start, end] = placement === "cross" ? [-length, length] : placement === "in" ? [0, inward] : [0, outward];
4910
+ return {
4911
+ kind: "line",
4912
+ x1: x,
4913
+ y1: y + start,
4914
+ x2: x,
4915
+ y2: y + end,
4916
+ ...chartLineStyle(axis.spPr)
4917
+ };
4918
+ }
4919
+ function calendarTicks(min, max, date1904, unit, step) {
4920
+ const result = [];
4921
+ let date = excelSerialToDate(min, date1904);
4922
+ for (let guard = 0; guard < 1e3 && date.getTime() <= excelSerialToDate(max, date1904).getTime(); guard++) {
4923
+ result.push(min + (date.getTime() - excelSerialToDate(min, date1904).getTime()) / DAY_MS);
4924
+ date = addUnit(date, step, unit);
4925
+ }
4926
+ return result;
4927
+ }
4928
+ function buildDateAxisPlan(chartData, layout, selectedAxis, axisY) {
4929
+ const dates = chartData.dateCategories;
4930
+ const axis = selectedAxis ?? primaryDateAxis(chartData);
4931
+ if (!dates || !axis || dates.values.length === 0) {
4932
+ return void 0;
4933
+ }
4934
+ const indexed = dates.values.map((value, index) => ({ value, index })).sort((a, b) => a.value - b.value);
4935
+ const dataMin = indexed[0]?.value ?? 0;
4936
+ const dataMax = indexed[indexed.length - 1]?.value ?? dataMin;
4937
+ const min = axis.min ?? dataMin;
4938
+ const max = axis.max ?? dataMax;
4939
+ const visible = indexed.filter(({ value }) => value >= min && value <= max);
4940
+ if (axis.orientation === "maxMin") {
4941
+ visible.reverse();
4942
+ }
4943
+ const sourceIndices2 = visible.map(({ index }) => index);
4944
+ const xPositions = visible.map(({ value }) => {
4945
+ const x = serialToX(value, min, max, layout);
4946
+ return axis.orientation === "maxMin" ? layout.plotLeft + layout.plotWidth - (x - layout.plotLeft) : x;
4947
+ });
4948
+ const unit = axis.majorTimeUnit ?? axis.baseTimeUnit ?? "days";
4949
+ const ticks = calendarTicks(
4950
+ min,
4951
+ max,
4952
+ dates.date1904 ?? false,
4953
+ unit,
4954
+ Math.max(1, axis.majorUnit ?? 1)
4955
+ );
4956
+ const y = axisY ?? (axis.axPos === "t" ? layout.plotTop : layout.plotBottom);
4957
+ const offset = 4 + 8 * ((axis.labelOffset ?? 100) / 100);
4958
+ const labelsAbove = axis.tickLblPos === "high" || axis.tickLblPos !== "low" && axis.axPos === "t";
4959
+ const labelY = axis.tickLblPos === "high" ? layout.plotTop : axis.tickLblPos === "low" ? layout.plotBottom : y;
4960
+ const labelSkip = Math.max(1, axis.tickLabelSkip ?? 1);
4961
+ const labels = axis.deleted || axis.tickLblPos === "none" ? [] : ticks.flatMap((serial, index) => {
4962
+ if (index % labelSkip !== 0) {
4963
+ return [];
4964
+ }
4965
+ let x = serialToX(serial, min, max, layout);
4966
+ if (axis.orientation === "maxMin") {
4967
+ x = layout.plotLeft + layout.plotWidth - (x - layout.plotLeft);
4968
+ }
4969
+ return [
4970
+ {
4971
+ kind: "text",
4972
+ x,
4973
+ y: labelsAbove ? labelY - offset : labelY + offset,
4974
+ text: formatDate(
4975
+ excelSerialToDate(serial, dates.date1904),
4976
+ unit,
4977
+ axis.numFmt?.formatCode ?? dates.formatCode
4978
+ ),
4979
+ ...chartAxisTextStyle(axis),
4980
+ textAnchor: "middle"
4981
+ }
4982
+ ];
4983
+ });
4984
+ const tickMarks = axis.deleted ? [] : ticks.flatMap((serial, index) => {
4985
+ if (index % Math.max(1, axis.tickMarkSkip ?? 1) !== 0) {
4986
+ return [];
4987
+ }
4988
+ let x = serialToX(serial, min, max, layout);
4989
+ if (axis.orientation === "maxMin") {
4990
+ x = layout.plotLeft + layout.plotWidth - (x - layout.plotLeft);
4991
+ }
4992
+ const line2 = tickLine2(x, y, axis, false);
4993
+ return line2 ? [line2] : [];
4994
+ });
4995
+ if (!axis.deleted && axis.spPr) {
4996
+ tickMarks.unshift({
4997
+ kind: "line",
4998
+ x1: layout.plotLeft,
4999
+ y1: y,
5000
+ x2: layout.plotRight,
5001
+ y2: y,
5002
+ ...chartLineStyle(axis.spPr)
5003
+ });
5004
+ }
5005
+ if (!axis.deleted && axis.minorTickMark && axis.minorTickMark !== "none") {
5006
+ const minorUnit = axis.minorTimeUnit ?? axis.baseTimeUnit ?? unit;
5007
+ for (const serial of calendarTicks(
5008
+ min,
5009
+ max,
5010
+ dates.date1904 ?? false,
5011
+ minorUnit,
5012
+ Math.max(1, axis.minorUnit ?? 1)
5013
+ )) {
5014
+ let x = serialToX(serial, min, max, layout);
5015
+ if (axis.orientation === "maxMin") {
5016
+ x = layout.plotLeft + layout.plotWidth - (x - layout.plotLeft);
5017
+ }
5018
+ const line2 = tickLine2(x, y, axis, true);
5019
+ if (line2) {
5020
+ tickMarks.push(line2);
5021
+ }
5022
+ }
5023
+ }
5024
+ return { sourceIndices: sourceIndices2, xPositions, labels, tickMarks };
5025
+ }
5026
+ function buildCartesianHorizontalAxis(chartData, categoryLabels, layout, kind, primaryRange, secondaryRange) {
5027
+ let catAxisStyle = kind === "line" || kind === "area" || kind === "scatter" || kind === "bubble" ? "line" : "bar";
5028
+ const horizontalAxes = chartData.axes?.filter(
5029
+ (axis) => axis.axisType === "catAx" || axis.axisType === "dateAx"
5030
+ );
5031
+ const primaryAxis = horizontalAxes?.find((axis) => axis.axPos !== "t") ?? horizontalAxes?.[0];
5032
+ const secondaryAxis = horizontalAxes?.find((axis) => axis.axPos === "t" && axis !== primaryAxis);
5033
+ const linkedValueAxis = (axis) => chartData.axes?.find(
5034
+ (candidate) => candidate.axisType === "valAx" && candidate.axisId === axis?.crossAxisId
5035
+ );
5036
+ const primaryValueAxis = linkedValueAxis(primaryAxis);
5037
+ if (primaryValueAxis?.crossBetween === "between") {
5038
+ catAxisStyle = "bar";
5039
+ } else if (primaryValueAxis?.crossBetween === "midCat") {
5040
+ catAxisStyle = "line";
5041
+ }
5042
+ const rangeAndY = (axis) => {
5043
+ const valueAxis = linkedValueAxis(axis);
5044
+ const range = valueAxis?.axPos === "r" && secondaryRange ? secondaryRange : primaryRange;
5045
+ return horizontalAxisY(valueAxis, range, layout, axis?.axPos === "t" ? "top" : "bottom");
5046
+ };
5047
+ const supportsDateScale = kind === "line" || kind === "area" || kind === "combo" || kind === "stock";
5048
+ const datePlan = supportsDateScale && primaryAxis?.axisType === "dateAx" ? buildDateAxisPlan(chartData, layout, primaryAxis, rangeAndY(primaryAxis)) : void 0;
5049
+ const categoryPlan = kind === "scatter" || kind === "bubble" || datePlan ? void 0 : buildCategoryAxisPlan(
5050
+ categoryLabels,
5051
+ layout,
5052
+ catAxisStyle,
5053
+ chartData.axes,
5054
+ primaryAxis,
5055
+ rangeAndY(primaryAxis),
5056
+ chartData.categoryLevels
5057
+ );
5058
+ const secondaryPlan = secondaryAxis ? secondaryAxis.axisType === "dateAx" ? buildDateAxisPlan(chartData, layout, secondaryAxis, rangeAndY(secondaryAxis)) : buildCategoryAxisPlan(
5059
+ categoryLabels,
5060
+ layout,
5061
+ linkedValueAxis(secondaryAxis)?.crossBetween === "between" ? "bar" : "line",
5062
+ chartData.axes,
5063
+ secondaryAxis,
5064
+ rangeAndY(secondaryAxis),
5065
+ chartData.categoryLevels
5066
+ ) : void 0;
5067
+ const sourceIndices2 = datePlan?.sourceIndices ?? categoryPlan?.sourceIndices ?? categoryLabels.map((_label, index) => index);
5068
+ return {
5069
+ catAxisStyle,
5070
+ sourceIndices: sourceIndices2,
5071
+ xPositions: datePlan?.xPositions ?? categoryPlan?.sourceIndices.map(
5072
+ (_sourceIndex, displayIndex) => categoryX(displayIndex, categoryPlan.sourceIndices.length, layout, catAxisStyle)
5073
+ ),
5074
+ labels: [
5075
+ ...datePlan?.labels ?? categoryPlan?.labels ?? buildCategoryLabels(categoryLabels, layout, catAxisStyle),
5076
+ ...secondaryPlan?.labels ?? []
5077
+ ],
5078
+ tickMarks: [
5079
+ ...datePlan?.tickMarks ?? categoryPlan?.tickMarks ?? [],
5080
+ ...secondaryPlan?.tickMarks ?? []
5081
+ ],
5082
+ displayChartData: datePlan || categoryPlan ? chartDataInCategoryOrder(chartData, sourceIndices2) : chartData
5083
+ };
5084
+ }
5085
+ var DEFAULT_COLOR3 = "#334155";
5086
+ var CAP_HALF_WIDTH = 4;
5087
+ function categoryX2(value, count, layout, mode) {
5088
+ if (mode === "bar") {
5089
+ const slot = layout.plotWidth / Math.max(count, 1);
5090
+ return layout.plotLeft + slot * value + slot / 2;
5091
+ }
5092
+ return layout.plotLeft + value / Math.max(count - 1, 1) * layout.plotWidth;
5093
+ }
5094
+ function positionedCategoryX(value, positions, fallback) {
5095
+ if (!positions || positions.length === 0) {
5096
+ return fallback(value);
5097
+ }
5098
+ const lower = Math.floor(value);
5099
+ const upper = Math.ceil(value);
5100
+ const at = (index) => {
5101
+ if (positions[index] !== void 0) {
5102
+ return positions[index];
5103
+ }
5104
+ const lastIndex = positions.length - 1;
5105
+ const last = positions[lastIndex] ?? 0;
5106
+ const beforeLast = lastIndex > 0 ? positions[lastIndex - 1] ?? positions[0] ?? 0 : positions[0] ?? 0;
5107
+ const gap = last - beforeLast;
5108
+ return last + gap * (index - positions.length + 1);
5109
+ };
5110
+ return at(lower) + (at(upper) - at(lower)) * (value - lower);
5111
+ }
5112
+ function numericX(value, range, layout) {
5113
+ return layout.plotLeft + (value - range.min) / Math.max(range.span, 1e-9) * layout.plotWidth;
5114
+ }
5115
+ function xValuesForSeries(chartData, series, indexes) {
5116
+ const seriesType = series.seriesChartType ?? chartData.chartType;
5117
+ const numeric = seriesType === "scatter" || seriesType === "bubble";
5118
+ if (!numeric) {
5119
+ return { values: indexes.map((_index, displayIndex) => displayIndex), numeric: false };
5120
+ }
5121
+ return {
5122
+ values: indexes.map((index) => {
5123
+ const parsed = Number(chartData.categories[index]);
5124
+ return Number.isFinite(parsed) ? parsed : index;
5125
+ }),
5126
+ numeric: true
5127
+ };
5128
+ }
5129
+ function numericXRange(values) {
5130
+ const min = Math.min(...values);
5131
+ const max = Math.max(...values);
5132
+ return { min, max, span: Math.max(max - min, 1) };
5133
+ }
5134
+ function errorValue(errBars, values, displayIndex, sourceIndex, direction) {
5135
+ switch (errBars.valType) {
5136
+ case "fixedVal":
5137
+ return errBars.val ?? 0;
5138
+ case "percentage":
5139
+ return Math.abs(values[displayIndex] ?? 0) * ((errBars.val ?? 0) / 100);
5140
+ case "stdDev": {
5141
+ const mean = values.reduce((sum, value) => sum + value, 0) / Math.max(values.length, 1);
5142
+ const variance = values.reduce((sum, value) => sum + (value - mean) ** 2, 0) / Math.max(values.length, 1);
5143
+ return Math.sqrt(variance) * (errBars.val ?? 1);
5144
+ }
5145
+ case "stdErr": {
5146
+ const count = Math.max(values.length, 1);
5147
+ const mean = values.reduce((sum, value) => sum + value, 0) / count;
5148
+ const variance = values.reduce((sum, value) => sum + (value - mean) ** 2, 0) / count;
5149
+ return Math.sqrt(variance / count);
5150
+ }
5151
+ case "cust":
5152
+ return direction === "plus" ? errBars.customPlus?.[sourceIndex] ?? 0 : errBars.customMinus?.[sourceIndex] ?? 0;
5153
+ }
5154
+ }
5155
+ function line(x1, y1, x2, y2, color) {
5156
+ return { kind: "line", x1, y1, x2, y2, stroke: color, strokeWidth: 1 };
5157
+ }
5158
+ function appendYBar(out, errBars, x, baseValue, error, direction, range, layout) {
5159
+ const color = errBars.color ?? DEFAULT_COLOR3;
5160
+ const baseY = valueToY2(baseValue, range, layout.plotTop, layout.plotBottom);
5161
+ const endValue = direction === "plus" ? baseValue + error : baseValue - error;
5162
+ const endY = valueToY2(endValue, range, layout.plotTop, layout.plotBottom);
5163
+ out.push(line(x, baseY, x, endY, color));
5164
+ if (!errBars.noEndCap) {
5165
+ out.push(line(x - CAP_HALF_WIDTH, endY, x + CAP_HALF_WIDTH, endY, color));
5166
+ }
5167
+ }
5168
+ function appendXBar(out, errBars, baseXValue, error, direction, y, toPixel) {
5169
+ const color = errBars.color ?? DEFAULT_COLOR3;
5170
+ const baseX = toPixel(baseXValue);
5171
+ const endValue = direction === "plus" ? baseXValue + error : baseXValue - error;
5172
+ const endX = toPixel(endValue);
5173
+ out.push(line(baseX, y, endX, y, color));
5174
+ if (!errBars.noEndCap) {
5175
+ out.push(line(endX, y - CAP_HALF_WIDTH, endX, y + CAP_HALF_WIDTH, color));
5176
+ }
5177
+ }
5178
+ function computeErrorBarPrimitives(chartData, catCount, layout, range, mode = "line", options = {}) {
5179
+ const out = [];
5180
+ chartData.series.forEach((series, seriesIndex) => {
5181
+ const indexes = options.sourceIndices ?? series.values.map((_value, index) => index);
5182
+ const yValues = indexes.map((index) => series.values[index] ?? 0);
5183
+ const xValues = xValuesForSeries(chartData, series, indexes);
5184
+ const xRange = xValues.numeric ? numericXRange(xValues.values) : void 0;
5185
+ const seriesMode = options.seriesModes?.[seriesIndex] ?? mode;
5186
+ const yRange = options.seriesRanges?.[seriesIndex] ?? range;
5187
+ series.errBars?.forEach((errBars) => {
5188
+ indexes.forEach((sourceIndex, displayIndex) => {
5189
+ const yValue = yValues[displayIndex] ?? 0;
5190
+ const baseXValue = xValues.values[displayIndex] ?? displayIndex;
5191
+ const categoryMapper = (value) => positionedCategoryX(
5192
+ value,
5193
+ options.xPositions,
5194
+ (index) => categoryX2(index, indexes.length || catCount, layout, seriesMode)
5195
+ );
5196
+ const toPixel = xValues.numeric ? (value) => numericX(value, xRange, layout) : categoryMapper;
5197
+ const x = toPixel(baseXValue);
5198
+ const y = valueToY2(yValue, yRange, layout.plotTop, layout.plotBottom);
5199
+ for (const direction of ["plus", "minus"]) {
5200
+ if (errBars.barType !== direction && errBars.barType !== "both") {
5201
+ continue;
5202
+ }
5203
+ const values = errBars.direction === "x" ? xValues.values : yValues;
5204
+ const error = errorValue(errBars, values, displayIndex, sourceIndex, direction);
5205
+ if (errBars.direction === "x") {
5206
+ appendXBar(out, errBars, baseXValue, error, direction, y, toPixel);
5207
+ } else {
5208
+ appendYBar(out, errBars, x, yValue, error, direction, yRange, layout);
5209
+ }
5210
+ }
5211
+ });
5212
+ });
5213
+ });
5214
+ return out;
5215
+ }
5216
+ function xToPixel2(xVal, catCount, layout, mode) {
5217
+ if (mode === "bar") {
5218
+ const slotWidth = layout.plotWidth / Math.max(catCount, 1);
5219
+ return layout.plotLeft + slotWidth * xVal + slotWidth / 2;
5220
+ }
5221
+ const maxIdx = Math.max(catCount - 1, 1);
4077
5222
  return layout.plotLeft + xVal / maxIdx * layout.plotWidth;
4078
5223
  }
4079
5224
  function computeLinearRegression2(xVals, yVals) {
@@ -4322,102 +5467,6 @@ function computeTrendlinePrimitives(chartData, catCount, layout, range, mode = "
4322
5467
  });
4323
5468
  return out;
4324
5469
  }
4325
- function computeErrorValue(errBars, values, pointIndex, direction) {
4326
- switch (errBars.valType) {
4327
- case "fixedVal":
4328
- return errBars.val ?? 0;
4329
- case "percentage":
4330
- return Math.abs(values[pointIndex]) * ((errBars.val ?? 0) / 100);
4331
- case "stdDev": {
4332
- const n = values.length;
4333
- if (n === 0) {
4334
- return 0;
4335
- }
4336
- const mean = values.reduce((s, v) => s + v, 0) / n;
4337
- const variance = values.reduce((s, v) => s + (v - mean) ** 2, 0) / n;
4338
- return Math.sqrt(variance) * (errBars.val ?? 1);
4339
- }
4340
- case "stdErr": {
4341
- const n2 = values.length;
4342
- if (n2 === 0) {
4343
- return 0;
4344
- }
4345
- const mean2 = values.reduce((s, v) => s + v, 0) / n2;
4346
- const variance2 = values.reduce((s, v) => s + (v - mean2) ** 2, 0) / n2;
4347
- return Math.sqrt(variance2 / n2);
4348
- }
4349
- case "cust":
4350
- return direction === "plus" ? errBars.customPlus?.[pointIndex] ?? 0 : errBars.customMinus?.[pointIndex] ?? 0;
4351
- default:
4352
- return 0;
4353
- }
4354
- }
4355
- var ERROR_BAR_COLOR = "#334155";
4356
- function computeErrorBarPrimitives(chartData, catCount, layout, range, mode = "line") {
4357
- const out = [];
4358
- const capW = 4;
4359
- chartData.series.forEach((series) => {
4360
- if (!series.errBars || series.errBars.length === 0) {
4361
- return;
4362
- }
4363
- series.errBars.forEach((eb) => {
4364
- if (eb.direction !== "y") {
4365
- return;
4366
- }
4367
- series.values.forEach((val2, vi) => {
4368
- const cx = xToPixel2(vi, catCount, layout, mode);
4369
- const baseY = valueToY2(val2, range, layout.plotTop, layout.plotBottom);
4370
- if (eb.barType === "plus" || eb.barType === "both") {
4371
- const plusErr = computeErrorValue(eb, series.values, vi, "plus");
4372
- const topY = valueToY2(val2 + plusErr, range, layout.plotTop, layout.plotBottom);
4373
- const stem = {
4374
- kind: "line",
4375
- x1: cx,
4376
- y1: baseY,
4377
- x2: cx,
4378
- y2: topY,
4379
- stroke: ERROR_BAR_COLOR,
4380
- strokeWidth: 1
4381
- };
4382
- const cap = {
4383
- kind: "line",
4384
- x1: cx - capW,
4385
- y1: topY,
4386
- x2: cx + capW,
4387
- y2: topY,
4388
- stroke: ERROR_BAR_COLOR,
4389
- strokeWidth: 1
4390
- };
4391
- out.push(stem, cap);
4392
- }
4393
- if (eb.barType === "minus" || eb.barType === "both") {
4394
- const minusErr = computeErrorValue(eb, series.values, vi, "minus");
4395
- const botY = valueToY2(val2 - minusErr, range, layout.plotTop, layout.plotBottom);
4396
- const stem = {
4397
- kind: "line",
4398
- x1: cx,
4399
- y1: baseY,
4400
- x2: cx,
4401
- y2: botY,
4402
- stroke: ERROR_BAR_COLOR,
4403
- strokeWidth: 1
4404
- };
4405
- const cap = {
4406
- kind: "line",
4407
- x1: cx - capW,
4408
- y1: botY,
4409
- x2: cx + capW,
4410
- y2: botY,
4411
- stroke: ERROR_BAR_COLOR,
4412
- strokeWidth: 1
4413
- };
4414
- out.push(stem, cap);
4415
- }
4416
- });
4417
- });
4418
- });
4419
- return out;
4420
- }
4421
5470
  var AXIS_TITLE_COLOR = "#475569";
4422
5471
  function computeAxisTitlePrimitives(chartData, layout) {
4423
5472
  const out = [];
@@ -4627,42 +5676,6 @@ function computeDataTablePrimitives(chartData, layout, colorPalette) {
4627
5676
  });
4628
5677
  return out;
4629
5678
  }
4630
- function hasRicherAxisFeatures(chartData) {
4631
- const axes = chartData.axes;
4632
- if (axes && axes.length > 0) {
4633
- for (const a of axes) {
4634
- if (a.axisType === "valAx" && (a.logScale || a.displayUnits || a.axPos === "r")) {
4635
- return true;
4636
- }
4637
- }
4638
- }
4639
- return Boolean(chartData.dataTable);
4640
- }
4641
- function findPrimaryValueAxis(chartData) {
4642
- const axes = chartData.axes;
4643
- if (!axes) {
4644
- return void 0;
4645
- }
4646
- return axes.find((a) => a.axisType === "valAx" && a.axPos !== "r") ?? axes.find((a) => a.axisType === "valAx");
4647
- }
4648
- function buildAxes(chartData, layout, primaryRange, secondaryRange) {
4649
- const richer = hasRicherAxisFeatures(chartData);
4650
- if (!richer) {
4651
- const { gridlines: gridlines2, axisLabels: axisLabels2 } = buildGridlinesAndLabels(primaryRange, layout);
4652
- return { gridlines: gridlines2, axisLabels: axisLabels2, secondaryGridlines: void 0, secondaryAxisLabels: void 0 };
4653
- }
4654
- const primaryAxis = findPrimaryValueAxis(chartData);
4655
- const { gridlines, axisLabels } = buildPrimaryAxis(primaryRange, layout, primaryAxis);
4656
- let secondaryGridlines;
4657
- let secondaryAxisLabels;
4658
- if (secondaryRange) {
4659
- const secAxis = getSecondaryValueAxis(chartData.axes);
4660
- const sec = buildSecondaryAxis(secondaryRange, layout, secAxis);
4661
- secondaryGridlines = sec.gridlines;
4662
- secondaryAxisLabels = sec.axisLabels;
4663
- }
4664
- return { gridlines, axisLabels, secondaryGridlines, secondaryAxisLabels };
4665
- }
4666
5679
  function stackedRange(chartData, catCount, isPercent) {
4667
5680
  if (isPercent) {
4668
5681
  return { min: 0, max: 100, span: 100 };
@@ -4681,15 +5694,31 @@ function buildCartesianViewModel(element, chartData, categoryLabels, kind) {
4681
5694
  const useSecondary = !isStacked && secondaryIdx.size > 0;
4682
5695
  const primaryPlotSeries = useSecondary ? chartData.series.filter((_s, i) => !secondaryIdx.has(i)) : chartData.series;
4683
5696
  const secondaryPlotSeries = useSecondary ? chartData.series.filter((_s, i) => secondaryIdx.has(i)) : [];
4684
- const primaryRange = isStacked ? stackedRange(chartData, catCount, isPercent) : computeValueRangeForChart(
5697
+ const primaryAxis = chartData.axes?.find(
5698
+ (axis) => axis.axisType === "valAx" && axis.axPos !== "r"
5699
+ );
5700
+ const primaryRange = isStacked ? {
5701
+ ...stackedRange(chartData, catCount, isPercent),
5702
+ ...primaryAxis?.orientation === "maxMin" ? { reverseOrder: true } : {}
5703
+ } : computeValueRangeForChart(
4685
5704
  primaryPlotSeries.length > 0 ? primaryPlotSeries : chartData.series,
4686
5705
  chartData.axes
4687
5706
  );
4688
- const secondaryRange = useSecondary && secondaryPlotSeries.length > 0 ? computeValueRange2(secondaryPlotSeries) : void 0;
4689
- const axisRes = buildAxes(chartData, layout, primaryRange, secondaryRange);
5707
+ const secondaryRange = useSecondary && secondaryPlotSeries.length > 0 ? computeValueRangeForAxis(
5708
+ secondaryPlotSeries,
5709
+ chartData.axes?.find((axis) => axis.axisType === "valAx" && axis.axPos === "r")
5710
+ ) : void 0;
5711
+ const axisRes = buildCartesianAxes(chartData, layout, primaryRange, secondaryRange, catCount);
4690
5712
  const zeroLine = primaryRange.logScale ? void 0 : buildZeroLine(primaryRange, layout);
4691
- const catAxisStyle = kind === "line" || kind === "area" || kind === "scatter" || kind === "bubble" ? "line" : "bar";
4692
- const catLabels = buildCategoryLabels(categoryLabels, layout, catAxisStyle);
5713
+ const horizontalAxis = buildCartesianHorizontalAxis(
5714
+ chartData,
5715
+ categoryLabels,
5716
+ layout,
5717
+ kind,
5718
+ primaryRange,
5719
+ secondaryRange
5720
+ );
5721
+ const { catAxisStyle, sourceIndices: sourceIndices2, displayChartData } = horizontalAxis;
4693
5722
  const legendPos = chartData.style?.legendPosition ?? "b";
4694
5723
  const { legend, legendX, legendY, legendAnchor } = buildLegend(
4695
5724
  chartData.series,
@@ -4708,31 +5737,52 @@ function buildCartesianViewModel(element, chartData, categoryLabels, kind) {
4708
5737
  primaryRange,
4709
5738
  secondaryRange,
4710
5739
  secondaryIdx,
4711
- isStacked ? isPercent ? "percentStacked" : "stacked" : "clustered"
5740
+ isStacked ? isPercent ? "percentStacked" : "stacked" : "clustered",
5741
+ sourceIndices2
4712
5742
  );
4713
5743
  } else if (kind === "line") {
4714
- plot = buildLines(chartData, catCount, layout, primaryRange, secondaryRange, secondaryIdx);
5744
+ plot = buildLines(
5745
+ chartData,
5746
+ catCount,
5747
+ layout,
5748
+ primaryRange,
5749
+ secondaryRange,
5750
+ secondaryIdx,
5751
+ sourceIndices2,
5752
+ horizontalAxis.xPositions
5753
+ );
4715
5754
  } else if (kind === "area") {
4716
- plot = buildAreas(chartData, catCount, layout, primaryRange);
5755
+ plot = buildAreas(
5756
+ chartData,
5757
+ catCount,
5758
+ layout,
5759
+ primaryRange,
5760
+ sourceIndices2,
5761
+ horizontalAxis.xPositions
5762
+ );
4717
5763
  } else if (kind === "scatter") {
4718
5764
  plot = buildScatter(chartData, layout, primaryRange);
4719
5765
  } else {
4720
5766
  plot = buildBubbles(chartData, layout, primaryRange);
4721
5767
  }
4722
- const primitives = [...plot.primitives];
5768
+ const primitives = [...plot.primitives, ...horizontalAxis.tickMarks];
4723
5769
  const overlays = [
4724
5770
  ...computeTrendlinePrimitives(
4725
- chartData,
5771
+ displayChartData,
4726
5772
  catCount,
4727
5773
  layout,
4728
5774
  primaryRange,
4729
5775
  catAxisStyle,
4730
5776
  chartData.colorPalette
4731
5777
  ),
4732
- ...computeErrorBarPrimitives(chartData, catCount, layout, primaryRange, catAxisStyle),
5778
+ ...computeErrorBarPrimitives(displayChartData, catCount, layout, primaryRange, catAxisStyle),
4733
5779
  ...computeAxisTitlePrimitives(chartData, layout)
4734
5780
  ];
4735
- const dataTablePrims = computeDataTablePrimitives(chartData, layout, chartData.colorPalette);
5781
+ const dataTablePrims = computeDataTablePrimitives(
5782
+ displayChartData,
5783
+ layout,
5784
+ chartData.colorPalette
5785
+ );
4736
5786
  primitives.push(...overlays, ...dataTablePrims);
4737
5787
  const title = chartData.style?.hasTitle && chartData.title ? chartData.title : void 0;
4738
5788
  const valueDrag = isStacked ? void 0 : {
@@ -4751,7 +5801,7 @@ function buildCartesianViewModel(element, chartData, categoryLabels, kind) {
4751
5801
  gridlines: axisRes.gridlines,
4752
5802
  axisLabels: axisRes.axisLabels,
4753
5803
  zeroLine,
4754
- categoryLabels: catLabels,
5804
+ categoryLabels: horizontalAxis.labels,
4755
5805
  primitives,
4756
5806
  dataLabels: plot.dataLabels,
4757
5807
  legend: chartData.style?.hasLegend ? legend : [],
@@ -4765,135 +5815,255 @@ function buildCartesianViewModel(element, chartData, categoryLabels, kind) {
4765
5815
  valueDrag
4766
5816
  };
4767
5817
  }
4768
- var CANDLE_UP_FILL = "#22c55e";
4769
- var CANDLE_DOWN_FILL = "#ef4444";
4770
- var CANDLE_WICK_COLOR = "#334155";
5818
+ function rangeForSeries(index, primaryRange, secondaryRange, secondaryIndexes) {
5819
+ return secondaryRange && secondaryIndexes.has(index) ? secondaryRange : primaryRange;
5820
+ }
4771
5821
  function buildComboViewModel(element, chartData, categoryLabels) {
4772
- const layout = computePlotLayout(element.width, element.height, chartData, true);
4773
- const catCount = Math.max(categoryLabels.length, 1);
4774
- const range = computeValueRange2(chartData.series);
4775
- const { gridlines, axisLabels } = buildGridlinesAndLabels(range, layout);
4776
- const zeroLine = buildZeroLine(range, layout);
4777
- const catLabels = buildCategoryLabels(categoryLabels, layout, "bar");
4778
- const legendPos = chartData.style?.legendPosition ?? "b";
4779
- const { legend, legendX, legendY, legendAnchor } = buildLegend(
4780
- chartData.series,
4781
- chartData.colorPalette,
4782
- layout.svgWidth,
4783
- legendPos,
4784
- layout.svgHeight,
4785
- layout.plotTop
5822
+ const layoutOptions = computeLayoutOptions(
5823
+ chartData.axes,
5824
+ chartData.dataTable,
5825
+ chartData.series.length
4786
5826
  );
4787
- const primitives = [];
4788
- const dataLabels = [];
4789
- const barSeries = chartData.series.slice(0, 1);
4790
- if (barSeries.length > 0) {
4791
- const barRects = computeBarRects(barSeries, catCount, layout, range, chartData.colorPalette);
4792
- for (const r of barRects) {
4793
- primitives.push({
4794
- kind: "rect",
4795
- x: r.x,
4796
- y: r.y,
4797
- w: r.w,
4798
- h: r.h,
4799
- fill: r.fill,
4800
- rx: 1
4801
- });
4802
- }
4803
- if (chartData.style?.hasDataLabels && barSeries[0]) {
4804
- const barGroupWidth2 = layout.plotWidth / catCount;
4805
- const singleBarWidth = barGroupWidth2 * 0.7;
4806
- const groupOffset = (barGroupWidth2 - singleBarWidth) / 2;
4807
- barSeries[0].values.forEach((val2, vi) => {
4808
- const x = layout.plotLeft + barGroupWidth2 * vi + groupOffset + singleBarWidth / 2;
4809
- const zeroY = valueToY2(0, range, layout.plotTop, layout.plotBottom);
4810
- const valY = valueToY2(val2, range, layout.plotTop, layout.plotBottom);
4811
- const labelY = val2 >= 0 ? Math.min(zeroY, valY) - 4 : Math.max(zeroY, valY) + 10;
4812
- dataLabels.push({
4813
- kind: "text",
4814
- x,
4815
- y: labelY,
4816
- text: formatAxisValue2(val2),
4817
- fontSize: 7,
4818
- fill: "#334155",
4819
- textAnchor: "middle"
4820
- });
4821
- });
4822
- }
5827
+ const layout = computePlotLayout(
5828
+ element.width,
5829
+ element.height,
5830
+ chartData,
5831
+ true,
5832
+ layoutOptions
5833
+ );
5834
+ const catCount = Math.max(categoryLabels.length, 1);
5835
+ const { primary, secondary } = splitSeriesByAxis(chartData.series, chartData.axes);
5836
+ const secondaryIndexes = new Set(secondary.map((entry) => entry.index));
5837
+ const primarySeries = primary.length > 0 ? primary.map((entry) => entry.series) : chartData.series;
5838
+ const primaryAxisId = getPrimaryValueAxisId(chartData.axes);
5839
+ const primaryAxis = chartData.axes?.find((axis) => axis.axisId === primaryAxisId);
5840
+ const secondaryAxisFormatting = getSecondaryValueAxis(chartData.axes);
5841
+ const primaryRange = computeValueRangeForAxis(primarySeries, primaryAxis);
5842
+ const secondaryRange = secondary.length > 0 ? computeValueRangeForAxis(
5843
+ secondary.map((entry) => entry.series),
5844
+ secondaryAxisFormatting
5845
+ ) : void 0;
5846
+ const primaryCategoryAxis2 = chartData.axes?.find(
5847
+ (axis) => (axis.axisType === "catAx" || axis.axisType === "dateAx") && axis.axisId === primaryAxis?.crossAxisId
5848
+ );
5849
+ const primaryAxisX = verticalAxisX(
5850
+ primaryCategoryAxis2,
5851
+ catCount,
5852
+ layout,
5853
+ "left",
5854
+ chartData.dateCategories?.values
5855
+ );
5856
+ const primaryRendered = primaryCategoryAxis2?.crosses !== void 0 || primaryCategoryAxis2?.crossesAt !== void 0 ? buildPrimaryAxis(primaryRange, layout, primaryAxis, primaryAxisX) : buildGridlinesAndLabels(primaryRange, layout);
5857
+ const { gridlines, axisLabels } = primaryRendered;
5858
+ const secondaryCategoryAxis = chartData.axes?.find(
5859
+ (axis) => (axis.axisType === "catAx" || axis.axisType === "dateAx") && axis.axisId === secondaryAxisFormatting?.crossAxisId
5860
+ );
5861
+ const secondaryAxis = secondaryRange ? buildSecondaryAxis(
5862
+ secondaryRange,
5863
+ layout,
5864
+ secondaryAxisFormatting,
5865
+ verticalAxisX(
5866
+ secondaryCategoryAxis,
5867
+ catCount,
5868
+ layout,
5869
+ "right",
5870
+ chartData.dateCategories?.values
5871
+ )
5872
+ ) : void 0;
5873
+ const zeroLine = primaryRange.logScale ? void 0 : buildZeroLine(primaryRange, layout);
5874
+ const horizontalAxis = buildCartesianHorizontalAxis(
5875
+ chartData,
5876
+ categoryLabels,
5877
+ layout,
5878
+ "combo",
5879
+ primaryRange,
5880
+ secondaryRange
5881
+ );
5882
+ const sourceIndices2 = horizontalAxis.sourceIndices;
5883
+ const legendPos = chartData.style?.legendPosition ?? "b";
5884
+ const { legend, legendX, legendY, legendAnchor } = buildLegend(
5885
+ chartData.series,
5886
+ chartData.colorPalette,
5887
+ layout.svgWidth,
5888
+ legendPos,
5889
+ layout.svgHeight,
5890
+ layout.plotTop
5891
+ );
5892
+ const primitives = [];
5893
+ const dataLabels = [];
5894
+ const barSeries = chartData.series.slice(0, 1);
5895
+ if (barSeries[0]) {
5896
+ const barRange = rangeForSeries(0, primaryRange, secondaryRange, secondaryIndexes);
5897
+ const displayBarSeries = [
5898
+ { ...barSeries[0], values: sourceIndices2.map((index) => barSeries[0].values[index] ?? 0) }
5899
+ ];
5900
+ primitives.push(
5901
+ ...computeBarRects(displayBarSeries, catCount, layout, barRange, chartData.colorPalette).map(
5902
+ (rect, displayIndex) => ({
5903
+ kind: "rect",
5904
+ x: horizontalAxis.xPositions ? (horizontalAxis.xPositions[displayIndex] ?? rect.x) - rect.w / 2 : rect.x,
5905
+ y: rect.y,
5906
+ w: rect.w,
5907
+ h: rect.h,
5908
+ fill: rect.fill,
5909
+ rx: 1,
5910
+ part: {
5911
+ role: "dataPoint",
5912
+ seriesIndex: 0,
5913
+ pointIndex: sourceIndices2[displayIndex] ?? displayIndex
5914
+ }
5915
+ })
5916
+ )
5917
+ );
5918
+ appendBarLabels(
5919
+ barSeries[0],
5920
+ chartData,
5921
+ layout,
5922
+ catCount,
5923
+ barRange,
5924
+ sourceIndices2,
5925
+ dataLabels,
5926
+ horizontalAxis.xPositions
5927
+ );
4823
5928
  }
4824
5929
  const barGroupWidth = layout.plotWidth / catCount;
4825
- const lineSeries = chartData.series.slice(1);
4826
- lineSeries.forEach((series, si) => {
5930
+ chartData.series.slice(1).forEach((series, offset) => {
5931
+ const seriesIndex = offset + 1;
4827
5932
  if (series.values.length === 0) {
4828
5933
  return;
4829
5934
  }
4830
- const seriesIdx = si + 1;
4831
- const c = seriesColor2(series, seriesIdx, chartData.colorPalette);
4832
- const pts = series.values.map((val2, vi) => ({
4833
- x: layout.plotLeft + barGroupWidth * vi + barGroupWidth / 2,
4834
- y: valueToY2(val2, range, layout.plotTop, layout.plotBottom)
4835
- }));
4836
- const pointsStr = pts.map((p) => `${p.x.toFixed(2)},${p.y.toFixed(2)}`).join(" ");
5935
+ const range = rangeForSeries(seriesIndex, primaryRange, secondaryRange, secondaryIndexes);
5936
+ const fill = seriesColor2(series, seriesIndex, chartData.colorPalette);
5937
+ const points = sourceIndices2.map((sourceIndex, displayIndex) => {
5938
+ const value = series.values[sourceIndex] ?? 0;
5939
+ return {
5940
+ x: horizontalAxis.xPositions?.[displayIndex] ?? layout.plotLeft + barGroupWidth * displayIndex + barGroupWidth / 2,
5941
+ y: valueToY2(value, range, layout.plotTop, layout.plotBottom),
5942
+ sourceIndex,
5943
+ value
5944
+ };
5945
+ });
4837
5946
  primitives.push({
4838
5947
  kind: "polyline",
4839
- points: pointsStr,
4840
- stroke: c,
5948
+ points: points.map((point) => `${point.x.toFixed(2)},${point.y.toFixed(2)}`).join(" "),
5949
+ stroke: fill,
4841
5950
  strokeWidth: 2.4,
4842
5951
  fill: "none"
4843
5952
  });
4844
- for (const pt of pts) {
4845
- primitives.push({
4846
- kind: "circle",
4847
- cx: pt.x,
4848
- cy: pt.y,
4849
- r: 2.5,
4850
- fill: c
4851
- });
4852
- }
5953
+ primitives.push(
5954
+ ...points.map(
5955
+ (point) => ({
5956
+ kind: "circle",
5957
+ cx: point.x,
5958
+ cy: point.y,
5959
+ r: 2.5,
5960
+ fill,
5961
+ part: { role: "dataPoint", seriesIndex, pointIndex: point.sourceIndex }
5962
+ })
5963
+ )
5964
+ );
4853
5965
  if (chartData.style?.hasDataLabels) {
4854
- series.values.forEach((val2, vi) => {
4855
- const pt = pts[vi];
4856
- if (!pt) {
4857
- return;
5966
+ points.forEach((point) => {
5967
+ if (point) {
5968
+ dataLabels.push({
5969
+ kind: "text",
5970
+ x: point.x,
5971
+ y: point.y - 7,
5972
+ text: formatAxisValue2(point.value),
5973
+ fontSize: 7,
5974
+ fill: "#334155",
5975
+ textAnchor: "middle"
5976
+ });
4858
5977
  }
4859
- dataLabels.push({
4860
- kind: "text",
4861
- x: pt.x,
4862
- y: pt.y - 7,
4863
- text: formatAxisValue2(val2),
4864
- fontSize: 7,
4865
- fill: "#334155",
4866
- textAnchor: "middle"
4867
- });
4868
5978
  });
4869
5979
  }
4870
5980
  });
4871
- const title = chartData.style?.hasTitle && chartData.title ? chartData.title : void 0;
5981
+ primitives.push(...horizontalAxis.tickMarks);
5982
+ const displayChartData = horizontalAxis.displayChartData;
5983
+ primitives.push(
5984
+ ...computeErrorBarPrimitives(displayChartData, catCount, layout, primaryRange, "line", {
5985
+ xPositions: horizontalAxis.xPositions,
5986
+ seriesRanges: chartData.series.map(
5987
+ (_series, index) => rangeForSeries(index, primaryRange, secondaryRange, secondaryIndexes)
5988
+ ),
5989
+ seriesModes: chartData.series.map((_series, index) => index === 0 ? "bar" : "line")
5990
+ })
5991
+ );
4872
5992
  return {
4873
5993
  svgWidth: layout.svgWidth,
4874
5994
  svgHeight: layout.svgHeight,
4875
- title,
5995
+ title: chartData.style?.hasTitle && chartData.title ? chartData.title : void 0,
4876
5996
  titleX: layout.svgWidth / 2,
4877
5997
  titleY: 12,
4878
5998
  gridlines,
4879
5999
  axisLabels,
4880
6000
  zeroLine,
4881
- categoryLabels: catLabels,
6001
+ categoryLabels: horizontalAxis.labels,
4882
6002
  primitives,
4883
6003
  dataLabels,
4884
6004
  legend: chartData.style?.hasLegend ? legend : [],
4885
6005
  legendX,
4886
6006
  legendY,
4887
- legendAnchor
6007
+ legendAnchor,
6008
+ secondaryGridlines: secondaryAxis?.gridlines,
6009
+ secondaryAxisLabels: secondaryAxis?.axisLabels
4888
6010
  };
4889
6011
  }
6012
+ function appendBarLabels(series, chartData, layout, catCount, range, sourceIndices2, labels, xPositions) {
6013
+ if (!chartData.style?.hasDataLabels) {
6014
+ return;
6015
+ }
6016
+ const groupWidth = layout.plotWidth / catCount;
6017
+ const barWidth = groupWidth * 0.7;
6018
+ const offset = (groupWidth - barWidth) / 2;
6019
+ sourceIndices2.forEach((sourceIndex, displayIndex) => {
6020
+ const value = series.values[sourceIndex] ?? 0;
6021
+ const zeroY = valueToY2(0, range, layout.plotTop, layout.plotBottom);
6022
+ const valueY = valueToY2(value, range, layout.plotTop, layout.plotBottom);
6023
+ labels.push({
6024
+ kind: "text",
6025
+ x: xPositions?.[displayIndex] ?? layout.plotLeft + groupWidth * displayIndex + offset + barWidth / 2,
6026
+ y: value >= 0 ? Math.min(zeroY, valueY) - 4 : Math.max(zeroY, valueY) + 10,
6027
+ text: formatAxisValue2(value),
6028
+ fontSize: 7,
6029
+ fill: "#334155",
6030
+ textAnchor: "middle"
6031
+ });
6032
+ });
6033
+ }
6034
+ var CANDLE_UP_FILL = "#22c55e";
6035
+ var CANDLE_DOWN_FILL = "#ef4444";
6036
+ var CANDLE_WICK_COLOR = "#334155";
4890
6037
  function buildStockViewModel(element, chartData, categoryLabels) {
4891
- const layout = computePlotLayout(element.width, element.height, chartData, true);
6038
+ const layout = computePlotLayout(
6039
+ element.width,
6040
+ element.height,
6041
+ chartData,
6042
+ true,
6043
+ computeLayoutOptions(chartData.axes, chartData.dataTable, chartData.series.length)
6044
+ );
4892
6045
  const catCount = Math.max(categoryLabels.length, 1);
4893
6046
  const range = computeValueRange2(chartData.series);
4894
- const { gridlines, axisLabels } = buildGridlinesAndLabels(range, layout);
6047
+ const valueAxis = chartData.axes?.find((axis) => axis.axisType === "valAx" && axis.axPos !== "r");
6048
+ const categoryAxis = chartData.axes?.find(
6049
+ (axis) => (axis.axisType === "catAx" || axis.axisType === "dateAx") && axis.axisId === valueAxis?.crossAxisId
6050
+ );
6051
+ const renderedAxis = categoryAxis?.crosses !== void 0 || categoryAxis?.crossesAt !== void 0 ? buildPrimaryAxis(
6052
+ range,
6053
+ layout,
6054
+ valueAxis,
6055
+ verticalAxisX(categoryAxis, catCount, layout, "left", chartData.dateCategories?.values)
6056
+ ) : buildGridlinesAndLabels(range, layout);
6057
+ const { gridlines, axisLabels } = renderedAxis;
4895
6058
  const zeroLine = buildZeroLine(range, layout);
4896
- const catLabels = buildCategoryLabels(categoryLabels, layout, "bar");
6059
+ const horizontalAxis = buildCartesianHorizontalAxis(
6060
+ chartData,
6061
+ categoryLabels,
6062
+ layout,
6063
+ "stock",
6064
+ range
6065
+ );
6066
+ const sourceIndices2 = horizontalAxis.sourceIndices;
4897
6067
  const legendPos = chartData.style?.legendPosition ?? "b";
4898
6068
  const { legend, legendX, legendY, legendAnchor } = buildLegend(
4899
6069
  chartData.series,
@@ -4913,13 +6083,14 @@ function buildStockViewModel(element, chartData, categoryLabels) {
4913
6083
  if (highSeries && lowSeries && closeSeries) {
4914
6084
  const barGroupWidth = layout.plotWidth / catCount;
4915
6085
  const candleWidth = barGroupWidth * 0.5;
4916
- for (let ci = 0; ci < catCount; ci++) {
4917
- const high = highSeries.values[ci] ?? 0;
4918
- const low = lowSeries.values[ci] ?? 0;
4919
- const open = openSeries ? openSeries.values[ci] ?? low : low;
4920
- const close = closeSeries.values[ci] ?? high;
6086
+ for (let displayIndex = 0; displayIndex < catCount; displayIndex++) {
6087
+ const sourceIndex = sourceIndices2[displayIndex] ?? displayIndex;
6088
+ const high = highSeries.values[sourceIndex] ?? 0;
6089
+ const low = lowSeries.values[sourceIndex] ?? 0;
6090
+ const open = openSeries ? openSeries.values[sourceIndex] ?? low : low;
6091
+ const close = closeSeries.values[sourceIndex] ?? high;
4921
6092
  const isUp = close >= open;
4922
- const cx = layout.plotLeft + barGroupWidth * ci + barGroupWidth / 2;
6093
+ const cx = horizontalAxis.xPositions?.[displayIndex] ?? layout.plotLeft + barGroupWidth * displayIndex + barGroupWidth / 2;
4923
6094
  const highY = valueToY2(high, range, layout.plotTop, layout.plotBottom);
4924
6095
  const lowY = valueToY2(low, range, layout.plotTop, layout.plotBottom);
4925
6096
  const openY = valueToY2(open, range, layout.plotTop, layout.plotBottom);
@@ -4942,7 +6113,12 @@ function buildStockViewModel(element, chartData, categoryLabels) {
4942
6113
  w: candleWidth,
4943
6114
  h: bodyHeight,
4944
6115
  fill: isUp ? CANDLE_UP_FILL : CANDLE_DOWN_FILL,
4945
- rx: 1
6116
+ rx: 1,
6117
+ part: {
6118
+ role: "dataPoint",
6119
+ seriesIndex: hasFour ? 3 : 2,
6120
+ pointIndex: sourceIndex
6121
+ }
4946
6122
  });
4947
6123
  if (chartData.style?.hasDataLabels) {
4948
6124
  dataLabels.push({
@@ -4957,6 +6133,7 @@ function buildStockViewModel(element, chartData, categoryLabels) {
4957
6133
  }
4958
6134
  }
4959
6135
  }
6136
+ primitives.push(...horizontalAxis.tickMarks);
4960
6137
  const title = chartData.style?.hasTitle && chartData.title ? chartData.title : void 0;
4961
6138
  return {
4962
6139
  svgWidth: layout.svgWidth,
@@ -4967,7 +6144,7 @@ function buildStockViewModel(element, chartData, categoryLabels) {
4967
6144
  gridlines,
4968
6145
  axisLabels,
4969
6146
  zeroLine,
4970
- categoryLabels: catLabels,
6147
+ categoryLabels: horizontalAxis.labels,
4971
6148
  primitives,
4972
6149
  dataLabels,
4973
6150
  legend: chartData.style?.hasLegend ? legend : [],
@@ -4976,255 +6153,544 @@ function buildStockViewModel(element, chartData, categoryLabels) {
4976
6153
  legendAnchor
4977
6154
  };
4978
6155
  }
4979
- var WHISKER_COLOR = "#64748b";
4980
- var MEDIAN_COLOR = "#1e293b";
4981
- var DATA_LABEL_COLOR = "#334155";
6156
+ function percentile(sorted, p, method) {
6157
+ const rank = method === "inclusive" ? (sorted.length - 1) * p : (sorted.length + 1) * p - 1;
6158
+ const clamped = Math.max(0, Math.min(rank, sorted.length - 1));
6159
+ const lower = Math.floor(clamped);
6160
+ const upper = Math.ceil(clamped);
6161
+ const fraction = clamped - lower;
6162
+ return sorted[lower] + (sorted[upper] - sorted[lower]) * fraction;
6163
+ }
6164
+ function computeBoxStats(values, method) {
6165
+ if (values.length < 2) {
6166
+ return void 0;
6167
+ }
6168
+ const sorted = [...values].sort((a, b) => a - b);
6169
+ if (!method) {
6170
+ return {
6171
+ min: sorted[0],
6172
+ q1: sorted[Math.floor(sorted.length * 0.25)],
6173
+ median: sorted[Math.floor(sorted.length * 0.5)],
6174
+ q3: sorted[Math.floor(sorted.length * 0.75)],
6175
+ max: sorted[sorted.length - 1]
6176
+ };
6177
+ }
6178
+ return {
6179
+ min: sorted[0],
6180
+ q1: percentile(sorted, 0.25, method),
6181
+ median: percentile(sorted, 0.5, method),
6182
+ q3: percentile(sorted, 0.75, method),
6183
+ max: sorted[sorted.length - 1]
6184
+ };
6185
+ }
4982
6186
  function distributionRange(series) {
4983
- const all = series.flatMap((s) => s.values);
6187
+ const all = series.flatMap((item) => item.values);
4984
6188
  const min = Math.min(...all, 0);
4985
6189
  const max = Math.max(...all, 1);
4986
6190
  return { min, max, span: Math.max(max - min, 1) };
4987
6191
  }
6192
+ var WHISKER_COLOR = "#64748b";
6193
+ var MEDIAN_COLOR = "#1e293b";
6194
+ function computeBoxWhiskerGeometry(chartData, catCount, layout, range, colorPalette) {
6195
+ const groupWidth = layout.plotWidth / catCount;
6196
+ const boxW = groupWidth * 0.5;
6197
+ const options = chartData.series.find((series) => series.boxWhiskerOptions)?.boxWhiskerOptions;
6198
+ const method = options ? options.quartileMethod ?? "exclusive" : void 0;
6199
+ const output = [];
6200
+ for (let categoryIndex = 0; categoryIndex < catCount; categoryIndex++) {
6201
+ const observations = chartData.series.map((series, seriesIndex) => ({ value: series.values[categoryIndex], seriesIndex })).filter((item) => item.value !== void 0);
6202
+ const stats = computeBoxStats(
6203
+ observations.map((item) => item.value),
6204
+ method
6205
+ );
6206
+ if (!stats) {
6207
+ continue;
6208
+ }
6209
+ const iqr = stats.q3 - stats.q1;
6210
+ const lowerFence = stats.q1 - 1.5 * iqr;
6211
+ const upperFence = stats.q3 + 1.5 * iqr;
6212
+ const inliers = observations.filter(
6213
+ (item) => !options || item.value >= lowerFence && item.value <= upperFence
6214
+ );
6215
+ const whiskerMin = Math.min(...inliers.map((item) => item.value));
6216
+ const whiskerMax = Math.max(...inliers.map((item) => item.value));
6217
+ const boxX = layout.plotLeft + groupWidth * categoryIndex + (groupWidth - boxW) / 2;
6218
+ const mean = observations.reduce((sum, item) => sum + item.value, 0) / observations.length;
6219
+ output.push({
6220
+ stats,
6221
+ boxX,
6222
+ boxW,
6223
+ xMid: boxX + boxW / 2,
6224
+ yMin: valueToY2(whiskerMin, range, layout.plotTop, layout.plotBottom),
6225
+ yMax: valueToY2(whiskerMax, range, layout.plotTop, layout.plotBottom),
6226
+ yQ1: valueToY2(stats.q1, range, layout.plotTop, layout.plotBottom),
6227
+ yQ3: valueToY2(stats.q3, range, layout.plotTop, layout.plotBottom),
6228
+ yMed: valueToY2(stats.median, range, layout.plotTop, layout.plotBottom),
6229
+ yMean: valueToY2(mean, range, layout.plotTop, layout.plotBottom),
6230
+ fill: paletteColor2(categoryIndex, colorPalette),
6231
+ points: observations.map((item, index) => ({
6232
+ x: boxX + boxW * (0.2 + 0.6 * (index + 1) / (observations.length + 1)),
6233
+ y: valueToY2(item.value, range, layout.plotTop, layout.plotBottom),
6234
+ seriesIndex: item.seriesIndex,
6235
+ outlier: item.value < whiskerMin || item.value > whiskerMax
6236
+ }))
6237
+ });
6238
+ }
6239
+ return output;
6240
+ }
6241
+ function whiskerPrimitives(geometry) {
6242
+ const g = geometry;
6243
+ return [
6244
+ {
6245
+ kind: "line",
6246
+ x1: g.xMid,
6247
+ y1: g.yMax,
6248
+ x2: g.xMid,
6249
+ y2: g.yQ3,
6250
+ stroke: WHISKER_COLOR,
6251
+ strokeWidth: 1
6252
+ },
6253
+ {
6254
+ kind: "line",
6255
+ x1: g.xMid,
6256
+ y1: g.yQ1,
6257
+ x2: g.xMid,
6258
+ y2: g.yMin,
6259
+ stroke: WHISKER_COLOR,
6260
+ strokeWidth: 1
6261
+ },
6262
+ {
6263
+ kind: "line",
6264
+ x1: g.boxX + g.boxW * 0.25,
6265
+ y1: g.yMax,
6266
+ x2: g.boxX + g.boxW * 0.75,
6267
+ y2: g.yMax,
6268
+ stroke: WHISKER_COLOR,
6269
+ strokeWidth: 1
6270
+ },
6271
+ {
6272
+ kind: "line",
6273
+ x1: g.boxX + g.boxW * 0.25,
6274
+ y1: g.yMin,
6275
+ x2: g.boxX + g.boxW * 0.75,
6276
+ y2: g.yMin,
6277
+ stroke: WHISKER_COLOR,
6278
+ strokeWidth: 1
6279
+ },
6280
+ {
6281
+ kind: "rect",
6282
+ x: g.boxX,
6283
+ y: Math.min(g.yQ1, g.yQ3),
6284
+ w: g.boxW,
6285
+ h: Math.abs(g.yQ1 - g.yQ3),
6286
+ fill: g.fill,
6287
+ rx: 1,
6288
+ opacity: 0.8
6289
+ },
6290
+ {
6291
+ kind: "line",
6292
+ x1: g.boxX,
6293
+ y1: g.yMed,
6294
+ x2: g.boxX + g.boxW,
6295
+ y2: g.yMed,
6296
+ stroke: MEDIAN_COLOR,
6297
+ strokeWidth: 2
6298
+ }
6299
+ ];
6300
+ }
6301
+ function optionPrimitives(geometry, options, categoryIndex) {
6302
+ if (!options) {
6303
+ return [];
6304
+ }
6305
+ const output = [];
6306
+ if (options.showMeanLine) {
6307
+ output.push({
6308
+ kind: "line",
6309
+ x1: geometry.boxX,
6310
+ y1: geometry.yMean,
6311
+ x2: geometry.boxX + geometry.boxW,
6312
+ y2: geometry.yMean,
6313
+ stroke: "#0f766e",
6314
+ strokeWidth: 1.5
6315
+ });
6316
+ }
6317
+ if (options.showMeanMarker) {
6318
+ output.push({
6319
+ kind: "circle",
6320
+ cx: geometry.xMid,
6321
+ cy: geometry.yMean,
6322
+ r: 3,
6323
+ fill: "#0f766e"
6324
+ });
6325
+ }
6326
+ for (const point of geometry.points) {
6327
+ if (point.outlier && !options.showOutlierPoints || !point.outlier && !options.showInnerPoints) {
6328
+ continue;
6329
+ }
6330
+ output.push({
6331
+ kind: "circle",
6332
+ cx: point.x,
6333
+ cy: point.y,
6334
+ r: 2.25,
6335
+ fill: point.outlier ? "#dc2626" : MEDIAN_COLOR,
6336
+ part: { role: "dataPoint", seriesIndex: point.seriesIndex, pointIndex: categoryIndex }
6337
+ });
6338
+ }
6339
+ return output;
6340
+ }
6341
+ function buildBoxWhiskerViewModel(element, chartData, categoryLabels) {
6342
+ const layout = computePlotLayout(element.width, element.height, chartData, true);
6343
+ const range = distributionRange(chartData.series);
6344
+ const options = chartData.series.find((series) => series.boxWhiskerOptions)?.boxWhiskerOptions;
6345
+ const geometries = computeBoxWhiskerGeometry(
6346
+ chartData,
6347
+ Math.max(categoryLabels.length, 1),
6348
+ layout,
6349
+ range,
6350
+ chartData.colorPalette
6351
+ );
6352
+ const primitives = geometries.flatMap((geometry, index) => [
6353
+ ...whiskerPrimitives(geometry),
6354
+ ...optionPrimitives(geometry, options, index)
6355
+ ]);
6356
+ const { gridlines, axisLabels } = buildGridlinesAndLabels(range, layout);
6357
+ const { legend, legendX, legendY, legendAnchor } = buildLegend(
6358
+ chartData.series,
6359
+ chartData.colorPalette,
6360
+ layout.svgWidth,
6361
+ chartData.style?.legendPosition ?? "b",
6362
+ layout.svgHeight,
6363
+ layout.plotTop
6364
+ );
6365
+ const categoryLegend = categoryLabels.map((label, index) => ({
6366
+ color: paletteColor2(index, chartData.colorPalette),
6367
+ label
6368
+ }));
6369
+ return {
6370
+ svgWidth: layout.svgWidth,
6371
+ svgHeight: layout.svgHeight,
6372
+ title: chartData.style?.hasTitle ? chartData.title : void 0,
6373
+ titleX: layout.svgWidth / 2,
6374
+ titleY: 12,
6375
+ gridlines,
6376
+ axisLabels,
6377
+ zeroLine: buildZeroLine(range, layout),
6378
+ categoryLabels: buildCategoryLabels(categoryLabels, layout, "bar"),
6379
+ primitives,
6380
+ dataLabels: [],
6381
+ legend: chartData.style?.hasLegend ? categoryLegend.length ? categoryLegend : legend : [],
6382
+ legendX,
6383
+ legendY,
6384
+ legendAnchor
6385
+ };
6386
+ }
6387
+ function orderParetoEntries(entries) {
6388
+ return [...entries].sort(
6389
+ (left, right) => Math.max(right.value, 0) - Math.max(left.value, 0) || left.sourcePointIndex - right.sourcePointIndex
6390
+ );
6391
+ }
6392
+ function buildParetoPrimitives(entries, layout, series, seriesIndex) {
6393
+ const total = entries.reduce((sum, entry) => sum + Math.max(entry.value, 0), 0);
6394
+ if (total <= 0) {
6395
+ return [];
6396
+ }
6397
+ let cumulative = 0;
6398
+ const points = entries.map((entry, displayIndex) => {
6399
+ cumulative += Math.max(entry.value, 0);
6400
+ const percentage = displayIndex === entries.length - 1 ? 100 : cumulative / total * 100;
6401
+ return {
6402
+ x: layout.plotLeft + layout.plotWidth * (displayIndex + 0.5) / entries.length,
6403
+ y: layout.plotBottom - layout.plotHeight * percentage / 100,
6404
+ pointIndex: entry.sourcePointIndex
6405
+ };
6406
+ });
6407
+ const color = series.color ?? "#ED7D31";
6408
+ return [
6409
+ {
6410
+ kind: "polyline",
6411
+ points: points.map((point) => `${point.x},${point.y}`).join(" "),
6412
+ stroke: color,
6413
+ strokeWidth: 2,
6414
+ fill: "none",
6415
+ part: { role: "series", seriesIndex }
6416
+ },
6417
+ ...points.map(
6418
+ (point) => ({
6419
+ kind: "circle",
6420
+ cx: point.x,
6421
+ cy: point.y,
6422
+ r: 2.5,
6423
+ fill: color,
6424
+ part: { role: "dataPoint", seriesIndex, pointIndex: point.pointIndex }
6425
+ })
6426
+ )
6427
+ ];
6428
+ }
6429
+ function buildParetoAxis(layout) {
6430
+ const axis = buildSecondaryAxis({ min: 0, max: 100, span: 100 }, layout, {
6431
+ majorUnit: 20,
6432
+ majorTickMark: "out"
6433
+ });
6434
+ return {
6435
+ secondaryGridlines: axis.gridlines,
6436
+ secondaryAxisLabels: axis.axisLabels.map((label) => ({
6437
+ ...label,
6438
+ text: `${label.text}%`
6439
+ }))
6440
+ };
6441
+ }
6442
+ var DATA_LABEL_COLOR = "#334155";
6443
+ function binLabel(lower, upper, closed) {
6444
+ return closed === "r" ? `(${formatAxisValue2(lower)}, ${formatAxisValue2(upper)}]` : `[${formatAxisValue2(lower)}, ${formatAxisValue2(upper)})`;
6445
+ }
6446
+ function computeHistogramBins(values, options) {
6447
+ const closed = options.intervalClosed ?? "l";
6448
+ const finite = values.map((value, sourceIndex) => ({ value, sourceIndex })).filter((item) => Number.isFinite(item.value));
6449
+ if (finite.length === 0) {
6450
+ return [];
6451
+ }
6452
+ const underflow = typeof options.underflow === "number" ? options.underflow : void 0;
6453
+ const overflow = typeof options.overflow === "number" ? options.overflow : void 0;
6454
+ const under = underflow === void 0 ? [] : finite.filter(
6455
+ (item) => closed === "r" ? item.value <= underflow : item.value < underflow
6456
+ );
6457
+ const over = overflow === void 0 ? [] : finite.filter((item) => closed === "l" ? item.value >= overflow : item.value > overflow);
6458
+ const regular = finite.filter((item) => !under.includes(item) && !over.includes(item));
6459
+ const result = [];
6460
+ if (underflow !== void 0) {
6461
+ result.push({
6462
+ value: under.length,
6463
+ label: `${closed === "r" ? "\u2264" : "<"} ${formatAxisValue2(underflow)}`,
6464
+ sourceIndices: under.map((item) => item.sourceIndex)
6465
+ });
6466
+ }
6467
+ if (regular.length > 0) {
6468
+ const min = Math.min(...regular.map((item) => item.value));
6469
+ const max = Math.max(...regular.map((item) => item.value));
6470
+ const requestedSize = options.binSize && options.binSize > 0 ? options.binSize : void 0;
6471
+ const requestedCount = options.binCount && options.binCount > 0 ? options.binCount : void 0;
6472
+ const start = requestedSize ? Math.floor(min / requestedSize) * requestedSize : min;
6473
+ const count = requestedSize ? Math.max(
6474
+ closed === "l" ? Math.floor((max - start) / requestedSize) + 1 : Math.ceil((max - start) / requestedSize),
6475
+ 1
6476
+ ) : Math.max(requestedCount ?? Math.ceil(Math.sqrt(regular.length)), 1);
6477
+ const width = requestedSize ?? Math.max((max - start) / count, 1);
6478
+ const bins = Array.from({ length: count }, (_, index) => ({
6479
+ value: 0,
6480
+ label: binLabel(start + index * width, start + (index + 1) * width, closed),
6481
+ sourceIndices: []
6482
+ }));
6483
+ for (const item of regular) {
6484
+ const rawIndex = closed === "r" ? Math.ceil((item.value - start) / width) - 1 : Math.floor((item.value - start) / width);
6485
+ const index = Math.max(0, Math.min(rawIndex, bins.length - 1));
6486
+ bins[index].value += 1;
6487
+ bins[index].sourceIndices.push(item.sourceIndex);
6488
+ }
6489
+ result.push(...bins);
6490
+ }
6491
+ if (overflow !== void 0) {
6492
+ result.push({
6493
+ value: over.length,
6494
+ label: `${closed === "l" ? "\u2265" : ">"} ${formatAxisValue2(overflow)}`,
6495
+ sourceIndices: over.map((item) => item.sourceIndex)
6496
+ });
6497
+ }
6498
+ return result;
6499
+ }
4988
6500
  function computeHistogramBars(values, catCount, layout, range, seriesColorOverride, colorPalette) {
4989
6501
  const count = Math.max(catCount, values.length, 1);
4990
6502
  const barWidth = layout.plotWidth / count;
4991
- return values.map((val2, i) => {
4992
- const x = layout.plotLeft + barWidth * i;
6503
+ return values.map((val2, pointIndex) => {
4993
6504
  const zeroY = valueToY2(0, range, layout.plotTop, layout.plotBottom);
4994
6505
  const valY = valueToY2(val2, range, layout.plotTop, layout.plotBottom);
4995
- const y = Math.min(zeroY, valY);
4996
- const h = Math.max(Math.abs(zeroY - valY), 1);
4997
6506
  return {
4998
- x,
4999
- y,
6507
+ x: layout.plotLeft + barWidth * pointIndex,
6508
+ y: Math.min(zeroY, valY),
5000
6509
  w: Math.max(barWidth - 0.5, 1),
5001
- h,
5002
- fill: seriesColorOverride ?? paletteColor2(i, colorPalette)
6510
+ h: Math.max(Math.abs(zeroY - valY), 1),
6511
+ fill: seriesColorOverride ?? paletteColor2(pointIndex, colorPalette),
6512
+ pointIndex
5003
6513
  };
5004
6514
  });
5005
6515
  }
5006
6516
  function buildHistogramViewModel(element, chartData, categoryLabels) {
5007
- const layout = computePlotLayout(element.width, element.height, chartData, true);
5008
- const range = distributionRange(chartData.series);
5009
- const values = chartData.series[0]?.values ?? [];
5010
- const catCount = Math.max(categoryLabels.length, values.length, 1);
5011
- const barWidth = layout.plotWidth / catCount;
6517
+ const paretoIndex = chartData.series.findIndex(
6518
+ (item) => item.histogramOptions?.layout === "pareto"
6519
+ );
6520
+ const layout = computePlotLayout(element.width, element.height, chartData, true, {
6521
+ hasSecondaryValueAxis: paretoIndex >= 0
6522
+ });
6523
+ const histogramIndex = Math.max(
6524
+ chartData.series.findIndex((series2) => series2.histogramOptions?.layout !== "pareto"),
6525
+ 0
6526
+ );
6527
+ const series = chartData.series[histogramIndex];
6528
+ const options = series?.histogramOptions;
6529
+ const bins = options?.layout === "histogram" ? computeHistogramBins(series?.values ?? [], options) : void 0;
6530
+ const baseEntries = (bins?.map((bin) => ({ value: bin.value, label: bin.label })) ?? (series?.values ?? []).map((value, index) => ({
6531
+ value,
6532
+ label: categoryLabels[index] ?? ""
6533
+ }))).map((entry, sourcePointIndex) => ({ ...entry, sourcePointIndex }));
6534
+ const entries = paretoIndex >= 0 ? orderParetoEntries(baseEntries) : baseEntries;
6535
+ const values = entries.map((entry) => entry.value);
6536
+ const labels = entries.map((entry) => entry.label);
6537
+ const range = distributionRange([{ name: series?.name ?? "", values }]);
5012
6538
  const bars = computeHistogramBars(
5013
6539
  values,
5014
- categoryLabels.length,
6540
+ labels.length,
5015
6541
  layout,
5016
6542
  range,
5017
- chartData.series[0]?.color,
6543
+ series?.color,
5018
6544
  chartData.colorPalette
5019
6545
  );
5020
6546
  const primitives = bars.map(
5021
- (b) => ({
6547
+ (bar, displayIndex) => ({
5022
6548
  kind: "rect",
5023
- x: b.x,
5024
- y: b.y,
5025
- w: b.w,
5026
- h: b.h,
5027
- fill: b.fill,
5028
- opacity: 0.85
6549
+ x: bar.x,
6550
+ y: bar.y,
6551
+ w: bar.w,
6552
+ h: bar.h,
6553
+ fill: bar.fill,
6554
+ opacity: 0.85,
6555
+ ...bins ? {} : {
6556
+ part: {
6557
+ role: "dataPoint",
6558
+ seriesIndex: histogramIndex,
6559
+ pointIndex: entries[displayIndex]?.sourcePointIndex ?? displayIndex
6560
+ }
6561
+ }
5029
6562
  })
5030
6563
  );
5031
- const dataLabels = [];
5032
- if (chartData.style?.hasDataLabels) {
5033
- bars.forEach((b, i) => {
5034
- const val2 = values[i];
5035
- if (val2 === void 0) {
5036
- return;
5037
- }
5038
- dataLabels.push({
5039
- kind: "text",
5040
- x: b.x + barWidth / 2,
5041
- y: b.y - 4,
5042
- text: formatAxisValue2(val2),
5043
- fontSize: 7,
5044
- fill: DATA_LABEL_COLOR,
5045
- textAnchor: "middle"
5046
- });
5047
- });
6564
+ if (paretoIndex >= 0) {
6565
+ primitives.push(
6566
+ ...buildParetoPrimitives(entries, layout, chartData.series[paretoIndex], paretoIndex)
6567
+ );
5048
6568
  }
6569
+ const dataLabels = chartData.style?.hasDataLabels ? bars.map((bar, index) => ({
6570
+ kind: "text",
6571
+ x: bar.x + bar.w / 2,
6572
+ y: bar.y - 4,
6573
+ text: formatAxisValue2(values[index]),
6574
+ fontSize: 7,
6575
+ fill: DATA_LABEL_COLOR,
6576
+ textAnchor: "middle"
6577
+ })) : [];
5049
6578
  const { gridlines, axisLabels } = buildGridlinesAndLabels(range, layout);
5050
- const zeroLine = buildZeroLine(range, layout);
5051
- const catLabels = buildCategoryLabels(categoryLabels, layout, "bar");
5052
- const legendPos = chartData.style?.legendPosition ?? "b";
6579
+ const paretoAxis = paretoIndex >= 0 ? buildParetoAxis(layout) : void 0;
5053
6580
  const { legend, legendX, legendY, legendAnchor } = buildLegend(
5054
6581
  chartData.series,
5055
6582
  chartData.colorPalette,
5056
6583
  layout.svgWidth,
5057
- legendPos,
6584
+ chartData.style?.legendPosition ?? "b",
5058
6585
  layout.svgHeight,
5059
6586
  layout.plotTop
5060
6587
  );
5061
- const title = chartData.style?.hasTitle && chartData.title ? chartData.title : void 0;
5062
6588
  return {
5063
6589
  svgWidth: layout.svgWidth,
5064
6590
  svgHeight: layout.svgHeight,
5065
- title,
6591
+ title: chartData.style?.hasTitle ? chartData.title : void 0,
5066
6592
  titleX: layout.svgWidth / 2,
5067
6593
  titleY: 12,
5068
6594
  gridlines,
5069
6595
  axisLabels,
5070
- zeroLine,
5071
- categoryLabels: catLabels,
6596
+ zeroLine: buildZeroLine(range, layout),
6597
+ categoryLabels: buildCategoryLabels(labels, layout, "bar"),
5072
6598
  primitives,
5073
6599
  dataLabels,
5074
6600
  legend: chartData.style?.hasLegend ? legend : [],
5075
6601
  legendX,
5076
6602
  legendY,
5077
- legendAnchor
6603
+ legendAnchor,
6604
+ secondaryGridlines: paretoAxis?.secondaryGridlines,
6605
+ secondaryAxisLabels: paretoAxis?.secondaryAxisLabels
5078
6606
  };
5079
6607
  }
5080
- function computeBoxStats(values) {
5081
- if (values.length < 2) {
5082
- return void 0;
5083
- }
5084
- const sorted = [...values].sort((a, b) => a - b);
5085
- const n = sorted.length;
5086
- return {
5087
- min: sorted[0],
5088
- q1: sorted[Math.floor(n * 0.25)],
5089
- median: sorted[Math.floor(n * 0.5)],
5090
- q3: sorted[Math.floor(n * 0.75)],
5091
- max: sorted[n - 1]
5092
- };
6608
+ function arcPath(cx, cy, innerRadius, outerRadius, startAngle, endAngle) {
6609
+ const sweep = endAngle - startAngle;
6610
+ const largeArc = sweep > Math.PI ? 1 : 0;
6611
+ const x1 = cx + outerRadius * Math.cos(startAngle);
6612
+ const y1 = cy + outerRadius * Math.sin(startAngle);
6613
+ const x2 = cx + outerRadius * Math.cos(endAngle);
6614
+ const y2 = cy + outerRadius * Math.sin(endAngle);
6615
+ const x3 = cx + innerRadius * Math.cos(endAngle);
6616
+ const y3 = cy + innerRadius * Math.sin(endAngle);
6617
+ const x4 = cx + innerRadius * Math.cos(startAngle);
6618
+ const y4 = cy + innerRadius * Math.sin(startAngle);
6619
+ return `M ${x1} ${y1} A ${outerRadius} ${outerRadius} 0 ${largeArc} 1 ${x2} ${y2} L ${x3} ${y3} A ${innerRadius} ${innerRadius} 0 ${largeArc} 0 ${x4} ${y4} Z`;
5093
6620
  }
5094
- function computeBoxWhiskerGeometry(chartData, catCount, layout, range, colorPalette) {
5095
- const boxGroupW = layout.plotWidth / catCount;
5096
- const boxW = boxGroupW * 0.5;
5097
- const boxOffset = (boxGroupW - boxW) / 2;
6621
+ function computeSunburstArcs(series, cx, cy, maxR, colorPalette) {
6622
+ const seriesCount = Math.max(series.length, 1);
6623
+ const ringWidth = maxR / (seriesCount + 0.5);
5098
6624
  const out = [];
5099
- for (let ci = 0; ci < catCount; ci++) {
5100
- const catVals = chartData.series.map((s) => s.values[ci] ?? 0);
5101
- const stats = computeBoxStats(catVals);
5102
- if (!stats) {
5103
- continue;
6625
+ for (let si = 0; si < series.length; si++) {
6626
+ const values = series[si].values;
6627
+ const innerRadius = ringWidth * (si + 0.5);
6628
+ const outerRadius = ringWidth * (si + 1.5);
6629
+ const total = values.reduce((acc, value) => acc + Math.abs(value), 0) || 1;
6630
+ let startAngle = -Math.PI / 2;
6631
+ for (let pointIndex = 0; pointIndex < values.length; pointIndex++) {
6632
+ const endAngle = startAngle + Math.abs(values[pointIndex]) / total * Math.PI * 2;
6633
+ out.push({
6634
+ d: arcPath(cx, cy, innerRadius, outerRadius, startAngle, endAngle),
6635
+ fill: paletteColor2(pointIndex, colorPalette),
6636
+ opacity: Math.max(0.1, 0.9 - si * 0.1),
6637
+ pointIndex,
6638
+ level: si
6639
+ });
6640
+ startAngle = endAngle;
5104
6641
  }
5105
- const boxX = layout.plotLeft + boxGroupW * ci + boxOffset;
5106
- out.push({
5107
- stats,
5108
- boxX,
5109
- boxW,
5110
- xMid: boxX + boxW / 2,
5111
- yMin: valueToY2(stats.min, range, layout.plotTop, layout.plotBottom),
5112
- yMax: valueToY2(stats.max, range, layout.plotTop, layout.plotBottom),
5113
- yQ1: valueToY2(stats.q1, range, layout.plotTop, layout.plotBottom),
5114
- yQ3: valueToY2(stats.q3, range, layout.plotTop, layout.plotBottom),
5115
- yMed: valueToY2(stats.median, range, layout.plotTop, layout.plotBottom),
5116
- fill: paletteColor2(ci, colorPalette)
5117
- });
5118
6642
  }
5119
6643
  return out;
5120
6644
  }
5121
- function buildBoxWhiskerViewModel(element, chartData, categoryLabels) {
5122
- const layout = computePlotLayout(element.width, element.height, chartData, true);
5123
- const range = distributionRange(chartData.series);
5124
- const catCount = Math.max(categoryLabels.length, 1);
5125
- const geometries = computeBoxWhiskerGeometry(
5126
- chartData,
5127
- catCount,
5128
- layout,
5129
- range,
5130
- chartData.colorPalette
5131
- );
5132
- const primitives = [];
5133
- for (const g of geometries) {
5134
- primitives.push(
5135
- {
5136
- kind: "line",
5137
- x1: g.xMid,
5138
- y1: g.yMax,
5139
- x2: g.xMid,
5140
- y2: g.yQ3,
5141
- stroke: WHISKER_COLOR,
5142
- strokeWidth: 1
5143
- },
5144
- {
5145
- kind: "line",
5146
- x1: g.xMid,
5147
- y1: g.yQ1,
5148
- x2: g.xMid,
5149
- y2: g.yMin,
5150
- stroke: WHISKER_COLOR,
5151
- strokeWidth: 1
6645
+ function hierarchyKey(levels, level, index) {
6646
+ return levels.slice(level).map((labels) => labels[index] ?? "").join("\0");
6647
+ }
6648
+ function computeHierarchicalSunburstArcs(categoryLevels, values, cx, cy, maxR, colorPalette) {
6649
+ const levels = categoryLevels.filter((level) => level.length > 0);
6650
+ const leafCount = Math.max(values.length, ...levels.map((level) => level.length), 0);
6651
+ if (levels.length === 0 || leafCount === 0) {
6652
+ return [];
6653
+ }
6654
+ const rawWeights = Array.from({ length: leafCount }, (_, index) => Math.abs(values[index] ?? 0));
6655
+ const weights = rawWeights.some((value) => value !== 0) ? rawWeights : rawWeights.map(() => 1);
6656
+ const total = weights.reduce((sum, value) => sum + value, 0);
6657
+ const prefix = [0];
6658
+ for (const weight of weights) {
6659
+ prefix.push((prefix[prefix.length - 1] ?? 0) + weight);
6660
+ }
6661
+ const ringWidth = maxR / (levels.length + 0.5);
6662
+ const rootLabels = levels[levels.length - 1] ?? [];
6663
+ const rootColors = /* @__PURE__ */ new Map();
6664
+ const out = [];
6665
+ for (let level = levels.length - 1; level >= 0; level--) {
6666
+ const ringIndex = levels.length - 1 - level;
6667
+ const innerRadius = ringWidth * (ringIndex + 0.5);
6668
+ const outerRadius = ringWidth * (ringIndex + 1.5);
6669
+ let start = 0;
6670
+ while (start < leafCount) {
6671
+ const key = hierarchyKey(levels, level, start);
6672
+ let end = start + 1;
6673
+ while (end < leafCount && hierarchyKey(levels, level, end) === key) {
6674
+ end++;
5152
6675
  }
5153
- );
5154
- primitives.push(
5155
- {
5156
- kind: "line",
5157
- x1: g.boxX + g.boxW * 0.25,
5158
- y1: g.yMax,
5159
- x2: g.boxX + g.boxW * 0.75,
5160
- y2: g.yMax,
5161
- stroke: WHISKER_COLOR,
5162
- strokeWidth: 1
5163
- },
5164
- {
5165
- kind: "line",
5166
- x1: g.boxX + g.boxW * 0.25,
5167
- y1: g.yMin,
5168
- x2: g.boxX + g.boxW * 0.75,
5169
- y2: g.yMin,
5170
- stroke: WHISKER_COLOR,
5171
- strokeWidth: 1
6676
+ const startAngle = -Math.PI / 2 + prefix[start] / total * Math.PI * 2;
6677
+ const endAngle = -Math.PI / 2 + prefix[end] / total * Math.PI * 2;
6678
+ const rootLabel = rootLabels[start] ?? "";
6679
+ if (!rootColors.has(rootLabel)) {
6680
+ rootColors.set(rootLabel, rootColors.size);
5172
6681
  }
5173
- );
5174
- primitives.push({
5175
- kind: "rect",
5176
- x: g.boxX,
5177
- y: Math.min(g.yQ1, g.yQ3),
5178
- w: g.boxW,
5179
- h: Math.abs(g.yQ1 - g.yQ3),
5180
- fill: g.fill,
5181
- rx: 1,
5182
- opacity: 0.8
5183
- });
5184
- primitives.push({
5185
- kind: "line",
5186
- x1: g.boxX,
5187
- y1: g.yMed,
5188
- x2: g.boxX + g.boxW,
5189
- y2: g.yMed,
5190
- stroke: MEDIAN_COLOR,
5191
- strokeWidth: 2
5192
- });
6682
+ out.push({
6683
+ d: arcPath(cx, cy, innerRadius, outerRadius, startAngle, endAngle),
6684
+ fill: paletteColor2(rootColors.get(rootLabel) ?? start, colorPalette),
6685
+ opacity: Math.max(0.1, 0.9 - ringIndex * 0.1),
6686
+ level: ringIndex,
6687
+ label: levels[level][start] ?? "",
6688
+ ...level === 0 && end === start + 1 ? { pointIndex: start } : {}
6689
+ });
6690
+ start = end;
6691
+ }
5193
6692
  }
5194
- const { gridlines, axisLabels } = buildGridlinesAndLabels(range, layout);
5195
- const zeroLine = buildZeroLine(range, layout);
5196
- const catLabels = buildCategoryLabels(categoryLabels, layout, "bar");
5197
- const legendPos = chartData.style?.legendPosition ?? "b";
5198
- const { legend, legendX, legendY, legendAnchor } = buildLegend(
5199
- chartData.series,
5200
- chartData.colorPalette,
5201
- layout.svgWidth,
5202
- legendPos,
5203
- layout.svgHeight,
5204
- layout.plotTop
5205
- );
5206
- const catLegend = categoryLabels.map((label, i) => ({
5207
- color: paletteColor2(i, chartData.colorPalette),
5208
- label
5209
- }));
5210
- const title = chartData.style?.hasTitle && chartData.title ? chartData.title : void 0;
5211
- return {
5212
- svgWidth: layout.svgWidth,
5213
- svgHeight: layout.svgHeight,
5214
- title,
5215
- titleX: layout.svgWidth / 2,
5216
- titleY: 12,
5217
- gridlines,
5218
- axisLabels,
5219
- zeroLine,
5220
- categoryLabels: catLabels,
5221
- primitives,
5222
- dataLabels: [],
5223
- legend: chartData.style?.hasLegend ? catLegend.length > 0 ? catLegend : legend : [],
5224
- legendX,
5225
- legendY,
5226
- legendAnchor
5227
- };
6693
+ return out;
5228
6694
  }
5229
6695
  function emptyChrome() {
5230
6696
  return {
@@ -5321,45 +6787,20 @@ function buildFunnelViewModel(element, chartData, categoryLabels) {
5321
6787
  legendAnchor: "middle"
5322
6788
  };
5323
6789
  }
5324
- function computeSunburstArcs(series, cx, cy, maxR, colorPalette) {
5325
- const seriesCount = Math.max(series.length, 1);
5326
- const ringWidth = maxR / (seriesCount + 0.5);
5327
- const out = [];
5328
- for (let si = 0; si < series.length; si++) {
5329
- const s = series[si];
5330
- const iR = ringWidth * (si + 0.5);
5331
- const oR = ringWidth * (si + 1.5);
5332
- const total = s.values.reduce((acc, v) => acc + Math.abs(v), 0) || 1;
5333
- let startAngle = -Math.PI / 2;
5334
- for (let vi = 0; vi < s.values.length; vi++) {
5335
- const val2 = s.values[vi];
5336
- const sweep = Math.abs(val2) / total * Math.PI * 2;
5337
- const endAngle = startAngle + sweep;
5338
- const largeArc = sweep > Math.PI ? 1 : 0;
5339
- const x1 = cx + oR * Math.cos(startAngle);
5340
- const y1 = cy + oR * Math.sin(startAngle);
5341
- const x2 = cx + oR * Math.cos(endAngle);
5342
- const y2 = cy + oR * Math.sin(endAngle);
5343
- const x3 = cx + iR * Math.cos(endAngle);
5344
- const y3 = cy + iR * Math.sin(endAngle);
5345
- const x4 = cx + iR * Math.cos(startAngle);
5346
- const y4 = cy + iR * Math.sin(startAngle);
5347
- out.push({
5348
- d: `M ${x1} ${y1} A ${oR} ${oR} 0 ${largeArc} 1 ${x2} ${y2} L ${x3} ${y3} A ${iR} ${iR} 0 ${largeArc} 0 ${x4} ${y4} Z`,
5349
- fill: paletteColor2(vi, colorPalette),
5350
- opacity: Math.max(0.1, 0.9 - si * 0.1)
5351
- });
5352
- startAngle = endAngle;
5353
- }
5354
- }
5355
- return out;
5356
- }
5357
6790
  function buildSunburstViewModel(element, chartData, categoryLabels) {
5358
6791
  const layout = computePlotLayout(element.width, element.height, chartData, false);
5359
6792
  const cx = layout.plotLeft + layout.plotWidth / 2;
5360
6793
  const cy = layout.plotTop + layout.plotHeight / 2;
5361
6794
  const maxR = Math.min(layout.plotWidth, layout.plotHeight) / 2 - 4;
5362
- const arcs = computeSunburstArcs(chartData.series, cx, cy, maxR, chartData.colorPalette);
6795
+ const categoryLevels = chartData.categoryLevels;
6796
+ const arcs = categoryLevels?.length ? computeHierarchicalSunburstArcs(
6797
+ categoryLevels,
6798
+ chartData.series[0]?.values ?? [],
6799
+ cx,
6800
+ cy,
6801
+ maxR,
6802
+ chartData.colorPalette
6803
+ ) : computeSunburstArcs(chartData.series, cx, cy, maxR, chartData.colorPalette);
5363
6804
  const primitives = arcs.map(
5364
6805
  (arc) => ({
5365
6806
  kind: "path",
@@ -5367,10 +6808,12 @@ function buildSunburstViewModel(element, chartData, categoryLabels) {
5367
6808
  fill: arc.fill,
5368
6809
  stroke: "#ffffff",
5369
6810
  strokeWidth: 1,
5370
- opacity: arc.opacity
6811
+ opacity: arc.opacity,
6812
+ part: arc.pointIndex === void 0 ? void 0 : { role: "dataPoint", seriesIndex: 0, pointIndex: arc.pointIndex }
5371
6813
  })
5372
6814
  );
5373
- const legend = chartData.style?.hasLegend ? categoryLabels.map((label, i) => ({ color: paletteColor2(i, chartData.colorPalette), label })) : [];
6815
+ const legendLabels = categoryLevels?.length ? [...new Set(categoryLevels[categoryLevels.length - 1]?.filter(Boolean) ?? categoryLabels)] : categoryLabels;
6816
+ const legend = chartData.style?.hasLegend ? legendLabels.map((label, i) => ({ color: paletteColor2(i, chartData.colorPalette), label })) : [];
5374
6817
  const title = chartData.style?.hasTitle && chartData.title ? chartData.title : void 0;
5375
6818
  return {
5376
6819
  svgWidth: layout.svgWidth,
@@ -5387,6 +6830,176 @@ function buildSunburstViewModel(element, chartData, categoryLabels) {
5387
6830
  legendAnchor: "middle"
5388
6831
  };
5389
6832
  }
6833
+ function normalizedLevels(levels, pointCount) {
6834
+ return levels.map((level) => {
6835
+ let previous = "";
6836
+ return Array.from({ length: pointCount }, (_, index) => {
6837
+ const label = level[index]?.trim() ?? "";
6838
+ if (label) {
6839
+ previous = label;
6840
+ }
6841
+ return label || previous;
6842
+ });
6843
+ });
6844
+ }
6845
+ function findOrAddParent(children, label, colorIndex, parentLabelLayout) {
6846
+ const existing = children.find((child2) => child2.children && child2.label === label);
6847
+ if (existing) {
6848
+ return existing;
6849
+ }
6850
+ const parent = {
6851
+ label,
6852
+ weight: 0,
6853
+ children: [],
6854
+ colorIndex,
6855
+ parentLabelLayout
6856
+ };
6857
+ children.push(parent);
6858
+ return parent;
6859
+ }
6860
+ function buildSeriesHierarchy(series, seriesIndex, categoryLabels, categoryLevels, colorStride) {
6861
+ const pointCount = series.values.length;
6862
+ const levels = normalizedLevels(categoryLevels ?? [], pointCount);
6863
+ const layout = series.treemapOptions?.parentLabelLayout ?? "banner";
6864
+ const roots = [];
6865
+ for (let pointIndex = 0; pointIndex < pointCount; pointIndex++) {
6866
+ let children = roots;
6867
+ for (let levelIndex = levels.length - 1; levelIndex >= 1; levelIndex--) {
6868
+ const label = levels[levelIndex][pointIndex] || `Group ${levelIndex}`;
6869
+ children = findOrAddParent(children, label, pointIndex, layout).children;
6870
+ }
6871
+ children.push({
6872
+ label: levels[0]?.[pointIndex] || categoryLabels[pointIndex] || String(pointIndex + 1),
6873
+ weight: Math.abs(series.values[pointIndex] ?? 0),
6874
+ seriesIndex,
6875
+ pointIndex,
6876
+ colorIndex: seriesIndex * colorStride + pointIndex,
6877
+ parentLabelLayout: layout
6878
+ });
6879
+ }
6880
+ return roots;
6881
+ }
6882
+ function aggregate(node) {
6883
+ if (node.children) {
6884
+ node.weight = node.children.reduce((sum, child2) => sum + aggregate(child2), 0);
6885
+ }
6886
+ return node.weight;
6887
+ }
6888
+ function splitNodes(nodes, box) {
6889
+ const sorted = [...nodes].sort((a, b) => b.weight - a.weight);
6890
+ const total = sorted.reduce((sum, node) => sum + node.weight, 0);
6891
+ const fallback = total === 0 ? sorted.length : total;
6892
+ let remaining = fallback;
6893
+ let current = { ...box };
6894
+ return sorted.map((node) => {
6895
+ const weight = total === 0 ? 1 : node.weight;
6896
+ const fraction = remaining > 0 ? weight / remaining : 0;
6897
+ const splitWidth = current.w >= current.h;
6898
+ const allocated = splitWidth ? { ...current, w: current.w * fraction } : { ...current, h: current.h * fraction };
6899
+ if (splitWidth) {
6900
+ current = { ...current, x: current.x + allocated.w, w: current.w - allocated.w };
6901
+ } else {
6902
+ current = { ...current, y: current.y + allocated.h, h: current.h - allocated.h };
6903
+ }
6904
+ remaining -= weight;
6905
+ return [node, allocated];
6906
+ });
6907
+ }
6908
+ function renderNodes(nodes, box, colorPalette, primitives) {
6909
+ for (const [node, allocation] of splitNodes(nodes, box)) {
6910
+ const cell = {
6911
+ x: allocation.x + 1,
6912
+ y: allocation.y + 1,
6913
+ w: Math.max(allocation.w - 2, 1),
6914
+ h: Math.max(allocation.h - 2, 1)
6915
+ };
6916
+ if (node.children) {
6917
+ const showParent = node.parentLabelLayout !== "none" && cell.w > 28 && cell.h > 16;
6918
+ if (showParent) {
6919
+ primitives.push({
6920
+ kind: "text",
6921
+ x: cell.x + 4,
6922
+ y: cell.y + 11,
6923
+ text: node.label,
6924
+ fontSize: 10,
6925
+ fill: "#303030",
6926
+ textAnchor: "start",
6927
+ fontWeight: "bold"
6928
+ });
6929
+ }
6930
+ const bannerHeight = showParent && node.parentLabelLayout === "banner" ? 16 : 0;
6931
+ renderNodes(
6932
+ node.children,
6933
+ { ...cell, y: cell.y + bannerHeight, h: Math.max(cell.h - bannerHeight, 1) },
6934
+ colorPalette,
6935
+ primitives
6936
+ );
6937
+ continue;
6938
+ }
6939
+ primitives.push({
6940
+ kind: "rect",
6941
+ ...cell,
6942
+ fill: paletteColor2(node.colorIndex, colorPalette),
6943
+ rx: 2,
6944
+ opacity: 0.85,
6945
+ part: {
6946
+ role: "dataPoint",
6947
+ seriesIndex: node.seriesIndex,
6948
+ pointIndex: node.pointIndex
6949
+ }
6950
+ });
6951
+ if (cell.w > 30 && cell.h > 14) {
6952
+ primitives.push({
6953
+ kind: "text",
6954
+ x: cell.x + cell.w / 2,
6955
+ y: cell.y + cell.h / 2,
6956
+ text: node.label,
6957
+ fontSize: Math.min(10, cell.h * 0.3),
6958
+ fill: "#ffffff",
6959
+ textAnchor: "middle",
6960
+ fontWeight: "bold",
6961
+ dominantBaseline: "central"
6962
+ });
6963
+ }
6964
+ }
6965
+ }
6966
+ function buildHierarchicalTreemapPrimitives(chartData, categoryLabels, box) {
6967
+ const colorStride = Math.max(
6968
+ categoryLabels.length,
6969
+ ...chartData.series.map((s) => s.values.length),
6970
+ 1
6971
+ );
6972
+ let roots = chartData.series.flatMap(
6973
+ (series, seriesIndex) => buildSeriesHierarchy(
6974
+ series,
6975
+ seriesIndex,
6976
+ categoryLabels,
6977
+ chartData.categoryLevels,
6978
+ colorStride
6979
+ )
6980
+ );
6981
+ if (chartData.series.length > 1) {
6982
+ roots = chartData.series.map((series, seriesIndex) => ({
6983
+ label: series.name,
6984
+ weight: 0,
6985
+ children: buildSeriesHierarchy(
6986
+ series,
6987
+ seriesIndex,
6988
+ categoryLabels,
6989
+ chartData.categoryLevels,
6990
+ colorStride
6991
+ ),
6992
+ colorIndex: seriesIndex * colorStride,
6993
+ parentLabelLayout: series.treemapOptions?.parentLabelLayout ?? "banner"
6994
+ }));
6995
+ }
6996
+ for (const root of roots) {
6997
+ aggregate(root);
6998
+ }
6999
+ const primitives = [];
7000
+ renderNodes(roots, box, chartData.colorPalette, primitives);
7001
+ return primitives;
7002
+ }
5390
7003
  var ISO_COS30 = Math.cos(Math.PI / 6);
5391
7004
  var ISO_SIN30 = Math.sin(Math.PI / 6);
5392
7005
  function isoProject(x, y, z) {
@@ -5556,67 +7169,24 @@ function buildFlatSurfaceViewModel(element, chartData, categoryLabels) {
5556
7169
  legendX,
5557
7170
  legendY,
5558
7171
  legendAnchor
5559
- };
5560
- }
5561
- function buildSurfaceViewModel(element, chartData, categoryLabels) {
5562
- const catCount = Math.max(categoryLabels.length, 1);
5563
- const seriesCount = chartData.series.length;
5564
- if (seriesCount >= 2 && catCount >= 2) {
5565
- return buildIsometricSurfaceViewModel(element, chartData, categoryLabels);
5566
- }
5567
- return buildFlatSurfaceViewModel(element, chartData, categoryLabels);
5568
- }
5569
- function buildTreemapViewModel(element, chartData, categoryLabels) {
5570
- const layout = computePlotLayout(element.width, element.height, chartData, false);
5571
- const allValues = chartData.series.flatMap((s) => s.values);
5572
- const totalAbs = allValues.reduce((sum, v) => sum + Math.abs(v), 0) || 1;
5573
- const primitives = [];
5574
- let curX = layout.plotLeft;
5575
- let curY = layout.plotTop;
5576
- let remainW = layout.plotWidth;
5577
- let remainH = layout.plotHeight;
5578
- let remainTotal = totalAbs;
5579
- const items = allValues.map((v, i) => ({ value: Math.abs(v), index: i })).sort((a, b) => b.value - a.value);
5580
- for (const item of items) {
5581
- const fraction = remainTotal > 0 ? item.value / remainTotal : 0;
5582
- const useWidth = remainW >= remainH;
5583
- const w = useWidth ? remainW * fraction : remainW;
5584
- const h = useWidth ? remainH : remainH * fraction;
5585
- const rw = Math.max(w - 1, 1);
5586
- const rh = Math.max(h - 1, 1);
5587
- primitives.push({
5588
- kind: "rect",
5589
- x: curX,
5590
- y: curY,
5591
- w: rw,
5592
- h: rh,
5593
- fill: paletteColor2(item.index, chartData.colorPalette),
5594
- rx: 2,
5595
- opacity: 0.85
5596
- });
5597
- const label = categoryLabels[item.index] ?? String(item.index + 1);
5598
- if (rw > 30 && rh > 14) {
5599
- primitives.push({
5600
- kind: "text",
5601
- x: curX + rw / 2,
5602
- y: curY + rh / 2,
5603
- text: label,
5604
- fontSize: Math.min(10, rh * 0.3),
5605
- fill: "#ffffff",
5606
- textAnchor: "middle",
5607
- fontWeight: "bold",
5608
- dominantBaseline: "central"
5609
- });
5610
- }
5611
- if (useWidth) {
5612
- curX += w;
5613
- remainW -= w;
5614
- } else {
5615
- curY += h;
5616
- remainH -= h;
5617
- }
5618
- remainTotal -= item.value;
7172
+ };
7173
+ }
7174
+ function buildSurfaceViewModel(element, chartData, categoryLabels) {
7175
+ const catCount = Math.max(categoryLabels.length, 1);
7176
+ const seriesCount = chartData.series.length;
7177
+ if (seriesCount >= 2 && catCount >= 2) {
7178
+ return buildIsometricSurfaceViewModel(element, chartData, categoryLabels);
5619
7179
  }
7180
+ return buildFlatSurfaceViewModel(element, chartData, categoryLabels);
7181
+ }
7182
+ function buildTreemapViewModel(element, chartData, categoryLabels) {
7183
+ const layout = computePlotLayout(element.width, element.height, chartData, false);
7184
+ const primitives = buildHierarchicalTreemapPrimitives(chartData, categoryLabels, {
7185
+ x: layout.plotLeft,
7186
+ y: layout.plotTop,
7187
+ w: layout.plotWidth,
7188
+ h: layout.plotHeight
7189
+ });
5620
7190
  const legendPos = chartData.style?.legendPosition ?? "b";
5621
7191
  const { legend, legendX, legendY, legendAnchor } = buildLegend(
5622
7192
  chartData.series,
@@ -5646,31 +7216,144 @@ function buildTreemapViewModel(element, chartData, categoryLabels) {
5646
7216
  legendAnchor
5647
7217
  };
5648
7218
  }
7219
+ function sourceIndices(indices, length) {
7220
+ return Array.from({ length }, (_, position) => indices?.[position] ?? position);
7221
+ }
7222
+ function valueAtSource(values, indices, sourceIndex) {
7223
+ const position = indices.indexOf(sourceIndex);
7224
+ return position >= 0 ? values[position] : void 0;
7225
+ }
7226
+ function cachedEntityName(cache, entityId) {
7227
+ if (!cache || typeof cache !== "object") {
7228
+ return void 0;
7229
+ }
7230
+ const record = cache;
7231
+ if (String(record["@_entityId"] ?? "") === entityId && record["@_entityName"] !== void 0) {
7232
+ return String(record["@_entityName"]);
7233
+ }
7234
+ for (const value of Object.values(record)) {
7235
+ if (Array.isArray(value)) {
7236
+ for (const item of value) {
7237
+ const name = cachedEntityName(item, entityId);
7238
+ if (name) {
7239
+ return name;
7240
+ }
7241
+ }
7242
+ } else {
7243
+ const name = cachedEntityName(value, entityId);
7244
+ if (name) {
7245
+ return name;
7246
+ }
7247
+ }
7248
+ }
7249
+ return void 0;
7250
+ }
7251
+ function resolveRegionEntityCode(entityId, options, resolveCode) {
7252
+ if (!entityId) {
7253
+ return void 0;
7254
+ }
7255
+ const direct = resolveCode(entityId);
7256
+ if (direct) {
7257
+ return direct;
7258
+ }
7259
+ const tokens = entityId.split(/[:/|._-]+/u).filter(Boolean);
7260
+ for (let index = tokens.length - 1; index >= 0; index--) {
7261
+ const resolved = resolveCode(tokens[index] ?? "");
7262
+ if (resolved) {
7263
+ return resolved;
7264
+ }
7265
+ }
7266
+ const cachedName = cachedEntityName(options?.geographyCache, entityId);
7267
+ return cachedName ? resolveCode(cachedName) : void 0;
7268
+ }
7269
+ function buildRegionMapEntries(categories, values, options, resolveCode) {
7270
+ const entityIds = options?.entityIds ?? [];
7271
+ const categoryIndices = sourceIndices(options?.categorySourceIndices, categories.length);
7272
+ const valueIndices = sourceIndices(options?.valueSourceIndices, values.length);
7273
+ const entityIndices = sourceIndices(options?.entityIdSourceIndices, entityIds.length);
7274
+ const indices = [.../* @__PURE__ */ new Set([...categoryIndices, ...valueIndices, ...entityIndices])].sort(
7275
+ (a, b) => a - b
7276
+ );
7277
+ return indices.map((sourceIndex) => {
7278
+ const category = valueAtSource(categories, categoryIndices, sourceIndex) ?? "";
7279
+ const value = valueAtSource(values, valueIndices, sourceIndex) ?? 0;
7280
+ const entityId = valueAtSource(entityIds, entityIndices, sourceIndex);
7281
+ return {
7282
+ sourceIndex,
7283
+ label: category || entityId || `Region ${sourceIndex + 1}`,
7284
+ value,
7285
+ ...entityId ? { entityId } : {},
7286
+ code: resolveRegionEntityCode(entityId, options, resolveCode) ?? resolveCode(category)
7287
+ };
7288
+ });
7289
+ }
7290
+ function shouldRenderRegionLabel(layout, projectedWidth, projectedHeight) {
7291
+ if (layout === "none") {
7292
+ return false;
7293
+ }
7294
+ if (layout === "bestFitOnly") {
7295
+ return projectedWidth >= 18 && projectedHeight >= 10;
7296
+ }
7297
+ return true;
7298
+ }
7299
+ function formatRegionMapValue(value, cultureLanguage) {
7300
+ if (!cultureLanguage) {
7301
+ return String(value);
7302
+ }
7303
+ try {
7304
+ return new Intl.NumberFormat(cultureLanguage, { maximumFractionDigits: 2 }).format(value);
7305
+ } catch {
7306
+ return String(value);
7307
+ }
7308
+ }
7309
+ function buildWaterfallSteps(values, options) {
7310
+ const subtotalIndices = new Set(
7311
+ options?.subtotalIndices ?? (values.length > 0 ? [values.length - 1] : [])
7312
+ );
7313
+ const steps = [];
7314
+ let runningTotal = 0;
7315
+ for (let sourceIndex = 0; sourceIndex < values.length; sourceIndex++) {
7316
+ const value = values[sourceIndex] ?? 0;
7317
+ const isSubtotal = subtotalIndices.has(sourceIndex);
7318
+ const startValue = isSubtotal ? 0 : runningTotal;
7319
+ const endValue = isSubtotal ? value : runningTotal + value;
7320
+ steps.push({ sourceIndex, value, startValue, endValue, isSubtotal });
7321
+ runningTotal = endValue;
7322
+ }
7323
+ return steps;
7324
+ }
7325
+ function computeWaterfallRange(steps) {
7326
+ return computeValueRange2([
7327
+ {
7328
+ name: "Waterfall range",
7329
+ values: steps.flatMap((step) => [step.startValue, step.endValue])
7330
+ }
7331
+ ]);
7332
+ }
5649
7333
  var WF_COLOR_POSITIVE = "#22c55e";
5650
7334
  var WF_COLOR_NEGATIVE = "#ef4444";
5651
7335
  var WF_COLOR_TOTAL = "#6366f1";
5652
7336
  var WF_CONNECTOR_COLOR = "#94a3b8";
5653
7337
  function buildWaterfallViewModel(element, chartData, categoryLabels) {
5654
7338
  const layout = computePlotLayout(element.width, element.height, chartData, true);
5655
- const values = chartData.series[0]?.values ?? [];
5656
- const range = computeValueRange2(chartData.series);
7339
+ const series = chartData.series[0];
7340
+ const values = series?.values ?? [];
7341
+ const steps = buildWaterfallSteps(values, series?.waterfallOptions);
7342
+ const range = computeWaterfallRange(steps);
5657
7343
  const catCount = Math.max(categoryLabels.length, values.length, 1);
5658
7344
  const barWidth = layout.plotWidth / catCount * 0.6;
5659
7345
  const gap = layout.plotWidth / catCount * 0.2;
5660
7346
  const primitives = [];
5661
7347
  const dataLabels = [];
5662
- let runningTotal = 0;
5663
- for (let i = 0; i < values.length; i++) {
5664
- const val2 = values[i] ?? 0;
5665
- const isLast = i === values.length - 1;
5666
- const startVal = isLast ? 0 : runningTotal;
5667
- const endVal = runningTotal + val2;
5668
- const barStartY = valueToY2(startVal, range, layout.plotTop, layout.plotBottom);
5669
- const barEndY = valueToY2(endVal, range, layout.plotTop, layout.plotBottom);
7348
+ for (const step of steps) {
7349
+ const { sourceIndex, value, startValue, endValue, isSubtotal } = step;
7350
+ const barStartY = valueToY2(startValue, range, layout.plotTop, layout.plotBottom);
7351
+ const barEndY = valueToY2(endValue, range, layout.plotTop, layout.plotBottom);
7352
+ const i = sourceIndex;
5670
7353
  const x = layout.plotLeft + layout.plotWidth / catCount * i + gap;
5671
7354
  const y = Math.min(barStartY, barEndY);
5672
7355
  const h = Math.max(Math.abs(barEndY - barStartY), 1);
5673
- const barColor = isLast ? WF_COLOR_TOTAL : val2 >= 0 ? WF_COLOR_POSITIVE : WF_COLOR_NEGATIVE;
7356
+ const barColor = isSubtotal ? WF_COLOR_TOTAL : value >= 0 ? WF_COLOR_POSITIVE : WF_COLOR_NEGATIVE;
5674
7357
  primitives.push({
5675
7358
  kind: "rect",
5676
7359
  x,
@@ -5678,20 +7361,21 @@ function buildWaterfallViewModel(element, chartData, categoryLabels) {
5678
7361
  w: barWidth,
5679
7362
  h,
5680
7363
  fill: barColor,
5681
- rx: 1
7364
+ rx: 1,
7365
+ part: { role: "dataPoint", seriesIndex: 0, pointIndex: sourceIndex }
5682
7366
  });
5683
7367
  if (chartData.style?.hasDataLabels) {
5684
7368
  dataLabels.push({
5685
7369
  kind: "text",
5686
7370
  x: x + barWidth / 2,
5687
7371
  y: y - 4,
5688
- text: formatAxisValue2(isLast ? endVal : val2),
7372
+ text: formatAxisValue2(value),
5689
7373
  fontSize: 7,
5690
7374
  fill: "#334155",
5691
7375
  textAnchor: "middle"
5692
7376
  });
5693
7377
  }
5694
- if (!isLast && i < values.length - 1) {
7378
+ if (series?.waterfallOptions?.connectorLines !== false && i < values.length - 1) {
5695
7379
  const nextX = layout.plotLeft + layout.plotWidth / catCount * (i + 1) + gap;
5696
7380
  primitives.push({
5697
7381
  kind: "line",
@@ -5704,9 +7388,6 @@ function buildWaterfallViewModel(element, chartData, categoryLabels) {
5704
7388
  dashArray: "3 2"
5705
7389
  });
5706
7390
  }
5707
- if (!isLast) {
5708
- runningTotal += val2;
5709
- }
5710
7391
  }
5711
7392
  const { gridlines, axisLabels } = buildGridlinesAndLabels(range, layout);
5712
7393
  const zeroLine = buildZeroLine(range, layout);
@@ -5815,7 +7496,7 @@ function resolveRegionCode(label) {
5815
7496
  return REGION_ALIAS_MAP[normalized];
5816
7497
  }
5817
7498
  function lerpColor(a, b, t) {
5818
- const clamp3 = (v) => Math.max(0, Math.min(255, Math.round(v)));
7499
+ const clamp7 = (v) => Math.max(0, Math.min(255, Math.round(v)));
5819
7500
  const ha = a.replace("#", "");
5820
7501
  const hb = b.replace("#", "");
5821
7502
  const r1 = parseInt(ha.substring(0, 2), 16);
@@ -5824,9 +7505,9 @@ function lerpColor(a, b, t) {
5824
7505
  const r2 = parseInt(hb.substring(0, 2), 16);
5825
7506
  const g2 = parseInt(hb.substring(2, 4), 16);
5826
7507
  const b2 = parseInt(hb.substring(4, 6), 16);
5827
- const r = clamp3(r1 + (r2 - r1) * t);
5828
- const g = clamp3(g1 + (g2 - g1) * t);
5829
- const bl = clamp3(b1 + (b2 - b1) * t);
7508
+ const r = clamp7(r1 + (r2 - r1) * t);
7509
+ const g = clamp7(g1 + (g2 - g1) * t);
7510
+ const bl = clamp7(b1 + (b2 - b1) * t);
5830
7511
  const toHex = (n) => n.toString(16).padStart(2, "0");
5831
7512
  return `#${toHex(r)}${toHex(g)}${toHex(bl)}`;
5832
7513
  }
@@ -5977,24 +7658,61 @@ var WORLD_REGIONS = [
5977
7658
  labelXY: [838, 378]
5978
7659
  }
5979
7660
  ];
7661
+ function regionBounds(region) {
7662
+ const coordinates = region.path.match(/-?\d+(?:\.\d+)?/gu)?.map(Number) ?? [];
7663
+ const xs = [];
7664
+ const ys = [];
7665
+ for (let index = 0; index < coordinates.length; index += 2) {
7666
+ xs.push(coordinates[index] ?? 0);
7667
+ ys.push(coordinates[index + 1] ?? 0);
7668
+ }
7669
+ return {
7670
+ minX: Math.min(...xs),
7671
+ minY: Math.min(...ys),
7672
+ maxX: Math.max(...xs),
7673
+ maxY: Math.max(...ys)
7674
+ };
7675
+ }
7676
+ function regionViewBounds(viewedRegionType, regionValues) {
7677
+ if (!viewedRegionType || viewedRegionType === "world" || regionValues.size === 0) {
7678
+ return { minX: 0, minY: 0, maxX: 1e3, maxY: 500 };
7679
+ }
7680
+ const matched = WORLD_REGIONS.filter((region) => regionValues.has(region.code));
7681
+ const targets = viewedRegionType === "countryRegion" ? matched.slice(0, 1) : matched;
7682
+ if (targets.length === 0) {
7683
+ return { minX: 0, minY: 0, maxX: 1e3, maxY: 500 };
7684
+ }
7685
+ const bounds = targets.map(regionBounds);
7686
+ const padding = 10;
7687
+ return {
7688
+ minX: Math.max(0, Math.min(...bounds.map((item) => item.minX)) - padding),
7689
+ minY: Math.max(0, Math.min(...bounds.map((item) => item.minY)) - padding),
7690
+ maxX: Math.min(1e3, Math.max(...bounds.map((item) => item.maxX)) + padding),
7691
+ maxY: Math.min(500, Math.max(...bounds.map((item) => item.maxY)) + padding)
7692
+ };
7693
+ }
5980
7694
  function buildRegionMapViewModel(element, chartData, categoryLabels) {
5981
7695
  const svgWidth = Math.max(element.width, 320);
5982
7696
  const svgHeight = Math.max(element.height, 200);
5983
7697
  const categories = categoryLabels.length > 0 ? categoryLabels : chartData.categories;
5984
- const values = chartData.series.length > 0 ? chartData.series[0]?.values ?? [] : [];
7698
+ const series = chartData.series[0];
7699
+ const options = series?.regionMapOptions;
7700
+ const entries = buildRegionMapEntries(categories, series?.values ?? [], options, resolveRegionCode);
7701
+ const values = entries.map((entry) => entry.value);
5985
7702
  const finiteVals = values.filter((v) => Number.isFinite(v));
5986
7703
  const minVal = finiteVals.length > 0 ? Math.min(...finiteVals) : 0;
5987
7704
  const maxVal = finiteVals.length > 0 ? Math.max(...finiteVals) : 1;
5988
7705
  const regionValueMap = /* @__PURE__ */ new Map();
5989
7706
  const unmatchedRows = [];
5990
- for (let i = 0; i < categories.length; i++) {
5991
- const cat = categories[i] ?? "";
5992
- const value = values[i] ?? 0;
5993
- const code = resolveRegionCode(cat);
5994
- if (code !== void 0) {
5995
- regionValueMap.set(code, { value, label: cat });
7707
+ for (const entry of entries) {
7708
+ if (entry.code !== void 0) {
7709
+ regionValueMap.set(entry.code, {
7710
+ value: entry.value,
7711
+ label: entry.label,
7712
+ sourceIndex: entry.sourceIndex
7713
+ });
5996
7714
  } else {
5997
- unmatchedRows.push({ label: cat, value });
7715
+ unmatchedRows.push({ label: entry.label, value: entry.value });
5998
7716
  }
5999
7717
  }
6000
7718
  const legendHeight = 30;
@@ -6003,9 +7721,12 @@ function buildRegionMapViewModel(element, chartData, categoryLabels) {
6003
7721
  const fallbackTableH = unmatchedRows.length > 0 ? (maxFallbackRows + 1) * fallbackRowH + 8 : 0;
6004
7722
  const titleH = chartData.title ? 22 : 0;
6005
7723
  const mapAreaH = Math.max(svgHeight - titleH - legendHeight - fallbackTableH - 8, 80);
6006
- const mapScale = Math.min((svgWidth - 20) / 1e3, mapAreaH / 500);
6007
- const mapOffsetX = (svgWidth - 1e3 * mapScale) / 2;
6008
- const mapOffsetY = titleH + 4;
7724
+ const viewBounds = regionViewBounds(options?.viewedRegionType, regionValueMap);
7725
+ const viewWidth = Math.max(viewBounds.maxX - viewBounds.minX, 1);
7726
+ const viewHeight = Math.max(viewBounds.maxY - viewBounds.minY, 1);
7727
+ const mapScale = Math.min((svgWidth - 20) / viewWidth, mapAreaH / viewHeight);
7728
+ const mapOffsetX = (svgWidth - viewWidth * mapScale) / 2 - viewBounds.minX * mapScale;
7729
+ const mapOffsetY = titleH + 4 - viewBounds.minY * mapScale;
6009
7730
  const primitives = [];
6010
7731
  primitives.push({
6011
7732
  kind: "rect",
@@ -6040,16 +7761,28 @@ function buildRegionMapViewModel(element, chartData, categoryLabels) {
6040
7761
  d: scaledPath,
6041
7762
  fill,
6042
7763
  stroke: "#94a3b8",
6043
- strokeWidth: Math.max(0.5 / mapScale, 0.3)
7764
+ strokeWidth: Math.max(0.5 / mapScale, 0.3),
7765
+ ...entry ? {
7766
+ part: {
7767
+ role: "dataPoint",
7768
+ seriesIndex: 0,
7769
+ pointIndex: entry.sourceIndex
7770
+ }
7771
+ } : {}
6044
7772
  });
6045
- if (entry !== void 0) {
7773
+ const bounds = regionBounds(region);
7774
+ if (entry !== void 0 && shouldRenderRegionLabel(
7775
+ options?.regionLabelLayout,
7776
+ (bounds.maxX - bounds.minX) * mapScale,
7777
+ (bounds.maxY - bounds.minY) * mapScale
7778
+ )) {
6046
7779
  const lx = region.labelXY[0] * mapScale + mapOffsetX;
6047
7780
  const ly = region.labelXY[1] * mapScale + mapOffsetY + 4;
6048
7781
  primitives.push({
6049
7782
  kind: "text",
6050
7783
  x: lx,
6051
7784
  y: ly,
6052
- text: formatAxisValue2(entry.value),
7785
+ text: formatRegionMapValue(entry.value, options?.cultureLanguage),
6053
7786
  fontSize: Math.max(6, 7 * mapScale),
6054
7787
  fill: "#1e293b",
6055
7788
  textAnchor: "middle",
@@ -6104,6 +7837,17 @@ function buildRegionMapViewModel(element, chartData, categoryLabels) {
6104
7837
  textAnchor: "middle"
6105
7838
  }
6106
7839
  );
7840
+ if (options?.attribution) {
7841
+ primitives.push({
7842
+ kind: "text",
7843
+ x: svgWidth - 4,
7844
+ y: svgHeight - 4,
7845
+ text: options.attribution,
7846
+ fontSize: 5,
7847
+ fill: "#64748b",
7848
+ textAnchor: "end"
7849
+ });
7850
+ }
6107
7851
  if (unmatchedRows.length > 0) {
6108
7852
  const tableY = legendY + 26;
6109
7853
  const fontSize = Math.min(8, fallbackRowH * 0.7);
@@ -6281,14 +8025,17 @@ function computeStackedValueRange(series, catCount) {
6281
8025
  }
6282
8026
  function valueToY2(val2, range, topY, bottomY) {
6283
8027
  const usable = bottomY - topY;
8028
+ let ratio;
6284
8029
  if (range.logScale && range.logBase) {
6285
8030
  const base = range.logBase;
6286
8031
  const clampedVal = Math.max(val2, range.min);
6287
8032
  const logVal = Math.log(clampedVal) / Math.log(base);
6288
8033
  const logMin = Math.log(range.min) / Math.log(base);
6289
- return bottomY - (logVal - logMin) / range.span * usable;
8034
+ ratio = (logVal - logMin) / range.span;
8035
+ } else {
8036
+ ratio = (val2 - range.min) / range.span;
6290
8037
  }
6291
- return bottomY - (val2 - range.min) / range.span * usable;
8038
+ return range.reverseOrder ? topY + ratio * usable : bottomY - ratio * usable;
6292
8039
  }
6293
8040
  function formatAxisValue2(val2) {
6294
8041
  if (Math.abs(val2) >= 1e6) {
@@ -6349,7 +8096,7 @@ function computePlotLayout(elementWidth, elementHeight, chartData, hasAxes, opti
6349
8096
  };
6350
8097
  }
6351
8098
  var GRIDLINE_COLOR2 = "#e2e8f0";
6352
- var AXIS_LABEL_COLOR2 = "#64748b";
8099
+ var AXIS_LABEL_COLOR = "#64748b";
6353
8100
  var ZERO_LINE_COLOR = "#94a3b8";
6354
8101
  var TICK_COUNT2 = 5;
6355
8102
  function buildGridlinesAndLabels(range, layout) {
@@ -6373,7 +8120,7 @@ function buildGridlinesAndLabels(range, layout) {
6373
8120
  y,
6374
8121
  text: formatAxisValue2(val2),
6375
8122
  fontSize: 8,
6376
- fill: AXIS_LABEL_COLOR2,
8123
+ fill: AXIS_LABEL_COLOR,
6377
8124
  textAnchor: "end",
6378
8125
  dominantBaseline: "central"
6379
8126
  });
@@ -6405,7 +8152,7 @@ function buildCategoryLabels(categoryLabels, layout, catSpacing) {
6405
8152
  y: layout.plotBottom + 12,
6406
8153
  text: label,
6407
8154
  fontSize: 8,
6408
- fill: AXIS_LABEL_COLOR2,
8155
+ fill: AXIS_LABEL_COLOR,
6409
8156
  textAnchor: "middle"
6410
8157
  };
6411
8158
  });
@@ -6559,9 +8306,12 @@ function computePieSlices(values, cx, cy, outerR, innerR) {
6559
8306
  return computePieSlicePath(cx, cy, outerR, innerR, startAngle, cumAngle);
6560
8307
  });
6561
8308
  }
6562
- function computeScatterDots(values, maxXIndex, layout, range) {
8309
+ function computeScatterDots(values, maxXIndex, layout, range, xValues) {
8310
+ const finiteX = xValues?.slice(0, values.length).filter(Number.isFinite);
8311
+ const minX = finiteX?.length ? Math.min(...finiteX) : 0;
8312
+ const spanX = finiteX?.length ? Math.max(Math.max(...finiteX) - minX, 1) : maxXIndex;
6563
8313
  return values.map((val2, i) => ({
6564
- cx: layout.plotLeft + (maxXIndex > 0 ? i / maxXIndex : 0) * layout.plotWidth,
8314
+ cx: layout.plotLeft + (spanX > 0 ? (Number.isFinite(xValues?.[i]) ? xValues[i] - minX : i) / spanX : 0) * layout.plotWidth,
6565
8315
  cy: valueToY2(val2, range, layout.plotTop, layout.plotBottom)
6566
8316
  }));
6567
8317
  }
@@ -6977,13 +8727,14 @@ function valueFromY(y, range, topY, bottomY) {
6977
8727
  if (usable === 0) {
6978
8728
  return range.min;
6979
8729
  }
8730
+ const ratio = range.reverseOrder ? (y - topY) / usable : (bottomY - y) / usable;
6980
8731
  if (range.logScale && range.logBase) {
6981
8732
  const base = range.logBase;
6982
8733
  const logMin = Math.log(range.min) / Math.log(base);
6983
- const logVal = logMin + (bottomY - y) / usable * range.span;
8734
+ const logVal = logMin + ratio * range.span;
6984
8735
  return base ** logVal;
6985
8736
  }
6986
- return range.min + (bottomY - y) / usable * range.span;
8737
+ return range.min + ratio * range.span;
6987
8738
  }
6988
8739
  function roundDragValue(value, range) {
6989
8740
  if (!Number.isFinite(value) || range.span <= 0) {
@@ -6993,16 +8744,16 @@ function roundDragValue(value, range) {
6993
8744
  const rounded = Math.round(value / step) * step;
6994
8745
  return Number.parseFloat(rounded.toPrecision(12));
6995
8746
  }
6996
- function rangeForSeries(drag, seriesIndex) {
8747
+ function rangeForSeries2(drag, seriesIndex) {
6997
8748
  const useSecondary = drag.secondaryRange !== void 0 && (drag.secondarySeriesIndexes?.includes(seriesIndex) ?? false);
6998
8749
  return useSecondary && drag.secondaryRange ? drag.secondaryRange : drag.range;
6999
8750
  }
7000
8751
  function dragValueForPart(viewY, drag, seriesIndex) {
7001
- const range = rangeForSeries(drag, seriesIndex);
8752
+ const range = rangeForSeries2(drag, seriesIndex);
7002
8753
  return roundDragValue(valueFromY(viewY, range, drag.plotTop, drag.plotBottom), range);
7003
8754
  }
7004
8755
  function dragAnchorViewY(value, drag, seriesIndex) {
7005
- return valueToY2(value, rangeForSeries(drag, seriesIndex), drag.plotTop, drag.plotBottom);
8756
+ return valueToY2(value, rangeForSeries2(drag, seriesIndex), drag.plotTop, drag.plotBottom);
7006
8757
  }
7007
8758
  function withChartPointValue(chartData, seriesIndex, pointIndex, value) {
7008
8759
  const series = chartData.series[seriesIndex];
@@ -7305,8 +9056,8 @@ function hexToRgb3(hex) {
7305
9056
  };
7306
9057
  }
7307
9058
  function rgbToHex3(r, g, b) {
7308
- const clamp3 = (v) => Math.max(0, Math.min(255, Math.round(v)));
7309
- return `#${clamp3(r).toString(16).padStart(2, "0")}${clamp3(g).toString(16).padStart(2, "0")}${clamp3(b).toString(16).padStart(2, "0")}`;
9059
+ const clamp7 = (v) => Math.max(0, Math.min(255, Math.round(v)));
9060
+ return `#${clamp7(r).toString(16).padStart(2, "0")}${clamp7(g).toString(16).padStart(2, "0")}${clamp7(b).toString(16).padStart(2, "0")}`;
7310
9061
  }
7311
9062
  function rgbToHsl(r, g, b) {
7312
9063
  const rn = r / 255;
@@ -10360,6 +12111,16 @@ function getSlideBackgroundStyle(slide) {
10360
12111
  style["background-repeat"] = "no-repeat";
10361
12112
  } else if (slide?.backgroundGradient) {
10362
12113
  style["background-image"] = slide.backgroundGradient;
12114
+ } else if (pattern) {
12115
+ const svg = getPatternSvg(
12116
+ pattern.preset,
12117
+ pattern.fgColor ?? solid ?? "#000000",
12118
+ pattern.bgColor ?? DEFAULT_SLIDE_BACKGROUND
12119
+ );
12120
+ if (svg) {
12121
+ style["background-image"] = `url("data:image/svg+xml,${encodeURIComponent(svg)}")`;
12122
+ style["background-repeat"] = "repeat";
12123
+ }
10363
12124
  }
10364
12125
  return style;
10365
12126
  }
@@ -10565,6 +12326,37 @@ function resolveSlideId(slide, index) {
10565
12326
  const cSld = rawXml?.["p:sld"];
10566
12327
  return String(cSld?.["@_id"] || slide?.slideNumber || index + 1);
10567
12328
  }
12329
+ function groupSlidesBySection(sections, slides) {
12330
+ if (slides.length === 0) {
12331
+ return [];
12332
+ }
12333
+ if (sections.length === 0) {
12334
+ return [
12335
+ { section: void 0, slides: [...slides], slideIndexes: slides.map((_slide, i) => i) }
12336
+ ];
12337
+ }
12338
+ const sectionById = new Map(sections.map((section) => [section.id, section]));
12339
+ const grouped = /* @__PURE__ */ new Map();
12340
+ const ungrouped = {
12341
+ section: void 0,
12342
+ slides: [],
12343
+ slideIndexes: []
12344
+ };
12345
+ for (const section of sections) {
12346
+ grouped.set(section.id, { section, slides: [], slideIndexes: [] });
12347
+ }
12348
+ slides.forEach((slide, index) => {
12349
+ const group = slide.sectionId ? grouped.get(slide.sectionId) : void 0;
12350
+ const target = group && sectionById.has(slide.sectionId ?? "") ? group : ungrouped;
12351
+ target.slides.push(slide);
12352
+ target.slideIndexes.push(index);
12353
+ });
12354
+ const result = sections.map((section) => grouped.get(section.id)).filter((group) => group.slides.length > 0);
12355
+ if (ungrouped.slides.length > 0) {
12356
+ result.push(ungrouped);
12357
+ }
12358
+ return result;
12359
+ }
10568
12360
  function addSection(sections, slides, name, afterSlideIndex, idGenerator = generateSectionId) {
10569
12361
  const newId = idGenerator();
10570
12362
  const slideAtIndex = slides[afterSlideIndex];
@@ -10893,6 +12685,19 @@ function generateTicks(slideLengthPx, scale, unit) {
10893
12685
  }
10894
12686
  return ticks;
10895
12687
  }
12688
+ function resolvePictureBullet(info, baseFontSize) {
12689
+ if (!info.imageDataUrl && !info.imageRelId && !info.imageBlipFillXml) {
12690
+ return void 0;
12691
+ }
12692
+ const sizePx = typeof info.sizePts === "number" ? info.sizePts : typeof info.sizePercent === "number" ? baseFontSize * (info.sizePercent / 100) : baseFontSize;
12693
+ return {
12694
+ ...info.imageDataUrl ? { src: info.imageDataUrl } : {},
12695
+ sizePx,
12696
+ fallbackMarker: "\u2022",
12697
+ accessibleLabel: "Bullet",
12698
+ ...info.imageRelId ? { imageRelId: info.imageRelId } : {}
12699
+ };
12700
+ }
10896
12701
  var BLOCKED_PROTOCOLS = /* @__PURE__ */ new Set([
10897
12702
  `${"javascript"}:`,
10898
12703
  "data:",
@@ -11699,16 +13504,12 @@ function purifyFn() {
11699
13504
  const purify = DOMPurify;
11700
13505
  return typeof purify.sanitize === "function" ? purify.sanitize : void 0;
11701
13506
  }
11702
- function sanitizeMarkupOrRaw(markup, config) {
11703
- const sanitize = purifyFn();
11704
- return sanitize ? sanitize(markup, config) : markup;
11705
- }
11706
13507
  function sanitizeMarkupOrEmpty(markup, config) {
11707
13508
  const sanitize = purifyFn();
11708
13509
  return sanitize ? sanitize(markup, config) : "";
11709
13510
  }
11710
13511
  function sanitizeMathMl(markup) {
11711
- const sanitized = sanitizeMarkupOrRaw(markup, { USE_PROFILES: { mathMl: true, svg: true } });
13512
+ const sanitized = sanitizeMarkupOrEmpty(markup, { USE_PROFILES: { mathMl: true, svg: true } });
11712
13513
  if (/<math(?:\s|>)/iu.test(markup) && !/<math(?:\s|>)/iu.test(sanitized) && sanitized) {
11713
13514
  return `<math xmlns="http://www.w3.org/1998/Math/MathML" display="inline">${sanitized}</math>`;
11714
13515
  }
@@ -11723,8 +13524,8 @@ function hexToRgb6(hex) {
11723
13524
  };
11724
13525
  }
11725
13526
  function rgbToHex5(r, g, b) {
11726
- const clamp3 = (v) => Math.max(0, Math.min(255, Math.round(v)));
11727
- return `#${clamp3(r).toString(16).padStart(2, "0").toUpperCase()}${clamp3(g).toString(16).padStart(2, "0").toUpperCase()}${clamp3(b).toString(16).padStart(2, "0").toUpperCase()}`;
13527
+ const clamp7 = (v) => Math.max(0, Math.min(255, Math.round(v)));
13528
+ return `#${clamp7(r).toString(16).padStart(2, "0").toUpperCase()}${clamp7(g).toString(16).padStart(2, "0").toUpperCase()}${clamp7(b).toString(16).padStart(2, "0").toUpperCase()}`;
11728
13529
  }
11729
13530
  function tintColor2(hex, tintFactor) {
11730
13531
  const { r, g, b } = hexToRgb6(hex);
@@ -12912,6 +14713,7 @@ var SCALAR_ELEMENT_KEYS = /* @__PURE__ */ new Set([
12912
14713
  "mediaMimeType",
12913
14714
  "mediaReferenceKind",
12914
14715
  "mediaReferenceName",
14716
+ "mediaReferenceContentType",
12915
14717
  "trimStartMs",
12916
14718
  "trimEndMs",
12917
14719
  "posterFramePath",
@@ -12946,9 +14748,11 @@ var SCALAR_ELEMENT_KEYS = /* @__PURE__ */ new Set([
12946
14748
  "inkWidths",
12947
14749
  "inkOpacities",
12948
14750
  "inkTool",
14751
+ "inkPartPath",
12949
14752
  "zoomType",
12950
14753
  "targetSlideIndex",
12951
14754
  "targetSectionId",
14755
+ "summaryLayout",
12952
14756
  "modelPath",
12953
14757
  "modelMimeType",
12954
14758
  "posterImage",
@@ -12982,6 +14786,8 @@ var COMPLEX_ELEMENT_FIELDS = {
12982
14786
  groupFill: "_gf",
12983
14787
  inkPointPressures: "_ipp",
12984
14788
  inkStrokes: "_cis",
14789
+ inkPartRawXml: "_cirx",
14790
+ summaryTargets: "_zst",
12985
14791
  extensionXml: "_ext",
12986
14792
  customGeometryPaths: "_cgp",
12987
14793
  customGeometryRawData: "_cgr",
@@ -13623,6 +15429,38 @@ function compareSlides(base, compare) {
13623
15429
  function comparePresentation(base, compare) {
13624
15430
  return compareSlides(base.slides, compare.slides);
13625
15431
  }
15432
+ function applyAcceptSlide(slides, diff) {
15433
+ const next = [...slides];
15434
+ if (diff.status === "added" && diff.compareSlide) {
15435
+ next.splice(Math.min(diff.compareIndex, next.length), 0, { ...diff.compareSlide });
15436
+ } else if (diff.status === "changed" && diff.compareSlide && diff.baseIndex >= 0) {
15437
+ next[diff.baseIndex] = { ...diff.compareSlide };
15438
+ } else if (diff.status === "removed" && diff.baseIndex >= 0) {
15439
+ next.splice(diff.baseIndex, 1);
15440
+ }
15441
+ return next;
15442
+ }
15443
+ function applyAcceptAllSlides(slides, compareResult) {
15444
+ const next = [...slides];
15445
+ const diffs = [...compareResult.diffs];
15446
+ for (const diff of diffs) {
15447
+ if (diff.status === "changed" && diff.compareSlide && diff.baseIndex >= 0 && diff.baseIndex < next.length) {
15448
+ next[diff.baseIndex] = { ...diff.compareSlide };
15449
+ }
15450
+ }
15451
+ for (let index = diffs.length - 1; index >= 0; index--) {
15452
+ const diff = diffs[index];
15453
+ if (diff.status === "removed" && diff.baseIndex >= 0 && diff.baseIndex < next.length) {
15454
+ next.splice(diff.baseIndex, 1);
15455
+ }
15456
+ }
15457
+ for (const diff of diffs) {
15458
+ if (diff.status === "added" && diff.compareSlide) {
15459
+ next.splice(Math.min(diff.compareIndex, next.length), 0, { ...diff.compareSlide });
15460
+ }
15461
+ }
15462
+ return next;
15463
+ }
13626
15464
  function resolveDirection(direction, defaultDir) {
13627
15465
  switch (direction) {
13628
15466
  case "l":
@@ -16844,6 +18682,15 @@ function setCachedResult(key, dataUrl) {
16844
18682
  }
16845
18683
  colorChangeCache.set(key, dataUrl);
16846
18684
  }
18685
+ function getImageColorWashStyle(wash) {
18686
+ if (!wash?.color) {
18687
+ return void 0;
18688
+ }
18689
+ return {
18690
+ backgroundColor: wash.color,
18691
+ opacity: Math.max(0, Math.min(100, wash.opacity)) / 100
18692
+ };
18693
+ }
16847
18694
  function mapDuotonePixels(data, shadow, highlight) {
16848
18695
  const len = data.length;
16849
18696
  for (let i = 0; i < len; i += 4) {
@@ -17530,6 +19377,55 @@ function rebuildDrawingShapesIfCleared(smartArtData, layout, palette, style, ele
17530
19377
  drawingShapes: reflowToDrawingShapes(layoutResult, smartArtData.nodes)
17531
19378
  };
17532
19379
  }
19380
+ function buildSummaryZoomView(element, lookup) {
19381
+ if (element.zoomType !== "summary" || !element.summaryTargets?.length) {
19382
+ return void 0;
19383
+ }
19384
+ const layout = element.summaryLayout ?? "grid";
19385
+ const columns = Math.max(1, Math.ceil(Math.sqrt(element.summaryTargets.length)));
19386
+ const tiles = element.summaryTargets.map((target, index) => {
19387
+ const targetInfo = lookup?.(target.targetSlideIndex);
19388
+ const label = target.title || targetInfo?.sectionName || target.sectionId;
19389
+ const slideNumber = targetInfo?.slideNumber ?? target.targetSlideIndex + 1;
19390
+ return {
19391
+ key: `${target.sectionId}:${index}`,
19392
+ sectionId: target.sectionId,
19393
+ targetSlideIndex: target.targetSlideIndex,
19394
+ label,
19395
+ slideLabel: `Slide ${slideNumber}`,
19396
+ imageSrc: target.imageData,
19397
+ backgroundColor: targetInfo?.backgroundColor ?? "#f0f0f0",
19398
+ style: layout === "fixed" ? fixedTileStyle(element, target.x, target.y, target.width, target.height) : { position: "relative", minWidth: "0", minHeight: "0" },
19399
+ ariaLabel: `Zoom to section ${label}, slide ${slideNumber}`
19400
+ };
19401
+ });
19402
+ return {
19403
+ layout,
19404
+ containerStyle: layout === "fixed" ? { position: "relative", width: "100%", height: "100%" } : {
19405
+ display: "grid",
19406
+ gridTemplateColumns: `repeat(${columns}, minmax(0, 1fr))`,
19407
+ gap: "4px",
19408
+ width: "100%",
19409
+ height: "100%"
19410
+ },
19411
+ tiles,
19412
+ ariaLabel: `Summary Zoom with ${tiles.length} sections`
19413
+ };
19414
+ }
19415
+ function fixedTileStyle(element, x, y, width, height) {
19416
+ const safeWidth = Math.max(element.width, 1);
19417
+ const safeHeight = Math.max(element.height, 1);
19418
+ return {
19419
+ position: "absolute",
19420
+ left: percent((x - element.x) / safeWidth),
19421
+ top: percent((y - element.y) / safeHeight),
19422
+ width: percent(width / safeWidth),
19423
+ height: percent(height / safeHeight)
19424
+ };
19425
+ }
19426
+ function percent(value) {
19427
+ return `${Math.round(value * 1e5) / 1e3}%`;
19428
+ }
17533
19429
  var ARTISTIC_EFFECTS = [
17534
19430
  ["none", "pptx.image.effectNone", ""],
17535
19431
  ["blur", "pptx.image.effectBlur", "blur(4px)"],
@@ -18126,6 +20022,55 @@ function putAutosaveRecord(filePath, data) {
18126
20022
  })
18127
20023
  );
18128
20024
  }
20025
+ async function getAutosaveSnapshot(filePath) {
20026
+ const db = await openAutosaveDb();
20027
+ return new Promise((resolve) => {
20028
+ const tx = db.transaction(AUTOSAVE_STORE_NAME, "readonly");
20029
+ const store = tx.objectStore(AUTOSAVE_STORE_NAME);
20030
+ const req = store.get(filePath);
20031
+ req.onsuccess = () => {
20032
+ db.close();
20033
+ resolve(req.result);
20034
+ };
20035
+ req.onerror = () => {
20036
+ db.close();
20037
+ resolve(void 0);
20038
+ };
20039
+ });
20040
+ }
20041
+ async function listAutosaveSnapshots() {
20042
+ const db = await openAutosaveDb();
20043
+ return new Promise((resolve) => {
20044
+ const results = [];
20045
+ try {
20046
+ const tx = db.transaction(AUTOSAVE_STORE_NAME, "readonly");
20047
+ const store = tx.objectStore(AUTOSAVE_STORE_NAME);
20048
+ const cursorReq = store.openCursor();
20049
+ cursorReq.onsuccess = () => {
20050
+ const cursor = cursorReq.result;
20051
+ if (cursor) {
20052
+ const val2 = cursor.value;
20053
+ results.push({ key: val2.key, timestamp: val2.timestamp, size: val2.size });
20054
+ cursor.continue();
20055
+ }
20056
+ };
20057
+ tx.oncomplete = () => {
20058
+ db.close();
20059
+ resolve(results);
20060
+ };
20061
+ tx.onerror = () => {
20062
+ db.close();
20063
+ resolve([]);
20064
+ };
20065
+ } catch {
20066
+ try {
20067
+ db.close();
20068
+ } catch {
20069
+ }
20070
+ resolve([]);
20071
+ }
20072
+ });
20073
+ }
18129
20074
  async function saveAutosaveSnapshot(filePath, data) {
18130
20075
  try {
18131
20076
  return await putAutosaveRecord(filePath, data);
@@ -18141,6 +20086,233 @@ async function saveAutosaveSnapshot(filePath, data) {
18141
20086
  return putAutosaveRecord(filePath, data);
18142
20087
  }
18143
20088
  }
20089
+ var BACKSTAGE_NAV = [
20090
+ { id: "home", label: "Home" },
20091
+ { id: "new", label: "New" },
20092
+ { id: "open", label: "Open" },
20093
+ { id: "info", label: "Info" },
20094
+ { id: "save", label: "Save" },
20095
+ { id: "saveAs", label: "Save As" },
20096
+ { id: "print", label: "Print" },
20097
+ { id: "share", label: "Share" },
20098
+ { id: "export", label: "Export" },
20099
+ { id: "close", label: "Close" },
20100
+ { id: "account", label: "Account", group: "footer" },
20101
+ { id: "options", label: "Options", group: "footer" }
20102
+ ];
20103
+ var BACKSTAGE_TEMPLATES = [
20104
+ {
20105
+ id: "blank",
20106
+ name: "Blank Presentation",
20107
+ description: "Start with a clean canvas",
20108
+ preview: "linear-gradient(145deg, #fff 0 88%, #d34a1f 88%)"
20109
+ },
20110
+ {
20111
+ id: "warm",
20112
+ name: "Warm Welcome",
20113
+ description: "Bold editorial title slides",
20114
+ preview: "linear-gradient(135deg, #d94b20 0 62%, #f4b183 62%)"
20115
+ },
20116
+ {
20117
+ id: "geometry",
20118
+ name: "Geometric",
20119
+ description: "Modern shapes and strong contrast",
20120
+ preview: "conic-gradient(from 220deg, #173b8f, #dce6ff, #efb7bd, #173b8f)"
20121
+ },
20122
+ {
20123
+ id: "mono",
20124
+ name: "Urban Monochrome",
20125
+ description: "Architectural black and white",
20126
+ preview: "linear-gradient(125deg, #171717, #eee 49%, #777 50%, #fafafa)"
20127
+ },
20128
+ {
20129
+ id: "earth",
20130
+ name: "Earthy Inspiration",
20131
+ description: "Natural, calm presentation system",
20132
+ preview: "radial-gradient(circle at 70% 30%, #be9473, #34271f 38%, #111 70%)"
20133
+ },
20134
+ {
20135
+ id: "future",
20136
+ name: "Future Forward",
20137
+ description: "Clean technology storytelling",
20138
+ preview: "repeating-radial-gradient(ellipse at bottom, #111 0 3px, #fff 4px 13px)"
20139
+ }
20140
+ ];
20141
+ var TEMPLATE_BACKGROUNDS = {
20142
+ blank: { backgroundColor: "#ffffff" },
20143
+ warm: {
20144
+ backgroundColor: "#d94b20",
20145
+ backgroundGradient: "linear-gradient(135deg, #d94b20 0 62%, #f4b183 62%)"
20146
+ },
20147
+ geometry: {
20148
+ backgroundColor: "#dce6ff",
20149
+ backgroundGradient: "linear-gradient(135deg, #173b8f, #dce6ff 54%, #efb7bd)"
20150
+ },
20151
+ mono: {
20152
+ backgroundColor: "#f4f4f4",
20153
+ backgroundGradient: "linear-gradient(125deg, #171717, #eeeeee 49%, #777777 50%, #fafafa)"
20154
+ },
20155
+ earth: {
20156
+ backgroundColor: "#34271f",
20157
+ backgroundGradient: "radial-gradient(circle at 70% 30%, #be9473, #34271f 38%, #111111 70%)"
20158
+ },
20159
+ future: {
20160
+ backgroundColor: "#ffffff",
20161
+ backgroundGradient: "repeating-radial-gradient(ellipse at bottom, #111111 0 3px, #ffffff 4px 13px)"
20162
+ }
20163
+ };
20164
+ function createBackstagePresentation(templateId) {
20165
+ const slide = createBlankSlide(1);
20166
+ return [{ ...slide, ...TEMPLATE_BACKGROUNDS[templateId] ?? TEMPLATE_BACKGROUNDS.blank }];
20167
+ }
20168
+ function splitFilePath(path) {
20169
+ const normalized = path.replace(/\\/g, "/");
20170
+ const parts = normalized.split("/").filter(Boolean);
20171
+ return {
20172
+ name: parts[parts.length - 1] || "Untitled Presentation.pptx",
20173
+ location: parts.slice(0, -1).join("/") || "Browser storage"
20174
+ };
20175
+ }
20176
+ async function listBackstageRecentFiles() {
20177
+ if (typeof indexedDB === "undefined") {
20178
+ return [];
20179
+ }
20180
+ try {
20181
+ const snapshots = await listAutosaveSnapshots();
20182
+ return snapshots.map((snapshot) => ({ ...snapshot, ...splitFilePath(snapshot.key) })).sort((a, b) => b.timestamp - a.timestamp);
20183
+ } catch {
20184
+ return [];
20185
+ }
20186
+ }
20187
+ async function readBackstageRecentFile(key) {
20188
+ if (typeof indexedDB === "undefined") {
20189
+ return void 0;
20190
+ }
20191
+ return (await getAutosaveSnapshot(key))?.data;
20192
+ }
20193
+ function formatBackstageDate(timestamp, now = Date.now()) {
20194
+ const elapsed = Math.max(0, now - timestamp);
20195
+ if (elapsed < 6e4) {
20196
+ return "Just now";
20197
+ }
20198
+ if (elapsed < 36e5) {
20199
+ return `${Math.floor(elapsed / 6e4)} min ago`;
20200
+ }
20201
+ if (elapsed < 864e5) {
20202
+ return `${Math.floor(elapsed / 36e5)} hr ago`;
20203
+ }
20204
+ return new Intl.DateTimeFormat(void 0, { month: "short", day: "numeric" }).format(timestamp);
20205
+ }
20206
+ function formatBackstageSize(bytes) {
20207
+ if (bytes < 1024 * 1024) {
20208
+ return `${Math.max(1, Math.round(bytes / 1024))} KB`;
20209
+ }
20210
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
20211
+ }
20212
+ var DEFAULT_VIRTUAL_OVERSCAN = 5;
20213
+ var SLIDE_VIRTUALIZATION_THRESHOLD = 50;
20214
+ function computeVirtualRange(totalItems, itemHeight, scrollTop, viewportHeight, overscan = DEFAULT_VIRTUAL_OVERSCAN) {
20215
+ const safeCount = Math.max(0, totalItems);
20216
+ const safeItemHeight = Math.max(itemHeight, 1);
20217
+ const totalHeight = safeCount * safeItemHeight;
20218
+ if (safeCount === 0) {
20219
+ return {
20220
+ startIndex: 0,
20221
+ endIndex: -1,
20222
+ totalHeight: 0,
20223
+ offsetY: 0,
20224
+ visibleRange: { start: 0, end: -1 }
20225
+ };
20226
+ }
20227
+ const visibleStart = Math.min(safeCount - 1, Math.max(0, Math.floor(scrollTop / safeItemHeight)));
20228
+ const visibleEnd = Math.min(
20229
+ safeCount - 1,
20230
+ Math.max(visibleStart, Math.floor((scrollTop + Math.max(0, viewportHeight)) / safeItemHeight))
20231
+ );
20232
+ const safeOverscan = Math.max(0, overscan);
20233
+ const startIndex = Math.max(0, visibleStart - safeOverscan);
20234
+ const endIndex = Math.min(safeCount - 1, visibleEnd + safeOverscan);
20235
+ return {
20236
+ startIndex,
20237
+ endIndex,
20238
+ totalHeight,
20239
+ offsetY: startIndex * safeItemHeight,
20240
+ visibleRange: { start: visibleStart, end: visibleEnd }
20241
+ };
20242
+ }
20243
+ function collectFontsFromElement(element, fonts) {
20244
+ if (hasTextProperties(element)) {
20245
+ if (element.textStyle?.fontFamily) {
20246
+ fonts.add(element.textStyle.fontFamily);
20247
+ }
20248
+ for (const segment of element.textSegments ?? []) {
20249
+ if (segment.style?.fontFamily) {
20250
+ fonts.add(segment.style.fontFamily);
20251
+ }
20252
+ }
20253
+ }
20254
+ if (element.type === "group") {
20255
+ for (const child2 of element.children ?? []) {
20256
+ collectFontsFromElement(child2, fonts);
20257
+ }
20258
+ }
20259
+ }
20260
+ function collectUsedFonts(slides) {
20261
+ const fonts = /* @__PURE__ */ new Set();
20262
+ for (const slide of slides) {
20263
+ for (const element of slide.elements ?? []) {
20264
+ collectFontsFromElement(element, fonts);
20265
+ }
20266
+ }
20267
+ return [...fonts].sort();
20268
+ }
20269
+ function browserFontSource() {
20270
+ return typeof document === "undefined" ? void 0 : document.fonts;
20271
+ }
20272
+ function isFontFamilyAvailable(family, source = browserFontSource()) {
20273
+ if (!source) {
20274
+ return false;
20275
+ }
20276
+ try {
20277
+ const escaped = family.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
20278
+ return source.check(`12px "${escaped}"`);
20279
+ } catch {
20280
+ return false;
20281
+ }
20282
+ }
20283
+ async function scanAvailableFontFamilies(families, source = browserFontSource()) {
20284
+ if (!source) {
20285
+ return /* @__PURE__ */ new Set();
20286
+ }
20287
+ try {
20288
+ await source.ready;
20289
+ return new Set(families.filter((family) => isFontFamilyAvailable(family, source)));
20290
+ } catch {
20291
+ return /* @__PURE__ */ new Set();
20292
+ }
20293
+ }
20294
+ function shouldLoopContinuously(input) {
20295
+ return Boolean(input.loopContinuously) || input.showType === "kiosk";
20296
+ }
20297
+ function applyRehearsalTimings(slides, timings) {
20298
+ return slides.map((slide, index) => {
20299
+ const advanceAfterMs = timings[index];
20300
+ if (typeof advanceAfterMs !== "number") {
20301
+ return slide;
20302
+ }
20303
+ return {
20304
+ ...slide,
20305
+ transition: {
20306
+ ...slide.transition,
20307
+ type: slide.transition?.type ?? "none",
20308
+ advanceAfterMs
20309
+ }
20310
+ };
20311
+ });
20312
+ }
20313
+ function computeEntranceAnimationDelay(delayMs, animationIndex) {
20314
+ return Math.max(0, delayMs || 0) + animationIndex * 60;
20315
+ }
18144
20316
  var FALLBACK_DOWNLOAD_NAME = "presentation.pptx";
18145
20317
  var REVOKE_DELAY_MS = 200;
18146
20318
  var OPEN_REVOKE_DELAY_MS = 6e4;
@@ -18233,6 +20405,22 @@ function canvasToJpegData(canvas, quality = 0.92) {
18233
20405
  }
18234
20406
  return { bytes, w: canvas.width, h: canvas.height };
18235
20407
  }
20408
+ function generatePackageReadme(presentationFilename) {
20409
+ return [
20410
+ "Presentation Package",
20411
+ "====================",
20412
+ "",
20413
+ `This folder contains the presentation "${presentationFilename}" along with`,
20414
+ "all linked media files (images, audio, video) in the /media subfolder.",
20415
+ "",
20416
+ "To view this presentation:",
20417
+ "1. Open the presentation file with any compatible presentation software",
20418
+ "2. Ensure the /media folder remains alongside the presentation file",
20419
+ "",
20420
+ `Packaged on ${(/* @__PURE__ */ new Date()).toLocaleDateString()}`,
20421
+ ""
20422
+ ].join("\n");
20423
+ }
18236
20424
  function encodeGif(frames, delay = {}) {
18237
20425
  if (frames.length === 0) {
18238
20426
  throw new Error("[gif-encoder] encodeGif: frames array must not be empty");
@@ -18693,11 +20881,11 @@ function buildNotesTextStream(lines, startY) {
18693
20881
  let content = `BT /F1 ${NOTES_FONT_SIZE} Tf 0 0 0 rg `;
18694
20882
  content += `${NOTES_MARGIN} ${startY.toFixed(2)} Td `;
18695
20883
  for (let li = 0; li < lines.length; li++) {
18696
- const line = lines[li];
20884
+ const line2 = lines[li];
18697
20885
  if (li === 0) {
18698
- content += `(${escapePdfText(line)}) Tj `;
20886
+ content += `(${escapePdfText(line2)}) Tj `;
18699
20887
  } else {
18700
- content += `0 ${(-15.399999999999999).toFixed(2)} Td (${escapePdfText(line)}) Tj `;
20888
+ content += `0 ${(-15.399999999999999).toFixed(2)} Td (${escapePdfText(line2)}) Tj `;
18701
20889
  }
18702
20890
  }
18703
20891
  content += "ET\n";
@@ -18742,12 +20930,24 @@ function isSafeSvgMarkup(svg) {
18742
20930
  }
18743
20931
  return !EVENT_HANDLER_ATTR_RE.test(trimmed);
18744
20932
  }
20933
+ function sanitizeSlideSvg(svg, width, height) {
20934
+ if (!isSafeSvgMarkup(svg)) {
20935
+ return "";
20936
+ }
20937
+ const sanitized = sanitizeMarkupOrEmpty(svg, { USE_PROFILES: { svg: true } }).trim();
20938
+ if (!sanitized || sanitized.toLowerCase().startsWith("<svg")) {
20939
+ return sanitized;
20940
+ }
20941
+ const safeWidth = Number.isFinite(width) && width > 0 ? width : 1;
20942
+ const safeHeight = Number.isFinite(height) && height > 0 ? height : 1;
20943
+ return `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${safeWidth} ${safeHeight}" width="${safeWidth}" height="${safeHeight}">${sanitized}</svg>`;
20944
+ }
18745
20945
  function buildPrintDocument(svgs, width, height, options = {}) {
18746
20946
  const { title = "Print", orientation = "landscape", colorFilter = "" } = options;
18747
20947
  const safeOrientation = sanitizeOrientation(orientation);
18748
20948
  const safeColorFilter = sanitizeCssDeclaration(colorFilter);
18749
20949
  const slidePages = svgs.map((svg, i) => {
18750
- const safeSvg = isSafeSvgMarkup(svg) ? sanitizeMarkupOrEmpty(svg, { USE_PROFILES: { svg: true } }) : "";
20950
+ const safeSvg = sanitizeSlideSvg(svg, width, height);
18751
20951
  return `<section class="print-slide-page" aria-label="Slide ${i + 1}">
18752
20952
  ${safeSvg}
18753
20953
  </section>`;
@@ -19741,4 +21941,4 @@ function segmentFrameCount(durationMs, fps) {
19741
21941
  return Math.max(1, Math.ceil(durationMs / (1e3 / fps)));
19742
21942
  }
19743
21943
 
19744
- export { ACTION_BUTTON_PRESETS, ARTISTIC_EFFECTS, BROADCAST_THROTTLE_MS, CATEGORIES, CONNECTION_TIMEOUT_MS, DEFAULT_BROADCAST_SERVER_URL, DEFAULT_COLOR_CHANGE_TOLERANCE, DEFAULT_INSERT_CHART_TYPE, DEFAULT_ROW_HEIGHT, DIRECTIONAL_PRESETS, DUOTONE_PRESETS, EXPORT_ASSEMBLING_PERCENT, EXPORT_DONE_PERCENT, FILL_MODE_OPTIONS, GRADIENT_TYPE_OPTIONS, HANDOUT_OPTIONS, INK_REPLAY_KEYFRAMES, INSERT_CHART_TYPES, LBL, LOCAL_SYNC_ORIGIN, MOBILE_BREAKPOINT, NOTES_FONT_SIZE_DEFAULT, NOTES_FONT_SIZE_MAX, NOTES_FONT_SIZE_MIN, NOTES_FONT_SIZE_STEP, NUM, PATTERN_OPTIONS, PRESENTATION_CHANNEL_NAME, PRESENTATION_HASH, PRESENTATION_MESSAGE_ORIGIN, PRESENTATION_NONCE_KEY, PRESETS, RULER_FONT_SIZE, RULER_THICKNESS, SECTION_HEADING, SEL, SHAPE_PRESET_DEFS, SLIDE_TRANSITION_KEYFRAMES, TABLET_BREAKPOINT, TABLE_STYLE_PRESETS, TAB_ROW_ACTION_CLASSES, TEXT_BUILD_ID_SEP, THEME_COLOR_LABELS, TITLE_BAR_CLASSES, TITLE_BAR_DEFAULT_FILE_KEY, TimelineEngine, WEBM_MIME_CANDIDATES, activateModalFocus, addSection, alignElements, appendPresentationInkPoint, apply3dEffects, applyCaseTransformToSegments, applyColorChange, applyDuotone, applyFormatToElement, applyReroutedConnectors, applyResize, applyStyleToSelectedSegments, bringForward, bringToFront, build3DExtrusionData, buildBroadcastViewerUrl, buildCacheKey, buildChartViewModel, buildCollaborationShareUrl, buildCreateCollaborationConfig, buildCssGradientFromShapeStyle, buildDuotoneCacheKey, buildElementClipboardPayload, buildGlowBoxShadow, buildHandoutsHtml, buildInitialGuides, buildInnerShadowCssFromShapeStyle, buildJoinCollaborationConfig, buildMultiLayerShadowCss, buildNotesHtml, buildNotesPdfBytes, buildOutlineHtml, buildPatternFillCss, buildPresentationAudienceUrl, buildPreviewAnimation, buildPrintDocument, buildPrintHtmlDocument, buildReflectionCss, buildShadowCssFromShapeStyle, buildSlidesHtml, buildSlidesPdfBytes, buildSmartArt3DModel, buildSmartArtA11y, buildSmartArtPresetData, buildTextBody3DSceneStyle, buildTextFillCss, buildTextReflectionCss, buildTextRunFilterChain, buildTextShadowCss, buildThemeColorGrid, buildUserFontFaceStyles, canvasToJpegData, chartPartToAttrs, clampCropValue, clampNotesFontSize, clampPercent, clampUnitInterval, clearPresentationDeck, cloneElementForPaste, cloneHistorySnapshot, cloneSlide, cloneTemplateElementsBySlideId, collectImagePaths, collectMediaElements, colorWithOpacity, comparePresentation, computeAllNotesPages, computeAutoFitTextStyle, computeColumnBoundaries, computeHandoutLayout, computeInlineEditorRect, computeKeyboardInset, computeLayout, computeLayoutOptions, computeMarqueeHitIds, computeMergeCellDown, computeMergeCellRight, computeResizedColumnWidths, computeResizedRowHeight, computeScrollDelta, computeSelectionRect, computeSmartArtLayout, computeSnapToShape, computeSplitCell, computeValueRange, computeValueRangeForChart, convertLatexToOmml, convertOmmlToLatex, convertOmmlToMathMl, copyFormatFromElement, countTextSegments, createBlankSlide, createDefaultChartElement, createInitialPresentationSnapshot, createPresentationSessionId, createPresenterTimer, createSyncGate, createTouchGestureRecognizer, defaultCssVars, defaultRadius, defaultThemeColors, deleteSection, deleteTableColumn, deleteTableRow, derivePresenceList, detectOrientation, detectTouchDevice, distributeElements, downloadBlob, downloadDataUrl, dragAnchorViewY, dragValueForPart, encodeGif, erasePresentationInkAt, expandTextBuildAnimations, extractPathPoints, filterCommands, findChartPartTarget, findSmartArtNodeText, formatAxisValue, formatAxisValueWithUnits, formatBytes, formatCommentTimestamp, formatElapsed, formatMobileElapsed, formatRelativeTime, formatSlideCounter, formatTime, formatVersionTimestamp, fpsToFrameIntervalMs, generateBroadcastRoomId, generateElementId, generateLogTicks, generateNoteLineCount, generatePressureCircles, generateTicks, getAnimationInitialStyle, getAriaLabel, getAriaRole, getAriaRoleDescription, getCachedResult, getChartStylePalette, getCommentMarkerPosition, getCompoundLineBorderWidth, getCompoundLineBoxShadow, getCompoundLineOffsets, getCompoundLineWidths, getConnectionSites, getContentPartReplayStyles, getCssBorderDashStyle, getDisplayUnitLabel, getDuotoneCachedResult, getEffectDagFilter, getElementLabel, getElementTransform, getElementTransformWithoutRotation, getImageEffectsFilter, getImageEffectsOpacity, getInkReplayStyles, getInlineEditorSelection, getKinsokuLineBreakStyles, getNotesPrintableArea, getOleBadgeLabel, getOleTypeColor, getOleTypeLabel, getPatternSvg, getPendingSelectionRestore, getPrintableArea, getResolvedShapeClipPath, getRoundRectRadiusPx, getSecondaryValueAxis, getShapeAdjustmentHandleDescriptor, getSlideBackgroundStyle, getSlideTransitionAnimations, getSvgStrokeDasharray, getTextAlphaOpacity, getTextCompensationTransform, getWarpPath, groupElements, groupIntoParagraphs, hasCopyableFormat, hasDistinctScriptFonts, hasPressureVariation, insertTableColumn, insertTableRow, isBrowserOpenableMime, isCellInRect, isEditableTextElement, isExportAbortError, isFirstSlide, isKeyboardOpen, isLastSlide, isMixedContentBlocked, isMobileViewport, isPpactionUrl, isPresentationSessionMessage, isTemplateElement, isTemplateElementId, isUrlSafe, loadPresentationDeck, makeCloneId, makeSlideId, mergeAdditiveSelection, mergeCells, mergePresentationSnapshot, mobileElapsedSince, mobileSlideCounter, mountModel3D, mountSurfaceChart3D, movePresenterPointer, moveSectionDown, moveSectionUp, moveSlidesToSection, newTableElement, normalizeColorsForCapture, normalizeHexColor, normalizeStrokeDashType, normalizeValue, notesSegmentsToSpans, observeYDocSlides, ooxmlDashToCssBorderStyle, openPptxFile, openUrlInNewTab, paletteColor, parseDrawingColor, parseDrawingColorOpacity, parsePpactionUrl, pickSupportedMimeType, placeAudienceWindow, preprocessCssForCapture, presentationInkPath, presenterElapsed, pressuresToWidths, readSlidesFromYDoc, readViewportMetrics, rebuildDrawingShapesIfCleared, reconcileSlidesInYDoc, recordProgressPercent, rectToCells, remapTextToSegments, renameSection, rerouteConnectorsForMovedElements, resetPresenterTimer, resolveAudienceScreenPlacement, resolveCssTextAlign, resolveDrawingShapeNodeId, resolveFontForScript, resolveInkColor, resolveInkOpacity, resolveInkWidth, resolveLineHeight, resolveOleType, resolveParagraphAlign, resolveParagraphRtl, resolveRegionCode, resolveTitleBarStatusKey, resolveTransitionDurationMs, resolveTransportForServerUrl, resolveUnderlineDecorationStyle, restoreSegmentSelection, safeOpenUrl, sanitizeColor, sanitizeDownloadFilename, sanitizeMathMl, saveAutosaveSnapshot, segmentByScript, segmentFrameCount, sendBackward, sendToBack, sequentialColorScale, seriesColor, setCachedResult, setDuotoneCachedResult, setPendingSelectionRestore, shadeColor2, shouldCommitSmartArtNodeText, shouldUseSvgWarp, slideProgressPercent, slideStatusLabel, snapBoxToGrid, splitCell, splitSeriesByAxis, stepPresenterZoom, storePresentationDeck, substituteFieldText, svgLineCap, swapPresentationWindows, themeToCssVars, tintColor2, toCssTextOrientation, toCssVerticalDirection, toCssWritingMode, togglePresenterTimer, transformTextCase, validateRoomId, valueToY, vermilionDarkColors, vermilionDarkTheme, vermilionLightColors, vermilionLightTheme, vermilionRadius, withChartPointValue, withChartTitle };
21944
+ export { ACTION_BUTTON_PRESETS, ARTISTIC_EFFECTS, BACKSTAGE_NAV, BACKSTAGE_TEMPLATES, BROADCAST_THROTTLE_MS, CATEGORIES, CONNECTION_TIMEOUT_MS, DEFAULT_BROADCAST_SERVER_URL, DEFAULT_COLOR_CHANGE_TOLERANCE, DEFAULT_INSERT_CHART_TYPE, DEFAULT_ROW_HEIGHT, DEFAULT_VIRTUAL_OVERSCAN, DIRECTIONAL_PRESETS, DUOTONE_PRESETS, EXPORT_ASSEMBLING_PERCENT, EXPORT_DONE_PERCENT, FILL_MODE_OPTIONS, GRADIENT_TYPE_OPTIONS, HANDOUT_OPTIONS, INK_REPLAY_KEYFRAMES, INSERT_CHART_TYPES, LBL, LOCAL_SYNC_ORIGIN, MOBILE_BREAKPOINT, NOTES_FONT_SIZE_DEFAULT, NOTES_FONT_SIZE_MAX, NOTES_FONT_SIZE_MIN, NOTES_FONT_SIZE_STEP, NUM, PATTERN_OPTIONS, PRESENTATION_CHANNEL_NAME, PRESENTATION_HASH, PRESENTATION_MESSAGE_ORIGIN, PRESENTATION_NONCE_KEY, PRESETS, RULER_FONT_SIZE, RULER_THICKNESS, SECTION_HEADING, SEL, SHAPE_PRESET_DEFS, SLIDE_TRANSITION_KEYFRAMES, SLIDE_VIRTUALIZATION_THRESHOLD, TABLET_BREAKPOINT, TABLE_STYLE_PRESETS, TAB_ROW_ACTION_CLASSES, TEXT_BUILD_ID_SEP, THEME_COLOR_LABELS, TITLE_BAR_CLASSES, TITLE_BAR_DEFAULT_FILE_KEY, TimelineEngine, WEBM_MIME_CANDIDATES, activateModalFocus, addSection, alignElements, appendPresentationInkPoint, apply3dEffects, applyAcceptAllSlides, applyAcceptSlide, applyCaseTransformToSegments, applyColorChange, applyDuotone, applyFormatToElement, applyRehearsalTimings, applyReroutedConnectors, applyResize, applyStyleToSelectedSegments, bringForward, bringToFront, build3DExtrusionData, buildBroadcastViewerUrl, buildCacheKey, buildChartViewModel, buildCollaborationShareUrl, buildCreateCollaborationConfig, buildCssGradientFromShapeStyle, buildDuotoneCacheKey, buildElementClipboardPayload, buildGlowBoxShadow, buildHandoutsHtml, buildInitialGuides, buildInnerShadowCssFromShapeStyle, buildJoinCollaborationConfig, buildMultiLayerShadowCss, buildNotesHtml, buildNotesPdfBytes, buildOutlineHtml, buildPatternFillCss, buildPresentationAudienceUrl, buildPreviewAnimation, buildPrintDocument, buildPrintHtmlDocument, buildReflectionCss, buildShadowCssFromShapeStyle, buildSlidesHtml, buildSlidesPdfBytes, buildSmartArt3DModel, buildSmartArtA11y, buildSmartArtPresetData, buildSummaryZoomView, buildTextBody3DSceneStyle, buildTextFillCss, buildTextReflectionCss, buildTextRunFilterChain, buildTextShadowCss, buildThemeColorGrid, buildUserFontFaceStyles, canvasToJpegData, chartPartToAttrs, clampCropValue, clampNotesFontSize, clampPercent, clampUnitInterval, clearPresentationDeck, cloneElementForPaste, cloneHistorySnapshot, cloneSlide, cloneTemplateElementsBySlideId, collectImagePaths, collectMediaElements, collectUsedFonts, colorWithOpacity, comparePresentation, computeAllNotesPages, computeAutoFitTextStyle, computeColumnBoundaries, computeEntranceAnimationDelay, computeHandoutLayout, computeInlineEditorRect, computeKeyboardInset, computeLayout, computeLayoutOptions, computeMarqueeHitIds, computeMergeCellDown, computeMergeCellRight, computeResizedColumnWidths, computeResizedRowHeight, computeScrollDelta, computeSelectionRect, computeSmartArtLayout, computeSnapToShape, computeSplitCell, computeValueRange, computeValueRangeForChart, computeVirtualRange, convertLatexToOmml, convertOmmlToLatex, convertOmmlToMathMl, copyFormatFromElement, countTextSegments, createBackstagePresentation, createBlankSlide, createDefaultChartElement, createInitialPresentationSnapshot, createPresentationSessionId, createPresenterTimer, createSyncGate, createTouchGestureRecognizer, defaultCssVars, defaultRadius, defaultThemeColors, deleteSection, deleteTableColumn, deleteTableRow, derivePresenceList, detectOrientation, detectTouchDevice, distributeElements, downloadBlob, downloadDataUrl, dragAnchorViewY, dragValueForPart, encodeGif, erasePresentationInkAt, expandTextBuildAnimations, extractPathPoints, filterCommands, findChartPartTarget, findSmartArtNodeText, formatAxisValue, formatAxisValueWithUnits, formatBackstageDate, formatBackstageSize, formatBytes, formatCommentTimestamp, formatElapsed, formatMobileElapsed, formatRelativeTime, formatSlideCounter, formatTime, formatVersionTimestamp, fpsToFrameIntervalMs, generateBroadcastRoomId, generateElementId, generateLogTicks, generateNoteLineCount, generatePackageReadme, generatePressureCircles, generateTicks, getAnimationInitialStyle, getAriaLabel, getAriaRole, getAriaRoleDescription, getCachedResult, getChartStylePalette, getCommentMarkerPosition, getCompoundLineBorderWidth, getCompoundLineBoxShadow, getCompoundLineOffsets, getCompoundLineWidths, getConnectionSites, getContentPartReplayStyles, getCssBorderDashStyle, getDisplayUnitLabel, getDuotoneCachedResult, getEffectDagFilter, getElementLabel, getElementTransform, getElementTransformWithoutRotation, getImageColorWashStyle, getImageEffectsFilter, getImageEffectsOpacity, getImageSvgFilters, getInkReplayStyles, getInlineEditorSelection, getKinsokuLineBreakStyles, getNotesPrintableArea, getOleBadgeLabel, getOleTypeColor, getOleTypeLabel, getPatternSvg, getPendingSelectionRestore, getPrintableArea, getResolvedShapeClipPath, getRoundRectRadiusPx, getSecondaryValueAxis, getShapeAdjustmentHandleDescriptor, getSlideBackgroundStyle, getSlideTransitionAnimations, getSvgStrokeDasharray, getTextAlphaOpacity, getTextCompensationTransform, getWarpPath, groupElements, groupIntoParagraphs, groupSlidesBySection, hasCopyableFormat, hasDistinctScriptFonts, hasPressureVariation, insertTableColumn, insertTableRow, isBrowserOpenableMime, isCellInRect, isEditableTextElement, isExportAbortError, isFirstSlide, isKeyboardOpen, isLastSlide, isMixedContentBlocked, isMobileViewport, isPpactionUrl, isPresentationSessionMessage, isTemplateElement, isTemplateElementId, isUrlSafe, listBackstageRecentFiles, loadPresentationDeck, makeCloneId, makeSlideId, mergeAdditiveSelection, mergeCells, mergePresentationSnapshot, mobileElapsedSince, mobileSlideCounter, mountModel3D, mountSurfaceChart3D, movePresenterPointer, moveSectionDown, moveSectionUp, moveSlidesToSection, newTableElement, normalizeColorsForCapture, normalizeHexColor, normalizeStrokeDashType, normalizeValue, notesSegmentsToSpans, observeYDocSlides, ooxmlDashToCssBorderStyle, openPptxFile, openUrlInNewTab, paletteColor, parseDrawingColor, parseDrawingColorOpacity, parsePpactionUrl, pickSupportedMimeType, placeAudienceWindow, preprocessCssForCapture, presentationInkPath, presenterElapsed, pressuresToWidths, readBackstageRecentFile, readSlidesFromYDoc, readViewportMetrics, rebuildDrawingShapesIfCleared, reconcileSlidesInYDoc, recordProgressPercent, rectToCells, remapTextToSegments, renameSection, rerouteConnectorsForMovedElements, resetPresenterTimer, resolveAudienceScreenPlacement, resolveCssTextAlign, resolveDrawingShapeNodeId, resolveFontForScript, resolveInkColor, resolveInkOpacity, resolveInkWidth, resolveLineHeight, resolveOleType, resolveParagraphAlign, resolveParagraphRtl, resolvePictureBullet, resolveRegionCode, resolveTitleBarStatusKey, resolveTransitionDurationMs, resolveTransportForServerUrl, resolveUnderlineDecorationStyle, restoreSegmentSelection, safeOpenUrl, sanitizeColor, sanitizeDownloadFilename, sanitizeMathMl, saveAutosaveSnapshot, scanAvailableFontFamilies, segmentByScript, segmentFrameCount, sendBackward, sendToBack, sequentialColorScale, seriesColor, setCachedResult, setDuotoneCachedResult, setPendingSelectionRestore, shadeColor2, shouldCommitSmartArtNodeText, shouldLoopContinuously, shouldUseSvgWarp, slideProgressPercent, slideStatusLabel, snapBoxToGrid, splitCell, splitSeriesByAxis, stepPresenterZoom, storePresentationDeck, substituteFieldText, svgLineCap, swapPresentationWindows, themeToCssVars, tintColor2, toCssTextOrientation, toCssVerticalDirection, toCssWritingMode, togglePresenterTimer, transformTextCase, validateRoomId, valueToY, vermilionDarkColors, vermilionDarkTheme, vermilionLightColors, vermilionLightTheme, vermilionRadius, withChartPointValue, withChartTitle };