@sliiu/present-renderer 0.0.0-stage → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (126) hide show
  1. package/COPYRIGHT +5 -0
  2. package/LICENSE +661 -0
  3. package/README.md +8 -2
  4. package/THIRD-PARTY-NOTICES.md +221 -0
  5. package/dist/cjs/arrow-geom.js +38 -0
  6. package/dist/cjs/canvas-backend.js +1368 -0
  7. package/dist/cjs/cell-borders.js +25 -0
  8. package/dist/cjs/chart-canvas.js +1059 -0
  9. package/dist/cjs/chart-ex-render.js +275 -0
  10. package/dist/cjs/chart-geom.js +313 -0
  11. package/dist/cjs/chart-svg.js +1110 -0
  12. package/dist/cjs/compose.js +74 -0
  13. package/dist/cjs/effect-clip.js +652 -0
  14. package/dist/cjs/entrance-hide.js +63 -0
  15. package/dist/cjs/font-stack.js +103 -0
  16. package/dist/cjs/hit-geom.js +50 -0
  17. package/dist/cjs/hybrid-backend.js +93 -0
  18. package/dist/cjs/hybrid-segments.js +54 -0
  19. package/dist/cjs/index.js +70 -0
  20. package/dist/cjs/linked-src.js +116 -0
  21. package/dist/cjs/metafile-preview.js +142 -0
  22. package/dist/cjs/model3d-frame.js +199 -0
  23. package/dist/cjs/model3d-render.js +78 -0
  24. package/dist/cjs/morph-transition.js +57 -0
  25. package/dist/cjs/node-affine.js +79 -0
  26. package/dist/cjs/ole-render.js +291 -0
  27. package/dist/cjs/package.json +1 -0
  28. package/dist/cjs/paint-canvas.js +701 -0
  29. package/dist/cjs/paint-svg.js +674 -0
  30. package/dist/cjs/scene-node.js +762 -0
  31. package/dist/cjs/selection.js +130 -0
  32. package/dist/cjs/shape-effects.js +66 -0
  33. package/dist/cjs/shape-front.js +38 -0
  34. package/dist/cjs/stage-chrome.js +15 -0
  35. package/dist/cjs/svg-backend.js +2172 -0
  36. package/dist/cjs/text-autofit.js +48 -0
  37. package/dist/cjs/text-hit.js +284 -0
  38. package/dist/cjs/text-layout.js +408 -0
  39. package/dist/cjs/text-shared.js +238 -0
  40. package/dist/cjs/text-stacked.js +82 -0
  41. package/dist/cjs/text-warp.js +377 -0
  42. package/dist/cjs/text-wordart.js +33 -0
  43. package/dist/cjs/video-frame.js +113 -0
  44. package/dist/cjs/webgl-model3d-backend.js +285 -0
  45. package/dist/cjs/webgl-shape-backend.js +360 -0
  46. package/dist/esm/arrow-geom.js +33 -0
  47. package/dist/esm/canvas-backend.js +1363 -0
  48. package/dist/esm/cell-borders.js +21 -0
  49. package/dist/esm/chart-canvas.js +1056 -0
  50. package/dist/esm/chart-ex-render.js +261 -0
  51. package/dist/esm/chart-geom.js +296 -0
  52. package/dist/esm/chart-svg.js +1107 -0
  53. package/dist/esm/compose.js +71 -0
  54. package/dist/esm/effect-clip.js +648 -0
  55. package/dist/esm/entrance-hide.js +59 -0
  56. package/dist/esm/font-stack.js +100 -0
  57. package/dist/esm/hit-geom.js +45 -0
  58. package/dist/esm/hybrid-backend.js +89 -0
  59. package/dist/esm/hybrid-segments.js +51 -0
  60. package/dist/esm/index.js +24 -0
  61. package/dist/esm/linked-src.js +109 -0
  62. package/dist/esm/metafile-preview.js +135 -0
  63. package/dist/esm/model3d-frame.js +158 -0
  64. package/dist/esm/model3d-render.js +73 -0
  65. package/dist/esm/morph-transition.js +54 -0
  66. package/dist/esm/node-affine.js +71 -0
  67. package/dist/esm/ole-render.js +286 -0
  68. package/dist/esm/paint-canvas.js +693 -0
  69. package/dist/esm/paint-svg.js +670 -0
  70. package/dist/esm/scene-node.js +750 -0
  71. package/dist/esm/selection.js +122 -0
  72. package/dist/esm/shape-effects.js +60 -0
  73. package/dist/esm/shape-front.js +35 -0
  74. package/dist/esm/stage-chrome.js +12 -0
  75. package/dist/esm/svg-backend.js +2168 -0
  76. package/dist/esm/text-autofit.js +44 -0
  77. package/dist/esm/text-hit.js +280 -0
  78. package/dist/esm/text-layout.js +400 -0
  79. package/dist/esm/text-shared.js +221 -0
  80. package/dist/esm/text-stacked.js +78 -0
  81. package/dist/esm/text-warp.js +371 -0
  82. package/dist/esm/text-wordart.js +30 -0
  83. package/dist/esm/video-frame.js +109 -0
  84. package/dist/esm/webgl-model3d-backend.js +248 -0
  85. package/dist/esm/webgl-shape-backend.js +323 -0
  86. package/dist/types/arrow-geom.d.ts +10 -0
  87. package/dist/types/canvas-backend.d.ts +55 -0
  88. package/dist/types/cell-borders.d.ts +9 -0
  89. package/dist/types/chart-canvas.d.ts +2 -0
  90. package/dist/types/chart-ex-render.d.ts +79 -0
  91. package/dist/types/chart-geom.d.ts +112 -0
  92. package/dist/types/chart-svg.d.ts +2 -0
  93. package/dist/types/compose.d.ts +2 -0
  94. package/dist/types/effect-clip.d.ts +10 -0
  95. package/dist/types/entrance-hide.d.ts +16 -0
  96. package/dist/types/font-stack.d.ts +9 -0
  97. package/dist/types/hit-geom.d.ts +15 -0
  98. package/dist/types/hybrid-backend.d.ts +21 -0
  99. package/dist/types/hybrid-segments.d.ts +11 -0
  100. package/dist/types/index.d.ts +24 -0
  101. package/dist/types/linked-src.d.ts +13 -0
  102. package/dist/types/metafile-preview.d.ts +11 -0
  103. package/dist/types/model3d-frame.d.ts +8 -0
  104. package/dist/types/model3d-render.d.ts +4 -0
  105. package/dist/types/morph-transition.d.ts +15 -0
  106. package/dist/types/node-affine.d.ts +22 -0
  107. package/dist/types/ole-render.d.ts +7 -0
  108. package/dist/types/paint-canvas.d.ts +22 -0
  109. package/dist/types/paint-svg.d.ts +19 -0
  110. package/dist/types/scene-node.d.ts +128 -0
  111. package/dist/types/selection.d.ts +22 -0
  112. package/dist/types/shape-effects.d.ts +13 -0
  113. package/dist/types/shape-front.d.ts +4 -0
  114. package/dist/types/stage-chrome.d.ts +8 -0
  115. package/dist/types/svg-backend.d.ts +88 -0
  116. package/dist/types/text-autofit.d.ts +7 -0
  117. package/dist/types/text-hit.d.ts +7 -0
  118. package/dist/types/text-layout.d.ts +35 -0
  119. package/dist/types/text-shared.d.ts +86 -0
  120. package/dist/types/text-stacked.d.ts +11 -0
  121. package/dist/types/text-warp.d.ts +37 -0
  122. package/dist/types/text-wordart.d.ts +18 -0
  123. package/dist/types/video-frame.d.ts +2 -0
  124. package/dist/types/webgl-model3d-backend.d.ts +25 -0
  125. package/dist/types/webgl-shape-backend.d.ts +33 -0
  126. package/package.json +43 -4
