pptx-angular-viewer 4.10.0 → 4.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -387,13 +387,25 @@ function renderSelectMenu(menu, choices, value) {
387
387
  });
388
388
  menu.replaceChildren(...menuItems);
389
389
  }
390
- /** Keep the popup anchored below the trigger within the viewport. */
390
+ /** Prefer below the trigger, flipping above when it offers more room. */
391
391
  function positionSelectMenu(menu, trigger) {
392
392
  const rect = trigger.getBoundingClientRect();
393
- menu.style.minWidth = `${rect.width}px`;
394
- menu.style.maxHeight = `${Math.min(240, Math.max(0, window.innerHeight - rect.bottom - 8))}px`;
395
- menu.style.top = `${rect.bottom + 4}px`;
396
- menu.style.left = `${Math.max(0, Math.min(rect.left, window.innerWidth - menu.offsetWidth))}px`;
393
+ const margin = 8;
394
+ const gap = 4;
395
+ const width = Math.max(0, window.innerWidth - margin * 2);
396
+ const below = Math.max(0, window.innerHeight - rect.bottom - gap - margin);
397
+ const above = Math.max(0, rect.top - gap - margin);
398
+ menu.style.minWidth = `${Math.min(rect.width, width)}px`;
399
+ menu.style.maxWidth = `${width}px`;
400
+ // Measure at the normal height cap before choosing a side. Reset the cap
401
+ // on every reposition so a previously constrained menu can grow again.
402
+ menu.style.maxHeight = '240px';
403
+ const preferredHeight = menu.getBoundingClientRect().height;
404
+ const flip = below < preferredHeight && above > below;
405
+ menu.style.maxHeight = `${Math.min(240, flip ? above : below)}px`;
406
+ const { height, width: menuWidth } = menu.getBoundingClientRect();
407
+ menu.style.top = `${Math.max(margin, flip ? rect.top - gap - height : rect.bottom + gap)}px`;
408
+ menu.style.left = `${Math.max(margin, Math.min(rect.left, window.innerWidth - menuWidth - margin))}px`;
397
409
  }
398
410
  /** Expose the keyboard target while keeping the selected state separate. */
