| 1 | --- |
| 2 | name: oh-my-ppt-chart |
| 3 | description: Must be read before adding or modifying Oh My PPT slide charts. Defines product-safe Chart.js usage, canvas layout constraints, axis label rules, and retry fixes. |
| 4 | --- |
| 5 | |
| 6 | # Oh My PPT Chart |
| 7 | |
| 8 | For deeper examples (chart frame height guide, category axis patterns, layout integration tips), read `references/chart.md`. |
| 9 | |
| 10 | ## When to use |
| 11 | |
| 12 | - Adding a chart to a new or existing slide |
| 13 | - Modifying chart type, data, or options |
| 14 | - Repairing a blank or broken chart canvas |
| 15 | |
| 16 | ## When not to use |
| 17 | |
| 18 | - Pure visual micro-edits that don't change chart structure (e.g. adjusting a single color value). These still follow chart skill hard rules, but don't require reading the full reference. |
| 19 | |
| 20 | ## 30-second decision checklist |
| 21 | |
| 22 | Before writing chart HTML, answer these in order: |
| 23 | |
| 24 | 1. **Chart type**: bar, line, pie, doughnut, radar, polarArea, scatter, bubble? |
| 25 | 2. **Content slot**: how much vertical space remains for the chart zone after title, outer padding, gaps, notes, and reserve? |
| 26 | 3. **Support budget**: if metric cards, insight rails, legends, or footnotes share that zone, subtract their height first. Default to 0-2 support items around a main chart. |
| 27 | 4. **Final chart height**: hero / standard / compact? Pick one final px height for the chart frame. The `h-[Npx]` class must equal this final number. |
| 28 | 5. **Data semantics**: one value axis = one unit/meaning. Do not mix counts, percentages, money, or "new role" labels in one numeric dataset. |
| 29 | 6. **Takeaway**: what should the audience conclude from the chart? Write one visible interpretation sentence or a compact annotation rail; the chart cannot be the whole argument by itself. |
| 30 | 7. **Data shape**: labels + datasets. Are there enough data points for the chosen type? |
| 31 | 8. **Script event**: DOMContentLoaded only — the runtime loads Chart.js before this event fires. |
| 32 | |
| 33 | ## How to create a chart |
| 34 | |
| 35 | Every chart needs exactly two parts: an HTML frame with explicit height, and a script block using DOMContentLoaded + PPT.createChart. |
| 36 | |
| 37 | ### 1. HTML — chart frame with explicit height |
| 38 | |
| 39 | Before writing the chart frame, you MUST calculate the chart slot and then choose the actual chart frame height. Write the calculation as an HTML comment immediately before the chart frame, include the dedicated marker `@ppt-chart-height=N`, and make the marker, the **final chart height**, and the `h-[Npx]` value all use the same number. Never put `@ppt-chart-height=...` as visible text inside `.ppt-chart-frame`. Do NOT write a comment that ends with one number and a frame height that uses another number. Two terms: **content slot** = current canvas height − padding − title − gaps − reserve (the area for the chart plus its support modules); **chart slot** = content slot − support modules. The final `h-[Npx]` MUST equal the chart slot, never the content slot. |
| 40 | |
| 41 | ```html |
| 42 | <!-- height calc @ppt-chart-height=560: default 900 canvas example; content slot = 900 - 48(p-6) - 68(title) - 24(gap-6) - 28(h3) - 8(gap-2) - 32(reserve) = 692 (chart + support area); support rail = 132; chart slot = 692 - 132 = 560 -> h-[560px] --> |
| 43 | <div class="ppt-chart-frame relative h-[560px] w-full overflow-hidden"> |
| 44 | <canvas id="my-chart" class="h-full w-full"></canvas> |
| 45 | </div> |
| 46 | ``` |
| 47 | |
| 48 | The final number in the comment and `h-[Npx]` MUST match. If the comment ends with `chart height = 420`, the div MUST say `h-[420px]`; never write `chart height = 420` and then use `h-[240px]`. |
| 49 | |
| 50 | Use this exact comment structure for generated charts: |
| 51 | |
| 52 | ```html |
| 53 | <!-- height calc @ppt-chart-height=[final]: content slot = [current canvas height - ... - reserve] = [content slot]; support = [support height]; chart slot = [content slot - support] = [chart slot]; chart height = [role decision] = [final] --> |
| 54 | <!-- Example after replacing placeholders: height calc @ppt-chart-height=560: chart slot = 560; chart height = hero/main = 560 --> |
| 55 | <div class="ppt-chart-frame relative h-[560px] w-full overflow-hidden"> |
| 56 | ``` |
| 57 | |
| 58 | Calculation steps: |
| 59 | 1. Start from the current canvas height stated by the layout/canvas prompt (runtime page root has no default padding) |
| 60 | 2. Subtract outer padding (p-6=48, p-8=64) |
| 61 | 3. Subtract all modules above the chart: title, subtitle, metrics row, legends |
| 62 | 4. Subtract all gaps between modules |
| 63 | 5. If chart is inside a card: subtract card padding and card title |
| 64 | 6. Subtract a 24-40px safety reserve (use 40px on dense data slides) |
| 65 | 7. This gives the **content slot** for the chart zone. |
| 66 | 8. Subtract only sibling modules stacked above/below the chart inside the same column or vertical zone. Side-by-side modules in other columns share width, not height; do **not** divide the content slot by column count, and do not subtract a left metric rail from a right-column chart height. |
| 67 | 9. Choose the chart frame height from the chart slot (do not stop short and leave an empty band): |
| 68 | - Hero/main chart (dominant zone): 380–560px only when the chart is the primary evidence |
| 69 | - Standard chart beside/under 1–2 support modules: 280–360px, with breathing room around the support modules |
| 70 | - Compact supporting chart (one small module in a dense layout): 220–280px, with other modules kept concise |
| 71 | - If the computed chart slot is 600px+ and the chart is the primary evidence, use the top of the hero/main range (usually 520–560px). Do not calculate a 600+ slot and then choose 340px for the primary chart. |
| 72 | 10. If the chart slot is below 220px, redesign the chart/support relationship and run the layout width/height self-check again. |
| 73 | |
| 74 | Column budget rule: columns share width, not height. If the page uses `grid-cols-2`, the chart column still receives the full post-title vertical content slot. A bad calc is `content slot = 732; left metrics = 732/2; right side = 366`; the correct calc is `right column content slot = 732`, then subtract only the right-column heading, insight card, gaps, padding, and reserve. |
| 75 | |
| 76 | Do not pair a standard/tall chart with a two-row bottom card grid. If the slide has 4-6 additional facts, choose a density-appropriate structure such as in-chart annotations, one short evidence rail, a compact table, or a grouped list. A main chart usually gets 0-2 support items, not a full card set. |
| 77 | |
| 78 | Axis-heavy charts need extra space. For horizontal bars with 6+ categories, long labels, negative+positive ranges, or wide tick labels such as percentages, reserve 40-60px inside the chart options for the x-axis/tick area (`layout.padding.bottom`, tick padding, and a modest `maxTicksLimit`). If that would push support modules into a second row, redesign the support area as a side rail, annotation band, compact table, or in-chart callouts instead of shrinking the plot. |
| 79 | |
| 80 | Only the `.ppt-chart-frame` owns chart size. The `<canvas>` uses `class="h-full w-full"` and must not have `width`, `height`, or inline `style` size attributes in generated HTML. |
| 81 | |
| 82 | ### Data semantics — one axis, one meaning |
| 83 | |
| 84 | Do not mix units or meanings in a single Chart.js dataset or value axis. A bar chart whose x-axis is "变化率 %" must contain only percentages; do not put headcount values such as `850` or `1400` into that same dataset. If the source has both absolute counts and percent changes, choose one expression: |
| 85 | |
| 86 | - **Counts story**: grouped bars for 2022 vs 2026 headcount; put percent change in tooltips, labels, or a compact insight rail. |
| 87 | - **Change-rate story**: bars for comparable percentage changes only; represent "new role / 0 → 850" as an annotation or separate card, not as `850` on the percent axis. |
| 88 | |
| 89 | Chart.js category labels are plain text. Do not put HTML such as `<br>`, `<span>`, or inline styles inside `data.labels`; Chart.js will draw that HTML literally or mis-measure it. For multi-line category labels, use string-array labels (e.g. `['AI调校师', '约80→1,400']`) or keep details in tooltip callbacks / nearby annotations. |
| 90 | |
| 91 | ### Chart slides need interpretation |
| 92 | |
| 93 | A chart-only slide is usually under-explained even if the chart is large. Pair the main chart with one visible takeaway sentence and, when useful, 1-2 compact annotations, an insight rail, or a source/note line. These support modules explain how to read the chart; they must not repeat every bar/table row as equal-weight cards. If the source has rich context, use the support area for "so what", caveat, baseline, or implication rather than more labels. |
| 94 | |
| 95 | Bad patterns to avoid: |
| 96 | |
| 97 | ```html |
| 98 | <!-- height calc: current canvas height - 48(...) = available content slot --> |
| 99 | <div class="ppt-chart-frame relative h-[400px]">...</div> |
| 100 | |
| 101 | <div class="ppt-chart-frame relative h-64">...</div> |
| 102 | |
| 103 | <canvas id="chart" width="320" height="380" style="height: 240px"></canvas> |
| 104 | |
| 105 | <!-- Mixed units on one percentage axis: 850 people, 1650%, and 17.9% are not comparable --> |
| 106 | data: { labels: ['AI叙事设计师', 'AI调校师', '原画师'], datasets: [{ label: '变化率', data: [850, 1650, 17.9] }] } |
| 107 | |
| 108 | <!-- Chart.js labels are not HTML --> |
| 109 | labels: ['AI调校师<br><span>约80→1,400</span>'] |
| 110 | ``` |
| 111 | |
| 112 | ### 2. JavaScript — always use DOMContentLoaded + PPT.createChart |
| 113 | |
| 114 | ```html |
| 115 | <script> |
| 116 | document.addEventListener('DOMContentLoaded', function() { |
| 117 | PPT.createChart(document.getElementById('my-chart'), { |
| 118 | type: 'bar', |
| 119 | data: { |
| 120 | labels: ['A', 'B', 'C'], |
| 121 | datasets: [{ |
| 122 | label: 'Revenue', |
| 123 | data: [10, 20, 30], |
| 124 | backgroundColor: ['#3B82F6', '#10B981', '#F59E0B'] |
| 125 | }] |
| 126 | }, |
| 127 | options: { |
| 128 | responsive: true, |
| 129 | maintainAspectRatio: false, |
| 130 | plugins: { |
| 131 | legend: { position: 'bottom' } |
| 132 | }, |
| 133 | scales: { |
| 134 | y: { beginAtZero: true } |
| 135 | } |
| 136 | } |
| 137 | }); |
| 138 | }); |
| 139 | </script> |
| 140 | ``` |
| 141 | |
| 142 | This is the only correct event and the only correct API. The runtime loads Chart.js before `DOMContentLoaded` fires, so the helper is always available inside this callback. |
| 143 | |
| 144 | ### Complete working example |
| 145 | |
| 146 | ```html |
| 147 | <div class="grid grid-cols-2 gap-4"> |
| 148 | <div class="flex flex-col gap-2"> |
| 149 | <h3 class="text-2xl font-bold">Quarterly Revenue</h3> |
| 150 | <p class="text-lg text-gray-500">Growth trend across regions</p> |
| 151 | </div> |
| 152 | <!-- height calc @ppt-chart-height=560: default 900 canvas example; page content slot = 900 - 48(p-6) - 68(title/subtitle) - 24(gap-6) - 32(reserve) = 728; two columns share width, not height; right-column heading/support = 168; chart slot = 728 - 168 = 560; chart height = hero/main side chart = 560 --> |
| 153 | <div class="ppt-chart-frame relative h-[560px] w-full overflow-hidden"> |
| 154 | <canvas id="revenue-chart" class="h-full w-full"></canvas> |
| 155 | </div> |
| 156 | </div> |
| 157 | <script> |
| 158 | document.addEventListener('DOMContentLoaded', function() { |
| 159 | PPT.createChart(document.getElementById('revenue-chart'), { |
| 160 | type: 'bar', |
| 161 | data: { |
| 162 | labels: ['Q1', 'Q2', 'Q3', 'Q4'], |
| 163 | datasets: [{ |
| 164 | label: 'Revenue (M)', |
| 165 | data: [12, 19, 15, 22], |
| 166 | backgroundColor: '#3B82F6' |
| 167 | }] |
| 168 | }, |
| 169 | options: { |
| 170 | responsive: true, |
| 171 | maintainAspectRatio: false, |
| 172 | plugins: { legend: { display: false } }, |
| 173 | scales: { y: { beginAtZero: true } } |
| 174 | } |
| 175 | }); |
| 176 | }); |
| 177 | </script> |
| 178 | ``` |
| 179 | |
| 180 | ## Hard rules |
| 181 | |
| 182 | - Use `PPT.createChart(canvasElement, config)` — pass the canvas DOM element, not a 2D context. |
| 183 | - Wrap every `PPT.createChart` call inside `document.addEventListener('DOMContentLoaded', function() { ... })`. |
| 184 | - Put category labels in `data.labels` as plain strings or string arrays. If a category-axis `ticks.callback` is needed, return `this.getLabelForValue(value)`. |
| 185 | - Derive chart frame height from the layout budget (current canvas height minus all other modules and a 24-40px safety reserve), then choose a height that fits the chart role. Do not blindly use all leftover height. |
| 186 | - Every generated `.ppt-chart-frame` needs the height calc comment immediately before it. |
| 187 | - Do not add padding to the `.ppt-chart-frame` div — it wastes height budget without visual benefit. Padding belongs on the parent card/container, not on the chart frame itself. |
| 188 | - Do not put `width`, `height`, or inline `style` size attributes on `<canvas>`; the chart frame controls the size. |
| 189 | - Keep chart code local and deterministic. |
| 190 | |
| 191 | ## Failure repair strategy |
| 192 | |
| 193 | When a chart is blank or broken: |
| 194 | |
| 195 | 1. **Check the event**: the script must use `DOMContentLoaded`. Any other event name (ppt-ready, ppt-rendered, ppt-page-ready, load, etc.) will not fire or fire too early. |
| 196 | 2. **Check the canvas id**: the `getElementById` string must match the canvas `id` attribute exactly. |
| 197 | 3. **Check the height**: the chart frame `h-[Npx]` must be a positive number. A missing or zero height produces an invisible canvas. |
| 198 | 4. **Check the data format**: labels must be an array of plain strings/string arrays (no HTML), datasets an array of objects with a `data` array of numbers using one unit/meaning per value axis. |
| 199 | 5. **Remove duplicate scripts**: if editing a page that already has a chart, merge scripts rather than adding a second `DOMContentLoaded` listener for the same canvas. |
| 200 | |
| 201 | ## Chart animation boundary |
| 202 | |
| 203 | Two levels of chart animation, each handled by a different system: |
| 204 | |
| 205 | - **Chart container entrance** (the whole chart block fading/sliding in): add `data-anim` on the `.ppt-chart-frame` div. This is a standard layout animation — see animation skill. |
| 206 | - **Chart internal drawing** (bars growing, lines drawing, pie slices rotating): controlled by Chart.js `options.animation`. The runtime defaults handle this; you rarely need to customize it. |
| 207 | - **Do not** write custom JS timelines that animate individual chart elements. Use `data-anim` for the container, and Chart.js options for the internals. |
| 208 | |
| 209 | ## Cross-skill references |
| 210 | |
| 211 | - Budget chart height from the current slide height (see layout skill). Title + modules + gaps + chart frame + 24-40px reserve <= current canvas height. |
| 212 | - Chart container entrance animation uses `data-anim` on the chart frame div (see animation skill). |
| 213 |