@@ -0,0 +1,275 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.CHART_EX_PALETTE = void 0;
4
+ exports.layoutHistogram = layoutHistogram;
5
+ exports.layoutPareto = layoutPareto;
6
+ exports.layoutBoxWhisker = layoutBoxWhisker;
7
+ exports.layoutRegion = layoutRegion;
8
+ exports.normalizeChartExSlices = normalizeChartExSlices;
9
+ exports.layoutTreemap = layoutTreemap;
10
+ exports.layoutSunburst = layoutSunburst;
11
+ exports.layoutWaterfall = layoutWaterfall;
12
+ exports.layoutFunnel = layoutFunnel;
13
+ exports.sunburstArcPath = sunburstArcPath;
14
+ exports.polygonPath = polygonPath;
15
+ exports.CHART_EX_PALETTE = [
16
+ "#4E79A7",
17
+ "#F28E2B",
18
+ "#E15759",
19
+ "#76B7B2",
20
+ "#59A14F",
21
+ "#EDC948",
22
+ "#B07AA1",
23
+ "#FF9DA7",
24
+ "#9C755F",
25
+ "#BAB0AC",
26
+ ];
27
+ function columnBarsFromSlices(slices, plot) {
28
+ const items = slices.filter((s) => s.value > 0);
29
+ if (!items.length)
30
+ return [];
31
+ const max = Math.max(...items.map((s) => s.value), 1);
32
+ const gap = plot.w / items.length;
33
+ const barW = gap * 0.65;
34
+ return items.map((s, i) => {
35
+ const h = (s.value / max) * plot.h;
36
+ return {
37
+ ...s,
38
+ x: plot.x + gap * i + (gap - barW) / 2,
39
+ y: plot.y + plot.h - h,
40
+ w: barW,
41
+ h,
42
+ };
43
+ });
44
+ }
45
+ function layoutHistogram(slices, plot) {
46
+ return columnBarsFromSlices(slices, plot);
47
+ }
48
+ function layoutPareto(slices, plot) {
49
+ const sorted = [...slices].filter((s) => s.value > 0).sort((a, b) => b.value - a.value);
50
+ const bars = columnBarsFromSlices(sorted, plot);
51
+ const total = sorted.reduce((sum, s) => sum + s.value, 0);
52
+ let cum = 0;
53
+ const line = [];
54
+ const gap = plot.w / Math.max(sorted.length, 1);
55
+ for (let i = 0; i < sorted.length; i++) {
56
+ cum += sorted[i].value;
57
+ const pct = total > 0 ? cum / total : 0;
58
+ line.push([plot.x + gap * (i + 0.5), plot.y + plot.h - pct * plot.h]);
59
+ }
60
+ return { bars, line };
61
+ }
62
+ function layoutBoxWhisker(slices, plot) {
63
+ const items = slices.filter((s) => s.value >= 0);
64
+ if (!items.length)
65
+ return [];
66
+ const max = Math.max(...items.map((s) => s.value), 1);
67
+ const slot = plot.w / items.length;
68
+ const boxW = slot * 0.45;
69
+ return items.map((s, i) => {
70
+ const v = s.value;
71
+ const cx = plot.x + slot * (i + 0.5);
72
+ const toY = (val) => plot.y + plot.h - (Math.min(val, max) / max) * plot.h;
73
+ const spread = Math.max(v * 0.15, max * 0.04);
74
+ return {
75
+ label: s.label,
76
+ color: s.color,
77
+ cx,
78
+ whiskerLow: toY(Math.max(0, v - spread * 2)),
79
+ q1: toY(Math.max(0, v - spread)),
80
+ median: toY(v),
81
+ q3: toY(Math.min(max, v + spread)),
82
+ whiskerHigh: toY(Math.min(max, v + spread * 2)),
83
+ boxW,
84
+ };
85
+ });
86
+ }
87
+ function layoutRegion(slices, plot) {
88
+ const items = slices.filter((s) => s.value > 0);
89
+ if (!items.length)
90
+ return [];
91
+ const max = Math.max(...items.map((s) => s.value), 1);
92
+ const rowH = plot.h / items.length;
93
+ return items.map((s, i) => ({
94
+ ...s,
95
+ x: plot.x,
96
+ y: plot.y + rowH * i + 2,
97
+ w: (s.value / max) * plot.w * 0.95,
98
+ h: Math.max(4, rowH - 4),
99
+ }));
100
+ }
101
+ function normalizeChartExSlices(def, opts) {
102
+ const categories = def.categories?.length
103
+ ? def.categories
104
+ : Array.from({ length: Math.max(1, def.series[0]?.values.length ?? 1) }, (_, i) => `Cat ${i + 1}`);
105
+ const series = def.series.length > 0
106
+ ? def.series
107
+ : [{ name: "Series 1", values: categories.map(() => 0) }];
108
+ const s0 = series[0];
109
+ const palette = def.colors ?? exports.CHART_EX_PALETTE;
110
+ const vary = def.varyColors !== false;
111
+ return categories.map((label, i) => {
112
+ const raw = s0.values[i] ?? 0;
113
+ return {
114
+ label: label || `Item ${i + 1}`,
115
+ value: opts?.preserveSign ? raw : Math.max(0, Math.abs(raw)),
116
+ color: vary ? palette[i % palette.length] : s0.color ?? palette[i % palette.length],
117
+ };
118
+ });
119
+ }
120
+ /** Slice-and-dice treemap — 足够表达占比结构,编辑预览用。 */
121
+ function layoutTreemap(slices, plot) {
122
+ const items = slices.filter((s) => s.value > 0);
123
+ if (items.length === 0)
124
+ return [];
125
+ const pad = 2;
126
+ const cells = [];
127
+ const walk = (list, x, y, w, h, depth) => {
128
+ if (list.length === 0 || w <= 0 || h <= 0)
129
+ return;
130
+ if (list.length === 1) {
131
+ const s = list[0];
132
+ cells.push({
133
+ ...s,
134
+ x: x + pad / 2,
135
+ y: y + pad / 2,
136
+ w: Math.max(0, w - pad),
137
+ h: Math.max(0, h - pad),
138
+ });
139
+ return;
140
+ }
141
+ const total = list.reduce((sum, s) => sum + s.value, 0);
142
+ if (total <= 0)
143
+ return;
144
+ const mid = Math.ceil(list.length / 2);
145
+ const left = list.slice(0, mid);
146
+ const right = list.slice(mid);
147
+ const leftSum = left.reduce((sum, s) => sum + s.value, 0);
148
+ const ratio = leftSum / total;
149
+ const horizontal = depth % 2 === 0 ? w >= h : h > w;
150
+ if (horizontal) {
151
+ const lw = w * ratio;
152
+ walk(left, x, y, lw, h, depth + 1);
153
+ walk(right, x + lw, y, w - lw, h, depth + 1);
154
+ }
155
+ else {
156
+ const lh = h * ratio;
157
+ walk(left, x, y, w, lh, depth + 1);
158
+ walk(right, x, y + lh, w, h - lh, depth + 1);
159
+ }
160
+ };
161
+ walk(items, plot.x, plot.y, plot.w, plot.h, 0);
162
+ return cells;
163
+ }
164
+ /** 单层 sunburst:内环空心 + 外环扇区(扁平 categories)。 */
165
+ function layoutSunburst(slices, plot) {
166
+ const items = slices.filter((s) => s.value > 0);
167
+ const total = items.reduce((sum, s) => sum + s.value, 0);
168
+ if (total <= 0)
169
+ return [];
170
+ const cx = plot.x + plot.w / 2;
171
+ const cy = plot.y + plot.h / 2;
172
+ const r = Math.min(plot.w, plot.h) / 2 - 4;
173
+ const r0 = r * 0.38;
174
+ const r1 = r;
175
+ let a = -Math.PI / 2;
176
+ const arcs = [];
177
+ for (const s of items) {
178
+ const span = (s.value / total) * Math.PI * 2;
179
+ if (span <= 0)
180
+ continue;
181
+ arcs.push({ ...s, cx, cy, r0, r1, a0: a, a1: a + span });
182
+ a += span;
183
+ }
184
+ return arcs;
185
+ }
186
+ function layoutWaterfall(slices, plot) {
187
+ const items = slices.filter((s) => s.value !== 0 || slices.length === 1);
188
+ if (items.length === 0)
189
+ return [];
190
+ let running = 0;
191
+ let minY = 0;
192
+ let maxY = 0;
193
+ const deltas = items.map((s) => s.value);
194
+ for (const v of deltas) {
195
+ const top = running + Math.max(0, v);
196
+ const bottom = running + Math.min(0, v);
197
+ minY = Math.min(minY, bottom, top);
198
+ maxY = Math.max(maxY, bottom, top);
199
+ running += v;
200
+ }
201
+ minY = Math.min(minY, 0);
202
+ maxY = Math.max(maxY, running, 1);
203
+ const range = maxY - minY || 1;
204
+ const barW = (plot.w / Math.max(items.length, 1)) * 0.55;
205
+ const gap = plot.w / Math.max(items.length, 1);
206
+ running = 0;
207
+ const bars = [];
208
+ for (let i = 0; i < items.length; i++) {
209
+ const s = items[i];
210
+ const v = s.value;
211
+ const y0 = running;
212
+ const y1 = running + v;
213
+ const top = Math.max(y0, y1);
214
+ const bottom = Math.min(y0, y1);
215
+ const pxTop = plot.y + plot.h - ((top - minY) / range) * plot.h;
216
+ const pxBottom = plot.y + plot.h - ((bottom - minY) / range) * plot.h;
217
+ bars.push({
218
+ ...s,
219
+ x: plot.x + gap * i + (gap - barW) / 2,
220
+ y: Math.min(pxTop, pxBottom),
221
+ w: barW,
222
+ h: Math.max(2, Math.abs(pxBottom - pxTop)),
223
+ kind: v >= 0 ? "increase" : "decrease",
224
+ });
225
+ running = y1;
226
+ }
227
+ return bars;
228
+ }
229
+ function layoutFunnel(slices, plot) {
230
+ const items = slices.filter((s) => s.value > 0);
231
+ if (items.length === 0)
232
+ return [];
233
+ const maxVal = Math.max(...items.map((s) => s.value));
234
+ const segH = plot.h / items.length;
235
+ const cx = plot.x + plot.w / 2;
236
+ const segs = [];
237
+ for (let i = 0; i < items.length; i++) {
238
+ const s = items[i];
239
+ const topVal = i === 0 ? s.value : items[i - 1].value;
240
+ const botVal = s.value;
241
+ const topW = (topVal / maxVal) * plot.w * 0.92;
242
+ const botW = (botVal / maxVal) * plot.w * 0.92;
243
+ const y0 = plot.y + segH * i;
244
+ const y1 = plot.y + segH * (i + 1) - 1;
245
+ segs.push({
246
+ ...s,
247
+ points: [
248
+ [cx - topW / 2, y0],
249
+ [cx + topW / 2, y0],
250
+ [cx + botW / 2, y1],
251
+ [cx - botW / 2, y1],
252
+ ],
253
+ });
254
+ }
255
+ return segs;
256
+ }
257
+ function sunburstArcPath(arc) {
258
+ const { cx, cy, r0, r1, a0, a1 } = arc;
259
+ const large = a1 - a0 > Math.PI ? 1 : 0;
260
+ const x0 = cx + Math.cos(a0) * r1;
261
+ const y0 = cy + Math.sin(a0) * r1;
262
+ const x1 = cx + Math.cos(a1) * r1;
263
+ const y1 = cy + Math.sin(a1) * r1;
264
+ const ix0 = cx + Math.cos(a0) * r0;
265
+ const iy0 = cy + Math.sin(a0) * r0;
266
+ const ix1 = cx + Math.cos(a1) * r0;
267
+ const iy1 = cy + Math.sin(a1) * r0;
268
+ return `M ${x0} ${y0} A ${r1} ${r1} 0 ${large} 1 ${x1} ${y1} L ${ix1} ${iy1} A ${r0} ${r0} 0 ${large} 0 ${ix0} ${iy0} Z`;
269
+ }
270
+ function polygonPath(points) {
271
+ if (points.length === 0)
272
+ return "";
273
+ const [first, ...rest] = points;
274
+ return `M ${first[0]} ${first[1]} ${rest.map((p) => `L ${p[0]} ${p[1]}`).join(" ")} Z`;
275
+ }
@@ -0,0 +1,313 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.seriesXyPoints = seriesXyPoints;
4
+ exports.linearFit = linearFit;
5
+ exports.estimateTextWidth = estimateTextWidth;
6
+ exports.chartPadding = chartPadding;
7
+ exports.barGeometry = barGeometry;
8
+ exports.barLabelAnchor = barLabelAnchor;
9
+ exports.applyValueAxisScale = applyValueAxisScale;
10
+ exports.surfaceIsoQuads = surfaceIsoQuads;
11
+ exports.comboXyPlotX = comboXyPlotX;
12
+ exports.pointLabelText = pointLabelText;
13
+ exports.chart3DOffset = chart3DOffset;
14
+ exports.chartTextStyle = chartTextStyle;
15
+ exports.mixHex = mixHex;
16
+ exports.niceTicks = niceTicks;
17
+ exports.shadeHex = shadeHex;
18
+ /** 散点 / 组合图系列的 (x,y) 点:优先 series.points,否则用分类下标 + values。 */
19
+ function seriesXyPoints(def, s) {
20
+ if (s.points?.length)
21
+ return s.points;
22
+ const defPts = def.points;
23
+ const idx = def.series.indexOf(s);
24
+ if (idx >= 0 && defPts?.[idx]?.length)
25
+ return defPts[idx];
26
+ return s.values.map((y, i) => ({ x: i, y }));
27
+ }
28
+ function linearFit(xs, ys) {
29
+ const n = Math.min(xs.length, ys.length);
30
+ if (n < 2)
31
+ return null;
32
+ let sx = 0;
33
+ let sy = 0;
34
+ let sxx = 0;
35
+ let sxy = 0;
36
+ for (let i = 0; i < n; i++) {
37
+ sx += xs[i];
38
+ sy += ys[i];
39
+ sxx += xs[i] * xs[i];
40
+ sxy += xs[i] * ys[i];
41
+ }
42
+ const den = n * sxx - sx * sx;
43
+ if (Math.abs(den) < 1e-12)
44
+ return null;
45
+ const b = (n * sxy - sx * sy) / den;
46
+ const a = (sy - b * sx) / n;
47
+ return { a, b };
48
+ }
49
+ /** Rough advance of `text` at `size` px: CJK glyphs are square, Latin about 0.58em. Legend rows used `length × 6` and overprinted CJK series names. */
50
+ function estimateTextWidth(text, size) {
51
+ let w = 0;
52
+ for (const ch of text)
53
+ w += /[\u2e80-\u9fff\uff00-\uffef]/.test(ch) ? size : size * 0.58;
54
+ return w;
55
+ }
56
+ /** Chart types whose category labels run along the bottom edge of the plot. */
57
+ /** Types that carry a labelled axis along the bottom edge — categories for most, the value axis for a horizontal bar chart. */
58
+ const BOTTOM_CATEGORY_AXIS = new Set(["column", "bar", "line", "area", "scatter", "bubble", "stock", "waterfall", "pareto", "histogram", "boxWhisker"]);
59
+ /**
60
+ * Bottom padding of the plot: room for the category labels (they used to be drawn 14px under
61
+ * the plot into an 8px pad, and the legend was then drawn over them) plus a legend row.
62
+ */
63
+ function chartPadding(def, textSize) {
64
+ const hasCombo = def.series.some((s) => s.chartType && s.chartType !== def.type);
65
+ const catAxis = BOTTOM_CATEGORY_AXIS.has(def.type) || hasCombo;
66
+ const legend = def.legend ? textSize + 10 : 0;
67
+ const labels = catAxis ? textSize + 10 : 8;
68
+ return { bottom: labels + legend, legend };
69
+ }
70
+ function barGeometry(def, plot, sharedTicks) {
71
+ const cats = def.categories ?? [];
72
+ const isHoriz = def.type === "bar";
73
+ const stacked = !!def.stacked || !!def.percentStacked;
74
+ let yMin = 0;
75
+ let yMax = 1;
76
+ const totals = new Array(cats.length).fill(0);
77
+ if (stacked) {
78
+ for (const s of def.series)
79
+ for (let i = 0; i < s.values.length; i++)
80
+ totals[i] = (totals[i] ?? 0) + (s.values[i] ?? 0);
81
+ yMax = Math.max(...totals, 0);
82
+ yMin = Math.min(0, ...totals);
83
+ }
84
+ else {
85
+ for (const s of def.series) {
86
+ for (const v of s.values) {
87
+ if (v > yMax)
88
+ yMax = v;
89
+ if (v < yMin)
90
+ yMin = v;
91
+ }
92
+ }
93
+ }
94
+ if (def.percentStacked) {
95
+ yMin = 0;
96
+ yMax = 1;
97
+ }
98
+ else {
99
+ ({ yMin, yMax } = applyValueAxisScale(yMin, yMax, def));
100
+ }
101
+ const ticks = sharedTicks ?? niceTicks(yMin, yMax, 5);
102
+ const groupStep = (isHoriz ? plot.h : plot.w) / Math.max(cats.length, 1);
103
+ const inner = groupStep * 0.7;
104
+ const barW = stacked ? inner : inner / Math.max(def.series.length, 1);
105
+ return { isHoriz, stacked, ticks, tmin: ticks[0], tmax: ticks[ticks.length - 1], groupStep, inner, barW, totals };
106
+ }
107
+ /**
108
+ * Where the value label of series `si`, category `ci` goes: just past the free end of a
109
+ * clustered bar (above a column, right of a horizontal bar), or centred in a stacked segment.
110
+ * `stackBefore` is the signed running total of the segments under this one (stacked only).
111
+ */
112
+ function barLabelAnchor(geo, plot, si, ci, v, stackBefore = 0) {
113
+ const span = geo.tmax - geo.tmin || 1;
114
+ const gap = 3;
115
+ if (geo.isHoriz) {
116
+ const yCat = plot.y + geo.groupStep * ci + (geo.groupStep - geo.inner) / 2;
117
+ const yMid = (geo.stacked ? yCat : yCat + si * geo.barW) + geo.barW / 2;
118
+ if (geo.stacked) {
119
+ const mid = stackBefore + v / 2;
120
+ return { x: plot.x + ((mid - geo.tmin) / span) * plot.w, y: yMid, anchor: "middle", baseline: "middle" };
121
+ }
122
+ const end = plot.x + ((v - geo.tmin) / span) * plot.w;
123
+ return { x: v >= 0 ? end + gap : end - gap, y: yMid, anchor: v >= 0 ? "start" : "end", baseline: "middle" };
124
+ }
125
+ const xCat = plot.x + geo.groupStep * ci + (geo.groupStep - geo.inner) / 2;
126
+ const xMid = (geo.stacked ? xCat : xCat + si * geo.barW) + geo.barW / 2;
127
+ const yOf = (val) => plot.y + plot.h - ((val - geo.tmin) / span) * plot.h;
128
+ if (geo.stacked)
129
+ return { x: xMid, y: yOf(stackBefore + v / 2), anchor: "middle", baseline: "middle" };
130
+ // Negative columns hang below the baseline; their label goes under the bar's free end.
131
+ return v >= 0 ? { x: xMid, y: yOf(v) - gap, anchor: "middle", baseline: "bottom" } : { x: xMid, y: yOf(v) + gap, anchor: "middle", baseline: "top" };
132
+ }
133
+ function applyValueAxisScale(yMin, yMax, def) {
134
+ const val = def.axes?.find((a) => a.type === "val" && (a.position === "l" || a.position === "r")) ??
135
+ def.axes?.find((a) => a.type === "val");
136
+ if (val?.scaling?.min != null)
137
+ yMin = val.scaling.min;
138
+ if (val?.scaling?.max != null)
139
+ yMax = val.scaling.max;
140
+ return { yMin, yMax };
141
+ }
142
+ /**
143
+ * 曲面图等距网格:系列 = Y 行,分类 = X 列,values = Z。
144
+ * 单系列时沿 Y 挤一条薄带,避免退化成折线。
145
+ */
146
+ function surfaceIsoQuads(def, plot) {
147
+ const nX = Math.max(2, def.categories?.length ?? Math.max(0, ...def.series.map((s) => s.values.length)));
148
+ const nYraw = def.series.length;
149
+ const nY = Math.max(2, nYraw);
150
+ let zMin = 0;
151
+ let zMax = 1;
152
+ for (const s of def.series) {
153
+ for (const v of s.values) {
154
+ if (v < zMin)
155
+ zMin = v;
156
+ if (v > zMax)
157
+ zMax = v;
158
+ }
159
+ }
160
+ const zAt = (j, i) => {
161
+ const row = def.series[Math.min(j, nYraw - 1)] ?? def.series[0];
162
+ if (!row)
163
+ return 0;
164
+ return row.values[Math.min(i, Math.max(0, row.values.length - 1))] ?? 0;
165
+ };
166
+ const project = (i, j, z) => {
167
+ const nx = (nX <= 1 ? 0 : i / (nX - 1));
168
+ const ny = (nY <= 1 ? 0 : j / (nY - 1));
169
+ const nz = (z - zMin) / (zMax - zMin || 1);
170
+ return {
171
+ x: plot.x + plot.w * 0.18 + (nx - ny) * plot.w * 0.4 + ny * plot.w * 0.12,
172
+ y: plot.y + plot.h * 0.78 - (nx + ny) * plot.h * 0.22 - nz * plot.h * 0.48,
173
+ };
174
+ };
175
+ const quads = [];
176
+ for (let j = nY - 2; j >= 0; j--) {
177
+ for (let i = 0; i < nX - 1; i++) {
178
+ const z00 = zAt(j, i);
179
+ const z10 = zAt(j, i + 1);
180
+ const z11 = zAt(j + 1, i + 1);
181
+ const z01 = zAt(j + 1, i);
182
+ const color = def.series[Math.min(j, nYraw - 1)]?.color ?? "#4E79A7";
183
+ quads.push({
184
+ pts: [project(i, j, z00), project(i + 1, j, z10), project(i + 1, j + 1, z11), project(i, j + 1, z01)],
185
+ color,
186
+ stroke: !!def.wireframe,
187
+ });
188
+ }
189
+ }
190
+ return quads;
191
+ }
192
+ function comboXyPlotX(xVal, cats, plot, xs) {
193
+ const n = Math.max(1, cats.length);
194
+ if (!cats.length) {
195
+ const xMin = xs.length ? Math.min(...xs) : 0;
196
+ const xMax = xs.length ? Math.max(...xs) : 1;
197
+ return plot.x + ((xVal - xMin) / (xMax - xMin || 1)) * plot.w;
198
+ }
199
+ const xMin = xs.length ? Math.min(...xs) : 0;
200
+ const xMax = xs.length ? Math.max(...xs) : 0;
201
+ const asCategory = xMin >= -0.5 && xMax <= Math.max(n, 1) && xMax - xMin <= n + 0.5;
202
+ if (asCategory) {
203
+ if (n === 1)
204
+ return plot.x + plot.w / 2;
205
+ return plot.x + (xVal / Math.max(1, n - 1)) * plot.w;
206
+ }
207
+ return plot.x + ((xVal - xMin) / (xMax - xMin || 1)) * plot.w;
208
+ }
209
+ function pointLabelText(def, s, i, v) {
210
+ const d = s.dataLabels ?? def.dataLabels;
211
+ const showVal = d?.showVal ?? !!def.showValues;
212
+ if (!d && !showVal)
213
+ return undefined;
214
+ const parts = [];
215
+ if (d?.showSerName && s.name)
216
+ parts.push(s.name);
217
+ if (d?.showCatName && def.categories?.[i])
218
+ parts.push(def.categories[i]);
219
+ if (showVal) {
220
+ parts.push(Number.isInteger(v) ? String(v) : String(Math.round(v * 10) / 10));
221
+ }
222
+ if (d?.showPercent) {
223
+ const total = s.values.reduce((a, b) => a + Math.abs(b), 0);
224
+ parts.push(`${total ? Math.round((Math.abs(v) / total) * 100) : 0}%`);
225
+ }
226
+ if (d?.showBubbleSize && s.points?.[i]?.z != null)
227
+ parts.push(String(s.points[i].z));
228
+ const text = parts.filter(Boolean).join(d?.separator ?? ", ");
229
+ return text || undefined;
230
+ }
231
+ /** 3D 柱/条预览的挤出偏移(view3D rotX/rotY → 屏幕位移)。 */
232
+ function chart3DOffset(def) {
233
+ if (!def.chart3D && !def.view3D)
234
+ return null;
235
+ const rotY = def.view3D?.rotY ?? 20;
236
+ const rotX = def.view3D?.rotX ?? 15;
237
+ const depth = Math.max(4, Math.min(16, (def.view3D?.depthPercent ?? 100) / 12));
238
+ return {
239
+ dx: depth * Math.sin((rotY * Math.PI) / 180),
240
+ dy: -depth * Math.sin((rotX * Math.PI) / 180),
241
+ };
242
+ }
243
+ /**
244
+ * Chart-wide text and line colours. `def.textColor` / `def.lineColor` (chartSpace txPr, axis spPr)
245
+ * win; otherwise the neutral greys PowerPoint uses on a white chart area. A single text colour
246
+ * gives labels at full strength and ticks / legend at 75% towards the line colour so a dark-theme
247
+ * chart keeps the same hierarchy a light one has.
248
+ */
249
+ function chartTextStyle(def) {
250
+ const size = def.textSize && def.textSize > 0 ? def.textSize : 13;
251
+ if (!def.textColor && !def.lineColor)
252
+ return { text: "#111827", muted: "#6b7280", axis: "#9ca3af", grid: "#e5e7eb", size };
253
+ const text = def.textColor ?? "#111827";
254
+ const line = def.lineColor ?? mixHex(text, "#808080", 0.6);
255
+ return { text, muted: def.textColor ? mixHex(text, line, 0.35) : "#6b7280", axis: line, grid: line, size };
256
+ }
257
+ /** Linear mix of two #rrggbb colours; `t` = weight of `b`. */
258
+ function mixHex(a, b, t) {
259
+ const pa = parseHex(a);
260
+ const pb = parseHex(b);
261
+ if (!pa || !pb)
262
+ return a;
263
+ const ch = (i) => Math.round(pa[i] * (1 - t) + pb[i] * t).toString(16).padStart(2, "0");
264
+ return `#${ch(0)}${ch(1)}${ch(2)}`;
265
+ }
266
+ function parseHex(color) {
267
+ const hex = color.replace("#", "");
268
+ if (hex.length !== 6 && hex.length !== 3)
269
+ return null;
270
+ const full = hex.length === 3 ? hex.split("").map((c) => c + c).join("") : hex;
271
+ const n = parseInt(full, 16);
272
+ if (!Number.isFinite(n))
273
+ return null;
274
+ return [(n >> 16) & 0xff, (n >> 8) & 0xff, n & 0xff];
275
+ }
276
+ /**
277
+ * "Nice" value-axis ticks: about `count` steps at 1 / 2 / 5 × 10ⁿ. (0..1500 → 0, 500, 1000, 1500;
278
+ * not one line per hundred.)
279
+ */
280
+ function niceTicks(min, max, count = 5) {
281
+ if (min === max) {
282
+ if (min === 0)
283
+ return [0, 1];
284
+ min = min - Math.abs(min) * 0.1;
285
+ max = max + Math.abs(max) * 0.1;
286
+ }
287
+ const range = max - min;
288
+ const rough = range / count;
289
+ const step = Math.pow(10, Math.floor(Math.log10(rough)));
290
+ const frac = rough / step;
291
+ const m = frac > 5 ? 10 : frac > 2 ? 5 : frac > 1 ? 2 : 1;
292
+ const s = step * m;
293
+ const lo = Math.floor(min / s) * s;
294
+ const hi = Math.ceil(max / s) * s;
295
+ const out = [];
296
+ for (let v = lo; v <= hi + 1e-9; v += s)
297
+ out.push(Number(v.toFixed(10)));
298
+ return out;
299
+ }
300
+ function shadeHex(color, factor) {
301
+ const hex = color.replace("#", "");
302
+ if (hex.length !== 6 && hex.length !== 3)
303
+ return color;
304
+ const full = hex.length === 3 ? hex.split("").map((c) => c + c).join("") : hex;
305
+ const n = parseInt(full, 16);
306
+ if (!Number.isFinite(n))
307
+ return color;
308
+ const ch = (shift) => {
309
+ const v = Math.max(0, Math.min(255, Math.round(((n >> shift) & 0xff) * factor)));
310
+ return v.toString(16).padStart(2, "0");
311
+ };
312
+ return `#${ch(16)}${ch(8)}${ch(0)}`;
313
+ }