399
411
  function markSelectActive(menu, trigger, active, open) {
@@ -693,16 +705,18 @@ function definePptxSelect(registry) {
693
705
  this.text.textContent =
694
706
  this.choices.find((choice) => choice.value === this.value)?.label ?? '';
695
707
  this.trigger.disabled = this.disabled;
708
+ if (this.disabled) {
709
+ this.close();
710
+ }
696
711
  this.trigger.setAttribute('aria-label', this.getAttribute('aria-label') ?? '');
697
712
  this.internals?.setFormValue?.(this.disabled ? null : this.value);
698
- // Build the popup lazily when an inspector select opens.
699
713
  if (this.open || this.menu.childElementCount) {
700
714
  this.renderMenu();
701
715
  }
702
716
  }
703
717
  renderMenu() {
704
718
  renderSelectMenu(this.menu, this.choices, this.value);
705
- this.markActive();
719
+ markSelectActive(this.menu, this.trigger, this.active, this.open);
706
720
  }
707
721
  show() {
708
722
  if (this.disabled ||
@@ -710,10 +724,6 @@ function definePptxSelect(registry) {
710
724
  this.open) {
711
725
  return;
712
726
  }
713
- const availableBelow = window.innerHeight - this.trigger.getBoundingClientRect().bottom;
714
- if (availableBelow < Math.min(this.choices.length * 32 + 12, 160)) {
715
- this.trigger.scrollIntoView?.({ block: 'center' });
716
- }
717
727
  this.open = true;
718
728
  this.setAttribute('open', '');
719
729
  const selected = this.choices.findIndex((choice) => choice.value === this.value && !choice.disabled && !choice.hidden);
@@ -730,7 +740,7 @@ function definePptxSelect(registry) {
730
740
  }
731
741
  this.trigger.setAttribute('aria-expanded', 'true');
732
742
  this.position();
733
- this.markActive();
743
+ markSelectActive(this.menu, this.trigger, this.active, this.open);
734
744
  document.addEventListener('pointerdown', this.onOutside, true);
735
745
  window.addEventListener('resize', this.position);
736
746
  window.addEventListener('scroll', this.position, true);
@@ -740,6 +750,8 @@ function definePptxSelect(registry) {
740
750
  return;
741
751
  }
742
752
  this.open = false;
753
+ window.clearTimeout(this.searchTimer);
754
+ this.search = '';
743
755
  this.removeAttribute('open');
744
756
  if (this.menu.matches(':popover-open')) {
745
757
  this.menu.hidePopover();
@@ -751,9 +763,6 @@ function definePptxSelect(registry) {
751
763
  window.removeEventListener('resize', this.position);
752
764
  window.removeEventListener('scroll', this.position, true);
753
765
  }
754
- markActive() {
755
- markSelectActive(this.menu, this.trigger, this.active, this.open);
756
- }
757
766
  move(step) {
758
767
  if (!this.open) {
759
768
  this.show();
@@ -762,7 +771,7 @@ function definePptxSelect(registry) {
762
771
  return;
763
772
  }
764
773
  this.active = nextSelectActive(this.choices, this.active, step);
765
- this.markActive();
774
+ markSelectActive(this.menu, this.trigger, this.active, this.open);
766
775
  }
767
776
  commit(index) {
768
777
  const choice = this.choices[index];
@@ -793,11 +802,9 @@ function definePptxSelect(registry) {
793
802
  this.show();
794
803
  this.active =
795
804
  event.key === 'Home'
796
- ? this.choices.findIndex((choice) => !choice.disabled && !choice.hidden)
797
- : this.choices.length -
798
- 1 -
799
- [...this.choices].reverse().findIndex((choice) => !choice.disabled && !choice.hidden);
800
- this.markActive();
805
+ ? nextSelectActive(this.choices, -1, 1)
806
+ : nextSelectActive(this.choices, 0, -1);
807
+ markSelectActive(this.menu, this.trigger, this.active, this.open);
801
808
  }
802
809
  else if (event.key === 'Enter' || event.key === ' ') {
803
810
  event.preventDefault();
@@ -815,6 +822,9 @@ function definePptxSelect(registry) {
815
822
  this.close();
816
823
  this.trigger.focus();
817
824
  }
825
+ else if (event.key === 'Tab') {
826
+ this.close();
827
+ }
818
828
  else if (event.key.length === 1 && !event.ctrlKey && !event.metaKey && !event.altKey) {
819
829
  event.stopPropagation();
820
830
  this.search += event.key.toLocaleLowerCase();
@@ -828,7 +838,7 @@ function definePptxSelect(registry) {
828
838
  choice.label.toLocaleLowerCase().startsWith(this.search));
829
839
  if (index >= 0) {
830
840
  this.active = index;
831
- this.markActive();
841
+ markSelectActive(this.menu, this.trigger, this.active, this.open);
832
842
  }
833
843
  }
834
844
  }
@@ -6513,7 +6523,7 @@ function renderedImageCorrections(effects) {
6513
6523
  };
6514
6524
  }
6515
6525
  /** Clamp a number to the inclusive `[lo, hi]` range. */
6516
- function clamp$a(v, lo, hi) {
6526
+ function clamp$9(v, lo, hi) {
6517
6527
  return v < lo ? lo : v > hi ? hi : v;
6518
6528
  }
6519
6529
  /** Neutral colour temperature, in Kelvin (`a14:colorTemperature/@colorTemp`). */
@@ -6532,7 +6542,7 @@ function buildColorTemperatureCss(colorTempK) {
6532
6542
  if (diff === 0) {
6533
6543
  return undefined;
6534
6544
  }
6535
- const strength = clamp$a(Math.abs(diff) / COLOR_TEMP_RANGE_K, 0, 1);
6545
+ const strength = clamp$9(Math.abs(diff) / COLOR_TEMP_RANGE_K, 0, 1);
6536
6546
  if (diff < 0) {
6537
6547
  // Warmer: sepia + a small negative hue-rotate biases the sepia toward
6538
6548
  // orange rather than its default brown.
@@ -6581,7 +6591,7 @@ function getImageCorrectionsFilterTokens(imageEffects) {
6581
6591
  // Soften: a small CSS blur. -100000 (fully softened) maps to a 3px blur,
6582
6592
  // a deliberately gentle ceiling since this runs on top of the picture's
6583
6593
  // full-resolution bitmap rather than a thumbnail.
6584
- const strength = clamp$a(Math.abs(sharpenSoftenAmount) / 100000, 0, 1);
6594
+ const strength = clamp$9(Math.abs(sharpenSoftenAmount) / 100000, 0, 1);
6585
6595
  tokens.push(`blur(${(strength * 3).toFixed(2)}px)`);
6586
6596
  }
6587
6597
  return tokens;
@@ -6612,18 +6622,18 @@ function getImageSharpenFilter(effects, elementId) {
6612
6622
  if (typeof amount !== 'number' || amount <= 0) {
6613
6623
  return undefined;
6614
6624
  }
6615
- const strength = clamp$a(amount / 100000, 0, 1);
6625
+ const strength = clamp$9(amount / 100000, 0, 1);
6616
6626
  const id = getImageSharpenFilterId(elementId);
6617
6627
  const filterMarkup = `<feConvolveMatrix order="3" kernelMatrix="${buildSharpenKernel(strength)}" preserveAlpha="true"/>`;
6618
6628
  return { id, cssReference: `url(#${id})`, filterMarkup };
6619
6629
  }
6620
6630
 
6621
- function clamp$9(value, minimum, maximum) {
6631
+ function clamp$8(value, minimum, maximum) {
6622
6632
  return Math.min(maximum, Math.max(minimum, value));
6623
6633
  }
6624
6634
  /** Build an SVG discrete-transfer table for a 0 through 100 percent threshold. */
6625
6635
  function buildImageBiLevelTable(threshold) {
6626
- const cutoff = clamp$9(threshold, 0, 100);
6636
+ const cutoff = clamp$8(threshold, 0, 100);
6627
6637
  return Array.from({ length: 101 }, (_, index) => (index >= cutoff ? '1' : '0')).join(' ');
6628
6638
  }
6629
6639
  /**
@@ -6631,7 +6641,7 @@ function buildImageBiLevelTable(threshold) {
6631
6641
  * Positive values move channels toward white; negative values move them toward black.
6632
6642
  */
6633
6643
  function buildImageLuminanceTransfer(amount) {
6634
- const normalized = clamp$9(amount / 100, -1, 1);
6644
+ const normalized = clamp$8(amount / 100, -1, 1);
6635
6645
  return normalized >= 0
6636
6646
  ? { slope: 1 - normalized, intercept: normalized }
6637
6647
  : { slope: 1 + normalized, intercept: 0 };
@@ -6805,12 +6815,12 @@ function getImageFillOverlayFilter(element, elementId = isImageLikeElement$1(ele
6805
6815
 
6806
6816
  // ── Internal helpers ────────────────────────────────────────────────────────
6807
6817
  /** Clamp a number to the inclusive `[lo, hi]` range. */
6808
- function clamp$8(v, lo, hi) {
6818
+ function clamp$7(v, lo, hi) {
6809
6819
  return v < lo ? lo : v > hi ? hi : v;
6810
6820
  }
6811
6821
  /** Normalize a radius value (0–100) to a 0–1 float for proportional scaling. */
6812
6822
  function normalizeRadius01(radius) {
6813
- return clamp$8(radius / 100, 0, 1);
6823
+ return clamp$7(radius / 100, 0, 1);
6814
6824
  }
6815
6825
  /** Get the image effects off an element, or `undefined` for non-image elements. */
6816
6826
  function getEffects(element) {
@@ -7089,7 +7099,7 @@ function getImageEffectsOpacity(element) {
7089
7099
  return undefined;
7090
7100
  }
7091
7101
  if (typeof effects.alphaModFix === 'number') {
7092
- return clamp$8(effects.alphaModFix / 100, 0, 1);
7102
+ return clamp$7(effects.alphaModFix / 100, 0, 1);
7093
7103
  }
7094
7104
  return undefined;
7095
7105
  }
@@ -7158,7 +7168,7 @@ function buildImageAlphaFilterMarkup(effects) {
7158
7168
  // a:cont container (see PptxImageEffects.alphaMod.amt). Distinct from the
7159
7169
  // sibling alphaModFix effect (handled via CSS opacity, not here).
7160
7170
  if (typeof effects.alphaMod?.amt === 'number') {
7161
- const mul = clamp$8(effects.alphaMod.amt / 100, 0, 1);
7171
+ const mul = clamp$7(effects.alphaMod.amt / 100, 0, 1);
7162
7172
  next((inp, out) => `<feComponentTransfer in="${inp}" result="${out}">` +
7163
7173
  `<feFuncA type="linear" slope="${mul}" intercept="0"/>` +
7164
7174
  '</feComponentTransfer>');
@@ -7179,13 +7189,13 @@ function buildImageAlphaFilterMarkup(effects) {
7179
7189
  '</feComponentTransfer>');
7180
7190
  }
7181
7191
  if (typeof effects.alphaRepl === 'number') {
7182
- const a = clamp$8(effects.alphaRepl / 100, 0, 1);
7192
+ const a = clamp$7(effects.alphaRepl / 100, 0, 1);
7183
7193
  next((inp, out) => `<feComponentTransfer in="${inp}" result="${out}">` +
7184
7194
  `<feFuncA type="linear" slope="0" intercept="${a}"/>` +
7185
7195
  '</feComponentTransfer>');
7186
7196
  }
7187
7197
  if (typeof effects.alphaBiLevel === 'number') {
7188
- const t = clamp$8(effects.alphaBiLevel / 100, 0, 1);
7198
+ const t = clamp$7(effects.alphaBiLevel / 100, 0, 1);
7189
7199
  const table = Array.from({ length: 10 }, (_, i) => (i / 10 >= t ? '1' : '0')).join(' ');
7190
7200
  next((inp, out) => `<feComponentTransfer in="${inp}" result="${out}">` +
7191
7201
  `<feFuncA type="discrete" tableValues="${table}"/>` +
@@ -7228,7 +7238,7 @@ function buildImageAlphaFilterMarkup(effects) {
7228
7238
  '</feComponentTransfer>');
7229
7239
  }
7230
7240
  if (effects.hsl && typeof effects.hsl.sat === 'number') {
7231
- const v = clamp$8(1 + effects.hsl.sat / 100, 0, 2);
7241
+ const v = clamp$7(1 + effects.hsl.sat / 100, 0, 2);
7232
7242
  next((inp, out) => `<feColorMatrix in="${inp}" result="${out}" type="saturate" values="${v}"/>`);
7233
7243
  }
7234
7244
  if (effects.hsl && typeof effects.hsl.lum === 'number') {
@@ -7297,7 +7307,7 @@ function buildDiscreteTable(steps) {
7297
7307
  * don't need an SVG filter (handled via CSS instead).
7298
7308
  */
7299
7309
  function buildArtisticFilterMarkup(effectName, radius) {
7300
- const normalizedRadius = clamp$8(radius, 0, 100) / 100;
7310
+ const normalizedRadius = clamp$7(radius, 0, 100) / 100;
7301
7311
  switch (effectName) {
7302
7312
  case 'artisticFilmGrain':
7303
7313
  case 'filmGrain': {
@@ -19801,7 +19811,7 @@ function buildAreas(chartData, catCount, layout, range, sourceIndices, xPosition
19801
19811
  return { primitives: [...primitives, ...labelBoxes], dataLabels };
19802
19812
  }
19803
19813
 
19804
- function clamp$7(value, min, max) {
19814
+ function clamp$6(value, min, max) {
19805
19815
  return Math.min(Math.max(value, min), max);
19806
19816
  }
19807
19817
  /** Resolve where the horizontal category/date axis crosses its linked value axis. */
@@ -19814,7 +19824,7 @@ function horizontalAxisY(valueAxis, range, layout, fallback) {
19814
19824
  }
19815
19825
  const crossingValue = valueAxis?.crossesAt ?? 0;
19816
19826
  if (valueAxis?.crossesAt !== undefined || valueAxis?.crosses === 'autoZero') {
19817
- return valueToY(clamp$7(crossingValue, range.min, range.max), range, layout.plotTop, layout.plotBottom);
19827
+ return valueToY(clamp$6(crossingValue, range.min, range.max), range, layout.plotTop, layout.plotBottom);
19818
19828
  }
19819
19829
  return fallback === 'top' ? layout.plotTop : layout.plotBottom;
19820
19830
  }
@@ -19830,10 +19840,10 @@ function verticalAxisX(categoryAxis, categoryCount, layout, fallback, dateValues
19830
19840
  if (categoryAxis.axisType === 'dateAx' && dateValues?.length) {
19831
19841
  const min = categoryAxis.min ?? Math.min(...dateValues);
19832
19842
  const max = categoryAxis.max ?? Math.max(...dateValues);
19833
- const ratio = max === min ? 0.5 : (clamp$7(categoryAxis.crossesAt, min, max) - min) / (max - min);
19843
+ const ratio = max === min ? 0.5 : (clamp$6(categoryAxis.crossesAt, min, max) - min) / (max - min);
19834
19844
  return layout.plotLeft + ratio * layout.plotWidth;
19835
19845
  }
19836
- const index = clamp$7(categoryAxis.crossesAt - 1, 0, Math.max(categoryCount - 1, 0));
19846
+ const index = clamp$6(categoryAxis.crossesAt - 1, 0, Math.max(categoryCount - 1, 0));
19837
19847
  const ratio = categoryCount <= 1 ? 0.5 : index / (categoryCount - 1);
19838
19848
  return layout.plotLeft + ratio * layout.plotWidth;
19839
19849
  }
@@ -19948,7 +19958,7 @@ function buildCartesianAxes(chartData, layout, primaryRange, secondaryRange, cat
19948
19958
  }
19949
19959
 
19950
19960
  /** Per-category absolute totals (for percentStacked normalisation). */
19951
- function categoryTotals$2(series, catCount) {
19961
+ function categoryTotals$1(series, catCount) {
19952
19962
  return Array.from({ length: catCount }, (_, ci) => series.reduce((sum, s) => sum + Math.abs(s.values[ci] ?? 0), 0));
19953
19963
  }
19954
19964
  /**
@@ -19961,7 +19971,7 @@ function buildPercentStackedBars(chartData, catCount, layout, primaryRange, sour
19961
19971
  const primitives = [], dataLabels = [], series = chartData.series, palette = chartData.colorPalette, showLabels = chartData.style?.hasDataLabels, barGroupWidth = layout.plotWidth / Math.max(catCount, 1), barW = barGroupWidth * 0.6, barOffset = (barGroupWidth - barW) / 2, displaySeries = series.map((entry) => ({
19962
19972
  ...entry,
19963
19973
  values: sourceIndices.map((sourceIndex) => entry.values[sourceIndex] ?? 0),
19964
- })), totals = categoryTotals$2(displaySeries, catCount);
19974
+ })), totals = categoryTotals$1(displaySeries, catCount);
19965
19975
  for (let ci = 0; ci < catCount; ci++) {
19966
19976
  let posRunning = 0, negRunning = 0;
19967
19977
  const catTotal = totals[ci] || 1;
@@ -21423,7 +21433,7 @@ function widestCategoryLabelWidth(categoryLabels, fontSize) {
21423
21433
  return categoryLabels.reduce((max, label) => Math.max(max, estimateTextWidth(label, fontSize)), 0);
21424
21434
  }
21425
21435
  /** Per-category absolute totals (for percentStacked normalisation). */
21426
- function categoryTotals$1(series, catCount) {
21436
+ function categoryTotals(series, catCount) {
21427
21437
  return Array.from({ length: catCount }, (_, ci) => series.reduce((sum, s) => sum + Math.abs(s.values[ci] ?? 0), 0));
21428
21438
  }
21429
21439
  /** Resolve the fill for one bar, honouring dPt overrides and varyColors. */
@@ -21509,7 +21519,7 @@ function buildHorizontalBarViewModel(element, chartData, categoryLabels) {
21509
21519
  }
21510
21520
  }
21511
21521
  else {
21512
- const totals = isPercent ? categoryTotals$1(series, catCount) : [], barH = band * (isPercent ? 0.6 : 0.7), barOffset = (band - barH) / 2;
21522
+ const totals = isPercent ? categoryTotals(series, catCount) : [], barH = band * (isPercent ? 0.6 : 0.7), barOffset = (band - barH) / 2;
21513
21523
  for (let ci = 0; ci < catCount; ci++) {
21514
21524
  let posRunning = 0, negRunning = 0;
21515
21525
  const catTotal = isPercent ? totals[ci] || 1 : 1;
@@ -26429,7 +26439,7 @@ const RADAR_RINGS = 4, RADAR_RING_COLOR = '#cbd5e1', RADAR_SPOKE_COLOR = '#94a3b
26429
26439
  // COM (callouts-com.pptx): with no c:dLblPos, PowerPoint centres a radar
26430
26440
  // label on the spoke, this many label font sizes out past its point.
26431
26441
  RADAR_LABEL_RADIAL_EM = 3.5;
26432
- const clamp$6 = (v, lo, hi) => Math.min(Math.max(v, lo), Math.max(lo, hi));
26442
+ const clamp$5 = (v, lo, hi) => Math.min(Math.max(v, lo), Math.max(lo, hi));
26433
26443
  /**
26434
26444
  * Build the view-model for a radar / spider chart. Polar, so it has no
26435
26445
  * cartesian gridlines/axes; ring + spoke geometry and the data polygons all
@@ -26525,8 +26535,8 @@ function buildRadarViewModel(element, chartData, categoryLabels) {
26525
26535
  const angle = radarAngle(vi, catCount), out = RADAR_LABEL_RADIAL_EM * fontSize,
26526
26536
  // Kept inside the chart box, as PowerPoint keeps every label.
26527
26537
  auto = {
26528
- x: clamp$6(p.x + out * Math.cos(angle), fontSize, frame.width - fontSize),
26529
- y: clamp$6(p.y + out * Math.sin(angle), fontSize, frame.height - fontSize),
26538
+ x: clamp$5(p.x + out * Math.cos(angle), fontSize, frame.width - fontSize),
26539
+ y: clamp$5(p.y + out * Math.sin(angle), fontSize, frame.height - fontSize),
26530
26540
  };
26531
26541
  anchor = {
26532
26542
  ...applyLabelManualLayout(findPointLabel(series, vi)?.layout, frame, auto),
@@ -86121,6 +86131,29 @@ function smartArt3DRigSpecularLights(rig, perspective) {
86121
86131
  const own = perspective && rig ? SMARTART_RIG_SPECULAR_LIGHTS[rig] : undefined;
86122
86132
  return own ?? DEFAULT_HIGHLIGHT;
86123
86133
  }
86134
+ /**
86135
+ * Key-light elevations under a scene camera, fitted per rig against the scene
86136
+ * quick styles' exports (whole-slide MAE over all eight layouts of each
86137
+ * style in `e2e/fixtures/three-d-parity/gt/`, sweeping the elevation):
86138
+ * `flat` (Brick Scene, isometric) 4.18 -> 3.94 at 40, `threePt` (Metallic
86139
+ * Scene) 3.89 -> 3.60 at 90, `morning` (Sunset Scene) 3.63 -> 3.53 at 60,
86140
+ * `soft` (Bird's Eye Scene) 3.69 -> 3.12 at 80. Under a parallel view the
86141
+ * grazing default stays: raising it made Polished (`flat`) and Cartoon
86142
+ * (`contrasting`) worse, because the bevel constants were fitted with it.
86143
+ */
86144
+ const SMARTART_RIG_SCENE_KEY_LIGHTS = {
86145
+ flat: { azimuthDeg: 0, elevationDeg: 40 },
86146
+ threePt: { azimuthDeg: 0, elevationDeg: 90 },
86147
+ morning: { azimuthDeg: 0, elevationDeg: 60 },
86148
+ soft: { azimuthDeg: 0, elevationDeg: 80 },
86149
+ };
86150
+ /**
86151
+ * A rig's own key light under a scene camera, or `undefined` for the default
86152
+ * (a grazing light from `@dir`, fitted against the bevel quick styles).
86153
+ */
86154
+ function smartArt3DRigKeyLight(rig, sceneCamera) {
86155
+ return sceneCamera && rig ? SMARTART_RIG_SCENE_KEY_LIGHTS[rig] : undefined;
86156
+ }
86124
86157
 
86125
86158
  /**
86126
86159
  * Per-vertex shading for the lit SmartArt 3D solids (framework-agnostic,
@@ -86192,8 +86225,10 @@ function normalize(v) {
86192
86225
  * @param perspective - the solid is seen through a perspective scene camera,
86193
86226
  * which switches on the rig's own specular lights (see
86194
86227
  * `smartart-3d-light-rig.ts`).
86228
+ * @param sceneCamera - the diagram is turned by a quick-style scene camera
86229
+ * (perspective or not), which switches on the rig's own key light.
86195
86230
  */
86196
- function resolveSmartArt3DLightModel(lighting, material, perspective = false) {
86231
+ function resolveSmartArt3DLightModel(lighting, material, perspective = false, sceneCamera = perspective) {
86197
86232
  // CSS space (y-down) -> view space (y-up), then the rig's own revolution.
86198
86233
  const snap = getBevelHighlightDirection(lighting?.direction ?? 't');
86199
86234
  const rev = ((lighting?.revDeg ?? 0) * Math.PI) / 180;
@@ -86215,8 +86250,9 @@ function resolveSmartArt3DLightModel(lighting, material, perspective = false) {
86215
86250
  };
86216
86251
  const response = getMaterialLighting(material);
86217
86252
  const specular = response.specularConstant * SPECULAR_GAIN;
86253
+ const key = smartArt3DRigKeyLight(lighting?.rig, sceneCamera);
86218
86254
  return {
86219
- light: toward(KEY_ELEVATION_DEG),
86255
+ light: key ? toward(key.elevationDeg, key.azimuthDeg) : toward(KEY_ELEVATION_DEG),
86220
86256
  highlights: smartArt3DRigSpecularLights(lighting?.rig, perspective).map((rigLight) => ({
86221
86257
  direction: toward(rigLight.elevationDeg, rigLight.azimuthDeg),
86222
86258
  weight: specular * rigLight.intensity,
@@ -87429,620 +87465,6 @@ function buildSmartArt3DSpecForElement(element, options = {}) {
87429
87465
  return model.meshes.length > 0 ? model : null;
87430
87466
  }
87431
87467
 
87432
- /**
87433
- * Pure geometry helpers for an interactive 3D CARTESIAN chart scene (category
87434
- * axis x depth/series axis x value axis), three-agnostic so the row/col/camera
87435
- * maths is unit-testable without mocking WebGL.
87436
- *
87437
- * Generalises the surface chart's grid/camera conventions
87438
- * ({@link ./surface-chart-3d-geom.ts}) for a plot whose Z (depth) axis is the
87439
- * SERIES axis rather than a second value dimension, and whose depth extent is
87440
- * driven by the authored `c:view3D/@depthPercent` (percentage of the plot's
87441
- * own width) rather than 1:1 with the row count. `bar3D`
87442
- * ({@link ./bar-chart-3d-scene.ts}) is the first consumer; `line3D`/`area3D`
87443
- * are expected to reuse this module unchanged, only swapping the mesh built
87444
- * from the resulting grid/camera placement.
87445
- *
87446
- * @module cartesian-chart-3d-geom
87447
- */
87448
- /** Half-unit spacing between adjacent category slots, in world units. Matches
87449
- * the surface chart's `GRID_SPACING` so the two scene kinds share one visual
87450
- * scale. */
87451
- const GRID_SPACING$1 = 0.5;
87452
- /** World-space height of the tallest data point (value axis extent). */
87453
- const MAX_VALUE_HEIGHT = 1.5;
87454
- /**
87455
- * Remap a point built in the "vertical" cartesian local frame (category =
87456
- * world X, value = world Y, series = world Z) onto the frame a horizontal
87457
- * `c:barDir val="bar"` `bar3D` chart needs (value = world X, category = world
87458
- * Y, series unchanged on Z): a -90 degree rotation about Z, i.e. `(x, y, z) ->
87459
- * (y, -x, z)`. The negation keeps category index 0 at the TOP of the scene
87460
- * (positive world Y), matching the flat SVG horizontal-bar engine's own
87461
- * top-to-bottom category order ({@link ./chart-horizontal-bars.ts}) so the
87462
- * true-3D scene and its 2D fallback never disagree about layout.
87463
- *
87464
- * Every "vertical-frame" point this cartesian geometry module produces (box
87465
- * centers, label anchors) can go through this same function, so a mesh
87466
- * rotated `-Math.PI / 2` about Z (see `bar-chart-3d-geometry.ts`) lines up
87467
- * with everything else built from the SAME local coordinates without a
87468
- * second, divergent set of horizontal-only layout maths.
87469
- */
87470
- function transposeForHorizontalBar3D(point) {
87471
- return [point[1], -point[0], point[2]];
87472
- }
87473
- /**
87474
- * Compute the world-space grid extent: `gridWidth` scales with the category
87475
- * count exactly like the surface chart's column axis; `gridDepth` is the
87476
- * chart's authored depth as a percentage of that width
87477
- * (`c:view3D/@depthPercent`, ECMA-376 default 100), NOT a per-series 1:1
87478
- * spacing, matching the OOXML semantics ("chart depth as a percentage of base
87479
- * width"). `rows` still gates the minimum depth so a single-series chart
87480
- * keeps a visible sliver of depth instead of collapsing to a flat plane.
87481
- */
87482
- function computeCartesianGridExtent(cols, rows, depthPercent) {
87483
- const gridWidth = Math.max(cols - 1, 1) * GRID_SPACING$1;
87484
- const depthScale = Math.min(Math.max((depthPercent ?? 100) / 100, 0.05), 3);
87485
- const gridDepth = Math.max(gridWidth * depthScale, rows > 1 ? GRID_SPACING$1 : GRID_SPACING$1 * 0.5);
87486
- return { gridWidth, gridDepth };
87487
- }
87488
- const DEFAULT_ELEVATION_DEG$1 = 15;
87489
- const DEFAULT_AZIMUTH_DEG$1 = 20;
87490
- const DEFAULT_PERSPECTIVE_DEG$1 = 30;
87491
- const FOV_MIN$1 = 15;
87492
- const FOV_MAX$1 = 75;
87493
- /** Near-orthographic FOV used when `rAngAx` (right-angle axes) is authored. */
87494
- const RIGHT_ANGLE_FOV = 20;
87495
- /** `c:view3D/c:perspective` is documented 0-240 degrees; PowerPoint's own UI
87496
- * caps the picker at 120, so anything past that is treated as "very deep". */
87497
- const PERSPECTIVE_MAX_DEG$1 = 120;
87498
- function clamp$5(value, min, max) {
87499
- return Math.min(Math.max(value, min), max);
87500
- }
87501
- function normalizeAzimuth$1(deg) {
87502
- const wrapped = deg % 360;
87503
- return wrapped < 0 ? wrapped + 360 : wrapped;
87504
- }
87505
- /**
87506
- * Resolve the camera's vertical field of view from `c:view3D`. `rAngAx`
87507
- * (right-angle axes) asks PowerPoint to drop true perspective, so this maps
87508
- * it to a narrow, near-orthographic FOV; otherwise `perspective` (0-120
87509
- * degrees in practice) scales linearly onto a `[FOV_MIN, FOV_MAX]` degree
87510
- * range. Not measured against PowerPoint's own COM-rendered output: this is a
87511
- * reasonable, documented approximation, not a claimed exact match.
87512
- */
87513
- function resolveCartesianCameraFov(view3D) {
87514
- if (view3D?.rAngAx) {
87515
- return RIGHT_ANGLE_FOV;
87516
- }
87517
- const perspective = clamp$5(view3D?.rperspective ?? DEFAULT_PERSPECTIVE_DEG$1, 0, PERSPECTIVE_MAX_DEG$1);
87518
- return FOV_MIN$1 + (perspective / PERSPECTIVE_MAX_DEG$1) * (FOV_MAX$1 - FOV_MIN$1);
87519
- }
87520
- /**
87521
- * Camera placement that frames a scene of world-space bounding radius
87522
- * `maxExtent`, looking at `[0, targetY, 0]`.
87523
- *
87524
- * The camera sits on a sphere around that target at the elevation/azimuth
87525
- * given by `rotX`/`rotY` (PowerPoint-like defaults when either is absent,
87526
- * matching the flat oblique-projection engine's own defaults so an untagged
87527
- * 3D chart looks consistent across presentations), at a distance derived
87528
- * from the resolved FOV so the scene's bounding sphere stays framed
87529
- * regardless of how narrow (near-orthographic) or wide the perspective is.
87530
- *
87531
- * `rotX` is clamped to `(-90, 90)` exclusive: exactly on the vertical axis is
87532
- * a degenerate `lookAt`/OrbitControls case (an ill-defined up vector).
87533
- *
87534
- * Pure sphere-placement math, extracted out of
87535
- * {@link computeCartesianCameraPlacement} so a non-cartesian 3D chart scene
87536
- * (no category/series grid to speak of) can reuse the identical
87537
- * camera-placement approach instead of duplicating it. `pie3D`
87538
- * ({@link ../pie-chart-3d-geom.ts}, `computePieChart3DCameraPlacement`) is
87539
- * the first such consumer.
87540
- */
87541
- function computeSphericalCameraPlacement(maxExtent, targetY, view3D) {
87542
- const fov = resolveCartesianCameraFov(view3D);
87543
- const halfFovRad = (fov * Math.PI) / 360;
87544
- // Distance for the scene's bounding radius to just fit the vertical FOV, plus margin.
87545
- const radius = (Math.max(maxExtent, 0.01) / Math.tan(halfFovRad)) * 0.75;
87546
- const target = [0, targetY, 0];
87547
- const elevationDeg = clamp$5(view3D?.rotX ?? DEFAULT_ELEVATION_DEG$1, -89, 89);
87548
- const azimuthDeg = normalizeAzimuth$1(view3D?.rotY ?? DEFAULT_AZIMUTH_DEG$1);
87549
- // Polar angle measured from the +Y axis; spherical -> cartesian.
87550
- const phi = ((90 - elevationDeg) * Math.PI) / 180;
87551
- const theta = (azimuthDeg * Math.PI) / 180;
87552
- const sinPhi = Math.sin(phi);
87553
- return {
87554
- position: [
87555
- radius * sinPhi * Math.sin(theta),
87556
- radius * Math.cos(phi) + target[1],
87557
- radius * sinPhi * Math.cos(theta),
87558
- ],
87559
- target,
87560
- fov,
87561
- };
87562
- }
87563
- /**
87564
- * Camera placement that frames the whole cartesian grid. Thin wrapper around
87565
- * {@link computeSphericalCameraPlacement} with the cartesian grid's own
87566
- * extent + look-at height.
87567
- *
87568
- * `horizontal` (a `c:barDir val="bar"` `bar3D` chart) mounts its boxes
87569
- * remapped into the transposed frame ({@link transposeForHorizontalBar3D}:
87570
- * value = world X, category = world Y instead of the vertical frame's
87571
- * category = X, value = Y). Framing those boxes with the UNTRANSPOSED camera
87572
- * placement pointed the look-at target at `[0, someHeight, 0]`, a point that
87573
- * is only near the rendered content in the vertical frame: once transposed,
87574
- * the value axis starts at world X = 0 and runs only in the POSITIVE
87575
- * direction (never centred on 0), so the untransposed target sat off to one
87576
- * side of the actual box cluster, cropping it hard against one edge of the
87577
- * frame and pushing the axis-label overlay (projected through the same
87578
- * camera) out of the view frustum entirely. Applying the identical rigid
87579
- * transform to both the computed position and target re-centres the camera
87580
- * on the actual (rotated) content without re-deriving the elevation/azimuth
87581
- * maths: rotating a camera and its look-at target by the same transform that
87582
- * rotated the scene preserves the viewing distance and angle. World "up"
87583
- * intentionally stays untouched (`camera.up` default `+Y`): the floor and
87584
- * `c:floor`/`c:sideWall`/`c:backWall` panels are never reoriented for
87585
- * `horizontal` either (see `bar-chart-3d-scene.ts`), so only the camera's
87586
- * position/target move, not its up vector.
87587
- */
87588
- function computeCartesianCameraPlacement(cols, rows, view3D, horizontal = false) {
87589
- const { gridWidth, gridDepth } = computeCartesianGridExtent(cols, rows, view3D?.depthPercent);
87590
- const maxExtent = Math.max(gridWidth, gridDepth, MAX_VALUE_HEIGHT);
87591
- const placement = computeSphericalCameraPlacement(maxExtent, MAX_VALUE_HEIGHT * 0.2, view3D);
87592
- if (!horizontal) {
87593
- return placement;
87594
- }
87595
- return {
87596
- ...placement,
87597
- position: transposeForHorizontalBar3D(placement.position),
87598
- target: transposeForHorizontalBar3D(placement.target),
87599
- };
87600
- }
87601
- /**
87602
- * Build the axis labels (category along the front edge, series along the
87603
- * right edge) as world-anchored {@link SurfaceLabel}s, reusing the surface
87604
- * chart's label type + DOM overlay ({@link ./surface-chart-3d-label-overlay.ts})
87605
- * unchanged. Thinned to at most `maxCat`/`maxSer` entries to avoid clutter,
87606
- * mirroring `buildSurfaceLabels`.
87607
- */
87608
- function buildCartesianChart3DLabels(cols, rows, categoryLabels, seriesNames, depthPercent, maxCat = 8, maxSer = 6, horizontal = false) {
87609
- const { gridWidth, gridDepth } = computeCartesianGridExtent(cols, rows, depthPercent);
87610
- const labels = [];
87611
- const remap = (anchor) => horizontal ? transposeForHorizontalBar3D(anchor) : anchor;
87612
- const catStep = Math.max(1, Math.ceil(categoryLabels.length / maxCat));
87613
- for (let i = 0; i < categoryLabels.length; i += catStep) {
87614
- const x = -gridWidth / 2 + (i / Math.max(cols - 1, 1)) * gridWidth;
87615
- labels.push({
87616
- key: `cat-${i}`,
87617
- text: categoryLabels[i],
87618
- anchor: remap([x, -0.15, gridDepth / 2 + 0.25]),
87619
- axis: 'category',
87620
- });
87621
- }
87622
- const serStep = Math.max(1, Math.ceil(seriesNames.length / maxSer));
87623
- for (let i = 0; i < seriesNames.length; i += serStep) {
87624
- const z = -gridDepth / 2 + (i / Math.max(rows - 1, 1)) * gridDepth;
87625
- labels.push({
87626
- key: `ser-${i}`,
87627
- text: seriesNames[i],
87628
- anchor: remap([gridWidth / 2 + 0.3, -0.15, z]),
87629
- axis: 'series',
87630
- });
87631
- }
87632
- return labels;
87633
- }
87634
-
87635
- /**
87636
- * Pure path-layout maths for the interactive `line3D` and `area3D` scenes
87637
- * ({@link ./line-chart-3d-scene.ts}, {@link ./area-chart-3d-scene.ts}).
87638
- *
87639
- * PowerPoint's real 3-D Line and 3-D Area chart types give every series its
87640
- * own depth ("Z") plane along the series axis, exactly like a clustered
87641
- * bar3D column (see {@link ./bar-chart-3d-layout.ts}) - unlike this engine's
87642
- * flat oblique-projection line3D/area3D fallback ({@link ./chart-3d-depth.ts}),
87643
- * which only offsets the whole chart by a single shared depth vector, with no
87644
- * per-series stagger. Also unlike bar3D, PowerPoint's chart-type gallery
87645
- * offers no "stacked 3-D Line"/"stacked 3-D Area", so every series always
87646
- * plots its OWN authored values on its OWN plane, never a running sum.
87647
- *
87648
- * X (category axis) uses the exact same anchor formula as
87649
- * `buildCartesianChart3DLabels`'s category labels, so a path vertex sits
87650
- * directly under its axis label. Z (series/depth axis) uses the exact same
87651
- * formula as `bar-chart-3d-layout.ts`'s clustered box Z, so a line3D/area3D
87652
- * chart's depth planes line up with a bar3D chart's for the same
87653
- * category/series grid.
87654
- *
87655
- * @module cartesian-line-chart-3d-layout
87656
- */
87657
- function normalizeToHeight$1(value, range) {
87658
- if (range.span <= 0) {
87659
- return 0;
87660
- }
87661
- return ((value - range.min) / range.span) * MAX_VALUE_HEIGHT;
87662
- }
87663
- /**
87664
- * Lay out one 3D path per series (grouped from the flat `points` list by
87665
- * `seriesIndex`, sorted by `categoryIndex` within each series). Series with
87666
- * no points are simply absent from the result (there is nothing to path).
87667
- */
87668
- function layoutCartesianLine3DSeries(points, cols, rows, range, depthPercent) {
87669
- const { gridWidth, gridDepth } = computeCartesianGridExtent(cols, rows, depthPercent);
87670
- const rowStep = gridDepth / Math.max(rows, 1);
87671
- const baselineY = normalizeToHeight$1(0, range);
87672
- const bySeries = new Map();
87673
- for (const p of points) {
87674
- const list = bySeries.get(p.seriesIndex);
87675
- if (list) {
87676
- list.push(p);
87677
- }
87678
- else {
87679
- bySeries.set(p.seriesIndex, [p]);
87680
- }
87681
- }
87682
- const paths = [];
87683
- for (const [seriesIndex, seriesPoints] of bySeries) {
87684
- const sorted = [...seriesPoints].sort((a, b) => a.categoryIndex - b.categoryIndex);
87685
- const z = -gridDepth / 2 + rowStep * (seriesIndex + 0.5);
87686
- const vertices = sorted.map((p) => {
87687
- const x = -gridWidth / 2 + (p.categoryIndex / Math.max(cols - 1, 1)) * gridWidth;
87688
- const y = normalizeToHeight$1(p.value, range);
87689
- return {
87690
- seriesIndex,
87691
- categoryIndex: p.categoryIndex,
87692
- value: p.value,
87693
- position: [x, y, z],
87694
- };
87695
- });
87696
- paths.push({
87697
- seriesIndex,
87698
- color: sorted[0]?.color ?? '#4472C4',
87699
- vertices,
87700
- depthZ: z,
87701
- baselineY,
87702
- });
87703
- }
87704
- return paths.sort((a, b) => a.seriesIndex - b.seriesIndex);
87705
- }
87706
- /**
87707
- * Flatten a series path's vertices + baseline into an area3D fill ribbon: for
87708
- * every pair of adjacent category vertices, two triangles connect the top
87709
- * (value) edge to the bottom (baseline) edge. Returns a flat
87710
- * `[x, y, z, x, y, z, ...]` triangle-list ready for a `BufferGeometry`
87711
- * position attribute (9 numbers per triangle, 2 triangles per segment).
87712
- *
87713
- * A single-vertex path (one category) has no segment to fill and returns an
87714
- * empty array; line3D never calls this (it only needs the raw vertex path for
87715
- * its tube curve).
87716
- */
87717
- function buildAreaRibbonTriangles(path) {
87718
- const { vertices, baselineY } = path;
87719
- const out = [];
87720
- for (let i = 0; i < vertices.length - 1; i++) {
87721
- const [x1, y1, z1] = vertices[i].position;
87722
- const [x2, y2, z2] = vertices[i + 1].position;
87723
- // Top edge (the authored values) and bottom edge (the baseline), wound so
87724
- // both triangles face the same way (+Y-ish normal) for consistent shading.
87725
- out.push(x1, y1, z1, x1, baselineY, z1, x2, baselineY, z2);
87726
- out.push(x1, y1, z1, x2, baselineY, z2, x2, y2, z2);
87727
- }
87728
- return out;
87729
- }
87730
-
87731
- /** Build the resolved (value + colour) points, row-major (series x category). */
87732
- function buildPoints$1(chartData, catCount) {
87733
- const series = chartData.series;
87734
- const palette = chartData.colorPalette;
87735
- const points = [];
87736
- for (let si = 0; si < series.length; si++) {
87737
- const baseColor = seriesColor(series[si], si, palette);
87738
- for (let ci = 0; ci < catCount; ci++) {
87739
- const value = series[si].values[ci] ?? 0;
87740
- const color = resolveDataPointFill(series[si], ci, paletteColor(si, palette)) ?? baseColor;
87741
- points.push({ seriesIndex: si, categoryIndex: ci, value, color });
87742
- }
87743
- }
87744
- return points;
87745
- }
87746
- /**
87747
- * Build the {@link CartesianLine3DSceneOptions} `createLineChart3DScene`/
87748
- * `createAreaChart3DScene` need, or `null` when the chart has no plottable grid (no
87749
- * series, or every series has zero categories). Chart-type-agnostic: the
87750
- * caller (`buildLineChart3DDataForElement` / `buildAreaChart3DDataForElement`)
87751
- * is the one that gates on the authored `c:chartType`.
87752
- */
87753
- function buildCartesianLine3DSceneData(chartData, categoryLabels, options) {
87754
- const rows = chartData.series.length;
87755
- const cols = categoryLabels.length;
87756
- if (rows === 0 || cols === 0) {
87757
- return null;
87758
- }
87759
- const points = buildPoints$1(chartData, cols);
87760
- // See the matching comment in `bar-chart-3d-data.ts`: the element height
87761
- // stands in for a plot-area height this 3D scene doesn't separately compute.
87762
- const range = computeValueRange(chartData.series, options.height);
87763
- const depthPercent = chartData.view3D?.depthPercent;
87764
- const series = layoutCartesianLine3DSeries(points, cols, rows, range, depthPercent);
87765
- return {
87766
- cols,
87767
- rows,
87768
- series,
87769
- categoryLabels,
87770
- seriesNames: chartData.series.map((s) => s.name),
87771
- numberFormats: chartData.series.map((s) => s.numberFormat),
87772
- width: options.width,
87773
- height: options.height,
87774
- view3D: chartData.view3D
87775
- ? {
87776
- rotX: chartData.view3D.rotX,
87777
- rotY: chartData.view3D.rotY,
87778
- rperspective: chartData.view3D.perspective,
87779
- depthPercent: chartData.view3D.depthPercent,
87780
- rAngAx: chartData.view3D.rAngAx,
87781
- }
87782
- : undefined,
87783
- wallColors: resolveChart3DWallColors(chartData),
87784
- };
87785
- }
87786
-
87787
- /**
87788
- * Single decision point every binding calls to decide whether a chart element
87789
- * should mount the interactive 3D area scene. Mirrors
87790
- * {@link ./line-chart-3d-data.ts}'s `buildLineChart3DDataForElement} exactly,
87791
- * gated on `c:chartType === 'area3D'` instead.
87792
- *
87793
- * Returns `null` when the element is not a chart, its `c:chartType` is not
87794
- * literally `area3D`, or the chart has no plottable grid. A non-null result
87795
- * means "render the WebGL scene"; `null` means "fall back to the flat SVG
87796
- * oblique-projection area3D renderer".
87797
- */
87798
- function buildAreaChart3DDataForElement(element, options) {
87799
- if (element.type !== 'chart') {
87800
- return null;
87801
- }
87802
- const chartEl = element;
87803
- const chartData = chartEl.chartData;
87804
- if (!chartData || chartData.series.length === 0) {
87805
- return null;
87806
- }
87807
- if (chartData.chartType !== 'area3D') {
87808
- return null;
87809
- }
87810
- const longestLen = chartData.series.reduce((m, s) => Math.max(m, s.values.length), 0);
87811
- const categoryLabels = chartData.categories.length > 0
87812
- ? chartData.categories
87813
- : Array.from({ length: longestLen }, (_, i) => String(i + 1));
87814
- return buildCartesianLine3DSceneData(chartData, categoryLabels, options);
87815
- }
87816
-
87817
- /** Fraction (0-1) of the base radius `coneToMax`/`pyramidToMax` tapers to at the top. */
87818
- function coneToMaxTopRadiusFactor(shape, value, max) {
87819
- if (shape !== 'coneToMax' && shape !== 'pyramidToMax') {
87820
- return undefined;
87821
- }
87822
- if (!(max > 0)) {
87823
- return 0;
87824
- }
87825
- return Math.max(0, Math.min(1, 1 - Math.abs(value) / max));
87826
- }
87827
- const MIN_BOX_HEIGHT = 0.02;
87828
- const BOX_GAP = 0.15;
87829
- function normalizeToHeight(value, range) {
87830
- if (range.span <= 0) {
87831
- return 0;
87832
- }
87833
- return ((value - range.min) / range.span) * MAX_VALUE_HEIGHT;
87834
- }
87835
- /** Clustered box layout: each series gets its own depth ("Z") plane. */
87836
- function layoutClustered(points, cols, rows, range, depthPercent) {
87837
- const { gridWidth, gridDepth } = computeCartesianGridExtent(cols, rows, depthPercent);
87838
- const colStep = gridWidth / cols;
87839
- const rowStep = gridDepth / Math.max(rows, 1);
87840
- const boxW = colStep * (1 - BOX_GAP);
87841
- const boxD = rowStep * (1 - BOX_GAP);
87842
- const zeroH = normalizeToHeight(0, range);
87843
- return points.map((p) => {
87844
- const valueH = normalizeToHeight(p.plotValue, range);
87845
- const top = Math.max(valueH, zeroH);
87846
- const bottom = Math.min(valueH, zeroH);
87847
- const h = Math.max(top - bottom, MIN_BOX_HEIGHT);
87848
- const x = -gridWidth / 2 + colStep * (p.categoryIndex + 0.5);
87849
- const z = -gridDepth / 2 + rowStep * (p.seriesIndex + 0.5);
87850
- return {
87851
- seriesIndex: p.seriesIndex,
87852
- categoryIndex: p.categoryIndex,
87853
- value: p.value,
87854
- color: p.color,
87855
- center: [x, bottom + h / 2, z],
87856
- size: [boxW, h, boxD],
87857
- shape: p.shape,
87858
- coneToMaxTopRadiusFactor: coneToMaxTopRadiusFactor(p.shape, p.value, range.max),
87859
- };
87860
- });
87861
- }
87862
- /** Stacked/percentStacked box layout: one coplanar depth plane, vertical stack. */
87863
- function layoutStacked(points, cols, rows, range, depthPercent) {
87864
- const { gridWidth, gridDepth } = computeCartesianGridExtent(cols, rows, depthPercent);
87865
- const colStep = gridWidth / cols;
87866
- const boxW = colStep * (1 - BOX_GAP);
87867
- const boxD = gridDepth * (1 - BOX_GAP);
87868
- const boxes = [];
87869
- for (let ci = 0; ci < cols; ci++) {
87870
- const x = -gridWidth / 2 + colStep * (ci + 0.5);
87871
- let posRunning = 0;
87872
- let negRunning = 0;
87873
- const catPoints = points
87874
- .filter((p) => p.categoryIndex === ci)
87875
- .sort((a, b) => a.seriesIndex - b.seriesIndex);
87876
- for (const p of catPoints) {
87877
- const isNeg = p.plotValue < 0;
87878
- const base = isNeg ? negRunning : posRunning;
87879
- const top = base + p.plotValue;
87880
- if (isNeg) {
87881
- negRunning = top;
87882
- }
87883
- else {
87884
- posRunning = top;
87885
- }
87886
- const baseH = normalizeToHeight(base, range);
87887
- const topH = normalizeToHeight(top, range);
87888
- const h = Math.max(Math.abs(topH - baseH), MIN_BOX_HEIGHT);
87889
- boxes.push({
87890
- seriesIndex: p.seriesIndex,
87891
- categoryIndex: p.categoryIndex,
87892
- value: p.value,
87893
- color: p.color,
87894
- center: [x, Math.min(baseH, topH) + h / 2, 0],
87895
- size: [boxW, h, boxD],
87896
- shape: p.shape,
87897
- coneToMaxTopRadiusFactor: coneToMaxTopRadiusFactor(p.shape, p.value, range.max),
87898
- });
87899
- }
87900
- }
87901
- return boxes;
87902
- }
87903
- /**
87904
- * Build the box-mesh layout for a resolved grouping: clustered gives every
87905
- * series its own depth plane; stacked/percentStacked keeps them coplanar and
87906
- * stacks vertically (percentStacked's `plotValue`s are pre-normalised to
87907
- * percent by the caller, so this function treats it identically to `stacked`).
87908
- *
87909
- * `horizontal` (`c:barDir val="bar"`) remaps every box's CENTER from the
87910
- * vertical local frame (category = X, value = Y, series = Z) into the
87911
- * horizontal one (value = X, category = Y, series unchanged on Z) via
87912
- * {@link transposeForHorizontalBar3D}. `size` is deliberately left in the
87913
- * unrotated local frame: the scene mounts every box mesh with a matching
87914
- * `-Math.PI / 2` Z rotation instead of swapping width/height numerically, so
87915
- * a cylinder/cone/pyramid box keeps its true (non-elliptical) cross-section
87916
- * when it ends up lying on its side (see `bar-chart-3d-geometry.ts`).
87917
- */
87918
- function layoutBarChart3D(points, cols, rows, range, grouping, depthPercent, horizontal = false) {
87919
- const boxes = grouping === 'clustered'
87920
- ? layoutClustered(points, cols, rows, range, depthPercent)
87921
- : layoutStacked(points, cols, rows, range, depthPercent);
87922
- if (!horizontal) {
87923
- return boxes;
87924
- }
87925
- return boxes.map((box) => ({ ...box, center: transposeForHorizontalBar3D(box.center) }));
87926
- }
87927
-
87928
- /** Per-category sum of absolute values, for percentStacked normalisation. */
87929
- function categoryTotals(series, catCount) {
87930
- return Array.from({ length: catCount }, (_, ci) => series.reduce((sum, s) => sum + Math.abs(s.values[ci] ?? 0), 0));
87931
- }
87932
- /** Build the resolved (value + colour) grid points, row-major (series x category). */
87933
- function buildPoints(chartData, catCount, isPercent) {
87934
- const series = chartData.series;
87935
- const palette = chartData.colorPalette;
87936
- const totals = isPercent ? categoryTotals(series, catCount) : undefined;
87937
- const points = [];
87938
- for (let si = 0; si < series.length; si++) {
87939
- const baseColor = seriesColor(series[si], si, palette);
87940
- for (let ci = 0; ci < catCount; ci++) {
87941
- const value = series[si].values[ci] ?? 0;
87942
- const color = resolveDataPointFill(series[si], ci, paletteColor(si, palette)) ?? baseColor;
87943
- const catTotal = totals?.[ci];
87944
- const plotValue = isPercent && catTotal !== undefined && catTotal > 0 ? (value / catTotal) * 100 : value;
87945
- points.push({
87946
- seriesIndex: si,
87947
- categoryIndex: ci,
87948
- value,
87949
- plotValue,
87950
- color,
87951
- // Per-series `c:ser/c:shape` wins over the chart-level `c:bar3DChart/c:shape`.
87952
- shape: series[si].shape ?? chartData.barShape,
87953
- });
87954
- }
87955
- }
87956
- return points;
87957
- }
87958
- /**
87959
- * Build the {@link BarChart3DSceneOptions} `createBarChart3DScene` needs, or `null`
87960
- * when the chart has no plottable grid (no series, or every series has zero
87961
- * categories).
87962
- */
87963
- function buildBarChart3DData(chartData, categoryLabels, options) {
87964
- const rows = chartData.series.length;
87965
- const cols = categoryLabels.length;
87966
- if (rows === 0 || cols === 0) {
87967
- return null;
87968
- }
87969
- const grouping = chartData.grouping === 'stacked' || chartData.grouping === 'percentStacked'
87970
- ? chartData.grouping
87971
- : 'clustered';
87972
- const isPercent = grouping === 'percentStacked';
87973
- const horizontal = chartData.barDirection === 'bar';
87974
- const points = buildPoints(chartData, cols, isPercent);
87975
- // No separate plot-area inset exists for the 3D scene the way the flat SVG
87976
- // engine's `computePlotLayout` reserves one, so the full element height
87977
- // stands in for it; see `axisTargetIntervals`'s doc comment for why this
87978
- // only makes the automatic scale saturate at its gridline cap slightly
87979
- // sooner; it does not change the "ordinary chart" result this was fixed for.
87980
- const range = isPercent
87981
- ? { min: 0, max: 100, span: 100 }
87982
- : grouping === 'stacked'
87983
- ? computeStackedValueRange(chartData.series, cols, options.height)
87984
- : computeValueRange(chartData.series, options.height);
87985
- const depthPercent = chartData.view3D?.depthPercent;
87986
- const boxes = layoutBarChart3D(points, cols, rows, range, grouping, depthPercent, horizontal);
87987
- return {
87988
- cols,
87989
- rows,
87990
- boxes,
87991
- categoryLabels,
87992
- seriesNames: chartData.series.map((s) => s.name),
87993
- numberFormats: chartData.series.map((s) => s.numberFormat),
87994
- grouping,
87995
- width: options.width,
87996
- height: options.height,
87997
- view3D: chartData.view3D
87998
- ? {
87999
- rotX: chartData.view3D.rotX,
88000
- rotY: chartData.view3D.rotY,
88001
- rperspective: chartData.view3D.perspective,
88002
- depthPercent: chartData.view3D.depthPercent,
88003
- rAngAx: chartData.view3D.rAngAx,
88004
- }
88005
- : undefined,
88006
- wallColors: resolveChart3DWallColors(chartData),
88007
- picture: { series: chartData.series },
88008
- ...(horizontal ? { horizontal: true } : {}),
88009
- };
88010
- }
88011
- /**
88012
- * Single decision point every binding calls to decide whether a chart element
88013
- * should mount the interactive 3D bar scene: resolves the category-label
88014
- * fallback (mirrors `buildChartViewModel`'s derivation exactly, so 2D and 3D
88015
- * never disagree about what a category is called) and the box layout, in one
88016
- * place.
88017
- *
88018
- * Returns `null` when the element is not a chart, its `c:chartType` is not
88019
- * literally `bar3D` (a plain `bar` chart never mounts the 3D scene, even
88020
- * though `resolveChartKind` folds both onto the same 'bar' kind), or the
88021
- * chart has no plottable grid. A non-null result means "render the WebGL
88022
- * scene"; `null` means "fall back to the flat SVG oblique-projection bar3D
88023
- * renderer". A horizontal 3-D Bar (`c:barDir="bar"`) mounts the SAME scene
88024
- * with its boxes remapped into the horizontal frame (`BarChart3DSceneOptions
88025
- * .horizontal`); see `layoutBarChart3D`.
88026
- */
88027
- function buildBarChart3DDataForElement(element, options) {
88028
- if (element.type !== 'chart') {
88029
- return null;
88030
- }
88031
- const chartEl = element;
88032
- const chartData = chartEl.chartData;
88033
- if (!chartData || chartData.series.length === 0) {
88034
- return null;
88035
- }
88036
- if (chartData.chartType !== 'bar3D') {
88037
- return null;
88038
- }
88039
- const longestLen = chartData.series.reduce((m, s) => Math.max(m, s.values.length), 0);
88040
- const categoryLabels = chartData.categories.length > 0
88041
- ? chartData.categories
88042
- : Array.from({ length: longestLen }, (_, i) => String(i + 1));
88043
- return buildBarChart3DData(chartData, categoryLabels, options);
88044
- }
88045
-
88046
87468
  /** Value-axis label text (`percentStacked` axes read as whole percents). */
88047
87469
  function formatValueLabel(chartData, grouping, v) {
88048
87470
  if (grouping === 'percentStacked') {
@@ -89041,273 +88463,6 @@ function resolveChart3DProjection(chartType, view3D) {
89041
88463
  };
89042
88464
  }
89043
88465
 
89044
- /**
89045
- * Single decision point every binding calls to decide whether a chart element
89046
- * should mount the interactive 3D line scene: resolves the category-label
89047
- * fallback (mirrors `buildChartViewModel`'s derivation exactly, so 2D and 3D
89048
- * never disagree about what a category is called) and the per-series path
89049
- * layout, in one place.
89050
- *
89051
- * Returns `null` when the element is not a chart, its `c:chartType` is not
89052
- * literally `line3D` (a plain `line` chart never mounts the 3D scene), or the
89053
- * chart has no plottable grid. A non-null result means "render the WebGL
89054
- * scene"; `null` means "fall back to the flat SVG oblique-projection line3D
89055
- * renderer".
89056
- */
89057
- function buildLineChart3DDataForElement(element, options) {
89058
- if (element.type !== 'chart') {
89059
- return null;
89060
- }
89061
- const chartEl = element;
89062
- const chartData = chartEl.chartData;
89063
- if (!chartData || chartData.series.length === 0) {
89064
- return null;
89065
- }
89066
- if (chartData.chartType !== 'line3D') {
89067
- return null;
89068
- }
89069
- const longestLen = chartData.series.reduce((m, s) => Math.max(m, s.values.length), 0);
89070
- const categoryLabels = chartData.categories.length > 0
89071
- ? chartData.categories
89072
- : Array.from({ length: longestLen }, (_, i) => String(i + 1));
89073
- return buildCartesianLine3DSceneData(chartData, categoryLabels, options);
89074
- }
89075
-
89076
- /** World-space outer radius of the pie disc. Matches the bar3D scene's world scale. */
89077
- const PIE_RADIUS = 1;
89078
- /** Wedge thickness (world units) at the ECMA-376 default `hPercent` of 100. */
89079
- const BASE_THICKNESS_RATIO = 0.3;
89080
- /** Never let an extreme `hPercent` collapse the wedges to an invisible sliver, or blow up past a chunky block. */
89081
- const MIN_THICKNESS_SCALE = 0.05;
89082
- const MAX_THICKNESS_SCALE = 3;
89083
- /** Margin so the camera frames the disc with room for exploded slices + labels. */
89084
- const CAMERA_EXTENT_MARGIN = 1.35;
89085
- /**
89086
- * Resolve the wedge thickness (world units, the Y-axis extent of every
89087
- * `CylinderGeometry` wedge) from the authored `c:view3D/@hPercent`. Absent
89088
- * uses the ECMA-376 default of 100 (percent), matching
89089
- * {@link ./cartesian-chart-3d-geom.ts}'s `depthPercent` clamp shape.
89090
- */
89091
- function computePieChart3DThickness(view3D) {
89092
- const scale = Math.min(Math.max((view3D?.hPercent ?? 100) / 100, MIN_THICKNESS_SCALE), MAX_THICKNESS_SCALE);
89093
- return PIE_RADIUS * BASE_THICKNESS_RATIO * scale;
89094
- }
89095
- /**
89096
- * Camera placement that frames the pie disc (plus its wedge thickness),
89097
- * reusing {@link computeSphericalCameraPlacement} - the same
89098
- * elevation/azimuth/FOV approach `bar3D` uses - with a pie-appropriate extent
89099
- * (radius, not a category/series grid) and a look-at height centred on the
89100
- * puck's thickness.
89101
- */
89102
- function computePieChart3DCameraPlacement(view3D) {
89103
- const thickness = computePieChart3DThickness(view3D);
89104
- const maxExtent = PIE_RADIUS * CAMERA_EXTENT_MARGIN;
89105
- return computeSphericalCameraPlacement(maxExtent, thickness / 2, view3D);
89106
- }
89107
- /**
89108
- * Build the per-slice angle + explosion geometry for a pie3D scene, mirroring
89109
- * `computePieSlices`' cumulative-angle bookkeeping (`chart-view-model.ts`) so
89110
- * the true-3D wedges and the flat SVG fallback always agree on where each
89111
- * slice starts, how wide it sweeps, and how far `c:explosion` pulls it out.
89112
- *
89113
- * `firstSliceAngleDeg` is `c:firstSliceAng` (degrees clockwise from 12
89114
- * o'clock); `explosions` is the per-point pull-out distance as a percentage
89115
- * of the outer radius (0-100), aligned index-for-index with `values`.
89116
- */
89117
- function computePieChart3DSliceAngles(values, explosions, firstSliceAngleDeg, outerRadius) {
89118
- const total = values.reduce((s, v) => s + Math.abs(v), 0) || 1;
89119
- let cumAngle = -Math.PI / 2 + ((firstSliceAngleDeg ?? 0) * Math.PI) / 180;
89120
- return values.map((value, pointIndex) => {
89121
- const sliceAngle = (Math.abs(value) / total) * Math.PI * 2;
89122
- const startAngle = cumAngle;
89123
- cumAngle += sliceAngle;
89124
- const explosion = explosions?.[pointIndex] ?? 0;
89125
- let explodeOffset = [0, 0];
89126
- if (explosion > 0) {
89127
- const mid = (startAngle + cumAngle) / 2;
89128
- const offset = outerRadius * (explosion / 100);
89129
- explodeOffset = [Math.cos(mid) * offset, Math.sin(mid) * offset];
89130
- }
89131
- return { pointIndex, value, startAngle, thetaLength: sliceAngle, explodeOffset };
89132
- });
89133
- }
89134
-
89135
- /**
89136
- * Build the {@link PieChart3DSceneOptions} `createPieChart3DScene` needs, or `null`
89137
- * when the chart has no plottable series (no series, or the (only) series has
89138
- * no values).
89139
- */
89140
- function buildPieChart3DData(chartData, categoryLabels, options) {
89141
- const pieSeries = chartData.series[0];
89142
- if (!pieSeries || pieSeries.values.length === 0) {
89143
- return null;
89144
- }
89145
- const values = pieSeries.values;
89146
- const explosions = values.map((_v, i) => resolveDataPointExplosion(pieSeries, i));
89147
- const outerRadius = PIE_RADIUS;
89148
- const angles = computePieChart3DSliceAngles(values, explosions, chartData.firstSliceAngle, outerRadius);
89149
- const wedges = angles.map((angle) => ({
89150
- ...angle,
89151
- color: resolveVaryColorFill(pieSeries, angle.pointIndex, paletteColor(angle.pointIndex, chartData.colorPalette)),
89152
- }));
89153
- const view3D = chartData.view3D
89154
- ? {
89155
- rotX: chartData.view3D.rotX,
89156
- rotY: chartData.view3D.rotY,
89157
- rperspective: chartData.view3D.perspective,
89158
- rAngAx: chartData.view3D.rAngAx,
89159
- hPercent: chartData.view3D.hPercent,
89160
- }
89161
- : undefined;
89162
- return {
89163
- wedges,
89164
- categoryLabels,
89165
- seriesName: pieSeries.name,
89166
- numberFormat: pieSeries.numberFormat,
89167
- outerRadius,
89168
- thickness: computePieChart3DThickness(view3D),
89169
- width: options.width,
89170
- height: options.height,
89171
- view3D,
89172
- values,
89173
- explosions,
89174
- firstSliceAngleDeg: chartData.firstSliceAngle,
89175
- };
89176
- }
89177
- /**
89178
- * Single decision point every binding calls to decide whether a chart element
89179
- * should mount the interactive 3D pie scene: resolves the category-label
89180
- * fallback (mirrors `buildChartViewModel`'s derivation exactly, so 2D and 3D
89181
- * never disagree about what a category is called) and the wedge layout, in
89182
- * one place.
89183
- *
89184
- * Returns `null` when the element is not a chart, its `c:chartType` is not
89185
- * literally `pie3D` (a plain `pie`/`doughnut` chart never mounts the 3D
89186
- * scene, even though `resolveChartKind` folds them onto the same 'pie'
89187
- * kind), or the chart has no plottable series. A non-null result means
89188
- * "render the WebGL scene"; `null` means "fall back to the flat SVG
89189
- * oblique-projection pie3D renderer".
89190
- */
89191
- function buildPieChart3DDataForElement(element, options) {
89192
- if (element.type !== 'chart') {
89193
- return null;
89194
- }
89195
- const chartEl = element;
89196
- const chartData = chartEl.chartData;
89197
- if (!chartData || chartData.series.length === 0) {
89198
- return null;
89199
- }
89200
- if (chartData.chartType !== 'pie3D') {
89201
- return null;
89202
- }
89203
- const pieSeries = chartData.series[0];
89204
- const categoryLabels = chartData.categories.length > 0
89205
- ? chartData.categories
89206
- : Array.from({ length: pieSeries.values.length }, (_, i) => String(i + 1));
89207
- return buildPieChart3DData(chartData, categoryLabels, options);
89208
- }
89209
-
89210
- /**
89211
- * Adapts a chart's `PptxChartData` into the flat typed-array grid the
89212
- * interactive 3D surface scene ({@link ./surface-chart-3d-scene.ts},
89213
- * `createSurfaceChart3DScene`) needs to mount.
89214
- *
89215
- * The SVG surface renderer (`chart-surface-treemap.ts`) and this adapter both
89216
- * normalise values the same way (`computeValueRange` + `surfaceColor`), so the
89217
- * 3D view's colour ramp matches the 2D fallback exactly: one set of chart
89218
- * maths, two presentations.
89219
- *
89220
- * @module surface-chart-3d-data
89221
- */
89222
- /** `c:floor`/`c:sideWall`/`c:backWall` fill colours the 3D scene can paint. */
89223
- function resolveSurfaceColors(chartData) {
89224
- return resolveChart3DWallColors(chartData);
89225
- }
89226
- /**
89227
- * Build the {@link SurfaceChart3DSceneOptions} `createSurfaceChart3DScene` needs from
89228
- * a chart element's data, or `null` when the chart has no plottable grid (no
89229
- * series, or every series has zero categories).
89230
- */
89231
- function buildSurfaceChart3DData(chartData, categoryLabels, options) {
89232
- const seriesCount = chartData.series.length;
89233
- const catCount = categoryLabels.length;
89234
- if (seriesCount === 0 || catCount === 0) {
89235
- return null;
89236
- }
89237
- // See the matching comment in `bar-chart-3d-data.ts`: the element height
89238
- // stands in for a plot-area height this 3D scene doesn't separately compute.
89239
- const range = computeValueRange(chartData.series, options.height);
89240
- const heightMap = new Float32Array(seriesCount * catCount);
89241
- const colorMap = new Float32Array(seriesCount * catCount * 3);
89242
- // Raw (un-normalised) values, kept alongside the [0,1] heightMap so the
89243
- // scene's raycast hover tooltip (`surface-chart-3d-hit-test.ts`) can report
89244
- // the authored value rather than its normalised height.
89245
- const values = new Float32Array(seriesCount * catCount);
89246
- for (let row = 0; row < seriesCount; row++) {
89247
- for (let col = 0; col < catCount; col++) {
89248
- const idx = row * catCount + col;
89249
- const val = chartData.series[row]?.values[col] ?? 0;
89250
- values[idx] = val;
89251
- const t = range.span > 0 ? (val - range.min) / range.span : 0;
89252
- heightMap[idx] = t;
89253
- const bandFill = resolveSurfaceBandFill(t, chartData.bandFmts);
89254
- const { r, g, b } = bandFill ? hexToRgb$3(bandFill) : surfaceColor(t);
89255
- const ci = idx * 3;
89256
- colorMap[ci] = r / 255;
89257
- colorMap[ci + 1] = g / 255;
89258
- colorMap[ci + 2] = b / 255;
89259
- }
89260
- }
89261
- return {
89262
- cols: catCount,
89263
- rows: seriesCount,
89264
- heightMap,
89265
- colorMap,
89266
- values,
89267
- numberFormats: chartData.series.map((s) => s.numberFormat),
89268
- wireframe: options.wireframe ?? chartData.wireframe ?? true,
89269
- categoryLabels,
89270
- seriesNames: chartData.series.map((s) => s.name),
89271
- width: options.width,
89272
- height: options.height,
89273
- view3D: chartData.view3D
89274
- ? { rotX: chartData.view3D.rotX, rotY: chartData.view3D.rotY }
89275
- : undefined,
89276
- surfaceColors: resolveSurfaceColors(chartData),
89277
- };
89278
- }
89279
- /**
89280
- * Single decision point every binding calls to decide whether a chart element
89281
- * should mount the interactive 3D surface scene: resolves the chart kind, the
89282
- * category-label fallback (mirrors `buildChartViewModel`'s `categoryLabels`
89283
- * derivation exactly, so 2D and 3D never disagree about what a category is
89284
- * called), and the 3D grid, in one place.
89285
- *
89286
- * Returns `null` when the element is not a chart, its kind does not resolve to
89287
- * `surface` (covers both `surface` and `surface3D` `c:chartType`s), or the
89288
- * chart has no plottable grid. Callers gate the 3D scene on this alone; a
89289
- * non-null result means "render the WebGL view", `null` means "fall back to
89290
- * the SVG isometric/flat surface renderer".
89291
- */
89292
- function buildSurfaceChart3DDataForElement(element, options) {
89293
- if (element.type !== 'chart') {
89294
- return null;
89295
- }
89296
- const chartEl = element;
89297
- const chartData = chartEl.chartData;
89298
- if (!chartData || chartData.series.length === 0) {
89299
- return null;
89300
- }
89301
- if (resolveChartKind(chartData.chartType ?? 'bar') !== 'surface') {
89302
- return null;
89303
- }
89304
- const longestLen = chartData.series.reduce((m, s) => Math.max(m, s.values.length), 0);
89305
- const categoryLabels = chartData.categories.length > 0
89306
- ? chartData.categories
89307
- : Array.from({ length: longestLen }, (_, i) => String(i + 1));
89308
- return buildSurfaceChart3DData(chartData, categoryLabels, options);
89309
- }
89310
-
89311
88466
  /** The chart types the 3D chart scene renders (`surface` covers the 2D and 3D surface charts; see below). */
89312
88467
  const CHART_3D_TYPES = new Set([
89313
88468
  'bar3D',
@@ -89364,6 +88519,10 @@ function buildChart3DSpecForElement(element) {
89364
88519
  : chartType === 'pie3D'
89365
88520
  ? buildPieGeometry(element, vm)
89366
88521
  : null;
88522
+ if (!geometry) {
88523
+ // Nothing to draw in 3D (no series or categories): the 2D chart stays.
88524
+ return null;
88525
+ }
89367
88526
  const longest = chartData.series.reduce((m, series) => Math.max(m, series.values.length), 0);
89368
88527
  const categoryLabels = chartData.categories.length > 0
89369
88528
  ? chartData.categories
@@ -89377,37 +88536,8 @@ function buildChart3DSpecForElement(element) {
89377
88536
  vm,
89378
88537
  geometry,
89379
88538
  categoryLabels,
89380
- perspective: geometry ? null : buildPerspectiveScene(element),
89381
88539
  };
89382
88540
  }
89383
- /** The perspective scene for a chart the oblique geometry does not cover, or `null`. */
89384
- function buildPerspectiveScene(element) {
89385
- const size = { width: element.width, height: element.height };
89386
- switch (element.chartData?.chartType) {
89387
- case 'bar3D': {
89388
- const options = buildBarChart3DDataForElement(element, size);
89389
- return options ? { kind: 'bar', options } : null;
89390
- }
89391
- case 'line3D': {
89392
- const options = buildLineChart3DDataForElement(element, size);
89393
- return options ? { kind: 'line', options } : null;
89394
- }
89395
- case 'area3D': {
89396
- const options = buildAreaChart3DDataForElement(element, size);
89397
- return options ? { kind: 'area', options } : null;
89398
- }
89399
- case 'pie3D': {
89400
- const options = buildPieChart3DDataForElement(element, size);
89401
- return options ? { kind: 'pie', options } : null;
89402
- }
89403
- case 'surface': {
89404
- const options = buildSurfaceChart3DDataForElement(element, size);
89405
- return options ? { kind: 'surface', options } : null;
89406
- }
89407
- default:
89408
- return null;
89409
- }
89410
- }
89411
88541
 
89412
88542
  /**
89413
88543
  * Live height of a DOM element, for layout that has to clear a strip whose
@@ -89763,7 +88893,7 @@ const THREE_UNAVAILABLE = {
89763
88893
  setInteractive: () => { },
89764
88894
  dispose: () => { },
89765
88895
  };
89766
- const FOV$1 = 50;
88896
+ const FOV = 50;
89767
88897
  /** Dynamically load `three`; returns `null` when the package is not installed. */
89768
88898
  async function loadThree() {
89769
88899
  try {
@@ -89842,7 +88972,7 @@ async function mountModel3D(container, modelUrl, options) {
89842
88972
  if (options.background) {
89843
88973
  scene.background = new three.Color(options.background);
89844
88974
  }
89845
- const camera = new three.PerspectiveCamera(FOV$1, width / height, 0.1, 1000);
88975
+ const camera = new three.PerspectiveCamera(FOV, width / height, 0.1, 1000);
89846
88976
  camera.position.set(0, 0, 5);
89847
88977
  camera.lookAt(0, 0, 0);
89848
88978
  scene.add(new three.AmbientLight(0xffffff, 0.5));
@@ -90112,1171 +89242,6 @@ function computeCameraPlacement(cols, rows, view3D) {
90112
89242
  };
90113
89243
  }
90114
89244
 
90115
- /** Pointer events after which a scene may look different (hover, highlight, drag preview). */
90116
- const REDRAW_EVENTS = ['pointermove', 'pointerdown', 'pointerup', 'pointerleave', 'wheel'];
90117
- /** Build the lights, camera and controls every perspective chart scene shares. */
90118
- function createHostedChart3DStage(ctx, placement, limits, ambientIntensity = 0.6) {
90119
- const three = ctx.three;
90120
- const width = Math.max(1, ctx.size.width);
90121
- const height = Math.max(1, ctx.size.height);
90122
- const scene = new three.Scene();
90123
- scene.add(new three.AmbientLight(0xffffff, ambientIntensity));
90124
- const key = new three.DirectionalLight(0xffffff, 0.8);
90125
- key.position.set(5, 8, 5);
90126
- scene.add(key);
90127
- const fill = new three.DirectionalLight(0xffffff, 0.3);
90128
- fill.position.set(-3, 4, -2);
90129
- scene.add(fill);
90130
- const camera = new three.PerspectiveCamera(placement.fov, width / height, 0.1, 1000);
90131
- camera.position.set(...placement.position);
90132
- const target = new three.Vector3(...placement.target);
90133
- camera.lookAt(target);
90134
- let orbit = null;
90135
- if (ctx.OrbitControls) {
90136
- orbit = new ctx.OrbitControls(camera, ctx.eventTarget);
90137
- const limited = orbit;
90138
- limited.enablePan = true;
90139
- limited.enableZoom = true;
90140
- limited.enableRotate = true;
90141
- limited.minDistance = limits.minDistance;
90142
- limited.maxDistance = limits.maxDistance;
90143
- limited.maxPolarAngle = Math.PI / 2 + 0.3;
90144
- limited.target.copy(target);
90145
- limited.enabled = ctx.interactive;
90146
- limited.update();
90147
- }
90148
- return {
90149
- three,
90150
- canvas: ctx.eventTarget,
90151
- scene,
90152
- camera,
90153
- controls: orbit ?? { enabled: false },
90154
- orbit,
90155
- width,
90156
- height,
90157
- };
90158
- }
90159
- /**
90160
- * The value-interaction callbacks a hosted scene hands its pointer wiring:
90161
- * they raise `<pptx-three-view>` select/drag events, and stay silent while the
90162
- * view is not interactive (a read-only mount never touches the selection).
90163
- */
90164
- function hostedChart3DInteraction(ctx) {
90165
- let interactive = ctx.interactive;
90166
- return {
90167
- onSelect: (part) => {
90168
- if (interactive) {
90169
- ctx.emit({ type: 'select', part });
90170
- }
90171
- },
90172
- onValueDragPreview: (part, value) => {
90173
- if (interactive) {
90174
- ctx.emit({ type: 'drag', detail: { part, value, phase: 'move' } });
90175
- }
90176
- },
90177
- onValueDragCommit: (part, value) => {
90178
- if (interactive) {
90179
- ctx.emit({ type: 'drag', detail: { part, value, phase: 'commit' } });
90180
- }
90181
- },
90182
- set: (on) => {
90183
- interactive = on;
90184
- },
90185
- };
90186
- }
90187
- /** Wrap a built stage + its scene-specific parts as the {@link ThreeViewScene} the host drives. */
90188
- function finishHostedChart3DScene(ctx, stage, parts, interactive) {
90189
- let width = stage.width;
90190
- let height = stage.height;
90191
- const requestRender = () => ctx.requestRender();
90192
- stage.orbit?.addEventListener('change', requestRender);
90193
- for (const type of REDRAW_EVENTS) {
90194
- ctx.eventTarget.addEventListener(type, requestRender);
90195
- }
90196
- let disposed = false;
90197
- return {
90198
- render(renderer) {
90199
- stage.orbit?.update();
90200
- renderer.render(stage.scene, stage.camera);
90201
- parts.afterRender?.(stage.camera, width, height);
90202
- },
90203
- resize(size) {
90204
- width = Math.max(1, size.width);
90205
- height = Math.max(1, size.height);
90206
- stage.camera.aspect = width / height;
90207
- stage.camera.updateProjectionMatrix();
90208
- parts.resize?.(width, height);
90209
- },
90210
- isAnimating: () => false,
90211
- setInteractive(on) {
90212
- interactive.set(on);
90213
- if (stage.orbit) {
90214
- stage.orbit.enabled = on;
90215
- }
90216
- },
90217
- setSelectedPart(part) {
90218
- parts.setSelectedPart?.(part);
90219
- },
90220
- setTextStyle(style) {
90221
- parts.setTextStyle?.(style);
90222
- },
90223
- dispose() {
90224
- if (disposed) {
90225
- return;
90226
- }
90227
- disposed = true;
90228
- stage.orbit?.removeEventListener('change', requestRender);
90229
- for (const type of REDRAW_EVENTS) {
90230
- ctx.eventTarget.removeEventListener(type, requestRender);
90231
- }
90232
- stage.orbit?.dispose();
90233
- parts.dispose();
90234
- stage.scene.clear();
90235
- },
90236
- };
90237
- }
90238
-
90239
- /** Attach the shared raycast-hover-tooltip wiring to one scene's canvas. */
90240
- function attachChart3DHoverTooltip(options) {
90241
- const raycaster = new options.three.Raycaster();
90242
- const ndc = new options.three.Vector2();
90243
- let hoveredTooltip;
90244
- const updateHoverTooltip = (clientX, clientY) => {
90245
- const rect = options.canvas.getBoundingClientRect();
90246
- if (rect.width <= 0 || rect.height <= 0) {
90247
- return;
90248
- }
90249
- ndc.x = ((clientX - rect.left) / rect.width) * 2 - 1;
90250
- ndc.y = -((clientY - rect.top) / rect.height) * 2 + 1;
90251
- raycaster.setFromCamera(ndc, options.camera);
90252
- const hit = raycaster.intersectObjects(options.meshes, false)[0];
90253
- const tooltip = options.buildTooltip(hit);
90254
- if (tooltip !== hoveredTooltip) {
90255
- hoveredTooltip = tooltip;
90256
- options.canvas.title = tooltip ?? '';
90257
- }
90258
- };
90259
- const onPointerMove = (event) => {
90260
- updateHoverTooltip(event.clientX, event.clientY);
90261
- };
90262
- const onPointerLeave = () => {
90263
- if (hoveredTooltip !== undefined) {
90264
- hoveredTooltip = undefined;
90265
- options.canvas.title = '';
90266
- }
90267
- };
90268
- options.canvas.addEventListener('pointermove', onPointerMove);
90269
- options.canvas.addEventListener('pointerleave', onPointerLeave);
90270
- return {
90271
- dispose() {
90272
- options.canvas.removeEventListener('pointermove', onPointerMove);
90273
- options.canvas.removeEventListener('pointerleave', onPointerLeave);
90274
- },
90275
- };
90276
- }
90277
-
90278
- /** Base (un-emphasised) font size, in CSS px; `fontScale` multiplies this, matching every other text surface's relative-size convention. */
90279
- const BASE_FONT_SIZE_PX = 9;
90280
- /** Create the DOM overlay nodes for the axis labels, returned with the layer. */
90281
- function createLabelOverlay(doc, labels) {
90282
- const layer = doc.createElement('div');
90283
- Object.assign(layer.style, {
90284
- position: 'absolute',
90285
- inset: '0',
90286
- pointerEvents: 'none',
90287
- overflow: 'hidden',
90288
- });
90289
- const entries = labels.map((label) => {
90290
- const node = doc.createElement('div');
90291
- node.textContent = label.text;
90292
- const baseColor = label.axis === 'value' ? '#999' : '#666';
90293
- Object.assign(node.style, {
90294
- position: 'absolute',
90295
- fontSize: `${BASE_FONT_SIZE_PX}px`,
90296
- color: baseColor,
90297
- whiteSpace: 'nowrap',
90298
- userSelect: 'none',
90299
- transform: 'translate(-50%, -50%)',
90300
- willChange: 'left, top',
90301
- });
90302
- if (label.axis === 'value') {
90303
- node.style.writingMode = 'vertical-rl';
90304
- }
90305
- layer.appendChild(node);
90306
- return { node, baseColor };
90307
- });
90308
- function applyTextStyle(style) {
90309
- for (const { node, baseColor } of entries) {
90310
- node.style.fontWeight = style?.bold === undefined ? '' : style.bold ? 'bold' : 'normal';
90311
- node.style.fontStyle = style?.italic === undefined ? '' : style.italic ? 'italic' : 'normal';
90312
- node.style.textDecorationLine =
90313
- style?.underline === undefined ? '' : style.underline ? 'underline' : 'none';
90314
- const scale = typeof style?.fontScale === 'number' &&
90315
- Number.isFinite(style.fontScale) &&
90316
- style.fontScale > 0
90317
- ? style.fontScale
90318
- : 1;
90319
- node.style.fontSize = `${BASE_FONT_SIZE_PX * scale}px`;
90320
- node.style.color = style?.color ?? baseColor;
90321
- }
90322
- }
90323
- return { layer, nodes: entries.map((e) => e.node), applyTextStyle };
90324
- }
90325
-
90326
- /** Build the label overlay and its per-frame projector for one scene. */
90327
- function createChart3DLabelProjector(three, doc, labels) {
90328
- const overlay = createLabelOverlay(doc, labels);
90329
- const anchors = labels.map((l) => new three.Vector3(...l.anchor));
90330
- const projected = new three.Vector3();
90331
- return {
90332
- layer: overlay.layer,
90333
- applyTextStyle: overlay.applyTextStyle,
90334
- update(camera, width, height) {
90335
- for (let i = 0; i < overlay.nodes.length; i++) {
90336
- projected.copy(anchors[i]).project(camera);
90337
- const node = overlay.nodes[i];
90338
- if (projected.z > 1) {
90339
- node.style.display = 'none';
90340
- continue;
90341
- }
90342
- node.style.display = '';
90343
- node.style.left = `${((projected.x + 1) / 2) * width}px`;
90344
- node.style.top = `${((-projected.y + 1) / 2) * height}px`;
90345
- }
90346
- },
90347
- };
90348
- }
90349
-
90350
- /**
90351
- * Pure raycast-to-data-cell mapping for the interactive 3D surface chart mesh
90352
- * ({@link ./surface-chart-3d-scene.ts}).
90353
- *
90354
- * Every other chart kind's hover tooltip (bar / line / area / scatter / bubble
90355
- * / pie / radar / map) is projected as a native SVG `<title>` child on the
90356
- * mark itself (`buildMarkTooltip` in `chart-view-model.ts`); a browser shows
90357
- * that title on hover for free. The WebGL surface mesh has no SVG marks to
90358
- * attach a `<title>` to, so the scene controller raycasts the pointer against
90359
- * the mesh, maps the hit back to the (series, category) cell it belongs to
90360
- * with the pure functions here, and sets that same `buildMarkTooltip` text as
90361
- * the canvas element's own `title` attribute, i.e. the identical native-tooltip
90362
- * mechanism every other mark already uses.
90363
- *
90364
- * Kept three-agnostic (no runtime `three` import, only a raw face index in and
90365
- * a row/col out) so the row/col math is unit-testable without mocking WebGL.
90366
- *
90367
- * @module surface-chart-3d-hit-test
90368
- */
90369
- /**
90370
- * Map a three.js `Raycaster` intersection's `faceIndex` (a triangle index into
90371
- * the surface mesh, built by `buildSurfaceGeometry`'s `THREE.PlaneGeometry`)
90372
- * back to the (row, col) grid cell whose facet the triangle belongs to.
90373
- *
90374
- * `PlaneGeometry(width, height, widthSegs, depthSegs)` emits `widthSegs *
90375
- * depthSegs` quads in row-major order, each split into 2 triangles, so
90376
- * `floor(faceIndex / 2)` is the quad index and `floor(quadIndex / widthSegs)`
90377
- * / `quadIndex % widthSegs` recover its row/col. A facet spans the four grid
90378
- * vertices `[row, col]..[row+1, col+1]`; it is reported anchored at its
90379
- * top-left corner `(row, col)`, mirroring the SVG isometric surface renderer's
90380
- * `part: { seriesIndex: row, pointIndex: col }` anchor convention
90381
- * (`chart-surface-treemap.ts`) so both presentations agree on which data point
90382
- * a given facet "is".
90383
- *
90384
- * Returns `null` when `faceIndex` is out of range for a `cols x rows` grid
90385
- * (fewer than 2 columns or rows has no facets at all).
90386
- */
90387
- function surfaceFaceIndexToCell(faceIndex, cols, rows) {
90388
- const quadsPerRow = cols - 1;
90389
- const quadsPerCol = rows - 1;
90390
- if (!Number.isFinite(faceIndex) || faceIndex < 0 || quadsPerRow <= 0 || quadsPerCol <= 0) {
90391
- return null;
90392
- }
90393
- const quadIndex = Math.floor(faceIndex / 2);
90394
- const row = Math.floor(quadIndex / quadsPerRow);
90395
- const col = quadIndex % quadsPerRow;
90396
- if (row < 0 || row >= quadsPerCol || col < 0 || col >= quadsPerRow) {
90397
- return null;
90398
- }
90399
- return { row, col };
90400
- }
90401
- /**
90402
- * Build the hover-tooltip text for a raycast hit on the surface mesh, or
90403
- * `undefined` when there is no hit, the hit falls outside the data grid, or
90404
- * the scene was mounted without raw `values` (older callers that only supply
90405
- * the normalised height/colour maps keep rendering with no tooltip).
90406
- *
90407
- * Mirrors `buildMarkTooltip`'s "<series>, <category>: <value>" text exactly,
90408
- * so a surface facet's tooltip reads identically to every other chart kind's.
90409
- */
90410
- function buildSurfaceHoverTooltip(faceIndex, data) {
90411
- if (faceIndex === undefined || faceIndex === null || !data.values) {
90412
- return undefined;
90413
- }
90414
- const cell = surfaceFaceIndexToCell(faceIndex, data.cols, data.rows);
90415
- if (!cell) {
90416
- return undefined;
90417
- }
90418
- const idx = cell.row * data.cols + cell.col;
90419
- const value = data.values[idx];
90420
- if (value === undefined) {
90421
- return undefined;
90422
- }
90423
- return buildMarkTooltip(data.seriesNames[cell.row], data.categoryLabels[cell.col], value, data.numberFormats?.[cell.row]);
90424
- }
90425
-
90426
- /**
90427
- * Minimum pointer travel (px) before a mark press becomes a value drag,
90428
- * mirroring {@link CHART_DRAG_THRESHOLD_PX} (`chart-canvas-drag.ts`): without
90429
- * a threshold, every click on a mark would commit a (tiny) value change, so a
90430
- * user could never select a mark without editing it.
90431
- */
90432
- const CHART_3D_DRAG_THRESHOLD_PX = 3;
90433
- /** Emissive colour + intensity applied to a selected mark's material. */
90434
- const CHART_3D_SELECTED_EMISSIVE = '#3b82f6';
90435
- const CHART_3D_SELECTED_EMISSIVE_INTENSITY = 0.55;
90436
- /**
90437
- * Map a 3D raycast hit (or none) to the same selection descriptor the 2D
90438
- * chart-interaction model uses.
90439
- */
90440
- function chart3DHitToPartRef(hit) {
90441
- if (!hit) {
90442
- return null;
90443
- }
90444
- return { role: 'dataPoint', seriesIndex: hit.seriesIndex, pointIndex: hit.pointIndex };
90445
- }
90446
- /** Whether a mesh's own (series, point) reference is the currently-selected part. */
90447
- function chart3DMarkMatchesPart(mark, part) {
90448
- return (part !== null &&
90449
- part !== undefined &&
90450
- part.role === 'dataPoint' &&
90451
- part.seriesIndex === mark.seriesIndex &&
90452
- part.pointIndex === mark.pointIndex);
90453
- }
90454
- /**
90455
- * Calibrate a value axis from two already-projected screen points known to
90456
- * correspond to two distinct data values on the same mark (e.g. its
90457
- * value-zero base and its current top). Returns `null` when the two values
90458
- * coincide or the two screen points are (near) coincident (camera looking
90459
- * straight down the axis), since no calibration is possible.
90460
- *
90461
- * Order-independent: swapping `(screenAtValue0, value0)` with
90462
- * `(screenAtValue1, value1)` yields the same {@link chart3DPointerDeltaToValueDelta}
90463
- * result, since the direction vector and `pixelsPerUnit` sign flip together.
90464
- */
90465
- function calibrateChart3DValueAxis(screenAtValue0, value0, screenAtValue1, value1) {
90466
- const valueSpan = value1 - value0;
90467
- const dx = screenAtValue1.x - screenAtValue0.x;
90468
- const dy = screenAtValue1.y - screenAtValue0.y;
90469
- const pixelSpan = Math.hypot(dx, dy);
90470
- if (valueSpan === 0 || pixelSpan < 1e-6 || !Number.isFinite(valueSpan)) {
90471
- return null;
90472
- }
90473
- return {
90474
- directionScreen: { x: dx / pixelSpan, y: dy / pixelSpan },
90475
- pixelsPerUnit: pixelSpan / valueSpan,
90476
- };
90477
- }
90478
- /**
90479
- * Convert a screen-space pointer delta (current position minus drag-start
90480
- * position, in CSS px) into a value delta, using a calibration taken at drag
90481
- * start. Add the result to the mark's value at drag start to get the live
90482
- * (or committed) dragged value.
90483
- */
90484
- function chart3DPointerDeltaToValueDelta(calibration, pointerDeltaScreen) {
90485
- const dot = pointerDeltaScreen.x * calibration.directionScreen.x +
90486
- pointerDeltaScreen.y * calibration.directionScreen.y;
90487
- return dot / calibration.pixelsPerUnit;
90488
- }
90489
-
90490
- function projectToScreen(three, camera, point, width, height) {
90491
- const v = new three.Vector3(point[0], point[1], point[2]);
90492
- v.project(camera);
90493
- return { x: ((v.x + 1) / 2) * width, y: ((-v.y + 1) / 2) * height };
90494
- }
90495
- /** Attach the shared click-to-select / drag-to-value pointer handlers to one scene's canvas. */
90496
- function attachChart3DPointerInteraction(options) {
90497
- let width = options.width;
90498
- let height = options.height;
90499
- const raycaster = new options.three.Raycaster();
90500
- const ndc = new options.three.Vector2();
90501
- let dragState = null;
90502
- function raycastAt(clientX, clientY) {
90503
- const rect = options.canvas.getBoundingClientRect();
90504
- if (rect.width <= 0 || rect.height <= 0) {
90505
- return undefined;
90506
- }
90507
- ndc.x = ((clientX - rect.left) / rect.width) * 2 - 1;
90508
- ndc.y = -((clientY - rect.top) / rect.height) * 2 + 1;
90509
- raycaster.setFromCamera(ndc, options.camera);
90510
- return raycaster.intersectObjects(options.meshes, false)[0];
90511
- }
90512
- function setSelectedPart(part) {
90513
- options.applyHighlight?.(part);
90514
- }
90515
- function currentDelta(clientX, clientY, state) {
90516
- return { x: clientX - state.startClientX, y: clientY - state.startClientY };
90517
- }
90518
- const onPointerDown = (event) => {
90519
- // Pointer capture keeps delivering move/up to THIS canvas even once the
90520
- // pointer travels outside its bounds mid-drag; optional-chained since
90521
- // test doubles for `canvas` do not implement it.
90522
- options.canvas.setPointerCapture?.(event.pointerId);
90523
- const hit = raycastAt(event.clientX, event.clientY);
90524
- const part = hit ? options.resolveHit(hit) : null;
90525
- const armed = part !== null && isChartInteractionArmed(options.canvas);
90526
- if (armed) {
90527
- // The scene owns this press (select / value-drag): keep it from the
90528
- // stage's element-move handler. See the module doc.
90529
- event.stopPropagation();
90530
- }
90531
- let calibration = null;
90532
- let startValue = 0;
90533
- if (hit && part && armed && options.calibrateDrag) {
90534
- const input = options.calibrateDrag(hit, part);
90535
- if (input) {
90536
- const s0 = projectToScreen(options.three, options.camera, input.worldAtValue0, width, height);
90537
- const s1 = projectToScreen(options.three, options.camera, input.worldAtValue1, width, height);
90538
- calibration = calibrateChart3DValueAxis(s0, input.value0, s1, input.value1);
90539
- startValue = input.value1;
90540
- }
90541
- }
90542
- dragState = {
90543
- part,
90544
- calibration,
90545
- startValue,
90546
- startClientX: event.clientX,
90547
- startClientY: event.clientY,
90548
- moved: false,
90549
- };
90550
- if (calibration) {
90551
- // Cancelling the pointerdown also suppresses its compatibility
90552
- // mousedown, which is what the React stage's DESKTOP element-move
90553
- // path listens to (stopPropagation alone leaves that one alive).
90554
- // Same as every 2D mark press once its drag has started.
90555
- event.preventDefault();
90556
- options.setControlsEnabled?.(false);
90557
- }
90558
- };
90559
- const onPointerMove = (event) => {
90560
- if (!dragState || !dragState.part || !dragState.calibration) {
90561
- return;
90562
- }
90563
- const delta = currentDelta(event.clientX, event.clientY, dragState);
90564
- if (!dragState.moved && Math.hypot(delta.x, delta.y) < CHART_3D_DRAG_THRESHOLD_PX) {
90565
- return;
90566
- }
90567
- dragState.moved = true;
90568
- const value = dragState.startValue + chart3DPointerDeltaToValueDelta(dragState.calibration, delta);
90569
- options.onValueDragPreview?.(dragState.part, value);
90570
- };
90571
- const onPointerUp = (event) => {
90572
- if (!dragState) {
90573
- return;
90574
- }
90575
- const state = dragState;
90576
- dragState = null;
90577
- options.canvas.releasePointerCapture?.(event.pointerId);
90578
- if (state.calibration) {
90579
- options.setControlsEnabled?.(true);
90580
- }
90581
- if (state.moved && state.part && state.calibration) {
90582
- const delta = currentDelta(event.clientX, event.clientY, state);
90583
- const value = state.startValue + chart3DPointerDeltaToValueDelta(state.calibration, delta);
90584
- options.onValueDragCommit?.(state.part, value);
90585
- return;
90586
- }
90587
- setSelectedPart(state.part);
90588
- options.onSelect?.(state.part);
90589
- };
90590
- options.canvas.addEventListener('pointerdown', onPointerDown);
90591
- options.canvas.addEventListener('pointermove', onPointerMove);
90592
- options.canvas.addEventListener('pointerup', onPointerUp);
90593
- options.canvas.addEventListener('pointercancel', onPointerUp);
90594
- return {
90595
- setSelectedPart,
90596
- updateSize(w, h) {
90597
- width = w;
90598
- height = h;
90599
- },
90600
- dispose() {
90601
- options.canvas.removeEventListener('pointerdown', onPointerDown);
90602
- options.canvas.removeEventListener('pointermove', onPointerMove);
90603
- options.canvas.removeEventListener('pointerup', onPointerUp);
90604
- options.canvas.removeEventListener('pointercancel', onPointerUp);
90605
- },
90606
- };
90607
- }
90608
-
90609
- /**
90610
- * Calibrate a value drag for one surface3D vertex, given its own (already
90611
- * height-displaced) world position, its raw value, and every raw value in the
90612
- * grid (to recover the `[min, max]` the heightMap normalised against; the
90613
- * scene already holds this flat array for the hover tooltip, so no extra data
90614
- * needs threading through). Returns `null` when every cell shares the same
90615
- * value: there is then no vertical axis to calibrate a drag against.
90616
- */
90617
- function calibrateSurfaceChart3DDrag(position, value, values) {
90618
- let min = Infinity;
90619
- let max = -Infinity;
90620
- for (const v of values) {
90621
- if (v < min) {
90622
- min = v;
90623
- }
90624
- if (v > max) {
90625
- max = v;
90626
- }
90627
- }
90628
- if (!(max > min)) {
90629
- return null;
90630
- }
90631
- // A reference point at a DIFFERENT value on the SAME vertical column
90632
- // (fixed x/z), exact by construction: world Y 0 <-> the grid minimum,
90633
- // world Y MAX_HEIGHT <-> the grid maximum. Use whichever end differs from
90634
- // this vertex's own value (a vertex already at the minimum needs the
90635
- // maximum as its reference, and vice versa).
90636
- const [x, , z] = position;
90637
- const atMin = value === min;
90638
- return {
90639
- worldAtValue0: [x, atMin ? MAX_HEIGHT : 0, z],
90640
- value0: atMin ? max : min,
90641
- worldAtValue1: position,
90642
- value1: value,
90643
- };
90644
- }
90645
-
90646
- const MARKER_RADIUS$2 = 0.07;
90647
- /** Lifts the marker just clear of the mesh surface so it never z-fights it. */
90648
- const MARKER_Y_OFFSET = 0.03;
90649
- /**
90650
- * Build the selection marker mesh for a surface3D scene. The caller adds
90651
- * `.mesh` to its scene and disposes the returned handle alongside its other
90652
- * geometries/materials.
90653
- */
90654
- function createSurfaceHighlightMarker(three, cols, rows, heightMap) {
90655
- const geometry = new three.SphereGeometry(MARKER_RADIUS$2, 12, 8);
90656
- const material = new three.MeshPhongMaterial({
90657
- color: CHART_3D_SELECTED_EMISSIVE,
90658
- emissive: CHART_3D_SELECTED_EMISSIVE,
90659
- emissiveIntensity: CHART_3D_SELECTED_EMISSIVE_INTENSITY,
90660
- shininess: 30,
90661
- });
90662
- const mesh = new three.Mesh(geometry, material);
90663
- mesh.visible = false;
90664
- return {
90665
- mesh,
90666
- setSelected(cell) {
90667
- if (!cell) {
90668
- mesh.visible = false;
90669
- return;
90670
- }
90671
- const [x, y, z] = surfaceVertexWorldPosition(cols, rows, cell.row, cell.col, heightMap);
90672
- mesh.position.set(x, y + MARKER_Y_OFFSET, z);
90673
- mesh.visible = true;
90674
- },
90675
- dispose() {
90676
- geometry.dispose();
90677
- material.dispose();
90678
- },
90679
- };
90680
- }
90681
- function cellFromIntersection(intersection, cols, rows) {
90682
- return intersection.faceIndex === undefined || intersection.faceIndex === null
90683
- ? null
90684
- : surfaceFaceIndexToCell(intersection.faceIndex, cols, rows);
90685
- }
90686
- /** Attach click-to-select / drag-to-value handling to a mounted surface3D scene. */
90687
- function attachSurfaceChart3DInteraction(params) {
90688
- return attachChart3DPointerInteraction({
90689
- three: params.three,
90690
- canvas: params.canvas,
90691
- camera: params.camera,
90692
- width: params.width,
90693
- height: params.height,
90694
- meshes: [params.surfaceMesh],
90695
- resolveHit: (intersection) => {
90696
- const cell = cellFromIntersection(intersection, params.cols, params.rows);
90697
- return chart3DHitToPartRef(cell ? { seriesIndex: cell.row, pointIndex: cell.col } : null);
90698
- },
90699
- calibrateDrag: (intersection) => {
90700
- const cell = cellFromIntersection(intersection, params.cols, params.rows);
90701
- if (!cell || !params.values) {
90702
- return null;
90703
- }
90704
- const idx = cell.row * params.cols + cell.col;
90705
- const value = params.values[idx];
90706
- if (value === undefined) {
90707
- return null;
90708
- }
90709
- const position = surfaceVertexWorldPosition(params.cols, params.rows, cell.row, cell.col, params.heightMap);
90710
- return calibrateSurfaceChart3DDrag(position, value, params.values);
90711
- },
90712
- applyHighlight: (part) => {
90713
- const cell = part && part.role === 'dataPoint' && part.pointIndex !== undefined
90714
- ? { row: part.seriesIndex, col: part.pointIndex }
90715
- : null;
90716
- params.highlightMarker.setSelected(cell);
90717
- },
90718
- setControlsEnabled: (enabled) => {
90719
- params.controls.enabled = enabled;
90720
- },
90721
- onSelect: params.interaction?.onSelect,
90722
- onValueDragPreview: params.interaction?.onValueDragPreview,
90723
- onValueDragCommit: params.interaction?.onValueDragCommit,
90724
- });
90725
- }
90726
-
90727
- const PANEL_OPACITY = 0.35;
90728
- /**
90729
- * Build plane meshes for each authored surface colour. The floor lies flat
90730
- * under the grid (z=0); the back wall stands at the grid's far depth edge;
90731
- * the side wall stands at the grid's far width edge, matching the 2D
90732
- * isometric renderer's back-wall/side-wall corner convention.
90733
- *
90734
- * `extent` defaults to the surface chart's own `computeGridExtent(cols, rows)`
90735
- * (1:1 grid-step spacing) when omitted, so existing callers are unaffected.
90736
- * A cartesian scene whose depth axis is NOT 1:1 with its row count (bar3D's
90737
- * `c:view3D/@depthPercent` scaling) passes its own precomputed extent instead
90738
- * of letting this module recompute a mismatched one.
90739
- */
90740
- function buildSurfaceWallMeshes(three, cols, rows, wallHeight, colors, extent) {
90741
- const { gridWidth, gridDepth } = extent ?? computeGridExtent(cols, rows);
90742
- const meshes = [];
90743
- const disposables = [];
90744
- const addPlane = (width, height, color, position, rotation) => {
90745
- const geometry = new three.PlaneGeometry(width, height);
90746
- const material = new three.MeshBasicMaterial({
90747
- color,
90748
- transparent: true,
90749
- opacity: PANEL_OPACITY,
90750
- side: three.DoubleSide,
90751
- depthWrite: false,
90752
- });
90753
- const mesh = new three.Mesh(geometry, material);
90754
- mesh.position.set(...position);
90755
- mesh.rotation.set(...rotation);
90756
- meshes.push(mesh);
90757
- disposables.push(geometry, material);
90758
- };
90759
- if (colors.floor) {
90760
- addPlane(gridWidth, gridDepth, colors.floor, [0, -0.01, 0], [-Math.PI / 2, 0, 0]);
90761
- }
90762
- if (colors.backWall) {
90763
- addPlane(gridWidth, wallHeight, colors.backWall, [0, wallHeight / 2, -gridDepth / 2], [0, 0, 0]);
90764
- }
90765
- if (colors.sideWall) {
90766
- addPlane(gridDepth, wallHeight, colors.sideWall, [gridWidth / 2, wallHeight / 2, 0], [0, Math.PI / 2, 0]);
90767
- }
90768
- return {
90769
- meshes,
90770
- dispose: () => {
90771
- for (const d of disposables) {
90772
- d.dispose();
90773
- }
90774
- },
90775
- };
90776
- }
90777
-
90778
- /**
90779
- * three.js 3D surface-chart scene for `<pptx-three-view>` (framework-agnostic).
90780
- *
90781
- * Builds an interactive surface chart as a `<pptx-three-view>` scene: a
90782
- * colour-displaced surface mesh (with optional wireframe), grid floor,
90783
- * lights, an isometric camera, and DOM axis labels re-projected to screen
90784
- * after each frame. Raycasts pointer moves against the mesh for a native
90785
- * hover tooltip (see {@link ./surface-chart-3d-hit-test.ts}) and clicks/drags
90786
- * for selection and value editing (see
90787
- * {@link ./surface-chart-3d-interaction-wiring.ts}).
90788
- *
90789
- * @module surface-chart-3d-scene
90790
- */
90791
- const FOV = 45;
90792
- /** Build the hosted SurfaceChart3D scene. */
90793
- function createSurfaceChart3DScene(ctx, options) {
90794
- const { cols, rows } = options;
90795
- const placement = computeCameraPlacement(cols, rows, options.view3D);
90796
- const stage = createHostedChart3DStage(ctx, { fov: FOV, ...placement }, { minDistance: 1, maxDistance: 20 }, 0.6);
90797
- const { three, scene, camera, canvas, controls, width, height } = stage;
90798
- const interaction = hostedChart3DInteraction(ctx);
90799
- // Grid floor under the surface.
90800
- const { gridWidth, gridDepth } = computeGridExtent(cols, rows);
90801
- const floorSize = Math.max(gridWidth, gridDepth) * 1.2;
90802
- const gridFloor = new three.GridHelper(floorSize, Math.max(cols, rows), 0xcccccc, 0xe8e8e8);
90803
- gridFloor.position.y = -0.02;
90804
- scene.add(gridFloor);
90805
- const walls = options.surfaceColors
90806
- ? buildSurfaceWallMeshes(three, cols, rows, MAX_HEIGHT, options.surfaceColors)
90807
- : null;
90808
- for (const mesh of walls?.meshes ?? []) {
90809
- scene.add(mesh);
90810
- }
90811
- const { heightMap, colorMap } = options;
90812
- const { geometry, wireGeometry } = buildSurfaceGeometry(three, cols, rows, heightMap, colorMap);
90813
- const surfaceMaterial = new three.MeshPhongMaterial({
90814
- vertexColors: true,
90815
- side: three.DoubleSide,
90816
- shininess: 30,
90817
- transparent: true,
90818
- opacity: 0.92,
90819
- });
90820
- const surfaceMesh = new three.Mesh(geometry, surfaceMaterial);
90821
- scene.add(surfaceMesh);
90822
- // Selected-vertex highlight: see `surface-chart-3d-interaction-wiring.ts`'s
90823
- // module doc for why this is a marker mesh rather than a material tint.
90824
- const highlightMarker = createSurfaceHighlightMarker(three, cols, rows, heightMap);
90825
- scene.add(highlightMarker.mesh);
90826
- let wireMaterial = null;
90827
- if (options.wireframe) {
90828
- wireMaterial = new three.LineBasicMaterial({
90829
- color: 0x333333,
90830
- transparent: true,
90831
- opacity: 0.25,
90832
- });
90833
- scene.add(new three.LineSegments(wireGeometry, wireMaterial));
90834
- }
90835
- // Raycast-based hover tooltip against the single surface mesh (see
90836
- // `surface-chart-3d-hit-test.ts` for the raycast-to-cell math).
90837
- const hoverTooltip = attachChart3DHoverTooltip({
90838
- three,
90839
- canvas,
90840
- camera,
90841
- meshes: [surfaceMesh],
90842
- buildTooltip: (intersection) => buildSurfaceHoverTooltip(intersection?.faceIndex, {
90843
- cols,
90844
- rows,
90845
- categoryLabels: options.categoryLabels,
90846
- seriesNames: options.seriesNames,
90847
- values: options.values,
90848
- numberFormats: options.numberFormats,
90849
- }),
90850
- });
90851
- // Click-to-select + drag-to-value (see surface-chart-3d-interaction-wiring.ts).
90852
- const pointerInteraction = attachSurfaceChart3DInteraction({
90853
- three,
90854
- canvas,
90855
- camera,
90856
- controls,
90857
- width,
90858
- height,
90859
- surfaceMesh,
90860
- cols,
90861
- rows,
90862
- heightMap,
90863
- values: options.values,
90864
- highlightMarker,
90865
- interaction,
90866
- });
90867
- // Axis-label DOM overlay, re-projected to screen each frame.
90868
- const labels = buildSurfaceLabels(cols, rows, options.categoryLabels, options.seriesNames);
90869
- const labelProjector = createChart3DLabelProjector(three, ctx.document, labels);
90870
- labelProjector.applyTextStyle(options.textStyle);
90871
- ctx.overlay.appendChild(labelProjector.layer);
90872
- return finishHostedChart3DScene(ctx, stage, {
90873
- afterRender: (cam, w, h) => labelProjector.update(cam, w, h),
90874
- resize: (w, h) => pointerInteraction.updateSize(w, h),
90875
- setSelectedPart: (part) => pointerInteraction.setSelectedPart(part),
90876
- setTextStyle: (style) => labelProjector.applyTextStyle(style),
90877
- dispose() {
90878
- hoverTooltip.dispose();
90879
- pointerInteraction.dispose();
90880
- geometry.dispose();
90881
- wireGeometry.dispose();
90882
- surfaceMaterial.dispose();
90883
- wireMaterial?.dispose();
90884
- highlightMarker.dispose();
90885
- gridFloor.dispose();
90886
- walls?.dispose();
90887
- labelProjector.layer.remove();
90888
- },
90889
- }, interaction);
90890
- }
90891
-
90892
- function colorFace(color) {
90893
- return { kind: 'color', color };
90894
- }
90895
- /**
90896
- * World-space height one unit of the plotted VALUE spans for this specific
90897
- * box, derived from the box's own already-resolved `size`/`value` (both set
90898
- * by `layoutBarChart3D` from the SAME linear value-axis scale every box in
90899
- * the chart shares). This needs no container/camera measurement at all: unlike
90900
- * a screen-pixel height, `box.size`/`box.value` are fixed the moment the chart
90901
- * is laid out and never change as the user orbits or zooms the scene.
90902
- *
90903
- * `undefined` when `box.value` is `0` (nothing to anchor a "one value unit"
90904
- * height to); the caller falls back to a single tile. Note this is a
90905
- * per-box approximation, not an exact axis-scale lookup: `layoutBarChart3D`
90906
- * clamps a very small bar to a minimum visible height and a non-zero-based
90907
- * value range offsets `box.value`, so the ratio can drift slightly from the
90908
- * chart's true value-per-world-unit scale in those edge cases - the same
90909
- * spirit of "closest available approximation" as this renderer's other
90910
- * documented 3-D-scene gaps (see `chart-bar3d-face-picture.ts`'s module doc).
90911
- */
90912
- function worldHeightPerValueUnit(box) {
90913
- return box.value !== 0 ? box.size[1] / box.value : undefined;
90914
- }
90915
- /**
90916
- * Repeat count (>=1 tile) for `stack`/`stackScale` along one face axis;
90917
- * always `1` for `stretch`. `pictureStackUnit` (`c:pictureStackUnit`) is
90918
- * interpreted here as a VALUE-axis quantity (one tile per that many data
90919
- * units, matching the OOXML spec's own "units per picture" semantics): the
90920
- * number of tiles that exactly fit a box of value `box.value` is then
90921
- * `box.value / pictureStackUnit`, which is exactly what falls out of
90922
- * `faceWorldSize / (pictureStackUnit * worldHeightPerValueUnit(box))` when
90923
- * `faceWorldSize` is `box.size[1]` (see {@link worldHeightPerValueUnit}).
90924
- */
90925
- function faceRepeat(format, pictureStackUnit, faceWorldSize, box) {
90926
- if (format === 'stretch' || pictureStackUnit === undefined || pictureStackUnit <= 0) {
90927
- return 1;
90928
- }
90929
- const perValueUnit = worldHeightPerValueUnit(box);
90930
- if (!perValueUnit) {
90931
- return 1;
90932
- }
90933
- const tileWorldSize = pictureStackUnit * perValueUnit;
90934
- return tileWorldSize > 0 ? Math.max(1, faceWorldSize / tileWorldSize) : 1;
90935
- }
90936
- /** Build the picture-or-colour fill for one named face, resolving its repeat maths from `faceWorldSize`. */
90937
- function resolveTargetedFace(series, box, face, faceWorldSize, untargetedFallback) {
90938
- const resolved = resolveDataPointPictureFill(series, box.categoryIndex, box.seriesIndex, face);
90939
- if (!resolved) {
90940
- return colorFace(untargetedFallback);
90941
- }
90942
- const pictureStackUnit = resolveActiveDataPointPicture(series, box.categoryIndex)?.pictureStackUnit;
90943
- return {
90944
- kind: 'picture',
90945
- imageUrl: resolved.imageUrl,
90946
- repeatX: 1,
90947
- repeatY: faceRepeat(resolved.format, pictureStackUnit, faceWorldSize, box),
90948
- };
90949
- }
90950
- /** `true` (and the shared colour) only when every given face resolved to the SAME plain colour. */
90951
- function allSameColor(fills) {
90952
- const first = fills[0];
90953
- if (!first || first.kind !== 'color') {
90954
- return undefined;
90955
- }
90956
- return fills.every((fill) => fill.kind === 'color' && fill.color === first.color)
90957
- ? first.color
90958
- : undefined;
90959
- }
90960
- /**
90961
- * Resolve one box's six `BoxGeometry` face fills.
90962
- *
90963
- * Returns all six as the box's plain, unmodified colour (no derived tint or
90964
- * shade: the caller relies on real per-face Phong lighting for the 3-D look,
90965
- * unlike the flat SVG renderer's manual shading) when the box is not a `box`
90966
- * shape, or has no picture fill resolved at all - i.e. the pre-existing,
90967
- * unchanged appearance for the overwhelming majority of bar3D charts that
90968
- * never author `c:pictureOptions`.
90969
- *
90970
- * Once a point (or its series) has ANY picture fill configured, this mirrors
90971
- * the flat SVG renderer's own per-face treatment: `front` keeps the plain
90972
- * resolved colour when untargeted (never tinted, matching
90973
- * `chart-datapoint-picture-fills.ts`'s front-face fallback); `side`/`end`
90974
- * fall back to {@link resolveUntargetedBarFaceFill}'s shade/tint when
90975
- * untargeted, matching the SVG oblique-projection renderer's own fallback for
90976
- * those two faces (`chart-3d-depth.ts`). `back` (`-z`) and `bottom` (`-y`)
90977
- * are never modelled by PowerPoint's `applyToFront`/`applyToSides`/
90978
- * `applyToEnd` (they are never visible: bottom sits on the floor, back faces
90979
- * away from the scene's default camera), so they always keep the box's plain
90980
- * resolved colour.
90981
- */
90982
- function resolveBarBoxFaceFills(box, series) {
90983
- const plain = {
90984
- posX: colorFace(box.color),
90985
- negX: colorFace(box.color),
90986
- posY: colorFace(box.color),
90987
- negY: colorFace(box.color),
90988
- posZ: colorFace(box.color),
90989
- negZ: colorFace(box.color),
90990
- };
90991
- if (box.shape !== undefined && box.shape !== 'box') {
90992
- return plain;
90993
- }
90994
- const seriesData = series[box.seriesIndex];
90995
- if (!seriesData) {
90996
- return plain;
90997
- }
90998
- const active = resolveActiveDataPointPicture(seriesData, box.categoryIndex);
90999
- if (!active?.imageUrl) {
91000
- return plain;
91001
- }
91002
- const [, height, depth] = box.size;
91003
- const frontFill = resolveTargetedFace(seriesData, box, 'front', height, box.color);
91004
- const sideFill = resolveTargetedFace(seriesData, box, 'side', height, resolveUntargetedBarFaceFill('side', box.color));
91005
- const endFill = resolveTargetedFace(seriesData, box, 'end', depth, resolveUntargetedBarFaceFill('end', box.color));
91006
- return {
91007
- posX: sideFill,
91008
- negX: sideFill,
91009
- posY: endFill,
91010
- negY: colorFace(box.color),
91011
- posZ: frontFill,
91012
- negZ: colorFace(box.color),
91013
- };
91014
- }
91015
- /**
91016
- * Resolve one round bar's (cylinder/cone/pyramid/coneToMax/pyramidToMax)
91017
- * three material-group fills: `side` (the lateral surface, `applyToSides`),
91018
- * `end` (the top cap, `applyToEnd`) and `bottom` (never targeted by
91019
- * PowerPoint, like a box's `-Y`; always the plain resolved colour). Returns
91020
- * all three plain when the shape has no picture fill resolved at all, same
91021
- * fast path as {@link resolveBarBoxFaceFills}.
91022
- */
91023
- function resolveBarRoundFaceFills(box, series) {
91024
- const plain = {
91025
- side: colorFace(box.color),
91026
- end: colorFace(box.color),
91027
- bottom: colorFace(box.color),
91028
- };
91029
- const seriesData = series[box.seriesIndex];
91030
- if (!seriesData) {
91031
- return plain;
91032
- }
91033
- const active = resolveActiveDataPointPicture(seriesData, box.categoryIndex);
91034
- if (!active?.imageUrl) {
91035
- return plain;
91036
- }
91037
- const [, height, depth] = box.size;
91038
- const sideFill = resolveTargetedFace(seriesData, box, 'side', height, resolveUntargetedBarFaceFill('side', box.color));
91039
- const endFill = resolveTargetedFace(seriesData, box, 'end', depth, resolveUntargetedBarFaceFill('end', box.color));
91040
- return { side: sideFill, end: endFill, bottom: colorFace(box.color) };
91041
- }
91042
- /** `true` (and the shared colour) when a resolved {@link BarBoxFaceFills} is uniform across all six faces. */
91043
- function uniformBoxColor(fills) {
91044
- return allSameColor([fills.posX, fills.negX, fills.posY, fills.negY, fills.posZ, fills.negZ]);
91045
- }
91046
- /** `true` (and the shared colour) when a resolved {@link BarRoundFaceFills} is uniform across all three material groups. */
91047
- function uniformRoundColor(fills) {
91048
- return allSameColor([fills.side, fills.end, fills.bottom]);
91049
- }
91050
-
91051
- /** Create a texture manager backed by `three.TextureLoader`, scoped to one mounted scene. */
91052
- function createBarChart3DTextureManager(three) {
91053
- const loader = new three.TextureLoader();
91054
- const cache = new Map();
91055
- const loaded = [];
91056
- return {
91057
- load(url) {
91058
- const cached = cache.get(url);
91059
- if (cached) {
91060
- return cached;
91061
- }
91062
- const pending = new Promise((resolve, reject) => {
91063
- loader.load(url, (texture) => {
91064
- loaded.push(texture);
91065
- resolve(texture);
91066
- }, undefined, reject);
91067
- });
91068
- cache.set(url, pending);
91069
- return pending;
91070
- },
91071
- disposeAll() {
91072
- for (const texture of loaded) {
91073
- texture.dispose();
91074
- }
91075
- loaded.length = 0;
91076
- cache.clear();
91077
- },
91078
- };
91079
- }
91080
- const FACE_ORDER = [
91081
- 'posX',
91082
- 'negX',
91083
- 'posY',
91084
- 'negY',
91085
- 'posZ',
91086
- 'negZ',
91087
- ];
91088
- function buildColorMaterial(three, color) {
91089
- return new three.MeshPhongMaterial({ color, shininess: 30 });
91090
- }
91091
- /**
91092
- * Build one `BoxGeometry` face's material. A picture face mounts a plain
91093
- * white `MeshPhongMaterial` (white so `map * color` paints the picture
91094
- * unmodified once it lands, matching the SVG `<pattern>` fill) and swaps its
91095
- * `map` in once the shared texture manager resolves the image, cloning the
91096
- * base texture so this face's own `repeat` never affects a sibling face or
91097
- * box sharing the same underlying image. `isDisposed` guards the async
91098
- * continuation against a box disposed (e.g. the chart unmounted, or the deck
91099
- * navigated away) before its texture finished loading.
91100
- */
91101
- function buildFaceMaterial(three, fill, textures, ownedTextures, isDisposed) {
91102
- if (fill.kind === 'color') {
91103
- return buildColorMaterial(three, fill.color);
91104
- }
91105
- const material = buildColorMaterial(three, '#ffffff');
91106
- textures
91107
- .load(fill.imageUrl)
91108
- .then((base) => {
91109
- const texture = base.clone();
91110
- texture.wrapS = three.RepeatWrapping;
91111
- texture.wrapT = three.RepeatWrapping;
91112
- texture.repeat.set(fill.repeatX, fill.repeatY);
91113
- texture.needsUpdate = true;
91114
- if (isDisposed()) {
91115
- texture.dispose();
91116
- return undefined;
91117
- }
91118
- ownedTextures.push(texture);
91119
- material.map = texture;
91120
- material.needsUpdate = true;
91121
- return undefined;
91122
- })
91123
- .catch(() => {
91124
- // Image failed to load/decode: keep the white fallback material
91125
- // rather than throwing out of an async continuation, matching every
91126
- // other optional-asset load in this renderer.
91127
- });
91128
- return material;
91129
- }
91130
- /** Build a `BarBoxMaterialResult` from an already-resolved fill array, one material per array entry (matching order = matching `THREE` `materialIndex`). */
91131
- function buildMaterialArrayResult(three, fills, textures) {
91132
- let disposed = false;
91133
- const ownedTextures = [];
91134
- const materials = fills.map((fill) => buildFaceMaterial(three, fill, textures, ownedTextures, () => disposed));
91135
- return {
91136
- material: materials,
91137
- dispose: () => {
91138
- disposed = true;
91139
- for (const material of materials) {
91140
- material.dispose();
91141
- }
91142
- for (const texture of ownedTextures) {
91143
- texture.dispose();
91144
- }
91145
- },
91146
- };
91147
- }
91148
- /**
91149
- * Build the material(s) for one bar3D mesh. `assets` is `undefined` for a
91150
- * scene mounted with no series/picture context (every pre-existing caller),
91151
- * which keeps the original single uniform `MeshPhongMaterial` behaviour
91152
- * unchanged. When `assets` is given: a `box`-shaped bar resolves
91153
- * {@link resolveBarBoxFaceFills} (up to a 6-entry `BoxGeometry` face-material
91154
- * array); a round shape (cylinder/cone/pyramid/coneToMax/pyramidToMax)
91155
- * resolves {@link resolveBarRoundFaceFills} (up to a 3-entry
91156
- * `CylinderGeometry`/`ConeGeometry` material-group array: lateral surface,
91157
- * top cap, bottom cap - see this module's `BarRoundFaceFills` import for the
91158
- * `materialIndex` order this array must match). Either shape returns a
91159
- * single uniform material instead when no picture targets any of its faces
91160
- * (the common no-picture case), same as before per-face targeting existed.
91161
- */
91162
- function buildBarBoxMaterial(three, box, assets) {
91163
- if (!assets) {
91164
- const material = buildColorMaterial(three, box.color);
91165
- return { material, dispose: () => material.dispose() };
91166
- }
91167
- const isRound = box.shape !== undefined && box.shape !== 'box';
91168
- if (isRound) {
91169
- const roundFills = resolveBarRoundFaceFills(box, assets.context.series);
91170
- const solid = uniformRoundColor(roundFills);
91171
- if (solid !== undefined) {
91172
- const material = buildColorMaterial(three, solid);
91173
- return { material, dispose: () => material.dispose() };
91174
- }
91175
- return buildMaterialArrayResult(three, [roundFills.side, roundFills.end, roundFills.bottom], assets.textures);
91176
- }
91177
- const fills = resolveBarBoxFaceFills(box, assets.context.series);
91178
- const solid = uniformBoxColor(fills);
91179
- if (solid !== undefined) {
91180
- const material = buildColorMaterial(three, solid);
91181
- return { material, dispose: () => material.dispose() };
91182
- }
91183
- return buildMaterialArrayResult(three, FACE_ORDER.map((face) => fills[face]), assets.textures);
91184
- }
91185
-
91186
- /**
91187
- * Per-shape THREE geometry construction for the interactive bar3D scene
91188
- * ({@link ./bar-chart-3d-scene.ts}), keyed by the resolved
91189
- * `c:bar3DChart/c:shape` (or per-series `c:ser/c:shape` override).
91190
- *
91191
- * Every geometry is a UNIT mesh (spans -0.5..0.5 on every axis, matching
91192
- * `THREE.BoxGeometry(1, 1, 1)`'s own extent), so the caller's existing
91193
- * `mesh.scale.set(...box.size)` keeps working unchanged regardless of shape:
91194
- * this module only decides WHICH geometry, never the box's world-space size
91195
- * or position.
91196
- *
91197
- * `coneToMax` / `pyramidToMax` render as a truncated cone/pyramid
91198
- * (`radiusTop < radiusBottom`) using
91199
- * {@link BarChart3DBox.coneToMaxTopRadiusFactor}, so a bar far below the
91200
- * value-axis maximum looks like a wide near-cylindrical frustum and a bar at
91201
- * the maximum comes to a full point, matching every box being read as a
91202
- * slice of one imaginary cone/pyramid whose apex sits at the axis max.
91203
- *
91204
- * @module bar-chart-3d-geometry
91205
- */
91206
- /** Radial segment count for a full-round shape (cylinder / cone). */
91207
- const ROUND_SEGMENTS = 32;
91208
- /** Radial segment count for a 4-sided shape (pyramid), approximating a square base. */
91209
- const PYRAMID_SEGMENTS = 4;
91210
- /**
91211
- * Build the unit-size geometry for one bar3D box, keyed by its resolved
91212
- * shape. `box` (or an unrecognised/absent shape) keeps the original
91213
- * `BoxGeometry(1, 1, 1)` behaviour.
91214
- */
91215
- function buildBar3DGeometry(three, shape, coneToMaxTopRadiusFactor) {
91216
- switch (shape) {
91217
- case 'cylinder':
91218
- return new three.CylinderGeometry(0.5, 0.5, 1, ROUND_SEGMENTS);
91219
- case 'cone':
91220
- return new three.ConeGeometry(0.5, 1, ROUND_SEGMENTS);
91221
- case 'pyramid':
91222
- return new three.ConeGeometry(0.5, 1, PYRAMID_SEGMENTS);
91223
- case 'coneToMax':
91224
- return new three.CylinderGeometry(0.5 * (coneToMaxTopRadiusFactor ?? 0), 0.5, 1, ROUND_SEGMENTS);
91225
- case 'pyramidToMax':
91226
- return new three.CylinderGeometry(0.5 * (coneToMaxTopRadiusFactor ?? 0), 0.5, 1, PYRAMID_SEGMENTS);
91227
- case 'box':
91228
- default:
91229
- return new three.BoxGeometry(1, 1, 1);
91230
- }
91231
- }
91232
- /**
91233
- * Build and add one mesh per box to `scene`, geometry chosen per box's
91234
- * resolved shape (see {@link buildBar3DGeometry}). Distinct shapes need
91235
- * distinct THREE geometry instances (cone/pyramid share a class but differ by
91236
- * radial segment count), so geometries are built per box rather than shared,
91237
- * and returned for the caller to dispose individually on teardown.
91238
- *
91239
- * `horizontal` (`c:barDir val="bar"`) rotates every mesh `-Math.PI / 2` about
91240
- * Z: `box.center` already sits in the horizontal world frame
91241
- * (`layoutBarChart3D`), but `box.size` stays in the UNROTATED local frame
91242
- * (width/height/depth as if the chart were still vertical), so a
91243
- * cylinder/cone/pyramid keeps its true round cross-section lying on its side
91244
- * instead of the ellipse a numeric width/height swap would produce. A plain
91245
- * box shape looks identical either way (rotating a rectangular prism 90
91246
- * degrees is the same as swapping two of its extents).
91247
- *
91248
- * `picture`, when given, resolves each box's `c:pictureOptions` picture-fill
91249
- * face targeting via {@link buildBarBoxMaterial}; omitted entirely, every box
91250
- * keeps its original single uniform `MeshPhongMaterial(box.color)`.
91251
- */
91252
- function buildBar3DMeshGroup(three, scene, boxes, horizontal = false, picture) {
91253
- const meshes = [];
91254
- const materials = [];
91255
- const materialDisposers = [];
91256
- const geometries = [];
91257
- for (const box of boxes) {
91258
- const geometry = buildBar3DGeometry(three, box.shape, box.coneToMaxTopRadiusFactor);
91259
- const { material, dispose } = buildBarBoxMaterial(three, box, picture);
91260
- const mesh = new three.Mesh(geometry, material);
91261
- mesh.position.set(...box.center);
91262
- mesh.scale.set(...box.size);
91263
- if (horizontal) {
91264
- mesh.rotation.z = -Math.PI / 2;
91265
- }
91266
- mesh.userData = {
91267
- seriesIndex: box.seriesIndex,
91268
- categoryIndex: box.categoryIndex,
91269
- value: box.value,
91270
- };
91271
- scene.add(mesh);
91272
- meshes.push(mesh);
91273
- materials.push(material);
91274
- materialDisposers.push(dispose);
91275
- geometries.push(geometry);
91276
- }
91277
- return { meshes, materials, geometries, materialDisposers };
91278
- }
91279
-
91280
89245
  /**
91281
89246
  * Pure raycast-hit -> hover-tooltip mapping for the interactive 3D bar chart
91282
89247
  * scene ({@link ./bar-chart-3d-scene.ts}).
@@ -91306,978 +89271,6 @@ function buildBarChart3DHoverTooltip(hit, data) {
91306
89271
  return buildMarkTooltip(data.seriesNames[hit.seriesIndex], data.categoryLabels[hit.categoryIndex], hit.value, data.numberFormats?.[hit.seriesIndex]);
91307
89272
  }
91308
89273
 
91309
- /** World-space axis index the value axis occupies: Y normally, X for a horizontal (`c:barDir val="bar"`) chart. */
91310
- function valueAxisIndex(horizontal) {
91311
- return horizontal ? 0 : 1;
91312
- }
91313
- /**
91314
- * Calibrate a value drag for the box a raycast hit landed on, or `null` when
91315
- * the chart's grouping has no per-box "spans from zero" shape, the matching
91316
- * box cannot be found, or the box has (near) zero extent along the value axis.
91317
- */
91318
- function calibrateBarChart3DDrag(boxes, hit, grouping, horizontal) {
91319
- if (grouping !== 'clustered') {
91320
- return null;
91321
- }
91322
- const box = boxes.find((b) => b.seriesIndex === hit.seriesIndex && b.categoryIndex === hit.categoryIndex);
91323
- if (!box) {
91324
- return null;
91325
- }
91326
- const axis = valueAxisIndex(horizontal);
91327
- const half = box.size[axis] / 2;
91328
- const center = box.center[axis];
91329
- const lo = center - half;
91330
- const hi = center + half;
91331
- // One of the two faces sits at world value-zero (see module doc); the
91332
- // closer-to-zero extreme is that face, the other is the box's own value.
91333
- const zeroIsLo = Math.abs(lo) <= Math.abs(hi);
91334
- const zeroCoord = zeroIsLo ? lo : hi;
91335
- const valueCoord = zeroIsLo ? hi : lo;
91336
- if (Math.abs(valueCoord - zeroCoord) < 1e-9) {
91337
- return null;
91338
- }
91339
- const at = (coord) => axis === 0 ? [coord, box.center[1], box.center[2]] : [box.center[0], coord, box.center[2]];
91340
- return {
91341
- worldAtValue0: at(zeroCoord),
91342
- value0: 0,
91343
- worldAtValue1: at(valueCoord),
91344
- value1: hit.value,
91345
- };
91346
- }
91347
-
91348
- /**
91349
- * `chart-3d-mesh-highlight`: the one-mesh-per-mark selected-highlight loop
91350
- * shared by bar3D, line3D, area3D, and pie3D (every interactive 3D chart
91351
- * scene EXCEPT surface3D, whose grid is a single mesh with no per-cell
91352
- * material to isolate - it highlights its selected vertex with a small
91353
- * marker mesh instead, see `surface-chart-3d-interaction-wiring.ts`'s
91354
- * `createSurfaceHighlightMarker`).
91355
- *
91356
- * Each of those four scenes tags every mark's own mesh material with its
91357
- * (series, point) in `userData` already (for hover/click hit-testing); this
91358
- * module just walks that same list and toggles `material.emissive` on the
91359
- * mark matching the current selection, clearing every other mark's.
91360
- *
91361
- * A bar3D box with a `c:pictureOptions` picture fill mounts a per-face
91362
- * `THREE.Material[]` array rather than one material (see
91363
- * `bar-chart-3d-materials.ts`), so `material` here also accepts a readonly
91364
- * array: every material in it gets the same emissive toggle, highlighting
91365
- * (or clearing) the whole box regardless of how many materials its mesh
91366
- * carries.
91367
- *
91368
- * @module chart-3d-mesh-highlight
91369
- */
91370
- const CLEAR_EMISSIVE = '#000000';
91371
- /**
91372
- * Apply (or clear) the selected-mark emissive highlight across a scene's
91373
- * mark meshes. `entries` pairs each mesh's own (series, point) reference with
91374
- * its material (or, for a multi-material mesh, every material in its
91375
- * per-face array); every entry not matching `part` is reset to no highlight.
91376
- */
91377
- function applyChart3DMeshHighlight(entries, part) {
91378
- for (const { mark, material } of entries) {
91379
- const selected = chart3DMarkMatchesPart(mark, part);
91380
- const materials = Array.isArray(material) ? material : [material];
91381
- for (const one of materials) {
91382
- one.emissive.set(selected ? CHART_3D_SELECTED_EMISSIVE : CLEAR_EMISSIVE);
91383
- one.emissiveIntensity = selected ? CHART_3D_SELECTED_EMISSIVE_INTENSITY : 0;
91384
- }
91385
- }
91386
- }
91387
-
91388
- /** Attach click-to-select / drag-to-value handling to a mounted bar3D scene. */
91389
- function attachBarChart3DInteraction(params) {
91390
- const highlightEntries = params.boxMeshes.map((mesh, i) => {
91391
- const hit = mesh.userData;
91392
- return {
91393
- mark: { seriesIndex: hit.seriesIndex, pointIndex: hit.categoryIndex },
91394
- material: params.boxMaterials[i],
91395
- };
91396
- });
91397
- return attachChart3DPointerInteraction({
91398
- three: params.three,
91399
- canvas: params.canvas,
91400
- camera: params.camera,
91401
- width: params.width,
91402
- height: params.height,
91403
- meshes: params.boxMeshes,
91404
- resolveHit: (intersection) => {
91405
- const hit = intersection.object.userData;
91406
- return chart3DHitToPartRef(hit ? { seriesIndex: hit.seriesIndex, pointIndex: hit.categoryIndex } : null);
91407
- },
91408
- calibrateDrag: (intersection) => {
91409
- const hit = intersection.object.userData;
91410
- return calibrateBarChart3DDrag(params.boxes, hit, params.grouping, params.horizontal);
91411
- },
91412
- applyHighlight: (part) => applyChart3DMeshHighlight(highlightEntries, part),
91413
- setControlsEnabled: (enabled) => {
91414
- params.controls.enabled = enabled;
91415
- },
91416
- onSelect: params.interaction?.onSelect,
91417
- onValueDragPreview: params.interaction?.onValueDragPreview,
91418
- onValueDragCommit: params.interaction?.onValueDragCommit,
91419
- });
91420
- }
91421
-
91422
- /**
91423
- * three.js 3D bar-chart scene for `<pptx-three-view>` (framework-agnostic).
91424
- *
91425
- * Builds an interactive `bar3D` chart as a `<pptx-three-view>` scene
91426
- * (hosted on the shared renderer; see {@link ./chart-3d-hosted-stage.ts}): builds
91427
- * one mesh per data point (clustered: each series its own depth plane;
91428
- * stacked/percentStacked: coplanar, stacked vertically - see
91429
- * {@link ./bar-chart-3d-data.ts}; geometry per box's resolved `c:shape` - see
91430
- * {@link ./bar-chart-3d-geometry.ts}), a grid floor, authored `c:floor`/
91431
- * `c:sideWall`/`c:backWall` panels and a perspective camera driven by
91432
- * `c:view3D`. Raycasts pointer moves against the boxes to
91433
- * set a native hover tooltip on the canvas element (see
91434
- * {@link ./bar-chart-3d-hit-test.ts}), matching every other chart kind's
91435
- * SVG-`<title>` hover tooltip.
91436
- *
91437
- * A horizontal 3-D Bar (`options.horizontal`) mounts the SAME scene: boxes
91438
- * arrive already remapped into the horizontal frame, so this module only
91439
- * rotates each mesh to match, builds the label overlay in that frame, and
91440
- * asks {@link computeCartesianCameraPlacement} for the matching (also
91441
- * transposed) camera placement so the view frames the actual rendered
91442
- * extents instead of the untransposed vertical bounding box.
91443
- *
91444
- * @module bar-chart-3d-scene
91445
- */
91446
- /** Build the hosted bar3D scene. */
91447
- function createBarChart3DScene(ctx, options) {
91448
- const placement = computeCartesianCameraPlacement(options.cols, options.rows, options.view3D, options.horizontal);
91449
- const stage = createHostedChart3DStage(ctx, placement, { minDistance: 0.5, maxDistance: 30 });
91450
- const { three, scene, camera, canvas } = stage;
91451
- // Grid floor, sized to this module's OWN grid extent (depthPercent-scaled).
91452
- const extent = computeCartesianGridExtent(options.cols, options.rows, options.view3D?.depthPercent);
91453
- // A horizontal bar3D chart's boxes sit at various world-Y heights (the
91454
- // remapped category axis), so the floor also covers that vertical spread.
91455
- const floorSize = Math.max(extent.gridWidth, extent.gridDepth, MAX_VALUE_HEIGHT * 2) * 1.2;
91456
- const gridFloor = new three.GridHelper(floorSize, Math.max(options.cols, options.rows), 0xcccccc, 0xe8e8e8);
91457
- gridFloor.position.y = -0.02;
91458
- scene.add(gridFloor);
91459
- // Authored c:floor / c:sideWall / c:backWall backdrop panels. Passes this
91460
- // module's OWN grid extent, never letting buildSurfaceWallMeshes recompute
91461
- // a mismatched one. KNOWN APPROXIMATION: unlike the boxes and labels, these
91462
- // panels are not reoriented for `options.horizontal` (they still paint the
91463
- // vertical category=X, value=Y frame's panel positions).
91464
- const walls = options.wallColors
91465
- ? buildSurfaceWallMeshes(three, options.cols, options.rows, MAX_VALUE_HEIGHT, options.wallColors, extent)
91466
- : null;
91467
- for (const mesh of walls?.meshes ?? []) {
91468
- scene.add(mesh);
91469
- }
91470
- // One mesh per data point, geometry chosen per box's resolved bar3D shape
91471
- // (see {@link ./bar-chart-3d-geometry.ts}). `c:pictureOptions` picture-fill
91472
- // textures share one texture manager across every box; each finished load
91473
- // asks the host for a frame, since nothing else would redraw the view.
91474
- const baseTextures = createBarChart3DTextureManager(three);
91475
- const textures = {
91476
- load: (url) => baseTextures.load(url).then((texture) => {
91477
- setTimeout(ctx.requestRender, 0);
91478
- return texture;
91479
- }),
91480
- disposeAll: () => baseTextures.disposeAll(),
91481
- };
91482
- const { meshes: boxMeshes, materials: boxMaterials, geometries: boxGeometries, materialDisposers: boxMaterialDisposers, } = buildBar3DMeshGroup(three, scene, options.boxes, options.horizontal, options.picture ? { context: options.picture, textures } : undefined);
91483
- // Raycast-based hover tooltip: each box mesh carries its own (series,
91484
- // category, value) in `userData`, so a hit reports the cell directly.
91485
- const hoverTooltip = attachChart3DHoverTooltip({
91486
- three,
91487
- canvas,
91488
- camera,
91489
- meshes: boxMeshes,
91490
- buildTooltip: (intersection) => buildBarChart3DHoverTooltip(intersection?.object.userData, {
91491
- categoryLabels: options.categoryLabels,
91492
- seriesNames: options.seriesNames,
91493
- numberFormats: options.numberFormats,
91494
- }),
91495
- });
91496
- // Click-to-select + drag-to-value: raycasts the SAME box meshes the hover
91497
- // tooltip above uses (see bar-chart-3d-interaction-wiring.ts).
91498
- const interaction = hostedChart3DInteraction(ctx);
91499
- const pointerInteraction = attachBarChart3DInteraction({
91500
- three,
91501
- canvas,
91502
- camera,
91503
- controls: stage.controls,
91504
- width: stage.width,
91505
- height: stage.height,
91506
- boxMeshes,
91507
- boxMaterials: boxMaterials,
91508
- boxes: options.boxes,
91509
- grouping: options.grouping,
91510
- horizontal: Boolean(options.horizontal),
91511
- interaction,
91512
- });
91513
- // Axis-label DOM overlay, re-projected to screen after each frame.
91514
- const labels = buildCartesianChart3DLabels(options.cols, options.rows, options.categoryLabels, options.seriesNames, options.view3D?.depthPercent, undefined, undefined, options.horizontal);
91515
- const labelProjector = createChart3DLabelProjector(three, ctx.document, labels);
91516
- labelProjector.applyTextStyle(options.textStyle);
91517
- ctx.overlay.appendChild(labelProjector.layer);
91518
- return finishHostedChart3DScene(ctx, stage, {
91519
- afterRender: (cam, width, height) => labelProjector.update(cam, width, height),
91520
- resize: (width, height) => pointerInteraction.updateSize(width, height),
91521
- setSelectedPart: (part) => pointerInteraction.setSelectedPart(part),
91522
- setTextStyle: (style) => labelProjector.applyTextStyle(style),
91523
- dispose() {
91524
- hoverTooltip.dispose();
91525
- pointerInteraction.dispose();
91526
- for (const g of boxGeometries) {
91527
- g.dispose();
91528
- }
91529
- for (const disposeMaterial of boxMaterialDisposers) {
91530
- disposeMaterial();
91531
- }
91532
- textures.disposeAll();
91533
- gridFloor.dispose();
91534
- walls?.dispose();
91535
- labelProjector.layer.remove();
91536
- },
91537
- }, interaction);
91538
- }
91539
-
91540
- /**
91541
- * Pure raycast-hit -> hover-tooltip mapping shared by the interactive
91542
- * line3D/area3D cartesian chart scenes ({@link ./line-chart-3d-scene.ts},
91543
- * {@link ./area-chart-3d-scene.ts}).
91544
- *
91545
- * Each series path's data-point marker mesh tags its Object3D `userData` with
91546
- * the (series, category, value) triple a raycast hit reports directly,
91547
- * exactly like a bar3D box mesh does (see `bar-chart-3d-hit-test.ts`, the
91548
- * original of this pattern). This module only builds the tooltip text from
91549
- * those indices, kept three-agnostic so it is unit-testable without mocking
91550
- * WebGL.
91551
- *
91552
- * @module cartesian-chart-3d-hit-test
91553
- */
91554
- /**
91555
- * Build the hover-tooltip text for a raycast hit on a line3D/area3D data-point
91556
- * marker, or `undefined` when there is no hit.
91557
- *
91558
- * Mirrors `buildMarkTooltip`'s "<series>, <category>: <value>" text exactly,
91559
- * so a line3D/area3D marker's tooltip reads identically to the flat SVG
91560
- * line/area renderer's, and to `buildBarChart3DHoverTooltip`'s.
91561
- */
91562
- function buildCartesianChart3DHoverTooltip(hit, data) {
91563
- if (!hit) {
91564
- return undefined;
91565
- }
91566
- return buildMarkTooltip(data.seriesNames[hit.seriesIndex], data.categoryLabels[hit.categoryIndex], hit.value, data.numberFormats?.[hit.seriesIndex]);
91567
- }
91568
-
91569
- /**
91570
- * Calibrate a value drag for one line3D/area3D marker, given its world
91571
- * position (value axis = world Y) and its own data value. Returns `null` when
91572
- * the value is (near) zero: the marker then sits on the floor itself, so the
91573
- * two calibration points would coincide and no direction could be recovered.
91574
- */
91575
- function calibrateCartesianChart3DDrag(markerPosition, value) {
91576
- if (Math.abs(value) < 1e-9) {
91577
- return null;
91578
- }
91579
- return {
91580
- worldAtValue0: [markerPosition[0], 0, markerPosition[2]],
91581
- value0: 0,
91582
- worldAtValue1: markerPosition,
91583
- value1: value,
91584
- };
91585
- }
91586
-
91587
- /** Attach click-to-select / drag-to-value handling to a mounted line3D/area3D scene. */
91588
- function attachCartesianChart3DInteraction(params) {
91589
- const highlightEntries = params.markerMeshes.map((mesh, i) => {
91590
- const hit = mesh.userData;
91591
- return {
91592
- mark: { seriesIndex: hit.seriesIndex, pointIndex: hit.categoryIndex },
91593
- material: params.markerMaterials[i],
91594
- };
91595
- });
91596
- return attachChart3DPointerInteraction({
91597
- three: params.three,
91598
- canvas: params.canvas,
91599
- camera: params.camera,
91600
- width: params.width,
91601
- height: params.height,
91602
- meshes: params.markerMeshes,
91603
- resolveHit: (intersection) => {
91604
- const hit = intersection.object.userData;
91605
- return chart3DHitToPartRef(hit ? { seriesIndex: hit.seriesIndex, pointIndex: hit.categoryIndex } : null);
91606
- },
91607
- calibrateDrag: (intersection) => {
91608
- const hit = intersection.object.userData;
91609
- const vertex = params.series[hit.seriesIndex]?.vertices.find((v) => v.categoryIndex === hit.categoryIndex);
91610
- return vertex ? calibrateCartesianChart3DDrag(vertex.position, hit.value) : null;
91611
- },
91612
- applyHighlight: (part) => applyChart3DMeshHighlight(highlightEntries, part),
91613
- setControlsEnabled: (enabled) => {
91614
- params.controls.enabled = enabled;
91615
- },
91616
- onSelect: params.interaction?.onSelect,
91617
- onValueDragPreview: params.interaction?.onValueDragPreview,
91618
- onValueDragCommit: params.interaction?.onValueDragCommit,
91619
- });
91620
- }
91621
-
91622
- /**
91623
- * three.js 3D line-chart scene for `<pptx-three-view>` (framework-agnostic).
91624
- *
91625
- * Builds an interactive `line3D` chart as a `<pptx-three-view>` scene: one
91626
- * `THREE.TubeGeometry` per series (swept along a `CatmullRomCurve3` through
91627
- * its category/value points, own depth "Z" plane, see
91628
- * {@link ./cartesian-line-chart-3d-layout.ts}), a small sphere marker per data
91629
- * point (hover + click/drag hit target), grid floor, authored wall panels, a
91630
- * `c:view3D` camera and OrbitControls, on the shared stage from
91631
- * {@link ./chart-3d-hosted-stage.ts}.
91632
- *
91633
- * @module line-chart-3d-scene
91634
- */
91635
- /** World-space radius of the tube swept along a series' path. */
91636
- const TUBE_RADIUS$1 = 0.025;
91637
- /** World-space radius of each per-vertex hover marker. */
91638
- const MARKER_RADIUS$1 = 0.045;
91639
- /** Build the hosted LineChart3D scene. */
91640
- function createLineChart3DScene(ctx, options) {
91641
- const placement = computeCartesianCameraPlacement(options.cols, options.rows, options.view3D);
91642
- const stage = createHostedChart3DStage(ctx, placement, { minDistance: 0.5, maxDistance: 30 }, 0.6);
91643
- const { three, scene, camera, canvas, controls, width, height } = stage;
91644
- const interaction = hostedChart3DInteraction(ctx);
91645
- const extent = computeCartesianGridExtent(options.cols, options.rows, options.view3D?.depthPercent);
91646
- const floorSize = Math.max(extent.gridWidth, extent.gridDepth) * 1.2;
91647
- const gridFloor = new three.GridHelper(floorSize, Math.max(options.cols, options.rows), 0xcccccc, 0xe8e8e8);
91648
- gridFloor.position.y = -0.02;
91649
- scene.add(gridFloor);
91650
- const walls = options.wallColors
91651
- ? buildSurfaceWallMeshes(three, options.cols, options.rows, MAX_VALUE_HEIGHT, options.wallColors, extent)
91652
- : null;
91653
- for (const mesh of walls?.meshes ?? []) {
91654
- scene.add(mesh);
91655
- }
91656
- // One tube per series; one shared sphere geometry for the hover markers.
91657
- const markerGeometry = new three.SphereGeometry(MARKER_RADIUS$1, 8, 6);
91658
- const tubeGeometries = [];
91659
- const tubeMaterials = [];
91660
- const markerMeshes = [];
91661
- const markerMaterials = [];
91662
- for (const path of options.series) {
91663
- if (path.vertices.length >= 2) {
91664
- const curve = new three.CatmullRomCurve3(path.vertices.map((v) => new three.Vector3(...v.position)));
91665
- const tubeGeometry = new three.TubeGeometry(curve, Math.max(path.vertices.length * 8, 16), TUBE_RADIUS$1, 8, false);
91666
- const tubeMaterial = new three.MeshPhongMaterial({ color: path.color, shininess: 30 });
91667
- const tube = new three.Mesh(tubeGeometry, tubeMaterial);
91668
- scene.add(tube);
91669
- tubeGeometries.push(tubeGeometry);
91670
- tubeMaterials.push(tubeMaterial);
91671
- }
91672
- for (const v of path.vertices) {
91673
- const markerMaterial = new three.MeshPhongMaterial({ color: path.color, shininess: 30 });
91674
- const marker = new three.Mesh(markerGeometry, markerMaterial);
91675
- marker.position.set(...v.position);
91676
- marker.userData = {
91677
- seriesIndex: v.seriesIndex,
91678
- categoryIndex: v.categoryIndex,
91679
- value: v.value,
91680
- };
91681
- scene.add(marker);
91682
- markerMeshes.push(marker);
91683
- markerMaterials.push(markerMaterial);
91684
- }
91685
- }
91686
- // Raycast-based hover tooltip against the per-vertex marker meshes.
91687
- const tooltipData = {
91688
- categoryLabels: options.categoryLabels,
91689
- seriesNames: options.seriesNames,
91690
- numberFormats: options.numberFormats,
91691
- };
91692
- const hoverTooltip = attachChart3DHoverTooltip({
91693
- three,
91694
- canvas,
91695
- camera,
91696
- meshes: markerMeshes,
91697
- buildTooltip: (intersection) => buildCartesianChart3DHoverTooltip(intersection?.object.userData, tooltipData),
91698
- });
91699
- // Click-to-select + drag-to-value (see cartesian-chart-3d-interaction-wiring.ts).
91700
- const pointerInteraction = attachCartesianChart3DInteraction({
91701
- three,
91702
- canvas,
91703
- camera,
91704
- controls,
91705
- width,
91706
- height,
91707
- markerMeshes,
91708
- markerMaterials: markerMaterials,
91709
- series: options.series,
91710
- interaction,
91711
- });
91712
- const labels = buildCartesianChart3DLabels(options.cols, options.rows, options.categoryLabels, options.seriesNames, options.view3D?.depthPercent);
91713
- const labelProjector = createChart3DLabelProjector(three, ctx.document, labels);
91714
- labelProjector.applyTextStyle(options.textStyle);
91715
- ctx.overlay.appendChild(labelProjector.layer);
91716
- return finishHostedChart3DScene(ctx, stage, {
91717
- afterRender: (cam, w, h) => labelProjector.update(cam, w, h),
91718
- resize: (w, h) => pointerInteraction.updateSize(w, h),
91719
- setSelectedPart: (part) => pointerInteraction.setSelectedPart(part),
91720
- setTextStyle: (style) => labelProjector.applyTextStyle(style),
91721
- dispose() {
91722
- hoverTooltip.dispose();
91723
- pointerInteraction.dispose();
91724
- markerGeometry.dispose();
91725
- for (const g of tubeGeometries) {
91726
- g.dispose();
91727
- }
91728
- for (const m of [...tubeMaterials, ...markerMaterials]) {
91729
- m.dispose();
91730
- }
91731
- gridFloor.dispose();
91732
- walls?.dispose();
91733
- labelProjector.layer.remove();
91734
- },
91735
- }, interaction);
91736
- }
91737
-
91738
- /** Build a ribbon `BufferGeometry` for one series' area fill, or `null` when it has < 2 vertices. */
91739
- function buildAreaRibbonGeometry(three, path) {
91740
- const triangles = buildAreaRibbonTriangles(path);
91741
- if (triangles.length === 0) {
91742
- return null;
91743
- }
91744
- const geometry = new three.BufferGeometry();
91745
- geometry.setAttribute('position', new three.Float32BufferAttribute(triangles, 3));
91746
- geometry.computeVertexNormals();
91747
- return geometry;
91748
- }
91749
-
91750
- /**
91751
- * three.js 3D area-chart scene for `<pptx-three-view>` (framework-agnostic).
91752
- *
91753
- * Identical to {@link ./line-chart-3d-scene.ts}, EXCEPT it additionally fills
91754
- * a translucent ribbon from each series' path down to its baseline (value =
91755
- * 0), via {@link ./area-chart-3d-ribbon-geometry.ts}, matching PowerPoint's
91756
- * real 3-D Area chart.
91757
- *
91758
- * @module area-chart-3d-scene
91759
- */
91760
- /** World-space radius of the tube swept along a series' path (drawn atop the ribbon). */
91761
- const TUBE_RADIUS = 0.02;
91762
- /** World-space radius of each per-vertex hover marker. */
91763
- const MARKER_RADIUS = 0.045;
91764
- /** Ribbon fill opacity, translucent so overlapping series planes stay legible. */
91765
- const RIBBON_OPACITY = 0.75;
91766
- /** Build the hosted AreaChart3D scene. */
91767
- function createAreaChart3DScene(ctx, options) {
91768
- const placement = computeCartesianCameraPlacement(options.cols, options.rows, options.view3D);
91769
- const stage = createHostedChart3DStage(ctx, placement, { minDistance: 0.5, maxDistance: 30 }, 0.6);
91770
- const { three, scene, camera, canvas, controls, width, height } = stage;
91771
- const interaction = hostedChart3DInteraction(ctx);
91772
- const extent = computeCartesianGridExtent(options.cols, options.rows, options.view3D?.depthPercent);
91773
- const floorSize = Math.max(extent.gridWidth, extent.gridDepth) * 1.2;
91774
- const gridSegs = Math.max(options.cols, options.rows);
91775
- const gridFloor = new three.GridHelper(floorSize, gridSegs, 0xcccccc, 0xe8e8e8);
91776
- gridFloor.position.y = -0.02;
91777
- scene.add(gridFloor);
91778
- const walls = options.wallColors
91779
- ? buildSurfaceWallMeshes(three, options.cols, options.rows, MAX_VALUE_HEIGHT, options.wallColors, extent)
91780
- : null;
91781
- for (const mesh of walls?.meshes ?? []) {
91782
- scene.add(mesh);
91783
- }
91784
- const markerGeometry = new three.SphereGeometry(MARKER_RADIUS, 8, 6);
91785
- const tubeGeometries = [];
91786
- const ribbonGeometries = [];
91787
- const otherMaterials = [];
91788
- const markerMeshes = [];
91789
- const markerMaterials = [];
91790
- for (const path of options.series) {
91791
- const ribbonGeometry = buildAreaRibbonGeometry(three, path);
91792
- if (ribbonGeometry) {
91793
- const ribbonMaterial = new three.MeshPhongMaterial({
91794
- color: path.color,
91795
- side: three.DoubleSide,
91796
- transparent: true,
91797
- opacity: RIBBON_OPACITY,
91798
- shininess: 10,
91799
- });
91800
- scene.add(new three.Mesh(ribbonGeometry, ribbonMaterial));
91801
- ribbonGeometries.push(ribbonGeometry);
91802
- otherMaterials.push(ribbonMaterial);
91803
- }
91804
- if (path.vertices.length >= 2) {
91805
- const curve = new three.CatmullRomCurve3(path.vertices.map((v) => new three.Vector3(...v.position)));
91806
- const tubeGeometry = new three.TubeGeometry(curve, Math.max(path.vertices.length * 8, 16), TUBE_RADIUS, 8, false);
91807
- const tubeMaterial = new three.MeshPhongMaterial({ color: path.color, shininess: 30 });
91808
- scene.add(new three.Mesh(tubeGeometry, tubeMaterial));
91809
- tubeGeometries.push(tubeGeometry);
91810
- otherMaterials.push(tubeMaterial);
91811
- }
91812
- for (const v of path.vertices) {
91813
- const markerMaterial = new three.MeshPhongMaterial({ color: path.color, shininess: 30 });
91814
- const marker = new three.Mesh(markerGeometry, markerMaterial);
91815
- marker.position.set(...v.position);
91816
- marker.userData = {
91817
- seriesIndex: v.seriesIndex,
91818
- categoryIndex: v.categoryIndex,
91819
- value: v.value,
91820
- };
91821
- scene.add(marker);
91822
- markerMeshes.push(marker);
91823
- markerMaterials.push(markerMaterial);
91824
- otherMaterials.push(markerMaterial);
91825
- }
91826
- }
91827
- const tooltipData = {
91828
- categoryLabels: options.categoryLabels,
91829
- seriesNames: options.seriesNames,
91830
- numberFormats: options.numberFormats,
91831
- };
91832
- const hoverTooltip = attachChart3DHoverTooltip({
91833
- three,
91834
- canvas,
91835
- camera,
91836
- meshes: markerMeshes,
91837
- buildTooltip: (intersection) => buildCartesianChart3DHoverTooltip(intersection?.object.userData, tooltipData),
91838
- });
91839
- // Click-to-select + drag-to-value (see cartesian-chart-3d-interaction-wiring.ts).
91840
- const pointerInteraction = attachCartesianChart3DInteraction({
91841
- three,
91842
- canvas,
91843
- camera,
91844
- controls,
91845
- width,
91846
- height,
91847
- markerMeshes,
91848
- markerMaterials: markerMaterials,
91849
- series: options.series,
91850
- interaction,
91851
- });
91852
- const labels = buildCartesianChart3DLabels(options.cols, options.rows, options.categoryLabels, options.seriesNames, options.view3D?.depthPercent);
91853
- const labelProjector = createChart3DLabelProjector(three, ctx.document, labels);
91854
- labelProjector.applyTextStyle(options.textStyle);
91855
- ctx.overlay.appendChild(labelProjector.layer);
91856
- return finishHostedChart3DScene(ctx, stage, {
91857
- afterRender: (cam, w, h) => labelProjector.update(cam, w, h),
91858
- resize: (w, h) => pointerInteraction.updateSize(w, h),
91859
- setSelectedPart: (part) => pointerInteraction.setSelectedPart(part),
91860
- setTextStyle: (style) => labelProjector.applyTextStyle(style),
91861
- dispose() {
91862
- hoverTooltip.dispose();
91863
- pointerInteraction.dispose();
91864
- markerGeometry.dispose();
91865
- for (const g of [...tubeGeometries, ...ribbonGeometries]) {
91866
- g.dispose();
91867
- }
91868
- for (const m of otherMaterials) {
91869
- m.dispose();
91870
- }
91871
- gridFloor.dispose();
91872
- walls?.dispose();
91873
- labelProjector.layer.remove();
91874
- },
91875
- }, interaction);
91876
- }
91877
-
91878
- /**
91879
- * Pure raycast-hit -> hover-tooltip mapping for the interactive 3D pie chart
91880
- * scene ({@link ./pie-chart-3d-scene.ts}).
91881
- *
91882
- * Like `bar3D` (each data point is its own box mesh), every pie3D wedge is
91883
- * its OWN `CylinderGeometry` mesh, so the scene controller tags every wedge's
91884
- * `userData` with its point index directly and a `Raycaster.intersectObjects`
91885
- * hit reports it straight from the hit object - no face-index arithmetic
91886
- * needed. This module only builds the tooltip text from that index, kept
91887
- * three-agnostic so it is unit-testable without mocking WebGL. Mirrors
91888
- * {@link ./bar-chart-3d-hit-test.ts} exactly.
91889
- *
91890
- * @module pie-chart-3d-hit-test
91891
- */
91892
- /**
91893
- * Build the hover-tooltip text for a raycast hit on a pie3D wedge mesh, or
91894
- * `undefined` when there is no hit.
91895
- *
91896
- * Mirrors `buildMarkTooltip`'s "<series>, <category>: <value>" text exactly,
91897
- * so a pie3D wedge's tooltip reads identically to the flat SVG pie
91898
- * renderer's.
91899
- */
91900
- function buildPieChart3DHoverTooltip(hit, data) {
91901
- if (!hit) {
91902
- return undefined;
91903
- }
91904
- return buildMarkTooltip(data.seriesName, data.categoryLabels[hit.pointIndex], hit.value, data.numberFormat);
91905
- }
91906
-
91907
- /**
91908
- * Drag-to-value math for an interactive 3D `pie3D` wedge, three-agnostic so it
91909
- * is unit-testable without mocking WebGL.
91910
- *
91911
- * A pie3D wedge has no single visible world axis a screen-space pointer delta
91912
- * can be projected onto the way a bar3D box's height or a surface3D vertex's
91913
- * height can (`chart-3d-pointer-interaction.ts`'s `Chart3DDragCalibrationInput`
91914
- * model): like the flat SVG pie/doughnut, a wedge's value is a SHARE of the
91915
- * series total, changed by sweeping its trailing edge around the pie's centre.
91916
- * `chart-interaction-pie.ts`'s `resolvePieSliceShareValue` already holds that
91917
- * exact renormalisation formula (dragging the flat pie), so this module only
91918
- * supplies the 3D-specific half: turning a raycast hit on the pie's own plane
91919
- * into an angle in the SAME convention `computePieChart3DSliceAngles` uses for
91920
- * `startAngle`/`thetaStart`.
91921
- *
91922
- * `THREE.CylinderGeometry` places a vertex at parametric angle `theta` at
91923
- * local `(x, z) = (radius*sin(theta), radius*cos(theta))` (its own `generateTorso`
91924
- * step), so recovering `theta` from a world `(x, z)` hit point uses
91925
- * `Math.atan2(x, z)`, NOT the `atan2(y, x)` convention the flat 2D SVG pie's
91926
- * screen-space drag uses.
91927
- *
91928
- * @module pie-chart-3d-drag
91929
- */
91930
- /**
91931
- * Resolve the drag geometry for the wedge at `pointIndex`, or `null` when it
91932
- * cannot be found in `wedges` or `pointIndex` is out of range in `values`.
91933
- */
91934
- function buildPieChart3DDragGeometry(wedges, values, pointIndex) {
91935
- if (pointIndex < 0 || pointIndex >= values.length) {
91936
- return null;
91937
- }
91938
- const wedge = wedges.find((w) => w.pointIndex === pointIndex);
91939
- if (!wedge) {
91940
- return null;
91941
- }
91942
- return { values, pointIndex, leadingAngle: wedge.startAngle };
91943
- }
91944
- /**
91945
- * Convert a world-space hit point on the pie's own plane (already relative to
91946
- * the pie's centre, i.e. the `x`/`z` of a raycast against the invisible disc
91947
- * `pie-chart-3d-interaction-wiring.ts` raycasts, not a wedge mesh's own,
91948
- * possibly `c:explosion`-offset, local space) into the angle convention
91949
- * `computePieChart3DSliceAngles`'s `startAngle`/`thetaStart` use.
91950
- */
91951
- function pieChart3DPointerAngle(x, z) {
91952
- return Math.atan2(x, z);
91953
- }
91954
- /**
91955
- * New value for the dragged wedge given the pointer's current angle on the
91956
- * pie's plane (see {@link pieChart3DPointerAngle}). Delegates the actual
91957
- * renormalisation to the flat 2D pie's own `resolvePieSliceShareValue`, so a
91958
- * pie3D wedge drag renormalises every other (unchanged) slice's value exactly
91959
- * like its flat SVG counterpart.
91960
- */
91961
- function resolvePieChart3DDragValue(geometry, pointerAngle) {
91962
- return resolvePieSliceShareValue(pointerAngle, geometry.leadingAngle, geometry.values, geometry.pointIndex);
91963
- }
91964
-
91965
- /**
91966
- * Attach click-to-select / drag-to-value handling to a mounted pie3D scene.
91967
- * Unlike {@link ../chart-3d-pointer-interaction.ts}'s generic
91968
- * `attachChartPointerInteraction`, this raycasts the wedge meshes only to
91969
- * decide WHICH wedge a press landed on; the drag itself samples an invisible
91970
- * disc (the pie's own XZ plane through its centre) on every pointer move.
91971
- */
91972
- function attachPieChart3DInteraction(params) {
91973
- const raycaster = new params.three.Raycaster();
91974
- const ndc = new params.three.Vector2();
91975
- // The pie's own plane: normal +Y, through the world origin (every wedge's
91976
- // unexploded local origin, per `computePieChart3DSliceAngles`).
91977
- const plane = new params.three.Plane(new params.three.Vector3(0, 1, 0), 0);
91978
- const planeHit = new params.three.Vector3();
91979
- let dragState = null;
91980
- const highlightEntries = params.wedgeMeshes.map((mesh, i) => {
91981
- const hit = mesh.userData;
91982
- return {
91983
- mark: { seriesIndex: 0, pointIndex: hit.pointIndex },
91984
- material: params.wedgeMaterials[i],
91985
- };
91986
- });
91987
- function setNdc(clientX, clientY) {
91988
- const rect = params.canvas.getBoundingClientRect();
91989
- if (rect.width <= 0 || rect.height <= 0) {
91990
- return false;
91991
- }
91992
- ndc.x = ((clientX - rect.left) / rect.width) * 2 - 1;
91993
- ndc.y = -((clientY - rect.top) / rect.height) * 2 + 1;
91994
- return true;
91995
- }
91996
- function raycastWedge(clientX, clientY) {
91997
- if (!setNdc(clientX, clientY)) {
91998
- return undefined;
91999
- }
92000
- raycaster.setFromCamera(ndc, params.camera);
92001
- return raycaster.intersectObjects(params.wedgeMeshes, false)[0];
92002
- }
92003
- /** The pointer's current angle on the pie's plane, or `null` when the ray is (near) parallel to it. */
92004
- function planeAngleAt(clientX, clientY) {
92005
- if (!setNdc(clientX, clientY)) {
92006
- return null;
92007
- }
92008
- raycaster.setFromCamera(ndc, params.camera);
92009
- const hit = raycaster.ray.intersectPlane(plane, planeHit);
92010
- return hit ? pieChart3DPointerAngle(hit.x, hit.z) : null;
92011
- }
92012
- function setSelectedPart(part) {
92013
- applyChart3DMeshHighlight(highlightEntries, part);
92014
- }
92015
- function travelBelowThreshold(clientX, clientY, state) {
92016
- const dx = clientX - state.startClientX;
92017
- const dy = clientY - state.startClientY;
92018
- return Math.hypot(dx, dy) < CHART_3D_DRAG_THRESHOLD_PX;
92019
- }
92020
- const onPointerDown = (event) => {
92021
- params.canvas.setPointerCapture?.(event.pointerId);
92022
- const hit = raycastWedge(event.clientX, event.clientY);
92023
- const hitData = hit?.object.userData;
92024
- const part = chart3DHitToPartRef(hitData ? { seriesIndex: 0, pointIndex: hitData.pointIndex } : null);
92025
- // Same armed gate as `chart-3d-pointer-interaction.ts` (see its module
92026
- // doc): a wedge press on an armed chart is the scene's own select /
92027
- // value-drag, never the stage's element move; on an un-armed chart it
92028
- // bubbles so the first click still selects the chart.
92029
- const armed = part !== null && isChartInteractionArmed(params.canvas);
92030
- if (armed) {
92031
- event.stopPropagation();
92032
- }
92033
- let geometry = null;
92034
- if (part && hitData && armed) {
92035
- const wedges = params.getWedges();
92036
- const wedge = wedges.find((w) => w.pointIndex === hitData.pointIndex);
92037
- if (wedge) {
92038
- geometry = {
92039
- values: wedges.map((w) => w.value),
92040
- pointIndex: hitData.pointIndex,
92041
- leadingAngle: wedge.startAngle,
92042
- };
92043
- }
92044
- }
92045
- dragState = {
92046
- part,
92047
- geometry,
92048
- moved: false,
92049
- startClientX: event.clientX,
92050
- startClientY: event.clientY,
92051
- lastAngle: null,
92052
- };
92053
- if (geometry) {
92054
- // Cancel the press too: see `chart-3d-pointer-interaction.ts` (the
92055
- // compatibility mousedown drives the React stage's desktop move).
92056
- event.preventDefault();
92057
- params.controls.enabled = false;
92058
- }
92059
- };
92060
- const onPointerMove = (event) => {
92061
- const state = dragState;
92062
- if (!state || !state.part || !state.geometry) {
92063
- return;
92064
- }
92065
- if (!state.moved && travelBelowThreshold(event.clientX, event.clientY, state)) {
92066
- return;
92067
- }
92068
- state.moved = true;
92069
- const angle = planeAngleAt(event.clientX, event.clientY);
92070
- if (angle === null) {
92071
- return;
92072
- }
92073
- state.lastAngle = angle;
92074
- const value = resolvePieChart3DDragValue(state.geometry, angle);
92075
- params.interaction?.onValueDragPreview?.(state.part, value);
92076
- };
92077
- const onPointerUp = (event) => {
92078
- if (!dragState) {
92079
- return;
92080
- }
92081
- const state = dragState;
92082
- dragState = null;
92083
- params.canvas.releasePointerCapture?.(event.pointerId);
92084
- if (state.geometry) {
92085
- params.controls.enabled = true;
92086
- }
92087
- if (state.moved && state.part && state.geometry) {
92088
- const angle = planeAngleAt(event.clientX, event.clientY) ?? state.lastAngle;
92089
- if (angle !== null) {
92090
- const value = resolvePieChart3DDragValue(state.geometry, angle);
92091
- params.interaction?.onValueDragCommit?.(state.part, value);
92092
- }
92093
- return;
92094
- }
92095
- setSelectedPart(state.part);
92096
- params.interaction?.onSelect?.(state.part);
92097
- };
92098
- params.canvas.addEventListener('pointerdown', onPointerDown);
92099
- params.canvas.addEventListener('pointermove', onPointerMove);
92100
- params.canvas.addEventListener('pointerup', onPointerUp);
92101
- params.canvas.addEventListener('pointercancel', onPointerUp);
92102
- return {
92103
- setSelectedPart,
92104
- dispose() {
92105
- params.canvas.removeEventListener('pointerdown', onPointerDown);
92106
- params.canvas.removeEventListener('pointermove', onPointerMove);
92107
- params.canvas.removeEventListener('pointerup', onPointerUp);
92108
- params.canvas.removeEventListener('pointercancel', onPointerUp);
92109
- },
92110
- };
92111
- }
92112
-
92113
- /** Minimum radial segments per wedge so a thin slice is still visibly curved. */
92114
- const MIN_RADIAL_SEGMENTS = 3;
92115
- /** Radial segments for a full-circle wedge (360deg); scaled down for narrower slices. */
92116
- const FULL_CIRCLE_SEGMENTS = 64;
92117
- function wedgeSegments(thetaLength) {
92118
- return Math.max(MIN_RADIAL_SEGMENTS, Math.round((thetaLength / (Math.PI * 2)) * FULL_CIRCLE_SEGMENTS));
92119
- }
92120
- /**
92121
- * Build one `THREE.CylinderGeometry` wedge mesh per data point: radiusTop =
92122
- * radiusBottom = `outerRadius`, height = `thickness`, capped (`openEnded`
92123
- * `false`) so a partial `thetaLength` gets real flat top/bottom + curved rim +
92124
- * flat radial "cut" faces for free. Adds every mesh to `scene`.
92125
- */
92126
- function buildPieChart3DWedgeMeshes(three, scene, wedges, outerRadius, thickness) {
92127
- const meshes = [];
92128
- const geometries = [];
92129
- const materials = [];
92130
- for (const wedge of wedges) {
92131
- const geometry = new three.CylinderGeometry(outerRadius, outerRadius, thickness, wedgeSegments(wedge.thetaLength), 1, false, wedge.startAngle, wedge.thetaLength);
92132
- const material = new three.MeshPhongMaterial({ color: wedge.color, shininess: 30 });
92133
- const mesh = new three.Mesh(geometry, material);
92134
- mesh.position.set(wedge.explodeOffset[0], 0, wedge.explodeOffset[1]);
92135
- mesh.userData = { pointIndex: wedge.pointIndex, value: wedge.value };
92136
- scene.add(mesh);
92137
- meshes.push(mesh);
92138
- geometries.push(geometry);
92139
- materials.push(material);
92140
- }
92141
- return { meshes, geometries, materials };
92142
- }
92143
- /**
92144
- * Rebuild every wedge mesh's geometry + position + `userData` from a freshly
92145
- * recomputed angle set (a live value drag: `pie-chart-3d-scene.ts`'s
92146
- * `recomputeLiveAngles`), mutating `geometries` in place (disposing each old
92147
- * one) so `createPieChart3DScene`'s own `dispose()` still tears down exactly the
92148
- * geometries actually on screen, whichever wedge was last dragged.
92149
- */
92150
- function applyPieChart3DWedgeAngles(three, meshes, geometries, outerRadius, thickness, angles) {
92151
- for (const angle of angles) {
92152
- const mesh = meshes[angle.pointIndex];
92153
- const oldGeometry = geometries[angle.pointIndex];
92154
- if (!mesh || !oldGeometry) {
92155
- continue;
92156
- }
92157
- const geometry = new three.CylinderGeometry(outerRadius, outerRadius, thickness, wedgeSegments(angle.thetaLength), 1, false, angle.startAngle, angle.thetaLength);
92158
- mesh.geometry = geometry;
92159
- oldGeometry.dispose();
92160
- geometries[angle.pointIndex] = geometry;
92161
- mesh.position.set(angle.explodeOffset[0], 0, angle.explodeOffset[1]);
92162
- mesh.userData = { pointIndex: angle.pointIndex, value: angle.value };
92163
- }
92164
- }
92165
-
92166
- /**
92167
- * three.js 3D pie-chart scene for `<pptx-three-view>` (framework-agnostic).
92168
- *
92169
- * Builds an interactive `pie3D` chart as a `<pptx-three-view>` scene (drawn
92170
- * by the shared renderer; see {@link ./chart-3d-hosted-stage.ts}): builds
92171
- * one real `THREE.CylinderGeometry` wedge mesh per data point (a partial-arc
92172
- * cylinder, so each wedge gets a flat top/bottom face, the two curved rim
92173
- * faces, and the two flat radial "cut" faces for free), lights, a perspective
92174
- * camera driven by `c:view3D` (`rotX`/`rotY`/`rperspective`/`hPercent`) and
92175
- * OrbitControls. Raycasts pointer moves against the wedges to
92176
- * set a native hover tooltip on the canvas element (see
92177
- * {@link ./pie-chart-3d-hit-test.ts}), matching every other chart kind's
92178
- * SVG-`<title>` hover tooltip.
92179
- *
92180
- * Unlike the cartesian 3D scenes (bar3D/line3D/area3D), a pie has no plot
92181
- * rectangle to wall in, so this module mounts no grid floor or
92182
- * `c:floor`/`c:sideWall`/`c:backWall` panels, matching PowerPoint's own
92183
- * behaviour and the flat SVG engine's `chart-3d-surfaces.ts` doc comment.
92184
- *
92185
- * A wedge is also drag-to-value, exactly like the flat SVG pie/doughnut:
92186
- * dragging sweeps its trailing edge around the pie's centre, and every other
92187
- * slice's ANGLE renormalises live (its own absolute value stays fixed, but the
92188
- * series total the pie's angles divide against changes) - see
92189
- * `pie-chart-3d-drag.ts` / `pie-chart-3d-interaction-wiring.ts`. Because the
92190
- * bar3D/line3D/area3D/surface3D drag calibration in
92191
- * `chart-3d-pointer-interaction.ts` only handles a single fixed WORLD axis,
92192
- * this scene wires its own pointer interaction instead of that shared one.
92193
- *
92194
- * @module pie-chart-3d-scene
92195
- */
92196
- /** Build the hosted pie3D scene. */
92197
- function createPieChart3DScene(ctx, options) {
92198
- const placement = computePieChart3DCameraPlacement(options.view3D);
92199
- const stage = createHostedChart3DStage(ctx, placement, { minDistance: 0.5, maxDistance: 30 }, 0.65);
92200
- const { three, scene, camera, canvas, controls } = stage;
92201
- const interaction = hostedChart3DInteraction(ctx);
92202
- // One CylinderGeometry mesh per wedge (see pie-chart-3d-mesh.ts for why a
92203
- // partial-arc capped cylinder gets a full wedge shape for free).
92204
- const { meshes: wedgeMeshes, geometries: wedgeGeometries, materials: wedgeMaterials, } = buildPieChart3DWedgeMeshes(three, scene, options.wedges, options.outerRadius, options.thickness);
92205
- // Raycast-based hover tooltip: each wedge mesh carries its own point index
92206
- // in `userData`, so a hit reports the slice directly (no face-index -> cell
92207
- // arithmetic, matching bar3D's box meshes).
92208
- const hoverTooltip = attachChart3DHoverTooltip({
92209
- three,
92210
- canvas,
92211
- camera,
92212
- meshes: wedgeMeshes,
92213
- buildTooltip: (intersection) => buildPieChart3DHoverTooltip(intersection?.object.userData, {
92214
- categoryLabels: options.categoryLabels,
92215
- seriesName: options.seriesName,
92216
- numberFormat: options.numberFormat,
92217
- }),
92218
- });
92219
- // Live wedge-angle snapshot, updated by `recomputeLiveAngles` on every value
92220
- // drag preview/commit tick so `getWedges` (re-read by the interaction
92221
- // wiring at the start of every press) always reflects the on-screen
92222
- // geometry, even across several drags within one mount, before any commit
92223
- // round-trips through the caller and remounts the whole scene fresh.
92224
- let currentAngles = options.wedges;
92225
- let liveValues = options.values.slice();
92226
- /** Recompute every wedge's angle from `liveValues` (one value already replaced by the caller) and rebuild its mesh. */
92227
- function recomputeLiveAngles() {
92228
- const angles = computePieChart3DSliceAngles(liveValues, options.explosions, options.firstSliceAngleDeg, options.outerRadius);
92229
- currentAngles = angles;
92230
- applyPieChart3DWedgeAngles(three, wedgeMeshes, wedgeGeometries, options.outerRadius, options.thickness, angles);
92231
- }
92232
- // Click-to-select + drag-to-value: each wedge is its own mesh, so it gets
92233
- // the same emissive highlight bar3D/line3D/area3D marks do, and the ANGLE
92234
- // drag (see the module doc comment) is wired through
92235
- // `pie-chart-3d-interaction-wiring.ts` rather than the generic
92236
- // single-fixed-axis calibration `chart-3d-pointer-interaction.ts` uses for
92237
- // bar3D/line3D/area3D/surface3D.
92238
- const pointerInteraction = attachPieChart3DInteraction({
92239
- three,
92240
- canvas,
92241
- camera,
92242
- controls,
92243
- wedgeMeshes,
92244
- wedgeMaterials: wedgeMaterials,
92245
- getWedges: () => currentAngles,
92246
- interaction: {
92247
- onSelect: interaction?.onSelect,
92248
- onValueDragPreview: (part, value) => {
92249
- if (part.pointIndex !== undefined) {
92250
- liveValues = liveValues.map((v, i) => (i === part.pointIndex ? value : v));
92251
- recomputeLiveAngles();
92252
- }
92253
- interaction?.onValueDragPreview?.(part, value);
92254
- },
92255
- onValueDragCommit: (part, value) => {
92256
- if (part.pointIndex !== undefined) {
92257
- liveValues = liveValues.map((v, i) => (i === part.pointIndex ? value : v));
92258
- recomputeLiveAngles();
92259
- }
92260
- interaction?.onValueDragCommit?.(part, value);
92261
- },
92262
- },
92263
- });
92264
- return finishHostedChart3DScene(ctx, stage, {
92265
- // No `resize`: this scene's own wiring re-derives the pointer's NDC
92266
- // position from `canvas.getBoundingClientRect()` on every raycast.
92267
- setSelectedPart: (part) => pointerInteraction.setSelectedPart(part),
92268
- dispose() {
92269
- hoverTooltip.dispose();
92270
- pointerInteraction.dispose();
92271
- for (const g of wedgeGeometries) {
92272
- g.dispose();
92273
- }
92274
- for (const m of wedgeMaterials) {
92275
- m.dispose();
92276
- }
92277
- },
92278
- }, interaction);
92279
- }
92280
-
92281
89274
  /**
92282
89275
  * SmartArt fallback-layout paint decisions: where a node's label sits and how a
92283
89276
  * connector is stroked.
@@ -120244,16 +117237,6 @@ async function loadChart3DThree() {
120244
117237
  return null;
120245
117238
  }
120246
117239
  }
120247
- /** Dynamically load the OrbitControls addon; resolves to `null` when unavailable. */
120248
- async function loadChart3DOrbitControls() {
120249
- try {
120250
- const mod = await import('three/examples/jsm/controls/OrbitControls.js');
120251
- return mod.OrbitControls;
120252
- }
120253
- catch {
120254
- return null;
120255
- }
120256
- }
120257
117240
 
120258
117241
  /** Largest backing-store edge a single view is drawn at, in device pixels. */
120259
117242
  const MAX_VIEW_PIXELS = 4096;
@@ -120435,8 +117418,8 @@ function resetThreeRendererHostForTests() {
120435
117418
  }
120436
117419
 
120437
117420
  const loaders = {
120438
- chart: async () => (await import('./pptx-angular-viewer-chart-3d-view-scene-CkW1D-mg.mjs')).mountChart3DView,
120439
- smartart: async () => (await import('./pptx-angular-viewer-view-scene-BJXZicXV.mjs')).mountSmartArt3DView,
117421
+ chart: async () => (await import('./pptx-angular-viewer-chart-3d-view-scene-Cfk7dMVK.mjs')).mountChart3DView,
117422
+ smartart: async () => (await import('./pptx-angular-viewer-view-scene-CNlGt3WC.mjs')).mountSmartArt3DView,
120440
117423
  };
120441
117424
  /** Resolve the scene factory for a spec kind. */
120442
117425
  async function loadThreeViewScene(kind) {
@@ -120598,10 +117581,6 @@ class ThreeViewController {
120598
117581
  let view = null;
120599
117582
  const scene = await factory(spec.spec, {
120600
117583
  three,
120601
- // No camera orbit: PowerPoint never rotates a chart or SmartArt
120602
- // under the pointer, and an orbit would turn the scene while the
120603
- // user drags the element to move it on the slide.
120604
- OrbitControls: null,
120605
117584
  size: this.size,
120606
117585
  eventTarget: this.opts.eventTarget,
120607
117586
  overlay,
@@ -120802,6 +117781,7 @@ function createElementClass() {
120802
117781
  #resize = null;
120803
117782
  #intersect = null;
120804
117783
  #poll = null;
117784
+ #teardownPending = false;
120805
117785
  get spec() {
120806
117786
  return this.#spec;
120807
117787
  }
@@ -120810,9 +117790,9 @@ function createElementClass() {
120810
117790
  return;
120811
117791
  }
120812
117792
  this.#spec = value;
120813
- if (this.isConnected) {
120814
- void this.#controller?.setSpec(value);
120815
- }
117793
+ // A live controller (connected, or detached for a moment within a
117794
+ // re-render, see disconnectedCallback) takes the new spec at once.
117795
+ void this.#controller?.setSpec(value);
120816
117796
  }
120817
117797
  get interactive() {
120818
117798
  return this.hasAttribute('interactive');
@@ -120855,6 +117835,12 @@ function createElementClass() {
120855
117835
  // Survives html2canvas, which re-creates custom elements as <div>s
120856
117836
  // (see export-snapshot.ts).
120857
117837
  this.setAttribute(THREE_VIEW_MARKER_ATTR, '');
117838
+ if (this.#controller) {
117839
+ // Re-attached within the same task (a binding moving the node while
117840
+ // it rebuilds its DOM): keep the running scene.
117841
+ this.#teardownPending = false;
117842
+ return;
117843
+ }
120858
117844
  const root = this.shadowRoot ?? this.attachShadow({ mode: 'open' });
120859
117845
  if (!this.#canvas) {
120860
117846
  root.innerHTML = `<style>${SHADOW_CSS}</style><div class="fallback" part="fallback"><slot></slot></div><div class="stage" part="stage"><canvas></canvas><div class="hit" part="hit"></div><div class="overlay" part="overlay"></div></div>`;
@@ -120882,6 +117868,18 @@ function createElementClass() {
120882
117868
  void this.#controller.setSpec(this.#spec);
120883
117869
  }
120884
117870
  disconnectedCallback() {
117871
+ // Tear down one microtask later, so a node detached and re-attached
117872
+ // in the same task (Vanilla rebuilds its stage on every edit and moves
117873
+ // the live view across) keeps its scene instead of reloading it.
117874
+ this.#teardownPending = true;
117875
+ queueMicrotask(() => {
117876
+ if (this.#teardownPending && !this.isConnected) {
117877
+ this.#teardown();
117878
+ }
117879
+ });
117880
+ }
117881
+ #teardown() {
117882
+ this.#teardownPending = false;
120885
117883
  this.#resize?.disconnect();
120886
117884
  this.#intersect?.disconnect();
120887
117885
  if (this.#poll !== null) {
@@ -124141,25 +121139,51 @@ function isChart3DViewEnabled(flags, chartType) {
124141
121139
  }
124142
121140
  const chartSpecs = new WeakMap();
124143
121141
  const smartArtSpecs = new WeakMap();
124144
- /** The (memoised) chart spec for an element, ignoring flags; `null` when it is not a 3D chart. */
124145
- function chartThreeViewSpec(element) {
124146
- if (chartSpecs.has(element)) {
124147
- return chartSpecs.get(element) ?? null;
121142
+ /** The last element (and its spec) seen per element id, per kind. */
121143
+ const lastByIdChart = new Map();
121144
+ const lastByIdSmartArt = new Map();
121145
+ /** Fields a spec never reads: moving an element must not rebuild its scene. */
121146
+ const POSITION_KEYS = new Set(['x', 'y']);
121147
+ /**
121148
+ * Whether two versions of an element differ only in position. An editor
121149
+ * move patches `x`/`y` into a fresh element object on every pointer move;
121150
+ * without this the spec (keyed by object) changed each time and the view
121151
+ * rebuilt its whole scene mid-drag.
121152
+ */
121153
+ function differsOnlyInPosition(a, b) {
121154
+ const ra = a;
121155
+ const rb = b;
121156
+ const keys = new Set([...Object.keys(ra), ...Object.keys(rb)]);
121157
+ for (const key of keys) {
121158
+ if (!POSITION_KEYS.has(key) && ra[key] !== rb[key]) {
121159
+ return false;
121160
+ }
124148
121161
  }
124149
- const spec = buildChart3DSpecForElement(element);
124150
- const view = spec ? { kind: 'chart', spec } : null;
124151
- chartSpecs.set(element, view);
121162
+ return true;
121163
+ }
121164
+ function memoised(element, byObject, byId, build) {
121165
+ if (byObject.has(element)) {
121166
+ return byObject.get(element) ?? null;
121167
+ }
121168
+ const last = byId.get(element.id);
121169
+ const view = last && differsOnlyInPosition(last.element, element) ? last.view : build();
121170
+ byObject.set(element, view);
121171
+ byId.set(element.id, { element, view });
124152
121172
  return view;
124153
121173
  }
121174
+ /** The (memoised) chart spec for an element, ignoring flags; `null` when it is not a 3D chart. */
121175
+ function chartThreeViewSpec(element) {
121176
+ return memoised(element, chartSpecs, lastByIdChart, () => {
121177
+ const spec = buildChart3DSpecForElement(element);
121178
+ return spec ? { kind: 'chart', spec } : null;
121179
+ });
121180
+ }
124154
121181
  /** The (memoised) SmartArt spec for an element; `null` when it has nothing to draw. */
124155
121182
  function smartArtThreeViewSpec(element) {
124156
- if (smartArtSpecs.has(element)) {
124157
- return smartArtSpecs.get(element) ?? null;
124158
- }
124159
- const spec = buildSmartArt3DSpecForElement(element);
124160
- const view = spec ? { kind: 'smartart', spec } : null;
124161
- smartArtSpecs.set(element, view);
124162
- return view;
121183
+ return memoised(element, smartArtSpecs, lastByIdSmartArt, () => {
121184
+ const spec = buildSmartArt3DSpecForElement(element);
121185
+ return spec ? { kind: 'smartart', spec } : null;
121186
+ });
124163
121187
  }
124164
121188
  /**
124165
121189
  * The spec a chart element's `<pptx-three-view>` gets, or `null` for the
@@ -130570,7 +127594,7 @@ function createLocalStorageBackend(namespace) {
130570
127594
  /** Try IndexedDB first; fall back to localStorage on any failure. */
130571
127595
  async function resolveBackend(dbName, namespace) {
130572
127596
  try {
130573
- const { openChatDb, createIdbBackend } = await import('./pptx-angular-viewer-chat-history-idb-BboVG09q.mjs');
127597
+ const { openChatDb, createIdbBackend } = await import('./pptx-angular-viewer-chat-history-idb-BnV3ANp_.mjs');
130574
127598
  const db = await openChatDb(dbName);
130575
127599
  return createIdbBackend(db);
130576
127600
  }
@@ -176565,7 +173589,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImpor
176565
173589
  }], propDecorators: { canEdit: [{ type: i0.Input, args: [{ isSignal: true, alias: "canEdit", required: false }] }], slideIndex: [{ type: i0.Input, args: [{ isSignal: true, alias: "slideIndex", required: false }] }], selectedElement: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectedElement", required: false }] }] } });
176566
173590
 
176567
173591
  // Generated by scripts/inline-shared.mjs from package.json. Do not edit.
176568
- const PPTX_ANGULAR_VIEWER_VERSION = "4.9.0";
173592
+ const PPTX_ANGULAR_VIEWER_VERSION = "4.10.1";
176569
173593
 
176570
173594
  /**
176571
173595
  * account-page.component.ts: File > Account content.
@@ -214894,5 +211918,5 @@ function cn(...values) {
214894
211918
  * Generated bundle index. Do not edit.
214895
211919
  */
214896
211920
 
214897
- export { AiFocusBarComponent as $, smartArt3DGradientCoordinate as A, threeViewOverflowFromNdc as B, CHART_3D_DRAG_THRESHOLD_PX as C, DEFAULT_PALETTE$1 as D, hasThreeViewOverflow as E, overflowViewOffset as F, smartArt3DCameraMatrix as G, smartArt3DEyeInDiagram as H, resolveSmartArt3DLightModel as I, AFTER_ANIMATION_VALUES as J, ALIGN_OPTIONS as K, ANIMATION_PRESET_CATEGORIES as L, AUDIENCE_HASH as M, NO_THREE_VIEW_OVERFLOW as N, AUDIENCE_NONCE_KEY as O, AVATAR_COLOR_SWATCHES as P, AXIS_LABEL_COLOR as Q, AccessibilityPanelComponent as R, AccessibilityService as S, AccessibilityTextPanelComponent as T, AccountPageComponent as U, ActionSettingsPanelComponent as V, AdvancedChartEditorComponent as W, AiChangeOverlayComponent as X, AiChatPanelComponent as Y, AiChatService as Z, AiComposerComponent as _, appendChartLegendSvg as a, DIRECTIONAL_PRESETS as a$, AiFocusHighlightOverlayComponent as a0, AiHistoryMenuComponent as a1, AiHistoryService as a2, AiMessageListComponent as a3, AiPanelStore as a4, AiProposalCardComponent as a5, AiSettingsSectionComponent as a6, AiToolCallCardComponent as a7, AnimationAuthorPanelComponent as a8, AnimationPanelComponent as a9, ChartTypeSelectorComponent as aA, CollaborationCursorsComponent as aB, CollaborationService as aC, ColorChangedImageComponent as aD, CommentMarkersOverlayComponent as aE, CommentsPanelComponent as aF, CommentsService as aG, ComparePanelComponent as aH, ConnectorRendererComponent as aI, ConnectorTextOverlayComponent as aJ, CustomShowsComponent as aK, DEFAULT_BOUNDS as aL, DEFAULT_BROADCAST_SERVER_URL as aM, DEFAULT_CANVAS_HEIGHT as aN, DEFAULT_CANVAS_WIDTH as aO, DEFAULT_COLOR_SCHEME as aP, DEFAULT_FILL_COLOR$1 as aQ, DEFAULT_LAYOUT as aR, DEFAULT_MORPH_DURATION_MS as aS, DEFAULT_PATTERN_FILL_PRESET as aT, DEFAULT_PRINT_SETTINGS as aU, DEFAULT_SLIDE_BACKGROUND as aV, DEFAULT_STROKE_COLOR as aW, DEFAULT_STYLE as aX, DEFAULT_TABLE_ROW_HEIGHT as aY, DEFAULT_TEXT_COLOR$2 as aZ, DEFAULT_VIEWER_PROFILE as a_, AnimationPlaybackService as aa, AutosaveRecoveryDialogComponent as ab, AutosaveService as ac, BACKSTAGE_CARD_IDS as ad, BACKSTAGE_PAGE_IDS as ae, BroadcastDialogComponent as af, CANVAS_CONTEXT_MENU_COMMAND_IDS as ag, CHART_EDITOR_STYLES as ah, CINEMATIC_TRANSITION_KEYFRAMES as ai, CURSOR_PALETTE as aj, CanvasFitService as ak, ChartAxisOptionsComponent as al, ChartAxisStyleOptionsComponent as am, ChartComboTypeOptionsComponent as an, ChartDataEditorComponent as ao, ChartDataLabelOptionsComponent as ap, ChartDatapointMarkerOptionsComponent as aq, ChartDatapointOptionsComponent as ar, ChartDisplayOptionsComponent as as, ChartElementViewComponent as at, ChartErrorBarOptionsComponent as au, ChartMarkerOptionsComponent as av, ChartPartSelectionService as aw, ChartPrimitivesComponent as ax, ChartRendererComponent as ay, ChartTrendlineOptionsComponent as az, renderChartTextSvg as b, MobileSlidesSheetComponent as b$, DIRECTION_OPTIONS as b0, DocumentPropertiesCardComponent as b1, EASE as b2, EDITOR_SHORTCUT_ACTION_IDS as b3, EFFECT_SOUND_CATALOGUE as b4, ELEMENT_CONTEXT_MENU_COMMAND_IDS as b5, EMBEDDED_FONTS_STYLE_ID as b6, EMPHASIS_PRESETS as b7, ENTRANCE_PRESETS as b8, TEMPLATES as b9, HeaderFooterDialogComponent as bA, HyperlinkDialogComponent as bB, INSTANT as bC, ImagePropertiesPanelComponent as bD, InkDrawingService as bE, InkRendererComponent as bF, InsertSmartArtDialogComponent as bG, InspectorPaneHeaderComponent as bH, InspectorPanelComponent as bI, IsMobileService as bJ, KeepAnnotationsDialogComponent as bK, LOCALE_CATALOG as bL, LONG_PRESS_DURATION_MS as bM, LONG_PRESS_MOVE_TOLERANCE_PX as bN, LoadContentService as bO, LocalPresencePublisher as bP, MAX_ZOOM_SCALE as bQ, MIN_ZOOM_SCALE as bR, MOTION_PATH_COLUMNS as bS, MediaPreviewComponent as bT, MediaPropertiesPanelComponent as bU, MediaRendererComponent as bV, MediaTrimTimelineComponent as bW, MobileBottomBarComponent as bX, MobileMenuSheetComponent as bY, MobilePresenterViewComponent as bZ, MobileSheetComponent as b_, EXIT_PRESETS as ba, EditorContextMenuComponent as bb, EditorHistory as bc, EditorStateService as bd, EditorToolbarComponent as be, EffectsPanelComponent as bf, ElementRendererComponent as bg, EmbeddedFontsService as bh, EncryptedFileDialogComponent as bi, EquationEditorDialogComponent as bj, EquationRendererComponent as bk, EquationTemplateGalleryComponent as bl, ExportProgressModalComponent as bm, ExportService as bn, FieldContextService as bo, FindBarComponent as bp, FindReplaceBarComponent as bq, FollowModeBarComponent as br, FontEmbeddingListComponent as bs, FontEmbeddingPanelComponent as bt, GALLERY_THEME_PRESETS as bu, GOOGLE_WEBFONTS_LINK_ID as bv, GRIDLINE_COLOR$1 as bw, GoogleWebfontsService as bx, GradientPickerComponent as by, HANDOUT_OPTIONS as bz, createSvgEl as c, RibbonInsertSectionComponent as c$, MobileToolbarComponent as c0, ModalDialogComponent as c1, Model3DRendererComponent as c2, NotesHandoutCardComponent as c3, NotesPanelComponent as c4, NotesToolbarComponent as c5, OPTIONS_PAGE_IDS as c6, OPTIONS_SECTION_IDS as c7, OPTIONS_SETTING_IDS as c8, OleRendererComponent as c9, PrintDialogComponent as cA, PrintService as cB, PrintSettingsPanelComponent as cC, PropertiesDialogComponent as cD, RANDOM_ELIGIBLE_TYPES as cE, REPEAT_MODE_OPTIONS as cF, RESIZE_HANDLES as cG, RIBBON_TAB_IDS as cH, RULER_FONT_SIZE as cI, RULER_THICKNESS as cJ, ReadingViewOverlayComponent as cK, RemoteSelectionOverlayComponent as cL, Rendering3DService as cM, RibbonAnimationGalleryComponent as cN, RibbonAnimationsSectionComponent as cO, RibbonArrangeSectionComponent as cP, RibbonColorPopoverComponent as cQ, RibbonComponent as cR, RibbonDesignSectionComponent as cS, RibbonDrawSectionComponent as cT, RibbonDrawingGroupComponent as cU, RibbonEditingSectionComponent as cV, RibbonFileSectionComponent as cW, RibbonFontControlsComponent as cX, RibbonHomeSectionComponent as cY, RibbonHyperlinkButtonComponent as cZ, RibbonInsertFieldsComponent as c_, OutlineViewOverlayComponent as ca, P14_TRANSITION_KEYFRAMES as cb, P14_TRANSITION_KEYFRAMES_2 as cc, P14_TRANSITION_KEYFRAMES_ALL as cd, POWER_POINT_VIEWER_PROVIDERS as ce, PPTX_OPEN_ACCEPT as cf, PRESENTATION_OPEN_EXTENSIONS as cg, PRESENTER_CHANNEL_NAME as ch, PRESENTER_MSG_ORIGIN as ci, PRESENTER_TIMER_SEGMENT_MS as cj, PX_PER_CM as ck, PX_PER_INCH as cl, PasswordProtectionDialogComponent as cm, PasswordStrengthMeterComponent as cn, PowerPointViewerComponent as co, PresentToolbarAutoHide as cp, PresentationAnnotationOverlayComponent as cq, PresentationAnnotationsService as cr, PresentationOverlayComponent as cs, PresentationPropertiesPanelComponent as ct, PresentationSettingsCardComponent as cu, PresentationSubtitleBarComponent as cv, PresentationToolbarComponent as cw, PresentationTransitionOverlayComponent as cx, PresenterViewComponent as cy, PresenterWindowService as cz, buildBarChart3DHoverTooltip as d, TableCellAdvancedFillComponent as d$, RibbonMotionPathGalleryComponent as d0, RibbonParagraphControlsComponent as d1, RibbonPrimaryRowComponent as d2, RibbonReviewSectionComponent as d3, RibbonShapeExtrasComponent as d4, RibbonSlideshowSectionComponent as d5, RibbonTransitionsSectionComponent as d6, RibbonViewSectionComponent as d7, RulerGuidesService as d8, SEQUENCE_OPTIONS as d9, SignaturesService as dA, SlideBackgroundCardComponent as dB, SlideCanvasComponent as dC, SlideDefaultInspectorComponent as dD, SlideDiffChangesComponent as dE, SlideDiffRowComponent as dF, SlideDiffThumbnailsComponent as dG, SlideSizeCardComponent as dH, SlideSorterOverlayComponent as dI, SlideThemeOverridePanelComponent as dJ, SlideTransitionCardComponent as dK, SlidesPanelComponent as dL, SmartArt3DRendererComponent as dM, SmartArtPreviewComponent as dN, SmartArtPropertiesComponent as dO, SmartArtRendererComponent as dP, StatusBarComponent as dQ, TABLE_STRUCTURE_TOGGLES as dR, TEXT_3D_BOTTOM_BEVEL_KEYS as dS, TEXT_3D_TOP_BEVEL_KEYS as dT, TEXT_DIRECTION_OPTIONS$1 as dU, THEME_CATALOG as dV, TIMING_CURVE_OPTIONS as dW, TOOLBAR_BUTTON_IDS as dX, TRANSITION_SPEED_DURATION_MS as dY, TRIGGER_OPTIONS as dZ, TYPE_LABELS as d_, SEVERITY_GROUPS as da, SEVERITY_LABELS as db, DEFAULT_TRANSITION_DURATION_MS$1 as dc, SHORTCUT_REFERENCE_ITEMS as dd, SLIDE_TRANSITION_KEYFRAMES as de, SLIDE_TRANSITION_KEYFRAMES_CSS as df, DEFAULT_PALETTE as dg, PALETTES as dh, SMART_ART_COLOR_SCHEMES as di, SMART_ART_STYLE_OPTIONS as dj, SUB_ITEM_LABEL as dk, SVG_WARP_PRESETS as dl, SWIPE_MAX_VERTICAL_PX as dm, SWIPE_THRESHOLD_PX as dn, SelectionPaneComponent as dp, SetUpSlideShowDialogComponent as dq, SettingsAppearanceTabComponent as dr, SettingsDialogComponent as ds, SettingsLanguageTabComponent as dt, ShareDialogComponent as du, ShortcutPanelComponent as dv, ShowOptionsFieldsetComponent as dw, ShowSlidesFieldsetComponent as dx, SignatureStrippedDialogComponent as dy, SignaturesPanelComponent as dz, roundDragValue as e, animationFor as e$, TableCellFormattingComponent as e0, TableDataEditorComponent as e1, TablePropertiesComponent as e2, TableRendererComponent as e3, TableResizeOverlayComponent as e4, TableSelectionService as e5, TagsCardComponent as e6, Text3DBevelSectionComponent as e7, Text3DPanelComponent as e8, TextAdvancedPanelComponent as e9, ViewerFileIOService as eA, ViewerFindReplaceService as eB, ViewerFormatPainterService as eC, ViewerInspectorPanelService as eD, ViewerKeyboardService as eE, ViewerMobileSheetService as eF, ViewerPresentationModeService as eG, ViewerThemeGalleryService as eH, ViewerTouchGesturesService as eI, ViewerZoomService as eJ, WEBM_MIME_CANDIDATES as eK, WHEEL_SPOKE_COUNTS as eL, WriteBackScheduler as eM, ZERO_LINE_COLOR as eN, ZoomNavigationService as eO, ZoomRendererComponent as eP, ZoomTargetService as eQ, addCategory as eR, addCommentToList as eS, addGradientStopPatch as eT, addItem as eU, addSeries as eV, addSubItem as eW, advanceStep as eX, affordanceElements as eY, aiToggleVisible as eZ, alignPatch as e_, ThemeEditorFieldsComponent as ea, ThemeGalleryComponent as eb, ThemeSelectorCardComponent as ec, TitleBarComponent as ed, TitleBarSearchComponent as ee, TransitionDirectionPickerComponent as ef, TransitionPreviewComponent as eg, VALIGN_OPTIONS as eh, VIEWER_DIALOG_IDS as ei, VIEWER_EXPORT_FORMAT_IDS as ej, VIEWER_FEATURE_IDS as ek, VIEWER_PANEL_IDS as el, VIEWER_THEME as em, VersionHistoryPanelComponent as en, ViewerCanvasEditingService as eo, ViewerCollabCursorService as ep, ViewerCollaborationSessionService as eq, ViewerCollaborationShellService as er, ViewerCompareService as es, ViewerCustomShowsService as et, ViewerCustomizationHandle as eu, ViewerCustomizationService as ev, ViewerDialogsService as ew, ViewerDocumentPropertiesService as ex, ViewerExportService as ey, ViewerExtraDialogsComponent as ez, buildPerspCamera as f, buildStockViewModel as f$, animationPresetLabelKey as f0, annotationMapToInkInserts as f1, applyAcceptedDiff as f2, applyAnimationPreset as f3, applyFindReplacements as f4, applyFormatToElement as f5, applyMove as f6, applyResize as f7, asMediaElement as f8, assignUserColor as f9, buildEmbeddedFontStyles as fA, buildEquationElement as fB, buildEquationSegment as fC, buildFallbackViewModel as fD, buildFontFaceRule as fE, buildGradientFillCss as fF, buildGridlinesAndLabels as fG, buildHyperlinkPatch as fH, buildInkContainerStyle as fI, buildInkStrokes as fJ, buildLegend as fK, buildLiveInkStrokeView as fL, buildMarkTooltip as fM, buildModel3DContainerStyle as fN, buildModel3DViewModel as fO, buildOleActionModel as fP, buildOleInfoRows as fQ, buildPatternFillCss as fR, buildPieViewModel as fS, buildPrintHtmlDocument as fT, buildPropertiesPatch as fU, buildRadarViewModel as fV, buildRegionMapViewModel as fW, buildSaveSlides as fX, buildShareUrl as fY, buildSmartArtInsertElement as fZ, buildSmartArtNodes as f_, attachShowVisibilityPause as fa, attachTouchGestures as fb, axisTickValues as fc, beginDrawingNodeEdit as fd, beginNodeEdit as fe, bevelSizePatch as ff, boolFromEvent as fg, bringForward as fh, bringToFront as fi, buildBarActions as fj, buildBroadcastConfig as fk, buildBroadcastViewerUrl as fl, buildCategoryLabels as fm, buildCellParagraphs as fn, buildChartViewModel as fo, buildChatLogExport as fp, buildChatLogMarkdown as fq, buildChromeStyle as fr, buildClearHyperlinkPatch as fs, buildClickGroups as ft, buildColStyles as fu, buildCollaborationConfig as fv, buildComboViewModel as fw, buildCssGradientFromShapeStyle as fx, buildDuotoneFilter as fy, buildDuotoneFilterId as fz, perspToScreen as g, computePlotLayout as g$, buildSurfaceViewModel as g0, buildTableViewModel as g1, buildTreemapViewModel as g2, buildTrimFragment as g3, buildWaterfallViewModel as g4, buildZeroLine as g5, buildZoomContainerStyle as g6, buildZoomViewModel as g7, bulletIndentPx as g8, canAddTopLevelNode as g9, collectAccessibilityIssues as gA, collectElementText as gB, collectSlideText as gC, collectStoredChats as gD, collectUsedFontFamilies as gE, columnWidthStyle as gF, commitNodeText as gG, computeAlign as gH, computeAxisTitlePrimitives as gI, computeBarRects as gJ, computeBubbleRadius as gK, computeCornerHandle as gL, computeDistribute as gM, computeDrawingViewBox as gN, computeErrorBarPrimitives as gO, computeFocusTargets as gP, computeGridSpacingPx as gQ, computeHandleBoxes as gR, computeHandoutLayout as gS, computeIsMobile as gT, computeIsTablet as gU, computeLinePoints as gV, computeLinearRegression as gW, computePageCount as gX, computePieLayout as gY, computePieSlicePath as gZ, computePieSlices as g_, canEditSmartArtNodes as ga, canGroupSelection as gb, canRemoveTopLevelNode as gc, canSetStrokeWidth as gd, canStartBroadcast as ge, canStartShare as gf, canUngroupSelection as gg, canUseClipboard as gh, captionDisplayText as gi, cellRunStyle as gj, cellStyleToStyleMap as gk, cellTdStyle as gl, changeCountLabel as gm, changeIcon as gn, characterSpacingPatch as go, chartPreserveAspectRatio as gp, checkFontAvailable as gq, clampCursorPosition as gr, clampGifDimensions as gs, clampIndex as gt, clampNotesFontSize as gu, clampScale as gv, clampStep as gw, clearAllLocalViewerData as gx, clearAudienceContent as gy, cn as gz, pieRimPoint as h, firstVisibleIndex as h$, computeRSquared as h0, computeRadarPoints as h1, computeResizeHandleBoxes as h2, computeRotateHandleBox as h3, computeScatterDots as h4, computeScatterXDomain as h5, computeSelectionBoxes as h6, computeSingleSelected as h7, computeSlideIndices as h8, computeSnap as h9, describeCollaborationShellState as hA, describeSmartArtBounds as hB, directionPickerState as hC, disableGlowPatch as hD, disableInnerShadowPatch as hE, disableOuterShadowPatch as hF, disableReflectionPatch as hG, disableSoftEdgePatch as hH, duplicateElementById as hI, durationOf as hJ, effectsStateOf as hK, enableGlowPatch as hL, enableInnerShadowPatch as hM, enableOuterShadowPatch as hN, enableReflectionPatch as hO, enableSoftEdgePatch as hP, encodeGif as hQ, endShowMediaCleanup as hR, estimatePageCount as hS, exitPresentationFullscreen as hT, exportAiChatLogs as hU, extractPathPoints as hV, eyedropperAvailable as hW, fillColorOf$1 as hX, findInSlides as hY, findOwningSlideIndex as hZ, findSlideIndexByElementId as h_, computeStackedBarRects as ha, computeStackedValueRange as hb, computeTrendlinePrimitives as hc, computeValueRange as hd, convertOmmlToMathMl as he, copyFormatFromElement as hf, countAccessibilityIssues as hg, countAnnotationStrokes as hh, createAngularAiBridge as hi, createCustomShow as hj, createImageElementFromFile as hk, createSwipeDismissDrag as hl, createWebrtcBundle as hm, createWebsocketBundle as hn, cssObjectToStyleMap as ho, currentColorScheme as hp, currentLayout as hq, currentStyle as hr, defaultCssVars as hs, defaultRadius as ht, defaultThemeColors as hu, deleteElementsByIds as hv, deleteVersion as hw, demoteNode as hx, deriveModel3DBlobUrl as hy, derivePresenceList as hz, isChartInteractionArmed as i, hasExitedFullscreen as i$, fitPolynomial as i0, fitZoom as i1, focusTargetChips as i2, fontMimeForFormat as i3, fontSizeOf as i4, forgetSessionDeck as i5, formatAxisValue as i6, formatBytes as i7, formatCursorLabel as i8, formatElapsed as i9, getP14TransitionAnimations as iA, getPasswordStrength as iB, getPatternSvg as iC, getPlaceholderStyle as iD, getVersions as iE, getResolvedShapeClipPath as iF, getResolvedShapeClipPathFor as iG, getSessionTabId as iH, getShapeFillStrokeStyle as iI, getSlideBackgroundStyle as iJ, getSlideTransitionAnimations as iK, getSmartArtNodeBounds as iL, getSpeechRecognitionCtor as iM, getTextBlockStyle as iN, getTextWarp as iO, getTouchDistance as iP, getWarpCategory as iQ, getWarpPath as iR, gradientStateFromStyle as iS, gradientStateOf as iT, gradientStatePatch as iU, gradientStopColorCommitPatch as iV, gridColumns as iW, groupIssuesBySeverity as iX, hasAnimation as iY, hasCopyableFormat as iZ, hasExistingLink as i_, formatFileSize as ia, formatInlineListStyle as ib, formatPropertyDate as ic, formatTime as id, fpsToFrameIntervalMs as ie, generateBroadcastRoomId as ig, generateCommentId as ih, generateCustomShowId as ii, generatePressureCircles as ij, generateTicks as ik, getCinematicTransitionAnimations as il, getClrChangeParams as im, getContainerStyle as io, getDuotoneFilterDef as ip, getEffectSoundAsset as iq, getEffectSoundState as ir, getImageSrc as is, getLocalStorageUsageSummary as it, getOleAriaLabel as iu, getOleBadgeLabel as iv, getOleDisplayName as iw, getOleDownloadFileName as ix, getOleTypeColor as iy, getOleTypeLabel as iz, computePieChartLayout as j, nodeEditBox as j$, hasGradientFill as j0, hasPressureVariation as j1, hasVisibleSlideAfter as j2, headerLabel as j3, imageDimensions as j4, injectResolvedCustomization as j5, inkViewBox as j6, insertTableElementColumn as j7, insertTableElementRow as j8, interpolateWidth as j9, linePointsToSvgString as jA, lineSpacingPatch as jB, loadAudienceContent as jC, loadSessionDeck as jD, mediaFallbackFor as jE, mediaSurfaceFor as jF, mergeCaptionResults as jG, mergeDown as jH, mergeRight as jI, mergeSelection as jJ, mergeTablesDirective as jK, moveElementBy as jL, moveNodeDown as jM, moveNodeUp as jN, msToFrameDelayCs as jO, narrowToCircle as jP, narrowToPolygon as jQ, narrowToRect as jR, newChartElement as jS, newEquationElement as jT, newPresetShapeElement as jU, newShapeElement as jV, newSmartArtElement as jW, newTableElement as jX, newTextElement as jY, nextVisibleIndex as jZ, nodeBold as j_, isAudienceTab as ja, isBold as jb, isBrowserOpenableMime as jc, isChildNode as jd, isElementInteractive as je, isInjectableUrl as jf, isItalic as jg, isLegacyBinaryPresentation as jh, isPpactionUrl as ji, isPresenterMessage as jj, isSigned as jk, isSupportedPresentationFile as jl, isTextElement as jm, isTwoTableFocus as jn, isUnderline as jo, isUrlSafe as jp, isValidRoomId as jq, isViewportBackgroundPressTarget as jr, isZoomActivationKey as js, issueTrackKey as jt, issueTypeLabel as ju, keyToLabel as jv, lastVisibleIndex as jw, latexToMathml as jx, layoutConnectorPaints as jy, layoutNodeLabels as jz, perspScreenToPlaneY as k, resizeElement as k$, nodeFillColor as k0, nodeFontColor as k1, nodeIdFromKey as k2, nodeItalic as k3, nodeStyle as k4, normalizeFontFormat as k5, normalizeSlidesPerPage as k6, normalizeValue as k7, numFromEvent as k8, oleActionsVisibleFor as k9, presetsForCategory as kA, pressuresToWidths as kB, prevVisibleIndex as kC, projectDrawingShapes as kD, promoteNode as kE, provideViewerTheme as kF, radarAngle as kG, radarRingPoints as kH, readAsDataUrl as kI, recordWebm as kJ, registerCrossSlideAudio as kK, rememberSessionDeck as kL, removeAnimation as kM, removeCategory as kN, removeTableElementColumn as kO, removeCommentFromList as kP, removeElementAnimation as kQ, removeGradientStopPatch as kR, removeNode as kS, removeTableElementRow as kT, removeSeries as kU, renderToCanvas as kV, reorderAnimationDown as kW, reorderAnimationUp as kX, replaceInSlides as kY, replaceMatch as kZ, requestPresentationFullscreen as k_, ommlToMathml as ka, ooxmlDashToCssBorderStyle as kb, openNativeEyeDropper as kc, overallStatus as kd, paletteColor as ke, parseAudienceNonce as kf, parseNodeTextarea as kg, partitionSlides as kh, patchChartData as ki, patchChartStyle as kj, patchTableData as kk, patchTextStyle as kl, patternPresetOptions as km, pendingElementStyles as kn, pickColorByClickFallback as ko, pickFile as kp, pickSupportedMimeType as kq, planGifFrames as kr, planVideoSegments as ks, pointFromPointerEvent as kt, pointsToSvgPathD as ku, presenceToCursors as kv, presentationBaseName as kw, presentationStageStyle as kx, presenterTimerProgress as ky, presetByLayout as kz, resolvePieSliceShareValue as l, setDataPointFill as l$, resolveCaptionTracks as l0, resolveChartKind as l1, resolveDirection as l2, resolveDirection8 as l3, resolveFontVariant as l4, resolveHyperlinkHref as l5, resolveInteractiveElementId as l6, resolveLegendAnchorPosition as l7, resolveMediaSrc as l8, resolveOleType as l9, scanAvailableFonts as lA, searchSlides as lB, seedBroadcastFields as lC, seedHyperlinkDraft as lD, seedPropertiesDraft as lE, seedShareFields as lF, segmentFrameCount as lG, selectValue$3 as lH, sendBackward as lI, sendToBack as lJ, sequentialColorScale as lK, serializeWriteBack as lL, seriesColor as lM, seriesXValues as lN, setAfterAnimation as lO, setAfterAnimationColor as lP, setAnimationEmphasis as lQ, setAnimationEntrance as lR, setAnimationExit as lS, setAxis as lT, setAxisLogScale as lU, setAxisTitleStyle as lV, setCategoryLabel as lW, setCellText as lX, setColorScheme as lY, setDataLabels as lZ, setDataPointExplosion as l_, resolveOrientation as la, resolveParagraphBullet as lb, resolvePresenterNotes as lc, resolveProfileInitial as ld, resolveRegionCode as le, resolveRibbonCanGroup as lf, resolveSlideAutoAdvanceMs as lg, resolveSlideTransition as lh, resolvePalette as li, resolveThemeCatalogEntry as lj, resolveTransitionDuration as lk, resolveTransitionDurationMs as ll, resolveWheelSpokeCount as lm, restoreSessionDeck as ln, revealedElementStyles as lo, routeOrthogonalConnector as lp, rowStyle as lq, rulerDragToGuidePosition as lr, rulerHighlight as ls, rulerStripTicks as lt, sampleColorFromSlide as lu, sanitizeColor as lv, sanitizeSlideIndex as lw, sanitizeUserName as lx, saveViewerProfile as ly, savedPresentationFileName as lz, createSurfaceChart3DScene as m, themeStyle as m$, setDataPointLabel as m0, setDataPointMarker as m1, setDelay as m2, setDirection as m3, setDuration as m4, setEffectSound as m5, setEffectStockSound as m6, setElementPosition as m7, setGridlineStyle as m8, setLayout as m9, signatureTimestamp as mA, signerName as mB, statusLabel as mC, slideNumberOf as mD, slidesWithReappliedLayout as mE, smartArtNodes as mF, paletteColour as mG, snapToGridStep as mH, splitCursorCell as mI, splitMergedCell as mJ, statusKind as mK, statusLabel$1 as mL, storeAudienceContent as mM, stringFromEvent$5 as mN, strokeColorOf as mO, strokeToInkElement as mP, strokeWidthOf as mQ, styleShadowFilter as mR, surfaceColor as mS, textAdvancedPatch as mT, textAdvancedStateFromStyle as mU, textAdvancedStateOf as mV, textColorOf as mW, textDirectionPatch as mX, textFontSizePatch as mY, textStyleOf as mZ, textStylePatch as m_, setLegend as ma, setNodeStyle as mb, setNodeText as mc, setRepeatCount as md, setRepeatMode as me, setSequence as mf, setSeriesChartType as mg, setSeriesColor as mh, setSeriesErrorBars as mi, setSeriesMarker as mj, setSeriesName as mk, setSeriesTrendline as ml, setSeriesValue as mm, setStyle as mn, setTimingCurve as mo, setTitle as mp, setTrigger as mq, setTriggerShapeId as mr, shapeStylePatch$1 as ms, sheetAfterNavigate as mt, shouldBlockClickAdvance as mu, shouldUseSvgWarp as mv, showDirectionPicker as mw, showsTemplateAffordance as mx, signatureCountLabel as my, signatureKey as mz, createPieChart3DScene as n, themeToCssVars as n0, thumbnailHeight as n1, thumbnailZoom as n2, toggleCommentResolvedInList as n3, toggleNodeBold as n4, toggleNodeItalic as n5, toggleSheet as n6, topLevelNodeCount as n7, transformSelectedTextCase as n8, translationsEn as n9, updateElementById as na, updateGlowPatch as nb, updateGradientStopPatch as nc, updateInnerShadowPatch as nd, updateOuterShadowPatch as ne, updateReflectionPatch as nf, vAlignPatch as ng, validatePassword as nh, validatePrintSettings as ni, validateRoomId as nj, valueToY as nk, vermilionDarkColors as nl, vermilionDarkTheme as nm, vermilionLightColors as nn, vermilionLightTheme as no, vermilionRadius as np, waypointsToPathD as nq, withManualLayouts as nr, worstStatus as ns, zoomTargetSlideIndex as nt, obliqueBarTaper as o, perspBoxMatrix as p, createAreaChart3DScene as q, renderChartLineSvg as r, shade as s, toChatSummary as t, createLineChart3DScene as u, createBarChart3DScene as v, widenPieViewModel as w, hexToRgb as x, smartArt3DGradientColor as y, shadeSmartArt3DVertices as z };
214898
- //# sourceMappingURL=pptx-angular-viewer-pptx-angular-viewer-BWuuwItt.mjs.map
211921
+ export { AiProposalCardComponent as $, smartArt3DEyeInDiagram as A, resolveSmartArt3DLightModel as B, AFTER_ANIMATION_VALUES as C, DEFAULT_PALETTE$1 as D, ALIGN_OPTIONS as E, ANIMATION_PRESET_CATEGORIES as F, AUDIENCE_HASH as G, AUDIENCE_NONCE_KEY as H, AVATAR_COLOR_SWATCHES as I, AXIS_LABEL_COLOR as J, AccessibilityPanelComponent as K, AccessibilityService as L, AccessibilityTextPanelComponent as M, NO_THREE_VIEW_OVERFLOW as N, AccountPageComponent as O, ActionSettingsPanelComponent as P, AdvancedChartEditorComponent as Q, AiChangeOverlayComponent as R, AiChatPanelComponent as S, AiChatService as T, AiComposerComponent as U, AiFocusBarComponent as V, AiFocusHighlightOverlayComponent as W, AiHistoryMenuComponent as X, AiHistoryService as Y, AiMessageListComponent as Z, AiPanelStore as _, appendChartLegendSvg as a, ELEMENT_CONTEXT_MENU_COMMAND_IDS as a$, AiSettingsSectionComponent as a0, AiToolCallCardComponent as a1, AnimationAuthorPanelComponent as a2, AnimationPanelComponent as a3, AnimationPlaybackService as a4, AutosaveRecoveryDialogComponent as a5, AutosaveService as a6, BACKSTAGE_CARD_IDS as a7, BACKSTAGE_PAGE_IDS as a8, BroadcastDialogComponent as a9, CommentsService as aA, ComparePanelComponent as aB, ConnectorRendererComponent as aC, ConnectorTextOverlayComponent as aD, CustomShowsComponent as aE, DEFAULT_BOUNDS as aF, DEFAULT_BROADCAST_SERVER_URL as aG, DEFAULT_CANVAS_HEIGHT as aH, DEFAULT_CANVAS_WIDTH as aI, DEFAULT_COLOR_SCHEME as aJ, DEFAULT_FILL_COLOR$1 as aK, DEFAULT_LAYOUT as aL, DEFAULT_MORPH_DURATION_MS as aM, DEFAULT_PATTERN_FILL_PRESET as aN, DEFAULT_PRINT_SETTINGS as aO, DEFAULT_SLIDE_BACKGROUND as aP, DEFAULT_STROKE_COLOR as aQ, DEFAULT_STYLE as aR, DEFAULT_TABLE_ROW_HEIGHT as aS, DEFAULT_TEXT_COLOR$2 as aT, DEFAULT_VIEWER_PROFILE as aU, DIRECTIONAL_PRESETS as aV, DIRECTION_OPTIONS as aW, DocumentPropertiesCardComponent as aX, EASE as aY, EDITOR_SHORTCUT_ACTION_IDS as aZ, EFFECT_SOUND_CATALOGUE as a_, CANVAS_CONTEXT_MENU_COMMAND_IDS as aa, CHART_EDITOR_STYLES as ab, CINEMATIC_TRANSITION_KEYFRAMES as ac, CURSOR_PALETTE as ad, CanvasFitService as ae, ChartAxisOptionsComponent as af, ChartAxisStyleOptionsComponent as ag, ChartComboTypeOptionsComponent as ah, ChartDataEditorComponent as ai, ChartDataLabelOptionsComponent as aj, ChartDatapointMarkerOptionsComponent as ak, ChartDatapointOptionsComponent as al, ChartDisplayOptionsComponent as am, ChartElementViewComponent as an, ChartErrorBarOptionsComponent as ao, ChartMarkerOptionsComponent as ap, ChartPartSelectionService as aq, ChartPrimitivesComponent as ar, ChartRendererComponent as as, ChartTrendlineOptionsComponent as at, ChartTypeSelectorComponent as au, CollaborationCursorsComponent as av, CollaborationService as aw, ColorChangedImageComponent as ax, CommentMarkersOverlayComponent as ay, CommentsPanelComponent as az, renderChartTextSvg as b, NotesToolbarComponent as b$, EMBEDDED_FONTS_STYLE_ID as b0, EMPHASIS_PRESETS as b1, ENTRANCE_PRESETS as b2, TEMPLATES as b3, EXIT_PRESETS as b4, EditorContextMenuComponent as b5, EditorHistory as b6, EditorStateService as b7, EditorToolbarComponent as b8, EffectsPanelComponent as b9, InsertSmartArtDialogComponent as bA, InspectorPaneHeaderComponent as bB, InspectorPanelComponent as bC, IsMobileService as bD, KeepAnnotationsDialogComponent as bE, LOCALE_CATALOG as bF, LONG_PRESS_DURATION_MS as bG, LONG_PRESS_MOVE_TOLERANCE_PX as bH, LoadContentService as bI, LocalPresencePublisher as bJ, MAX_ZOOM_SCALE as bK, MIN_ZOOM_SCALE as bL, MOTION_PATH_COLUMNS as bM, MediaPreviewComponent as bN, MediaPropertiesPanelComponent as bO, MediaRendererComponent as bP, MediaTrimTimelineComponent as bQ, MobileBottomBarComponent as bR, MobileMenuSheetComponent as bS, MobilePresenterViewComponent as bT, MobileSheetComponent as bU, MobileSlidesSheetComponent as bV, MobileToolbarComponent as bW, ModalDialogComponent as bX, Model3DRendererComponent as bY, NotesHandoutCardComponent as bZ, NotesPanelComponent as b_, ElementRendererComponent as ba, EmbeddedFontsService as bb, EncryptedFileDialogComponent as bc, EquationEditorDialogComponent as bd, EquationRendererComponent as be, EquationTemplateGalleryComponent as bf, ExportProgressModalComponent as bg, ExportService as bh, FieldContextService as bi, FindBarComponent as bj, FindReplaceBarComponent as bk, FollowModeBarComponent as bl, FontEmbeddingListComponent as bm, FontEmbeddingPanelComponent as bn, GALLERY_THEME_PRESETS as bo, GOOGLE_WEBFONTS_LINK_ID as bp, GRIDLINE_COLOR$1 as bq, GoogleWebfontsService as br, GradientPickerComponent as bs, HANDOUT_OPTIONS as bt, HeaderFooterDialogComponent as bu, HyperlinkDialogComponent as bv, INSTANT as bw, ImagePropertiesPanelComponent as bx, InkDrawingService as by, InkRendererComponent as bz, createSvgEl as c, RibbonSlideshowSectionComponent as c$, OPTIONS_PAGE_IDS as c0, OPTIONS_SECTION_IDS as c1, OPTIONS_SETTING_IDS as c2, OleRendererComponent as c3, OutlineViewOverlayComponent as c4, P14_TRANSITION_KEYFRAMES as c5, P14_TRANSITION_KEYFRAMES_2 as c6, P14_TRANSITION_KEYFRAMES_ALL as c7, POWER_POINT_VIEWER_PROVIDERS as c8, PPTX_OPEN_ACCEPT as c9, RESIZE_HANDLES as cA, RIBBON_TAB_IDS as cB, RULER_FONT_SIZE as cC, RULER_THICKNESS as cD, ReadingViewOverlayComponent as cE, RemoteSelectionOverlayComponent as cF, Rendering3DService as cG, RibbonAnimationGalleryComponent as cH, RibbonAnimationsSectionComponent as cI, RibbonArrangeSectionComponent as cJ, RibbonColorPopoverComponent as cK, RibbonComponent as cL, RibbonDesignSectionComponent as cM, RibbonDrawSectionComponent as cN, RibbonDrawingGroupComponent as cO, RibbonEditingSectionComponent as cP, RibbonFileSectionComponent as cQ, RibbonFontControlsComponent as cR, RibbonHomeSectionComponent as cS, RibbonHyperlinkButtonComponent as cT, RibbonInsertFieldsComponent as cU, RibbonInsertSectionComponent as cV, RibbonMotionPathGalleryComponent as cW, RibbonParagraphControlsComponent as cX, RibbonPrimaryRowComponent as cY, RibbonReviewSectionComponent as cZ, RibbonShapeExtrasComponent as c_, PRESENTATION_OPEN_EXTENSIONS as ca, PRESENTER_CHANNEL_NAME as cb, PRESENTER_MSG_ORIGIN as cc, PRESENTER_TIMER_SEGMENT_MS as cd, PX_PER_CM as ce, PX_PER_INCH as cf, PasswordProtectionDialogComponent as cg, PasswordStrengthMeterComponent as ch, PowerPointViewerComponent as ci, PresentToolbarAutoHide as cj, PresentationAnnotationOverlayComponent as ck, PresentationAnnotationsService as cl, PresentationOverlayComponent as cm, PresentationPropertiesPanelComponent as cn, PresentationSettingsCardComponent as co, PresentationSubtitleBarComponent as cp, PresentationToolbarComponent as cq, PresentationTransitionOverlayComponent as cr, PresenterViewComponent as cs, PresenterWindowService as ct, PrintDialogComponent as cu, PrintService as cv, PrintSettingsPanelComponent as cw, PropertiesDialogComponent as cx, RANDOM_ELIGIBLE_TYPES as cy, REPEAT_MODE_OPTIONS as cz, buildBarChart3DHoverTooltip as d, TableSelectionService as d$, RibbonTransitionsSectionComponent as d0, RibbonViewSectionComponent as d1, RulerGuidesService as d2, SEQUENCE_OPTIONS as d3, SEVERITY_GROUPS as d4, SEVERITY_LABELS as d5, DEFAULT_TRANSITION_DURATION_MS$1 as d6, SHORTCUT_REFERENCE_ITEMS as d7, SLIDE_TRANSITION_KEYFRAMES as d8, SLIDE_TRANSITION_KEYFRAMES_CSS as d9, SlideDiffThumbnailsComponent as dA, SlideSizeCardComponent as dB, SlideSorterOverlayComponent as dC, SlideThemeOverridePanelComponent as dD, SlideTransitionCardComponent as dE, SlidesPanelComponent as dF, SmartArt3DRendererComponent as dG, SmartArtPreviewComponent as dH, SmartArtPropertiesComponent as dI, SmartArtRendererComponent as dJ, StatusBarComponent as dK, TABLE_STRUCTURE_TOGGLES as dL, TEXT_3D_BOTTOM_BEVEL_KEYS as dM, TEXT_3D_TOP_BEVEL_KEYS as dN, TEXT_DIRECTION_OPTIONS$1 as dO, THEME_CATALOG as dP, TIMING_CURVE_OPTIONS as dQ, TOOLBAR_BUTTON_IDS as dR, TRANSITION_SPEED_DURATION_MS as dS, TRIGGER_OPTIONS as dT, TYPE_LABELS as dU, TableCellAdvancedFillComponent as dV, TableCellFormattingComponent as dW, TableDataEditorComponent as dX, TablePropertiesComponent as dY, TableRendererComponent as dZ, TableResizeOverlayComponent as d_, DEFAULT_PALETTE as da, PALETTES as db, SMART_ART_COLOR_SCHEMES as dc, SMART_ART_STYLE_OPTIONS as dd, SUB_ITEM_LABEL as de, SVG_WARP_PRESETS as df, SWIPE_MAX_VERTICAL_PX as dg, SWIPE_THRESHOLD_PX as dh, SelectionPaneComponent as di, SetUpSlideShowDialogComponent as dj, SettingsAppearanceTabComponent as dk, SettingsDialogComponent as dl, SettingsLanguageTabComponent as dm, ShareDialogComponent as dn, ShortcutPanelComponent as dp, ShowOptionsFieldsetComponent as dq, ShowSlidesFieldsetComponent as dr, SignatureStrippedDialogComponent as ds, SignaturesPanelComponent as dt, SignaturesService as du, SlideBackgroundCardComponent as dv, SlideCanvasComponent as dw, SlideDefaultInspectorComponent as dx, SlideDiffChangesComponent as dy, SlideDiffRowComponent as dz, roundDragValue as e, applyFormatToElement as e$, TagsCardComponent as e0, Text3DBevelSectionComponent as e1, Text3DPanelComponent as e2, TextAdvancedPanelComponent as e3, ThemeEditorFieldsComponent as e4, ThemeGalleryComponent as e5, ThemeSelectorCardComponent as e6, TitleBarComponent as e7, TitleBarSearchComponent as e8, TransitionDirectionPickerComponent as e9, ViewerPresentationModeService as eA, ViewerThemeGalleryService as eB, ViewerTouchGesturesService as eC, ViewerZoomService as eD, WEBM_MIME_CANDIDATES as eE, WHEEL_SPOKE_COUNTS as eF, WriteBackScheduler as eG, ZERO_LINE_COLOR as eH, ZoomNavigationService as eI, ZoomRendererComponent as eJ, ZoomTargetService as eK, addCategory as eL, addCommentToList as eM, addGradientStopPatch as eN, addItem as eO, addSeries as eP, addSubItem as eQ, advanceStep as eR, affordanceElements as eS, aiToggleVisible as eT, alignPatch as eU, animationFor as eV, animationPresetLabelKey as eW, annotationMapToInkInserts as eX, applyAcceptedDiff as eY, applyAnimationPreset as eZ, applyFindReplacements as e_, TransitionPreviewComponent as ea, VALIGN_OPTIONS as eb, VIEWER_DIALOG_IDS as ec, VIEWER_EXPORT_FORMAT_IDS as ed, VIEWER_FEATURE_IDS as ee, VIEWER_PANEL_IDS as ef, VIEWER_THEME as eg, VersionHistoryPanelComponent as eh, ViewerCanvasEditingService as ei, ViewerCollabCursorService as ej, ViewerCollaborationSessionService as ek, ViewerCollaborationShellService as el, ViewerCompareService as em, ViewerCustomShowsService as en, ViewerCustomizationHandle as eo, ViewerCustomizationService as ep, ViewerDialogsService as eq, ViewerDocumentPropertiesService as er, ViewerExportService as es, ViewerExtraDialogsComponent as et, ViewerFileIOService as eu, ViewerFindReplaceService as ev, ViewerFormatPainterService as ew, ViewerInspectorPanelService as ex, ViewerKeyboardService as ey, ViewerMobileSheetService as ez, buildPerspCamera as f, buildZeroLine as f$, applyMove as f0, applyResize as f1, asMediaElement as f2, assignUserColor as f3, attachShowVisibilityPause as f4, attachTouchGestures as f5, axisTickValues as f6, beginDrawingNodeEdit as f7, beginNodeEdit as f8, bevelSizePatch as f9, buildGridlinesAndLabels as fA, buildHyperlinkPatch as fB, buildInkContainerStyle as fC, buildInkStrokes as fD, buildLegend as fE, buildLiveInkStrokeView as fF, buildMarkTooltip as fG, buildModel3DContainerStyle as fH, buildModel3DViewModel as fI, buildOleActionModel as fJ, buildOleInfoRows as fK, buildPatternFillCss as fL, buildPieViewModel as fM, buildPrintHtmlDocument as fN, buildPropertiesPatch as fO, buildRadarViewModel as fP, buildRegionMapViewModel as fQ, buildSaveSlides as fR, buildShareUrl as fS, buildSmartArtInsertElement as fT, buildSmartArtNodes as fU, buildStockViewModel as fV, buildSurfaceViewModel as fW, buildTableViewModel as fX, buildTreemapViewModel as fY, buildTrimFragment as fZ, buildWaterfallViewModel as f_, boolFromEvent as fa, bringForward as fb, bringToFront as fc, buildBarActions as fd, buildBroadcastConfig as fe, buildBroadcastViewerUrl as ff, buildCategoryLabels as fg, buildCellParagraphs as fh, buildChartViewModel as fi, buildChatLogExport as fj, buildChatLogMarkdown as fk, buildChromeStyle as fl, buildClearHyperlinkPatch as fm, buildClickGroups as fn, buildColStyles as fo, buildCollaborationConfig as fp, buildComboViewModel as fq, buildCssGradientFromShapeStyle as fr, buildDuotoneFilter as fs, buildDuotoneFilterId as ft, buildEmbeddedFontStyles as fu, buildEquationElement as fv, buildEquationSegment as fw, buildFallbackViewModel as fx, buildFontFaceRule as fy, buildGradientFillCss as fz, perspToScreen as g, computeScatterXDomain as g$, buildZoomContainerStyle as g0, buildZoomViewModel as g1, bulletIndentPx as g2, canAddTopLevelNode as g3, canEditSmartArtNodes as g4, canGroupSelection as g5, canRemoveTopLevelNode as g6, canSetStrokeWidth as g7, canStartBroadcast as g8, canStartShare as g9, commitNodeText as gA, computeAlign as gB, computeAxisTitlePrimitives as gC, computeBarRects as gD, computeBubbleRadius as gE, computeCornerHandle as gF, computeDistribute as gG, computeDrawingViewBox as gH, computeErrorBarPrimitives as gI, computeFocusTargets as gJ, computeGridSpacingPx as gK, computeHandleBoxes as gL, computeHandoutLayout as gM, computeIsMobile as gN, computeIsTablet as gO, computeLinePoints as gP, computeLinearRegression as gQ, computePageCount as gR, computePieLayout as gS, computePieSlicePath as gT, computePieSlices as gU, computePlotLayout as gV, computeRSquared as gW, computeRadarPoints as gX, computeResizeHandleBoxes as gY, computeRotateHandleBox as gZ, computeScatterDots as g_, canUngroupSelection as ga, canUseClipboard as gb, captionDisplayText as gc, cellRunStyle as gd, cellStyleToStyleMap as ge, cellTdStyle as gf, changeCountLabel as gg, changeIcon as gh, characterSpacingPatch as gi, chartPreserveAspectRatio as gj, checkFontAvailable as gk, clampCursorPosition as gl, clampGifDimensions as gm, clampIndex as gn, clampNotesFontSize as go, clampScale as gp, clampStep as gq, clearAllLocalViewerData as gr, clearAudienceContent as gs, cn as gt, collectAccessibilityIssues as gu, collectElementText as gv, collectSlideText as gw, collectStoredChats as gx, collectUsedFontFamilies as gy, columnWidthStyle as gz, pieRimPoint as h, forgetSessionDeck as h$, computeSelectionBoxes as h0, computeSingleSelected as h1, computeSlideIndices as h2, computeSnap as h3, computeStackedBarRects as h4, computeStackedValueRange as h5, computeTrendlinePrimitives as h6, computeValueRange as h7, convertOmmlToMathMl as h8, copyFormatFromElement as h9, disableReflectionPatch as hA, disableSoftEdgePatch as hB, duplicateElementById as hC, durationOf as hD, effectsStateOf as hE, enableGlowPatch as hF, enableInnerShadowPatch as hG, enableOuterShadowPatch as hH, enableReflectionPatch as hI, enableSoftEdgePatch as hJ, encodeGif as hK, endShowMediaCleanup as hL, estimatePageCount as hM, exitPresentationFullscreen as hN, exportAiChatLogs as hO, extractPathPoints as hP, eyedropperAvailable as hQ, fillColorOf$1 as hR, findInSlides as hS, findOwningSlideIndex as hT, findSlideIndexByElementId as hU, firstVisibleIndex as hV, fitPolynomial as hW, fitZoom as hX, focusTargetChips as hY, fontMimeForFormat as hZ, fontSizeOf as h_, countAccessibilityIssues as ha, countAnnotationStrokes as hb, createAngularAiBridge as hc, createCustomShow as hd, createImageElementFromFile as he, createSwipeDismissDrag as hf, createWebrtcBundle as hg, createWebsocketBundle as hh, cssObjectToStyleMap as hi, currentColorScheme as hj, currentLayout as hk, currentStyle as hl, defaultCssVars as hm, defaultRadius as hn, defaultThemeColors as ho, deleteElementsByIds as hp, deleteVersion as hq, demoteNode as hr, deriveModel3DBlobUrl as hs, derivePresenceList as ht, describeCollaborationShellState as hu, describeSmartArtBounds as hv, directionPickerState as hw, disableGlowPatch as hx, disableInnerShadowPatch as hy, disableOuterShadowPatch as hz, isChartInteractionArmed as i, injectResolvedCustomization as i$, formatAxisValue as i0, formatBytes as i1, formatCursorLabel as i2, formatElapsed as i3, formatFileSize as i4, formatInlineListStyle as i5, formatPropertyDate as i6, formatTime as i7, fpsToFrameIntervalMs as i8, generateBroadcastRoomId as i9, getResolvedShapeClipPathFor as iA, getSessionTabId as iB, getShapeFillStrokeStyle as iC, getSlideBackgroundStyle as iD, getSlideTransitionAnimations as iE, getSmartArtNodeBounds as iF, getSpeechRecognitionCtor as iG, getTextBlockStyle as iH, getTextWarp as iI, getTouchDistance as iJ, getWarpCategory as iK, getWarpPath as iL, gradientStateFromStyle as iM, gradientStateOf as iN, gradientStatePatch as iO, gradientStopColorCommitPatch as iP, gridColumns as iQ, groupIssuesBySeverity as iR, hasAnimation as iS, hasCopyableFormat as iT, hasExistingLink as iU, hasExitedFullscreen as iV, hasGradientFill as iW, hasPressureVariation as iX, hasVisibleSlideAfter as iY, headerLabel as iZ, imageDimensions as i_, generateCommentId as ia, generateCustomShowId as ib, generatePressureCircles as ic, generateTicks as id, getCinematicTransitionAnimations as ie, getClrChangeParams as ig, getContainerStyle as ih, getDuotoneFilterDef as ii, getEffectSoundAsset as ij, getEffectSoundState as ik, getImageSrc as il, getLocalStorageUsageSummary as im, getOleAriaLabel as io, getOleBadgeLabel as ip, getOleDisplayName as iq, getOleDownloadFileName as ir, getOleTypeColor as is, getOleTypeLabel as it, getP14TransitionAnimations as iu, getPasswordStrength as iv, getPatternSvg as iw, getPlaceholderStyle as ix, getVersions as iy, getResolvedShapeClipPath as iz, computePieChartLayout as j, normalizeFontFormat as j$, inkViewBox as j0, insertTableElementColumn as j1, insertTableElementRow as j2, interpolateWidth as j3, isAudienceTab as j4, isBold as j5, isBrowserOpenableMime as j6, isChildNode as j7, isElementInteractive as j8, isInjectableUrl as j9, mergeCaptionResults as jA, mergeDown as jB, mergeRight as jC, mergeSelection as jD, mergeTablesDirective as jE, moveElementBy as jF, moveNodeDown as jG, moveNodeUp as jH, msToFrameDelayCs as jI, narrowToCircle as jJ, narrowToPolygon as jK, narrowToRect as jL, newChartElement as jM, newEquationElement as jN, newPresetShapeElement as jO, newShapeElement as jP, newSmartArtElement as jQ, newTableElement as jR, newTextElement as jS, nextVisibleIndex as jT, nodeBold as jU, nodeEditBox as jV, nodeFillColor as jW, nodeFontColor as jX, nodeIdFromKey as jY, nodeItalic as jZ, nodeStyle as j_, isItalic as ja, isLegacyBinaryPresentation as jb, isPpactionUrl as jc, isPresenterMessage as jd, isSigned as je, isSupportedPresentationFile as jf, isTextElement as jg, isTwoTableFocus as jh, isUnderline as ji, isUrlSafe as jj, isValidRoomId as jk, isViewportBackgroundPressTarget as jl, isZoomActivationKey as jm, issueTrackKey as jn, issueTypeLabel as jo, keyToLabel as jp, lastVisibleIndex as jq, latexToMathml as jr, layoutConnectorPaints as js, layoutNodeLabels as jt, linePointsToSvgString as ju, lineSpacingPatch as jv, loadAudienceContent as jw, loadSessionDeck as jx, mediaFallbackFor as jy, mediaSurfaceFor as jz, perspScreenToPlaneY as k, resolveHyperlinkHref as k$, normalizeSlidesPerPage as k0, normalizeValue as k1, numFromEvent as k2, oleActionsVisibleFor as k3, ommlToMathml as k4, ooxmlDashToCssBorderStyle as k5, openNativeEyeDropper as k6, overallStatus as k7, paletteColor as k8, parseAudienceNonce as k9, radarAngle as kA, radarRingPoints as kB, readAsDataUrl as kC, recordWebm as kD, registerCrossSlideAudio as kE, rememberSessionDeck as kF, removeAnimation as kG, removeCategory as kH, removeTableElementColumn as kI, removeCommentFromList as kJ, removeElementAnimation as kK, removeGradientStopPatch as kL, removeNode as kM, removeTableElementRow as kN, removeSeries as kO, renderToCanvas as kP, reorderAnimationDown as kQ, reorderAnimationUp as kR, replaceInSlides as kS, replaceMatch as kT, requestPresentationFullscreen as kU, resizeElement as kV, resolveCaptionTracks as kW, resolveChartKind as kX, resolveDirection as kY, resolveDirection8 as kZ, resolveFontVariant as k_, parseNodeTextarea as ka, partitionSlides as kb, patchChartData as kc, patchChartStyle as kd, patchTableData as ke, patchTextStyle as kf, patternPresetOptions as kg, pendingElementStyles as kh, pickColorByClickFallback as ki, pickFile as kj, pickSupportedMimeType as kk, planGifFrames as kl, planVideoSegments as km, pointFromPointerEvent as kn, pointsToSvgPathD as ko, presenceToCursors as kp, presentationBaseName as kq, presentationStageStyle as kr, presenterTimerProgress as ks, presetByLayout as kt, presetsForCategory as ku, pressuresToWidths as kv, prevVisibleIndex as kw, projectDrawingShapes as kx, promoteNode as ky, provideViewerTheme as kz, resolvePieSliceShareValue as l, setEffectSound as l$, resolveInteractiveElementId as l0, resolveLegendAnchorPosition as l1, resolveMediaSrc as l2, resolveOleType as l3, resolveOrientation as l4, resolveParagraphBullet as l5, resolvePresenterNotes as l6, resolveProfileInitial as l7, resolveRegionCode as l8, resolveRibbonCanGroup as l9, segmentFrameCount as lA, selectValue$3 as lB, sendBackward as lC, sendToBack as lD, sequentialColorScale as lE, serializeWriteBack as lF, seriesColor as lG, seriesXValues as lH, setAfterAnimation as lI, setAfterAnimationColor as lJ, setAnimationEmphasis as lK, setAnimationEntrance as lL, setAnimationExit as lM, setAxis as lN, setAxisLogScale as lO, setAxisTitleStyle as lP, setCategoryLabel as lQ, setCellText as lR, setColorScheme as lS, setDataLabels as lT, setDataPointExplosion as lU, setDataPointFill as lV, setDataPointLabel as lW, setDataPointMarker as lX, setDelay as lY, setDirection as lZ, setDuration as l_, resolveSlideAutoAdvanceMs as la, resolveSlideTransition as lb, resolvePalette as lc, resolveThemeCatalogEntry as ld, resolveTransitionDuration as le, resolveTransitionDurationMs as lf, resolveWheelSpokeCount as lg, restoreSessionDeck as lh, revealedElementStyles as li, routeOrthogonalConnector as lj, rowStyle as lk, rulerDragToGuidePosition as ll, rulerHighlight as lm, rulerStripTicks as ln, sampleColorFromSlide as lo, sanitizeColor as lp, sanitizeSlideIndex as lq, sanitizeUserName as lr, saveViewerProfile as ls, savedPresentationFileName as lt, scanAvailableFonts as lu, searchSlides as lv, seedBroadcastFields as lw, seedHyperlinkDraft as lx, seedPropertiesDraft as ly, seedShareFields as lz, hexToRgb as m, toggleNodeItalic as m$, setEffectStockSound as m0, setElementPosition as m1, setGridlineStyle as m2, setLayout as m3, setLegend as m4, setNodeStyle as m5, setNodeText as m6, setRepeatCount as m7, setRepeatMode as m8, setSequence as m9, paletteColour as mA, snapToGridStep as mB, splitCursorCell as mC, splitMergedCell as mD, statusKind as mE, statusLabel$1 as mF, storeAudienceContent as mG, stringFromEvent$5 as mH, strokeColorOf as mI, strokeToInkElement as mJ, strokeWidthOf as mK, styleShadowFilter as mL, surfaceColor as mM, textAdvancedPatch as mN, textAdvancedStateFromStyle as mO, textAdvancedStateOf as mP, textColorOf as mQ, textDirectionPatch as mR, textFontSizePatch as mS, textStyleOf as mT, textStylePatch as mU, themeStyle as mV, themeToCssVars as mW, thumbnailHeight as mX, thumbnailZoom as mY, toggleCommentResolvedInList as mZ, toggleNodeBold as m_, setSeriesChartType as ma, setSeriesColor as mb, setSeriesErrorBars as mc, setSeriesMarker as md, setSeriesName as me, setSeriesTrendline as mf, setSeriesValue as mg, setStyle as mh, setTimingCurve as mi, setTitle as mj, setTrigger as mk, setTriggerShapeId as ml, shapeStylePatch$1 as mm, sheetAfterNavigate as mn, shouldBlockClickAdvance as mo, shouldUseSvgWarp as mp, showDirectionPicker as mq, showsTemplateAffordance as mr, signatureCountLabel as ms, signatureKey as mt, signatureTimestamp as mu, signerName as mv, statusLabel as mw, slideNumberOf as mx, slidesWithReappliedLayout as my, smartArtNodes as mz, smartArt3DGradientColor as n, toggleSheet as n0, topLevelNodeCount as n1, transformSelectedTextCase as n2, translationsEn as n3, updateElementById as n4, updateGlowPatch as n5, updateGradientStopPatch as n6, updateInnerShadowPatch as n7, updateOuterShadowPatch as n8, updateReflectionPatch as n9, vAlignPatch as na, validatePassword as nb, validatePrintSettings as nc, validateRoomId as nd, valueToY as ne, vermilionDarkColors as nf, vermilionDarkTheme as ng, vermilionLightColors as nh, vermilionLightTheme as ni, vermilionRadius as nj, waypointsToPathD as nk, withManualLayouts as nl, worstStatus as nm, zoomTargetSlideIndex as nn, obliqueBarTaper as o, perspBoxMatrix as p, shadeSmartArt3DVertices as q, renderChartLineSvg as r, shade as s, toChatSummary as t, smartArt3DGradientCoordinate as u, threeViewOverflowFromNdc as v, widenPieViewModel as w, hasThreeViewOverflow as x, overflowViewOffset as y, smartArt3DCameraMatrix as z };
211922
+ //# sourceMappingURL=pptx-angular-viewer-pptx-angular-viewer-Dy62s3Dt.mjs.map