@jsamuel1/pptxgenjs 4.1.3 → 4.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/pptxgen.bundle.js +2 -95
- package/dist/pptxgen.bundle.js.map +1 -1
- package/dist/pptxgen.cjs.js +295 -31
- package/dist/pptxgen.es.js +295 -31
- package/dist/pptxgen.min.js +2 -95
- package/dist/pptxgen.min.js.map +1 -1
- package/dist/utils.cjs.js +148 -0
- package/dist/utils.es.js +146 -0
- package/dist/utils.js +148 -0
- package/package.json +25 -26
- package/types/index.d.ts +41 -0
- package/types/utils.d.ts +40 -0
package/dist/pptxgen.cjs.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/* PptxGenJS 4.1.
|
|
1
|
+
/* PptxGenJS 4.1.4 @ 2026-06-08T05:10:40.789Z */
|
|
2
2
|
'use strict';
|
|
3
3
|
|
|
4
4
|
var JSZip = require('jszip');
|
|
@@ -844,11 +844,14 @@ function genXmlGradientFill(props) {
|
|
|
844
844
|
const stops = [...props.stops].sort((a, b) => (a.position || 0) - (b.position || 0));
|
|
845
845
|
const gsList = stops
|
|
846
846
|
.map(stop => {
|
|
847
|
-
// position 0–100 → `pos` in thousandths of a percent (× 1000)
|
|
848
|
-
|
|
847
|
+
// position 0–100 → `pos` in thousandths of a percent (× 1000).
|
|
848
|
+
// `pos` is ST_PositiveFixedPercentage [0,100000]; clamp the 0–100 input
|
|
849
|
+
// before scaling so out-of-range stops stay schema-valid (clamp-don't-crash).
|
|
850
|
+
const pos = Math.round(Math.max(0, Math.min(100, stop.position || 0)) * 1000);
|
|
849
851
|
// Per-stop transparency uses PROMPT.md direct mapping (100 = opaque → 100000; 40 → 40000).
|
|
850
852
|
// NOTE: this differs from the solid-fill path which inverts via `(100 - transparency) * 1000`.
|
|
851
|
-
|
|
853
|
+
// `a:alpha@val` is also ST_PositiveFixedPercentage [0,100000]; clamp into [0,100] first.
|
|
854
|
+
const inner = typeof stop.transparency === 'number' ? `<a:alpha val="${Math.round(Math.max(0, Math.min(100, stop.transparency)) * 1000)}"/>` : '';
|
|
852
855
|
return `<a:gs pos="${pos}">${createColorElement(stop.color, inner)}</a:gs>`;
|
|
853
856
|
})
|
|
854
857
|
.join('');
|
|
@@ -1091,6 +1094,75 @@ function svgPathToOoxml(svgPathD, width, height) {
|
|
|
1091
1094
|
}
|
|
1092
1095
|
return `<a:custGeom><a:avLst/><a:gdLst/><a:ahLst/><a:cxnLst/><a:rect l="l" t="t" r="r" b="b"/><a:pathLst><a:path w="${pathW}" h="${pathH}">${xml}</a:path></a:pathLst></a:custGeom>`;
|
|
1093
1096
|
}
|
|
1097
|
+
/**
|
|
1098
|
+
* Compute evenly-spaced grid cell positions within a bounding area.
|
|
1099
|
+
* - Pure math utility (no OOXML emission); returns one `{ x, y, w, h }` (inches) per item, in item order.
|
|
1100
|
+
* - Eliminates repetitive grid-math when positioning capability cards, icon grids, comparison layouts, etc.
|
|
1101
|
+
*
|
|
1102
|
+
* Calculation (per item `i`):
|
|
1103
|
+
* cellW = (area.w - (columns - 1) * gapX) / columns
|
|
1104
|
+
* rows = ceil(items / columns)
|
|
1105
|
+
* cellH = (area.h - (rows - 1) * gapY) / rows
|
|
1106
|
+
* col = i % columns; row = floor(i / columns)
|
|
1107
|
+
* x = area.x + col * (cellW + gapX); y = area.y + row * (cellH + gapY)
|
|
1108
|
+
*
|
|
1109
|
+
* @param {LayoutGridProps} props - grid options
|
|
1110
|
+
* @returns {LayoutGridResult} array of `{ x, y, w, h }` cells (inches), one per item
|
|
1111
|
+
* @throws {Error} when `area` has zero/negative width or height
|
|
1112
|
+
* @example pptx.layoutGrid({ items: 6, columns: 3, area: { x: 0.5, y: 2, w: 12, h: 4 }, gap: 0.2 })
|
|
1113
|
+
*/
|
|
1114
|
+
function layoutGrid(props) {
|
|
1115
|
+
var _a, _b, _c, _d, _e;
|
|
1116
|
+
const { items, columns, area } = props;
|
|
1117
|
+
const gap = (_a = props.gap) !== null && _a !== void 0 ? _a : 0.2;
|
|
1118
|
+
const gapX = (_b = props.gapX) !== null && _b !== void 0 ? _b : gap;
|
|
1119
|
+
const gapY = (_c = props.gapY) !== null && _c !== void 0 ? _c : gap;
|
|
1120
|
+
const padding = (_d = props.padding) !== null && _d !== void 0 ? _d : 0;
|
|
1121
|
+
const align = (_e = props.align) !== null && _e !== void 0 ? _e : 'start';
|
|
1122
|
+
// Edge case: no items -> empty result
|
|
1123
|
+
if (!items || items <= 0)
|
|
1124
|
+
return [];
|
|
1125
|
+
// Guard: a zero/negative area can't be subdivided
|
|
1126
|
+
if (!area || !(area.w > 0) || !(area.h > 0))
|
|
1127
|
+
throw new Error('layoutGrid: `area` requires positive `w` and `h`');
|
|
1128
|
+
if (!(columns > 0))
|
|
1129
|
+
throw new Error('layoutGrid: `columns` must be a positive number');
|
|
1130
|
+
const rows = Math.ceil(items / columns);
|
|
1131
|
+
const cellW = (area.w - (columns - 1) * gapX) / columns;
|
|
1132
|
+
const cellH = (area.h - (rows - 1) * gapY) / rows;
|
|
1133
|
+
const result = [];
|
|
1134
|
+
for (let i = 0; i < items; i++) {
|
|
1135
|
+
const col = i % columns;
|
|
1136
|
+
const row = Math.floor(i / columns);
|
|
1137
|
+
// Items on the final (possibly partial) row can be re-aligned within the area
|
|
1138
|
+
let rowCellW = cellW;
|
|
1139
|
+
let rowOffsetX = 0;
|
|
1140
|
+
const isLastRow = row === rows - 1;
|
|
1141
|
+
const lastRowCount = items - (rows - 1) * columns;
|
|
1142
|
+
if (isLastRow && lastRowCount < columns && lastRowCount > 0) {
|
|
1143
|
+
const rowCols = lastRowCount;
|
|
1144
|
+
if (align === 'stretch') {
|
|
1145
|
+
// Widen the partial row's cells to fill the full area width
|
|
1146
|
+
rowCellW = (area.w - (rowCols - 1) * gapX) / rowCols;
|
|
1147
|
+
}
|
|
1148
|
+
else if (align === 'center') {
|
|
1149
|
+
// Centre the partial row (cells keep their size)
|
|
1150
|
+
const rowWidth = rowCols * cellW + (rowCols - 1) * gapX;
|
|
1151
|
+
rowOffsetX = (area.w - rowWidth) / 2;
|
|
1152
|
+
}
|
|
1153
|
+
}
|
|
1154
|
+
const x = area.x + rowOffsetX + col * (rowCellW + gapX);
|
|
1155
|
+
const y = area.y + row * (cellH + gapY);
|
|
1156
|
+
// `padding` insets each cell box on all sides
|
|
1157
|
+
result.push({
|
|
1158
|
+
x: x + padding,
|
|
1159
|
+
y: y + padding,
|
|
1160
|
+
w: rowCellW - 2 * padding,
|
|
1161
|
+
h: cellH - 2 * padding,
|
|
1162
|
+
});
|
|
1163
|
+
}
|
|
1164
|
+
return result;
|
|
1165
|
+
}
|
|
1094
1166
|
|
|
1095
1167
|
/**
|
|
1096
1168
|
* PptxGenJS: Table Generation
|
|
@@ -2515,6 +2587,122 @@ function addCalloutDefinition(target, opts) {
|
|
|
2515
2587
|
textOpts.objectName = options.objectName;
|
|
2516
2588
|
addTextDefinition(target, [{ text: options.text || '', options: null }], textOpts, false);
|
|
2517
2589
|
}
|
|
2590
|
+
/**
|
|
2591
|
+
* Adds a structured "card" to a slide definition (docs/feature-card-helper.md).
|
|
2592
|
+
* Builds a single shape group (`<p:grpSp>`) containing a rounded-rect background and,
|
|
2593
|
+
* as applicable, an icon container + icon (SVG path or emoji/text), a title, a description,
|
|
2594
|
+
* and a top-right badge — all positioned with group-relative coordinates. When `animation`
|
|
2595
|
+
* is supplied it is attached to the group object so the whole card animates as one.
|
|
2596
|
+
* @param {PresSlide} target slide the card should be added to
|
|
2597
|
+
* @param {CardProps} opts card options
|
|
2598
|
+
*/
|
|
2599
|
+
function addCardDefinition(target, opts) {
|
|
2600
|
+
const options = typeof opts === 'object' ? opts : {};
|
|
2601
|
+
const x = options.x !== undefined ? Number(options.x) : 1;
|
|
2602
|
+
const y = options.y !== undefined ? Number(options.y) : 1;
|
|
2603
|
+
const w = options.w !== undefined ? Number(options.w) : 3;
|
|
2604
|
+
const h = options.h !== undefined ? Number(options.h) : 2;
|
|
2605
|
+
const padding = 0.2;
|
|
2606
|
+
const cornerRadius = options.cornerRadius !== undefined ? options.cornerRadius : 0.12;
|
|
2607
|
+
const fill = options.fill !== undefined ? options.fill : '1a1a24';
|
|
2608
|
+
const align = options.align === 'left' ? 'left' : 'center';
|
|
2609
|
+
const iconPosition = options.iconPosition === 'left' ? 'left' : 'top';
|
|
2610
|
+
const iconSize = options.iconSize !== undefined ? Number(options.iconSize) : 0.4;
|
|
2611
|
+
const hasIcon = options.icon !== undefined && options.icon !== null;
|
|
2612
|
+
const titleFont = options.titleFont || {};
|
|
2613
|
+
const descFont = options.descFont || {};
|
|
2614
|
+
const textAlign = align === 'center' ? 'center' : 'left';
|
|
2615
|
+
// Create the group container; children below use coords relative to the group origin (0,0..w,h)
|
|
2616
|
+
const group = addGroupDefinition(target, { x, y, w, h, objectName: options.objectName });
|
|
2617
|
+
// The group is the just-pushed top-level slide object — grab it so card-level animation can attach
|
|
2618
|
+
const groupObj = target._slideObjects[target._slideObjects.length - 1];
|
|
2619
|
+
if (options.animation && groupObj && groupObj.options)
|
|
2620
|
+
groupObj.options.animation = options.animation;
|
|
2621
|
+
// 1) Background roundRect (fill + optional border/shadow/glow). `rectRadius` maps inches -> adj.
|
|
2622
|
+
const bgOpts = {
|
|
2623
|
+
x: 0, y: 0, w, h,
|
|
2624
|
+
fill: typeof fill === 'string' ? { color: fill } : fill,
|
|
2625
|
+
rectRadius: cornerRadius,
|
|
2626
|
+
};
|
|
2627
|
+
if (options.border)
|
|
2628
|
+
bgOpts.line = { color: options.border.color || '2A2438', width: options.border.width || 1 };
|
|
2629
|
+
if (options.shadow)
|
|
2630
|
+
bgOpts.shadow = Object.assign({ type: 'outer' }, options.shadow);
|
|
2631
|
+
if (options.glow)
|
|
2632
|
+
bgOpts.glow = options.glow;
|
|
2633
|
+
group.addShape(SHAPE_TYPE.ROUNDED_RECTANGLE, bgOpts);
|
|
2634
|
+
// 2) Layout anchors for icon / title / description (group-relative inches)
|
|
2635
|
+
let iconX = (w - iconSize) / 2;
|
|
2636
|
+
let iconY = padding;
|
|
2637
|
+
let titleX = padding;
|
|
2638
|
+
let titleY = hasIcon ? padding + iconSize + 0.15 : padding;
|
|
2639
|
+
let titleW = w - 2 * padding;
|
|
2640
|
+
const titleH = 0.4;
|
|
2641
|
+
if (iconPosition === 'left' && hasIcon) {
|
|
2642
|
+
iconX = padding;
|
|
2643
|
+
iconY = padding;
|
|
2644
|
+
titleX = padding + iconSize + 0.2;
|
|
2645
|
+
titleY = padding;
|
|
2646
|
+
titleW = w - titleX - padding;
|
|
2647
|
+
}
|
|
2648
|
+
const descX = titleX;
|
|
2649
|
+
const descY = titleY + titleH + 0.05;
|
|
2650
|
+
const descW = titleW;
|
|
2651
|
+
const descH = Math.max(0.2, h - descY - padding);
|
|
2652
|
+
// 3) Icon container + glyph
|
|
2653
|
+
if (hasIcon) {
|
|
2654
|
+
const iconFill = options.iconFill !== undefined ? options.iconFill : '7C3AED';
|
|
2655
|
+
group.addShape(SHAPE_TYPE.ROUNDED_RECTANGLE, {
|
|
2656
|
+
x: iconX, y: iconY, w: iconSize, h: iconSize,
|
|
2657
|
+
fill: { color: iconFill }, rectRadius: cornerRadius / 2, line: { type: 'none' },
|
|
2658
|
+
});
|
|
2659
|
+
const glyphColor = titleFont.color || 'E4E4ED';
|
|
2660
|
+
if (typeof options.icon === 'string') {
|
|
2661
|
+
// Emoji / text glyph centred in the container
|
|
2662
|
+
group.addText(options.icon, {
|
|
2663
|
+
x: iconX, y: iconY, w: iconSize, h: iconSize,
|
|
2664
|
+
align: 'center', valign: 'middle', fontSize: Math.round(iconSize * 36), color: glyphColor,
|
|
2665
|
+
});
|
|
2666
|
+
}
|
|
2667
|
+
else if (options.icon && typeof options.icon === 'object' && options.icon.svgPath) {
|
|
2668
|
+
// SVG path glyph, inset within the container; emits <a:custGeom>
|
|
2669
|
+
const inset = iconSize * 0.22;
|
|
2670
|
+
group.addShape('rect', {
|
|
2671
|
+
x: iconX + inset, y: iconY + inset, w: iconSize - 2 * inset, h: iconSize - 2 * inset,
|
|
2672
|
+
svgPath: options.icon.svgPath, fill: { color: glyphColor }, line: { type: 'none' },
|
|
2673
|
+
});
|
|
2674
|
+
}
|
|
2675
|
+
}
|
|
2676
|
+
// 4) Title
|
|
2677
|
+
group.addText(options.title || '', {
|
|
2678
|
+
x: titleX, y: titleY, w: titleW, h: titleH,
|
|
2679
|
+
fontFace: titleFont.face, fontSize: titleFont.size !== undefined ? titleFont.size : 13,
|
|
2680
|
+
bold: titleFont.bold !== undefined ? titleFont.bold : true,
|
|
2681
|
+
color: titleFont.color || 'E4E4ED', align: textAlign, valign: 'top',
|
|
2682
|
+
});
|
|
2683
|
+
// 5) Description (shrink-to-fit so overflow stays inside the card)
|
|
2684
|
+
if (options.description) {
|
|
2685
|
+
group.addText(options.description, {
|
|
2686
|
+
x: descX, y: descY, w: descW, h: descH,
|
|
2687
|
+
fontFace: descFont.face, fontSize: descFont.size !== undefined ? descFont.size : 10,
|
|
2688
|
+
bold: descFont.bold !== undefined ? descFont.bold : false,
|
|
2689
|
+
color: descFont.color || '8A8A9A', align: textAlign, valign: 'top', fit: 'shrink',
|
|
2690
|
+
});
|
|
2691
|
+
}
|
|
2692
|
+
// 6) Badge (top-right)
|
|
2693
|
+
if (options.badge && options.badge.text) {
|
|
2694
|
+
const badgeH = 0.28;
|
|
2695
|
+
const badgeW = Math.max(0.5, options.badge.text.length * 0.11 + 0.2);
|
|
2696
|
+
group.addShape(SHAPE_TYPE.ROUNDED_RECTANGLE, {
|
|
2697
|
+
x: w - badgeW - padding, y: padding, w: badgeW, h: badgeH,
|
|
2698
|
+
fill: { color: options.badge.fill || '10B981' }, rectRadius: badgeH / 2, line: { type: 'none' },
|
|
2699
|
+
});
|
|
2700
|
+
group.addText(options.badge.text, {
|
|
2701
|
+
x: w - badgeW - padding, y: padding, w: badgeW, h: badgeH,
|
|
2702
|
+
align: 'center', valign: 'middle', fontSize: 8, bold: true, color: options.badge.color || 'FFFFFF',
|
|
2703
|
+
});
|
|
2704
|
+
}
|
|
2705
|
+
}
|
|
2518
2706
|
/**
|
|
2519
2707
|
* Feature 6: Adds a shape group to a slide definition and returns a group handle.
|
|
2520
2708
|
* The group emits a `<p:grpSp>` whose `<a:xfrm>` carries the absolute position/size plus
|
|
@@ -2858,9 +3046,14 @@ function addTextDefinition(target, text, opts, isPlaceholder) {
|
|
|
2858
3046
|
const value = from + i;
|
|
2859
3047
|
const frameOpts = Object.assign({}, opts);
|
|
2860
3048
|
delete frameOpts.counter;
|
|
2861
|
-
//
|
|
2862
|
-
|
|
2863
|
-
//
|
|
3049
|
+
// Odometer entrance: ALL frames live in ONE parallel build step (`withPrevious`),
|
|
3050
|
+
// each appearing at a CUMULATIVE delay from the container start (frame 0 -> 0,
|
|
3051
|
+
// frame 1 -> stepMs, frame 2 -> 2*stepMs, ...). `afterPrevious` would split each
|
|
3052
|
+
// frame into a separate build step in genXmlTiming, breaking the count-up; using
|
|
3053
|
+
// `withPrevious` keeps them in one group so the staggered delays drive the odometer.
|
|
3054
|
+
frameOpts.animation = { type: 'appear', trigger: 'withPrevious', delay: i * stepMs };
|
|
3055
|
+
// Every frame except the last hides itself one step after IT appears (the exit delay
|
|
3056
|
+
// is relative to each frame's own appearance), so frame N+1 masks frame N -> count-up.
|
|
2864
3057
|
if (i < frameCount - 1)
|
|
2865
3058
|
frameOpts._counterExit = stepMs;
|
|
2866
3059
|
else
|
|
@@ -3269,6 +3462,16 @@ class Slide {
|
|
|
3269
3462
|
addCalloutDefinition(this, options);
|
|
3270
3463
|
return this;
|
|
3271
3464
|
}
|
|
3465
|
+
/**
|
|
3466
|
+
* Add a structured card (rounded-rect background + optional icon/title/description/badge)
|
|
3467
|
+
* to Slide as a single shape group.
|
|
3468
|
+
* @param {CardProps} options - card options
|
|
3469
|
+
* @return {Slide} this Slide
|
|
3470
|
+
*/
|
|
3471
|
+
addCard(options) {
|
|
3472
|
+
addCardDefinition(this, options);
|
|
3473
|
+
return this;
|
|
3474
|
+
}
|
|
3272
3475
|
/**
|
|
3273
3476
|
* Add table to Slide
|
|
3274
3477
|
* @param {TableRow[]} tableRows - table rows
|
|
@@ -3990,7 +4193,7 @@ function makeXmlCharts(rel) {
|
|
|
3990
4193
|
* @example '<c:lineChart>'
|
|
3991
4194
|
* @return {string} XML chart
|
|
3992
4195
|
*/
|
|
3993
|
-
function makeChartType(chartType, data, opts, valAxisId, catAxisId,
|
|
4196
|
+
function makeChartType(chartType, data, opts, valAxisId, catAxisId, _isMultiTypeChart) {
|
|
3994
4197
|
// NOTE: "Chart Range" (as shown in "select Chart Area dialog") is calculated.
|
|
3995
4198
|
// ....: Ensure each X/Y Axis/Col has same row height (esp. applicable to XY Scatter where X can often be larger than Y's)
|
|
3996
4199
|
let colorIndex = -1; // Maintain the color index by region
|
|
@@ -4015,6 +4218,11 @@ function makeChartType(chartType, data, opts, valAxisId, catAxisId, isMultiTypeC
|
|
|
4015
4218
|
if (chartType === CHART_TYPE.RADAR) {
|
|
4016
4219
|
strXml += '<c:radarStyle val="' + opts.radarStyle + '"/>';
|
|
4017
4220
|
}
|
|
4221
|
+
// `c:grouping` is a required first child of CT_LineChart (EG_LineChartShared, minOccurs=1)
|
|
4222
|
+
// and must precede `c:varyColors`. Without it, line/combo charts fail schema validation.
|
|
4223
|
+
if (chartType === CHART_TYPE.LINE) {
|
|
4224
|
+
strXml += '<c:grouping val="standard"/>';
|
|
4225
|
+
}
|
|
4018
4226
|
strXml += '<c:varyColors val="0"/>';
|
|
4019
4227
|
// 2: "Series" block for every data row
|
|
4020
4228
|
/* EX1:
|
|
@@ -4090,7 +4298,26 @@ function makeChartType(chartType, data, opts, valAxisId, catAxisId, isMultiTypeC
|
|
|
4090
4298
|
}
|
|
4091
4299
|
strXml += createShadowElement(opts.shadow, DEF_SHAPE_SHADOW);
|
|
4092
4300
|
strXml += ' </c:spPr>';
|
|
4093
|
-
|
|
4301
|
+
// `c:invertIfNegative` is only valid on bar/bar3D series (CT_BarSer). Emitting it for
|
|
4302
|
+
// area/line/radar series violates the OOXML schema (invalid child of CT_AreaSer/CT_LineSer/CT_RadarSer).
|
|
4303
|
+
if (chartType === CHART_TYPE.BAR || chartType === CHART_TYPE.BAR3D) {
|
|
4304
|
+
strXml += ' <c:invertIfNegative val="0"/>';
|
|
4305
|
+
}
|
|
4306
|
+
// 'c:marker' tag: `lineDataSymbol`
|
|
4307
|
+
// NOTE: CT_LineSer requires `marker` to precede `dLbls` (sequence: …spPr, marker?, dPt*, dLbls?…),
|
|
4308
|
+
// so this block must be emitted before the `c:dLbls` block below or line/combo charts fail schema validation.
|
|
4309
|
+
if (chartType === CHART_TYPE.LINE || chartType === CHART_TYPE.RADAR) {
|
|
4310
|
+
strXml += '<c:marker>';
|
|
4311
|
+
strXml += ' <c:symbol val="' + opts.lineDataSymbol + '"/>';
|
|
4312
|
+
if (opts.lineDataSymbolSize)
|
|
4313
|
+
strXml += `<c:size val="${opts.lineDataSymbolSize}"/>`; // Defaults to "auto" otherwise (but this is usually too small, so there is a default)
|
|
4314
|
+
strXml += ' <c:spPr>';
|
|
4315
|
+
strXml += ` <a:solidFill>${createColorElement(opts.chartColors[obj._dataIndex + 1 > opts.chartColors.length ? Math.floor(Math.random() * opts.chartColors.length) : obj._dataIndex])}</a:solidFill>`;
|
|
4316
|
+
strXml += ` <a:ln w="${opts.lineDataSymbolLineSize}" cap="flat"><a:solidFill>${createColorElement(opts.lineDataSymbolLineColor || seriesColor)}</a:solidFill><a:prstDash val="solid"/><a:round/></a:ln>`;
|
|
4317
|
+
strXml += ' <a:effectLst/>';
|
|
4318
|
+
strXml += ' </c:spPr>';
|
|
4319
|
+
strXml += '</c:marker>';
|
|
4320
|
+
}
|
|
4094
4321
|
// Data Labels per series
|
|
4095
4322
|
// NOTE: [20190117] Adding these to RADAR chart causes unrecoverable corruption!
|
|
4096
4323
|
if (chartType !== CHART_TYPE.RADAR) {
|
|
@@ -4111,19 +4338,6 @@ function makeChartType(chartType, data, opts, valAxisId, catAxisId, isMultiTypeC
|
|
|
4111
4338
|
strXml += `<c:showLeaderLines val="${opts.showLeaderLines ? '1' : '0'}"/>`;
|
|
4112
4339
|
strXml += '</c:dLbls>';
|
|
4113
4340
|
}
|
|
4114
|
-
// 'c:marker' tag: `lineDataSymbol`
|
|
4115
|
-
if (chartType === CHART_TYPE.LINE || chartType === CHART_TYPE.RADAR) {
|
|
4116
|
-
strXml += '<c:marker>';
|
|
4117
|
-
strXml += ' <c:symbol val="' + opts.lineDataSymbol + '"/>';
|
|
4118
|
-
if (opts.lineDataSymbolSize)
|
|
4119
|
-
strXml += `<c:size val="${opts.lineDataSymbolSize}"/>`; // Defaults to "auto" otherwise (but this is usually too small, so there is a default)
|
|
4120
|
-
strXml += ' <c:spPr>';
|
|
4121
|
-
strXml += ` <a:solidFill>${createColorElement(opts.chartColors[obj._dataIndex + 1 > opts.chartColors.length ? Math.floor(Math.random() * opts.chartColors.length) : obj._dataIndex])}</a:solidFill>`;
|
|
4122
|
-
strXml += ` <a:ln w="${opts.lineDataSymbolLineSize}" cap="flat"><a:solidFill>${createColorElement(opts.lineDataSymbolLineColor || seriesColor)}</a:solidFill><a:prstDash val="solid"/><a:round/></a:ln>`;
|
|
4123
|
-
strXml += ' <a:effectLst/>';
|
|
4124
|
-
strXml += ' </c:spPr>';
|
|
4125
|
-
strXml += '</c:marker>';
|
|
4126
|
-
}
|
|
4127
4341
|
// Allow users with a single data set to pass their own array of colors (check for this using != ours)
|
|
4128
4342
|
// Color chart bars various colors when >1 color
|
|
4129
4343
|
// NOTE: `<c:dPt>` created with various colors will change PPT legend by design so each dataPt/color is an legend item!
|
|
@@ -4272,6 +4486,7 @@ function makeChartType(chartType, data, opts, valAxisId, catAxisId, isMultiTypeC
|
|
|
4272
4486
|
// 2: Series: (One for each Y-Axis)
|
|
4273
4487
|
colorIndex = -1;
|
|
4274
4488
|
data.filter((_obj, idx) => idx > 0).forEach((obj, idx) => {
|
|
4489
|
+
var _a;
|
|
4275
4490
|
colorIndex++;
|
|
4276
4491
|
strXml += '<c:ser>';
|
|
4277
4492
|
strXml += ` <c:idx val="${idx}"/>`;
|
|
@@ -4324,7 +4539,7 @@ function makeChartType(chartType, data, opts, valAxisId, catAxisId, isMultiTypeC
|
|
|
4324
4539
|
// Option: scatter data point labels
|
|
4325
4540
|
if (opts.showLabel) {
|
|
4326
4541
|
const chartUuid = getUuid('-xxxx-xxxx-xxxx-xxxxxxxxxxxx');
|
|
4327
|
-
if (obj.labels[0] && (opts.dataLabelFormatScatter === 'custom' || opts.dataLabelFormatScatter === 'customXY')) {
|
|
4542
|
+
if (((_a = obj.labels) === null || _a === void 0 ? void 0 : _a[0]) && (opts.dataLabelFormatScatter === 'custom' || opts.dataLabelFormatScatter === 'customXY')) {
|
|
4328
4543
|
strXml += '<c:dLbls>';
|
|
4329
4544
|
obj.labels[0].forEach((label, idx) => {
|
|
4330
4545
|
if (opts.dataLabelFormatScatter === 'custom' || opts.dataLabelFormatScatter === 'customXY') {
|
|
@@ -4793,8 +5008,12 @@ function makeChartType(chartType, data, opts, valAxisId, catAxisId, isMultiTypeC
|
|
|
4793
5008
|
// 4: Close "SERIES"
|
|
4794
5009
|
strXml += ' </c:ser>';
|
|
4795
5010
|
strXml += ` <c:firstSliceAng val="${opts.firstSliceAng ? Math.round(opts.firstSliceAng) : 0}"/>`;
|
|
4796
|
-
if (chartType === CHART_TYPE.DOUGHNUT)
|
|
4797
|
-
|
|
5011
|
+
if (chartType === CHART_TYPE.DOUGHNUT) {
|
|
5012
|
+
// ST_HoleSize restricts to [10,90]; clamp out-of-range input (clamp-don't-crash)
|
|
5013
|
+
const rawHoleSize = typeof opts.holeSize === 'number' ? opts.holeSize : 50;
|
|
5014
|
+
const holeSizeVal = Math.max(10, Math.min(90, Math.round(rawHoleSize)));
|
|
5015
|
+
strXml += `<c:holeSize val="${holeSizeVal}"/>`;
|
|
5016
|
+
}
|
|
4798
5017
|
strXml += '</c:' + chartType + 'Chart>';
|
|
4799
5018
|
// Done with Doughnut/Pie
|
|
4800
5019
|
break;
|
|
@@ -4887,11 +5106,15 @@ function makeCatAxis(opts, axisId, valAxisId) {
|
|
|
4887
5106
|
strXml += ' </c:txPr>';
|
|
4888
5107
|
strXml += ' <c:crossAx val="' + valAxisId + '"/>';
|
|
4889
5108
|
strXml += ` <c:${typeof opts.valAxisCrossesAt === 'number' ? 'crossesAt' : 'crosses'} val="${opts.valAxisCrossesAt || 'autoZero'}"/>`;
|
|
4890
|
-
|
|
4891
|
-
|
|
4892
|
-
|
|
4893
|
-
|
|
4894
|
-
strXml += ' <c:
|
|
5109
|
+
// NOTE: `auto`/`lblAlgn`/`noMultiLvlLbl`/`tickLblSkip` are CT_CatAx-only children and are invalid on the
|
|
5110
|
+
// CT_ValAx that scatter/bubble/bubble3D charts emit for their (numeric) x-axis. Gate them to the catAx/dateAx case.
|
|
5111
|
+
if (!(opts._type === CHART_TYPE.SCATTER || opts._type === CHART_TYPE.BUBBLE || opts._type === CHART_TYPE.BUBBLE3D)) {
|
|
5112
|
+
strXml += ' <c:auto val="1"/>';
|
|
5113
|
+
strXml += ' <c:lblAlgn val="ctr"/>';
|
|
5114
|
+
strXml += ` <c:noMultiLvlLbl val="${opts.catAxisMultiLevelLabels ? 0 : 1}"/>`;
|
|
5115
|
+
if (opts.catAxisLabelFrequency)
|
|
5116
|
+
strXml += ' <c:tickLblSkip val="' + opts.catAxisLabelFrequency + '"/>';
|
|
5117
|
+
}
|
|
4895
5118
|
// Issue#149: PPT will auto-adjust these as needed after calcing the date bounds, so we only include them when specified by user
|
|
4896
5119
|
// Allow major and minor units to be set for double value axis charts
|
|
4897
5120
|
if (opts.catLabelFormatCode || opts._type === CHART_TYPE.SCATTER || opts._type === CHART_TYPE.BUBBLE || opts._type === CHART_TYPE.BUBBLE3D) {
|
|
@@ -7179,6 +7402,36 @@ function genXmlTiming(slide) {
|
|
|
7179
7402
|
const presetMap = { appear: 1, fadeIn: 10, flyIn: 2, zoomIn: 23 };
|
|
7180
7403
|
// trigger -> build-step wrapper nodeType
|
|
7181
7404
|
const wrapNodeTypeMap = { afterPrevious: 'afterEffect', withPrevious: 'withEffect', onClick: 'clickEffect' };
|
|
7405
|
+
// --- Pre-process group/stagger sugar into trigger/delay -------------------
|
|
7406
|
+
// `group` is syntactic sugar over `trigger` (spec: docs/feature-animation-stagger.md).
|
|
7407
|
+
// Walking the animated objects in order: the first object of each consecutive `group`
|
|
7408
|
+
// run becomes the group leader (-> afterPrevious, starting a new build step); the rest
|
|
7409
|
+
// of the run join it (-> withPrevious). When `stagger` is set, the Nth item within the
|
|
7410
|
+
// run (0-indexed) gets `delay = N * stagger`. Objects without a `group` are left untouched
|
|
7411
|
+
// so the explicit `trigger` behaviour stays byte-for-byte identical (backwards-compat).
|
|
7412
|
+
// Entries are shallow-copied before mutation so the caller's `options.animation` is never
|
|
7413
|
+
// modified (keeps repeated `stream()`/`write()` calls deterministic).
|
|
7414
|
+
let prevGroup;
|
|
7415
|
+
let groupIndex = 0;
|
|
7416
|
+
animated.forEach(entry => {
|
|
7417
|
+
const a = entry.anim;
|
|
7418
|
+
if (typeof a.group === 'number') {
|
|
7419
|
+
const isNewGroup = a.group !== prevGroup;
|
|
7420
|
+
if (isNewGroup)
|
|
7421
|
+
groupIndex = 0;
|
|
7422
|
+
else
|
|
7423
|
+
groupIndex++;
|
|
7424
|
+
const resolved = Object.assign(Object.assign({}, a), { trigger: isNewGroup ? 'afterPrevious' : 'withPrevious' });
|
|
7425
|
+
if (typeof a.stagger === 'number')
|
|
7426
|
+
resolved.delay = groupIndex * a.stagger;
|
|
7427
|
+
entry.anim = resolved;
|
|
7428
|
+
prevGroup = a.group;
|
|
7429
|
+
}
|
|
7430
|
+
else {
|
|
7431
|
+
// Ungrouped object forms its own step; reset so a later same-numbered group is a fresh run
|
|
7432
|
+
prevGroup = undefined;
|
|
7433
|
+
}
|
|
7434
|
+
});
|
|
7182
7435
|
const steps = [];
|
|
7183
7436
|
animated.forEach(entry => {
|
|
7184
7437
|
var _a;
|
|
@@ -7571,7 +7824,7 @@ function makeXmlViewProps() {
|
|
|
7571
7824
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
7572
7825
|
* SOFTWARE.
|
|
7573
7826
|
*/
|
|
7574
|
-
const VERSION = '4.1.
|
|
7827
|
+
const VERSION = '4.1.4';
|
|
7575
7828
|
class PptxGenJS {
|
|
7576
7829
|
set layout(value) {
|
|
7577
7830
|
const newLayout = this.LAYOUTS[value];
|
|
@@ -7924,6 +8177,7 @@ class PptxGenJS {
|
|
|
7924
8177
|
addImage: null,
|
|
7925
8178
|
addMedia: null,
|
|
7926
8179
|
addNotes: null,
|
|
8180
|
+
addCard: null,
|
|
7927
8181
|
addShape: null,
|
|
7928
8182
|
addTable: null,
|
|
7929
8183
|
addText: null,
|
|
@@ -8086,6 +8340,16 @@ class PptxGenJS {
|
|
|
8086
8340
|
}
|
|
8087
8341
|
return newSlide;
|
|
8088
8342
|
}
|
|
8343
|
+
/**
|
|
8344
|
+
* Compute evenly-spaced grid cell positions within a bounding area.
|
|
8345
|
+
* - Pure layout helper: returns one `{ x, y, w, h }` (inches) per item; emits no slide content.
|
|
8346
|
+
* @param {LayoutGridProps} props - grid options
|
|
8347
|
+
* @returns {LayoutGridResult} array of `{ x, y, w, h }` cells (inches), one per item
|
|
8348
|
+
* @example const grid = pptx.layoutGrid({ items: 6, columns: 3, area: { x: 0.5, y: 2, w: 12, h: 4 }, gap: 0.2 })
|
|
8349
|
+
*/
|
|
8350
|
+
layoutGrid(props) {
|
|
8351
|
+
return layoutGrid(props);
|
|
8352
|
+
}
|
|
8089
8353
|
/**
|
|
8090
8354
|
* Create a custom Slide Layout in any size
|
|
8091
8355
|
* @param {PresLayout} layout - layout properties
|