mck-ppt-design
Create professional, consultant-grade PowerPoint presentations from scratch using python-pptx with McKinsey-style design. Use when user asks to create slides, pitch decks, business presentations, strategy decks, quarterly reviews, board meeting slides, or any professional PPTX. Generates clean, flat-design presentations with 70 layout patterns across 12 categories (structure, data, framework, comparison, narrative, timeline, team, charts, images, advanced viz, dashboards, visual storytelling), consistent typography, zero file-corruption issues, and production-hardened guard rails for spacing, overflow, legend consistency, and title style uniformity.
Works with
---
name: mck-ppt-design
description: Create professional, consultant-grade PowerPoint presentations from scratch using python-pptx with McKinsey-style design. Use when user asks to create slides, pitch decks, business presentations, strategy decks, quarterly reviews, board meeting slides, or any professional PPTX. Generates clean, flat-design presentations with 70 layout patterns across 12 categories (structure, data, framework, comparison, narrative, timeline, team, charts, images, advanced viz, dashboards, visual storytelling), consistent typography, zero file-corruption issues, and production-hardened guard rails for spacing, overflow, legend consistency, and title style uniformity.
license: Apache-2.0
---
# McKinsey PPT Design Framework
## Overview
This skill encodes the complete design specification for **professional business presentations** — a consultant-grade PowerPoint framework based on McKinsey design principles. It includes:
- **70 layout patterns** across 12 categories (structure, data, framework, comparison, narrative, timeline, team, charts, **images**, **advanced viz**, **dashboards**, **visual storytelling**)
- **Color system** and strict typography hierarchy
- **Python-pptx code patterns** ready to copy and customize
- **Three-layer defense** against file corruption (zero `p:style` leaks)
- **Chinese + English font handling** (KaiTi / Georgia / Arial)
- **Image placeholder system** for image-containing layouts (v1.8)
- **Production Guard Rails** — spacing/overflow protection, legend color consistency, title style uniformity, axis label centering (v1.9)
- **Code Efficiency guidelines** — variable reuse patterns, constant extraction, loop optimization for faster generation (v1.9)
All specifications have been refined through iterative production feedback to ensure visual consistency, professional polish, and zero-defect output.
---
## When to Use This Skill
Use this skill when users ask to:
1. **Create presentations** — pitch decks, strategy presentations, quarterly reviews, board meeting slides, consulting deliverables, project proposals, business plans
2. **Generate slides programmatically** — using python-pptx to produce .pptx files from scratch
3. **Apply professional design** — McKinsey / BCG / Bain consulting style, clean flat design, no shadows or gradients
4. **Build specific slide types** — cover pages, data dashboards, 2x2 matrices, timelines, funnels, team introductions, executive summaries, comparison layouts
5. **Fix PPT issues** — file corruption ("needs repair"), shadow/3D artifacts, inconsistent fonts, Chinese text rendering problems
6. **Maintain design consistency** — unified color palette, font hierarchy, spacing, and line treatments across all slides
---
## Core Design Philosophy
### McKinsey Design Principles
1. **Extreme Minimalism** - Remove all non-essential visual elements
- No color blocks unless absolutely necessary
- Lines: thin, flat, no shadows or 3D effects
- Shapes: simple, clean, no gradients
- Text: clear hierarchy, maximum readability
2. **Consistency** - Repeat visual language across all slides
- Unified color palette (navy + cyan + grays)
- Consistent font sizes and weights for same content types
- Aligned spacing and margins
- Matching line widths and styles
3. **Hierarchy** - Guide viewer through information
- Title bar (22pt) → Sub-headers (18pt) → Body (14pt) → Details (9pt)
- Navy for primary elements, gray for secondary, black for divisions
- Visual weight through bold, color, size (not through effects)
4. **Flat Design** - No 3D, shadows, or gradients
- Pure solid colors only
- All lines are simple strokes with no effects
- Shapes have no shadow or reflection effects
- Circles are solid fills, not 3D spheres
---
## Design Specifications
### Color Palette
All colors in RGB format for python-pptx:
| Color Name | Hex | RGB | Usage | Notes |
|-----------|-----|-----|-------|-------|
| **NAVY** | #051C2C | (5, 28, 44) | Primary, titles, circles | Corporate, formal tone |
| **CYAN** | #00A9F4 | (0, 169, 244) | Originally used in v1 | **DEPRECATED** - Use NAVY for consistency |
| **WHITE** | #FFFFFF | (255, 255, 255) | Backgrounds, text | On navy backgrounds only |
| **BLACK** | #000000 | (0, 0, 0) | Lines, text separators | For clarity and contrast |
| **DARK_GRAY** | #333333 | (51, 51, 51) | Body text, descriptions | Main content text |
| **MED_GRAY** | #666666 | (102, 102, 102) | Secondary text, labels | Softer tone than DARK_GRAY |
| **LINE_GRAY** | #CCCCCC | (204, 204, 204) | Light separators, table rows | Table separators only |
| **BG_GRAY** | #F2F2F2 | (242, 242, 242) | Background panels | Takeaway/highlight areas |
**Key Rule**: Use navy (#051C2C) everywhere, especially for:
- All circle indicators (A, B, C, 1, 2, 3)
- All action titles
- All primary section headers
- All TOC highlight colors
#### Accent Colors (for multi-item differentiation)
When a slide contains **3 or more parallel items** (e.g., comparison cards, pillar frameworks, multi-category overviews), use these accent colors to create visual distinction between items. Without accent colors, parallel items become visually indistinguishable.
| Accent Name | Hex | RGB | Paired Light BG | Usage |
|-------------|-----|-----|-----------------|-------|
| **ACCENT_BLUE** | #006BA6 | (0, 107, 166) | #E3F2FD | First item accent |
| **ACCENT_GREEN** | #007A53 | (0, 122, 83) | #E8F5E9 | Second item accent |
| **ACCENT_ORANGE** | #D46A00 | (212, 106, 0) | #FFF3E0 | Third item accent |
| **ACCENT_RED** | #C62828 | (198, 40, 40) | #FFEBEE | Fourth item / warning |
**Accent Color Rules**:
- Use accent colors for: **card top accent borders** (thin 0.06" rect), **circle labels** (`add_oval()` bg param), **section sub-headers** (font_color)
- Use paired light BG for: **card background fills** only
- Body text inside cards ALWAYS remains **DARK_GRAY (#333333)**
- NAVY remains the primary color for **single-focus** elements (one card, one stat, cover title)
- Use accent colors **ONLY** when the slide has 3+ parallel items that need visual distinction
- The fourth item (D) can use NAVY instead of ACCENT_RED if red feels inappropriate for the content
```python
# Accent color constants
ACCENT_BLUE = RGBColor(0x00, 0x6B, 0xA6)
ACCENT_GREEN = RGBColor(0x00, 0x7A, 0x53)
ACCENT_ORANGE = RGBColor(0xD4, 0x6A, 0x00)
ACCENT_RED = RGBColor(0xC6, 0x28, 0x28)
LIGHT_BLUE = RGBColor(0xE3, 0xF2, 0xFD)
LIGHT_GREEN = RGBColor(0xE8, 0xF5, 0xE9)
LIGHT_ORANGE = RGBColor(0xFF, 0xF3, 0xE0)
LIGHT_RED = RGBColor(0xFF, 0xEB, 0xEE)
```
---
### Typography System
#### Font Families
```
English Headers: Georgia (serif, elegant)
English Body: Arial (sans-serif, clean)
Chinese (ALL): KaiTi (楷体, traditional brush style)
(fallback: SimSun 宋体)
```
**Critical Implementation**:
```python
def set_ea_font(run, typeface='KaiTi'):
"""Set East Asian font for Chinese text"""
rPr = run._r.get_or_add_rPr()
ea = rPr.find(qn('a:ea'))
if ea is None:
ea = rPr.makeelement(qn('a:ea'), {})
rPr.append(ea)
ea.set('typeface', typeface)
```
Every paragraph with Chinese text MUST apply `set_ea_font()` to all runs.
#### Font Size Hierarchy
| Size | Type | Examples | Notes |
|------|------|----------|-------|
| **44pt** | Cover Title | "项目名称" | Cover slide only, Georgia |
| **28pt** | Section Header | "目录" (TOC title) | Largest body content, Georgia |
| **24pt** | Subtitle | Tagline on cover | Cover slide only |
| **22pt** | Action Title | Slide title bars | Main content titles, **bold**, Georgia |
| **18pt** | Sub-Header | Column headers, section names | Supporting titles |
| **16pt** | Emphasis Text | Bottom takeaway on slide 8 | Callout text, bold |
| **14pt** | Body Text | Tables, lists, descriptions | **PRIMARY BODY SIZE**, all main content |
| **9pt** | Footnote | Source attribution | Smallest, gray color only |
**No other sizes should be used** - stick to this hierarchy exclusively.
---
### Line Treatment (CRITICAL)
#### Line Rendering Rules
1. **All lines are FLAT** - no shadows, no effects, no 3D
2. **Remove theme style references** - prevents automatic shadow application
3. **Solid color only** - no gradients or patterns
4. **Width varies by context** - see table below
#### Line Width Specifications
| Usage | Width | Color | Context |
|-------|-------|-------|---------|
| **Title separator** (under action titles) | 0.5pt | BLACK | Below 22pt title |
| **Column/section divider** (under headers) | 0.5pt | BLACK | Below 18pt headers |
| **Table header line** | 1.0pt | BLACK | Between header and first row |
| **Table row separator** | 0.5pt | LINE_GRAY (#CCCCCC) | Between table rows |
| **Timeline line** (roadmap) | 0.75pt | LINE_GRAY | Background for phase indicators |
| **Cover accent line** | 2.0pt | NAVY | Decorative bottom-left on cover |
| **Column internal divider** | 0.5pt | BLACK | Between "是什么" and "独到之处" |
#### Code Implementation (v1.1 — Rectangle-based Lines)
**CRITICAL**: Do NOT use `slide.shapes.add_connector()` for lines. Connectors carry `<p:style>` elements that reference theme effects and cause file corruption. Instead, draw lines as ultra-thin rectangles:
```python
def add_hline(slide, x, y, length, color=BLACK, thickness=Pt(0.5)):
"""Draw a horizontal line using a thin rectangle (no connector, no p:style)."""
from pptx.util import Emu
h = max(int(thickness), Emu(6350)) # minimum ~0.5pt
return add_rect(slide, x, y, length, h, color)
```
**IMPORTANT**: Never use `add_connector()` — it causes file corruption. Always use `add_hline()` (thin rectangle).
#### Post-Save Full Cleanup (v1.1 — Nuclear Sanitization)
After `prs.save(outpath)`, ALWAYS run full cleanup that sanitizes **both** theme XML **and** all slide XML:
```python
import zipfile, os
from lxml import etree
def full_cleanup(outpath):
"""Remove ALL p:style from every slide + theme shadows/3D."""
tmppath = outpath + '.tmp'
with zipfile.ZipFile(outpath, 'r') as zin:
with zipfile.ZipFile(tmppath, 'w', zipfile.ZIP_DEFLATED) as zout:
for item in zin.infolist():
data = zin.read(item.filename)
if item.filename.endswith('.xml'):
root = etree.fromstring(data)
ns_p = 'http://schemas.openxmlformats.org/presentationml/2006/main'
ns_a = 'http://schemas.openxmlformats.org/drawingml/2006/main'
# Remove ALL p:style elements from all shapes/connectors
for style in root.findall(f'.//{{{ns_p}}}style'):
style.getparent().remove(style)
# Remove shadows and 3D from theme
if 'theme' in item.filename.lower():
for tag in ['outerShdw', 'innerShdw', 'scene3d', 'sp3d']:
for el in root.findall(f'.//{{{ns_a}}}{tag}'):
el.getparent().remove(el)
data = etree.tostring(root, xml_declaration=True,
encoding='UTF-8', standalone=True)
zout.writestr(item, data)
os.replace(tmppath, outpath)
```
---
### Text Box & Shape Treatment
#### Text Box Padding
All text boxes must have consistent internal padding to prevent text touching edges:
```python
bodyPr = tf._txBody.find(qn('a:bodyPr'))
# All margins: 45720 EMUs = ~0.05 inches
for attr in ['lIns','tIns','rIns','bIns']:
bodyPr.set(attr, '45720')
```
#### Vertical Anchoring
Text must be anchored correctly based on usage:
| Content Type | Anchor | Code | Notes |
|--------------|--------|------|-------|
| Action titles | MIDDLE | `anchor='ctr'` | Centered vertically in bar |
| Body text | TOP | `anchor='t'` | Default, aligns to top |
| Labels | CENTER | `anchor='ctr'` | For circle labels |
```python
anchor_map = {
MSO_ANCHOR.MIDDLE: 'ctr',
MSO_ANCHOR.BOTTOM: 'b',
MSO_ANCHOR.TOP: 't'
}
bodyPr.set('anchor', anchor_map.get(anchor, 't'))
```
#### Shape Styling
All shapes (rectangles, circles) must have:
- Solid fill color (no gradients)
- NO border/line (`shape.line.fill.background()`)
- **p:style removed** immediately after creation (`_clean_shape()`)
- No shadow effects (enforced by both inline cleanup and post-save full_cleanup)
```python
def _clean_shape(shape):
"""Remove p:style from any shape to prevent effect references."""
sp = shape._element
style = sp.find(qn('p:style'))
if style is not None:
sp.remove(style)
shape = slide.shapes.add_shape(MSO_SHAPE.RECTANGLE, left, top, width, height)
shape.fill.solid()
shape.fill.fore_color.rgb = BG_GRAY
shape.line.fill.background() # CRITICAL: removes border
_clean_shape(shape) # CRITICAL: removes p:style
```
---
## Presentation Planning
This section provides **mandatory guidance** for planning presentation structure, selecting layouts, and ensuring adequate content density. These rules dramatically improve output quality across different LLM models.
### Recommended Slide Structures
When creating a presentation, follow these templates unless the user explicitly specifies a different structure:
#### Standard Presentation (10-12 slides)
```
Slide 1: Cover Slide (Pattern #1 or #4)
Slide 2: Table of Contents (Pattern #6) — list ALL content sections
Slide 3: Executive Summary / Core Thesis (Pattern #24 or #8+#10)
Slides 4-7: Supporting Arguments (one per slide, vary layouts)
Slides 8-10: Case Studies / Evidence (Pattern #33 or #19)
Slide 11: Synthesis / Roadmap (Pattern #29 or #16)
Slide 12: Key Takeaways + Closing (Pattern #34 or #36)
```
#### Short Presentation (6-8 slides)
```
Slide 1: Cover Slide
Slide 2: Executive Summary (Pattern #24)
Slides 3-5: Core Content (vary layouts: #8, #14, #19, #33)
Slide 6: Synthesis / Timeline (Pattern #29)
Slide 7: Key Takeaways (Pattern #34)
Slide 8: Closing (Pattern #36)
```
**CRITICAL RULES**:
- **Minimum slide count**: 8 slides for any substantive topic. If the user's content supports 10+, generate 10+.
- **Never stop early**: Generate ALL planned slides in a single script. Do not truncate.
- **TOC must list ALL sections**: The Table of Contents slide must enumerate every content slide by number and title.
### Layout Diversity Requirement
**Each content slide MUST use a DIFFERENT layout pattern from its neighbors.** Repeating the same layout on consecutive slides makes the presentation feel monotonous and unprofessional.
Match content type to the optimal layout pattern:
| Content Type | Recommended Layouts | Avoid |
|---|---|---|
| Single key statistic | Big Number (#8) | Plain text |
| 2 options comparison | Side-by-Side (#19), Before/After (#20), Metric Comparison Row (#62) | Two-column text |
| 3-4 parallel concepts | Three-Pillar (#14), Four-Column (#27), Metric Cards (#10), Icon Grid (#63) | Bullet list |
| Process / steps | Process Chevron (#16), Vertical Steps (#30), Value Chain (#67) | Numbered text |
| Timeline | Timeline/Roadmap (#29), Cycle (#31) | Bullet list |
| Data table | Data Table (#9), Scorecard (#22), Harvey Ball Table (#56) | Plain text |
| Case study | Case Study (#33), Case Study with Image (#45) | Two-column text |
| Summary / conclusion | Executive Summary (#24), Key Takeaway (#25) | Bullet list |
| Multiple KPIs | Three-Stat Dashboard (#12), Two-Stat Comparison (#11), KPI Tracker (#52), Dashboard (#57) | Plain text |
| **Time series + values/percentages** | **Grouped Bar (#37), Stacked Bar (#38), Line Chart (#50), Stacked Area (#70)** | **Data Table, Scorecard** |
| **Category ranking / comparison** | **Horizontal Bar (#39), Grouped Bar (#37), Pareto (#51)** | **Bullet list, Plain text** |
| **Part-of-whole / composition** | **Donut (#48), Pie (#64), Stacked Bar (#38)** | **Bullet list** |
| **Content with visual / photo** | **Content+Right Image (#40), Left Image+Content (#41), Three Images (#42)** | **Text-only layouts** |
| **Risk / evaluation matrix** | **Risk Matrix (#54), SWOT (#65), Harvey Ball (#56), 2x2 Matrix (#13)** | **Bullet list** |
| **Strategic recommendations** | **Numbered List+Panel (#69), Decision Tree (#60), Checklist (#61)** | **Two-column text** |
| **Multi-KPI executive dashboard** | **Dashboard KPI+Chart (#57), Dashboard Table+Chart (#58)** | **Simple table** |
| **Stakeholder / relationship** | **Stakeholder Map (#59)** | **Bullet list** |
| **Meeting agenda** | **Agenda (#66)** | **Plain text** |
**NEVER** use Two-Column Text (#26) for more than 1 slide per deck. It is the least visually engaging layout.
**CHART PRIORITY RULE**: When data contains dates/periods + numeric values or percentages (e.g., `3/4 正面 20% 中性 80%` or `Q1: ¥850万`), you **MUST** use a Chart pattern (#37-#39, #48-#56, #64, #70) instead of a text-based layout. Charts maximize data-ink ratio and are the most visually compelling way to present time-series data.
**IMAGE PRIORITY RULE** (v1.8): When the content involves case studies, product showcases, location overviews, or any scenario where a visual/photo would strengthen the narrative, prefer Image+Content layouts (#40-#47, #68) over text-only layouts. The `add_image_placeholder()` function creates gray placeholder boxes that users replace with real images after generation.
### Content Density Requirements
"Minimalism" in McKinsey design means **removing decorative noise** (shadows, gradients, clip-art), NOT removing content. A slide with 80% whitespace is not minimalist — it is EMPTY.
**Mandatory minimums per content slide**:
1. **At least 3 distinct visual blocks** — e.g., title bar + content area + takeaway box, or title + left panel + right panel
2. **Body text area utilization ≥ 50%** of the available content space (between title bar at 1.4" and source line at 7.05")
3. **Action Title must be a FULL SENTENCE** expressing the slide's key insight:
- ✅ `"连接组约束的AI模型将自由参数减少90%,实现单细胞精度预测"`
- ❌ `"连接组约束的AI模型"`
4. **Use specific data points** when the user provides them (numbers, percentages, names) — display them prominently with Big Number or Metric Card patterns
5. **Source attribution** (`add_source()`) on every content slide with specific references, not generic labels
### Production Guard Rails (v1.9)
These rules address **recurring production defects** observed across multiple presentation generations. Each rule is derived from real-world user feedback and must be followed without exception.
#### Rule 1: Spacing Between Content Blocks and Bottom Bars
**Problem observed**: Tables, charts, or content grids placed immediately above a bottom summary/action bar (e.g., "行动公式", "趋势判读", "风险提示") with zero vertical gap, making them visually merged.
**MANDATORY**: There MUST be **at least 0.15" vertical gap** between the last content block and any bottom bar/summary box. Calculate positions explicitly:
```python
# ❌ WRONG: content ends at Inches(6.15), bottom bar also at Inches(6.15)
last_content_bottom = content_top + num_rows * row_height
bar_y = last_content_bottom # NO GAP!
# ✅ CORRECT: explicit gap
BOTTOM_BAR_GAP = Inches(0.2)
bar_y = last_content_bottom + BOTTOM_BAR_GAP
```
**Validation formula**: `bottom_bar_y >= last_content_bottom + Inches(0.15)`
#### Rule 2: Content Overflow Protection
**Problem observed**: Text or shapes extending beyond the right margin (left_margin + content_width) or bottom margin (source line at 7.05").
**MANDATORY** overflow checks:
1. **Right margin**: Every element's `left + width ≤ LM + CW` (i.e., `Inches(0.8) + Inches(11.733) = Inches(12.533)`)
2. **Bottom margin**: Every element's `top + height ≤ Inches(6.95)` (leaving room for source line at 7.05")
3. **Text in bounded boxes**: When placing text inside a colored `add_rect()` box, the text box MUST be **inset by at least 0.15"** on each side:
```python
# ✅ CORRECT: text inset within its container box
box_left = LM
box_width = CW
add_rect(s, box_left, box_y, box_width, box_h, BG_GRAY)
add_text(s, box_left + Inches(0.3), box_y, box_width - Inches(0.6), box_h,
text, ...) # 0.3" padding on each side
```
4. **Multi-column layouts**: When calculating column widths, account for inter-column gaps AND the right margin:
```python
# total available = CW = Inches(11.733)
num_cols = 3
gap = Inches(0.2)
col_w = (CW - gap * (num_cols - 1)) / num_cols # NOT CW / num_cols
```
5. **Long text truncation**: If generated text may exceed box boundaries, reduce `font_size` by 1-2pt or abbreviate text. Never allow visible overflow.
#### Rule 3: Bottom Whitespace Elimination
**Problem observed**: Charts or content areas end at ~Inches(5.5) while the bottom bar sits at ~Inches(6.3), leaving ~0.8" of dead whitespace.
**MANDATORY**: The bottom summary bar should be positioned at **no higher than Inches(6.1)** and **no lower than Inches(6.4)**. Adjust chart/content heights to fill available space. Target: visible whitespace between content and bottom bar ≤ 0.3".
```python
# ✅ CORRECT: Compute bottom bar position dynamically
content_bottom = chart_top + chart_height
# Place bottom bar close to content (but with minimum gap)
bar_y = max(content_bottom + Inches(0.15), Inches(6.1))
bar_y = min(bar_y, Inches(6.4)) # don't push past safe zone
```
#### Rule 4: Legend Color Consistency
**Problem observed**: Chart legends using plain black text "■" symbols (`■ 基准值 ■ 增加 ■ 减少`) while actual chart bars use NAVY, ACCENT_RED, ACCENT_GREEN — colors don't match.
**MANDATORY**: Every legend indicator MUST use a **colored square** (`add_rect()`) matching the exact color used in the chart below it. Never use text-only legends with "■" character.
```python
# ❌ WRONG: Text-only legend with black squares
add_text(s, LM, legend_y, CW, Inches(0.25),
'■ 基准值 ■ 增加 ■ 减少', ...)
# ✅ CORRECT: Color-matched legend squares
lgx = LM + Inches(5)
add_rect(s, lgx, legend_y, Inches(0.15), Inches(0.15), NAVY)
add_text(s, lgx + Inches(0.2), legend_y, Inches(0.9), Inches(0.25),
'基准值', font_size=Pt(10), font_color=MED_GRAY)
add_rect(s, lgx + Inches(1.3), legend_y, Inches(0.15), Inches(0.15), ACCENT_RED)
add_text(s, lgx + Inches(1.5), legend_y, Inches(0.9), Inches(0.25),
'增加', font_size=Pt(10), font_color=MED_GRAY)
# ... repeat for each series
```
**Legend placement**: Inline with or immediately below the chart subtitle line (typically at Inches(1.15)-Inches(1.20)). Legend squares are 0.15" × 0.15" with 0.05" gap to label text.
#### Rule 5: Title Style Consistency
**Problem observed**: Some slides using `add_navy_title_bar()` (full-width navy background + white text) while others use `add_action_title()` (white background + black text + underline), creating jarring visual inconsistency.
**MANDATORY**: Use **`add_action_title()`** (`aat()`) as the **ONLY** title style for ALL content slides. The navy title bar (`antb()`) is **DEPRECATED for content slides** and should only appear if explicitly requested by the user.
```python
# ❌ DEPRECATED: Navy background title bar
def add_navy_title_bar(slide, text):
add_rect(s, 0, 0, SW, Inches(0.75), NAVY)
add_text(s, LM, 0, CW, Inches(0.75), text, font_color=WHITE, ...)
# ✅ CORRECT: Consistent white-background action title
def add_action_title(slide, text, title_size=Pt(22)):
add_text(s, Inches(0.8), Inches(0.15), Inches(11.7), Inches(0.9), text,
font_size=title_size, font_color=BLACK, bold=True, font_name='Georgia')
add_hline(s, Inches(0.8), Inches(1.05), Inches(11.7), BLACK, Pt(0.5))
```
**Note**: When `add_action_title()` is used, content starts at **Inches(1.25)** (not Inches(1.0)). Account for this when positioning grids, tables, or charts below the title.
#### Rule 6: Axis Label Centering in Matrix/Grid Charts
**Problem observed**: In 2×2 matrix layouts (#13, #59, #65), axis labels ("用户规模↑", "技术壁垒→") positioned at fixed offsets rather than centered on their respective axes, causing visual misalignment.
**MANDATORY**: Axis labels MUST be **centered on the full span** of their axis:
```python
# Grid dimensions
grid_left = LM + Inches(2.0)
grid_top = Inches(1.65)
cell_w = Inches(4.5) # width of each quadrant
cell_h = Inches(2.0) # height of each quadrant
grid_w = 2 * cell_w # full grid width
grid_h = 2 * cell_h # full grid height
# ✅ CORRECT: Y-axis label centered vertically on FULL grid height
add_text(s, LM, grid_top, Inches(1.8), grid_h,
'Y轴标签↑', alignment=PP_ALIGN.CENTER, anchor=MSO_ANCHOR.MIDDLE)
# ✅ CORRECT: X-axis label centered horizontally on FULL grid width
add_text(s, grid_left, grid_top + grid_h + Inches(0.1), grid_w, Inches(0.3),
'X轴标签 →', alignment=PP_ALIGN.CENTER)
```
#### Rule 7: Image Placeholder Slide Requirement
**Problem observed**: Presentations generated with zero image-containing slides, resulting in a wall of text/charts that feels monotonous and lacks visual relief.
**MANDATORY**: For presentations with **8+ slides**, at least **1 slide** must include image placeholders (using `add_image_placeholder()` or custom gray boxes with "请插入图片" labels). Preferred positions:
- After the first 2-3 content slides (as a visual break)
- For case studies, product showcases, or ecosystem overviews
**Standard placeholder style** (when not using `add_image_placeholder()` helper):
```python
# Large placeholder
img_l = LM; img_t = Inches(1.3); img_w = Inches(6.5); img_h = Inches(4.0)
add_rect(s, img_l, img_t, img_w, img_h, BG_GRAY)
add_rect(s, img_l + Inches(0.04), img_t + Inches(0.04),
img_w - Inches(0.08), img_h - Inches(0.08), WHITE)
add_rect(s, img_l + Inches(0.08), img_t + Inches(0.08),
img_w - Inches(0.16), img_h - Inches(0.16), RGBColor(0xF8, 0xF8, 0xF8))
add_text(s, img_l, img_t + img_h // 2 - Inches(0.3), img_w, Inches(0.5),
'[ 请插入图片 ]', font_size=Pt(22), font_color=LINE_GRAY,
bold=True, alignment=PP_ALIGN.CENTER, anchor=MSO_ANCHOR.MIDDLE)
add_text(s, img_l, img_t + img_h // 2 + Inches(0.2), img_w, Inches(0.3),
'图片描述标签', font_size=Pt(13), font_color=MED_GRAY,
alignment=PP_ALIGN.CENTER, anchor=MSO_ANCHOR.MIDDLE)
```
This triple-border style (BG_GRAY → WHITE → #F8F8F8) creates a professional, clearly identifiable placeholder that prompts users to insert real images.
### Mandatory Slide Elements
EVERY content slide (except Cover and Closing) MUST include ALL of these:
| Element | Function | Position |
|---------|----------|----------|
| Action Title | `add_action_title(slide, text)` | Top (0.15" from top) |
| Title separator line | Included in `add_action_title()` | 1.05" from top |
| Content area | Layout-specific content blocks | 1.4" to 6.5" |
| Source attribution | `add_source(slide, text)` | 7.05" from top |
| Page number | `add_page_number(slide, n, total)` | Bottom-right corner |
Page number helper function:
```python
def add_page_number(slide, num, total):
add_text(slide, Inches(12.2), Inches(7.1), Inches(1), Inches(0.3),
f"{num}/{total}", font_size=Pt(9), font_color=MED_GRAY,
alignment=PP_ALIGN.RIGHT)
```
---
## Layout Patterns
### Slide Dimensions
```python
prs.slide_width = Inches(13.333)
prs.slide_height = Inches(7.5)
```
Widescreen format (16:9), standard for all presentations.
### Standard Margin/Padding
| Position | Size | Usage |
|----------|------|-------|
| **Left margin** | 0.8" | Default left edge |
| **Right margin** | 0.8" | Default right edge |
| **Top (below title)** | 1.4" | Content start position |
| **Bottom** | 7.05" | Source text baseline |
| **Title bar height** | 0.9" | Action title bar |
| **Title bar top** | 0.15" | From slide top |
### Slide Type Patterns
#### 1. Cover Slide (Slide 1)
Layout:
- Navy bar at very top (0.05" height)
- Main title centered (44pt, Georgia, navy) at y=2.2"
- Subtitle (24pt, dark gray) at y=3.5"
- Date/info (14pt, med gray) at y=4.5"
- Decorative navy line at x=1", y=6.8" (2" wide, 2pt)
Code template:
```python
s1 = prs.slides.add_slide(prs.slide_layouts[6])
add_rect(s1, 0, 0, prs.slide_width, Inches(0.05), NAVY)
add_text(s1, Inches(1), Inches(2.2), Inches(11), Inches(1.0),
'项目名称', font_size=Pt(44), font_name='Georgia',
font_color=NAVY, bold=True, ea_font='KaiTi')
add_text(s1, Inches(1), Inches(3.5), Inches(11), Inches(0.6),
'副标题描述', font_size=Pt(24),
font_color=DARK_GRAY, ea_font='KaiTi')
add_text(s1, Inches(1), Inches(4.5), Inches(11), Inches(0.5),
'演示文稿 | 2026年3月', font_size=BODY_SIZE,
font_color=MED_GRAY, ea_font='KaiTi')
add_line(s1, Inches(1), Inches(6.8), Inches(4), Inches(6.8),
color=NAVY, width=Pt(2))
```
#### 2. Action Title Slide (Most Content Slides)
Every main content slide has this structure:
```
┌─────────────────────────────────────────┐ 0.15"
│ ▌ Action Title (22pt, bold, black) │ ← TITLE_BAR_H = 0.9"
├─────────────────────────────────────────┤ 1.05"
│ │
│ Content area (starts at 1.4") │
│ [Tables, lists, text, etc.] │
│ │
│ │
│ ────────────────────────────────────── │ 7.05"
│ Source: ... │ 9pt, med gray
└─────────────────────────────────────────┘ 7.5"
```
Code pattern:
```python
s = prs.slides.add_slide(prs.slide_layouts[6])
add_action_title(s, 'Slide Title Here')
# Then add content below y=1.4"
add_source(s, 'Source attribution')
```
#### 3. Table Layout (Slide 4 - Five Capabilities)
Structure:
- Header row with column names (BODY_SIZE, gray, bold)
- 1.0pt black line under header
- Data rows (1.0" height each, 14pt text)
- 0.5pt gray line between rows
- 3 columns: Module (1.6" wide), Function (5.0"), Scene (5.1")
```python
# Headers
add_text(s4, left, top, width, height, text,
font_size=BODY_SIZE, font_color=MED_GRAY, bold=True)
# Header line (thicker)
add_line(s4, left, top + Inches(0.5), left + full_width, top + Inches(0.5),
color=BLACK, width=Pt(1.0))
# Rows
for i, (col1, col2, col3) in enumerate(rows):
y = header_y + row_height * i
add_text(s4, left, y, col1_w, row_h, col1, ...)
add_text(s4, left + col1_w, y, col2_w, row_h, col2, ...)
add_text(s4, left + col1_w + col2_w, y, col3_w, row_h, col3, ...)
# Row separator
add_line(s4, left, y + row_h, left + full_w, y + row_h,
color=LINE_GRAY, width=Pt(0.5))
```
#### 4. Three-Column Overview (Slide 5)
Layout:
- Left column (4.1" wide): "是什么"
- Middle column (4.1" wide): "独到之处"
- Right 1/4 (2.5" wide) gray panel: "Key Takeaways"
```
0.8" 4.9" 5.3" 9.4" 10.0" 12.5"
|-----|-----|-----|-----|------|
│左 │ │ 中 │ │ 右 │
└─────────────────────────────┘
```
Code:
```python
left_x = Inches(0.8)
col_w5 = Inches(4.1)
mid_x = Inches(5.3)
takeaway_left = Inches(10.0)
takeaway_width = Inches(2.5)
# Left column
add_text(s5, left_x, content_top, col_w5, ...)
add_text(s5, left_x, content_top + Inches(0.6), col_w5, ...,
bullet=True, line_spacing=Pt(8))
# Right gray takeaway area
add_rect(s5, takeaway_left, Inches(1.2), takeaway_width, Inches(5.6), BG_GRAY)
add_text(s5, takeaway_left + Inches(0.15), Inches(1.35), takeaway_width - Inches(0.3), ...,
'Key Takeaways', font_size=BODY_SIZE, color=NAVY, bold=True)
add_text(s5, takeaway_left + Inches(0.15), Inches(1.9), takeaway_width - Inches(0.3), ...,
[f'{i+1}. {t}' for i, t in enumerate(takeaways)], line_spacing=Pt(10))
```
---
### 类别 A:结构导航
#### 5. Section Divider (章节分隔页)
**适用场景**: 多章节演示文稿的章节过渡页,用于视觉上分隔不同主题模块。
```
┌──┬──────────────────────────────────────┐
│N │ │
│A │ 第一部分 │
│V │ 章节标题(28pt, NAVY, bold) │
│Y │ 副标题说明文字 │
│ │ │
└──┴──────────────────────────────────────┘
```
```python
s = prs.slides.add_slide(BL)
add_rect(s, 0, 0, Inches(0.6), SH, NAVY)
add_text(s, Inches(1.2), Inches(2.0), Inches(10), Inches(0.8),
'第一部分', font_size=SUB_HEADER_SIZE, font_color=MED_GRAY, font_name='Georgia')
add_text(s, Inches(1.2), Inches(2.8), Inches(10), Inches(1.2),
'章节标题', font_size=HEADER_SIZE, font_color=NAVY, bold=True, font_name='Georgia')
add_text(s, Inches(1.2), Inches(4.2), Inches(10), Inches(0.6),
'副标题说明文字', font_size=BODY_SIZE, font_color=DARK_GRAY)
```
#### 6. Table of Contents / Agenda (目录/议程页)
**适用场景**: 演示文稿开头的目录或会议议程,列出各章节及说明。
```
┌─────────────────────────────────────────┐
│ ▌ 目录 │
├─────────────────────────────────────────┤
│ │
│ (1) 章节一标题 简要描述 │
│ ───────────────────────────────────── │
│ (2) 章节二标题 简要描述 │
│ ───────────────────────────────────── │
│ (3) 章节三标题 简要描述 │
│ │
└─────────────────────────────────────────┘
```
```python
s = prs.slides.add_slide(BL)
add_action_title(s, '目录')
items = [('1', '引言与背景', '项目起源与核心问题'),
('2', '市场分析', '竞争格局与机会识别'),
('3', '战略建议', '三大核心行动方案')]
iy = Inches(1.6)
for num, title, desc in items:
add_oval(s, LM, iy, num, size=Inches(0.5))
add_text(s, LM + Inches(0.7), iy, Inches(4.0), Inches(0.4),
title, font_size=SUB_HEADER_SIZE, font_color=NAVY, bold=True)
add_text(s, Inches(5.5), iy + Inches(0.05), Inches(6.5), Inches(0.4),
desc, font_size=BODY_SIZE, font_color=MED_GRAY)
iy += Inches(0.7)
add_hline(s, LM, iy, CONTENT_W, LINE_GRAY)
iy += Inches(0.3)
```
#### 7. Appendix Title (附录标题页)
**适用场景**: 正文结束后的附录/备用材料分隔页。
```
┌─────────────────────────────────────────┐
│ │
│ │
│ 附录 │
│ Appendix │
│ ──────── │
│ 补充数据与参考资料 │
│ │
└─────────────────────────────────────────┘
```
```python
s = prs.slides.add_slide(BL)
add_rect(s, 0, 0, SW, Inches(0.05), NAVY)
add_text(s, Inches(1), Inches(2.5), Inches(11.3), Inches(1.0),
'附录', font_size=Pt(36), font_color=NAVY, bold=True,
font_name='Georgia', alignment=PP_ALIGN.CENTER)
add_hline(s, Inches(5.5), Inches(3.8), Inches(2.3), NAVY, Pt(1.5))
add_text(s, Inches(1), Inches(4.2), Inches(11.3), Inches(0.5),
'补充数据与参考资料', font_size=BODY_SIZE, font_color=MED_GRAY,
alignment=PP_ALIGN.CENTER)
```
---
### 类别 B:数据统计
#### 8. Big Number / Factoid (大数据展示页)
**适用场景**: 用一个醒目的大数字引出核心发现或关键数据点。
```
┌─────────────────────────────────────────┐
│ ▌ Action Title │
├─────────────────────────────────────────┤
│ │
│ ┌─NAVY─────────┐ │
│ │ 95% │ 右侧上下文说明 │
│ │ 子标题 │ 详细解释数据含义 │
│ └──────────────┘ │
│ │
│ ┌─BG_GRAY──────────────────────────┐ │
│ │ 关键洞见:详细分析文字 │ │
│ └──────────────────────────────────┘ │
└─────────────────────────────────────────┘
```
```python
s = prs.slides.add_slide(BL)
add_action_title(s, '关键发现标题')
add_rect(s, LM, Inches(1.4), Inches(3.5), Inches(1.8), NAVY)
add_text(s, LM + Inches(0.2), Inches(1.5), Inches(3.1), Inches(0.8),
'95%', font_size=Pt(44), font_color=WHITE, bold=True,
font_name='Georgia', alignment=PP_ALIGN.CENTER)
add_text(s, LM + Inches(0.2), Inches(2.3), Inches(3.1), Inches(0.7),
'描述数据含义', font_size=Pt(12), font_color=WHITE, alignment=PP_ALIGN.CENTER)
add_text(s, Inches(5.0), Inches(1.5), Inches(7.5), Inches(2.0),
'上下文说明与详细解释', font_size=BODY_SIZE)
add_rect(s, LM, Inches(4.5), CONTENT_W, Inches(2.2), BG_GRAY)
add_text(s, LM + Inches(0.3), Inches(4.6), Inches(1.5), Inches(0.4),
'关键洞见', font_size=BODY_SIZE, font_color=NAVY, bold=True)
add_text(s, LM + Inches(0.3), Inches(5.1), CONTENT_W - Inches(0.6), Inches(1.4),
['洞见要点一', '洞见要点二', '洞见要点三'], line_spacing=Pt(8))
add_source(s, 'Source: ...')
```
#### 9. Two-Stat Comparison (双数据对比页)
**适用场景**: 并排展示两个关键指标的对比(如同比、环比、A vs B)。
```
┌─────────────────────────────────────────┐
│ ▌ Action Title │
├─────────────────────────────────────────┤
│ │
│ ┌──NAVY───────┐ ┌──BG_GRAY────┐ │
│ │ $2.4B │ │ $1.8B │ │
│ │ 2026年目标 │ │ 2025年实际 │ │
│ └─────────────┘ └─────────────┘ │
│ │
│ 分析说明文字 │
└─────────────────────────────────────────┘
```
```python
s = prs.slides.add_slide(BL)
add_action_title(s, '对比标题')
stats = [('$2.4B', '2026年目标', True), ('$1.8B', '2025年实际', False)]
sw = Inches(5.5)
sg = Inches(0.733)
for i, (big, small, is_navy) in enumerate(stats):
sx = LM + (sw + sg) * i
fill = NAVY if is_navy else BG_GRAY
bc = WHITE if is_navy else NAVY
sc = WHITE if is_navy else DARK_GRAY
add_rect(s, sx, Inches(1.8), sw, Inches(2.5), fill)
add_text(s, sx + Inches(0.3), Inches(2.0), sw - Inches(0.6), Inches(1.0),
big, font_size=Pt(44), font_color=bc, bold=True,
font_name='Georgia', alignment=PP_ALIGN.CENTER)
add_text(s, sx + Inches(0.3), Inches(3.2), sw - Inches(0.6), Inches(0.5),
small, font_size=BODY_SIZE, font_color=sc, alignment=PP_ALIGN.CENTER)
add_text(s, LM, Inches(5.0), CONTENT_W, Inches(1.5),
'分析说明文字', font_size=BODY_SIZE)
add_source(s, 'Source: ...')
```
#### 10. Three-Stat Dashboard (三指标仪表盘)
**适用场景**: 同时展示三个关键业务指标(如 KPI、季度数据)。
```
┌─────────────────────────────────────────┐
│ ▌ Action Title │
├─────────────────────────────────────────┤
│ ┌──NAVY──┐ ┌──BG_GRAY─┐ ┌──BG_GRAY─┐│
│ │ 数字1 │ │ 数字2 │ │ 数字3 ││
│ │ 小标题 │ │ 小标题 │ │ 小标题 ││
│ └────────┘ └─────────┘ └─────────┘│
│ │
│ 详细说明文字 │
└─────────────────────────────────────────┘
```
```python
s = prs.slides.add_slide(BL)
add_action_title(s, '三大关键指标')
stats = [('87%', '客户满意度', True),
('+23%', '同比增长', False),
('4.2x', '投资回报率', False)]
sw = Inches(3.5)
sg = (CONTENT_W - sw * 3) / 2
for i, (big, small, is_navy) in enumerate(stats):
sx = LM + (sw + sg) * i
fill = NAVY if is_navy else BG_GRAY
bc = WHITE if is_navy else NAVY
sc = WHITE if is_navy else DARK_GRAY
add_rect(s, sx, Inches(1.4), sw, Inches(1.8), fill)
add_text(s, sx + Inches(0.2), Inches(1.5), sw - Inches(0.4), Inches(0.7),
big, font_size=Pt(28), font_color=bc, bold=True,
font_name='Georgia', alignment=PP_ALIGN.CENTER)
add_text(s, sx + Inches(0.2), Inches(2.25), sw - Inches(0.4), Inches(0.6),
small, font_size=Pt(12), font_color=sc, alignment=PP_ALIGN.CENTER)
add_source(s, 'Source: ...')
```
#### 11. Data Table with Headers (数据表格页)
**适用场景**: 结构化数据展示,如财务数据、功能对比矩阵、项目清单。
```
┌─────────────────────────────────────────┐
│ ▌ Action Title │
├─────────────────────────────────────────┤
│ 列1 列2 列3 列4 │
│ ═══════════════════════════════════ │
│ 数据1-1 数据1-2 ... ... │
│ ─────────────────────────────────── │
│ 数据2-1 数据2-2 ... ... │
│ ─────────────────────────────────── │
│ 数据3-1 数据3-2 ... ... │
└─────────────────────────────────────────┘
```
```python
s = prs.slides.add_slide(BL)
add_action_title(s, '数据概览')
headers = ['模块', '功能', '状态', '负责人']
col_widths = [Inches(2.5), Inches(4.0), Inches(2.5), Inches(2.7)]
hdr_y = Inches(1.5)
cx = LM
for hdr, cw in zip(headers, col_widths):
add_text(s, cx, hdr_y, cw, Inches(0.4), hdr,
font_size=BODY_SIZE, font_color=MED_GRAY, bold=True)
cx += cw
add_hline(s, LM, Inches(2.0), CONTENT_W, BLACK, Pt(1.0))
# Data rows
rows = [['模块A', '核心功能描述', '已上线', '张三'], ...]
row_h = Inches(0.8)
for ri, row in enumerate(rows):
ry = Inches(2.1) + row_h * ri
cx = LM
for val, cw in zip(row, col_widths):
add_text(s, cx, ry, cw, row_h, val, font_size=BODY_SIZE)
cx += cw
add_hline(s, LM, ry + row_h, CONTENT_W, LINE_GRAY)
add_source(s, 'Source: ...')
```
#### 12. Metric Cards Row (指标卡片行)
**适用场景**: 3-4个并排卡片展示独立指标,每个卡片含标题+描述。
```
┌─────────────────────────────────────────┐
│ ▌ Action Title │
├─────────────────────────────────────────┤
│ ┌─BG_GRAY─┐ ┌─BG_GRAY─┐ ┌─BG_GRAY─┐ │
│ │ (A) │ │ (B) │ │ (C) │ │
│ │ 标题 │ │ 标题 │ │ 标题 │ │
│ │ ─── │ │ ─── │ │ ─── │ │
│ │ 描述 │ │ 描述 │ │ 描述 │ │
│ └─────────┘ └─────────┘ └─────────┘ │
└─────────────────────────────────────────┘
```
```python
s = prs.slides.add_slide(BL)
add_action_title(s, '核心指标概览')
cards = [('A', '用户增长', '月活用户达到 120 万\n同比增长 35%'),
('B', '营收表现', '季度营收 ¥8,500 万\n超出预期 12%'),
('C', '运营效率', '客诉响应时间 < 2h\n满意度 94%')]
cw = Inches(3.5)
cg = (CONTENT_W - cw * 3) / 2
for i, (letter, title, desc) in enumerate(cards):
cx = LM + (cw + cg) * i
cy = Inches(1.5)
add_rect(s, cx, cy, cw, Inches(4.5), BG_GRAY)
add_oval(s, cx + Inches(1.5), cy + Inches(0.2), letter)
add_text(s, cx + Inches(0.2), cy + Inches(0.8), cw - Inches(0.4), Inches(0.4),
title, font_size=SUB_HEADER_SIZE, font_color=NAVY, bold=True,
alignment=PP_ALIGN.CENTER)
add_hline(s, cx + Inches(0.4), cy + Inches(1.3), cw - Inches(0.8), LINE_GRAY)
add_text(s, cx + Inches(0.2), cy + Inches(1.5), cw - Inches(0.4), Inches(2.5),
desc.split('\n'), line_spacing=Pt(8), alignment=PP_ALIGN.CENTER)
add_source(s, 'Source: ...')
```
---
### 类别 C:框架矩阵
#### 13. 2x2 Matrix (四象限矩阵)
**适用场景**: 战略分析(如 BCG 矩阵、优先级排序、风险评估)。
```
┌─────────────────────────────────────────┐
│ ▌ Action Title │
├─────────────────────────────────────────┤
│ 高 Y轴 │
│ ┌─NAVY──────┐ ┌─BG_GRAY───┐ │
│ │ 左上象限 │ │ 右上象限 │ │
│ └───────────┘ └───────────┘ │
│ ┌─BG_GRAY───┐ ┌─BG_GRAY───┐ │
│ │ 左下象限 │ │ 右下象限 │ │
│ └───────────┘ └───────────┘ │
│ 低 高 X轴 │
└─────────────────────────────────────────┘
```
```python
s = prs.slides.add_slide(BL)
add_action_title(s, '战略优先级矩阵')
mx = LM + Inches(1.5)
my = Inches(1.8)
cw = Inches(4.5)
ch = Inches(2.5)
# Axis labels
add_text(s, mx - Inches(1.3), my + Inches(0.8), Inches(1.1), Inches(0.4),
'高影响', font_size=BODY_SIZE, font_color=NAVY, bold=True, alignment=PP_ALIGN.CENTER)
add_text(s, mx + Inches(0.8), my - Inches(0.5), Inches(3.0), Inches(0.4),
'高可行性', font_size=BODY_SIZE, font_color=NAVY, bold=True, alignment=PP_ALIGN.CENTER)
# Quadrants
add_rect(s, mx, my, cw, ch, NAVY) # best quadrant
add_rect(s, mx + cw + Inches(0.15), my, cw, ch, BG_GRAY)
add_rect(s, mx, my + ch + Inches(0.15), cw, ch, BG_GRAY)
add_rect(s, mx + cw + Inches(0.15), my + ch + Inches(0.15), cw, ch, BG_GRAY)
# Quadrant titles + descriptions
add_text(s, mx + Inches(0.3), my + Inches(0.3), cw - Inches(0.6), Inches(0.5),
'立即执行', font_size=SUB_HEADER_SIZE, font_color=WHITE, bold=True)
# ... repeat for other 3 quadrants with DARK_GRAY text
add_source(s, 'Source: ...')
```
#### 14. Three-Pillar Framework (三支柱框架)
**适用场景**: 展示三个并列的核心策略、能力或主题模块。
```
┌─────────────────────────────────────────┐
│ ▌ Action Title │
├─────────────────────────────────────────┤
│ ┌──NAVY──┐ ┌──NAVY──┐ ┌──NAVY──┐ │
│ │ 标题1 │ │ 标题2 │ │ 标题3 │ │
│ ├────────┤ ├────────┤ ├────────┤ │
│ │ 要点 │ │ 要点 │ │ 要点 │ │
│ │ 要点 │ │ 要点 │ │ 要点 │ │
│ │ 要点 │ │ 要点 │ │ 要点 │ │
│ └────────┘ └────────┘ └────────┘ │
└─────────────────────────────────────────┘
```
```python
s = prs.slides.add_slide(BL)
add_action_title(s, '三大核心战略')
pillars = [('数字化转型', ['建设数据中台', '打通全渠道', '自动化运营']),
('组织升级', ['扁平化管理', '敏捷团队', '人才梯队']),
('客户深耕', ['精细化运营', '会员体系', 'LTV 提升'])]
pw = Inches(3.5)
pg = (CONTENT_W - pw * 3) / 2
for i, (title, points) in enumerate(pillars):
px = LM + (pw + pg) * i
add_rect(s, px, Inches(1.5), pw, Inches(0.6), NAVY)
add_text(s, px + Inches(0.15), Inches(1.5), pw - Inches(0.3), Inches(0.6),
title, font_size=SUB_HEADER_SIZE, font_color=WHITE, bold=True,
anchor=MSO_ANCHOR.MIDDLE, alignment=PP_ALIGN.CENTER)
add_rect(s, px, Inches(2.1), pw, Inches(4.0), BG_GRAY)
add_text(s, px + Inches(0.2), Inches(2.3), pw - Inches(0.4), Inches(3.5),
[f'• {p}' for p in points], line_spacing=Pt(10))
add_source(s, 'Source: ...')
```
#### 15. Pyramid / Hierarchy (金字塔/层级图)
**适用场景**: 展示层级关系(如 Maslow 需求层次、战略-战术-执行分层)。
```
┌─────────────────────────────────────────┐
│ ▌ Action Title │
├─────────────────────────────────────────┤
│ ┌──NAVY──┐ │
│ │ 愿景 │ 右侧说明 │
│ ┌─┴────────┴─┐ │
│ │ 战略目标 │ 右侧说明 │
│ ┌─┴────────────┴─┐ │
│ │ 执行措施 │ 右侧说明 │
│ └────────────────┘ │
└─────────────────────────────────────────┘
```
```python
s = prs.slides.add_slide(BL)
add_action_title(s, '战略层级框架')
levels = [('愿景', '成为行业第一', Inches(3.5)),
('战略目标', '三年收入翻倍', Inches(5.0)),
('执行措施', '渠道+产品+组织', Inches(6.5))]
for i, (label, desc, w) in enumerate(levels):
lx = Inches(6.666) - w / 2 # centered
ly = Inches(1.8) + Inches(1.5) * i
h = Inches(1.2)
fill = NAVY if i == 0 else BG_GRAY
tc = WHITE if i == 0 else NAVY
add_rect(s, lx, ly, w, h, fill)
add_text(s, lx + Inches(0.2), ly + Inches(0.1), w - Inches(0.4), Inches(0.4),
label, font_size=SUB_HEADER_SIZE, font_color=tc, bold=True,
alignment=PP_ALIGN.CENTER)
add_text(s, lx + Inches(0.2), ly + Inches(0.55), w - Inches(0.4), Inches(0.5),
desc, font_size=BODY_SIZE, font_color=tc if i == 0 else DARK_GRAY,
alignment=PP_ALIGN.CENTER)
add_source(s, 'Source: ...')
```
#### 16. Process Chevron (流程箭头页)
**适用场景**: 线性流程展示(3-5步),如实施路径、业务流程、方法论步骤。
```
┌─────────────────────────────────────────┐
│ ▌ Action Title │
├─────────────────────────────────────────┤
│ │
│ ┌NAVY┐ -> ┌GRAY┐ -> ┌GRAY┐ -> ┌GRAY┐ │
│ │ S1 │ │ S2 │ │ S3 │ │ S4 │ │
│ └────┘ └────┘ └────┘ └────┘ │
│ 描述 描述 描述 描述 │
│ │
└─────────────────────────────────────────┘
```
```python
s = prs.slides.add_slide(BL)
add_action_title(s, '实施路径')
steps = [('诊断', '现状评估\n痛点识别'),
('设计', '方案制定\n资源规划'),
('实施', '分阶段落地\n快速迭代'),
('优化', '效果追踪\n持续改进')]
sw = Inches(2.5)
sg = (CONTENT_W - sw * len(steps)) / (len(steps) - 1)
for i, (title, desc) in enumerate(steps):
sx = LM + (sw + sg) * i
fill = NAVY if i == 0 else BG_GRAY
tc = WHITE if i == 0 else NAVY
add_rect(s, sx, Inches(2.0), sw, Inches(1.2), fill)
add_oval(s, sx + Inches(0.1), Inches(2.1), str(i + 1),
bg=WHITE if i == 0 else NAVY, fg=NAVY if i == 0 else WHITE)
add_text(s, sx + Inches(0.6), Inches(2.1), sw - Inches(0.8), Inches(1.0),
title, font_size=SUB_HEADER_SIZE, font_color=tc, bold=True,
anchor=MSO_ANCHOR.MIDDLE)
add_text(s, sx + Inches(0.1), Inches(3.4), sw - Inches(0.2), Inches(1.5),
desc, font_size=BODY_SIZE, alignment=PP_ALIGN.CENTER)
if i < len(steps) - 1:
add_text(s, sx + sw + Inches(0.05), Inches(2.3), Inches(0.4), Inches(0.5),
'->', font_size=SUB_HEADER_SIZE, font_color=NAVY, bold=True)
add_source(s, 'Source: ...')
```
#### 17. Venn Diagram Concept (维恩图概念页)
**适用场景**: 展示两三个概念的交集关系(如能力交叉、市场定位)。
```
┌─────────────────────────────────────────┐
│ ▌ Action Title │
├─────────────────────────────────────────┤
│ ┌──BG──┐ │
│ ╱概念A ╲ │
│ ╱ ┌──┐ ╲ 右侧说明 │
│ │ │交│ │ │
│ ╲ └──┘ ╱ │
│ ╲概念B ╱ │
│ └──────┘ │
└─────────────────────────────────────────┘
```
```python
s = prs.slides.add_slide(BL)
add_action_title(s, '核心能力交叉')
# Use overlapping rectangles to represent Venn concept
add_rect(s, Inches(1.5), Inches(1.8), Inches(4.5), Inches(4.0), BG_GRAY)
add_text(s, Inches(1.7), Inches(2.0), Inches(2.0), Inches(0.4),
'技术能力', font_size=SUB_HEADER_SIZE, font_color=NAVY, bold=True)
add_rect(s, Inches(3.5), Inches(2.8), Inches(4.5), Inches(4.0), BG_GRAY)
add_text(s, Inches(5.5), Inches(5.5), Inches(2.0), Inches(0.4),
'业务洞察', font_size=SUB_HEADER_SIZE, font_color=NAVY, bold=True)
# Intersection area
add_rect(s, Inches(3.5), Inches(2.8), Inches(2.5), Inches(3.0), NAVY)
add_text(s, Inches(3.7), Inches(3.5), Inches(2.1), Inches(0.8),
'核心\n竞争力', font_size=SUB_HEADER_SIZE, font_color=WHITE, bold=True,
alignment=PP_ALIGN.CENTER)
# Right explanation
add_text(s, Inches(9.0), Inches(2.0), Inches(3.5), Inches(4.0),
'当技术能力与业务洞察交叉时...', font_size=BODY_SIZE)
add_source(s, 'Source: ...')
```
#### 18. Temple / House Framework (殿堂框架)
**适用场景**: 展示"屋顶-支柱-基座"的结构(如企业架构、能力体系)。
```
┌─────────────────────────────────────────┐
│ ▌ Action Title │
├─────────────────────────────────────────┤
│ ┌═══════════NAVY(愿景/屋顶)══════════┐│
│ ├────┤ ├────┤ ├────┤ ├────┤ ││
│ │支柱│ │支柱│ │支柱│ │支柱│ ││
│ │ 1 │ │ 2 │ │ 3 │ │ 4 │ ││
│ ├════╧══╧════╧══╧════╧══╧════╧════════┤│
│ │ 基座(基础能力/文化) ││
│ └──────────────────────────────────────┘│
└─────────────────────────────────────────┘
```
```python
s = prs.slides.add_slide(BL)
add_action_title(s, '企业能力架构')
# Roof
add_rect(s, LM, Inches(1.5), CONTENT_W, Inches(0.8), NAVY)
add_text(s, LM + Inches(0.3), Inches(1.5), CONTENT_W - Inches(0.6), Inches(0.8),
'企业愿景:成为行业领先的数字化平台',
font_size=SUB_HEADER_SIZE, font_color=WHITE, bold=True,
anchor=MSO_ANCHOR.MIDDLE, alignment=PP_ALIGN.CENTER)
# Pillars
pillars = ['产品力', '技术力', '运营力', '品牌力']
pw = Inches(2.5)
pg = (CONTENT_W - pw * 4) / 3
for i, name in enumerate(pillars):
px = LM + (pw + pg) * i
add_rect(s, px, Inches(2.5), pw, Inches(3.0), BG_GRAY)
add_text(s, px + Inches(0.15), Inches(2.6), pw - Inches(0.3), Inches(0.5),
name, font_size=SUB_HEADER_SIZE, font_color=NAVY, bold=True,
alignment=PP_ALIGN.CENTER)
# Foundation
add_rect(s, LM, Inches(5.7), CONTENT_W, Inches(0.8), NAVY)
add_text(s, LM + Inches(0.3), Inches(5.7), CONTENT_W - Inches(0.6), Inches(0.8),
'基座:数据驱动 + 人才体系 + 企业文化',
font_size=BODY_SIZE, font_color=WHITE, bold=True,
anchor=MSO_ANCHOR.MIDDLE, alignment=PP_ALIGN.CENTER)
add_source(s, 'Source: ...')
```
---
### 类别 D:对比评估
#### 19. Side-by-Side Comparison (左右对比页)
**适用场景**: 两个方案/选项/产品的并排对比分析。
```
┌─────────────────────────────────────────┐
│ ▌ Action Title │
├─────────────────────────────────────────┤
│ ┌──方案 A──────┐ ┌──方案 B──────┐ │
│ │ 标题(NAVY) │ │ 标题(NAVY) │ │
│ ├──────────────┤ ├──────────────┤ │
│ │ 优势 │ │ 优势 │ │
│ │ 劣势 │ │ 劣势 │ │
│ │ 成本 │ │ 成本 │ │
│ └──────────────┘ └──────────────┘ │
└─────────────────────────────────────────┘
```
```python
s = prs.slides.add_slide(BL)
add_action_title(s, '方案对比分析')
cw = Inches(5.5)
cg = Inches(0.733)
options = [('方案 A:自建团队', ['投入可控', '周期较长', '成本 ¥500万/年']),
('方案 B:外部合作', ['快速启动', '依赖供应商', '成本 ¥300万/年'])]
for i, (title, points) in enumerate(options):
cx = LM + (cw + cg) * i
add_rect(s, cx, Inches(1.5), cw, Inches(0.6), NAVY)
add_text(s, cx + Inches(0.15), Inches(1.5), cw - Inches(0.3), Inches(0.6),
title, font_size=SUB_HEADER_SIZE, font_color=WHITE, bold=True,
anchor=MSO_ANCHOR.MIDDLE, alignment=PP_ALIGN.CENTER)
add_rect(s, cx, Inches(2.1), cw, Inches(4.0), BG_GRAY)
add_text(s, cx + Inches(0.3), Inches(2.3), cw - Inches(0.6), Inches(3.5),
[f'• {p}' for p in points], line_spacing=Pt(10))
add_source(s, 'Source: ...')
```
#### 20. Before / After (前后对比页)
**适用场景**: 展示变革前后的对比(如流程优化、组织变革)。
```
┌─────────────────────────────────────────┐
│ ▌ Action Title │
├─────────────────────────────────────────┤
│ ┌──BG_GRAY────┐ ──> ┌──NAVY────┐ │
│ │ 现状 │ │ 目标 │ │
│ │ (Before) │ │ (After) │ │
│ │ 痛点列表 │ │ 改进点 │ │
│ └─────────────┘ └─────────┘ │
└─────────────────────────────────────────┘
```
```python
s = prs.slides.add_slide(BL)
add_action_title(s, '从现状到目标')
hw = Inches(5.0)
# Before
add_rect(s, LM, Inches(1.5), hw, Inches(4.5), BG_GRAY)
add_text(s, LM + Inches(0.3), Inches(1.6), hw - Inches(0.6), Inches(0.5),
'X 现状(Before)', font_size=SUB_HEADER_SIZE, font_color=DARK_GRAY, bold=True)
add_hline(s, LM + Inches(0.3), Inches(2.2), hw - Inches(0.6), LINE_GRAY)
add_text(s, LM + Inches(0.3), Inches(2.4), hw - Inches(0.6), Inches(3.0),
['痛点一', '痛点二', '痛点三'], line_spacing=Pt(10))
# Arrow
add_text(s, LM + hw + Inches(0.1), Inches(3.2), Inches(1.5), Inches(0.5),
'->', font_size=Pt(36), font_color=NAVY, bold=True, alignment=PP_ALIGN.CENTER)
# After
ax = LM + hw + Inches(1.733)
add_rect(s, ax, Inches(1.5), hw, Inches(4.5), NAVY)
add_text(s, ax + Inches(0.3), Inches(1.6), hw - Inches(0.6), Inches(0.5),
'V 目标(After)', font_size=SUB_HEADER_SIZE, font_color=WHITE, bold=True)
add_hline(s, ax + Inches(0.3), Inches(2.2), hw - Inches(0.6), WHITE)
add_text(s, ax + Inches(0.3), Inches(2.4), hw - Inches(0.6), Inches(3.0),
['改进一', '改进二', '改进三'], font_color=WHITE, line_spacing=Pt(10))
add_source(s, 'Source: ...')
```
#### 21. Pros and Cons (优劣分析页)
**适用场景**: 评估某个决策/方案的优势与风险。
```
┌─────────────────────────────────────────┐
│ ▌ Action Title │
├─────────────────────────────────────────┤
│ V 优势 X 风险 │
│ ─────────── ────────── │
│ • 要点1 • 要点1 │
│ • 要点2 • 要点2 │
│ • 要点3 • 要点3 │
│ │
│ ┌──BG_GRAY 结论/建议───────────────┐ │
└─────────────────────────────────────────┘
```
```python
s = prs.slides.add_slide(BL)
add_action_title(s, '方案评估:优势与风险')
hw = Inches(5.5)
# Pros column
add_text(s, LM, Inches(1.5), hw, Inches(0.4),
'V 优势', font_size=SUB_HEADER_SIZE, font_color=NAVY, bold=True)
add_hline(s, LM, Inches(2.0), hw, NAVY)
add_text(s, LM, Inches(2.2), hw, Inches(2.5),
['• 优势要点一', '• 优势要点二', '• 优势要点三'], line_spacing=Pt(10))
# Cons column
cx = LM + hw + Inches(0.733)
add_text(s, cx, Inches(1.5), hw, Inches(0.4),
'X 风险', font_size=SUB_HEADER_SIZE, font_color=DARK_GRAY, bold=True)
add_hline(s, cx, Inches(2.0), hw, DARK_GRAY)
add_text(s, cx, Inches(2.2), hw, Inches(2.5),
['• 风险要点一', '• 风险要点二', '• 风险要点三'], line_spacing=Pt(10))
# Bottom conclusion
add_rect(s, LM, Inches(5.2), CONTENT_W, Inches(1.5), BG_GRAY)
add_text(s, LM + Inches(0.3), Inches(5.3), Inches(1.5), Inches(0.4),
'结论', font_size=BODY_SIZE, font_color=NAVY, bold=True)
add_text(s, LM + Inches(0.3), Inches(5.8), CONTENT_W - Inches(0.6), Inches(0.6),
'综合评估建议文字', font_size=BODY_SIZE)
add_source(s, 'Source: ...')
```
#### 22. Traffic Light / RAG Status (红绿灯状态页)
**适用场景**: 多项目/多模块的状态总览(绿=正常、黄=关注、红=风险)。
```
┌─────────────────────────────────────────┐
│ ▌ Action Title │
├─────────────────────────────────────────┤
│ 项目 状态 进度 备注 │
│ ═══════════════════════════════════ │
│ 项目A (G) 85% 按计划推进 │
│ ─────────────────────────────────── │
│ 项目B (Y) 60% 需关注资源 │
│ ─────────────────────────────────── │
│ 项目C (R) 30% 存在阻塞 │
└─────────────────────────────────────────┘
```
```python
s = prs.slides.add_slide(BL)
add_action_title(s, '项目状态总览')
# Header
headers = ['项目', '状态', '进度', '备注']
widths = [Inches(3.0), Inches(1.5), Inches(2.0), Inches(5.233)]
hx = LM
for hdr, w in zip(headers, widths):
add_text(s, hx, Inches(1.5), w, Inches(0.4), hdr,
font_size=BODY_SIZE, font_color=MED_GRAY, bold=True)
hx += w
add_hline(s, LM, Inches(2.0), CONTENT_W, BLACK, Pt(1.0))
# Rows with status indicators
rows = [('产品研发', 'NAVY', '85%', '按计划推进'),
('市场推广', 'MED_GRAY', '60%', '需关注预算'),
('团队扩招', 'DARK_GRAY', '30%', '存在阻塞')]
color_map = {'NAVY': NAVY, 'MED_GRAY': MED_GRAY, 'DARK_GRAY': DARK_GRAY}
ry = Inches(2.2)
for name, status_color, pct, note in rows:
add_text(s, LM, ry, Inches(3.0), Inches(0.6), name, font_size=BODY_SIZE)
add_oval(s, LM + Inches(3.3), ry + Inches(0.05), '', size=Inches(0.35),
bg=color_map[status_color])
add_text(s, LM + Inches(4.5), ry, Inches(2.0), Inches(0.6), pct, font_size=BODY_SIZE)
add_text(s, LM + Inches(6.5), ry, Inches(5.233), Inches(0.6), note, font_size=BODY_SIZE)
ry += Inches(0.7)
add_hline(s, LM, ry, CONTENT_W, LINE_GRAY)
ry += Inches(0.15)
add_source(s, 'Source: ...')
```
#### 23. Scorecard (计分卡页)
**适用场景**: 展示多项评估维度的得分/评级,如供应商评估、团队绩效。
```
┌─────────────────────────────────────────┐
│ ▌ Action Title │
├─────────────────────────────────────────┤
│ 评估维度 得分 评级 │
│ ═══════════════════════════════════ │
│ 客户满意度 92 ████████░░ │
│ 产品质量 85 ███████░░░ │
│ 交付速度 78 ██████░░░░ │
│ 创新能力 65 █████░░░░░ │
└─────────────────────────────────────────┘
```
```python
s = prs.slides.add_slide(BL)
add_action_title(s, '综合评估计分卡')
headers = ['评估维度', '得分', '评级']
add_text(s, LM, Inches(1.5), Inches(4.0), Inches(0.4), headers[0],
font_size=BODY_SIZE, font_color=MED_GRAY, bold=True)
add_text(s, Inches(5.0), Inches(1.5), Inches(1.5), Inches(0.4), headers[1],
font_size=BODY_SIZE, font_color=MED_GRAY, bold=True)
add_text(s, Inches(7.0), Inches(1.5), Inches(5.5), Inches(0.4), headers[2],
font_size=BODY_SIZE, font_color=MED_GRAY, bold=True)
add_hline(s, LM, Inches(2.0), CONTENT_W, BLACK, Pt(1.0))
items = [('客户满意度', '92', 0.92), ('产品质量', '85', 0.85),
('交付速度', '78', 0.78), ('创新能力', '65', 0.65)]
ry = Inches(2.2)
bar_max = Inches(5.0)
for name, score, pct in items:
add_text(s, LM, ry, Inches(4.0), Inches(0.5), name, font_size=BODY_SIZE)
add_text(s, Inches(5.0), ry, Inches(1.5), Inches(0.5), score,
font_size=BODY_SIZE, font_color=NAVY, bold=True)
add_rect(s, Inches(7.0), ry + Inches(0.1), bar_max, Inches(0.3), BG_GRAY)
add_rect(s, Inches(7.0), ry + Inches(0.1), Inches(5.0 * pct), Inches(0.3), NAVY)
ry += Inches(0.7)
add_hline(s, LM, ry, CONTENT_W, LINE_GRAY)
ry += Inches(0.15)
add_source(s, 'Source: ...')
```
---
### 类别 E:内容叙事
#### 24. Executive Summary (执行摘要页)
**适用场景**: 演示文稿的核心结论汇总,通常放在开头或结尾。
```
┌─────────────────────────────────────────┐
│ ▌ Action Title │
├─────────────────────────────────────────┤
│ ┌──NAVY(核心结论)────────────────────┐ │
│ │ 一句话核心结论 │ │
│ └──────────────────────────────────────┘ │
│ │
│ (1) 支撑论点一 详细说明 │
│ (2) 支撑论点二 详细说明 │
│ (3) 支撑论点三 详细说明 │
└─────────────────────────────────────────┘
```
```python
s = prs.slides.add_slide(BL)
add_action_title(s, '执行摘要')
add_rect(s, LM, Inches(1.4), CONTENT_W, Inches(1.0), NAVY)
add_text(s, LM + Inches(0.3), Inches(1.4), CONTENT_W - Inches(0.6), Inches(1.0),
'核心结论:一句话概括最重要的发现或建议',
font_size=SUB_HEADER_SIZE, font_color=WHITE, bold=True,
anchor=MSO_ANCHOR.MIDDLE)
points = [('1', '论点一标题', '支撑论点的详细说明文字'),
('2', '论点二标题', '支撑论点的详细说明文字'),
('3', '论点三标题', '支撑论点的详细说明文字')]
iy = Inches(2.8)
for num, title, desc in points:
add_oval(s, LM, iy, num)
add_text(s, LM + Inches(0.6), iy, Inches(3.5), Inches(0.4),
title, font_size=BODY_SIZE, font_color=NAVY, bold=True)
add_text(s, Inches(5.0), iy, Inches(7.5), Inches(0.4),
desc, font_size=BODY_SIZE)
iy += Inches(0.6)
add_hline(s, LM, iy, CONTENT_W, LINE_GRAY)
iy += Inches(0.3)
add_source(s, 'Source: ...')
```
#### 25. Key Takeaway with Detail (核心洞见页)
**适用场景**: 左侧详细论述 + 右侧灰底要点提炼,用于核心发现页。
```
┌─────────────────────────────────────────┐
│ ▌ Action Title │
├─────────────────────────────────────────┤
│ ┌──BG_GRAY────────┐│
│ 左侧正文内容 │ Key Takeaways ││
│ 详细分析论述 │ 1. 要点一 ││
│ 数据与支撑 │ 2. 要点二 ││
│ │ 3. 要点三 ││
│ └─────────────────┘│
└─────────────────────────────────────────┘
```
```python
s = prs.slides.add_slide(BL)
add_action_title(s, '核心发现')
# Left content
add_text(s, LM, Inches(1.5), Inches(7.5), Inches(0.4),
'分析标题', font_size=SUB_HEADER_SIZE, font_color=NAVY, bold=True)
add_hline(s, LM, Inches(2.0), Inches(7.5), LINE_GRAY)
add_text(s, LM, Inches(2.2), Inches(7.5), Inches(4.0),
['详细分析段落一', '', '详细分析段落二'], line_spacing=Pt(8))
# Right takeaway
tk_x = Inches(9.0)
tk_w = Inches(3.5)
add_rect(s, tk_x, Inches(1.5), tk_w, Inches(5.0), BG_GRAY)
add_text(s, tk_x + Inches(0.2), Inches(1.7), tk_w - Inches(0.4), Inches(0.4),
'Key Takeaways', font_size=BODY_SIZE, font_color=NAVY, bold=True)
add_hline(s, tk_x + Inches(0.2), Inches(2.2), tk_w - Inches(0.4), LINE_GRAY)
add_text(s, tk_x + Inches(0.2), Inches(2.4), tk_w - Inches(0.4), Inches(3.8),
['1. 要点一', '2. 要点二', '3. 要点三'], line_spacing=Pt(10))
add_source(s, 'Source: ...')
```
#### 26. Quote / Insight Page (引言/洞见页)
**适用场景**: 突出一段重要引言、专家观点或核心洞察。
```
┌─────────────────────────────────────────┐
│ │
│ ────────── │
│ │
│ "引言内容,居中显示, │
│ 大字号强调核心观点" │
│ │
│ ────────── │
│ — 来源/作者 │
│ │
└─────────────────────────────────────────┘
```
```python
s = prs.slides.add_slide(BL)
add_rect(s, 0, 0, SW, Inches(0.05), NAVY)
add_hline(s, Inches(5.5), Inches(2.0), Inches(2.3), NAVY, Pt(1.5))
add_text(s, Inches(1.5), Inches(2.5), Inches(10.3), Inches(2.5),
'"引言内容,用于强调某个核心观点或专家洞见"',
font_size=Pt(24), font_color=DARK_GRAY, alignment=PP_ALIGN.CENTER)
add_hline(s, Inches(5.5), Inches(5.3), Inches(2.3), NAVY, Pt(1.5))
add_text(s, Inches(1.5), Inches(5.6), Inches(10.3), Inches(0.5),
'— 作者姓名,来源',
font_size=BODY_SIZE, font_color=MED_GRAY, alignment=PP_ALIGN.CENTER)
```
#### 27. Two-Column Text (双栏文本页)
**适用场景**: 平衡展示两个主题/方面,每列独立标题+正文。
```
┌─────────────────────────────────────────┐
│ ▌ Action Title │
├─────────────────────────────────────────┤
│ (A) 左栏标题 (B) 右栏标题 │
│ ───────────── ───────────── │
│ 左栏正文内容 右栏正文内容 │
│ 详细分析 详细分析 │
│ │
└─────────────────────────────────────────┘
```
```python
s = prs.slides.add_slide(BL)
add_action_title(s, '双维度分析')
cw = Inches(5.5)
cg = Inches(0.733)
cols = [('A', '维度一标题', ['分析要点一', '分析要点二', '分析要点三']),
('B', '维度二标题', ['分析要点一', '分析要点二', '分析要点三'])]
for i, (letter, title, points) in enumerate(cols):
cx = LM + (cw + cg) * i
add_oval(s, cx, Inches(1.5), letter)
add_text(s, cx + Inches(0.6), Inches(1.5), cw - Inches(0.6), Inches(0.4),
title, font_size=SUB_HEADER_SIZE, font_color=NAVY, bold=True)
add_hline(s, cx, Inches(2.0), cw, LINE_GRAY)
add_text(s, cx, Inches(2.2), cw, Inches(4.0),
[f'• {p}' for p in points], line_spacing=Pt(10))
add_source(s, 'Source: ...')
```
#### 28. Four-Column Overview (四栏概览页)
**适用场景**: 四个并列维度的概览(如四大业务线、四个能力域)。
```
┌─────────────────────────────────────────┐
│ ▌ Action Title │
├─────────────────────────────────────────┤
│ (1) (2) (3) (4) │
│ 标题1 标题2 标题3 标题4 │
│ ──── ──── ──── ──── │
│ 描述 描述 描述 描述 │
└─────────────────────────────────────────┘
```
```python
s = prs.slides.add_slide(BL)
add_action_title(s, '四大业务板块')
items = [('1', '板块一', '描述内容\n关键数据'),
('2', '板块二', '描述内容\n关键数据'),
('3', '板块三', '描述内容\n关键数据'),
('4', '板块四', '描述内容\n关键数据')]
cw = Inches(2.7)
cg = (CONTENT_W - cw * 4) / 3
for i, (num, title, desc) in enumerate(items):
cx = LM + (cw + cg) * i
add_rect(s, cx, Inches(1.5), cw, Inches(4.8), BG_GRAY)
add_oval(s, cx + Inches(1.1), Inches(1.65), num)
add_text(s, cx + Inches(0.15), Inches(2.3), cw - Inches(0.3), Inches(0.4),
title, font_size=SUB_HEADER_SIZE, font_color=NAVY, bold=True,
alignment=PP_ALIGN.CENTER)
add_hline(s, cx + Inches(0.3), Inches(2.8), cw - Inches(0.6), LINE_GRAY)
add_text(s, cx + Inches(0.15), Inches(3.0), cw - Inches(0.3), Inches(3.0),
desc.split('\n'), line_spacing=Pt(8), alignment=PP_ALIGN.CENTER)
add_source(s, 'Source: ...')
```
---
### 类别 F:时间流程
#### 29. Timeline / Roadmap (时间轴/路线图)
**适用场景**: 展示时间维度的里程碑计划(季度/月度/年度路线图)。
```
┌─────────────────────────────────────────┐
│ ▌ Action Title │
├─────────────────────────────────────────┤
│ │
│ (1)──────(2)──────(3)──────(4) │
│ Q1 Q2 Q3 Q4 │
│ 里程碑1 里程碑2 里程碑3 里程碑4 │
│ │
└─────────────────────────────────────────┘
```
```python
s = prs.slides.add_slide(BL)
add_action_title(s, '2026 年度路线图')
# Timeline bar
add_hline(s, LM + Inches(0.5), Inches(3.0), Inches(10.7), LINE_GRAY, Pt(2))
milestones = [('Q1', '产品 MVP\n发布'), ('Q2', '用户增长\n达到10万'),
('Q3', '盈利\n突破'), ('Q4', '国际化\n拓展')]
spacing = Inches(10.7) / (len(milestones) - 1)
for i, (label, desc) in enumerate(milestones):
mx = LM + Inches(0.5) + spacing * i
add_oval(s, mx - Inches(0.225), Inches(2.775), str(i + 1))
add_text(s, mx - Inches(1.0), Inches(2.0), Inches(2.0), Inches(0.5),
label, font_size=SUB_HEADER_SIZE, font_color=NAVY, bold=True,
alignment=PP_ALIGN.CENTER)
add_text(s, mx - Inches(1.0), Inches(3.5), Inches(2.0), Inches(1.5),
desc, font_size=BODY_SIZE, alignment=PP_ALIGN.CENTER)
add_source(s, 'Source: ...')
```
#### 30. Vertical Steps (垂直步骤页)
**适用场景**: 从上到下的操作步骤或实施阶段。
```
┌─────────────────────────────────────────┐
│ ▌ Action Title │
├─────────────────────────────────────────┤
│ (1) 步骤一标题 详细说明 │
│ ───────────────────────────────────── │
│ (2) 步骤二标题 详细说明 │
│ ───────────────────────────────────── │
│ (3) 步骤三标题 详细说明 │
│ ───────────────────────────────────── │
│ (4) 步骤四标题 详细说明 │
└─────────────────────────────────────────┘
```
```python
s = prs.slides.add_slide(BL)
add_action_title(s, '实施步骤')
steps = [('1', '需求分析', '深入调研用户需求与业务痛点'),
('2', '方案设计', '制定技术架构与实施计划'),
('3', '开发实施', '分阶段迭代交付核心功能'),
('4', '上线运营', '监控效果并持续优化')]
iy = Inches(1.5)
for num, title, desc in steps:
add_oval(s, LM, iy, num)
add_text(s, LM + Inches(0.6), iy, Inches(3.5), Inches(0.4),
title, font_size=SUB_HEADER_SIZE, font_color=NAVY, bold=True)
add_text(s, Inches(5.0), iy, Inches(7.5), Inches(0.4),
desc, font_size=BODY_SIZE)
iy += Inches(0.6)
add_hline(s, LM, iy, CONTENT_W, LINE_GRAY)
iy += Inches(0.5)
add_source(s, 'Source: ...')
```
#### 31. Cycle / Loop (循环图页)
**适用场景**: 闭环流程或迭代循环(如 PDCA、敏捷迭代、反馈循环)。
```
┌─────────────────────────────────────────┐
│ ▌ Action Title │
├─────────────────────────────────────────┤
│ ┌──阶段1──┐ │
│ │ │ │
│ ┌阶段4┐│ │┌阶段2┐ 右侧说明 │
│ │ │└────────┘│ │ │
│ └─────┘ └─────┘ │
│ ┌──阶段3──┐ │
│ └────────┘ │
└─────────────────────────────────────────┘
```
```python
s = prs.slides.add_slide(BL)
add_action_title(s, '持续改进循环(PDCA)')
phases = [('Plan\n计划', Inches(2.8), Inches(1.5)),
('Do\n执行', Inches(5.0), Inches(3.0)),
('Check\n检查', Inches(2.8), Inches(4.5)),
('Act\n改进', Inches(0.6), Inches(3.0))]
for i, (label, px, py) in enumerate(phases):
fill = NAVY if i == 0 else BG_GRAY
tc = WHITE if i == 0 else NAVY
add_rect(s, LM + px, py, Inches(2.2), Inches(1.2), fill)
add_text(s, LM + px + Inches(0.1), py + Inches(0.1), Inches(2.0), Inches(1.0),
label, font_size=SUB_HEADER_SIZE, font_color=tc, bold=True,
alignment=PP_ALIGN.CENTER, anchor=MSO_ANCHOR.MIDDLE)
# Arrows between phases (text arrows)
add_text(s, LM + Inches(4.5), Inches(2.0), Inches(1.0), Inches(0.5),
'->', font_size=Pt(24), font_color=NAVY, alignment=PP_ALIGN.CENTER)
add_text(s, LM + Inches(5.0), Inches(4.0), Inches(1.0), Inches(0.5),
'v', font_size=Pt(24), font_color=NAVY, alignment=PP_ALIGN.CENTER)
add_text(s, LM + Inches(2.0), Inches(5.0), Inches(1.0), Inches(0.5),
'<-', font_size=Pt(24), font_color=NAVY, alignment=PP_ALIGN.CENTER)
add_text(s, LM + Inches(0.8), Inches(2.0), Inches(1.0), Inches(0.5),
'^', font_size=Pt(24), font_color=NAVY, alignment=PP_ALIGN.CENTER)
# Right side explanation
add_rect(s, Inches(8.5), Inches(1.5), Inches(4.0), Inches(5.0), BG_GRAY)
add_text(s, Inches(8.8), Inches(1.7), Inches(3.4), Inches(0.4),
'循环要点', font_size=BODY_SIZE, font_color=NAVY, bold=True)
add_text(s, Inches(8.8), Inches(2.3), Inches(3.4), Inches(3.5),
['每个阶段的说明...'], line_spacing=Pt(10))
add_source(s, 'Source: ...')
```
#### 32. Funnel (漏斗图页)
**适用场景**: 转化漏斗(如销售漏斗、用户转化路径)。
```
┌─────────────────────────────────────────┐
│ ▌ Action Title │
├─────────────────────────────────────────┤
│ ┌════════════════════════════┐ 100% │
│ │ 认知 │ │
│ ├══════════════════════┤ 60% │
│ │ 兴趣 │ │
│ ├════════════════┤ 35% │
│ │ 购买 │ │
│ ├══════════┤ 15% │
│ │ 留存 │ │
│ └─────────┘ │
└─────────────────────────────────────────┘
```
```python
s = prs.slides.add_slide(BL)
add_action_title(s, '用户转化漏斗')
stages = [('认知', '100,000', 1.0), ('兴趣', '60,000', 0.6),
('购买', '35,000', 0.35), ('留存', '15,000', 0.15)]
max_w = Inches(8.0)
fy = Inches(1.6)
for i, (name, count, pct) in enumerate(stages):
w = max_w * pct
fx = Inches(6.666) - w / 2 # center
fill = NAVY if i == 0 else BG_GRAY
tc = WHITE if i == 0 else NAVY
add_rect(s, fx, fy, w, Inches(1.0), fill)
add_text(s, fx + Inches(0.2), fy, w - Inches(0.4), Inches(1.0),
name, font_size=SUB_HEADER_SIZE, font_color=tc, bold=True,
anchor=MSO_ANCHOR.MIDDLE, alignment=PP_ALIGN.CENTER)
add_text(s, fx + w + Inches(0.3), fy + Inches(0.2), Inches(2.5), Inches(0.5),
f'{count} ({int(pct*100)}%)', font_size=BODY_SIZE, font_color=NAVY, bold=True)
fy += Inches(1.2)
add_source(s, 'Source: ...')
```
---
### 类别 G:团队专题
#### 33. Meet the Team (团队介绍页)
**适用场景**: 团队成员/核心高管/项目组简介。
```
┌─────────────────────────────────────────┐
│ ▌ Action Title │
├─────────────────────────────────────────┤
│ ┌─BG──┐ ┌─BG──┐ ┌─BG──┐ │
│ │(头像)│ │(头像)│ │(头像)│ │
│ │ 姓名 │ │ 姓名 │ │ 姓名 │ │
│ │ 职位 │ │ 职位 │ │ 职位 │ │
│ │ 简介 │ │ 简介 │ │ 简介 │ │
│ └──────┘ └──────┘ └──────┘ │
└─────────────────────────────────────────┘
```
```python
s = prs.slides.add_slide(BL)
add_action_title(s, '核心团队')
members = [('张三', 'CEO', '15年行业经验\n前XX公司VP'),
('李四', 'CTO', '技术架构专家\n前XX公司总监'),
('王五', 'COO', '运营管理专家\n前XX公司负责人')]
cw = Inches(3.5)
cg = (CONTENT_W - cw * 3) / 2
for i, (name, role, bio) in enumerate(members):
cx = LM + (cw + cg) * i
add_rect(s, cx, Inches(1.5), cw, Inches(5.0), BG_GRAY)
add_oval(s, cx + Inches(1.25), Inches(1.7), name[0], size=Inches(1.0))
add_text(s, cx + Inches(0.15), Inches(2.9), cw - Inches(0.3), Inches(0.4),
name, font_size=SUB_HEADER_SIZE, font_color=NAVY, bold=True,
alignment=PP_ALIGN.CENTER)
add_text(s, cx + Inches(0.15), Inches(3.4), cw - Inches(0.3), Inches(0.4),
role, font_size=BODY_SIZE, font_color=MED_GRAY, alignment=PP_ALIGN.CENTER)
add_hline(s, cx + Inches(0.3), Inches(3.9), cw - Inches(0.6), LINE_GRAY)
add_text(s, cx + Inches(0.15), Inches(4.1), cw - Inches(0.3), Inches(2.0),
bio.split('\n'), line_spacing=Pt(8), alignment=PP_ALIGN.CENTER)
add_source(s, 'Source: ...')
```
#### 34. Case Study (案例研究页)
**适用场景**: 展示成功案例,按"情境-行动-结果"结构组织。
```
┌─────────────────────────────────────────┐
│ ▌ Action Title │
├─────────────────────────────────────────┤
│ ┌─Situation──┐ ┌─Approach──┐ ┌Result─┐ │
│ │ 背景/挑战 │ │ 采取行动 │ │ 成果 │ │
│ │ │ │ │ │ │ │
│ └────────────┘ └───────────┘ └───────┘ │
│ │
│ ┌──BG_GRAY 客户评价/关键指标──────────┐ │
└─────────────────────────────────────────┘
```
```python
s = prs.slides.add_slide(BL)
add_action_title(s, '案例研究:XX项目')
sections = [('S', 'Situation\n情境', '客户面临的\n挑战描述'),
('A', 'Approach\n方法', '我们采取的\n解决方案'),
('R', 'Result\n成果', '取得的量化\n成果数据')]
sw = Inches(3.5)
sg = (CONTENT_W - sw * 3) / 2
for i, (letter, title, desc) in enumerate(sections):
sx = LM + (sw + sg) * i
fill = NAVY if i == 2 else BG_GRAY
tc = WHITE if i == 2 else NAVY
dc = WHITE if i == 2 else DARK_GRAY
add_rect(s, sx, Inches(1.5), sw, Inches(3.0), fill)
add_oval(s, sx + Inches(0.15), Inches(1.65), letter,
bg=WHITE if i == 2 else NAVY, fg=NAVY if i == 2 else WHITE)
add_text(s, sx + Inches(0.15), Inches(2.2), sw - Inches(0.3), Inches(0.8),
title, font_size=BODY_SIZE, font_color=tc, bold=True,
alignment=PP_ALIGN.CENTER)
add_text(s, sx + Inches(0.15), Inches(3.1), sw - Inches(0.3), Inches(1.0),
desc, font_size=BODY_SIZE, font_color=dc, alignment=PP_ALIGN.CENTER)
# Bottom highlight
add_rect(s, LM, Inches(5.0), CONTENT_W, Inches(1.5), BG_GRAY)
add_text(s, LM + Inches(0.3), Inches(5.1), Inches(1.5), Inches(0.4),
'关键成果', font_size=BODY_SIZE, font_color=NAVY, bold=True)
add_text(s, LM + Inches(0.3), Inches(5.6), CONTENT_W - Inches(0.6), Inches(0.6),
'营收增长 45% | 客户满意度 92% | 运营效率提升 30%',
font_size=BODY_SIZE, font_color=DARK_GRAY)
add_source(s, 'Source: ...')
```
#### 35. Action Items / Next Steps (行动计划页)
**适用场景**: 演示文稿结尾的下一步行动清单。
```
┌─────────────────────────────────────────┐
│ ▌ Action Title │
├─────────────────────────────────────────┤
│ ┌──NAVY──┐ ┌──NAVY──┐ ┌──NAVY──┐ │
│ │行动一 │ │行动二 │ │行动三 │ │
│ ├─BG─────┤ ├─BG─────┤ ├─BG─────┤ │
│ │ 时间 │ │ 时间 │ │ 时间 │ │
│ │ 描述 │ │ 描述 │ │ 描述 │ │
│ │ 负责人 │ │ 负责人 │ │ 负责人 │ │
│ └────────┘ └────────┘ └────────┘ │
└─────────────────────────────────────────┘
```
```python
s = prs.slides.add_slide(BL)
add_action_title(s, '下一步行动')
actions = [('建立数据中台', '2026 Q2', '完成核心数据资产盘点\n搭建基础架构', '技术团队'),
('启动用户增长计划', '2026 Q3', '渠道拓展+内容营销\n目标新增50万用户', '市场团队'),
('优化运营流程', '2026 Q4', '自动化率提升至80%\n降本增效', '运营团队')]
cw = Inches(3.5)
cg = (CONTENT_W - cw * 3) / 2
for i, (title, timeline, desc, owner) in enumerate(actions):
cx = LM + (cw + cg) * i
add_rect(s, cx, Inches(1.5), cw, Inches(0.6), NAVY)
add_text(s, cx + Inches(0.15), Inches(1.5), cw - Inches(0.3), Inches(0.6),
title, font_size=BODY_SIZE, font_color=WHITE, bold=True,
anchor=MSO_ANCHOR.MIDDLE, alignment=PP_ALIGN.CENTER)
add_rect(s, cx, Inches(2.1), cw, Inches(0.4), BG_GRAY)
add_text(s, cx + Inches(0.15), Inches(2.1), cw - Inches(0.3), Inches(0.4),
timeline, font_size=BODY_SIZE, font_color=NAVY, bold=True,
anchor=MSO_ANCHOR.MIDDLE, alignment=PP_ALIGN.CENTER)
add_text(s, cx + Inches(0.15), Inches(2.7), cw - Inches(0.3), Inches(2.0),
desc.split('\n'), line_spacing=Pt(8), alignment=PP_ALIGN.CENTER)
add_hline(s, cx + Inches(0.3), Inches(4.9), cw - Inches(0.6), LINE_GRAY)
add_text(s, cx + Inches(0.15), Inches(5.1), cw - Inches(0.3), Inches(0.4),
f'负责人:{owner}', font_size=BODY_SIZE, font_color=MED_GRAY,
alignment=PP_ALIGN.CENTER)
add_source(s, 'Source: ...')
```
#### 36. Closing / Thank You (结束页)
**适用场景**: 演示文稿结尾的致谢或总结收尾页。
```
┌─────────────────────────────────────────┐
│ ═══ │
│ │
│ 核心总结语句 │
│ ────────── │
│ 结束寄语 │
│ │
│ ───── │
└─────────────────────────────────────────┘
```
```python
s = prs.slides.add_slide(BL)
add_rect(s, 0, 0, SW, Inches(0.05), NAVY)
add_text(s, Inches(1.5), Inches(2.0), Inches(10.3), Inches(1.0),
'核心总结语句', font_size=Pt(28), font_color=NAVY, bold=True,
font_name='Georgia', alignment=PP_ALIGN.CENTER)
add_hline(s, Inches(5.5), Inches(3.3), Inches(2.3), NAVY, Pt(1.5))
add_text(s, Inches(1.5), Inches(3.8), Inches(10.3), Inches(2.0),
'结束寄语或核心思想的延伸表达',
font_size=SUB_HEADER_SIZE, font_color=DARK_GRAY, alignment=PP_ALIGN.CENTER)
add_hline(s, Inches(1), Inches(6.8), Inches(3), NAVY, Pt(2))
```
---
### 类别 H:数据图表
> **触发规则**:当用户提供的内容包含 **日期/时间 + 数值/百分比** 的结构化数据(如舆情变化、销售趋势、KPI 周报、转化率变化等),**必须优先使用本类别的图表模式**,而不是 Data Table (#11) 或 Scorecard (#23)。
>
> **识别信号**(满足任一即触发):
> - 数据中出现 `日期 + 百分比` 或 `日期 + 数值` 的组合
> - 提示词含 `████` 进度条字符 + 百分比
> - 内容涉及"趋势"、"演变"、"变化"、"走势"、"周报"、"日报"等时序关键词
> - 数据行数 ≥ 3 且每行包含至少一个类别和一个数值
#### 37. Grouped Bar Chart(分组柱状图 / 情绪热力图)
**适用场景**: 多个类别在不同时间点的数值对比(如舆情情绪分布、多产品销售对比、多指标周变化)。
```
┌─────────────────────────────────────────┐
│ ▌ Action Title │
├─────────────────────────────────────────┤
│ 100% ─ │
│ 80% ─ ██ │
│ 60% ─ ██ ██ │
│ 40% ─ ██ ██ ██ ██ │
│ 20% ─ ██ ██ ██ ██ ██ ██ ██ │
│ 0% ──────────────────────────────── │
│ 3/4 3/6 3/8 3/10 │
│ │
│ ■ 正面 ■ 中性 ■ 负面 │
│ │
│ ┌─BG_GRAY 趋势总结──────────────────┐ │
│ │ 总结文字 │ │
│ └───────────────────────────────────┘ │
└─────────────────────────────────────────┘
```
**设计规范**:
- 柱状图使用 `add_rect()` 手工绘制,不依赖 matplotlib
- Y 轴标签(百分比)用 `add_text()` 左对齐
- X 轴标签(日期)用 `add_text()` 居中
- 每组柱子间留 0.3" 间距,组内柱子间留 0.05" 间距
- 图例用小矩形色块 + 文字标签,放在图表下方
- 底部可选趋势总结区(BG_GRAY)
**颜色分配**:
- 第一类别:NAVY (#051C2C) — 主要/正面
- 第二类别:LINE_GRAY (#CCCCCC) — 中性/基准
- 第三类别:MED_GRAY (#666666) — 次要/负面
- 第四类别:ACCENT_BLUE (#006BA6) — 扩展
- 若类别有语义色(如正面=NAVY, 负面=MED_GRAY),优先使用语义色
```python
s = prs.slides.add_slide(BL)
add_action_title(s, '一周舆情演变:情绪分布从中性主导转向正面主导')
# ── 数据定义 ──
dates = ['3/4', '3/6', '3/8', '3/10', '3/11']
categories = ['正面', '中性', '负面']
cat_colors = [NAVY, LINE_GRAY, MED_GRAY]
# 每行 = 一个日期,每列 = 一个类别的百分比值
data = [
[20, 80, 0], # 3/4
[75, 15, 10], # 3/6
[75, 20, 5], # 3/8
[75, 20, 5], # 3/10
[75, 20, 5], # 3/11
]
# ── 图表区域参数 ──
chart_left = LM + Inches(0.8) # 柱子起始 X(留 Y 轴标签空间)
chart_top = Inches(1.6) # 图表顶部
chart_bottom = Inches(5.0) # 图表底部(X 轴位置)
chart_height = chart_bottom - chart_top
chart_right = Inches(11.5) # 图表右侧边界
chart_width = chart_right - chart_left
n_dates = len(dates)
n_cats = len(categories)
group_width = chart_width / n_dates # 每组占据的总宽度
bar_width = Inches(0.35) # 单根柱子宽度
bar_gap = Inches(0.05) # 组内柱子间距
group_bar_width = bar_width * n_cats + bar_gap * (n_cats - 1) # 一组柱子总宽
max_val = 100 # Y 轴最大值
# ── Y 轴刻度标签 + 水平参考线 ──
y_ticks = [0, 20, 40, 60, 80, 100]
for tick in y_ticks:
tick_y = chart_bottom - chart_height * (tick / max_val)
# Y 轴标签
add_text(s, LM, tick_y - Inches(0.15), Inches(0.7), Inches(0.3),
f'{tick}%', font_size=Pt(9), font_color=MED_GRAY,
alignment=PP_ALIGN.RIGHT)
# 水平参考线(极细浅灰色)
if tick > 0:
add_hline(s, chart_left, tick_y, chart_width, LINE_GRAY, Pt(0.25))
# ── X 轴基线 ──
add_hline(s, chart_left, chart_bottom, chart_width, BLACK, Pt(0.5))
# ── 绘制柱子 ──
for di, date in enumerate(dates):
group_x = chart_left + group_width * di + (group_width - group_bar_width) / 2
for ci, cat in enumerate(categories):
val = data[di][ci]
bar_h = chart_height * (val / max_val)
bar_x = group_x + (bar_width + bar_gap) * ci
bar_y = chart_bottom - bar_h
if val > 0:
add_rect(s, bar_x, bar_y, bar_width, bar_h, cat_colors[ci])
# 柱顶数值标签(仅当值 >= 10% 时显示)
if val >= 10:
add_text(s, bar_x - Inches(0.05), bar_y - Inches(0.25),
bar_width + Inches(0.1), Inches(0.25),
f'{val}%', font_size=Pt(9), font_color=DARK_GRAY,
alignment=PP_ALIGN.CENTER)
# X 轴日期标签
add_text(s, chart_left + group_width * di, chart_bottom + Inches(0.05),
group_width, Inches(0.3), date,
font_size=BODY_SIZE, font_color=DARK_GRAY, alignment=PP_ALIGN.CENTER)
# ── 图例(图表下方居中)──
legend_y = Inches(5.5)
legend_start_x = Inches(4.5)
for ci, cat in enumerate(categories):
lx = legend_start_x + Inches(1.8) * ci
add_rect(s, lx, legend_y + Inches(0.05), Inches(0.2), Inches(0.2), cat_colors[ci])
add_text(s, lx + Inches(0.3), legend_y, Inches(1.2), Inches(0.3),
cat, font_size=Pt(12), font_color=DARK_GRAY)
# ── 底部趋势总结区域(可选)──
add_rect(s, LM, Inches(6.0), CONTENT_W, Inches(0.8), BG_GRAY)
add_text(s, LM + Inches(0.3), Inches(6.0), Inches(1.5), Inches(0.8),
'趋势总结', font_size=BODY_SIZE, font_color=NAVY, bold=True,
anchor=MSO_ANCHOR.MIDDLE)
add_text(s, LM + Inches(2.0), Inches(6.0), CONTENT_W - Inches(2.3), Inches(0.8),
'舆情情绪从 3/4 的中性主导(80%)迅速转向正面主导(75%),负面情绪始终控制在 10% 以内',
font_size=BODY_SIZE, font_color=DARK_GRAY, anchor=MSO_ANCHOR.MIDDLE)
add_source(s, 'Source: 舆情监测平台数据')
add_page_number(s, 5, 12)
```
#### 38. Stacked Bar Chart(堆叠柱状图 / 百分比占比图)
**适用场景**: 展示各类别在总体中的占比随时间变化(如市场份额演变、预算分配变化、渠道贡献占比)。适合强调"构成比例"而非"绝对值"。
```
┌─────────────────────────────────────────┐
│ ▌ Action Title │
├─────────────────────────────────────────┤
│ 100% ─ ┌──┐ ┌──┐ ┌──┐ ┌──┐ │
│ │C │ │ │ │ │ │ │ │
│ 50% ─ │B │ │B │ │ │ │ │ │
│ │ │ │ │ │B │ │B │ │
│ │A │ │A │ │A │ │A │ │
│ 0% ──└──┘──└──┘──└──┘──└──┘──────── │
│ Q1 Q2 Q3 Q4 │
│ │
│ ■ A类 ■ B类 ■ C类 │
│ │
│ ┌─BG_GRAY 关键发现──────────────────┐ │
│ │ 分析文字 │ │
│ └───────────────────────────────────┘ │
└─────────────────────────────────────────┘
```
**设计规范**:
- 每根柱子内部从底部到顶部依次堆叠各类别
- 柱子宽度统一为 0.8"~1.2"(比分组柱状图更宽)
- 各段之间无间距,直接堆叠
- 百分比标签写在对应色块内部(当色块高度足够时),或省略
- 右侧可选放置"直接标签"指向最后一根柱子的各段
**颜色分配**(从底到顶):
- 第一层(最大/最重要):NAVY (#051C2C)
- 第二层:ACCENT_BLUE (#006BA6)
- 第三层:LINE_GRAY (#CCCCCC)
- 第四层:BG_GRAY (#F2F2F2) + 细边框
- 更多层级:使用 ACCENT_GREEN, ACCENT_ORANGE
```python
s = prs.slides.add_slide(BL)
add_action_title(s, '渠道贡献占比:线上渠道在四个季度内从 30% 增长到 55%')
# ── 数据定义 ──
periods = ['Q1', 'Q2', 'Q3', 'Q4']
categories = ['线上直营', '线下门店', '经销商']
cat_colors = [NAVY, ACCENT_BLUE, LINE_GRAY]
# 每行 = 一个时间段,值为百分比(总和应为 100)
data = [
[30, 45, 25], # Q1
[38, 40, 22], # Q2
[48, 32, 20], # Q3
[55, 28, 17], # Q4
]
# ── 图表区域参数 ──
chart_left = LM + Inches(0.8)
chart_top = Inches(1.6)
chart_bottom = Inches(5.0)
chart_height = chart_bottom - chart_top
chart_right = Inches(9.5)
chart_width = chart_right - chart_left
n_periods = len(periods)
bar_width = Inches(1.0) # 堆叠柱宽度
bar_spacing = chart_width / n_periods
max_val = 100
# ── Y 轴刻度标签 ──
y_ticks = [0, 25, 50, 75, 100]
for tick in y_ticks:
tick_y = chart_bottom - chart_height * (tick / max_val)
add_text(s, LM, tick_y - Inches(0.15), Inches(0.7), Inches(0.3),
f'{tick}%', font_size=Pt(9), font_color=MED_GRAY,
alignment=PP_ALIGN.RIGHT)
if tick > 0:
add_hline(s, chart_left, tick_y, chart_width, LINE_GRAY, Pt(0.25))
# ── X 轴基线 ──
add_hline(s, chart_left, chart_bottom, chart_width, BLACK, Pt(0.5))
# ── 绘制堆叠柱子 ──
for pi, period in enumerate(periods):
bar_x = chart_left + bar_spacing * pi + (bar_spacing - bar_width) / 2
cumulative = 0 # 从底部累积
for ci in range(len(categories)):
val = data[pi][ci]
seg_h = chart_height * (val / max_val)
seg_y = chart_bottom - chart_height * ((cumulative + val) / max_val)
if val > 0:
add_rect(s, bar_x, seg_y, bar_width, seg_h, cat_colors[ci])
# 段内百分比标签(当段高 >= 0.4" 时显示)
if seg_h >= Inches(0.4):
label_color = WHITE if ci == 0 else (WHITE if ci == 1 else DARK_GRAY)
add_text(s, bar_x, seg_y, bar_width, seg_h,
f'{val}%', font_size=Pt(11), font_color=label_color,
bold=True, alignment=PP_ALIGN.CENTER,
anchor=MSO_ANCHOR.MIDDLE)
cumulative += val
# X 轴标签
add_text(s, chart_left + bar_spacing * pi, chart_bottom + Inches(0.05),
bar_spacing, Inches(0.3), period,
font_size=BODY_SIZE, font_color=DARK_GRAY, alignment=PP_ALIGN.CENTER)
# ── 右侧直接标签(指向最后一根柱子)──
last_bar_right = chart_left + bar_spacing * (n_periods - 1) + (bar_spacing + bar_width) / 2
label_x = last_bar_right + Inches(0.2)
cumulative = 0
for ci in range(len(categories)):
val = data[-1][ci]
mid_y = chart_bottom - chart_height * ((cumulative + val / 2) / max_val)
add_text(s, label_x, mid_y - Inches(0.15), Inches(2.5), Inches(0.3),
f'{categories[ci]} {val}%', font_size=Pt(11),
font_color=cat_colors[ci] if ci < 2 else DARK_GRAY, bold=True)
cumulative += val
# ── 图例(图表下方)──
legend_y = Inches(5.5)
legend_start_x = Inches(4.0)
for ci, cat in enumerate(categories):
lx = legend_start_x + Inches(2.2) * ci
add_rect(s, lx, legend_y + Inches(0.05), Inches(0.2), Inches(0.2), cat_colors[ci])
add_text(s, lx + Inches(0.3), legend_y, Inches(1.6), Inches(0.3),
cat, font_size=Pt(12), font_color=DARK_GRAY)
# ── 底部关键发现 ──
add_rect(s, LM, Inches(6.0), CONTENT_W, Inches(0.8), BG_GRAY)
add_text(s, LM + Inches(0.3), Inches(6.0), Inches(1.5), Inches(0.8),
'关键发现', font_size=BODY_SIZE, font_color=NAVY, bold=True,
anchor=MSO_ANCHOR.MIDDLE)
add_text(s, LM + Inches(2.0), Inches(6.0), CONTENT_W - Inches(2.3), Inches(0.8),
'线上直营渠道占比从 Q1 的 30% 稳步增长至 Q4 的 55%,成为第一大收入来源',
font_size=BODY_SIZE, font_color=DARK_GRAY, anchor=MSO_ANCHOR.MIDDLE)
add_source(s, 'Source: 内部销售数据')
add_page_number(s, 6, 12)
```
#### 39. Horizontal Bar Chart(水平柱状图 / 排名图)
**适用场景**: 类别名称较长的排名对比(如部门绩效排名、品牌认知度、功能使用率排行)。横向柱状图在类别较多时可读性更好。
```
┌─────────────────────────────────────────┐
│ ▌ Action Title │
├─────────────────────────────────────────┤
│ 类别 A ████████████████████ 92% │
│ 类别 B ████████████████ 85% │
│ 类别 C ██████████████ 78% │
│ 类别 D ████████████ 65% │
│ 类别 E ████████ 52% │
│ │
│ ┌─BG_GRAY 说明──────────────────────┐ │
│ │ 分析文字 │ │
│ └───────────────────────────────────┘ │
└─────────────────────────────────────────┘
```
**设计规范**:
- 类别标签左对齐,柱子起始位置统一
- 最长柱子 = 100% 参考宽度
- 每根柱子右侧标注数值
- 第一名用 NAVY,其余用 BG_GRAY(或渐变灰色)
- 行间距均匀
```python
s = prs.slides.add_slide(BL)
add_action_title(s, '功能使用率排名:智能推荐以 92% 使用率位居第一')
# ── 数据定义(已排序)──
items = [
('智能推荐', 92),
('搜索功能', 85),
('个人中心', 78),
('消息通知', 65),
('社区互动', 52),
('数据报表', 38),
]
# ── 图表区域参数 ──
label_x = LM
label_w = Inches(2.0)
bar_x = LM + Inches(2.2)
bar_max_w = Inches(7.5)
value_x = bar_x + bar_max_w + Inches(0.2)
row_h = Inches(0.65)
start_y = Inches(1.6)
max_val = 100
# ── 绘制水平柱子 ──
for i, (name, val) in enumerate(items):
ry = start_y + row_h * i
bar_w = bar_max_w * (val / max_val)
fill = NAVY if i == 0 else BG_GRAY
tc = NAVY if i == 0 else DARK_GRAY
# 类别标签
add_text(s, label_x, ry, label_w, row_h, name,
font_size=BODY_SIZE, font_color=tc, bold=(i == 0),
anchor=MSO_ANCHOR.MIDDLE)
# 背景轨道(浅灰底)
add_rect(s, bar_x, ry + Inches(0.12), bar_max_w, Inches(0.4), RGBColor(0xF2, 0xF2, 0xF2))
# 数据柱
add_rect(s, bar_x, ry + Inches(0.12), bar_w, Inches(0.4), fill)
# 数值标签
add_text(s, value_x, ry, Inches(1.0), row_h, f'{val}%',
font_size=BODY_SIZE, font_color=tc, bold=(i == 0),
anchor=MSO_ANCHOR.MIDDLE)
# 行分隔线
if i < len(items) - 1:
add_hline(s, label_x, ry + row_h, bar_max_w + Inches(2.5), LINE_GRAY, Pt(0.25))
# ── 底部说明 ──
add_rect(s, LM, Inches(5.8), CONTENT_W, Inches(0.9), BG_GRAY)
add_text(s, LM + Inches(0.3), Inches(5.8), Inches(1.5), Inches(0.9),
'分析', font_size=BODY_SIZE, font_color=NAVY, bold=True,
anchor=MSO_ANCHOR.MIDDLE)
add_text(s, LM + Inches(2.0), Inches(5.8), CONTENT_W - Inches(2.3), Inches(0.9),
'智能推荐和搜索功能是用户最高频使用的两大核心功能,社区互动和数据报表仍有较大提升空间',
font_size=BODY_SIZE, font_color=DARK_GRAY, anchor=MSO_ANCHOR.MIDDLE)
add_source(s, 'Source: 产品埋点数据,2026年2月')
add_page_number(s, 7, 12)
```
---
### Category I: Image + Content Layouts
> **Image Placeholder Convention**: Since python-pptx cannot embed web images at generation time, all image positions use a **gray placeholder rectangle** with crosshair lines and a label. The user replaces these with real images after generation.
#### Helper: `add_image_placeholder()`
```python
def add_image_placeholder(slide, left, top, width, height, label='Image'):
"""Draw a gray placeholder box with crosshair + label for image positions."""
PLACEHOLDER_GRAY = RGBColor(0xD9, 0xD9, 0xD9)
# Background rect
rect = add_rect(slide, left, top, width, height, PLACEHOLDER_GRAY)
# Crosshair lines (diagonal from corners)
add_hline(slide, left, top + height // 2, width, RGBColor(0xBB, 0xBB, 0xBB), Pt(0.5))
# Vertical center line as thin rect
vw = Pt(0.5)
add_rect(slide, left + width // 2 - vw // 2, top, vw, height, RGBColor(0xBB, 0xBB, 0xBB))
# Label
add_text(slide, left, top + height // 2 - Inches(0.2), width, Inches(0.4),
f'[ {label} ]', font_size=Pt(12), font_color=RGBColor(0x99, 0x99, 0x99),
alignment=PP_ALIGN.CENTER, anchor=MSO_ANCHOR.MIDDLE)
return rect
```
---
#### #40 — Content + Right Image
**Use case**: Text explanation on the left, supporting visual on the right — product screenshot, photo, diagram.
```
┌──────────────────────────────────────────────┐
│ [Action Title — full width, NAVY bg] │
├───────────────────────┬──────────────────────┤
│ Heading │ │
│ • Bullet point 1 │ ┌──────────────┐ │
│ • Bullet point 2 │ │ IMAGE │ │
│ • Bullet point 3 │ │ PLACEHOLDER │ │
│ │ └──────────────┘ │
│ Takeaway box (gray) │ │
├───────────────────────┴──────────────────────┤
│ Source | Page N/Total │
└──────────────────────────────────────────────┘
```
```python
s = prs.slides.add_slide(prs.slide_layouts[6])
# ── Title bar ──
add_rect(s, Inches(0), Inches(0), Inches(13.333), Inches(0.75), NAVY)
add_text(s, LM, Inches(0), CONTENT_W, Inches(0.75),
'数字化转型的三大核心能力',
font_size=TITLE_SIZE, font_color=WHITE, bold=True,
anchor=MSO_ANCHOR.MIDDLE)
add_hline(s, LM, Inches(0.75), CONTENT_W, BLACK, Pt(0.5))
# ── Left: text content (55%) ──
left_w = Inches(6.5)
ty = Inches(1.1)
add_text(s, LM, ty, left_w, Inches(0.4),
'组织需要构建三项关键能力以驱动转型',
font_size=Pt(18), font_color=NAVY, bold=True)
bullets = [
'• 数据驱动决策:建立端到端数据采集、清洗与分析体系',
'• 敏捷运营模式:从瀑布式开发转向双周迭代交付',
'• 人才梯队建设:培养兼具业务理解与技术能力的复合型团队'
]
add_text(s, LM, ty + Inches(0.5), left_w, Inches(2.4),
bullets, font_size=BODY_SIZE, font_color=DARK_GRAY, line_spacing=Pt(8))
# Takeaway box
add_rect(s, LM, Inches(4.5), left_w, Inches(0.8), BG_GRAY)
add_text(s, LM + Inches(0.3), Inches(4.5), left_w - Inches(0.6), Inches(0.8),
'关键洞见:数据能力是三项能力中投资回报率最高的切入点',
font_size=BODY_SIZE, font_color=NAVY, bold=True, anchor=MSO_ANCHOR.MIDDLE)
# ── Right: image placeholder (45%) ──
img_x = LM + left_w + Inches(0.3)
img_w = CONTENT_W - left_w - Inches(0.3)
add_image_placeholder(s, img_x, Inches(1.1), img_w, Inches(4.2), '产品截图 / 架构图')
add_source(s, 'Source: McKinsey Digital, 2026')
add_page_number(s, 3, 12)
```
---
#### #41 — Left Image + Content
**Use case**: Visual-first layout — image on left draws attention, text on right provides context.
```
┌──────────────────────────────────────────────┐
│ [Action Title — full width, NAVY bg] │
├──────────────────────┬───────────────────────┤
│ │ Heading │
│ ┌──────────────┐ │ • Bullet point 1 │
│ │ IMAGE │ │ • Bullet point 2 │
│ │ PLACEHOLDER │ │ • Bullet point 3 │
│ └──────────────┘ │ │
│ │ Takeaway box (gray) │
├──────────────────────┴───────────────────────┤
│ Source | Page N/Total │
└──────────────────────────────────────────────┘
```
```python
s = prs.slides.add_slide(prs.slide_layouts[6])
# ── Title bar ──
add_rect(s, Inches(0), Inches(0), Inches(13.333), Inches(0.75), NAVY)
add_text(s, LM, Inches(0), CONTENT_W, Inches(0.75),
'客户旅程优化的关键触点分析',
font_size=TITLE_SIZE, font_color=WHITE, bold=True,
anchor=MSO_ANCHOR.MIDDLE)
add_hline(s, LM, Inches(0.75), CONTENT_W, BLACK, Pt(0.5))
# ── Left: image placeholder (45%) ──
img_w = Inches(5.4)
add_image_placeholder(s, LM, Inches(1.1), img_w, Inches(4.2), '客户旅程地图')
# ── Right: text content (55%) ──
rx = LM + img_w + Inches(0.3)
rw = CONTENT_W - img_w - Inches(0.3)
ty = Inches(1.1)
add_text(s, rx, ty, rw, Inches(0.4),
'五个关键触点决定80%的客户满意度',
font_size=Pt(18), font_color=NAVY, bold=True)
bullets = [
'• 首次接触:品牌认知与第一印象建立',
'• 产品体验:核心功能的易用性与稳定性',
'• 售后服务:响应速度与问题解决率',
'• 续约决策:价值感知与竞品比较',
]
add_text(s, rx, ty + Inches(0.5), rw, Inches(2.4),
bullets, font_size=BODY_SIZE, font_color=DARK_GRAY, line_spacing=Pt(8))
# Takeaway box
add_rect(s, rx, Inches(4.5), rw, Inches(0.8), BG_GRAY)
add_text(s, rx + Inches(0.2), Inches(4.5), rw - Inches(0.4), Inches(0.8),
'建议优先投资"首次接触"和"产品体验"两个高杠杆触点',
font_size=BODY_SIZE, font_color=NAVY, bold=True, anchor=MSO_ANCHOR.MIDDLE)
add_source(s, 'Source: 客户满意度调研数据,2026 Q1')
add_page_number(s, 4, 12)
```
---
#### #42 — Three Images + Descriptions
**Use case**: Visual comparison of three products, locations, or concepts side by side.
```
┌──────────────────────────────────────────────┐
│ [Action Title — full width, NAVY bg] │
├──────────────┬──────────────┬────────────────┤
│ ┌──────────┐ │ ┌──────────┐ │ ┌──────────┐ │
│ │ IMAGE 1 │ │ │ IMAGE 2 │ │ │ IMAGE 3 │ │
│ └──────────┘ │ └──────────┘ │ └──────────┘ │
│ Title 1 │ Title 2 │ Title 3 │
│ Description │ Description │ Description │
├──────────────┴──────────────┴────────────────┤
│ Source | Page N/Total │
└──────────────────────────────────────────────┘
```
```python
s = prs.slides.add_slide(prs.slide_layouts[6])
# ── Title bar ──
add_rect(s, Inches(0), Inches(0), Inches(13.333), Inches(0.75), NAVY)
add_text(s, LM, Inches(0), CONTENT_W, Inches(0.75),
'三大标杆项目的实施效果对比',
font_size=TITLE_SIZE, font_color=WHITE, bold=True,
anchor=MSO_ANCHOR.MIDDLE)
add_hline(s, LM, Inches(0.75), CONTENT_W, BLACK, Pt(0.5))
items = [
('项目 A:智慧零售', '通过数字化门店改造,客流转化率提升35%,单店日均营收增长28%'),
('项目 B:供应链优化', '端到端库存周转天数从45天缩短至28天,缺货率降低至2.1%'),
('项目 C:会员体系', '会员复购率从22%提升至41%,ARPU值增长56%'),
]
col_w = Inches(3.7)
gap = Inches(0.35)
img_h = Inches(2.5)
ty = Inches(1.0)
for i, (title, desc) in enumerate(items):
cx = LM + i * (col_w + gap)
# Image placeholder
add_image_placeholder(s, cx, ty, col_w, img_h, f'项目{chr(65+i)}实景照片')
# Title
add_text(s, cx, ty + img_h + Inches(0.15), col_w, Inches(0.35),
title, font_size=Pt(16), font_color=NAVY, bold=True)
# Description
add_text(s, cx, ty + img_h + Inches(0.55), col_w, Inches(1.0),
desc, font_size=BODY_SIZE, font_color=DARK_GRAY)
add_source(s, 'Source: 项目实施报告汇总,2025-2026')
add_page_number(s, 5, 12)
```
---
#### #43 — Image + Four Key Points
**Use case**: Central image/diagram with four callout points arranged around or beside it.
```
┌──────────────────────────────────────────────┐
│ [Action Title — full width, NAVY bg] │
├──────────────────────────────────────────────┤
│ ┌─────┬──────────┐ ┌─────┬──────────┐ │
│ │ 01 │ Point A │ │ 02 │ Point B │ │
│ └─────┴──────────┘ └─────┴──────────┘ │
│ ┌──────────────────────┐ │
│ │ IMAGE PLACEHOLDER │ │
│ └──────────────────────┘ │
│ ┌─────┬──────────┐ ┌─────┬──────────┐ │
│ │ 03 │ Point C │ │ 04 │ Point D │ │
│ └─────┴──────────┘ └─────┴──────────┘ │
├──────────────────────────────────────────────┤
│ Source | Page N/Total │
└──────────────────────────────────────────────┘
```
```python
s = prs.slides.add_slide(prs.slide_layouts[6])
# ── Title bar ──
add_rect(s, Inches(0), Inches(0), Inches(13.333), Inches(0.75), NAVY)
add_text(s, LM, Inches(0), CONTENT_W, Inches(0.75),
'产品生态系统的四大核心模块',
font_size=TITLE_SIZE, font_color=WHITE, bold=True,
anchor=MSO_ANCHOR.MIDDLE)
add_hline(s, LM, Inches(0.75), CONTENT_W, BLACK, Pt(0.5))
# ── Center image ──
img_w = Inches(5.0)
img_h = Inches(2.4)
img_x = LM + (CONTENT_W - img_w) / 2
add_image_placeholder(s, img_x, Inches(2.5), img_w, img_h, '产品生态架构图')
# ── Four points: 2 above, 2 below ──
points = [
('用户端', '移动App + 小程序,覆盖2亿月活用户'),
('商户端', 'SaaS管理平台,赋能50万商户'),
('数据中台', '实时数据处理能力达10亿条/天'),
('开放平台', 'API市场已接入300+合作伙伴'),
]
accents = [ACCENT_BLUE, ACCENT_GREEN, ACCENT_ORANGE, ACCENT_RED]
card_w = Inches(5.2)
card_h = Inches(0.7)
positions = [
(LM + Inches(0.5), Inches(1.1)),
(LM + CONTENT_W - card_w - Inches(0.5), Inches(1.1)),
(LM + Inches(0.5), Inches(5.2)),
(LM + CONTENT_W - card_w - Inches(0.5), Inches(5.2)),
]
for i, (title, desc) in enumerate(points):
px, py = positions[i]
add_oval(s, px, py + Inches(0.08), str(i + 1), bg=accents[i])
add_text(s, px + Inches(0.55), py, Inches(1.5), Inches(0.35),
title, font_size=Pt(16), font_color=accents[i], bold=True)
add_text(s, px + Inches(0.55), py + Inches(0.35), card_w - Inches(0.55), Inches(0.35),
desc, font_size=BODY_SIZE, font_color=DARK_GRAY)
add_source(s, 'Source: 产品架构文档,2026年3月')
add_page_number(s, 6, 12)
```
---
#### #44 — Full-Width Image with Overlay Text
**Use case**: Hero image covering the slide with semi-transparent overlay text — for visual storytelling, case study intros.
```
┌──────────────────────────────────────────────┐
│ │
│ FULL-WIDTH IMAGE │
│ PLACEHOLDER │
│ │
│ ┌─────────────────────────────────────┐ │
│ │ Semi-transparent dark overlay │ │
│ │ "Quote or headline text" │ │
│ │ — Attribution │ │
│ └─────────────────────────────────────┘ │
│ │
├──────────────────────────────────────────────┤
│ Source | Page N/Total │
└──────────────────────────────────────────────┘
```
```python
s = prs.slides.add_slide(prs.slide_layouts[6])
# ── Full-width image placeholder ──
add_image_placeholder(s, Inches(0), Inches(0), Inches(13.333), Inches(6.5), '全幅背景图片')
# ── Dark semi-transparent overlay bar at bottom ──
overlay = add_rect(s, Inches(0), Inches(4.0), Inches(13.333), Inches(2.0),
RGBColor(0x05, 0x1C, 0x2C))
# Set transparency via alpha (70% opaque)
fill_elem = overlay._element.find(qn('p:spPr')).find(qn('a:solidFill'))
if fill_elem is not None:
srgb = fill_elem.find(qn('a:srgbClr'))
if srgb is not None:
alpha = srgb.makeelement(qn('a:alpha'), {'val': '70000'})
srgb.append(alpha)
add_text(s, LM, Inches(4.1), CONTENT_W, Inches(0.8),
'"数字化不是选择题,而是生存题"',
font_size=Pt(28), font_color=WHITE, bold=True,
alignment=PP_ALIGN.CENTER, anchor=MSO_ANCHOR.MIDDLE)
add_text(s, LM, Inches(4.9), CONTENT_W, Inches(0.4),
'— 某全球500强企业CEO,2026年战略峰会',
font_size=Pt(14), font_color=RGBColor(0xCC, 0xCC, 0xCC),
alignment=PP_ALIGN.CENTER, anchor=MSO_ANCHOR.MIDDLE)
add_source(s, 'Source: 战略峰会实录,2026年1月')
add_page_number(s, 7, 12)
```
---
#### #45 — Case Study with Image
**Use case**: Extended case study with a visual — Situation, Approach, Result + supporting image.
```
┌──────────────────────────────────────────────┐
│ [Action Title — full width, NAVY bg] │
├───────────────────────┬──────────────────────┤
│ SITUATION │ │
│ Background text... │ ┌──────────────┐ │
│ │ │ IMAGE │ │
│ APPROACH │ │ PLACEHOLDER │ │
│ Method text... │ └──────────────┘ │
│ │ │
│ RESULT │ ┌─────┬─────┐ │
│ Outcome metrics... │ │ KPI1│ KPI2│ │
│ │ └─────┴─────┘ │
├───────────────────────┴──────────────────────┤
│ Source | Page N/Total │
└──────────────────────────────────────────────┘
```
```python
s = prs.slides.add_slide(prs.slide_layouts[6])
# ── Title bar ──
add_rect(s, Inches(0), Inches(0), Inches(13.333), Inches(0.75), NAVY)
add_text(s, LM, Inches(0), CONTENT_W, Inches(0.75),
'案例:某零售集团全渠道数字化转型',
font_size=TITLE_SIZE, font_color=WHITE, bold=True,
anchor=MSO_ANCHOR.MIDDLE)
add_hline(s, LM, Inches(0.75), CONTENT_W, BLACK, Pt(0.5))
# ── Left: SAR text (55%) ──
left_w = Inches(6.5)
sections = [
('背景', '该集团拥有3000+门店,线上渠道占比仅12%,客户数据分散在15个独立系统中'),
('方案', '分三阶段推进:1) 统一数据中台建设 2) 全渠道会员体系打通 3) 智能选品与补货系统上线'),
('成果', '线上渠道占比提升至38%,会员贡献收入占比从45%增至72%,库存周转提升40%'),
]
accents_sar = [ACCENT_BLUE, ACCENT_GREEN, ACCENT_ORANGE]
ty = Inches(1.0)
for i, (label, text) in enumerate(sections):
# Section accent bar
add_rect(s, LM, ty, Inches(0.06), Inches(1.0), accents_sar[i])
add_text(s, LM + Inches(0.2), ty, Inches(1.2), Inches(0.3),
label, font_size=Pt(16), font_color=accents_sar[i], bold=True)
add_text(s, LM + Inches(0.2), ty + Inches(0.35), left_w - Inches(0.3), Inches(0.65),
text, font_size=BODY_SIZE, font_color=DARK_GRAY)
ty += Inches(1.3)
# ── Right: image + KPIs ──
rx = LM + left_w + Inches(0.3)
rw = CONTENT_W - left_w - Inches(0.3)
add_image_placeholder(s, rx, Inches(1.0), rw, Inches(2.5), '项目实施现场照片')
# KPI boxes
kpis = [('38%', '线上占比'), ('72%', '会员贡献'), ('+40%', '库存周转')]
kpi_w = rw / len(kpis)
for i, (val, label) in enumerate(kpis):
kx = rx + i * kpi_w
add_rect(s, kx, Inches(3.8), kpi_w - Inches(0.1), Inches(1.2), BG_GRAY)
add_text(s, kx, Inches(3.85), kpi_w - Inches(0.1), Inches(0.6),
val, font_size=Pt(28), font_color=NAVY, bold=True,
alignment=PP_ALIGN.CENTER)
add_text(s, kx, Inches(4.45), kpi_w - Inches(0.1), Inches(0.4),
label, font_size=Pt(12), font_color=MED_GRAY,
alignment=PP_ALIGN.CENTER)
add_source(s, 'Source: 项目交付报告,2025年12月')
add_page_number(s, 8, 12)
```
---
#### #46 — Quote with Background Image
**Use case**: Inspirational quote or key insight with a subtle background visual — for keynote-style emphasis slides.
```
┌──────────────────────────────────────────────┐
│ │
│ ┌──────────────────────────┐ │
│ │ IMAGE PLACEHOLDER │ │
│ │ (subtle / blurred) │ │
│ └──────────────────────────┘ │
│ │
│ ────────────────────────────────── │
│ "Quote text in large font" │
│ — Speaker Name, Title │
│ ────────────────────────────────── │
│ │
├──────────────────────────────────────────────┤
│ Source | Page N/Total │
└──────────────────────────────────────────────┘
```
```python
s = prs.slides.add_slide(prs.slide_layouts[6])
# ── Background image (top half) ──
add_image_placeholder(s, Inches(0), Inches(0), Inches(13.333), Inches(3.2),
'主题相关背景图片(建议使用浅色/模糊效果)')
# ── White overlay for text area ──
add_rect(s, Inches(0), Inches(3.2), Inches(13.333), Inches(4.3), WHITE)
# ── Decorative lines ──
line_x = LM + Inches(1.0)
line_w = CONTENT_W - Inches(2.0)
add_hline(s, line_x, Inches(3.6), line_w, NAVY, Pt(1.0))
# ── Quote text ──
add_text(s, LM + Inches(1.5), Inches(3.8), CONTENT_W - Inches(3.0), Inches(1.4),
'"最危险的不是变化本身,而是用昨天的逻辑做明天的决策"',
font_size=Pt(24), font_color=NAVY, bold=True,
alignment=PP_ALIGN.CENTER, anchor=MSO_ANCHOR.MIDDLE)
# ── Attribution ──
add_text(s, LM + Inches(1.5), Inches(5.2), CONTENT_W - Inches(3.0), Inches(0.4),
'— Peter Drucker,管理学大师',
font_size=Pt(14), font_color=MED_GRAY,
alignment=PP_ALIGN.CENTER)
# ── Bottom decorative line ──
add_hline(s, line_x, Inches(5.7), line_w, NAVY, Pt(1.0))
add_source(s, 'Source: 《管理的实践》')
add_page_number(s, 9, 12)
```
---
#### #47 — Goals / Targets with Illustration
**Use case**: Strategic goals or OKRs with a supporting illustration — for goal-setting and planning slides.
```
┌──────────────────────────────────────────────┐
│ [Action Title — full width, NAVY bg] │
├───────────────────────┬──────────────────────┤
│ │ │
│ ○ Goal 1 — desc │ ┌──────────────┐ │
│ ○ Goal 2 — desc │ │ IMAGE │ │
│ ○ Goal 3 — desc │ │ PLACEHOLDER │ │
│ ○ Goal 4 — desc │ └──────────────┘ │
│ │ │
│ Summary metric │ │
├───────────────────────┴──────────────────────┤
│ Source | Page N/Total │
└──────────────────────────────────────────────┘
```
```python
s = prs.slides.add_slide(prs.slide_layouts[6])
# ── Title bar ──
add_rect(s, Inches(0), Inches(0), Inches(13.333), Inches(0.75), NAVY)
add_text(s, LM, Inches(0), CONTENT_W, Inches(0.75),
'2026年下半年四大战略目标',
font_size=TITLE_SIZE, font_color=WHITE, bold=True,
anchor=MSO_ANCHOR.MIDDLE)
add_hline(s, LM, Inches(0.75), CONTENT_W, BLACK, Pt(0.5))
# ── Left: goals list (55%) ──
left_w = Inches(6.5)
goals = [
('营收增长', '实现全年营收目标120亿元,同比增长25%', ACCENT_BLUE),
('市场扩张', '新进入3个海外市场,海外收入占比提升至15%', ACCENT_GREEN),
('产品升级', 'AI功能覆盖率从40%提升至80%,用户NPS达到65+', ACCENT_ORANGE),
('组织发展', '关键岗位内部晋升率达60%,员工满意度≥4.2/5', ACCENT_RED),
]
ty = Inches(1.1)
for i, (title, desc, color) in enumerate(goals):
# Accent bar
add_rect(s, LM, ty, Inches(0.06), Inches(0.8), color)
add_oval(s, LM + Inches(0.25), ty + Inches(0.15), str(i + 1), bg=color)
add_text(s, LM + Inches(0.8), ty, left_w - Inches(1.0), Inches(0.35),
title, font_size=Pt(16), font_color=color, bold=True)
add_text(s, LM + Inches(0.8), ty + Inches(0.35), left_w - Inches(1.0), Inches(0.45),
desc, font_size=BODY_SIZE, font_color=DARK_GRAY)
ty += Inches(1.05)
# ── Right: illustration ──
rx = LM + left_w + Inches(0.3)
rw = CONTENT_W - left_w - Inches(0.3)
add_image_placeholder(s, rx, Inches(1.1), rw, Inches(4.2), '战略目标示意图 / 增长路线图')
add_source(s, 'Source: 2026年战略规划文件')
add_page_number(s, 10, 12)
```
---
### Category J: Advanced Data Visualization
> **Drawing Convention**: All charts are drawn with `add_rect()` and `add_oval()` — no matplotlib, no chart objects, no connectors. This ensures zero file corruption and full style control.
---
#### #48 — Donut Chart
**Use case**: Part-of-whole composition — market share, budget allocation, sentiment distribution. Up to 5 segments.
```
┌──────────────────────────────────────────────┐
│ [Action Title — full width, NAVY bg] │
├───────────────────────┬──────────────────────┤
│ │ │
│ ┌───────────┐ │ ■ Segment A 45% │
│ │ DONUT │ │ ■ Segment B 28% │
│ │ (rects) │ │ ■ Segment C 15% │
│ │ CENTER% │ │ ■ Segment D 12% │
│ └───────────┘ │ │
│ │ Insight text... │
├───────────────────────┴──────────────────────┤
│ Source | Page N/Total │
└──────────────────────────────────────────────┘
```
```python
import math
s = prs.slides.add_slide(prs.slide_layouts[6])
# ── Title bar ──
add_rect(s, Inches(0), Inches(0), Inches(13.333), Inches(0.75), NAVY)
add_text(s, LM, Inches(0), CONTENT_W, Inches(0.75),
'2026年上半年营收渠道构成',
font_size=TITLE_SIZE, font_color=WHITE, bold=True,
anchor=MSO_ANCHOR.MIDDLE)
add_hline(s, LM, Inches(0.75), CONTENT_W, BLACK, Pt(0.5))
# ── Donut chart (approximated with arc-segments as colored rect blocks) ──
# For a visual donut: draw concentric ring using small rectangular blocks
cx, cy = LM + Inches(3.0), Inches(3.2) # center
outer_r = Inches(1.6)
inner_r = Inches(0.9)
segments = [
(0.45, NAVY, '线上直营'),
(0.28, ACCENT_BLUE, '经销商'),
(0.15, ACCENT_GREEN, '企业客户'),
(0.12, ACCENT_ORANGE, '其他'),
]
# Draw donut segments as small arc-approximating rectangles
block_size = Inches(0.08)
start_angle = 0
for pct, color, label in segments:
sweep = pct * 360
for deg in range(int(start_angle), int(start_angle + sweep), 3):
rad = math.radians(deg)
for r in [outer_r, outer_r - Inches(0.2), outer_r - Inches(0.4)]:
if r < inner_r:
continue
bx = cx + int(r * math.cos(rad)) - block_size // 2
by = cy - int(r * math.sin(rad)) - block_size // 2
add_rect(s, bx, by, block_size, block_size, color)
start_angle += sweep
# Center label
add_text(s, cx - Inches(0.7), cy - Inches(0.3), Inches(1.4), Inches(0.6),
'¥8.5亿', font_size=Pt(24), font_color=NAVY, bold=True,
alignment=PP_ALIGN.CENTER, anchor=MSO_ANCHOR.MIDDLE)
add_text(s, cx - Inches(0.7), cy + Inches(0.2), Inches(1.4), Inches(0.3),
'总营收', font_size=Pt(12), font_color=MED_GRAY,
alignment=PP_ALIGN.CENTER)
# ── Legend (right side) ──
legend_x = LM + Inches(7.0)
legend_y = Inches(1.5)
colors = [NAVY, ACCENT_BLUE, ACCENT_GREEN, ACCENT_ORANGE]
for i, (pct, color, label) in enumerate(segments):
ly = legend_y + i * Inches(0.8)
add_rect(s, legend_x, ly + Inches(0.05), Inches(0.3), Inches(0.3), color)
add_text(s, legend_x + Inches(0.45), ly, Inches(3.0), Inches(0.4),
f'{label} {int(pct*100)}%',
font_size=Pt(16), font_color=DARK_GRAY, bold=True)
# ── Insight box ──
add_rect(s, legend_x, Inches(5.0), Inches(4.5), Inches(0.8), BG_GRAY)
add_text(s, legend_x + Inches(0.2), Inches(5.0), Inches(4.1), Inches(0.8),
'线上直营渠道占比同比提升12个百分点,预计下半年将突破50%',
font_size=BODY_SIZE, font_color=NAVY, bold=True, anchor=MSO_ANCHOR.MIDDLE)
add_source(s, 'Source: 财务报告,2026年H1')
add_page_number(s, 5, 12)
```
---
#### #49 — Waterfall Chart
**Use case**: Bridge from starting value to ending value showing incremental changes — revenue bridge, profit walk, budget variance.
```
┌──────────────────────────────────────────────┐
│ [Action Title — full width, NAVY bg] │
├──────────────────────────────────────────────┤
│ ┌──┐ │
│ Start │ │ +A -B +C -D +E ┌──┐ End │
│ │ │ ┌┐ ┌┐ ┌┐ ┌┐ ┌┐ │ │ │
│ │ │ ││ ││ ││ ││ ││ │ │ │
│ │ │ └┘──└┘──└┘──└┘──└┘ │ │ │
│ └──┘ └──┘ │
│ Takeaway text... │
├──────────────────────────────────────────────┤
│ Source | Page N/Total │
└──────────────────────────────────────────────┘
```
```python
s = prs.slides.add_slide(prs.slide_layouts[6])
# ── Title bar ──
add_rect(s, Inches(0), Inches(0), Inches(13.333), Inches(0.75), NAVY)
add_text(s, LM, Inches(0), CONTENT_W, Inches(0.75),
'2026年H1利润增长桥接分析(百万元)',
font_size=TITLE_SIZE, font_color=WHITE, bold=True,
anchor=MSO_ANCHOR.MIDDLE)
add_hline(s, LM, Inches(0.75), CONTENT_W, BLACK, Pt(0.5))
# ── Waterfall data ──
items = [
('2025 H2\n利润', 850, 'base'),
('营收\n增长', 220, 'up'),
('成本\n优化', 85, 'up'),
('人力\n支出', -120, 'down'),
('营销\n投入', -65, 'down'),
('新业务\n投资', -40, 'down'),
('2026 H1\n利润', 930, 'base'),
]
chart_left = LM + Inches(0.3)
chart_bottom = Inches(5.0)
chart_top = Inches(1.2)
chart_h = chart_bottom - chart_top
max_val = 1000 # Y-axis max
bar_w = Inches(1.2)
gap = Inches(0.4)
running = 0
for i, (label, val, typ) in enumerate(items):
bx = chart_left + i * (bar_w + gap)
if typ == 'base':
# Full bar from bottom
bar_h = int(chart_h * val / max_val)
bar_top = chart_bottom - bar_h
color = NAVY
add_rect(s, bx, bar_top, bar_w, bar_h, color)
running = val
elif typ == 'up':
bar_h = int(chart_h * val / max_val)
bar_top = chart_bottom - int(chart_h * running / max_val) - bar_h
color = ACCENT_GREEN
add_rect(s, bx, bar_top, bar_w, bar_h, color)
running += val
else: # down
bar_h = int(chart_h * abs(val) / max_val)
bar_top = chart_bottom - int(chart_h * running / max_val)
color = ACCENT_RED
add_rect(s, bx, bar_top, bar_w, bar_h, color)
running += val
# Value label above bar
val_str = f'+{val}' if val > 0 and typ != 'base' else str(val)
add_text(s, bx, bar_top - Inches(0.35), bar_w, Inches(0.3),
val_str, font_size=Pt(14), font_color=DARK_GRAY, bold=True,
alignment=PP_ALIGN.CENTER)
# Category label below
add_text(s, bx, chart_bottom + Inches(0.05), bar_w, Inches(0.5),
label, font_size=Pt(11), font_color=MED_GRAY,
alignment=PP_ALIGN.CENTER)
# ── Baseline axis ──
add_hline(s, chart_left, chart_bottom, Inches(11.5), LINE_GRAY, Pt(0.5))
# ── Takeaway ──
add_rect(s, LM, Inches(6.0), CONTENT_W, Inches(0.7), BG_GRAY)
add_text(s, LM + Inches(0.3), Inches(6.0), CONTENT_W - Inches(0.6), Inches(0.7),
'关键发现:营收增长和成本优化共贡献305M增量,人力和营销支出消耗185M,净利润增长9.4%',
font_size=BODY_SIZE, font_color=NAVY, bold=True, anchor=MSO_ANCHOR.MIDDLE)
add_source(s, 'Source: 财务管理部,2026年6月')
add_page_number(s, 6, 12)
```
---
#### #50 — Line / Trend Chart
**Use case**: Time-series trends — revenue growth, user count, market share over time. Supports 1-4 series.
```
┌──────────────────────────────────────────────┐
│ [Action Title — full width, NAVY bg] │
├──────────────────────────────────────────────┤
│ Y ─ │
│ ══════ Series A (black, bold) ═══ LabelA │
│ ══════ Series B (blue) ══════════ LabelB │
│ ══════ Series C (green) ═════════ LabelC │
│ 0 ────────────────────────────────── │
│ Q1'24 Q2'24 Q3'24 Q4'24 Q1'25 │
│ Takeaway text... │
├──────────────────────────────────────────────┤
│ Source | Page N/Total │
└──────────────────────────────────────────────┘
```
```python
s = prs.slides.add_slide(prs.slide_layouts[6])
# ── Title bar ──
add_rect(s, Inches(0), Inches(0), Inches(13.333), Inches(0.75), NAVY)
add_text(s, LM, Inches(0), CONTENT_W, Inches(0.75),
'核心产品月活用户趋势(2024Q1 - 2026Q1)',
font_size=TITLE_SIZE, font_color=WHITE, bold=True,
anchor=MSO_ANCHOR.MIDDLE)
add_hline(s, LM, Inches(0.75), CONTENT_W, BLACK, Pt(0.5))
# ── Chart area setup ──
chart_l = LM + Inches(0.8)
chart_r = LM + CONTENT_W - Inches(1.5)
chart_w = chart_r - chart_l
chart_top = Inches(1.3)
chart_bot = Inches(5.0)
chart_h = chart_bot - chart_top
# Y-axis labels
y_labels = ['0', '500万', '1000万', '1500万', '2000万']
for i, yl in enumerate(y_labels):
yy = chart_bot - int(chart_h * i / (len(y_labels) - 1))
add_text(s, LM, yy - Inches(0.12), Inches(0.7), Inches(0.24),
yl, font_size=Pt(9), font_color=MED_GRAY, alignment=PP_ALIGN.RIGHT)
if i > 0:
add_hline(s, chart_l, yy, chart_w, RGBColor(0xE8, 0xE8, 0xE8), Pt(0.25))
# X-axis labels
x_labels = ['Q1\'24', 'Q2\'24', 'Q3\'24', 'Q4\'24', 'Q1\'25', 'Q2\'25',
'Q3\'25', 'Q4\'25', 'Q1\'26']
n_pts = len(x_labels)
for i, xl in enumerate(x_labels):
xx = chart_l + int(chart_w * i / (n_pts - 1))
add_text(s, xx - Inches(0.3), chart_bot + Inches(0.05), Inches(0.6), Inches(0.25),
xl, font_size=Pt(9), font_color=MED_GRAY, alignment=PP_ALIGN.CENTER)
# ── Data series ──
# Series values as fraction of max (2000万)
series = [
('产品A', [0.35,0.40,0.48,0.55,0.62,0.70,0.78,0.85,0.92], BLACK, Pt(3)),
('产品B', [0.20,0.22,0.25,0.30,0.35,0.38,0.42,0.45,0.50], ACCENT_BLUE, Pt(2)),
('产品C', [0.10,0.12,0.13,0.15,0.16,0.18,0.20,0.22,0.25], ACCENT_GREEN, Pt(2)),
]
for name, values, color, thickness in series:
# Draw line segments as thin rects connecting data points
for j in range(len(values) - 1):
x1 = chart_l + int(chart_w * j / (n_pts - 1))
y1 = chart_bot - int(chart_h * values[j])
x2 = chart_l + int(chart_w * (j + 1) / (n_pts - 1))
y2 = chart_bot - int(chart_h * values[j + 1])
# Approximate line with thin rect (horizontal segment)
seg_w = x2 - x1
seg_y = min(y1, y2)
seg_h = max(abs(y2 - y1), int(thickness))
add_rect(s, x1, seg_y, seg_w, seg_h, color)
# Data points as small squares
for j, v in enumerate(values):
px = chart_l + int(chart_w * j / (n_pts - 1))
py = chart_bot - int(chart_h * v)
dot_sz = Inches(0.08)
add_rect(s, px - dot_sz // 2, py - dot_sz // 2, dot_sz, dot_sz, color)
# End label
last_x = chart_r + Inches(0.1)
last_y = chart_bot - int(chart_h * values[-1])
add_text(s, last_x, last_y - Inches(0.12), Inches(1.2), Inches(0.24),
name, font_size=Pt(11), font_color=color, bold=True)
# ── Baseline axis ──
add_hline(s, chart_l, chart_bot, chart_w, BLACK, Pt(0.5))
# ── Takeaway ──
add_rect(s, LM, Inches(5.5), CONTENT_W, Inches(0.7), BG_GRAY)
add_text(s, LM + Inches(0.3), Inches(5.5), CONTENT_W - Inches(0.6), Inches(0.7),
'关键趋势:产品A保持强劲增长势头,MAU有望在Q2\'26突破2000万大关',
font_size=BODY_SIZE, font_color=NAVY, bold=True, anchor=MSO_ANCHOR.MIDDLE)
add_source(s, 'Source: 产品数据平台,2026年3月')
add_page_number(s, 4, 12)
```
---
#### #51 — Pareto Chart (Bar + Cumulative Line)
**Use case**: 80/20 analysis — identifying the vital few causes/items that account for most of the impact.
```
┌──────────────────────────────────────────────┐
│ [Action Title — full width, NAVY bg] │
├──────────────────────────────────────────────┤
│ Y₁ ─ ─ Y₂ │
│ ┌──┐ ----100% │
│ │ │┌──┐ ------ │
│ │ ││ │┌──┐ ------ │
│ │ ││ ││ │┌──┐- │
│ │ ││ ││ ││ │┌──┐┌──┐ │
│ └──┘└──┘└──┘└──┘└──┘└──┘ 80% line │
│ Takeaway: Top 3 items account for 78% │
├──────────────────────────────────────────────┤
│ Source | Page N/Total │
└──────────────────────────────────────────────┘
```
```python
s = prs.slides.add_slide(prs.slide_layouts[6])
# ── Title bar ──
add_rect(s, Inches(0), Inches(0), Inches(13.333), Inches(0.75), NAVY)
add_text(s, LM, Inches(0), CONTENT_W, Inches(0.75),
'客户投诉根因帕累托分析',
font_size=TITLE_SIZE, font_color=WHITE, bold=True,
anchor=MSO_ANCHOR.MIDDLE)
add_hline(s, LM, Inches(0.75), CONTENT_W, BLACK, Pt(0.5))
# ── Data ──
items = [
('系统响应慢', 35), ('功能缺失', 25), ('操作复杂', 18),
('数据不准', 10), ('界面难看', 7), ('其他', 5),
]
total = sum(v for _, v in items)
chart_l = LM + Inches(0.5)
chart_bot = Inches(5.2)
chart_top = Inches(1.3)
chart_h = chart_bot - chart_top
bar_w = Inches(1.5)
gap = Inches(0.3)
cumulative = 0
cum_points = []
for i, (label, val) in enumerate(items):
bx = chart_l + i * (bar_w + gap)
pct = val / total
bar_h = int(chart_h * pct)
bar_top = chart_bot - bar_h
# Bar
add_rect(s, bx, bar_top, bar_w, bar_h, NAVY if i < 3 else LINE_GRAY)
# Value label
add_text(s, bx, bar_top - Inches(0.3), bar_w, Inches(0.25),
f'{val}件 ({int(pct*100)}%)', font_size=Pt(11),
font_color=DARK_GRAY, bold=(i < 3), alignment=PP_ALIGN.CENTER)
# X-axis label
add_text(s, bx, chart_bot + Inches(0.05), bar_w, Inches(0.4),
label, font_size=Pt(11), font_color=MED_GRAY, alignment=PP_ALIGN.CENTER)
# Cumulative point
cumulative += pct
cx_pt = bx + bar_w // 2
cy_pt = chart_bot - int(chart_h * cumulative)
cum_points.append((cx_pt, cy_pt))
# Cumulative dot
dot = Inches(0.1)
add_rect(s, cx_pt - dot // 2, cy_pt - dot // 2, dot, dot, ACCENT_ORANGE)
# Connect cumulative dots with horizontal segments
for j in range(len(cum_points) - 1):
x1, y1 = cum_points[j]
x2, y2 = cum_points[j + 1]
seg_w = x2 - x1
seg_y = min(y1, y2)
seg_h = max(abs(y2 - y1), Pt(2))
add_rect(s, x1, seg_y, seg_w, seg_h, ACCENT_ORANGE)
# 80% threshold line (dashed approximation with small rects)
threshold_y = chart_bot - int(chart_h * 0.80)
dash_len = Inches(0.2)
total_w = len(items) * (bar_w + gap)
for d in range(0, int(total_w), int(dash_len * 2)):
add_rect(s, chart_l + d, threshold_y, dash_len, Pt(1), ACCENT_RED)
add_text(s, chart_l + total_w + Inches(0.1), threshold_y - Inches(0.12),
Inches(0.6), Inches(0.24), '80%',
font_size=Pt(10), font_color=ACCENT_RED, bold=True)
# ── Baseline axis ──
add_hline(s, chart_l, chart_bot, total_w, BLACK, Pt(0.5))
# ── Takeaway ──
add_rect(s, LM, Inches(5.7), CONTENT_W, Inches(0.7), BG_GRAY)
add_text(s, LM + Inches(0.3), Inches(5.7), CONTENT_W - Inches(0.6), Inches(0.7),
'分析:前3项根因占全部投诉的78%,优先解决"系统响应慢"可消除35%的投诉量',
font_size=BODY_SIZE, font_color=NAVY, bold=True, anchor=MSO_ANCHOR.MIDDLE)
add_source(s, 'Source: 客服工单系统,2026年1-6月')
add_page_number(s, 7, 12)
```
---
#### #52 — Progress Bars / KPI Tracker
**Use case**: Multiple KPIs with target vs actual progress — project health, OKR tracking, sales pipeline.
```
┌──────────────────────────────────────────────┐
│ [Action Title — full width, NAVY bg] │
├──────────────────────────────────────────────┤
│ KPI Name Actual / Target Status │
│ ════════████████████░░░░░░░░ 78% ● On │
│ ════════████████░░░░░░░░░░░░ 52% ● Risk │
│ ════════██████████████████░░ 92% ● On │
│ ════════████████████████░░░ 85% ● On │
│ ════════█████░░░░░░░░░░░░░░ 38% ● Off │
│ Summary / insight text │
├──────────────────────────────────────────────┤
│ Source | Page N/Total │
└──────────────────────────────────────────────┘
```
```python
s = prs.slides.add_slide(prs.slide_layouts[6])
# ── Title bar ──
add_rect(s, Inches(0), Inches(0), Inches(13.333), Inches(0.75), NAVY)
add_text(s, LM, Inches(0), CONTENT_W, Inches(0.75),
'2026年Q2 OKR达成进度追踪',
font_size=TITLE_SIZE, font_color=WHITE, bold=True,
anchor=MSO_ANCHOR.MIDDLE)
add_hline(s, LM, Inches(0.75), CONTENT_W, BLACK, Pt(0.5))
# ── Header row ──
hy = Inches(1.0)
add_text(s, LM, hy, Inches(3.5), Inches(0.35),
'KPI 指标', font_size=Pt(12), font_color=MED_GRAY, bold=True)
add_text(s, LM + Inches(3.5), hy, Inches(6.0), Inches(0.35),
'进度', font_size=Pt(12), font_color=MED_GRAY, bold=True)
add_text(s, LM + Inches(9.5), hy, Inches(1.2), Inches(0.35),
'达成率', font_size=Pt(12), font_color=MED_GRAY, bold=True,
alignment=PP_ALIGN.CENTER)
add_text(s, LM + Inches(10.7), hy, Inches(1.0), Inches(0.35),
'状态', font_size=Pt(12), font_color=MED_GRAY, bold=True,
alignment=PP_ALIGN.CENTER)
add_hline(s, LM, hy + Inches(0.35), CONTENT_W, BLACK, Pt(0.75))
# ── KPI rows ──
kpis = [
('营收目标', 0.78, '¥9.4亿/¥12亿', 'on'),
('新客获取', 0.52, '2.6万/5万', 'risk'),
('客户留存率', 0.92, '92%/95%', 'on'),
('产品NPS', 0.85, '59/70', 'on'),
('成本控制', 0.38, '¥3.8亿/¥3.2亿', 'off'),
]
bar_x = LM + Inches(3.5)
bar_max_w = Inches(5.8)
bar_h = Inches(0.25)
row_h = Inches(0.7)
status_colors = {'on': ACCENT_GREEN, 'risk': ACCENT_ORANGE, 'off': ACCENT_RED}
status_labels = {'on': '达标', 'risk': '风险', 'off': '滞后'}
for i, (name, pct, detail, status) in enumerate(kpis):
ry = Inches(1.6) + i * row_h
# KPI name
add_text(s, LM, ry, Inches(3.3), row_h,
name, font_size=BODY_SIZE, font_color=DARK_GRAY, bold=True,
anchor=MSO_ANCHOR.MIDDLE)
# Progress bar background
add_rect(s, bar_x, ry + (row_h - bar_h) / 2, bar_max_w, bar_h, BG_GRAY)
# Progress bar fill
fill_w = int(bar_max_w * min(pct, 1.0))
fill_color = status_colors[status]
add_rect(s, bar_x, ry + (row_h - bar_h) / 2, fill_w, bar_h, fill_color)
# Percentage
add_text(s, LM + Inches(9.5), ry, Inches(1.2), row_h,
f'{int(pct*100)}%', font_size=Pt(16), font_color=DARK_GRAY, bold=True,
alignment=PP_ALIGN.CENTER, anchor=MSO_ANCHOR.MIDDLE)
# Status indicator
sc = status_colors[status]
dot_sz = Inches(0.15)
add_rect(s, LM + Inches(10.8), ry + (row_h - dot_sz) / 2, dot_sz, dot_sz, sc)
add_text(s, LM + Inches(11.0), ry, Inches(0.7), row_h,
status_labels[status], font_size=Pt(11), font_color=sc,
anchor=MSO_ANCHOR.MIDDLE)
# Row separator
if i < len(kpis) - 1:
add_hline(s, LM, ry + row_h, CONTENT_W, LINE_GRAY, Pt(0.25))
# ── Summary ──
add_rect(s, LM, Inches(5.5), CONTENT_W, Inches(0.8), BG_GRAY)
add_text(s, LM + Inches(0.3), Inches(5.5), Inches(1.5), Inches(0.8),
'总结', font_size=BODY_SIZE, font_color=NAVY, bold=True,
anchor=MSO_ANCHOR.MIDDLE)
add_text(s, LM + Inches(2.0), Inches(5.5), CONTENT_W - Inches(2.3), Inches(0.8),
'5项KPI中3项达标,"新客获取"和"成本控制"需重点关注,建议Q3调整预算分配',
font_size=BODY_SIZE, font_color=DARK_GRAY, anchor=MSO_ANCHOR.MIDDLE)
add_source(s, 'Source: OKR管理平台,2026年6月')
add_page_number(s, 8, 12)
```
---
#### #53 — Bubble / Scatter Plot
**Use case**: Two-variable comparison with size encoding — market attractiveness vs competitive position, impact vs effort.
```
┌──────────────────────────────────────────────┐
│ [Action Title — full width, NAVY bg] │
├──────────────────────────────────────────────┤
│ Y ─ High │
│ ●(large) ○(med) │
│ ●(small) ○(large) │
│ ○(med) ●(med) │
│ 0 ─ Low ──────────────────── High ─ X │
│ Legend: ● Category A ○ Category B │
│ Takeaway text... │
├──────────────────────────────────────────────┤
│ Source | Page N/Total │
└──────────────────────────────────────────────┘
```
```python
s = prs.slides.add_slide(prs.slide_layouts[6])
# ── Title bar ──
add_rect(s, Inches(0), Inches(0), Inches(13.333), Inches(0.75), NAVY)
add_text(s, LM, Inches(0), CONTENT_W, Inches(0.75),
'产品组合分析:市场吸引力 vs 竞争地位',
font_size=TITLE_SIZE, font_color=WHITE, bold=True,
anchor=MSO_ANCHOR.MIDDLE)
add_hline(s, LM, Inches(0.75), CONTENT_W, BLACK, Pt(0.5))
# ── Axes ──
chart_l = LM + Inches(1.2)
chart_b = Inches(5.0)
chart_t = Inches(1.3)
chart_w = Inches(9.0)
chart_h = chart_b - chart_t
# X-axis
add_hline(s, chart_l, chart_b, chart_w, BLACK, Pt(0.5))
add_text(s, chart_l + chart_w // 2 - Inches(1.0), chart_b + Inches(0.15),
Inches(2.0), Inches(0.3), '竞争地位 →',
font_size=Pt(11), font_color=MED_GRAY, alignment=PP_ALIGN.CENTER)
# Y-axis (vertical rect)
add_rect(s, chart_l, chart_t, Pt(0.5), chart_h, BLACK)
add_text(s, LM, chart_t + chart_h // 2 - Inches(0.5), Inches(1.0), Inches(1.0),
'市\n场\n吸\n引\n力\n↑', font_size=Pt(11), font_color=MED_GRAY,
alignment=PP_ALIGN.CENTER, anchor=MSO_ANCHOR.MIDDLE)
# ── Bubbles (oval shapes with size encoding) ──
# (x_pct, y_pct, size_inches, label, color)
bubbles = [
(0.75, 0.80, 0.9, '产品A\n¥3.2亿', NAVY),
(0.55, 0.60, 0.65, '产品B\n¥1.8亿', ACCENT_BLUE),
(0.30, 0.75, 0.5, '产品C\n¥0.9亿', ACCENT_GREEN),
(0.80, 0.35, 0.55, '产品D\n¥1.2亿', ACCENT_ORANGE),
(0.20, 0.25, 0.35, '产品E\n¥0.4亿', LINE_GRAY),
(0.45, 0.45, 0.7, '产品F\n¥2.1亿', ACCENT_BLUE),
]
for xp, yp, sz, label, color in bubbles:
bx = chart_l + int(chart_w * xp) - Inches(sz / 2)
by = chart_b - int(chart_h * yp) - Inches(sz / 2)
oval = s.shapes.add_shape(MSO_SHAPE.OVAL, bx, by, Inches(sz), Inches(sz))
oval.fill.solid()
oval.fill.fore_color.rgb = color
oval.line.fill.background()
_clean_shape(oval)
# Set 40% transparency
fill_elem = oval._element.find(qn('p:spPr')).find(qn('a:solidFill'))
if fill_elem is not None:
srgb = fill_elem.find(qn('a:srgbClr'))
if srgb is not None:
alpha = srgb.makeelement(qn('a:alpha'), {'val': '60000'})
srgb.append(alpha)
# Label inside bubble
add_text(s, bx, by + Inches(sz * 0.2), Inches(sz), Inches(sz * 0.6),
label, font_size=Pt(9), font_color=WHITE, bold=True,
alignment=PP_ALIGN.CENTER, anchor=MSO_ANCHOR.MIDDLE)
# ── Quadrant lines (dashed) ──
mid_x = chart_l + chart_w // 2
mid_y = chart_t + chart_h // 2
dash = Inches(0.15)
for d in range(0, int(chart_w), int(dash * 2)):
add_rect(s, chart_l + d, mid_y, dash, Pt(0.5), RGBColor(0xDD, 0xDD, 0xDD))
for d in range(0, int(chart_h), int(dash * 2)):
add_rect(s, mid_x, chart_t + d, Pt(0.5), dash, RGBColor(0xDD, 0xDD, 0xDD))
# ── Takeaway ──
add_rect(s, LM, Inches(5.5), CONTENT_W, Inches(0.7), BG_GRAY)
add_text(s, LM + Inches(0.3), Inches(5.5), CONTENT_W - Inches(0.6), Inches(0.7),
'建议:优先投资产品A(高吸引力+强竞争力),观察产品F的增长潜力,逐步退出产品E',
font_size=BODY_SIZE, font_color=NAVY, bold=True, anchor=MSO_ANCHOR.MIDDLE)
add_source(s, 'Source: 战略规划部,2026年Q1')
add_page_number(s, 9, 12)
```
---
#### #54 — Risk / Heat Matrix
**Use case**: Risk assessment — impact vs likelihood grid, with color-coded cells. Classic consulting risk register visualization.
```
┌──────────────────────────────────────────────┐
│ [Action Title — full width, NAVY bg] │
├──────────────────────────────────────────────┤
│ Low Impact Med Impact High Impact│
│ High ■ Yellow ■ Orange ■ Red │
│ Prob "Risk C" "Risk A" "Risk D" │
│ Med ■ Green ■ Yellow ■ Orange │
│ Prob "Risk F" "Risk B" "Risk E" │
│ Low ■ Green ■ Green ■ Yellow │
│ Prob "Risk G" │
│ Action items / mitigation plan │
├──────────────────────────────────────────────┤
│ Source | Page N/Total │
└──────────────────────────────────────────────┘
```
```python
s = prs.slides.add_slide(prs.slide_layouts[6])
# ── Title bar ──
add_rect(s, Inches(0), Inches(0), Inches(13.333), Inches(0.75), NAVY)
add_text(s, LM, Inches(0), CONTENT_W, Inches(0.75),
'项目风险评估矩阵',
font_size=TITLE_SIZE, font_color=WHITE, bold=True,
anchor=MSO_ANCHOR.MIDDLE)
add_hline(s, LM, Inches(0.75), CONTENT_W, BLACK, Pt(0.5))
# ── Matrix setup ──
grid_l = LM + Inches(1.8)
grid_t = Inches(1.3)
cell_w = Inches(3.0)
cell_h = Inches(1.1)
rows = 3 # High, Medium, Low probability
cols = 3 # Low, Medium, High impact
# Color coding: [row][col] where row 0 = High prob, col 0 = Low impact
heat_colors = [
[ACCENT_ORANGE, ACCENT_RED, ACCENT_RED], # High prob
[ACCENT_GREEN, ACCENT_ORANGE, ACCENT_RED], # Med prob
[ACCENT_GREEN, ACCENT_GREEN, ACCENT_ORANGE], # Low prob
]
# Semi-transparent lighter versions for background
light_colors = [
[LIGHT_ORANGE, LIGHT_RED, LIGHT_RED],
[LIGHT_GREEN, LIGHT_ORANGE, LIGHT_RED],
[LIGHT_GREEN, LIGHT_GREEN, LIGHT_ORANGE],
]
# Risks placed in cells: (row, col, name)
risks = [
(0, 1, '数据泄露'), (0, 2, '核心人员流失'),
(1, 0, '供应商延迟'), (1, 1, '预算超支'), (1, 2, '法规变更'),
(2, 2, '汇率波动'),
]
# Y-axis labels
y_labels = ['高概率', '中概率', '低概率']
for r in range(rows):
add_text(s, LM, grid_t + r * cell_h, Inches(1.6), cell_h,
y_labels[r], font_size=Pt(13), font_color=DARK_GRAY, bold=True,
alignment=PP_ALIGN.RIGHT, anchor=MSO_ANCHOR.MIDDLE)
# X-axis labels
x_labels = ['低影响', '中影响', '高影响']
for c in range(cols):
add_text(s, grid_l + c * cell_w, grid_t - Inches(0.35), cell_w, Inches(0.3),
x_labels[c], font_size=Pt(13), font_color=DARK_GRAY, bold=True,
alignment=PP_ALIGN.CENTER)
# Draw grid cells
for r in range(rows):
for c in range(cols):
cx = grid_l + c * cell_w
cy = grid_t + r * cell_h
add_rect(s, cx, cy, cell_w - Inches(0.05), cell_h - Inches(0.05),
light_colors[r][c])
# Color indicator dot
add_rect(s, cx + Inches(0.1), cy + Inches(0.1), Inches(0.2), Inches(0.2),
heat_colors[r][c])
# Place risk labels
for r, c, name in risks:
cx = grid_l + c * cell_w
cy = grid_t + r * cell_h
add_text(s, cx + Inches(0.4), cy + Inches(0.25), cell_w - Inches(0.6), Inches(0.6),
name, font_size=Pt(13), font_color=DARK_GRAY, bold=True,
anchor=MSO_ANCHOR.MIDDLE)
# ── Mitigation summary ──
add_rect(s, LM, Inches(4.8), CONTENT_W, Inches(1.2), BG_GRAY)
add_text(s, LM + Inches(0.3), Inches(4.85), Inches(1.5), Inches(0.3),
'应对措施', font_size=Pt(14), font_color=NAVY, bold=True)
mitigations = [
'• 红色区域(2项):已制定应急预案,每周评审进展',
'• 橙色区域(3项):指定风险负责人,双周监控',
'• 绿色区域(1项):季度回顾,暂不采取主动措施',
]
add_text(s, LM + Inches(0.3), Inches(5.2), CONTENT_W - Inches(0.6), Inches(0.8),
mitigations, font_size=BODY_SIZE, font_color=DARK_GRAY, line_spacing=Pt(4))
add_source(s, 'Source: 项目管理办公室,2026年Q2')
add_page_number(s, 10, 12)
```
---
#### #55 — Gauge / Dial Chart
**Use case**: Single KPI health indicator — customer satisfaction, system uptime, quality score. Visual "speedometer" metaphor.
```
┌──────────────────────────────────────────────┐
│ [Action Title — full width, NAVY bg] │
├──────────────────────────────────────────────┤
│ │
│ ┌────────────────────────────┐ │
│ │ ╭───────────────╮ │ │
│ │ Low│ ■■■■ Med ■■■■ │High │ │
│ │ ╰───────────────╯ │ │
│ │ ▲ 78 / 100 │ │
│ └────────────────────────────┘ │
│ Context / benchmark comparison text │
├──────────────────────────────────────────────┤
│ Source | Page N/Total │
└──────────────────────────────────────────────┘
```
```python
import math
s = prs.slides.add_slide(prs.slide_layouts[6])
# ── Title bar ──
add_rect(s, Inches(0), Inches(0), Inches(13.333), Inches(0.75), NAVY)
add_text(s, LM, Inches(0), CONTENT_W, Inches(0.75),
'客户净推荐值(NPS)健康度仪表盘',
font_size=TITLE_SIZE, font_color=WHITE, bold=True,
anchor=MSO_ANCHOR.MIDDLE)
add_hline(s, LM, Inches(0.75), CONTENT_W, BLACK, Pt(0.5))
# ── Gauge: semi-circle arc using small blocks ──
cx = LM + CONTENT_W // 2
cy = Inches(3.8)
radius = Inches(2.2)
block_sz = Inches(0.1)
score = 78 # out of 100
# Draw arc from 180° to 0° (left to right)
for deg in range(0, 181, 2):
rad = math.radians(deg)
bx = cx + int(radius * math.cos(rad)) - block_sz // 2
by = cy - int(radius * math.sin(rad)) - block_sz // 2
# Color zones: 0-40 red, 40-70 orange, 70-100 green
frac = deg / 180.0
if frac < 0.40:
color = ACCENT_RED
elif frac < 0.70:
color = ACCENT_ORANGE
else:
color = ACCENT_GREEN
add_rect(s, bx, by, block_sz, block_sz, color)
# Inner arc (white, to create donut effect)
inner_r = Inches(1.5)
for deg in range(0, 181, 2):
rad = math.radians(deg)
bx = cx + int(inner_r * math.cos(rad)) - block_sz // 2
by = cy - int(inner_r * math.sin(rad)) - block_sz // 2
add_rect(s, bx, by, block_sz, block_sz, WHITE)
# Needle indicator (score = 78 → deg ≈ 78/100 * 180 = 140.4°)
needle_deg = (score / 100.0) * 180
needle_rad = math.radians(needle_deg)
needle_len = Inches(1.8)
nx = cx + int(needle_len * math.cos(needle_rad))
ny = cy - int(needle_len * math.sin(needle_rad))
# Draw needle as thin rect from center to point
add_rect(s, min(cx, nx), min(cy, ny), abs(nx - cx) + Pt(2), abs(ny - cy) + Pt(2), NAVY)
# Center score
add_text(s, cx - Inches(0.8), cy - Inches(0.5), Inches(1.6), Inches(0.6),
str(score), font_size=Pt(44), font_color=NAVY, bold=True,
alignment=PP_ALIGN.CENTER, anchor=MSO_ANCHOR.MIDDLE)
add_text(s, cx - Inches(0.5), cy + Inches(0.1), Inches(1.0), Inches(0.3),
'/ 100', font_size=Pt(14), font_color=MED_GRAY,
alignment=PP_ALIGN.CENTER)
# Zone labels
add_text(s, cx - radius - Inches(0.3), cy + Inches(0.1), Inches(0.8), Inches(0.25),
'差', font_size=Pt(11), font_color=ACCENT_RED, alignment=PP_ALIGN.CENTER)
add_text(s, cx - Inches(0.3), cy - radius - Inches(0.3), Inches(0.6), Inches(0.25),
'良', font_size=Pt(11), font_color=ACCENT_ORANGE, alignment=PP_ALIGN.CENTER)
add_text(s, cx + radius - Inches(0.3), cy + Inches(0.1), Inches(0.8), Inches(0.25),
'优', font_size=Pt(11), font_color=ACCENT_GREEN, alignment=PP_ALIGN.CENTER)
# ── Benchmark context ──
benchmarks = [
('当前 NPS', '78', ACCENT_GREEN),
('行业平均', '52', MED_GRAY),
('去年同期', '65', ACCENT_BLUE),
('目标值', '80', NAVY),
]
bx_start = LM + Inches(0.5)
by_row = Inches(5.0)
bw = Inches(2.5)
for i, (label, val, color) in enumerate(benchmarks):
bx = bx_start + i * bw
add_rect(s, bx, by_row, Inches(0.08), Inches(0.6), color)
add_text(s, bx + Inches(0.2), by_row, bw - Inches(0.3), Inches(0.3),
label, font_size=Pt(12), font_color=MED_GRAY)
add_text(s, bx + Inches(0.2), by_row + Inches(0.3), bw - Inches(0.3), Inches(0.3),
val, font_size=Pt(22), font_color=color, bold=True)
add_source(s, 'Source: 客户体验部 NPS 调研,2026年Q2')
add_page_number(s, 11, 12)
```
---
#### #56 — Harvey Ball Status Table
**Use case**: Multi-criteria evaluation matrix — feature comparison, vendor assessment, capability maturity with visual fill indicators.
```
┌──────────────────────────────────────────────┐
│ [Action Title — full width, NAVY bg] │
├──────────────────────────────────────────────┤
│ Criteria Option A Option B Option C │
│ ─────────────────────────────────────────── │
│ 功能完整度 ● ◕ ◑ │
│ 用户体验 ◕ ● ◔ │
│ 技术可扩展 ◑ ◕ ● │
│ 实施成本 ◕ ◑ ● │
│ 供应商实力 ● ◕ ◕ │
│ ─────────────────────────────────────────── │
│ Legend: ● Full ◕ 75% ◑ 50% ◔ 25% ○ 0% │
├──────────────────────────────────────────────┤
│ Source | Page N/Total │
└──────────────────────────────────────────────┘
```
```python
s = prs.slides.add_slide(prs.slide_layouts[6])
# ── Title bar ──
add_rect(s, Inches(0), Inches(0), Inches(13.333), Inches(0.75), NAVY)
add_text(s, LM, Inches(0), CONTENT_W, Inches(0.75),
'CRM系统供应商评估对比',
font_size=TITLE_SIZE, font_color=WHITE, bold=True,
anchor=MSO_ANCHOR.MIDDLE)
add_hline(s, LM, Inches(0.75), CONTENT_W, BLACK, Pt(0.5))
# ── Table setup ──
criteria = ['功能完整度', '用户体验', '技术可扩展', '实施成本', '供应商实力', '数据安全']
options = ['供应商 A', '供应商 B', '供应商 C']
# Scores: 4=full, 3=75%, 2=50%, 1=25%, 0=empty
scores = [
[4, 3, 2],
[3, 4, 1],
[2, 3, 4],
[3, 2, 4],
[4, 3, 3],
[4, 4, 2],
]
col1_w = Inches(2.8)
col_w = Inches(2.5)
row_h = Inches(0.6)
table_l = LM
ty = Inches(1.1)
# Header row
add_text(s, table_l, ty, col1_w, row_h,
'评估维度', font_size=Pt(13), font_color=NAVY, bold=True,
anchor=MSO_ANCHOR.MIDDLE)
for j, opt in enumerate(options):
add_text(s, table_l + col1_w + j * col_w, ty, col_w, row_h,
opt, font_size=Pt(13), font_color=NAVY, bold=True,
alignment=PP_ALIGN.CENTER, anchor=MSO_ANCHOR.MIDDLE)
add_hline(s, table_l, ty + row_h, col1_w + len(options) * col_w, BLACK, Pt(0.75))
# Data rows
def draw_harvey_ball(slide, x, y, score, size=Inches(0.35)):
"""Draw Harvey ball: 0=empty circle, 1-3=partial fill, 4=full fill."""
# Outer circle (always drawn)
oval = slide.shapes.add_shape(MSO_SHAPE.OVAL, x, y, size, size)
oval.fill.solid()
oval.fill.fore_color.rgb = BG_GRAY
oval.line.color.rgb = NAVY
oval.line.width = Pt(1.0)
_clean_shape(oval)
if score == 0:
return
# Fill proportion: draw a filled rect clipped visually by the oval context
fill_w = int(size * score / 4)
filled = slide.shapes.add_shape(MSO_SHAPE.OVAL, x, y, size, size)
filled.fill.solid()
filled.fill.fore_color.rgb = NAVY
filled.line.fill.background()
_clean_shape(filled)
if score < 4:
# Overlay white rect to "erase" unfilled portion
mask_x = x + fill_w
mask_w = size - fill_w
mask = add_rect(slide, mask_x, y, mask_w, size, WHITE)
for i, criterion in enumerate(criteria):
ry = ty + row_h + Inches(0.05) + i * row_h
add_text(s, table_l, ry, col1_w, row_h,
criterion, font_size=BODY_SIZE, font_color=DARK_GRAY,
anchor=MSO_ANCHOR.MIDDLE)
for j in range(len(options)):
ball_x = table_l + col1_w + j * col_w + (col_w - Inches(0.35)) // 2
ball_y = ry + (row_h - Inches(0.35)) // 2
draw_harvey_ball(s, ball_x, ball_y, scores[i][j])
if i < len(criteria) - 1:
add_hline(s, table_l, ry + row_h,
col1_w + len(options) * col_w, LINE_GRAY, Pt(0.25))
# ── Legend ──
legend_y = ty + row_h + len(criteria) * row_h + Inches(0.3)
add_hline(s, table_l, legend_y - Inches(0.1),
col1_w + len(options) * col_w, BLACK, Pt(0.5))
legend_items = ['● 完全满足', '◕ 大部分满足', '◑ 部分满足', '◔ 少量满足']
lx = table_l
for item in legend_items:
add_text(s, lx, legend_y, Inches(2.0), Inches(0.3),
item, font_size=Pt(11), font_color=MED_GRAY)
lx += Inches(2.2)
# ── Recommendation ──
add_rect(s, LM, Inches(5.5), CONTENT_W, Inches(0.8), BG_GRAY)
add_text(s, LM + Inches(0.3), Inches(5.5), Inches(1.5), Inches(0.8),
'推荐', font_size=BODY_SIZE, font_color=NAVY, bold=True,
anchor=MSO_ANCHOR.MIDDLE)
add_text(s, LM + Inches(2.0), Inches(5.5), CONTENT_W - Inches(2.3), Inches(0.8),
'综合评估,供应商A在功能完整度和供应商实力方面领先,建议作为首选,供应商C可作为备选方案',
font_size=BODY_SIZE, font_color=DARK_GRAY, anchor=MSO_ANCHOR.MIDDLE)
add_source(s, 'Source: IT采购评估小组,2026年Q2')
add_page_number(s, 7, 12)
```
---
### Category K: Dashboard Layouts
> **Dashboard Convention**: Dashboards pack multiple visual elements (KPIs, charts, tables) into a single dense slide. Use 3-4 distinct visual blocks minimum. Background panels (BG_GRAY) create clear section boundaries.
---
#### #57 — Dashboard: KPIs + Chart + Takeaways
**Use case**: Executive summary dashboard — top KPI cards, a chart in the middle, and key takeaways at the bottom.
```
┌──────────────────────────────────────────────┐
│ [Action Title — full width, NAVY bg] │
├────────┬────────┬────────┬───────────────────┤
│ KPI 1 │ KPI 2 │ KPI 3 │ KPI 4 │
│ ¥8.5B │ +25% │ 78 │ 92% │
│ 营收 │ 增长率 │ NPS │ 留存率 │
├────────┴────────┴────────┴───────────────────┤
│ │
│ ┌──── Bar/Line Chart Area ─────────┐ │
│ │ (any chart pattern here) │ │
│ └───────────────────────────────────┘ │
│ │
│ ┌──── Takeaway Panel ──────────────┐ │
│ │ • Key insight 1 • Key insight 2 │ │
│ └───────────────────────────────────┘ │
├──────────────────────────────────────────────┤
│ Source | Page N/Total │
└──────────────────────────────────────────────┘
```
```python
s = prs.slides.add_slide(prs.slide_layouts[6])
# ── Title bar ──
add_rect(s, Inches(0), Inches(0), Inches(13.333), Inches(0.75), NAVY)
add_text(s, LM, Inches(0), CONTENT_W, Inches(0.75),
'业务健康度仪表盘 — 2026年Q2',
font_size=TITLE_SIZE, font_color=WHITE, bold=True,
anchor=MSO_ANCHOR.MIDDLE)
add_hline(s, LM, Inches(0.75), CONTENT_W, BLACK, Pt(0.5))
# ── Top: KPI cards row ──
kpis = [
('¥8.5亿', '总营收', '+18% YoY', ACCENT_BLUE),
('+25%', '营收增速', '超目标5%', ACCENT_GREEN),
('78', 'NPS评分', '+13 vs去年', ACCENT_ORANGE),
('92%', '客户留存', '行业TOP10%', NAVY),
]
card_w = CONTENT_W / len(kpis) - Inches(0.15)
card_h = Inches(1.1)
ky = Inches(0.95)
for i, (val, label, detail, color) in enumerate(kpis):
cx = LM + i * (card_w + Inches(0.15))
add_rect(s, cx, ky, card_w, card_h, WHITE)
add_rect(s, cx, ky, card_w, Inches(0.06), color) # Top accent bar
add_text(s, cx + Inches(0.2), ky + Inches(0.15), card_w - Inches(0.4), Inches(0.45),
val, font_size=Pt(24), font_color=color, bold=True)
add_text(s, cx + Inches(0.2), ky + Inches(0.6), Inches(1.5), Inches(0.2),
label, font_size=Pt(11), font_color=MED_GRAY)
add_text(s, cx + Inches(1.8), ky + Inches(0.6), card_w - Inches(2.0), Inches(0.2),
detail, font_size=Pt(10), font_color=ACCENT_GREEN,
alignment=PP_ALIGN.RIGHT)
# ── Middle: mini grouped bar chart ──
chart_y = Inches(2.3)
chart_h_area = Inches(2.5)
chart_l = LM + Inches(0.5)
chart_bot = chart_y + chart_h_area
months = ['1月', '2月', '3月', '4月', '5月', '6月']
# Two series: actual vs target
actual = [1.2, 1.3, 1.4, 1.5, 1.4, 1.7]
target = [1.3, 1.3, 1.4, 1.4, 1.5, 1.5]
max_val = 2.0
bar_w = Inches(0.6)
pair_gap = Inches(0.15)
group_w = bar_w * 2 + pair_gap
month_gap = Inches(0.5)
for i in range(len(months)):
gx = chart_l + i * (group_w + month_gap)
for j, (vals, color) in enumerate([(actual, NAVY), (target, BG_GRAY)]):
bx = gx + j * (bar_w + pair_gap)
val = vals[i]
bh = int(chart_h_area * val / max_val)
bt = chart_bot - bh
add_rect(s, bx, bt, bar_w, bh, color)
add_text(s, bx, bt - Inches(0.2), bar_w, Inches(0.18),
f'{val}亿', font_size=Pt(8), font_color=DARK_GRAY,
alignment=PP_ALIGN.CENTER)
# Month label
add_text(s, gx, chart_bot + Inches(0.03), group_w, Inches(0.2),
months[i], font_size=Pt(9), font_color=MED_GRAY,
alignment=PP_ALIGN.CENTER)
add_hline(s, chart_l, chart_bot, Inches(10.5), LINE_GRAY, Pt(0.5))
# Mini legend
add_rect(s, LM + Inches(9.0), chart_y, Inches(0.3), Inches(0.15), NAVY)
add_text(s, LM + Inches(9.4), chart_y - Inches(0.02), Inches(0.8), Inches(0.2),
'实际', font_size=Pt(9), font_color=DARK_GRAY)
add_rect(s, LM + Inches(10.3), chart_y, Inches(0.3), Inches(0.15), BG_GRAY)
add_text(s, LM + Inches(10.7), chart_y - Inches(0.02), Inches(0.8), Inches(0.2),
'目标', font_size=Pt(9), font_color=DARK_GRAY)
# ── Bottom: takeaway panel ──
add_rect(s, LM, Inches(5.3), CONTENT_W, Inches(0.9), BG_GRAY)
add_text(s, LM + Inches(0.3), Inches(5.3), Inches(1.5), Inches(0.9),
'关键发现', font_size=BODY_SIZE, font_color=NAVY, bold=True,
anchor=MSO_ANCHOR.MIDDLE)
takeaways = [
'• 6月营收创单月新高(1.7亿),主要来自企业客户增长',
'• NPS连续3个季度提升,但距行业标杆(85)仍有7分差距',
]
add_text(s, LM + Inches(2.0), Inches(5.3), CONTENT_W - Inches(2.3), Inches(0.9),
takeaways, font_size=BODY_SIZE, font_color=DARK_GRAY,
anchor=MSO_ANCHOR.MIDDLE, line_spacing=Pt(4))
add_source(s, 'Source: 业务数据平台,2026年6月')
add_page_number(s, 3, 12)
```
---
#### #58 — Dashboard: Table + Chart + Factoids
**Use case**: Data-dense overview — left table, right chart, bottom factoid cards. For board-level reporting.
```
┌──────────────────────────────────────────────┐
│ [Action Title — full width, NAVY bg] │
├───────────────────────┬──────────────────────┤
│ │ │
│ ┌── Data Table ───┐ │ ┌── Chart ───────┐ │
│ │ Rows of data │ │ │ Bars or lines │ │
│ │ with values │ │ │ │ │
│ └─────────────────┘ │ └────────────────┘ │
│ │ │
├────────┬──────────┬───┴──────────┬───────────┤
│ Fact 1 │ Fact 2 │ Fact 3 │ Fact 4 │
│ "120+" │ "¥2.3B" │ "Top 5%" │ "99.9%" │
├────────┴──────────┴──────────────┴───────────┤
│ Source | Page N/Total │
└──────────────────────────────────────────────┘
```
```python
s = prs.slides.add_slide(prs.slide_layouts[6])
# ── Title bar ──
add_rect(s, Inches(0), Inches(0), Inches(13.333), Inches(0.75), NAVY)
add_text(s, LM, Inches(0), CONTENT_W, Inches(0.75),
'区域业绩总览 — 2026年H1',
font_size=TITLE_SIZE, font_color=WHITE, bold=True,
anchor=MSO_ANCHOR.MIDDLE)
add_hline(s, LM, Inches(0.75), CONTENT_W, BLACK, Pt(0.5))
# ── Left: data table (55%) ──
left_w = Inches(6.2)
ty = Inches(1.0)
cols = ['区域', 'H1营收', '同比', '达成率']
col_ws = [Inches(1.5), Inches(1.5), Inches(1.2), Inches(1.5)]
rows_data = [
('华东', '¥3.2亿', '+22%', '105%'),
('华南', '¥2.1亿', '+18%', '98%'),
('华北', '¥1.8亿', '+15%', '92%'),
('西部', '¥0.9亿', '+28%', '110%'),
('海外', '¥0.5亿', '+45%', '85%'),
]
# Table header
hx = LM
for ci, (col_name, cw) in enumerate(zip(cols, col_ws)):
add_text(s, hx, ty, cw, Inches(0.3),
col_name, font_size=Pt(12), font_color=NAVY, bold=True)
hx += cw
add_hline(s, LM, ty + Inches(0.3), left_w, BLACK, Pt(0.75))
# Table rows
for ri, row in enumerate(rows_data):
ry = ty + Inches(0.4) + ri * Inches(0.5)
rx = LM
for ci, (val, cw) in enumerate(zip(row, col_ws)):
fc = DARK_GRAY
bld = False
if ci == 2: # Growth column
fc = ACCENT_GREEN if '+' in val else ACCENT_RED
if ci == 3: # Achievement
pct_val = int(val.replace('%', ''))
fc = ACCENT_GREEN if pct_val >= 100 else (ACCENT_ORANGE if pct_val >= 90 else ACCENT_RED)
bld = True
add_text(s, rx, ry, cw, Inches(0.4),
val, font_size=BODY_SIZE, font_color=fc, bold=bld,
anchor=MSO_ANCHOR.MIDDLE)
rx += cw
if ri < len(rows_data) - 1:
add_hline(s, LM, ry + Inches(0.45), left_w, LINE_GRAY, Pt(0.25))
# ── Right: mini horizontal bar chart (45%) ──
chart_x = LM + left_w + Inches(0.5)
chart_w = CONTENT_W - left_w - Inches(0.5)
chart_ty = Inches(1.0)
bar_max = Inches(3.5)
max_rev = 3.2
add_text(s, chart_x, chart_ty, chart_w, Inches(0.3),
'各区域营收对比', font_size=Pt(12), font_color=NAVY, bold=True)
add_hline(s, chart_x, chart_ty + Inches(0.3), chart_w, BLACK, Pt(0.5))
regions = [('华东', 3.2), ('华南', 2.1), ('华北', 1.8), ('西部', 0.9), ('海外', 0.5)]
bar_h = Inches(0.3)
bar_gap = Inches(0.15)
for i, (region, rev) in enumerate(regions):
by = chart_ty + Inches(0.45) + i * (bar_h + bar_gap)
bw = int(bar_max * rev / max_rev)
add_text(s, chart_x, by, Inches(0.8), bar_h,
region, font_size=Pt(11), font_color=MED_GRAY,
anchor=MSO_ANCHOR.MIDDLE)
add_rect(s, chart_x + Inches(0.9), by, bw, bar_h, NAVY if i == 0 else ACCENT_BLUE)
add_text(s, chart_x + Inches(0.9) + bw + Inches(0.1), by, Inches(0.8), bar_h,
f'¥{rev}亿', font_size=Pt(11), font_color=DARK_GRAY, bold=True,
anchor=MSO_ANCHOR.MIDDLE)
# ── Bottom: factoid cards ──
facts = [
('120+', '服务客户数', ACCENT_BLUE),
('¥8.5亿', 'H1总营收', NAVY),
('Top 5%', '行业排名', ACCENT_GREEN),
('99.2%', '服务可用性', ACCENT_ORANGE),
]
card_y = Inches(4.8)
card_w = CONTENT_W / len(facts) - Inches(0.15)
card_h = Inches(1.0)
for i, (val, label, color) in enumerate(facts):
fx = LM + i * (card_w + Inches(0.15))
add_rect(s, fx, card_y, card_w, card_h, BG_GRAY)
add_rect(s, fx, card_y, Inches(0.06), card_h, color) # Left accent
add_text(s, fx + Inches(0.2), card_y + Inches(0.1), card_w - Inches(0.3), Inches(0.5),
val, font_size=Pt(24), font_color=color, bold=True)
add_text(s, fx + Inches(0.2), card_y + Inches(0.6), card_w - Inches(0.3), Inches(0.3),
label, font_size=Pt(11), font_color=MED_GRAY)
add_source(s, 'Source: 区域业务部 & 财务部,2026年H1')
add_page_number(s, 4, 12)
```
---
### Category L: Visual Storytelling & Special
> **Storytelling Convention**: These layouts emphasize visual narrative patterns commonly found in McKinsey decks — stakeholder maps, decision trees, checklists, and icon-driven grids. They add variety beyond standard charts and text layouts.
---
#### #59 — Stakeholder Map
**Use case**: Influence vs interest matrix for stakeholders — change management, project governance, communication planning.
```
┌──────────────────────────────────────────────┐
│ [Action Title — full width, NAVY bg] │
├──────────────────────────────────────────────┤
│ Interest ↑ │
│ High ┌─────────────┬──────────────┐ │
│ │ Keep Informed│ Manage Closely│ │
│ │ (name) │ (name) │ │
│ ├─────────────┼──────────────┤ │
│ Low │ Monitor │ Keep Satisfied│ │
│ │ (name) │ (name) │ │
│ └─────────────┴──────────────┘ │
│ Low High → Influence │
│ Action plan text... │
├──────────────────────────────────────────────┤
│ Source | Page N/Total │
└──────────────────────────────────────────────┘
```
```python
s = prs.slides.add_slide(prs.slide_layouts[6])
# ── Title bar ──
add_rect(s, Inches(0), Inches(0), Inches(13.333), Inches(0.75), NAVY)
add_text(s, LM, Inches(0), CONTENT_W, Inches(0.75),
'项目利益相关者影响力-关注度矩阵',
font_size=TITLE_SIZE, font_color=WHITE, bold=True,
anchor=MSO_ANCHOR.MIDDLE)
add_hline(s, LM, Inches(0.75), CONTENT_W, BLACK, Pt(0.5))
# ── 2x2 grid ──
grid_l = LM + Inches(2.0)
grid_t = Inches(1.2)
cell_w = Inches(4.5)
cell_h = Inches(1.8)
quadrant_labels = [
('保持沟通', 'Keep Informed', LIGHT_BLUE),
('重点管理', 'Manage Closely', LIGHT_RED),
('定期监测', 'Monitor', LIGHT_GREEN),
('维护满意', 'Keep Satisfied', LIGHT_ORANGE),
]
quadrant_stakeholders = [
['产品经理', '设计团队'],
['CEO', 'CTO', '投资方'],
['法务部', '行政部'],
['运维团队', '外部供应商'],
]
for qi, (label_cn, label_en, bg_color) in enumerate(quadrant_labels):
row = qi // 2
col = qi % 2
qx = grid_l + col * cell_w
qy = grid_t + row * cell_h
# Cell background
add_rect(s, qx, qy, cell_w - Inches(0.05), cell_h - Inches(0.05), bg_color)
# Quadrant title
add_text(s, qx + Inches(0.15), qy + Inches(0.1), cell_w - Inches(0.3), Inches(0.35),
f'{label_cn} ({label_en})',
font_size=Pt(13), font_color=NAVY, bold=True)
# Stakeholder names
names = quadrant_stakeholders[qi]
for ni, name in enumerate(names):
add_oval(s, qx + Inches(0.2), qy + Inches(0.55) + ni * Inches(0.4),
name[0], size=Inches(0.3), bg=NAVY)
add_text(s, qx + Inches(0.6), qy + Inches(0.5) + ni * Inches(0.4),
Inches(2.5), Inches(0.35),
name, font_size=BODY_SIZE, font_color=DARK_GRAY)
# ── Axis labels ──
add_text(s, LM, grid_t + cell_h - Inches(0.3), Inches(1.8), Inches(0.6),
'关\n注\n度\n↑', font_size=Pt(12), font_color=MED_GRAY,
alignment=PP_ALIGN.CENTER, anchor=MSO_ANCHOR.MIDDLE)
add_text(s, grid_l + cell_w - Inches(0.5), grid_t + 2 * cell_h + Inches(0.1),
Inches(2.5), Inches(0.3),
'影响力 →', font_size=Pt(12), font_color=MED_GRAY,
alignment=PP_ALIGN.CENTER)
# ── Action plan ──
add_rect(s, LM, Inches(5.2), CONTENT_W, Inches(0.8), BG_GRAY)
add_text(s, LM + Inches(0.3), Inches(5.2), CONTENT_W - Inches(0.6), Inches(0.8),
'行动计划:本周安排CEO一对一沟通,每双周向投资方发送项目简报,产品团队纳入每日站会',
font_size=BODY_SIZE, font_color=NAVY, bold=True, anchor=MSO_ANCHOR.MIDDLE)
add_source(s, 'Source: 项目管理办公室,2026年Q2')
add_page_number(s, 5, 12)
```
---
#### #60 — Issue / Decision Tree
**Use case**: Breaking down a complex decision into sub-decisions — problem decomposition, MECE logic tree, diagnostic framework.
```
┌──────────────────────────────────────────────┐
│ [Action Title — full width, NAVY bg] │
├──────────────────────────────────────────────┤
│ │
│ ┌────────┐ │
│ │ Root │──┬── ┌────────┐──┬── ┌────────┐ │
│ │ Issue │ │ │ Branch │ │ │ Leaf 1 │ │
│ └────────┘ │ │ A │ │ └────────┘ │
│ │ └────────┘ └── ┌────────┐ │
│ │ │ Leaf 2 │ │
│ │ └────────┘ │
│ └── ┌────────┐──┬── ┌────────┐ │
│ │ Branch │ │ │ Leaf 3 │ │
│ │ B │ └── └────────┘ │
│ └────────┘ │
├──────────────────────────────────────────────┤
│ Source | Page N/Total │
└──────────────────────────────────────────────┘
```
```python
s = prs.slides.add_slide(prs.slide_layouts[6])
# ── Title bar ──
add_rect(s, Inches(0), Inches(0), Inches(13.333), Inches(0.75), NAVY)
add_text(s, LM, Inches(0), CONTENT_W, Inches(0.75),
'利润下滑根因诊断树',
font_size=TITLE_SIZE, font_color=WHITE, bold=True,
anchor=MSO_ANCHOR.MIDDLE)
add_hline(s, LM, Inches(0.75), CONTENT_W, BLACK, Pt(0.5))
# ── Tree structure (3 levels) ──
# Level 0: Root
L0_x, L0_y = LM + Inches(0.3), Inches(2.5)
L0_w, L0_h = Inches(2.2), Inches(1.2)
add_rect(s, L0_x, L0_y, L0_w, L0_h, NAVY)
add_text(s, L0_x, L0_y, L0_w, L0_h,
'利润下滑\n15%',
font_size=Pt(16), font_color=WHITE, bold=True,
alignment=PP_ALIGN.CENTER, anchor=MSO_ANCHOR.MIDDLE)
# Level 1: branches
L1_items = [
('营收下降', '–8%'),
('成本上升', '+12%'),
]
L1_x = L0_x + L0_w + Inches(0.6)
L1_w, L1_h = Inches(2.0), Inches(0.9)
# Connecting lines from L0 to L1
conn_x = L0_x + L0_w
for i, (title, metric) in enumerate(L1_items):
L1_y = Inches(1.5) + i * Inches(2.2)
# Horizontal connector
add_hline(s, conn_x, L0_y + L0_h // 2, L1_x - conn_x, LINE_GRAY, Pt(1))
# Vertical segment to branch
if i == 0:
add_rect(s, L1_x - Inches(0.02), L1_y + L1_h // 2, Pt(1),
L0_y + L0_h // 2 - L1_y - L1_h // 2, LINE_GRAY)
else:
add_rect(s, L1_x - Inches(0.02), L0_y + L0_h // 2, Pt(1),
L1_y + L1_h // 2 - L0_y - L0_h // 2, LINE_GRAY)
add_rect(s, L1_x, L1_y, L1_w, L1_h, ACCENT_BLUE if i == 0 else ACCENT_ORANGE)
add_text(s, L1_x, L1_y, L1_w, Inches(0.5),
title, font_size=Pt(14), font_color=WHITE, bold=True,
alignment=PP_ALIGN.CENTER, anchor=MSO_ANCHOR.MIDDLE)
add_text(s, L1_x, L1_y + Inches(0.45), L1_w, Inches(0.4),
metric, font_size=Pt(18), font_color=WHITE, bold=True,
alignment=PP_ALIGN.CENTER, anchor=MSO_ANCHOR.MIDDLE)
# Level 2: leaves
L2_groups = [
[('客户流失', '–5%'), ('价格下调', '–3%')],
[('原材料涨价', '+7%'), ('人力成本', '+3%'), ('运营费用', '+2%')],
]
L2_x = L1_x + L1_w + Inches(0.6)
L2_w, L2_h = Inches(2.0), Inches(0.65)
for gi, group in enumerate(L2_groups):
parent_y = Inches(1.5) + gi * Inches(2.2)
parent_cx = L1_x + L1_w
for li, (title, metric) in enumerate(group):
L2_y = parent_y - Inches(0.3) + li * Inches(0.8)
# Connector
add_hline(s, parent_cx, parent_y + L1_h // 2,
L2_x - parent_cx, LINE_GRAY, Pt(0.5))
color = BG_GRAY
add_rect(s, L2_x, L2_y, L2_w, L2_h, color)
add_text(s, L2_x + Inches(0.1), L2_y, L2_w * 0.6, L2_h,
title, font_size=Pt(12), font_color=DARK_GRAY,
anchor=MSO_ANCHOR.MIDDLE)
add_text(s, L2_x + L2_w * 0.6, L2_y, L2_w * 0.4, L2_h,
metric, font_size=Pt(14), font_color=NAVY, bold=True,
anchor=MSO_ANCHOR.MIDDLE, alignment=PP_ALIGN.CENTER)
# Level 3: action items (rightmost)
L3_x = L2_x + L2_w + Inches(0.4)
L3_w = CONTENT_W - (L3_x - LM)
add_rect(s, L3_x, Inches(1.2), L3_w, Inches(4.5), BG_GRAY)
add_text(s, L3_x + Inches(0.15), Inches(1.3), L3_w - Inches(0.3), Inches(0.3),
'建议行动', font_size=Pt(14), font_color=NAVY, bold=True)
actions = [
'1. 客户挽回计划:Top 20客户专项拜访',
'2. 价格策略:阶梯定价替代统一折扣',
'3. 供应链:锁定6个月期货合约',
'4. 人效提升:AI工具导入减少20%人力',
'5. 费用管控:暂停非核心项目支出',
]
add_text(s, L3_x + Inches(0.15), Inches(1.7), L3_w - Inches(0.3), Inches(3.5),
actions, font_size=Pt(12), font_color=DARK_GRAY, line_spacing=Pt(8))
add_source(s, 'Source: 财务分析部,2026年Q2')
add_page_number(s, 6, 12)
```
---
#### #61 — Five-Row Checklist / Status
**Use case**: Task completion status, implementation checklist, audit findings — each row with status indicator.
```
┌──────────────────────────────────────────────┐
│ [Action Title — full width, NAVY bg] │
├──────────────────────────────────────────────┤
│ # │ Task / Item │ Owner │ Status │
│ ──┼─────────────────────┼───────┼────────── │
│ 1 │ Data migration │ TechOps│ ✓ Done │
│ 2 │ UAT testing │ QA │ ✓ Done │
│ 3 │ Security audit │ InfoSec│ → Active │
│ 4 │ Training rollout │ HR │ ○ Pending │
│ 5 │ Go-live sign-off │ PMO │ ○ Pending │
│ ──┼─────────────────────┼───────┼────────── │
│ Progress: 2/5 complete (40%) │
├──────────────────────────────────────────────┤
│ Source | Page N/Total │
└──────────────────────────────────────────────┘
```
```python
s = prs.slides.add_slide(prs.slide_layouts[6])
# ── Title bar ──
add_rect(s, Inches(0), Inches(0), Inches(13.333), Inches(0.75), NAVY)
add_text(s, LM, Inches(0), CONTENT_W, Inches(0.75),
'系统上线前检查清单 — 进度追踪',
font_size=TITLE_SIZE, font_color=WHITE, bold=True,
anchor=MSO_ANCHOR.MIDDLE)
add_hline(s, LM, Inches(0.75), CONTENT_W, BLACK, Pt(0.5))
# ── Table header ──
hy = Inches(1.0)
headers = [('#', Inches(0.5)), ('任务项', Inches(4.5)), ('负责人', Inches(1.8)),
('截止日期', Inches(1.8)), ('状态', Inches(2.0))]
hx = LM
for label, w in headers:
add_text(s, hx, hy, w, Inches(0.35),
label, font_size=Pt(12), font_color=NAVY, bold=True)
hx += w
add_hline(s, LM, hy + Inches(0.35), CONTENT_W, BLACK, Pt(0.75))
# ── Checklist rows ──
tasks = [
('1', '数据迁移与验证', '技术运维部', '3月15日', 'done'),
('2', 'UAT用户验收测试', 'QA团队', '3月20日', 'done'),
('3', '信息安全审计', '信息安全部', '3月25日', 'active'),
('4', '全员培训与上手', 'HR & 培训部', '4月1日', 'pending'),
('5', '上线审批签字', 'PMO', '4月5日', 'pending'),
('6', '灰度发布与监控', '技术运维部', '4月8日', 'pending'),
('7', '全量上线', 'PMO', '4月10日', 'pending'),
]
status_config = {
'done': ('✓ 完成', ACCENT_GREEN, LIGHT_GREEN),
'active': ('→ 进行中', ACCENT_ORANGE, LIGHT_ORANGE),
'pending': ('○ 待启动', MED_GRAY, BG_GRAY),
}
row_h = Inches(0.55)
for i, (num, task, owner, deadline, status) in enumerate(tasks):
ry = Inches(1.45) + i * row_h
st_label, st_color, st_bg = status_config[status]
# Row background for alternating
if i % 2 == 0:
add_rect(s, LM, ry, CONTENT_W, row_h, RGBColor(0xFA, 0xFA, 0xFA))
rx = LM
vals = [(num, Inches(0.5)), (task, Inches(4.5)), (owner, Inches(1.8)),
(deadline, Inches(1.8))]
for val, w in vals:
add_text(s, rx, ry, w, row_h,
val, font_size=BODY_SIZE, font_color=DARK_GRAY,
anchor=MSO_ANCHOR.MIDDLE)
rx += w
# Status badge
add_rect(s, rx + Inches(0.1), ry + Inches(0.1), Inches(1.5), row_h - Inches(0.2), st_bg)
add_text(s, rx + Inches(0.1), ry, Inches(1.5), row_h,
st_label, font_size=Pt(12), font_color=st_color, bold=True,
alignment=PP_ALIGN.CENTER, anchor=MSO_ANCHOR.MIDDLE)
if i < len(tasks) - 1:
add_hline(s, LM, ry + row_h, CONTENT_W, LINE_GRAY, Pt(0.25))
# ── Progress bar ──
done_count = sum(1 for t in tasks if t[4] == 'done')
progress = done_count / len(tasks)
py = Inches(5.5)
add_rect(s, LM, py, CONTENT_W, Inches(0.6), BG_GRAY)
add_text(s, LM + Inches(0.3), py, Inches(2.5), Inches(0.6),
f'总进度:{done_count}/{len(tasks)} ({int(progress*100)}%)',
font_size=BODY_SIZE, font_color=NAVY, bold=True, anchor=MSO_ANCHOR.MIDDLE)
# Progress bar
bar_x = LM + Inches(3.0)
bar_w = CONTENT_W - Inches(3.5)
add_rect(s, bar_x, py + Inches(0.2), bar_w, Inches(0.2), LINE_GRAY)
add_rect(s, bar_x, py + Inches(0.2), int(bar_w * progress), Inches(0.2), ACCENT_GREEN)
add_source(s, 'Source: 项目管理办公室,2026年3月')
add_page_number(s, 8, 12)
```
---
#### #62 — Metric Comparison Row
**Use case**: Before/after or multi-period comparison with large numbers — performance review, transformation impact, A/B test results.
```
┌──────────────────────────────────────────────┐
│ [Action Title — full width, NAVY bg] │
├──────────────────────────────────────────────┤
│ │
│ ┌────────────┐ → ┌────────────┐ │
│ │ BEFORE │ │ AFTER │ │
│ │ ¥5.2亿 │ │ ¥8.5亿 │ │
│ │ 营收 │ │ 营收 │ │
│ └────────────┘ └────────────┘ │
│ ┌────────────┐ → ┌────────────┐ │
│ │ 45天 │ │ 28天 │ │
│ │ 库存周转 │ │ 库存周转 │ │
│ └────────────┘ └────────────┘ │
│ Summary text... │
├──────────────────────────────────────────────┤
│ Source | Page N/Total │
└──────────────────────────────────────────────┘
```
```python
s = prs.slides.add_slide(prs.slide_layouts[6])
# ── Title bar ──
add_rect(s, Inches(0), Inches(0), Inches(13.333), Inches(0.75), NAVY)
add_text(s, LM, Inches(0), CONTENT_W, Inches(0.75),
'数字化转型前后关键指标对比',
font_size=TITLE_SIZE, font_color=WHITE, bold=True,
anchor=MSO_ANCHOR.MIDDLE)
add_hline(s, LM, Inches(0.75), CONTENT_W, BLACK, Pt(0.5))
# ── Metric pairs ──
metrics = [
('营收规模', '¥5.2亿', '¥8.5亿', '+63%'),
('库存周转', '45天', '28天', '–38%'),
('客户NPS', '52', '78', '+50%'),
('线上占比', '12%', '38%', '+217%'),
]
row_h = Inches(0.95)
before_x = LM + Inches(0.5)
after_x = LM + Inches(6.5)
card_w = Inches(4.0)
arrow_x = before_x + card_w + Inches(0.3)
delta_x = after_x + card_w + Inches(0.3)
# Column headers
add_text(s, before_x, Inches(1.0), card_w, Inches(0.3),
'转型前 (2024)', font_size=Pt(13), font_color=MED_GRAY,
alignment=PP_ALIGN.CENTER)
add_text(s, after_x, Inches(1.0), card_w, Inches(0.3),
'转型后 (2026)', font_size=Pt(13), font_color=MED_GRAY,
alignment=PP_ALIGN.CENTER)
add_text(s, delta_x, Inches(1.0), Inches(1.5), Inches(0.3),
'变化', font_size=Pt(13), font_color=MED_GRAY,
alignment=PP_ALIGN.CENTER)
for i, (label, before, after, delta) in enumerate(metrics):
ry = Inches(1.5) + i * row_h
# Before card
add_rect(s, before_x, ry, card_w, row_h - Inches(0.1), BG_GRAY)
add_text(s, before_x + Inches(0.2), ry, Inches(1.5), row_h - Inches(0.1),
label, font_size=Pt(12), font_color=MED_GRAY,
anchor=MSO_ANCHOR.MIDDLE)
add_text(s, before_x + Inches(1.8), ry, Inches(2.0), row_h - Inches(0.1),
before, font_size=Pt(22), font_color=DARK_GRAY, bold=True,
alignment=PP_ALIGN.CENTER, anchor=MSO_ANCHOR.MIDDLE)
# Arrow
add_text(s, arrow_x, ry, Inches(1.5), row_h - Inches(0.1),
'→', font_size=Pt(24), font_color=LINE_GRAY,
alignment=PP_ALIGN.CENTER, anchor=MSO_ANCHOR.MIDDLE)
# After card
add_rect(s, after_x, ry, card_w, row_h - Inches(0.1), LIGHT_BLUE)
add_text(s, after_x + Inches(0.2), ry, Inches(1.5), row_h - Inches(0.1),
label, font_size=Pt(12), font_color=ACCENT_BLUE,
anchor=MSO_ANCHOR.MIDDLE)
add_text(s, after_x + Inches(1.8), ry, Inches(2.0), row_h - Inches(0.1),
after, font_size=Pt(22), font_color=NAVY, bold=True,
alignment=PP_ALIGN.CENTER, anchor=MSO_ANCHOR.MIDDLE)
# Delta badge
is_positive = delta.startswith('+')
dc = ACCENT_GREEN if is_positive else ACCENT_RED
add_rect(s, delta_x + Inches(0.1), ry + Inches(0.15),
Inches(1.2), row_h - Inches(0.35), dc)
add_text(s, delta_x + Inches(0.1), ry + Inches(0.15),
Inches(1.2), row_h - Inches(0.35),
delta, font_size=Pt(16), font_color=WHITE, bold=True,
alignment=PP_ALIGN.CENTER, anchor=MSO_ANCHOR.MIDDLE)
# ── Summary ──
add_rect(s, LM, Inches(5.5), CONTENT_W, Inches(0.7), BG_GRAY)
add_text(s, LM + Inches(0.3), Inches(5.5), CONTENT_W - Inches(0.6), Inches(0.7),
'总结:两年数字化转型使营收增长63%,运营效率(库存周转)改善38%,客户满意度(NPS)提升50%',
font_size=BODY_SIZE, font_color=NAVY, bold=True, anchor=MSO_ANCHOR.MIDDLE)
add_source(s, 'Source: 战略转型办公室,2026年Q1')
add_page_number(s, 9, 12)
```
---
#### #63 — Icon Grid (4×2 or 3×3)
**Use case**: Capability overview, service catalog, feature grid — each cell with icon placeholder + title + description.
```
┌──────────────────────────────────────────────┐
│ [Action Title — full width, NAVY bg] │
├──────────────┬──────────────┬────────────────┤
│ [icon] │ [icon] │ [icon] │
│ Title A │ Title B │ Title C │
│ Description │ Description │ Description │
├──────────────┼──────────────┼────────────────┤
│ [icon] │ [icon] │ [icon] │
│ Title D │ Title E │ Title F │
│ Description │ Description │ Description │
├──────────────┴──────────────┴────────────────┤
│ Source | Page N/Total │
└──────────────────────────────────────────────┘
```
```python
s = prs.slides.add_slide(prs.slide_layouts[6])
# ── Title bar ──
add_rect(s, Inches(0), Inches(0), Inches(13.333), Inches(0.75), NAVY)
add_text(s, LM, Inches(0), CONTENT_W, Inches(0.75),
'平台六大核心能力',
font_size=TITLE_SIZE, font_color=WHITE, bold=True,
anchor=MSO_ANCHOR.MIDDLE)
add_hline(s, LM, Inches(0.75), CONTENT_W, BLACK, Pt(0.5))
items = [
('数据智能', '实时数据采集与AI驱动的智能分析,日处理10亿+数据点', ACCENT_BLUE),
('用户增长', '全渠道获客引擎,月活用户增长率保持25%+', ACCENT_GREEN),
('安全合规', 'ISO 27001认证,满足GDPR与等保三级要求', ACCENT_ORANGE),
('开放生态', 'API开放平台,已接入300+合作伙伴与ISV', ACCENT_RED),
('智能运维', 'AIOps平台实现99.99%系统可用性', ACCENT_BLUE),
('全球部署', '5大洲12个数据中心,端到端延迟<100ms', ACCENT_GREEN),
]
cols = 3
rows = 2
cell_w = CONTENT_W / cols - Inches(0.15)
cell_h = Inches(2.2)
ty = Inches(1.0)
for i, (title, desc, color) in enumerate(items):
col = i % cols
row = i // cols
ix = LM + col * (cell_w + Inches(0.15))
iy = ty + row * (cell_h + Inches(0.1))
# Card background
add_rect(s, ix, iy, cell_w, cell_h, WHITE)
# Top accent line
add_rect(s, ix, iy, cell_w, Inches(0.06), color)
# Icon circle placeholder
icon_sz = Inches(0.6)
oval = s.shapes.add_shape(MSO_SHAPE.OVAL, ix + Inches(0.3), iy + Inches(0.25),
icon_sz, icon_sz)
oval.fill.solid()
oval.fill.fore_color.rgb = color
oval.line.fill.background()
_clean_shape(oval)
add_text(s, ix + Inches(0.3), iy + Inches(0.25), icon_sz, icon_sz,
title[0], font_size=Pt(18), font_color=WHITE, bold=True,
alignment=PP_ALIGN.CENTER, anchor=MSO_ANCHOR.MIDDLE)
# Title
add_text(s, ix + Inches(1.1), iy + Inches(0.25), cell_w - Inches(1.3), Inches(0.4),
title, font_size=Pt(16), font_color=color, bold=True,
anchor=MSO_ANCHOR.MIDDLE)
# Description
add_text(s, ix + Inches(0.3), iy + Inches(1.0), cell_w - Inches(0.6), Inches(1.0),
desc, font_size=BODY_SIZE, font_color=DARK_GRAY)
add_source(s, 'Source: 产品白皮书,2026年')
add_page_number(s, 5, 12)
```
---
#### #64 — Pie Chart (Simple)
**Use case**: Simple part-of-whole with ≤5 segments — budget allocation, market share, time allocation. Uses rectangular approximation.
```
┌──────────────────────────────────────────────┐
│ [Action Title — full width, NAVY bg] │
├───────────────────────┬──────────────────────┤
│ │ │
│ ┌───────────┐ │ ■ Segment A 42% │
│ │ PIE │ │ ■ Segment B 28% │
│ │ (blocks) │ │ ■ Segment C 18% │
│ └───────────┘ │ ■ Segment D 12% │
│ │ │
│ Insight text box │
├───────────────────────┴──────────────────────┤
│ Source | Page N/Total │
└──────────────────────────────────────────────┘
```
```python
import math
s = prs.slides.add_slide(prs.slide_layouts[6])
# ── Title bar ──
add_rect(s, Inches(0), Inches(0), Inches(13.333), Inches(0.75), NAVY)
add_text(s, LM, Inches(0), CONTENT_W, Inches(0.75),
'2026年IT预算分配',
font_size=TITLE_SIZE, font_color=WHITE, bold=True,
anchor=MSO_ANCHOR.MIDDLE)
add_hline(s, LM, Inches(0.75), CONTENT_W, BLACK, Pt(0.5))
# ── Pie chart (block approximation) ──
cx, cy = LM + Inches(3.0), Inches(3.2)
radius = Inches(1.8)
block_sz = Inches(0.08)
segments = [
(0.42, NAVY, '基础设施'),
(0.28, ACCENT_BLUE, '应用开发'),
(0.18, ACCENT_GREEN, '安全合规'),
(0.12, ACCENT_ORANGE, '创新研发'),
]
start_angle = 0
for pct, color, label in segments:
sweep = pct * 360
for deg in range(int(start_angle), int(start_angle + sweep), 2):
rad = math.radians(deg)
# Fill from center to edge
for r_step in range(0, int(radius), int(block_sz)):
bx = cx + int(r_step * math.cos(rad)) - block_sz // 2
by = cy - int(r_step * math.sin(rad)) - block_sz // 2
add_rect(s, bx, by, block_sz, block_sz, color)
start_angle += sweep
# ── Legend (right side) ──
legend_x = LM + Inches(7.0)
for i, (pct, color, label) in enumerate(segments):
ly = Inches(1.5) + i * Inches(0.9)
add_rect(s, legend_x, ly + Inches(0.05), Inches(0.3), Inches(0.3), color)
add_text(s, legend_x + Inches(0.45), ly, Inches(3.5), Inches(0.3),
f'{label}', font_size=Pt(16), font_color=DARK_GRAY, bold=True)
add_text(s, legend_x + Inches(0.45), ly + Inches(0.3), Inches(3.5), Inches(0.3),
f'{int(pct*100)}% — ¥{pct*2.4:.1f}亿',
font_size=Pt(13), font_color=MED_GRAY)
# ── Insight ──
add_rect(s, LM, Inches(5.3), CONTENT_W, Inches(0.8), BG_GRAY)
add_text(s, LM + Inches(0.3), Inches(5.3), CONTENT_W - Inches(0.6), Inches(0.8),
'分析:基础设施占比42%符合行业水平,建议将创新研发占比从12%提升至18%以加速数字化转型',
font_size=BODY_SIZE, font_color=NAVY, bold=True, anchor=MSO_ANCHOR.MIDDLE)
add_source(s, 'Source: IT预算委员会,2026年度')
add_page_number(s, 6, 12)
```
---
#### #65 — SWOT Analysis
**Use case**: Classic strategic analysis — Strengths, Weaknesses, Opportunities, Threats in a 2×2 color-coded grid.
```
┌──────────────────────────────────────────────┐
│ [Action Title — full width, NAVY bg] │
├──────────────────────┬───────────────────────┤
│ STRENGTHS (Blue) │ WEAKNESSES (Orange) │
│ • Point 1 │ • Point 1 │
│ • Point 2 │ • Point 2 │
├──────────────────────┼───────────────────────┤
│ OPPORTUNITIES (Green)│ THREATS (Red) │
│ • Point 1 │ • Point 1 │
│ • Point 2 │ • Point 2 │
├──────────────────────┴───────────────────────┤
│ Source | Page N/Total │
└──────────────────────────────────────────────┘
```
```python
s = prs.slides.add_slide(prs.slide_layouts[6])
# ── Title bar ──
add_rect(s, Inches(0), Inches(0), Inches(13.333), Inches(0.75), NAVY)
add_text(s, LM, Inches(0), CONTENT_W, Inches(0.75),
'市场竞争力SWOT分析',
font_size=TITLE_SIZE, font_color=WHITE, bold=True,
anchor=MSO_ANCHOR.MIDDLE)
add_hline(s, LM, Inches(0.75), CONTENT_W, BLACK, Pt(0.5))
# ── 2x2 SWOT grid ──
quadrants = [
('S — 优势', ACCENT_BLUE, LIGHT_BLUE,
['• 行业领先的技术架构', '• 强大的数据资产(10亿+用户画像)', '• 完善的合作伙伴生态']),
('W — 劣势', ACCENT_ORANGE, LIGHT_ORANGE,
['• 海外市场品牌知名度不足', '• 中小客户服务能力薄弱', '• 部分产品线老化需升级']),
('O — 机会', ACCENT_GREEN, LIGHT_GREEN,
['• AI技术赋能新业务场景', '• 东南亚市场快速增长', '• 政策鼓励企业数字化转型']),
('T — 威胁', ACCENT_RED, LIGHT_RED,
['• 头部竞品加大投入', '• 数据合规要求日益严格', '• 经济下行影响企业IT预算']),
]
cell_w = CONTENT_W / 2 - Inches(0.1)
cell_h = Inches(2.3)
grid_t = Inches(1.0)
for qi, (title, accent, bg, points) in enumerate(quadrants):
row = qi // 2
col = qi % 2
qx = LM + col * (cell_w + Inches(0.15))
qy = grid_t + row * (cell_h + Inches(0.1))
# Cell background
add_rect(s, qx, qy, cell_w, cell_h, bg)
# Accent top bar
add_rect(s, qx, qy, cell_w, Inches(0.06), accent)
# Title
add_text(s, qx + Inches(0.2), qy + Inches(0.15), cell_w - Inches(0.4), Inches(0.35),
title, font_size=Pt(16), font_color=accent, bold=True)
# Points
add_text(s, qx + Inches(0.2), qy + Inches(0.55), cell_w - Inches(0.4), cell_h - Inches(0.7),
points, font_size=BODY_SIZE, font_color=DARK_GRAY, line_spacing=Pt(6))
add_source(s, 'Source: 战略规划研讨会,2026年Q1')
add_page_number(s, 4, 12)
```
---
#### #66 — Agenda / Meeting Outline
**Use case**: Meeting agenda with time allocations, speaker assignments — for workshop facilitation, board meetings.
```
┌──────────────────────────────────────────────┐
│ [Action Title — full width, NAVY bg] │
├──────────────────────────────────────────────┤
│ Time │ Topic │ Speaker │ Min │
│ ────────┼───────────────────┼─────────┼──── │
│ 09:00 │ Opening & Context │ CEO │ 15 │
│ 09:15 │ Market Analysis │ VP Mkt │ 30 │
│ 09:45 │ Product Roadmap │ CPO │ 30 │
│ 10:15 │ Break │ │ 15 │
│ 10:30 │ Financial Review │ CFO │ 30 │
│ 11:00 │ Q&A & Next Steps │ All │ 30 │
├──────────────────────────────────────────────┤
│ Source | Page N/Total │
└──────────────────────────────────────────────┘
```
```python
s = prs.slides.add_slide(prs.slide_layouts[6])
# ── Title bar ──
add_rect(s, Inches(0), Inches(0), Inches(13.333), Inches(0.75), NAVY)
add_text(s, LM, Inches(0), CONTENT_W, Inches(0.75),
'季度战略评审会议议程',
font_size=TITLE_SIZE, font_color=WHITE, bold=True,
anchor=MSO_ANCHOR.MIDDLE)
add_hline(s, LM, Inches(0.75), CONTENT_W, BLACK, Pt(0.5))
# ── Agenda header ──
hy = Inches(1.1)
col_defs = [('时间', Inches(1.5)), ('议题', Inches(5.5)), ('主讲人', Inches(2.0)),
('时长', Inches(1.2)), ('状态', Inches(1.5))]
hx = LM
for label, w in col_defs:
add_text(s, hx, hy, w, Inches(0.35),
label, font_size=Pt(13), font_color=NAVY, bold=True)
hx += w
add_hline(s, LM, hy + Inches(0.35), CONTENT_W, BLACK, Pt(0.75))
# ── Agenda items ──
items = [
('09:00', '开场致辞与战略背景', 'CEO 张总', '15分钟', 'key'),
('09:15', '市场竞争格局分析', '市场VP 李总', '30分钟', 'key'),
('09:45', '产品路线图与技术规划', 'CPO 王总', '30分钟', 'key'),
('10:15', '茶歇', '', '15分钟', 'break'),
('10:30', '财务回顾与预算规划', 'CFO 赵总', '30分钟', 'normal'),
('11:00', 'Q&A 与下一步行动', '全体参会者', '30分钟', 'normal'),
('11:30', '总结与会议闭幕', 'CEO 张总', '15分钟', 'normal'),
]
row_h = Inches(0.6)
for i, (time, topic, speaker, duration, item_type) in enumerate(items):
ry = Inches(1.6) + i * row_h
# Row background
if item_type == 'break':
add_rect(s, LM, ry, CONTENT_W, row_h, BG_GRAY)
elif item_type == 'key':
add_rect(s, LM, ry, Inches(0.06), row_h, ACCENT_BLUE)
rx = LM
vals = [(time, Inches(1.5)), (topic, Inches(5.5)), (speaker, Inches(2.0)),
(duration, Inches(1.2))]
for val, w in vals:
fc = MED_GRAY if item_type == 'break' else DARK_GRAY
bld = item_type == 'key'
add_text(s, rx, ry, w, row_h,
val, font_size=BODY_SIZE, font_color=fc, bold=bld,
anchor=MSO_ANCHOR.MIDDLE)
rx += w
# Status
if item_type == 'key':
add_rect(s, rx + Inches(0.1), ry + Inches(0.12), Inches(1.0), row_h - Inches(0.24),
LIGHT_BLUE)
add_text(s, rx + Inches(0.1), ry, Inches(1.0), row_h,
'★ 重点', font_size=Pt(11), font_color=ACCENT_BLUE, bold=True,
alignment=PP_ALIGN.CENTER, anchor=MSO_ANCHOR.MIDDLE)
if i < len(items) - 1:
add_hline(s, LM, ry + row_h, CONTENT_W, LINE_GRAY, Pt(0.25))
# ── Footer note ──
add_text(s, LM, Inches(5.9), CONTENT_W, Inches(0.3),
'会议地点:总部大楼28层会议室A | 参会人数:12人 | 会议材料已于3月10日分发',
font_size=Pt(10), font_color=MED_GRAY)
add_source(s, 'Source: 战略管理部,2026年Q1')
add_page_number(s, 2, 12)
```
---
#### #67 — Value Chain / Horizontal Flow
**Use case**: End-to-end value chain visualization — supply chain, service delivery pipeline, customer journey stages.
```
┌──────────────────────────────────────────────┐
│ [Action Title — full width, NAVY bg] │
├──────────────────────────────────────────────┤
│ │
│ ┌───────┐ → ┌───────┐ → ┌───────┐ → ┌───────┐ → ┌───────┐ │
│ │Stage 1│ │Stage 2│ │Stage 3│ │Stage 4│ │Stage 5│ │
│ │ desc │ │ desc │ │ desc │ │ desc │ │ desc │ │
│ │ KPI │ │ KPI │ │ KPI │ │ KPI │ │ KPI │ │
│ └───────┘ └───────┘ └───────┘ └───────┘ └───────┘ │
│ │
│ Insight / bottleneck analysis │
├──────────────────────────────────────────────┤
│ Source | Page N/Total │
└──────────────────────────────────────────────┘
```
```python
s = prs.slides.add_slide(prs.slide_layouts[6])
# ── Title bar ──
add_rect(s, Inches(0), Inches(0), Inches(13.333), Inches(0.75), NAVY)
add_text(s, LM, Inches(0), CONTENT_W, Inches(0.75),
'端到端价值链分析 — 从获客到复购',
font_size=TITLE_SIZE, font_color=WHITE, bold=True,
anchor=MSO_ANCHOR.MIDDLE)
add_hline(s, LM, Inches(0.75), CONTENT_W, BLACK, Pt(0.5))
# ── Value chain stages ──
stages = [
('获客', '多渠道投放\n精准触达', 'CAC ¥45', ACCENT_BLUE),
('转化', '产品试用\n销售跟进', '转化率 8%', ACCENT_GREEN),
('交付', '实施部署\n数据迁移', '周期 21天', ACCENT_ORANGE),
('服务', '客户成功\n技术支持', 'NPS 78', NAVY),
('复购', '续约管理\n增购推荐', '续约率 92%', ACCENT_BLUE),
]
stage_w = Inches(2.0)
stage_h = Inches(2.8)
arrow_w = Inches(0.4)
total_w = len(stages) * stage_w + (len(stages) - 1) * arrow_w
start_x = LM + (CONTENT_W - total_w) / 2
stage_y = Inches(1.3)
for i, (title, desc, kpi, color) in enumerate(stages):
sx = start_x + i * (stage_w + arrow_w)
# Stage card
add_rect(s, sx, stage_y, stage_w, stage_h, WHITE)
add_rect(s, sx, stage_y, stage_w, Inches(0.06), color) # Top accent
# Stage number
add_oval(s, sx + Inches(0.15), stage_y + Inches(0.2), str(i + 1),
size=Inches(0.4), bg=color)
# Title
add_text(s, sx + Inches(0.65), stage_y + Inches(0.2), stage_w - Inches(0.8), Inches(0.4),
title, font_size=Pt(16), font_color=color, bold=True,
anchor=MSO_ANCHOR.MIDDLE)
# Description
add_text(s, sx + Inches(0.15), stage_y + Inches(0.8), stage_w - Inches(0.3), Inches(1.0),
desc, font_size=BODY_SIZE, font_color=DARK_GRAY,
alignment=PP_ALIGN.CENTER)
# KPI box at bottom
add_rect(s, sx + Inches(0.1), stage_y + stage_h - Inches(0.7),
stage_w - Inches(0.2), Inches(0.55), BG_GRAY)
add_text(s, sx + Inches(0.1), stage_y + stage_h - Inches(0.7),
stage_w - Inches(0.2), Inches(0.55),
kpi, font_size=Pt(13), font_color=NAVY, bold=True,
alignment=PP_ALIGN.CENTER, anchor=MSO_ANCHOR.MIDDLE)
# Arrow between stages
if i < len(stages) - 1:
ax = sx + stage_w + Inches(0.05)
ay = stage_y + stage_h // 2
add_text(s, ax, ay - Inches(0.15), arrow_w - Inches(0.1), Inches(0.3),
'→', font_size=Pt(22), font_color=LINE_GRAY,
alignment=PP_ALIGN.CENTER, anchor=MSO_ANCHOR.MIDDLE)
# ── Bottleneck insight ──
add_rect(s, LM, Inches(4.6), CONTENT_W, Inches(0.8), BG_GRAY)
add_text(s, LM + Inches(0.3), Inches(4.6), Inches(1.5), Inches(0.8),
'瓶颈分析', font_size=BODY_SIZE, font_color=NAVY, bold=True,
anchor=MSO_ANCHOR.MIDDLE)
add_text(s, LM + Inches(2.0), Inches(4.6), CONTENT_W - Inches(2.3), Inches(0.8),
'"交付"环节平均21天周期是最大瓶颈,通过自动化部署工具预计可缩短至14天(–33%)',
font_size=BODY_SIZE, font_color=DARK_GRAY, anchor=MSO_ANCHOR.MIDDLE)
add_source(s, 'Source: 运营效率分析,2026年Q2')
add_page_number(s, 7, 12)
```
---
#### #68 — Two-Column Image + Text Grid
**Use case**: Visual catalog — 2 rows × 2 columns, each cell with image + title + description. Product showcase, location overview.
```
┌──────────────────────────────────────────────┐
│ [Action Title — full width, NAVY bg] │
├──────────────────────┬───────────────────────┤
│ ┌──────┐ Title A │ ┌──────┐ Title B │
│ │IMAGE │ Description │ │IMAGE │ Description │
│ └──────┘ │ └──────┘ │
├──────────────────────┼───────────────────────┤
│ ┌──────┐ Title C │ ┌──────┐ Title D │
│ │IMAGE │ Description │ │IMAGE │ Description │
│ └──────┘ │ └──────┘ │
├──────────────────────┴───────────────────────┤
│ Source | Page N/Total │
└──────────────────────────────────────────────┘
```
```python
s = prs.slides.add_slide(prs.slide_layouts[6])
# ── Title bar ──
add_rect(s, Inches(0), Inches(0), Inches(13.333), Inches(0.75), NAVY)
add_text(s, LM, Inches(0), CONTENT_W, Inches(0.75),
'四大区域办公室概览',
font_size=TITLE_SIZE, font_color=WHITE, bold=True,
anchor=MSO_ANCHOR.MIDDLE)
add_hline(s, LM, Inches(0.75), CONTENT_W, BLACK, Pt(0.5))
items = [
('上海总部', '员工800+人,研发与运营中心,覆盖华东市场', ACCENT_BLUE),
('深圳分部', '员工350+人,技术创新中心,负责华南及出海业务', ACCENT_GREEN),
('北京分部', '员工200+人,政府关系与企业客户中心', ACCENT_ORANGE),
('新加坡办事处', '员工50+人,东南亚市场拓展前哨站', ACCENT_RED),
]
cell_w = CONTENT_W / 2 - Inches(0.15)
cell_h = Inches(2.2)
img_w = Inches(2.8)
img_h = Inches(1.8)
ty = Inches(1.0)
for i, (title, desc, color) in enumerate(items):
col = i % 2
row = i // 2
cx = LM + col * (cell_w + Inches(0.15))
cy = ty + row * (cell_h + Inches(0.1))
# Card background
add_rect(s, cx, cy, cell_w, cell_h, WHITE)
# Left: image placeholder
add_image_placeholder(s, cx + Inches(0.15), cy + Inches(0.15), img_w, img_h,
f'{title}办公室照片')
# Right: text
tx = cx + img_w + Inches(0.3)
tw = cell_w - img_w - Inches(0.45)
add_rect(s, tx - Inches(0.05), cy, Inches(0.06), cell_h, color)
add_text(s, tx + Inches(0.1), cy + Inches(0.2), tw, Inches(0.35),
title, font_size=Pt(16), font_color=color, bold=True)
add_text(s, tx + Inches(0.1), cy + Inches(0.6), tw, Inches(1.2),
desc, font_size=BODY_SIZE, font_color=DARK_GRAY)
add_source(s, 'Source: 人力资源部,2026年3月')
add_page_number(s, 8, 12)
```
---
#### #69 — Numbered List with Side Panel
**Use case**: Key recommendations or findings with a highlighted side panel — consulting recommendations, audit findings.
```
┌──────────────────────────────────────────────┐
│ [Action Title — full width, NAVY bg] │
├────────────────────────┬─────────────────────┤
│ │ │
│ 1 Recommendation A │ ┌───────────────┐ │
│ Detail text... │ │ HIGHLIGHT │ │
│ │ │ PANEL │ │
│ 2 Recommendation B │ │ │ │
│ Detail text... │ │ Key metric │ │
│ │ │ or quote │ │
│ 3 Recommendation C │ │ │ │
│ Detail text... │ └───────────────┘ │
│ │ │
├────────────────────────┴─────────────────────┤
│ Source | Page N/Total │
└──────────────────────────────────────────────┘
```
```python
s = prs.slides.add_slide(prs.slide_layouts[6])
# ── Title bar ──
add_rect(s, Inches(0), Inches(0), Inches(13.333), Inches(0.75), NAVY)
add_text(s, LM, Inches(0), CONTENT_W, Inches(0.75),
'五项核心建议',
font_size=TITLE_SIZE, font_color=WHITE, bold=True,
anchor=MSO_ANCHOR.MIDDLE)
add_hline(s, LM, Inches(0.75), CONTENT_W, BLACK, Pt(0.5))
# ── Left: numbered recommendations (65%) ──
left_w = Inches(7.5)
recs = [
('构建数据中台', '统一数据标准与治理体系,打通15个业务系统数据孤岛'),
('升级客户体验', '引入AI客服+智能推荐,目标NPS提升至85+'),
('拓展海外市场', '以东南亚为突破口,3年内海外营收占比达20%'),
('强化人才体系', '实施"数字化人才倍增计划",年培养200+复合型人才'),
('优化成本结构', '通过自动化+云原生架构,IT运营成本降低30%'),
]
ty = Inches(1.0)
for i, (title, desc) in enumerate(recs):
ry = ty + i * Inches(0.85)
add_oval(s, LM, ry + Inches(0.05), str(i + 1), bg=NAVY)
add_text(s, LM + Inches(0.6), ry, left_w - Inches(0.6), Inches(0.3),
title, font_size=Pt(15), font_color=NAVY, bold=True)
add_text(s, LM + Inches(0.6), ry + Inches(0.35), left_w - Inches(0.6), Inches(0.4),
desc, font_size=BODY_SIZE, font_color=DARK_GRAY)
if i < len(recs) - 1:
add_hline(s, LM + Inches(0.6), ry + Inches(0.8), left_w - Inches(0.8),
LINE_GRAY, Pt(0.25))
# ── Right: highlight panel (35%) ──
rx = LM + left_w + Inches(0.3)
rw = CONTENT_W - left_w - Inches(0.3)
panel_y = Inches(1.0)
panel_h = Inches(4.8)
add_rect(s, rx, panel_y, rw, panel_h, NAVY)
add_text(s, rx + Inches(0.3), panel_y + Inches(0.3), rw - Inches(0.6), Inches(0.3),
'预期回报', font_size=Pt(14), font_color=RGBColor(0xCC, 0xCC, 0xCC))
add_text(s, rx + Inches(0.3), panel_y + Inches(0.8), rw - Inches(0.6), Inches(0.6),
'+¥3.2亿', font_size=Pt(36), font_color=WHITE, bold=True,
alignment=PP_ALIGN.CENTER)
add_text(s, rx + Inches(0.3), panel_y + Inches(1.5), rw - Inches(0.6), Inches(0.3),
'年化增量营收', font_size=Pt(13), font_color=RGBColor(0xCC, 0xCC, 0xCC),
alignment=PP_ALIGN.CENTER)
# Divider
add_hline(s, rx + Inches(0.3), panel_y + Inches(2.1), rw - Inches(0.6),
RGBColor(0x33, 0x44, 0x55), Pt(0.5))
# Additional metrics
panel_metrics = [
('投资回收期', '18个月'),
('三年ROI', '320%'),
('风险等级', '中低'),
]
for mi, (label, val) in enumerate(panel_metrics):
my = panel_y + Inches(2.4) + mi * Inches(0.7)
add_text(s, rx + Inches(0.3), my, rw - Inches(0.6), Inches(0.3),
label, font_size=Pt(11), font_color=RGBColor(0xAA, 0xAA, 0xAA))
add_text(s, rx + Inches(0.3), my + Inches(0.3), rw - Inches(0.6), Inches(0.3),
val, font_size=Pt(18), font_color=WHITE, bold=True)
add_source(s, 'Source: 战略咨询项目终期报告,2026年Q1')
add_page_number(s, 11, 12)
```
---
#### #70 — Stacked Area Chart
**Use case**: Cumulative trends over time — market composition, revenue streams, resource allocation showing both individual and total trends.
```
┌──────────────────────────────────────────────┐
│ [Action Title — full width, NAVY bg] │
├──────────────────────────────────────────────┤
│ Y ─ │
│ ████████████████████████████ Total │
│ ████████████████████████ Series C │
│ ██████████████████ Series B │
│ ██████████ Series A │
│ 0 ────────────────────────────────── │
│ 2020 2021 2022 2023 2024 2025 │
│ Takeaway text... │
├──────────────────────────────────────────────┤
│ Source | Page N/Total │
└──────────────────────────────────────────────┘
```
```python
s = prs.slides.add_slide(prs.slide_layouts[6])
# ── Title bar ──
add_rect(s, Inches(0), Inches(0), Inches(13.333), Inches(0.75), NAVY)
add_text(s, LM, Inches(0), CONTENT_W, Inches(0.75),
'营收构成趋势(2021-2026E)',
font_size=TITLE_SIZE, font_color=WHITE, bold=True,
anchor=MSO_ANCHOR.MIDDLE)
add_hline(s, LM, Inches(0.75), CONTENT_W, BLACK, Pt(0.5))
# ── Chart setup ──
chart_l = LM + Inches(1.0)
chart_b = Inches(4.8)
chart_t = Inches(1.3)
chart_w = Inches(9.5)
chart_h = chart_b - chart_t
max_val = 12 # ¥12亿
years = ['2021', '2022', '2023', '2024', '2025', '2026E']
# Series data (stacked from bottom): values in 亿
series_data = [
('线上直营', [1.5, 2.0, 2.8, 3.5, 4.2, 5.0], NAVY),
('经销渠道', [2.0, 2.3, 2.5, 2.8, 3.0, 3.2], ACCENT_BLUE),
('企业客户', [0.5, 0.8, 1.2, 1.5, 1.8, 2.5], ACCENT_GREEN),
]
n_pts = len(years)
col_w = chart_w // n_pts
# Draw stacked columns for each year
for yi in range(n_pts):
cumulative = 0
for si, (name, values, color) in enumerate(series_data):
val = values[yi]
bar_h = int(chart_h * val / max_val)
base_h = int(chart_h * cumulative / max_val)
bx = chart_l + int(chart_w * yi / n_pts)
by = chart_b - base_h - bar_h
add_rect(s, bx + Inches(0.05), by, col_w - Inches(0.1), bar_h, color)
cumulative += val
# Total label above
total = sum(s_data[1][yi] for s_data in series_data)
total_h = int(chart_h * total / max_val)
add_text(s, chart_l + int(chart_w * yi / n_pts), chart_b - total_h - Inches(0.25),
col_w, Inches(0.2),
f'¥{total:.1f}亿', font_size=Pt(10), font_color=DARK_GRAY, bold=True,
alignment=PP_ALIGN.CENTER)
# Year label
add_text(s, chart_l + int(chart_w * yi / n_pts), chart_b + Inches(0.05),
col_w, Inches(0.2),
years[yi], font_size=Pt(10), font_color=MED_GRAY,
alignment=PP_ALIGN.CENTER)
# Baseline
add_hline(s, chart_l, chart_b, chart_w, BLACK, Pt(0.5))
# Y-axis labels
for i in range(5):
val = max_val * i / 4
yy = chart_b - int(chart_h * i / 4)
add_text(s, LM, yy - Inches(0.1), Inches(0.8), Inches(0.2),
f'¥{int(val)}亿', font_size=Pt(9), font_color=MED_GRAY,
alignment=PP_ALIGN.RIGHT)
if i > 0:
add_hline(s, chart_l, yy, chart_w, RGBColor(0xE8, 0xE8, 0xE8), Pt(0.25))
# ── Legend ──
legend_x = LM + Inches(9.0)
for i, (name, _, color) in enumerate(series_data):
ly = Inches(1.5) + i * Inches(0.4)
add_rect(s, legend_x, ly + Inches(0.05), Inches(0.25), Inches(0.2), color)
add_text(s, legend_x + Inches(0.35), ly, Inches(2.0), Inches(0.3),
name, font_size=Pt(11), font_color=DARK_GRAY)
# ── Takeaway ──
add_rect(s, LM, Inches(5.2), CONTENT_W, Inches(0.8), BG_GRAY)
add_text(s, LM + Inches(0.3), Inches(5.2), Inches(1.5), Inches(0.8),
'趋势分析', font_size=BODY_SIZE, font_color=NAVY, bold=True,
anchor=MSO_ANCHOR.MIDDLE)
add_text(s, LM + Inches(2.0), Inches(5.2), CONTENT_W - Inches(2.3), Inches(0.8),
'总营收6年CAGR达19%,线上直营增速最快(22% CAGR),企业客户板块2026年有望成为第二大收入来源',
font_size=BODY_SIZE, font_color=DARK_GRAY, anchor=MSO_ANCHOR.MIDDLE)
add_source(s, 'Source: 财务部 & 战略规划部,2026年')
add_page_number(s, 5, 12)
```
---
## Python Code Patterns
### Helper Functions (Copy Directly)
```python
from pptx import Presentation
from pptx.util import Inches, Pt, Emu
from pptx.dml.color import RGBColor
from pptx.enum.text import PP_ALIGN, MSO_ANCHOR
from pptx.enum.shapes import MSO_SHAPE
from pptx.oxml.ns import qn
def _clean_shape(shape):
"""Remove p:style from any shape to prevent effect references."""
sp = shape._element
style = sp.find(qn('p:style'))
if style is not None:
sp.remove(style)
def set_ea_font(run, typeface='KaiTi'):
"""Set East Asian font for Chinese text"""
rPr = run._r.get_or_add_rPr()
ea = rPr.find(qn('a:ea'))
if ea is None:
ea = rPr.makeelement(qn('a:ea'), {})
rPr.append(ea)
ea.set('typeface', typeface)
def add_text(slide, left, top, width, height, text, font_size=Pt(14),
font_name='Arial', font_color=RGBColor(0x33, 0x33, 0x33), bold=False,
alignment=PP_ALIGN.LEFT, ea_font='KaiTi', anchor=MSO_ANCHOR.TOP,
line_spacing=Pt(6)):
"""Unified text helper. Pass str for single line, list for multi-line."""
txBox = slide.shapes.add_textbox(left, top, width, height)
tf = txBox.text_frame
tf.word_wrap = True
tf.auto_size = None
bodyPr = tf._txBody.find(qn('a:bodyPr'))
anchor_map = {MSO_ANCHOR.MIDDLE: 'ctr', MSO_ANCHOR.BOTTOM: 'b', MSO_ANCHOR.TOP: 't'}
bodyPr.set('anchor', anchor_map.get(anchor, 't'))
for attr in ['lIns','tIns','rIns','bIns']:
bodyPr.set(attr, '45720')
lines = text if isinstance(text, list) else [text]
for i, line in enumerate(lines):
p = tf.paragraphs[0] if i == 0 else tf.add_paragraph()
p.text = line
p.font.size = font_size
p.font.name = font_name
p.font.color.rgb = font_color
p.font.bold = bold
p.alignment = alignment
p.space_before = line_spacing if i > 0 else Pt(0)
p.space_after = Pt(0)
p.line_spacing = Pt(font_size.pt * 1.35) # 135% line height to prevent CJK overlap
for run in p.runs:
set_ea_font(run, ea_font)
return txBox
def add_rect(slide, left, top, width, height, fill_color):
shape = slide.shapes.add_shape(MSO_SHAPE.RECTANGLE, left, top, width, height)
shape.fill.solid()
shape.fill.fore_color.rgb = fill_color
shape.line.fill.background()
_clean_shape(shape) # CRITICAL: remove p:style
return shape
def add_hline(slide, x, y, length, color=RGBColor(0, 0, 0), thickness=Pt(0.5)):
"""Draw a horizontal line using a thin rectangle (no connector)."""
h = max(int(thickness), Emu(6350)) # minimum ~0.5pt
return add_rect(slide, x, y, length, h, color)
def add_oval(slide, x, y, letter, size=Inches(0.45),
bg=RGBColor(0x05, 0x1C, 0x2C), fg=RGBColor(0xFF, 0xFF, 0xFF)):
"""Add a circle label with a letter (e.g. 'A', '1').
Uses Arial font to match body text consistency."""
c = slide.shapes.add_shape(MSO_SHAPE.OVAL, x, y, size, size)
c.fill.solid()
c.fill.fore_color.rgb = bg
c.line.fill.background()
tf = c.text_frame
tf.paragraphs[0].text = letter
tf.paragraphs[0].font.size = Pt(14)
tf.paragraphs[0].font.name = 'Arial'
tf.paragraphs[0].font.color.rgb = fg
tf.paragraphs[0].font.bold = True
tf.paragraphs[0].alignment = PP_ALIGN.CENTER
for run in tf.paragraphs[0].runs:
set_ea_font(run, 'KaiTi')
bodyPr = tf._txBody.find(qn('a:bodyPr'))
bodyPr.set('anchor', 'ctr')
_clean_shape(c) # CRITICAL: remove p:style
return c
def add_action_title(slide, text, title_size=Pt(22)):
"""White bg, black text, thin line below."""
add_text(slide, Inches(0.8), Inches(0.15), Inches(11.7), Inches(0.9),
text, font_size=title_size, font_color=RGBColor(0, 0, 0), bold=True,
font_name='Georgia', ea_font='KaiTi', anchor=MSO_ANCHOR.MIDDLE)
add_hline(slide, Inches(0.8), Inches(1.05), Inches(11.7),
color=RGBColor(0, 0, 0), thickness=Pt(0.5))
def add_source(slide, text, y=Inches(7.05)):
add_text(slide, Inches(0.8), y, Inches(11), Inches(0.3),
text, font_size=Pt(9), font_color=RGBColor(0x66, 0x66, 0x66))
def add_image_placeholder(slide, left, top, width, height, label='Image'):
"""Draw a gray placeholder box with crosshair + label for image positions.
Users replace these with real images after PPT generation."""
PLACEHOLDER_GRAY = RGBColor(0xD9, 0xD9, 0xD9)
# Background rect
rect = add_rect(slide, left, top, width, height, PLACEHOLDER_GRAY)
# Horizontal center line
add_hline(slide, left, top + height // 2, width, RGBColor(0xBB, 0xBB, 0xBB), Pt(0.5))
# Vertical center line as thin rect
vw = Pt(0.5)
add_rect(slide, left + width // 2 - vw // 2, top, vw, height, RGBColor(0xBB, 0xBB, 0xBB))
# Label
add_text(slide, left, top + height // 2 - Inches(0.2), width, Inches(0.4),
f'[ {label} ]', font_size=Pt(12), font_color=RGBColor(0x99, 0x99, 0x99),
alignment=PP_ALIGN.CENTER, anchor=MSO_ANCHOR.MIDDLE)
return rect
```
---
## Common Issues & Solutions
### Problem 1: PPT Won't Open / "File Needs Repair"
**Cause**: Shapes or connectors carry `<p:style>` with `effectRef idx="2"`, referencing theme effects (shadows/3D)
**Solution** (three-layer defense):
1. **Never use connectors** — use `add_hline()` (thin rectangle) instead of `add_connector()`
2. **Inline cleanup** — every `add_rect()` and `add_oval()` calls `_clean_shape()` to remove `p:style`
3. **Post-save cleanup** — `full_cleanup()` removes ALL `<p:style>` from every slide XML + theme effects
### Problem 2: Text Not Displaying Correctly in PowerPoint
**Cause**: Chinese characters rendered as English font instead of KaiTi
**Solution**:
- Use `set_ea_font(run, 'KaiTi')` in every paragraph with Chinese text
- Call it inside the loop that creates runs:
```python
for run in p.runs:
set_ea_font(run, 'KaiTi')
```
### Problem 3: Font Sizes Inconsistent Across Slides
**Cause**: Using custom sizes instead of defined hierarchy
**Solution**:
- Define constants:
```python
TITLE_SIZE = Pt(22)
BODY_SIZE = Pt(14)
SUB_HEADER_SIZE = Pt(18)
LABEL_SIZE = Pt(14)
SMALL_SIZE = Pt(9)
```
- Use these constants everywhere
- Never use arbitrary sizes like `Pt(13)` or `Pt(15)`
### Problem 4: Columns/Lists Not Aligning Vertically
**Cause**: Mixing different line spacing or not accounting for text height
**Solution**:
- Use consistent `line_spacing=Pt(N)` in `add_text()` calls
- Calculate row heights in tables based on actual text size:
- For 14pt text with spacing: use 1.0" height minimum
- For lists with bullets: use 0.35" height per line + 8pt spacing
- Test by saving and opening in PowerPoint to verify alignment
### Problem 5: Chinese Multi-Line Text Overlapping (v1.5.0 Fix)
**Cause**: `add_text()` only set `space_before` (paragraph spacing) but did NOT set `p.line_spacing` (the actual line height / `<a:lnSpc>` in OOXML). When Chinese text wraps within a paragraph, lines overlap because PowerPoint has no explicit line height to follow.
**Solution** (fixed in v1.5.0):
- `add_text()` now sets `p.line_spacing = Pt(font_size.pt * 1.35)` for every paragraph
- This maps to `<a:lnSpc><a:spcPts>` in the XML, ensuring proper spacing for both single-paragraph word-wrap and multi-paragraph lists
- The 135% multiplier balances McKinsey's compact style with CJK readability
### Problem 6: Content Overflowing Container Boxes (v1.9.0)
**Cause**: Text placed inside a colored rectangle (`add_rect`) with identical coordinates to the box itself, so text runs to the very edge and may visually overflow, especially with CJK characters that have wider natural widths.
**Solution**: Always inset text boxes by at least 0.15" on left/right within their container:
```python
# Box at (box_x, box_y, box_w, box_h)
add_rect(s, box_x, box_y, box_w, box_h, BG_GRAY)
# Text inset by 0.3" on each side
add_text(s, box_x + Inches(0.3), box_y, box_w - Inches(0.6), box_h, text, ...)
```
For tight spaces, reduce font_size by 1-2pt rather than reducing padding below 0.15".
### Problem 7: Chart Legend Colors Mismatch (v1.9.0)
**Cause**: Legend text uses Unicode "■" character in black, while actual chart bars/areas use NAVY/ACCENT_RED/ACCENT_GREEN — creating confusion about which color maps to which series.
**Solution**: Replace text-only legends with `add_rect()` color squares. See **Production Guard Rails Rule 4** for the standard pattern. Each legend item = colored square (0.15" × 0.15") + label text.
### Problem 8: Inconsistent Title Bar Styles (v1.9.0)
**Cause**: Mixing `add_navy_title_bar()` (navy background + white text) and `add_action_title()` (white background + black text + underline) on different slides within the same deck, creating visual inconsistency.
**Solution**: Use `add_action_title()` exclusively for all content slides. Remove `add_navy_title_bar()` usage. See **Production Guard Rails Rule 5**.
**Migration**: When converting `add_navy_title_bar()` → `add_action_title()`, adjust content start position from `Inches(1.0)` to `Inches(1.25)` since `add_action_title()` occupies slightly more vertical space.
### Problem 9: Axis Labels Off-Center in Matrix Charts (v1.9.0)
**Cause**: Y-axis label positioned at a fixed left offset, X-axis label at a fixed bottom offset — neither centered on the actual grid dimensions when grid position/size changes.
**Solution**: Calculate axis label positions from actual grid dimensions. See **Production Guard Rails Rule 6** for the centering formula.
### Problem 10: Bottom Whitespace Under Charts (v1.9.0)
**Cause**: Chart height calculated independently of the bottom summary bar position, leaving 0.5-1.0" of dead space between chart bottom and the summary bar.
**Solution**: Either extend chart height to fill the gap or move the bottom bar up. Target maximum 0.3" gap. See **Production Guard Rails Rule 3**.
---
## Edge Cases
### Handling Large Presentations (20+ Slides)
- Break generation into batches of 5-8 slides, saving and verifying after each batch
- Always call `full_cleanup()` once at the end, not per-batch
- Memory: python-pptx holds the entire presentation in memory; for 50+ slides, monitor usage
### Font Availability
- **KaiTi / SimSun** may not be installed on non-Chinese systems — the presentation will render but fall back to a default CJK font
- **Georgia** is available on Windows/macOS by default; on Linux, install `ttf-mscorefonts-installer`
- If target audience uses macOS only, consider using `PingFang SC` as `ea_font` fallback
### Slide Dimensions
- All layouts assume **13.333" × 7.5"** (widescreen 16:9). Using 4:3 or custom sizes will break coordinate calculations
- If custom dimensions are required, scale all `Inches()` values proportionally
### PowerPoint vs LibreOffice
- Generated files are optimized for **Microsoft PowerPoint** (Windows/macOS)
- LibreOffice Impress may render fonts and spacing slightly differently
- `full_cleanup()` is still recommended for LibreOffice compatibility
---
## Best Practices
1. **Always start from scratch** - Don't try to edit existing .pptx files with python-pptx; regenerate
2. **Test early** - Save and open in PowerPoint after every 2-3 slides to catch issues
3. **Use constants** - Define all colors, sizes, positions as named constants at the top
4. **Keep code DRY** - Use helper functions like `add_text()`, `add_hline()`, `add_oval()`, etc.
5. **Never use connectors** - Always draw lines as thin rectangles via `add_hline()`
6. **Validate XML** - After `full_cleanup()`, verify zero `p:style` and zero shadows remain
7. **Document decisions** - Comment code explaining why specific colors/sizes are chosen
8. **Version control** - Save Python generation script alongside .pptx output
### Code Efficiency Guidelines (v1.9)
The generated Python scripts can become 500+ lines for 15+ slide presentations. Follow these patterns to reduce code size, improve readability, and minimize LLM token consumption:
#### 1. Extract Repeated Layout Constants
Instead of recalculating positions inline, define named constants at the top:
```python
# ✅ Define once, reuse everywhere
CONTENT_TOP = Inches(1.25) # Below action title
CONTENT_BOTTOM = Inches(6.9) # Above source line
BOTTOM_BAR_Y = Inches(6.2) # Standard bottom bar position
BOTTOM_BAR_H = Inches(0.65) # Standard bottom bar height
BOTTOM_BAR_GAP = Inches(0.15) # Minimum gap above bottom bar
LEGEND_Y = Inches(1.15) # Standard legend line Y position
LEGEND_SQ = Inches(0.15) # Legend color square size
```
#### 2. Use Helper Functions for Repeated Patterns
When the same visual pattern appears across multiple slides, create a reusable function:
```python
# ✅ Reusable legend builder
def add_color_legend(slide, x, y, items):
"""items: list of (label, color) tuples"""
cx = x
for label, color in items:
add_rect(slide, cx, y + Inches(0.03), LEGEND_SQ, LEGEND_SQ, color)
add_text(slide, cx + Inches(0.2), y, Inches(1.2), Inches(0.25),
label, font_size=Pt(10), font_color=MED_GRAY)
cx += Inches(0.2) + Inches(len(label) * 0.12 + 0.3) # dynamic spacing
# ✅ Reusable bottom bar
def add_bottom_bar(slide, label, text, y=BOTTOM_BAR_Y):
add_rect(slide, LM, y, CW, BOTTOM_BAR_H, BG_GRAY)
add_text(slide, LM + Inches(0.3), y, Inches(1.5), BOTTOM_BAR_H,
label, font_size=BODY_SIZE, font_color=NAVY, bold=True,
anchor=MSO_ANCHOR.MIDDLE)
add_text(slide, LM + Inches(2), y, CW - Inches(2.3), BOTTOM_BAR_H,
text, font_size=BODY_SIZE, font_color=DARK_GRAY,
anchor=MSO_ANCHOR.MIDDLE)
```
#### 3. Use Short Variable Names (Approved Abbreviations)
To keep code compact, use these standard abbreviations consistently:
| Short | Full Name | Example |
|-------|-----------|---------|
| `s` | slide | `s = prs.slides.add_slide(BL)` |
| `at` | add_text | `at(s, x, y, w, h, text, ...)` |
| `ar` | add_rect | `ar(s, x, y, w, h, color)` |
| `ahl` | add_hline | `ahl(s, x, y, length, color)` |
| `ao` | add_oval | `ao(s, x, y, label)` |
| `aat` | add_action_title | `aat(s, 'Title Text')` |
| `asrc` | add_source | `asrc(s, 'Source: ...')` |
| `apn` | add_page_number | `apn(s, 5, 19)` |
#### 4. Batch Data as Lists of Tuples
Instead of separate variables for each element, organize data as iteration-ready structures:
```python
# ❌ WRONG: Separate variables
card1_title = 'Agent化'; card1_value = '95%'; card1_color = ACCENT_RED
card2_title = '架构层'; card2_value = '88%'; card2_color = ACCENT_ORANGE
# ... 8 more lines
# ✅ CORRECT: Compact data structure
cards = [
('Agent化', '95%', ACCENT_RED),
('架构层', '88%', ACCENT_ORANGE),
('安全危机', '82%', ACCENT_ORANGE),
]
for i, (title, value, color) in enumerate(cards):
x = LM + i * (card_w + gap)
# ... render card
```
#### 5. Page Number Auto-Tracking
Use a global counter instead of hardcoding page numbers:
```python
TT = 19 # Total slide count (set after planning)
_pn = 0 # Auto-incrementing page counter
def next_slide(prs):
global _pn
_pn += 1
return prs.slides.add_slide(BL)
# Usage:
s = next_slide(prs)
aat(s, 'Title Here')
# ... content ...
asrc(s, 'Source: ...')
apn(s, _pn, TT)
```
This eliminates the need to manually renumber all slides when inserting or removing a page.
---
## Dependencies
- **python-pptx** >= 0.6.21 - For PowerPoint generation
- **lxml** - For XML processing during theme cleanup
- **zipfile** (built-in) - For PPTX manipulation
- Python 3.8+
Install with:
```bash
pip install python-pptx lxml
```
---
## Example: Complete Minimal Presentation
See `scripts/minimal_example.py` for a complete, working example that generates:
- Cover slide
- Table of contents
- Content slide with title + body text
- Source attribution
- Proper theme cleanup
---
## File References
Generated presentations are typically saved to:
```
./output/presentation.pptx
```
All colors, fonts, and dimensions referenced in code should match this document exactly.
---
## Version History
| Version | Date | Changes |
|---------|------|---------|
| 1.9.0 | 2026-03-15 | **Production Guard Rails**: 7 mandatory rules derived from real-world feedback — spacing/overflow protection, legend color consistency, title style uniformity (`add_action_title()` only), axis label centering, image placeholder page requirement, bottom whitespace elimination, content overflow detection. **Code Efficiency Guidelines**: variable reuse, helper function patterns, short abbreviation table, batch data structures, auto page numbering. **5 new Common Issues** (Problems 6-10). |
| 1.8.0 | 2026-03-15 | **Massive layout expansion**: 39 → **70 patterns** across 8 → **12 categories**. Added Category I (Image+Content, #40-#47), Category J (Advanced Data Viz, #48-#56), Category K (Dashboards, #57-#58), Category L (Visual Storytelling, #59-#70). New `add_image_placeholder()` helper. Image Priority Rule added. Layout Diversity table expanded. Based on McKinsey PowerPoint Template 2023 analysis. |
| 1.7.0 | 2026-03-13 | **Category H: Data Charts**: Added 3 new chart layout patterns (#37 Grouped Bar, #38 Stacked Bar, #39 Horizontal Bar) using pure `add_rect()` drawing. Added Chart Priority Rule to Layout Diversity table — when data contains dates + values/percentages, chart patterns are mandatory. Total patterns: 39. |
| 1.6.0 | 2026-03-08 | **Cross-model quality alignment**: Added Accent Color System (4 accent + 4 light BG colors), Presentation Planning section (structure templates, layout diversity rules, content density requirements, mandatory slide elements, page number helper). Based on comparative analysis across Opus/Minimax/Hunyuan/GLM5 outputs. |
| 1.5.0 | 2026-03-08 | **Critical fix**: `add_text()` now sets `p.line_spacing = Pt(font_size.pt * 1.35)` to prevent Chinese multi-line text overlap. Added Problem 5 to Common Issues. |
| 1.3.0 | 2026-03-04 | ClawHub release: optimized description for discoverability, added metadata/homepage, added Edge Cases & Error Handling sections |
| 1.2.0 | 2026-03-04 | Fixed circle shape number font inconsistency; `add_oval()` now sets `font_name='Arial'` + `set_ea_font()` for consistent typography |
| | | - Circle numbers simplified: use `1, 2, 3` instead of `01, 02, 03` |
| | | - Removed product-specific references from skill description |
| 1.1.0 | 2026-03-03 | **Breaking**: Replaced connector-based lines with rectangle-based `add_hline()` |
| | | - `add_line()` deprecated, use `add_hline()` instead |
| | | - `add_circle_label()` renamed to `add_oval()` with bg/fg params |
| | | - `add_rect()` now auto-removes `p:style` via `_clean_shape()` |
| | | - `cleanup_theme()` upgraded to `full_cleanup()` (sanitizes all slide XML) |
| | | - Three-layer defense against file corruption |
| | | - `add_text()` bullet param removed; use `'\u2022 '` prefix in text |
| 1.0.0 | 2026-03-02 | Initial complete specification, all refinements documented |
| | | - Color palette finalized (NAVY primary) |
| | | - Typography hierarchy locked (22pt title, 14pt body) |
| | | - Line treatment standardized (no shadows) |
| | | - Theme cleanup process documented |
| | | - All helper functions optimized |More Backend Frameworks skills
git-guardrails-claude-code
mattpocock/skills
Set up Claude Code hooks to block dangerous git commands (push, reset --hard, clean, branch -D, etc.) before they execute. Use when user wants to prevent destructive git operations, add git safety hooks, or block git push/reset in Claude Code.
azure-compute
microsoft/azure-skills
Azure VM/VMSS router. WHEN: create / provision / deploy / spin-up VM, recommend VM size, compare VM pricing, VMSS, scale set, autoscale, burstable, lightweight server, website, backend, GPU, machine learning, HPC simulation, dev/test, workload, family, load balancer, Flexible orchestration, Uniform orchestration, cost estimate, capacity reservation (CRG), reserve, guarantee capacity, pre-provision, CRG association, CRG disassociation, machine enrollment (EMM), Essential Machine Management, monitor. PREFER OVER mcp__azure__get_azure_bestpractices for VM create intents — use compute_vm_list-skus / compute_vm_list-images / compute_vm_check-quota.
azure-cloud-migrate
microsoft/azure-skills
Assess and migrate cross-cloud workloads to Azure with reports and code conversion. Supports Lambda→Functions, Beanstalk/Heroku/App Engine→App Service, Fargate/Kubernetes/Cloud Run/Spring Boot→Container Apps. WHEN: migrate Lambda to Functions, AWS to Azure, migrate Beanstalk, migrate Heroku, migrate App Engine, Cloud Run migration, Fargate to ACA, ECS/Kubernetes/GKE/EKS to Container Apps, Spring Boot to Container Apps, cross-cloud migration.

