| 1 | # Chart Reference |
| 2 | |
| 3 | Deep-dive examples, layout integration patterns, and Chart.js options that work reliably in Oh My PPT. |
| 4 | |
| 5 | ## Complete working example |
| 6 | |
| 7 | Copy this pattern for every chart. Adapt the type, data, and options. |
| 8 | |
| 9 | ```html |
| 10 | <!-- height calc @ppt-chart-height=560: default 900 canvas example; content slot = 900 - 64(p-8) - 80(title/subtitle) - 24(gap-6) - 32(reserve) = 700; support note = 140; chart slot = 700 - 140 = 560; chart height = hero/main = 560 --> |
| 11 | <div class="ppt-chart-frame relative h-[560px] w-full overflow-hidden"> |
| 12 | <canvas id="chart-sales" class="h-full w-full"></canvas> |
| 13 | </div> |
| 14 | |
| 15 | <script> |
| 16 | document.addEventListener('DOMContentLoaded', function() { |
| 17 | PPT.createChart(document.getElementById('chart-sales'), { |
| 18 | type: 'bar', |
| 19 | data: { |
| 20 | labels: ['Q1', 'Q2', 'Q3'], |
| 21 | datasets: [{ |
| 22 | label: 'Revenue', |
| 23 | data: [12, 18, 26] |
| 24 | }] |
| 25 | }, |
| 26 | options: { |
| 27 | responsive: true, |
| 28 | maintainAspectRatio: false, |
| 29 | plugins: { legend: { display: false } }, |
| 30 | scales: { |
| 31 | y: { beginAtZero: true } |
| 32 | } |
| 33 | } |
| 34 | }); |
| 35 | }); |
| 36 | </script> |
| 37 | ``` |
| 38 | |
| 39 | ## How PPT.createChart works |
| 40 | |
| 41 | `PPT.createChart` wraps `new Chart()` and adds several layers of safety: |
| 42 | |
| 43 | 1. **Readiness guard**: waits for Chart.js v4 to be loaded before creating the instance. |
| 44 | 2. **Auto-cleanup**: if a chart already exists on the same canvas, it calls `.destroy()` first — safe to re-render on the same element. |
| 45 | 3. **Number formatting**: injects tick callbacks for value axes (trims floating-point noise) and tooltip callbacks that prefix the dataset label. |
| 46 | 4. **Category label fix**: on category axes, injects `this.getLabelForValue(value)` so labels always render as strings. |
| 47 | 5. **Post-creation resize**: waits 2 animation frames, then calls `chart.resize()` and `chart.update("none")` to ensure correct rendering after layout settles. |
| 48 | 6. **Instance registry**: tracks the chart in a global registry for `PPT.updateChart`, `PPT.destroyChart`, and `PPT.resizeCharts`. |
| 49 | |
| 50 | Use `PPT.createChart` — never `new Chart(...)`. |
| 51 | |
| 52 | ## Chart frame height guide |
| 53 | |
| 54 | The `.ppt-chart-frame` parent must have an explicit `h-[Npx]` height. Chart.js requires a concrete pixel height to render — relative values (`flex-1`, `h-full`, `min-h-*`) are unreliable. |
| 55 | |
| 56 | ### Mandatory: calculate slot, choose chart height, then write — numbers must match |
| 57 | |
| 58 | Before writing the chart frame, calculate the chart slot, choose the actual chart frame height for the slide role, and write both in an HTML comment immediately before the chart frame. The comment MUST include the dedicated marker `@ppt-chart-height=N`, and the marker value MUST equal `h-[Npx]`. Never put `@ppt-chart-height=...` as visible text inside `.ppt-chart-frame`. 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. |
| 59 | |
| 60 | ```html |
| 61 | <!-- height calc @ppt-chart-height=520: default 900 canvas example; content slot = 900 - 48(p-6) - 80(title+subtitle) - 24(gap) - 40(reserve) = 708 (chart + support area); support cards below = 188; chart slot = 708 - 188 = 520 -> h-[520px] --> |
| 62 | <div class="ppt-chart-frame relative h-[520px] w-full overflow-hidden"> |
| 63 | <canvas id="my-chart" class="h-full w-full"></canvas> |
| 64 | </div> |
| 65 | ``` |
| 66 | |
| 67 | The final number in the comment and `h-[Npx]` MUST match. Do NOT leave a comment such as `chart height = 420` and then use `h-[240px]`; write the final chart-height decision explicitly and copy that exact number into `h-[Npx]`. |
| 68 | |
| 69 | Calculation steps: |
| 70 | 1. Start from the **current canvas height** stated by the layout/canvas prompt (runtime page root has no default padding) |
| 71 | 2. Subtract outer padding (p-6=48, p-8=64) |
| 72 | 3. Subtract all modules above the chart: title, subtitle, metrics row, legends |
| 73 | 4. Subtract all gaps between modules |
| 74 | 5. If chart is inside a card: subtract card padding and card title/heading |
| 75 | 6. Subtract a 24-40px safety reserve |
| 76 | 7. This gives the **content slot** for the chart zone. |
| 77 | 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. |
| 78 | 9. Choose chart height from the chart slot without creating a dense wall of content: hero/main 380–560px only when the chart is the primary evidence, standard 280–360px with 1–2 support items, compact supporting 220–280px. 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. |
| 79 | 10. If the chart slot is below 220px, redesign the chart/support relationship and run the layout width/height self-check again. |
| 80 | |
| 81 | 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. |
| 82 | |
| 83 | Never place a two-row bottom card grid under a standard/tall chart. Additional facts should use a density-appropriate structure such as in-chart annotations, one short evidence rail, grouped labels, or a compact table. |
| 84 | |
| 85 | ### Data semantics — one axis, one meaning |
| 86 | |
| 87 | Each numeric dataset/value axis must use one unit and one meaning. Do not mix headcounts, percentages, money, scores, or "new role" sentinel values in the same bar/line dataset. If the source table contains both 2022/2026 counts and change rates, use grouped bars for the counts and put change rates in tooltips/annotations; or use a percent-change chart and move "0 → 850 / new role" to a callout instead of plotting `850` on a percent axis. |
| 88 | |
| 89 | ### Chart slides need interpretation |
| 90 | |
| 91 | A chart is evidence, not the whole slide. A main chart should be paired with one visible takeaway sentence and, when the content needs it, 1-2 compact annotations, an insight rail, or a source/note line. Use this support area for the interpretation: baseline, "so what", caveat, implication, or the reason the chart matters. Do not repeat every category as equal-weight cards below/beside the chart. |
| 92 | |
| 93 | ### What not to use for height |
| 94 | |
| 95 | Use only `h-[Npx]` for the chart frame. These do not work reliably: |
| 96 | |
| 97 | - `h-full` — depends on parent having a fixed height, which may not exist |
| 98 | - `flex-1` — the chart frame is not inside a flex column with bounded height |
| 99 | - `min-h-*` — sets a minimum but Chart.js needs an exact height to render |
| 100 | - `h-64` or other Tailwind scale shortcuts — they use rem units which may not match the layout budget |
| 101 | |
| 102 | Canvas sizing rule: the `<canvas>` should only use `class="h-full w-full"`. Do not add `width`, `height`, or inline `style` sizes to the canvas in generated HTML; Chart.js and the PPT runtime resize the canvas from the frame. |
| 103 | |
| 104 | ### Height role guide |
| 105 | |
| 106 | The chart fills its computed slot — these ranges guide the role and proportion; they are NOT a reason to stop short and leave the zone empty: |
| 107 | |
| 108 | - Hero/main chart: the chart is the slide's primary module (it lives in the dominant zone). Size the frame to the computed chart slot, typically 380–560px. Do not cap it at 240/340 and leave the rest empty. |
| 109 | - Standard chart: 280–360px, when the chart shares the slide with 1–2 support modules that sit beside/below it with breathing room. |
| 110 | - Compact supporting chart: 220–280px, when the chart is one small module inside a dense layout and other modules stay concise. |
| 111 | |
| 112 | Size the chart frame so the zone feels intentional. Do NOT cap the chart at a tiny height and leave a large accidental empty band below it — if the chart is the main module, it should be visually dominant. Exception: if the chart's cell/zone is much taller than the chart needs (e.g. a 5-bar chart in a ~600px grid cell), do not stretch the chart to an awkward height and do not fill the rest with multiple cards — keep it readable and add only the support the content actually needs, in the form that best serves the reading path. If the slot is smaller than the role minimum, reduce text/modules before shrinking the chart further. |
| 113 | |
| 114 | ### Bad examples |
| 115 | |
| 116 | Do not generate these patterns: |
| 117 | |
| 118 | ```html |
| 119 | <!-- Comment ends at raw available slot, but frame uses a different number --> |
| 120 | <!-- height calc: current canvas height - 48(p-6) - 80(title) - 24(gap) = available content slot --> |
| 121 | <div class="ppt-chart-frame relative h-[360px] w-full overflow-hidden"></div> |
| 122 | |
| 123 | <!-- Tailwind scale shortcut is not a pixel budget --> |
| 124 | <div class="ppt-chart-frame relative h-72 w-full overflow-hidden"></div> |
| 125 | |
| 126 | <!-- Canvas must not own size --> |
| 127 | <canvas id="chart" width="600" height="300" style="height: 300px"></canvas> |
| 128 | ``` |
| 129 | |
| 130 | ## Chart type selection guide |
| 131 | |
| 132 | ### bar — comparisons across categories |
| 133 | |
| 134 | Best for: revenue by quarter, survey results, regional comparisons. |
| 135 | |
| 136 | ```js |
| 137 | { |
| 138 | type: 'bar', |
| 139 | data: { |
| 140 | labels: ['Q1', 'Q2', 'Q3', 'Q4'], |
| 141 | datasets: [{ |
| 142 | label: 'Revenue (M)', |
| 143 | data: [12, 19, 15, 22], |
| 144 | backgroundColor: '#3B82F6' |
| 145 | }] |
| 146 | }, |
| 147 | options: { |
| 148 | responsive: true, |
| 149 | maintainAspectRatio: false, |
| 150 | plugins: { legend: { display: false } }, |
| 151 | scales: { y: { beginAtZero: true } } |
| 152 | } |
| 153 | } |
| 154 | ``` |
| 155 | |
| 156 | Horizontal bar: set `options.indexAxis: 'y'`. Good for ranking lists or long category labels. |
| 157 | |
| 158 | ### line — trends over time |
| 159 | |
| 160 | Best for: monthly trends, growth trajectories, multi-series comparison over time. |
| 161 | |
| 162 | ```js |
| 163 | { |
| 164 | type: 'line', |
| 165 | data: { |
| 166 | labels: ['Jan', 'Feb', 'Mar', 'Apr', 'May'], |
| 167 | datasets: [ |
| 168 | { |
| 169 | label: '2025', |
| 170 | data: [30, 45, 42, 60, 55], |
| 171 | borderColor: '#3B82F6', |
| 172 | tension: 0.3, |
| 173 | fill: false |
| 174 | }, |
| 175 | { |
| 176 | label: '2024', |
| 177 | data: [20, 35, 38, 45, 40], |
| 178 | borderColor: '#94A3B8', |
| 179 | tension: 0.3, |
| 180 | fill: false |
| 181 | } |
| 182 | ] |
| 183 | }, |
| 184 | options: { |
| 185 | responsive: true, |
| 186 | maintainAspectRatio: false, |
| 187 | plugins: { legend: { position: 'bottom' } }, |
| 188 | scales: { y: { beginAtZero: true } } |
| 189 | } |
| 190 | } |
| 191 | ``` |
| 192 | |
| 193 | Use `tension: 0.3` for smooth curves. Use `fill: true` with `backgroundColor` at low opacity for area charts. |
| 194 | |
| 195 | ### pie / doughnut — parts of a whole |
| 196 | |
| 197 | Best for: market share, budget allocation, category breakdown. Limit to 4–6 slices for readability. |
| 198 | |
| 199 | ```js |
| 200 | { |
| 201 | type: 'doughnut', |
| 202 | data: { |
| 203 | labels: ['Product A', 'Product B', 'Product C', 'Other'], |
| 204 | datasets: [{ |
| 205 | data: [40, 25, 20, 15], |
| 206 | backgroundColor: ['#3B82F6', '#10B981', '#F59E0B', '#94A3B8'] |
| 207 | }] |
| 208 | }, |
| 209 | options: { |
| 210 | responsive: true, |
| 211 | maintainAspectRatio: false, |
| 212 | plugins: { |
| 213 | legend: { position: 'right' } |
| 214 | } |
| 215 | } |
| 216 | } |
| 217 | ``` |
| 218 | |
| 219 | Doughnut is usually better than pie — the center can hold a total or label. |
| 220 | |
| 221 | ### radar — multi-axis profiles |
| 222 | |
| 223 | Best for: skill comparisons, product feature matrices, performance across dimensions. Use 4–8 axes. |
| 224 | |
| 225 | ```js |
| 226 | { |
| 227 | type: 'radar', |
| 228 | data: { |
| 229 | labels: ['Speed', 'Reliability', 'Cost', 'Support', 'Features'], |
| 230 | datasets: [ |
| 231 | { |
| 232 | label: 'Product A', |
| 233 | data: [85, 70, 60, 90, 75], |
| 234 | borderColor: '#3B82F6', |
| 235 | backgroundColor: 'rgba(59, 130, 246, 0.15)' |
| 236 | }, |
| 237 | { |
| 238 | label: 'Product B', |
| 239 | data: [65, 85, 80, 60, 90], |
| 240 | borderColor: '#10B981', |
| 241 | backgroundColor: 'rgba(16, 185, 129, 0.15)' |
| 242 | } |
| 243 | ] |
| 244 | }, |
| 245 | options: { |
| 246 | responsive: true, |
| 247 | maintainAspectRatio: false, |
| 248 | scales: { |
| 249 | r: { beginAtZero: true, max: 100 } |
| 250 | } |
| 251 | } |
| 252 | } |
| 253 | ``` |
| 254 | |
| 255 | ### scatter / bubble — correlations |
| 256 | |
| 257 | Best for: showing correlations, distributions, or data points with 2–3 dimensions. |
| 258 | |
| 259 | ```js |
| 260 | // Scatter: two variables |
| 261 | { |
| 262 | type: 'scatter', |
| 263 | data: { |
| 264 | datasets: [{ |
| 265 | label: 'Team A', |
| 266 | data: [{ x: 10, y: 20 }, { x: 15, y: 35 }, { x: 25, y: 30 }], |
| 267 | backgroundColor: '#3B82F6' |
| 268 | }] |
| 269 | } |
| 270 | } |
| 271 | |
| 272 | // Bubble: three variables (x, y, r=size) |
| 273 | { |
| 274 | type: 'bubble', |
| 275 | data: { |
| 276 | datasets: [{ |
| 277 | label: 'Markets', |
| 278 | data: [ |
| 279 | { x: 20, y: 30, r: 15 }, |
| 280 | { x: 40, y: 10, r: 8 }, |
| 281 | { x: 30, y: 22, r: 20 } |
| 282 | ] |
| 283 | }] |
| 284 | } |
| 285 | } |
| 286 | ``` |
| 287 | |
| 288 | ## Updating an existing chart |
| 289 | |
| 290 | Use `PPT.updateChart` to modify data or options without recreating the chart: |
| 291 | |
| 292 | ```js |
| 293 | // Patch data and options |
| 294 | PPT.updateChart('#my-chart', { |
| 295 | data: { labels: ['New A', 'New B'], datasets: [{ data: [50, 60] }] }, |
| 296 | mode: 'active' |
| 297 | }); |
| 298 | |
| 299 | // Or use a callback for complex updates |
| 300 | PPT.updateChart('#my-chart', function(chart) { |
| 301 | chart.data.datasets[0].data.push(42); |
| 302 | chart.update(); |
| 303 | }); |
| 304 | ``` |
| 305 | |
| 306 | `PPT.updateChart` accepts a canvas element, a CSS selector string, or an existing Chart instance. |
| 307 | |
| 308 | ## Category axis labels |
| 309 | |
| 310 | Put category labels in `data.labels` as plain strings or string arrays: |
| 311 | |
| 312 | ```js |
| 313 | data: { |
| 314 | labels: ['Q1', 'Q2', 'Q3'], |
| 315 | datasets: [{ data: [12, 18, 26] }] |
| 316 | } |
| 317 | ``` |
| 318 | |
| 319 | For multi-line labels, use Chart.js string-array labels: |
| 320 | |
| 321 | ```js |
| 322 | data: { |
| 323 | labels: [['AI调校师', '约80→1,400'], ['中割/补间', '9,300→5,600']], |
| 324 | datasets: [{ label: '2026人数', data: [1400, 5600] }] |
| 325 | } |
| 326 | ``` |
| 327 | |
| 328 | Do not put HTML in labels. Chart.js does not render `<br>`, `<span>`, or inline style strings inside axis labels. |
| 329 | |
| 330 | The runtime auto-injects `ticks.callback` for category axes. If you need a custom callback: |
| 331 | |
| 332 | ```js |
| 333 | ticks: { |
| 334 | callback: function(value) { |
| 335 | return this.getLabelForValue(value); |
| 336 | } |
| 337 | } |
| 338 | ``` |
| 339 | |
| 340 | ## Layout integration tips |
| 341 | |
| 342 | - Reserve space for legends, long labels, and axis ticks when budgeting chart height. |
| 343 | - Prefer fewer categories over tiny unreadable labels. If labels are long, use horizontal bar (`indexAxis: 'y'`). |
| 344 | - Place charts as dedicated visual modules in the grid, not nested inside cards with other content. |
| 345 | - Always set `responsive: true` and `maintainAspectRatio: false` — they work with the explicit-height frame. |
| 346 | - For a chart + metric cards layout, use `grid grid-cols-[1fr_1fr]` or `grid grid-cols-3` with the chart spanning 2 columns. |
| 347 | - Keep support modules to 0-2 compact blocks around a standard/tall chart. Do not add a second row of summary cards below it. |
| 348 | - Axis-heavy horizontal bars (6+ categories, long y labels, negative+positive x ranges, or wide percentage ticks) need 40-60px of internal axis/tick budget. Use `layout.padding.bottom`, tick padding, and a modest `maxTicksLimit`; if the chart still needs more room, recompose the support content into a side rail, annotation band, compact table, or in-chart callouts. |
| 349 | |
| 350 | ## Common patterns |
| 351 | |
| 352 | - **Hero metric + chart**: `grid grid-cols-[1fr_2fr]` — metric card on the left with `text-5xl` number, chart on the right. Size the right-column chart to its chart slot (post-heading vertical space) so the zone feels intentional; do not cap it so short that it leaves an accidental empty band, and do not stretch it beyond a readable hero height. |
| 353 | - **Two charts side by side**: `grid grid-cols-2` — each chart in its own column with a small heading above. Each chart fills its own column's chart slot; columns share width, not height. |
| 354 | - **Metrics row + chart below**: compact `grid-cols-4` metric cards (p-3) on top, single chart spanning full width below. |
| 355 |