返回 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 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
355 lines MARKDOWN