canva-pptx 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1147 -0
- package/package.json +63 -0
- package/src/AdvancedComponents/accordionList.js +39 -0
- package/src/AdvancedComponents/calendarGrid.js +50 -0
- package/src/AdvancedComponents/certificateFrame.js +35 -0
- package/src/AdvancedComponents/deviceMockup.js +59 -0
- package/src/AdvancedComponents/featureGrid.js +70 -0
- package/src/AdvancedComponents/funnelDiagram.js +49 -0
- package/src/AdvancedComponents/ganttChart.js +35 -0
- package/src/AdvancedComponents/invoiceTable.js +32 -0
- package/src/AdvancedComponents/matrixGrid.js +43 -0
- package/src/AdvancedComponents/mindMap.js +43 -0
- package/src/AdvancedComponents/orgChart.js +76 -0
- package/src/AdvancedComponents/personaCard.js +44 -0
- package/src/AdvancedComponents/processCycle.js +47 -0
- package/src/AdvancedComponents/pyramidHierarchy.js +38 -0
- package/src/AdvancedComponents/saasFeatureBlock.js +38 -0
- package/src/AdvancedComponents/swotMatrix.js +55 -0
- package/src/AdvancedComponents/teamMemberProfile.js +75 -0
- package/src/AdvancedComponents/trafficLight.js +35 -0
- package/src/AdvancedComponents/vennDiagram.js +35 -0
- package/src/AdvancedComponents/winLossChart.js +43 -0
- package/src/components/StatMetric.js +43 -0
- package/src/components/alertBox.js +46 -0
- package/src/components/avatarGroup.js +56 -0
- package/src/components/badge.js +40 -0
- package/src/components/breadcrumbNav.js +31 -0
- package/src/components/browserWindow.js +86 -0
- package/src/components/brushStroke.js +23 -0
- package/src/components/callToAction.js +27 -0
- package/src/components/card.js +64 -0
- package/src/components/chart.js +19 -0
- package/src/components/codeBlock.js +40 -0
- package/src/components/codeDiff.js +51 -0
- package/src/components/comparisonTable.js +43 -0
- package/src/components/cornerAccent.js +32 -0
- package/src/components/dotPattern.js +30 -0
- package/src/components/geometricConfetti.js +34 -0
- package/src/components/gradientMesh.js +32 -0
- package/src/components/iconList.js +43 -0
- package/src/components/image.js +11 -0
- package/src/components/kanbanColumn.js +38 -0
- package/src/components/link.js +23 -0
- package/src/components/organicBlob.js +45 -0
- package/src/components/pricingColumn.js +53 -0
- package/src/components/progressBar.js +55 -0
- package/src/components/ratingStars.js +25 -0
- package/src/components/shape.js +12 -0
- package/src/components/slide.js +5 -0
- package/src/components/socialBar.js +59 -0
- package/src/components/squiggleLine.js +26 -0
- package/src/components/stepProcess.js +39 -0
- package/src/components/table.js +23 -0
- package/src/components/tagCloud.js +39 -0
- package/src/components/testimonialCard.js +54 -0
- package/src/components/text.js +14 -0
- package/src/components/theme.js +7 -0
- package/src/components/timeline.js +73 -0
- package/src/components/waveDecoration.js +35 -0
- package/src/core/PPTManager.js +17 -0
- package/src/index.js +225 -0
- package/src/layout/bento.js +47 -0
- package/src/layout/checkerboard.js +36 -0
- package/src/layout/filmStrip.js +29 -0
- package/src/layout/gallery.js +50 -0
- package/src/layout/grid.js +37 -0
- package/src/layout/hero.js +30 -0
- package/src/layout/magazine.js +39 -0
- package/src/layout/radial.js +34 -0
- package/src/layout/sidebar.js +26 -0
- package/src/layout/splitScreen.js +36 -0
- package/src/layout/zPattern.js +29 -0
- package/src/system/animation.js +36 -0
- package/src/system/contrastChecker.js +35 -0
- package/src/system/dataAdapter.js +36 -0
- package/src/system/layoutDebugger.js +40 -0
- package/src/system/markdownEngine.js +45 -0
- package/src/system/masterOverlay.js +50 -0
- package/src/system/sectionDivider.js +41 -0
- package/src/system/smartIcon.js +33 -0
- package/src/system/smartText.js +44 -0
- package/src/system/speakerNotes.js +12 -0
- package/src/system/syntaxHighlighter.js +71 -0
- package/src/system/themeGenerator.js +25 -0
- package/src/system/tocGenerator.js +68 -0
- package/src/system/watermark.js +26 -0
- package/src/themes/index.js +93 -0
package/package.json
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "canva-pptx",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "A professional, component-based PowerPoint (PPTX) generator for Node.js. Create complex, Canva-style slide decks, business reports, and dashboards programmatically with smart layouts and themes.",
|
|
5
|
+
"main": "src/index.js",
|
|
6
|
+
"files": [
|
|
7
|
+
"src/**/*",
|
|
8
|
+
"README.md",
|
|
9
|
+
"LICENSE"
|
|
10
|
+
],
|
|
11
|
+
"scripts": {
|
|
12
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
|
13
|
+
},
|
|
14
|
+
"keywords": [
|
|
15
|
+
"pptx",
|
|
16
|
+
"powerpoint",
|
|
17
|
+
"presentation",
|
|
18
|
+
"slides",
|
|
19
|
+
"deck",
|
|
20
|
+
"generator",
|
|
21
|
+
"builder",
|
|
22
|
+
"creator",
|
|
23
|
+
"automation",
|
|
24
|
+
"report-generation",
|
|
25
|
+
"reporting",
|
|
26
|
+
"pptxgenjs",
|
|
27
|
+
"office",
|
|
28
|
+
"microsoft",
|
|
29
|
+
"ms-office",
|
|
30
|
+
"document-generation",
|
|
31
|
+
"export-to-pptx",
|
|
32
|
+
"node-pptx",
|
|
33
|
+
"layout-engine",
|
|
34
|
+
"composable",
|
|
35
|
+
"components",
|
|
36
|
+
"design-system",
|
|
37
|
+
"programmatic-design",
|
|
38
|
+
"canvas",
|
|
39
|
+
"dashboard",
|
|
40
|
+
"charts",
|
|
41
|
+
"business-intelligence",
|
|
42
|
+
"nodejs",
|
|
43
|
+
"javascript",
|
|
44
|
+
"slide-maker"
|
|
45
|
+
],
|
|
46
|
+
"author": "Fairoz Ahmed",
|
|
47
|
+
"license": "ISC",
|
|
48
|
+
"type": "commonjs",
|
|
49
|
+
"dependencies": {
|
|
50
|
+
"pptxgenjs": "^3.12.0"
|
|
51
|
+
},
|
|
52
|
+
"repository": {
|
|
53
|
+
"type": "git",
|
|
54
|
+
"url": "git+https://github.com/fairoz-539/canva-pptx.git"
|
|
55
|
+
},
|
|
56
|
+
"bugs": {
|
|
57
|
+
"url": "https://github.com/fairoz-539/canva-pptx/issues"
|
|
58
|
+
},
|
|
59
|
+
"homepage": "https://github.com/fairoz-539/canva-pptx#readme",
|
|
60
|
+
"publishConfig": {
|
|
61
|
+
"access": "public"
|
|
62
|
+
}
|
|
63
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
function addAccordionList(slide, items = [], options = {}) {
|
|
2
|
+
const {
|
|
3
|
+
x = 1, y = 1, w = 6,
|
|
4
|
+
headerColor = "007AFF",
|
|
5
|
+
bodyColor = "F5F5F5"
|
|
6
|
+
} = options;
|
|
7
|
+
|
|
8
|
+
let currentY = y;
|
|
9
|
+
|
|
10
|
+
items.forEach((item) => {
|
|
11
|
+
// Header
|
|
12
|
+
slide.addShape("rect", {
|
|
13
|
+
x, y: currentY, w, h: 0.5,
|
|
14
|
+
fill: headerColor
|
|
15
|
+
});
|
|
16
|
+
slide.addText(`+ ${item.title}`, {
|
|
17
|
+
x: x + 0.2, y: currentY, w: w - 0.4, h: 0.5,
|
|
18
|
+
color: "FFFFFF", bold: true, valign: "middle", fontSize: 12
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
currentY += 0.5;
|
|
22
|
+
|
|
23
|
+
// Body (Expanded)
|
|
24
|
+
const bodyH = 0.8; // Fixed height for simplicity
|
|
25
|
+
slide.addShape("rect", {
|
|
26
|
+
x, y: currentY, w, h: bodyH,
|
|
27
|
+
fill: bodyColor,
|
|
28
|
+
line: { color: "DDDDDD" }
|
|
29
|
+
});
|
|
30
|
+
slide.addText(item.content, {
|
|
31
|
+
x: x + 0.2, y: currentY, w: w - 0.4, h: bodyH,
|
|
32
|
+
color: "333333", valign: "top", fontSize: 11
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
currentY += bodyH + 0.1; // Gap
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
module.exports = { addAccordionList };
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
function addCalendarGrid(slide, monthName, events = [], options = {}) {
|
|
2
|
+
const {
|
|
3
|
+
x = 1, y = 1, w = 5, h = 4,
|
|
4
|
+
headerColor = "D32F2F"
|
|
5
|
+
} = options;
|
|
6
|
+
|
|
7
|
+
const cols = 7;
|
|
8
|
+
const rows = 6; // Header + 5 weeks
|
|
9
|
+
const cellW = w / cols;
|
|
10
|
+
const cellH = (h - 0.5) / 5; // 0.5 for header
|
|
11
|
+
|
|
12
|
+
// Header
|
|
13
|
+
slide.addShape("rect", { x, y, w, h: 0.5, fill: headerColor });
|
|
14
|
+
slide.addText(monthName, { x, y, w, h: 0.5, color: "FFFFFF", bold: true, align: "center" });
|
|
15
|
+
|
|
16
|
+
// Grid
|
|
17
|
+
let dayCount = 1;
|
|
18
|
+
for (let r = 0; r < 5; r++) {
|
|
19
|
+
for (let c = 0; c < 7; c++) {
|
|
20
|
+
if (dayCount > 31) break; // Simple logic
|
|
21
|
+
|
|
22
|
+
const curX = x + c * cellW;
|
|
23
|
+
const curY = y + 0.5 + r * cellH;
|
|
24
|
+
|
|
25
|
+
// Check for event
|
|
26
|
+
const event = events.find(e => e.day === dayCount);
|
|
27
|
+
const bg = event ? event.color || "FFF3E0" : "FFFFFF";
|
|
28
|
+
|
|
29
|
+
slide.addShape("rect", {
|
|
30
|
+
x: curX, y: curY, w: cellW, h: cellH,
|
|
31
|
+
fill: bg, line: { color: "CCCCCC" }
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
slide.addText(String(dayCount), {
|
|
35
|
+
x: curX + 0.05, y: curY + 0.05, w: 0.3, h: 0.3,
|
|
36
|
+
fontSize: 9, color: "666666"
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
if (event) {
|
|
40
|
+
slide.addText(event.title, {
|
|
41
|
+
x: curX, y: curY + 0.2, w: cellW, h: cellH - 0.2,
|
|
42
|
+
fontSize: 8, align: "center", valign: "middle", bold: true
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
dayCount++;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
module.exports = { addCalendarGrid };
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
function addCertificateFrame(slide, recipient, title = "Certificate of Appreciation", options = {}) {
|
|
2
|
+
const { x = 0.5, y = 0.5, w = 9, h = 4.63 } = options;
|
|
3
|
+
|
|
4
|
+
// Outer Border (Gold)
|
|
5
|
+
slide.addShape("rect", {
|
|
6
|
+
x, y, w, h,
|
|
7
|
+
line: { color: "C5A059", width: 4 },
|
|
8
|
+
fill: { color: undefined }
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
// Inner Border (Thin)
|
|
12
|
+
slide.addShape("rect", {
|
|
13
|
+
x: x + 0.2, y: y + 0.2, w: w - 0.4, h: h - 0.4,
|
|
14
|
+
line: { color: "C5A059", width: 1 },
|
|
15
|
+
fill: { color: undefined }
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
// Content
|
|
19
|
+
slide.addText(title, {
|
|
20
|
+
x, y: y + 1, w, h: 1,
|
|
21
|
+
align: "center", fontFace: "Georgia", fontSize: 32, bold: true, color: "333333"
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
slide.addText("Presented to", {
|
|
25
|
+
x, y: y + 2, w, h: 0.5,
|
|
26
|
+
align: "center", fontSize: 14, color: "666666"
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
slide.addText(recipient, {
|
|
30
|
+
x, y: y + 2.5, w, h: 1,
|
|
31
|
+
align: "center", fontFace: "Arial", fontSize: 40, bold: true, color: "C5A059"
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
module.exports = { addCertificateFrame };
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
function addDeviceMockup(slide, imageUrl, options = {}) {
|
|
2
|
+
const {
|
|
3
|
+
x = 1, y = 1, w = 6, h = 4,
|
|
4
|
+
type = "laptop" // or 'tablet'
|
|
5
|
+
} = options;
|
|
6
|
+
|
|
7
|
+
const frameColor = "333333";
|
|
8
|
+
|
|
9
|
+
if (type === "laptop") {
|
|
10
|
+
// Screen Frame
|
|
11
|
+
slide.addShape("roundRect", {
|
|
12
|
+
x, y, w, h: h * 0.85,
|
|
13
|
+
fill: frameColor,
|
|
14
|
+
rounding: 0.05
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
// Screen Image Area
|
|
18
|
+
slide.addShape("rect", {
|
|
19
|
+
x: x + 0.2, y: y + 0.2, w: w - 0.4, h: h * 0.85 - 0.4,
|
|
20
|
+
fill: "000000" // Black bg behind image
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
if (imageUrl) {
|
|
24
|
+
slide.addImage({
|
|
25
|
+
path: imageUrl,
|
|
26
|
+
x: x + 0.2, y: y + 0.2, w: w - 0.4, h: h * 0.85 - 0.4,
|
|
27
|
+
sizing: { type: "contain", w: w - 0.4, h: h * 0.85 - 0.4 }
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Base/Keyboard area
|
|
32
|
+
slide.addShape("trapezoid", {
|
|
33
|
+
x: x - 0.4, y: y + h * 0.85, w: w + 0.8, h: h * 0.15,
|
|
34
|
+
fill: "555555",
|
|
35
|
+
flipV: true // Invert trapezoid for base look
|
|
36
|
+
});
|
|
37
|
+
} else {
|
|
38
|
+
// Tablet Mode (Simple Round Rect)
|
|
39
|
+
slide.addShape("roundRect", {
|
|
40
|
+
x, y, w, h,
|
|
41
|
+
fill: frameColor,
|
|
42
|
+
rounding: 0.1
|
|
43
|
+
});
|
|
44
|
+
// Screen
|
|
45
|
+
slide.addShape("rect", {
|
|
46
|
+
x: x + 0.3, y: y + 0.3, w: w - 0.6, h: h - 0.6,
|
|
47
|
+
fill: "000000"
|
|
48
|
+
});
|
|
49
|
+
if (imageUrl) {
|
|
50
|
+
slide.addImage({
|
|
51
|
+
path: imageUrl,
|
|
52
|
+
x: x + 0.3, y: y + 0.3, w: w - 0.6, h: h - 0.6,
|
|
53
|
+
sizing: { type: "cover", w: w - 0.6, h: h - 0.6 }
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
module.exports = { addDeviceMockup };
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
function addFeatureGrid(slide, features = [], options = {}) {
|
|
2
|
+
const {
|
|
3
|
+
x = 1,
|
|
4
|
+
y = 1,
|
|
5
|
+
w = 8,
|
|
6
|
+
h = 4,
|
|
7
|
+
cols = 3,
|
|
8
|
+
gap = 0.3,
|
|
9
|
+
iconColor = "007AFF"
|
|
10
|
+
} = options;
|
|
11
|
+
|
|
12
|
+
if (features.length === 0) return;
|
|
13
|
+
|
|
14
|
+
// Calculate cell dimensions
|
|
15
|
+
const rows = Math.ceil(features.length / cols);
|
|
16
|
+
const cellW = (w - (cols - 1) * gap) / cols;
|
|
17
|
+
const cellH = (h - (rows - 1) * gap) / rows;
|
|
18
|
+
|
|
19
|
+
features.forEach((item, index) => {
|
|
20
|
+
const colIndex = index % cols;
|
|
21
|
+
const rowIndex = Math.floor(index / cols);
|
|
22
|
+
|
|
23
|
+
const curX = x + colIndex * (cellW + gap);
|
|
24
|
+
const curY = y + rowIndex * (cellH + gap);
|
|
25
|
+
|
|
26
|
+
// FIX: Use object for transparency instead of 8-digit hex
|
|
27
|
+
// transparency: 80 means 80% transparent (only 20% visible, similar to hex '33')
|
|
28
|
+
slide.addShape("oval", {
|
|
29
|
+
x: curX,
|
|
30
|
+
y: curY,
|
|
31
|
+
w: 0.5,
|
|
32
|
+
h: 0.5,
|
|
33
|
+
fill: { color: iconColor, transparency: 80 },
|
|
34
|
+
line: { color: undefined }
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
// If user provides an icon char (like '★'), use it, otherwise use first letter
|
|
38
|
+
const iconChar = item.icon || item.title[0] || "★";
|
|
39
|
+
slide.addText(iconChar, {
|
|
40
|
+
x: curX, y: curY, w: 0.5, h: 0.5,
|
|
41
|
+
color: iconColor, fontSize: 14, align: "center", valign: "middle", bold: true
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
// Title
|
|
45
|
+
slide.addText(item.title, {
|
|
46
|
+
x: curX + 0.6,
|
|
47
|
+
y: curY,
|
|
48
|
+
w: cellW - 0.6,
|
|
49
|
+
h: 0.3,
|
|
50
|
+
bold: true,
|
|
51
|
+
fontSize: 12,
|
|
52
|
+
color: "333333"
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
// Description
|
|
56
|
+
if (item.desc) {
|
|
57
|
+
slide.addText(item.desc, {
|
|
58
|
+
x: curX + 0.6,
|
|
59
|
+
y: curY + 0.3,
|
|
60
|
+
w: cellW - 0.6,
|
|
61
|
+
h: cellH - 0.3,
|
|
62
|
+
fontSize: 10,
|
|
63
|
+
color: "666666",
|
|
64
|
+
valign: "top"
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
module.exports = { addFeatureGrid };
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
function addFunnelDiagram(slide, stages = [], options = {}) {
|
|
2
|
+
const {
|
|
3
|
+
x = 2, y = 1, w = 6, h = 4,
|
|
4
|
+
colors = ["003f5c", "58508d", "bc5090", "ff6361", "ffa600"]
|
|
5
|
+
} = options;
|
|
6
|
+
|
|
7
|
+
const count = stages.length;
|
|
8
|
+
const layerH = h / count;
|
|
9
|
+
|
|
10
|
+
stages.forEach((stage, i) => {
|
|
11
|
+
const curY = y + i * layerH;
|
|
12
|
+
const color = colors[i % colors.length];
|
|
13
|
+
|
|
14
|
+
// Calculate width reduction for funnel shape
|
|
15
|
+
// Top is widest (w), Bottom is narrowest (e.g. w/2)
|
|
16
|
+
// Interpolate width based on index
|
|
17
|
+
const topW = w - (i * (w * 0.5)) / count;
|
|
18
|
+
const bottomW = w - ((i + 1) * (w * 0.5)) / count;
|
|
19
|
+
|
|
20
|
+
// We approximate the visual by centering a rectangle or trapezoid.
|
|
21
|
+
// pptxgenjs trapezoid shape usually handles scaling well.
|
|
22
|
+
// For simplicity here, we use a centered trapezoid.
|
|
23
|
+
|
|
24
|
+
slide.addShape("trapezoid", {
|
|
25
|
+
x: x + (w - topW)/2, // Centering approximation
|
|
26
|
+
y: curY,
|
|
27
|
+
w: topW,
|
|
28
|
+
h: layerH * 0.9, // slight gap
|
|
29
|
+
fill: color,
|
|
30
|
+
// Note: "adj" property in pptxgenjs controls trapezoid slant,
|
|
31
|
+
// but hard to calculate perfectly dynamically without trial.
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
slide.addText(stage.label, {
|
|
35
|
+
x: x, y: curY, w: w, h: layerH * 0.9,
|
|
36
|
+
align: "center", valign: "middle", color: "FFFFFF", bold: true
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
// Value (e.g. conversion rate)
|
|
40
|
+
if (stage.value) {
|
|
41
|
+
slide.addText(stage.value, {
|
|
42
|
+
x: x + w + 0.2, y: curY, w: 1.5, h: layerH * 0.9,
|
|
43
|
+
valign: "middle", fontSize: 11, color: "666666"
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
module.exports = { addFunnelDiagram };
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
function addGanttChart(slide, tasks = [], options = {}) {
|
|
2
|
+
const {
|
|
3
|
+
x = 1, y = 1, w = 8, h = 4,
|
|
4
|
+
barColor = "007AFF"
|
|
5
|
+
} = options;
|
|
6
|
+
|
|
7
|
+
// Header (Timeline ticks - simplified to Month 1, Month 2...)
|
|
8
|
+
slide.addShape("line", { x: x + 2, y: y + 0.4, w: w - 2, h: 0, line: { color: "CCCCCC" } });
|
|
9
|
+
|
|
10
|
+
// Task Rows
|
|
11
|
+
const rowH = (h - 0.5) / (tasks.length || 1);
|
|
12
|
+
|
|
13
|
+
tasks.forEach((task, i) => {
|
|
14
|
+
const curY = y + 0.5 + i * rowH;
|
|
15
|
+
|
|
16
|
+
// Label
|
|
17
|
+
slide.addText(task.name, {
|
|
18
|
+
x: x, y: curY, w: 1.8, h: rowH * 0.6,
|
|
19
|
+
fontSize: 10, bold: true, valign: "middle"
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
// Bar
|
|
23
|
+
// Assuming start/end are normalized 0.0 to 1.0 relative to timeline width
|
|
24
|
+
const startX = x + 2 + (task.start * (w - 2));
|
|
25
|
+
const barW = (task.end - task.start) * (w - 2);
|
|
26
|
+
|
|
27
|
+
slide.addShape("rect", {
|
|
28
|
+
x: startX, y: curY + 0.1, w: barW, h: rowH * 0.4,
|
|
29
|
+
fill: task.color || barColor,
|
|
30
|
+
rounding: 0.1
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
module.exports = { addGanttChart };
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
function addInvoiceTable(slide, items = [], options = {}) {
|
|
2
|
+
const {
|
|
3
|
+
x = 1, y = 1, w = 8,
|
|
4
|
+
currency = "$"
|
|
5
|
+
} = options;
|
|
6
|
+
|
|
7
|
+
// Header
|
|
8
|
+
slide.addShape("rect", { x, y, w, h: 0.5, fill: "333333" });
|
|
9
|
+
slide.addText("Description", { x: x + 0.2, y, w: w/2, h: 0.5, color: "FFFFFF", valign: "middle" });
|
|
10
|
+
slide.addText("Amount", { x: x + w - 2, y, w: 2, h: 0.5, color: "FFFFFF", valign: "middle", align: "right" });
|
|
11
|
+
|
|
12
|
+
let curY = y + 0.5;
|
|
13
|
+
let total = 0;
|
|
14
|
+
|
|
15
|
+
items.forEach((item, i) => {
|
|
16
|
+
const bg = i % 2 === 0 ? "F9F9F9" : "FFFFFF";
|
|
17
|
+
slide.addShape("rect", { x, y: curY, w, h: 0.4, fill: bg });
|
|
18
|
+
|
|
19
|
+
slide.addText(item.desc, { x: x + 0.2, y: curY, w: w/2, h: 0.4, valign: "middle", fontSize: 11 });
|
|
20
|
+
slide.addText(`${currency}${item.amount}`, { x: x + w - 2, y: curY, w: 2, h: 0.4, valign: "middle", align: "right", fontSize: 11 });
|
|
21
|
+
|
|
22
|
+
total += item.amount;
|
|
23
|
+
curY += 0.4;
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
// Total Row
|
|
27
|
+
slide.addShape("line", { x, y: curY, w, h: 0, line: { color: "000000" } });
|
|
28
|
+
slide.addText("TOTAL", { x: x + w - 4, y: curY + 0.1, w: 2, h: 0.5, bold: true, align: "right" });
|
|
29
|
+
slide.addText(`${currency}${total}`, { x: x + w - 2, y: curY + 0.1, w: 2, h: 0.5, bold: true, align: "right", fontSize: 14 });
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
module.exports = { addInvoiceTable };
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
function addMatrixGrid(slide, quadrants = [], options = {}) {
|
|
2
|
+
const {
|
|
3
|
+
x = 1, y = 1, w = 5, h = 5,
|
|
4
|
+
xAxis = "Effort", yAxis = "Impact"
|
|
5
|
+
} = options;
|
|
6
|
+
|
|
7
|
+
const halfW = w / 2;
|
|
8
|
+
const halfH = h / 2;
|
|
9
|
+
|
|
10
|
+
// Backgrounds
|
|
11
|
+
const colors = ["E3F2FD", "E8F5E9", "FFF3E0", "FFEBEE"]; // TL, TR, BL, BR
|
|
12
|
+
const pos = [
|
|
13
|
+
{ ox: 0, oy: 0 }, { ox: halfW, oy: 0 },
|
|
14
|
+
{ ox: 0, oy: halfH }, { ox: halfW, oy: halfH }
|
|
15
|
+
];
|
|
16
|
+
|
|
17
|
+
pos.forEach((p, i) => {
|
|
18
|
+
slide.addShape("rect", {
|
|
19
|
+
x: x + p.ox, y: y + p.oy, w: halfW, h: halfH,
|
|
20
|
+
fill: colors[i]
|
|
21
|
+
});
|
|
22
|
+
if (quadrants[i]) {
|
|
23
|
+
slide.addText(quadrants[i], {
|
|
24
|
+
x: x + p.ox, y: y + p.oy, w: halfW, h: halfH,
|
|
25
|
+
align: "center", valign: "middle", bold: true, color: "555555"
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
// Axis Labels
|
|
31
|
+
// Y-Axis
|
|
32
|
+
slide.addText(yAxis, {
|
|
33
|
+
x: x - 0.6, y: y, w: 0.5, h: h,
|
|
34
|
+
rotate: 270, align: "center", bold: true
|
|
35
|
+
});
|
|
36
|
+
// X-Axis
|
|
37
|
+
slide.addText(xAxis, {
|
|
38
|
+
x: x, y: y + h + 0.1, w: w, h: 0.5,
|
|
39
|
+
align: "center", bold: true
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
module.exports = { addMatrixGrid };
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
function addMindMap(slide, centerText, branches = [], options = {}) {
|
|
2
|
+
const { x = 4, y = 2.5, w = 6, h = 4 } = options;
|
|
3
|
+
|
|
4
|
+
// Center Node
|
|
5
|
+
slide.addShape("oval", {
|
|
6
|
+
x: x, y: y, w: 1.5, h: 1.0,
|
|
7
|
+
fill: "FFFFFF", line: { color: "000000", width: 2 }
|
|
8
|
+
});
|
|
9
|
+
slide.addText(centerText, {
|
|
10
|
+
x: x, y: y, w: 1.5, h: 1.0,
|
|
11
|
+
align: "center", valign: "middle", bold: true
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
// Branches
|
|
15
|
+
// Very simple radial distribution
|
|
16
|
+
const angleStep = (2 * Math.PI) / branches.length;
|
|
17
|
+
const radius = 2.5;
|
|
18
|
+
|
|
19
|
+
branches.forEach((branch, i) => {
|
|
20
|
+
const angle = i * angleStep;
|
|
21
|
+
const nodeX = x + 0.75 + Math.cos(angle) * radius; // 0.75 is half width correction
|
|
22
|
+
const nodeY = y + 0.5 + Math.sin(angle) * radius;
|
|
23
|
+
|
|
24
|
+
// Line
|
|
25
|
+
slide.addShape("line", {
|
|
26
|
+
x: x + 0.75, y: y + 0.5,
|
|
27
|
+
w: (nodeX - (x + 0.75)), h: (nodeY - (y + 0.5)),
|
|
28
|
+
line: { color: "666666" }
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
// Branch Node
|
|
32
|
+
slide.addShape("roundRect", {
|
|
33
|
+
x: nodeX - 0.75, y: nodeY - 0.3, w: 1.5, h: 0.6,
|
|
34
|
+
fill: "F0F0F0", line: { color: "999999" }
|
|
35
|
+
});
|
|
36
|
+
slide.addText(branch, {
|
|
37
|
+
x: nodeX - 0.75, y: nodeY - 0.3, w: 1.5, h: 0.6,
|
|
38
|
+
align: "center", valign: "middle", fontSize: 10
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
module.exports = { addMindMap };
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
function addOrgChart(slide, rootData, options = {}) {
|
|
2
|
+
const {
|
|
3
|
+
x = 1, y = 1, w = 8, h = 4,
|
|
4
|
+
nodeW = 1.5, nodeH = 0.6,
|
|
5
|
+
gapX = 0.2, gapY = 0.8,
|
|
6
|
+
color = "007AFF"
|
|
7
|
+
} = options;
|
|
8
|
+
|
|
9
|
+
// Simple recursive renderer for 2 levels (Root -> Children)
|
|
10
|
+
// A full tree algorithm is complex; this covers the most common use case (CEO -> Reports)
|
|
11
|
+
|
|
12
|
+
// 1. Draw Root
|
|
13
|
+
const rootX = x + (w - nodeW) / 2;
|
|
14
|
+
const rootY = y;
|
|
15
|
+
|
|
16
|
+
slide.addShape("rect", {
|
|
17
|
+
x: rootX, y: rootY, w: nodeW, h: nodeH,
|
|
18
|
+
fill: color, line: { color: "FFFFFF" }
|
|
19
|
+
});
|
|
20
|
+
slide.addText(rootData.name, {
|
|
21
|
+
x: rootX, y: rootY, w: nodeW, h: nodeH,
|
|
22
|
+
color: "FFFFFF", bold: true, align: "center", fontSize: 11
|
|
23
|
+
});
|
|
24
|
+
if (rootData.role) {
|
|
25
|
+
slide.addText(rootData.role, {
|
|
26
|
+
x: rootX, y: rootY + 0.35, w: nodeW, h: 0.25,
|
|
27
|
+
color: "E0E0E0", align: "center", fontSize: 9
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// 2. Draw Children
|
|
32
|
+
if (rootData.children && rootData.children.length > 0) {
|
|
33
|
+
const count = rootData.children.length;
|
|
34
|
+
// Calculate total width of children row
|
|
35
|
+
const totalChildrenW = count * nodeW + (count - 1) * gapX;
|
|
36
|
+
let startX = x + (w - totalChildrenW) / 2;
|
|
37
|
+
const childY = rootY + nodeH + gapY;
|
|
38
|
+
|
|
39
|
+
// Draw Connector Lines
|
|
40
|
+
// Vertical from Root
|
|
41
|
+
slide.addShape("line", {
|
|
42
|
+
x: rootX + nodeW/2, y: rootY + nodeH,
|
|
43
|
+
w: 0, h: gapY / 2,
|
|
44
|
+
line: { color: "333333", width: 1 }
|
|
45
|
+
});
|
|
46
|
+
// Horizontal Bar
|
|
47
|
+
slide.addShape("line", {
|
|
48
|
+
x: startX + nodeW/2, y: rootY + nodeH + gapY/2,
|
|
49
|
+
w: totalChildrenW - nodeW, h: 0,
|
|
50
|
+
line: { color: "333333", width: 1 }
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
rootData.children.forEach((child, i) => {
|
|
54
|
+
const curX = startX + i * (nodeW + gapX);
|
|
55
|
+
|
|
56
|
+
// Vertical to Child
|
|
57
|
+
slide.addShape("line", {
|
|
58
|
+
x: curX + nodeW/2, y: rootY + nodeH + gapY/2,
|
|
59
|
+
w: 0, h: gapY / 2,
|
|
60
|
+
line: { color: "333333", width: 1 }
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
// Child Box
|
|
64
|
+
slide.addShape("rect", {
|
|
65
|
+
x: curX, y: childY, w: nodeW, h: nodeH,
|
|
66
|
+
fill: "FFFFFF", line: { color: color }
|
|
67
|
+
});
|
|
68
|
+
slide.addText(child.name, {
|
|
69
|
+
x: curX, y: childY, w: nodeW, h: nodeH,
|
|
70
|
+
color: "333333", bold: true, align: "center", fontSize: 10
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
module.exports = { addOrgChart };
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
function addPersonaCard(slide, persona, options = {}) {
|
|
2
|
+
const {
|
|
3
|
+
x = 1, y = 1, w = 4, h = 5,
|
|
4
|
+
accent = "9C27B0"
|
|
5
|
+
} = options;
|
|
6
|
+
|
|
7
|
+
// Background
|
|
8
|
+
slide.addShape("rect", { x, y, w, h, fill: "F9F9F9", line: { color: "DDDDDD" } });
|
|
9
|
+
|
|
10
|
+
// Header Color
|
|
11
|
+
slide.addShape("rect", { x, y, w, h: 0.15, fill: accent });
|
|
12
|
+
|
|
13
|
+
// Photo Placeholder
|
|
14
|
+
slide.addShape("oval", { x: x + 0.3, y: y + 0.4, w: 1, h: 1, fill: "CCCCCC" });
|
|
15
|
+
|
|
16
|
+
// Name & Role
|
|
17
|
+
slide.addText(persona.name, {
|
|
18
|
+
x: x + 1.5, y: y + 0.4, w: w - 1.6, h: 0.5,
|
|
19
|
+
fontSize: 16, bold: true, color: "333333"
|
|
20
|
+
});
|
|
21
|
+
slide.addText(persona.role, {
|
|
22
|
+
x: x + 1.5, y: y + 0.8, w: w - 1.6, h: 0.4,
|
|
23
|
+
fontSize: 12, color: accent
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
// Quote
|
|
27
|
+
slide.addText(`"${persona.quote}"`, {
|
|
28
|
+
x: x + 0.3, y: y + 1.6, w: w - 0.6, h: 0.6,
|
|
29
|
+
italic: true, fontSize: 11, color: "666666"
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
// Sections
|
|
33
|
+
const drawSection = (title, items, sY) => {
|
|
34
|
+
slide.addText(title, { x: x + 0.3, y: sY, w: w - 0.6, h: 0.3, bold: true, fontSize: 10 });
|
|
35
|
+
items.forEach((item, i) => {
|
|
36
|
+
slide.addText(`• ${item}`, { x: x + 0.3, y: sY + 0.3 + (i*0.25), w: w - 0.6, h: 0.25, fontSize: 9 });
|
|
37
|
+
});
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
if (persona.goals) drawSection("GOALS", persona.goals, y + 2.4);
|
|
41
|
+
if (persona.frustrations) drawSection("FRUSTRATIONS", persona.frustrations, y + 3.8);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
module.exports = { addPersonaCard };
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
function addProcessCycle(slide, steps = [], options = {}) {
|
|
2
|
+
const {
|
|
3
|
+
x = 3, y = 3, radius = 2,
|
|
4
|
+
color = "007AFF"
|
|
5
|
+
} = options;
|
|
6
|
+
|
|
7
|
+
if (steps.length === 0) return;
|
|
8
|
+
|
|
9
|
+
const angleStep = (2 * Math.PI) / steps.length;
|
|
10
|
+
|
|
11
|
+
steps.forEach((step, index) => {
|
|
12
|
+
// Start from top ( -PI/2 )
|
|
13
|
+
const angle = index * angleStep - Math.PI / 2;
|
|
14
|
+
|
|
15
|
+
// Position for the circle center
|
|
16
|
+
const cx = x + radius * Math.cos(angle);
|
|
17
|
+
const cy = y + radius * Math.sin(angle);
|
|
18
|
+
|
|
19
|
+
// Circle Node
|
|
20
|
+
const nodeSize = 1.2;
|
|
21
|
+
slide.addShape("oval", {
|
|
22
|
+
x: cx - nodeSize/2,
|
|
23
|
+
y: cy - nodeSize/2,
|
|
24
|
+
w: nodeSize,
|
|
25
|
+
h: nodeSize,
|
|
26
|
+
fill: "FFFFFF",
|
|
27
|
+
line: { color: color, width: 3 }
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
// Step Number
|
|
31
|
+
slide.addText(String(index + 1), {
|
|
32
|
+
x: cx - nodeSize/2, y: cy - nodeSize/2, w: nodeSize, h: 0.4,
|
|
33
|
+
align: "center", valign: "bottom", fontSize: 14, bold: true, color: color
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
// Step Title
|
|
37
|
+
slide.addText(step, {
|
|
38
|
+
x: cx - nodeSize/2 + 0.1, y: cy, w: nodeSize - 0.2, h: 0.5,
|
|
39
|
+
align: "center", valign: "top", fontSize: 10
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
// (Optional) Simple Arrow/Line pointing to next could be added here
|
|
43
|
+
// using trigonometry for start/end points, but pure shapes are cleaner.
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
module.exports = { addProcessCycle };
|