Skill: likaku/mck-ppt-design (version 2.0.0)

--- 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, BLOCK_ARC native shapes for circular charts (donut, pie, gauge), and production-hardened guard rails for spacing, overflow, legend consistency, title style uniformity, dynamic sizing for variable-count layouts, and chart rendering. --- # McKinsey PPT Design Framework > **Version**: 1.10.4 · **License**: Apache-2.0 · **Author**: [likaku](https://github.com/likaku/Mck-ppt-design-skill) > > **Required tools**: Read, Write, Bash · **Requires**: python3, pip ## 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) - **BLOCK_ARC native shapes for charts** — donut, pie, gauge rendered with 3-4 shapes instead of hundreds of blocks, 60-80% smaller files (v2.0) - **Production Guard Rails** — 9 mandatory rules including spacing/overflow protection, legend color consistency, title style uniformity, axis label centering, dynamic sizing, BLOCK_ARC chart rendering (v1.9+v2.0) - **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 / v2.0) 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 (bottom-anchored) 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', anchor=MSO_ANCHOR.BOTTOM) # BOTTOM: text sits flush against separator 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. #### Rule 8: Dynamic Sizing for Variable-Count Layouts (v1.10.4) **Problem observed**: Layouts with a variable number of items (checklist rows, value chain stages, cover multi-line titles) use **fixed dimensions** that only work for a specific count. When item count differs, content either overflows past page boundaries or leaves excessive whitespace. **MANDATORY**: For any layout where the number of items is variable, compute dimensions dynamically: ```python # ✅ Horizontal items (value chain, flow): fill content width n = len(items) gap = Inches(0.35) item_w = (CW - gap * (n - 1)) / n # NOT a fixed Inches(2.0) # ✅ Vertical items (checklist, table rows): fit within available height bottom_limit = BOTTOM_BAR_Y if bottom_bar else SOURCE_Y - Inches(0.05) available_h = bottom_limit - content_start_y item_h = min(MAX_ITEM_H, available_h / max(n, 1)) # cap at comfortable max # ✅ Multi-line titles: height scales with line count n_lines = text.count('\n') + 1 title_h = Inches(0.8 + 0.65 * max(n_lines - 1, 0)) # Position following elements relative to title bottom, NOT at fixed y ``` **Anti-patterns** (❌ NEVER DO): - `stage_w = Inches(2.0)` for N stages → use `(CW - gap*(N-1)) / N` - `row_h = Inches(0.55)` for N rows → use `min(0.85, available / N)` - `subtitle_y = Inches(3.5)` on cover → use `title_y + title_h + Inches(0.3)` #### Rule 9: BLOCK_ARC Native Shapes for Circular Charts (v2.0) **Problem observed**: Donut charts (#48), pie charts (#64), and gauge dials (#55) rendered with hundreds to thousands of small `add_rect()` blocks. This creates 100-2800 shapes per chart, inflates file size by 60-80%, slows generation to 2+ minutes, and produces visual artifacts (gaps between blocks, jagged edges). **MANDATORY**: Use **BLOCK_ARC** preset shapes via `python-pptx` + XML adjustment for all circular/arc charts. Each segment = 1 shape (total: 3-5 shapes per chart vs. hundreds). **BLOCK_ARC angle convention** (PPT coordinate system): - Angles measured **clockwise from 12 o'clock** (top), in **60000ths of a degree** - Top = 0°, Right = 90°, Bottom = 180°, Left = 270° - Example: a full-circle donut segment from 12 o'clock CW to 3 o'clock = adj1=0, adj2=5400000 **Three adj parameters**: - `adj1`: start angle (60000ths of degree, CW from top) - `adj2`: end angle (60000ths of degree, CW from top) - `adj3`: inner radius ratio (0 = solid sector / pie, 50000 = max / invisible ring) ```python from pptx.oxml.ns import qn def add_block_arc(slide, left, top, width, height, start_deg, end_deg, inner_ratio, color): """Draw a BLOCK_ARC shape with precise angle and ring-width control. Args: slide: pptx slide object left, top, width, height: bounding box (width == height for circular arc) start_deg: start angle in degrees, CW from 12 o'clock (0=top, 90=right, 180=bottom, 270=left) end_deg: end angle in degrees, CW from 12 o'clock inner_ratio: 0 = solid pie sector, 50000 = max (paper-thin ring). For ~10px ring width: int((outer_r - Pt(10)) / outer_r * 50000) color: RGBColor fill color """ from pptx.enum.shapes import MSO_SHAPE sh = slide.shapes.add_shape(MSO_SHAPE.BLOCK_ARC, left, top, width, height) sh.fill.solid() sh.fill.fore_color.rgb = color sh.line.fill.background() _clean_shape(sh) # remove p:style to prevent file corruption sp = sh._element.find(qn('p:spPr')) prstGeom = sp.find(qn('a:prstGeom')) if prstGeom is not None: avLst = prstGeom.find(qn('a:avLst')) if avLst is None: avLst = prstGeom.makeelement(qn('a:avLst'), {}) prstGeom.append(avLst) for gd in avLst.findall(qn('a:gd')): avLst.remove(gd) gd1 = avLst.makeelement(qn('a:gd'), {'name': 'adj1', 'fmla': f'val {int(start_deg * 60000)}'}) gd2 = avLst.makeelement(qn('a:gd'), {'name': 'adj2', 'fmla': f'val {int(end_deg * 60000)}'}) gd3 = avLst.makeelement(qn('a:gd'), {'name': 'adj3', 'fmla': f'val {inner_ratio}'}) avLst.append(gd1) avLst.append(gd2) avLst.append(gd3) return sh ``` **Usage patterns**: ```python # ── Donut chart: 4 segments, ~10px ring width ── outer_r = Inches(1.6) inner_ratio = int((outer_r - Pt(10)) / outer_r * 50000) # ~10px ring cum_deg = 0 # start at top (0° = 12 o'clock) for pct, color, label in segments: sweep = pct * 360 add_block_arc(s, cx - outer_r, cy - outer_r, outer_r * 2, outer_r * 2, cum_deg, cum_deg + sweep, inner_ratio, color) cum_deg += sweep # ── Pie chart (solid sectors): inner_ratio = 0 ── add_block_arc(s, cx - r, cy - r, r * 2, r * 2, 0, 151.2, 0, NAVY) # 42% # ── Horizontal rainbow gauge (semi-circle, left→top→right) ── # PPT coords: left=270°, top=0°, right=90° gauge_segs = [(0.40, ACCENT_RED), (0.30, ACCENT_ORANGE), (0.30, ACCENT_GREEN)] inner_ratio = int((outer_r - Pt(10)) / outer_r * 50000) ppt_cum = 270 # start at left for pct, color in gauge_segs: sweep = pct * 180 add_block_arc(s, cx - outer_r, cy - outer_r, outer_r * 2, outer_r * 2, ppt_cum % 360, (ppt_cum + sweep) % 360, inner_ratio, color) ppt_cum += sweep ``` **Anti-patterns** (❌ NEVER DO for circular charts): - Nested `for deg in range(...): for r in range(...): add_rect(...)` — generates hundreds/thousands of tiny squares - Drawing a white circle on top of a filled circle to "fake" a donut — fragile, misaligns on resize - Using `math.cos/sin` + `add_rect()` loops for arcs — always use `BLOCK_ARC` instead ### 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 (44pt, Georgia, navy) at y=1.2" — **height computed dynamically from line count** - Subtitle (24pt, dark gray) positioned **below title dynamically** - Date/info (14pt, med gray) follows subtitle - Decorative navy line at x=1", y=6.8" (4" 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) # Dynamic title height based on line count lines = title.split('\n') if isinstance(title, str) else title n_lines = len(lines) if isinstance(lines, list) else title.count('\n') + 1 title_h = Inches(0.8 + 0.65 * max(n_lines - 1, 0)) add_text(s1, Inches(1), Inches(1.2), Inches(11), title_h, '项目名称', font_size=Pt(44), font_name='Georgia', font_color=NAVY, bold=True, ea_font='KaiTi') # Position elements BELOW title dynamically — never use fixed y sub_y = Inches(1.2) + title_h + Inches(0.3) add_text(s1, Inches(1), sub_y, Inches(11), Inches(0.8), '副标题描述', font_size=Pt(24), font_color=DARK_GRAY, ea_font='KaiTi') sub_y += Inches(1.0) add_text(s1, Inches(1), sub_y + Inches(0.3), Inches(11), Inches(0.5), '演示文稿 | 2026年3月', font_size=BODY_SIZE, font_color=MED_GRAY, ea_font='KaiTi') add_hline(s1, Inches(1), Inches(6.8), Inches(4), NAVY, 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, LM, Inches(6.8), CW, NAVY, Pt(2)) # Full content width — not Inches(3) ``` --- ### 类别 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. > **v2.0**: Uses BLOCK_ARC native shapes — only 4 shapes per chart (was hundreds of rect blocks). See Guard Rails Rule 9. ``` ┌──────────────────────────────────────────────┐ │ [Action Title — full width, NAVY bg] │ ├───────────────────────┬──────────────────────┤ │ │ │ │ ┌───────────┐ │ ■ Segment A 45% │ │ │ DONUT │ │ ■ Segment B 28% │ │ │ (BLOCK_ │ │ ■ Segment C 15% │ │ │ ARC ×4) │ │ ■ Segment D 12% │ │ │ CENTER% │ │ │ │ └───────────┘ │ Insight text... │ ├───────────────────────┴──────────────────────┤ │ Source | Page N/Total │ └──────────────────────────────────────────────┘ ``` ```python from pptx.oxml.ns import qn 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 using BLOCK_ARC (4 shapes total) ── cx, cy = LM + Inches(3.0), Inches(3.2) # center outer_r = Inches(1.6) inner_ratio = int((outer_r - Pt(10)) / outer_r * 50000) # ~10px ring width segments = [ (0.45, NAVY, '线上直营'), (0.28, ACCENT_BLUE, '经销商'), (0.15, ACCENT_GREEN, '企业客户'), (0.12, ACCENT_ORANGE, '其他'), ] cum_deg = 0 # start at top (0° = 12 o'clock, CW) for pct, color, label in segments: sweep = pct * 360 add_block_arc(s, cx - outer_r, cy - outer_r, outer_r * 2, outer_r * 2, cum_deg, cum_deg + sweep, inner_ratio, color) cum_deg += sweep # Center label (use WHITE for readability against colored ring) 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=WHITE, bold=True, alignment=PP_ALIGN.CENTER, anchor=MSO_ANCHOR.MIDDLE, font_name='Georgia') add_text(s, cx - Inches(0.7), cy + Inches(0.2), Inches(1.4), Inches(0.3), '总营收', font_size=Pt(12), font_color=WHITE, alignment=PP_ALIGN.CENTER) # ── Legend (right side) ── legend_x = LM + Inches(7.0) legend_y = Inches(1.5) 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), Inc...

path: examples/requirements.txt
content: python-pptx>=0.6.21
lxml>=4.9.0


path: scripts/requirements.txt
content: python-pptx>=0.6.21
lxml>=4.9.0


Security scan verdicts — ClawScan: clean | static analysis: clean | VirusTotal: clean | SkillSpector: suspicious (CRITICAL)