返回 oh-my-ppt
chart.md
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 when the chart is the primary evidence, standard 280–360px with a compact support set, compact supporting 220–280px. Keep an actual nonzero gap between independent chart and support modules. 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 the space needed for its labels, axes, and intended reading distance (often around 220px on a 1600×900 canvas), 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 Avoid a two-row bottom card grid when it competes with a standard/tall chart. Additional facts can use in-chart annotations, an evidence rail, grouped labels, a compact table, or a compact aligned data mosaic with an explicit budget and a clearly dominant chart.
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 an actual nonzero gap.
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 selection guide
131
132 ### First decide whether this is a chart
133
134 Use Chart.js only when the source contains a factual relationship that benefits from visual encoding. Never create numbers, proportions, or time allocations merely to make a chart.
135
136 | Source relationship | Use | Do not substitute |
137 | --- | --- | --- |
138 | Same-unit values across categories or a ranking | Bar; horizontal bar for long labels | A doughnut, which makes ranking harder to read |
139 | Ordered observations over time | Line; area only for cumulative magnitude | A bar chart when continuity/change is the message |
140 | Verified, mutually exclusive parts of one stated total | Doughnut or pie, 4-6 slices; values must add to the shown total or 100% | A topic list, agenda, or invented allocation |
141 | Scores on the same scale across dimensions | Radar, with 4-8 shared-scale axes | Mixed metrics or unrelated concepts |
142 | Two or three numeric variables | Scatter or bubble | Category cards pretending to show correlation |
143 | No numeric relationship: course map, agenda, process, stages, hierarchy, qualitative comparison | Page-native cards, route, matrix, process, or relationship diagram | Any Chart.js chart |
144
145 For example, "40 minutes and four course themes" is a four-part course map unless the source explicitly allocates the minutes. Do not label an invented split as a chart. A confirmed equal allocation may use four equal segments, but it should still read as a course map rather than evidence of a meaningful difference.
146
147 ### bar — comparisons across categories
148
149 Best for: revenue by quarter, survey results, regional comparisons.
150
151 ```js
152 {
153 type: 'bar',
154 data: {
155 labels: ['Q1', 'Q2', 'Q3', 'Q4'],
156 datasets: [{
157 label: 'Revenue (M)',
158 data: [12, 19, 15, 22],
159 backgroundColor: '#3B82F6'
160 }]
161 },
162 options: {
163 responsive: true,
164 maintainAspectRatio: false,
165 plugins: { legend: { display: false } },
166 scales: { y: { beginAtZero: true } }
167 }
168 }
169 ```
170
171 Horizontal bar: set `options.indexAxis: 'y'`. Good for ranking lists or long category labels.
172
173 ### line — trends over time
174
175 Best for: monthly trends, growth trajectories, multi-series comparison over time.
176
177 ```js
178 {
179 type: 'line',
180 data: {
181 labels: ['Jan', 'Feb', 'Mar', 'Apr', 'May'],
182 datasets: [
183 {
184 label: '2025',
185 data: [30, 45, 42, 60, 55],
186 borderColor: '#3B82F6',
187 tension: 0.3,
188 fill: false
189 },
190 {
191 label: '2024',
192 data: [20, 35, 38, 45, 40],
193 borderColor: '#94A3B8',
194 tension: 0.3,
195 fill: false
196 }
197 ]
198 },
199 options: {
200 responsive: true,
201 maintainAspectRatio: false,
202 plugins: { legend: { position: 'bottom' } },
203 scales: { y: { beginAtZero: true } }
204 }
205 }
206 ```
207
208 Use `tension: 0.3` for smooth curves. Use `fill: true` with `backgroundColor` at low opacity for area charts.
209
210 ### pie / doughnut — verified parts of a whole
211
212 Best for: market share, budget allocation, category breakdown. Limit to 4–6 slices for readability; use only when slices are mutually exclusive, share one unit, and add to a stated total or 100%.
213
214 ```js
215 {
216 type: 'doughnut',
217 data: {
218 labels: ['Product A', 'Product B', 'Product C', 'Other'],
219 datasets: [{
220 data: [40, 25, 20, 15],
221 backgroundColor: ['#3B82F6', '#10B981', '#F59E0B', '#94A3B8']
222 }]
223 },
224 options: {
225 responsive: true,
226 maintainAspectRatio: false,
227 plugins: {
228 legend: { position: 'right' }
229 }
230 }
231 }
232 ```
233
234 Doughnut is usually better than pie — the center can hold a total or label. Do not use either for an agenda, four equal topics, a process, or an estimated allocation that the source does not state.
235
236 ### radar — multi-axis profiles
237
238 Best for: skill comparisons, product feature matrices, performance across dimensions. Use 4–8 axes.
239
240 ```js
241 {
242 type: 'radar',
243 data: {
244 labels: ['Speed', 'Reliability', 'Cost', 'Support', 'Features'],
245 datasets: [
246 {
247 label: 'Product A',
248 data: [85, 70, 60, 90, 75],
249 borderColor: '#3B82F6',
250 backgroundColor: 'rgba(59, 130, 246, 0.15)'
251 },
252 {
253 label: 'Product B',
254 data: [65, 85, 80, 60, 90],
255 borderColor: '#10B981',
256 backgroundColor: 'rgba(16, 185, 129, 0.15)'
257 }
258 ]
259 },
260 options: {
261 responsive: true,
262 maintainAspectRatio: false,
263 scales: {
264 r: { beginAtZero: true, max: 100 }
265 }
266 }
267 }
268 ```
269
270 ### scatter / bubble — correlations
271
272 Best for: showing correlations, distributions, or data points with 2–3 dimensions.
273
274 ```js
275 // Scatter: two variables
276 {
277 type: 'scatter',
278 data: {
279 datasets: [{
280 label: 'Team A',
281 data: [{ x: 10, y: 20 }, { x: 15, y: 35 }, { x: 25, y: 30 }],
282 backgroundColor: '#3B82F6'
283 }]
284 }
285 }
286
287 // Bubble: three variables (x, y, r=size)
288 {
289 type: 'bubble',
290 data: {
291 datasets: [{
292 label: 'Markets',
293 data: [
294 { x: 20, y: 30, r: 15 },
295 { x: 40, y: 10, r: 8 },
296 { x: 30, y: 22, r: 20 }
297 ]
298 }]
299 }
300 }
301 ```
302
303 ## Updating an existing chart
304
305 Use `PPT.updateChart` to modify data or options without recreating the chart:
306
307 ```js
308 // Patch data and options
309 PPT.updateChart('#my-chart', {
310 data: { labels: ['New A', 'New B'], datasets: [{ data: [50, 60] }] },
311 mode: 'active'
312 });
313
314 // Or use a callback for complex updates
315 PPT.updateChart('#my-chart', function(chart) {
316 chart.data.datasets[0].data.push(42);
317 chart.update();
318 });
319 ```
320
321 `PPT.updateChart` accepts a canvas element, a CSS selector string, or an existing Chart instance.
322
323 ## Category axis labels
324
325 Put category labels in `data.labels` as plain strings or string arrays:
326
327 ```js
328 data: {
329 labels: ['Q1', 'Q2', 'Q3'],
330 datasets: [{ data: [12, 18, 26] }]
331 }
332 ```
333
334 For multi-line labels, use Chart.js string-array labels:
335
336 ```js
337 data: {
338 labels: [['AI调校师', '约80→1,400'], ['中割/补间', '9,300→5,600']],
339 datasets: [{ label: '2026人数', data: [1400, 5600] }]
340 }
341 ```
342
343 Do not put HTML in labels. Chart.js does not render `<br>`, `<span>`, or inline style strings inside axis labels.
344
345 The runtime auto-injects `ticks.callback` for category axes. If you need a custom callback:
346
347 ```js
348 ticks: {
349 callback: function(value) {
350 return this.getLabelForValue(value);
351 }
352 }
353 ```
354
355 ## Layout integration tips
356
357 - Reserve space for legends, long labels, and axis ticks when budgeting chart height.
358 - Prefer fewer categories over tiny unreadable labels. If labels are long, use horizontal bar (`indexAxis: 'y'`).
359 - Place charts as dedicated visual modules in the grid, not nested inside cards with other content.
360 - Always set `responsive: true` and `maintainAspectRatio: false` — they work with the explicit-height frame.
361 - For a chart + metric cards layout, use `grid grid-cols-[1fr_1fr]` or `grid grid-cols-3` with the chart spanning 2 columns.
362 - Start with 0-2 compact support blocks around a standard/tall chart. Add a larger parallel set only when the chart remains dominant and the full layout budget stays clear.
363 - 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.
364
365 ## Common patterns
366
367 - **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.
368 - **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.
369 - **Metrics row + chart below**: compact `grid-cols-4` metric cards (p-3) on top, single chart spanning full width below.
370
370 lines MARKDOWN