@taloswork/ose-shared 0.1.11 → 0.1.13

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 (159) hide show
  1. package/package.json +3 -3
  2. package/skills/document/SKILL.md +135 -219
  3. package/skills/document/art-directions/base.css +27 -2
  4. package/skills/document/art-directions/cinematic-depth.css +3 -1
  5. package/skills/document/art-directions/collage-paper.css +5 -1
  6. package/skills/document/art-directions/editorial-cut.css +5 -1
  7. package/skills/document/art-directions/geometric-pop.css +6 -0
  8. package/skills/document/art-directions/gradient-mesh.css +5 -1
  9. package/skills/document/art-directions/illustrated-doodle.css +5 -0
  10. package/skills/document/art-directions/luxury-ornament.css +4 -1
  11. package/skills/document/art-directions/maximalist-blocks.css +6 -0
  12. package/skills/document/art-directions/technical-grid.css +4 -0
  13. package/skills/document/assets/fonts/fonts.json +2 -2
  14. package/skills/document/assets/pdf-fonts/NotoSansSC-PDF-Bold.ttf +0 -0
  15. package/skills/document/assets/pdf-fonts/NotoSansSC-PDF-Regular.ttf +0 -0
  16. package/skills/document/assets/pdf-fonts/OFL.txt +93 -0
  17. package/skills/document/assets/pdf-fonts/README.md +35 -0
  18. package/skills/document/assets/tessdata/LICENSE-Apache-2.0.txt +202 -0
  19. package/skills/document/assets/tessdata/README.md +21 -0
  20. package/skills/document/assets/tessdata/chi_sim.traineddata.gz +0 -0
  21. package/skills/document/assets/tessdata/eng.traineddata.gz +0 -0
  22. package/skills/document/bin/deck-build.cjs +12563 -83
  23. package/skills/document/bin/deck-chart.cjs +771 -0
  24. package/skills/document/bin/deck-plan.cjs +12414 -0
  25. package/skills/document/bin/deck-qa.cjs +1075 -266
  26. package/skills/document/bin/deck-scenario.cjs +1363 -0
  27. package/skills/document/bin/deck-styles.cjs +1604 -0
  28. package/skills/document/bin/doc-convert.cjs +308 -292
  29. package/skills/document/bin/doc-fill.cjs +73264 -192
  30. package/skills/document/bin/doc-gen.cjs +2491 -312
  31. package/skills/document/bin/doc-run.cjs +162 -24
  32. package/skills/document/bin/doc-scan.cjs +13 -0
  33. package/skills/document/bin/doc-text.cjs +13 -0
  34. package/skills/document/bin/doc-validate.cjs +838 -68
  35. package/skills/document/bin/doc-xml.cjs +9 -5
  36. package/skills/document/bin/docx-annotate.cjs +95 -19
  37. package/skills/document/bin/docx-defaults.cjs +9907 -0
  38. package/skills/document/bin/fontkit.cjs +1 -0
  39. package/skills/document/bin/html2pptx.cjs +36622 -31394
  40. package/skills/document/bin/ocr-worker.cjs +1 -0
  41. package/skills/document/bin/ocr.cjs +131 -40
  42. package/skills/document/bin/pdf-form.cjs +682 -177
  43. package/skills/document/bin/pdf-images.cjs +13 -0
  44. package/skills/document/bin/pdf-ops.cjs +642 -199
  45. package/skills/document/bin/pdf-render.cjs +19 -3
  46. package/skills/document/bin/pdf-tables.cjs +13 -0
  47. package/skills/document/bin/pptx-clone.cjs +3 -3
  48. package/skills/document/bin/pptx-notes.cjs +10038 -0
  49. package/skills/document/bin/pptx-slide.cjs +3 -3
  50. package/skills/document/bin/pptx-tokenize.cjs +19 -4
  51. package/skills/document/bin/template-skin.cjs +10910 -0
  52. package/skills/document/bin/xlsx-query.cjs +192 -61
  53. package/skills/document/bin-reference.md +61 -33
  54. package/skills/document/catalog.json +33 -32
  55. package/skills/document/content-pipeline.md +179 -257
  56. package/skills/document/deck-design.md +378 -638
  57. package/skills/document/generated-visuals.md +99 -0
  58. package/skills/document/layouts/MAINTAINERS.md +33 -0
  59. package/skills/document/layouts/README.md +49 -36
  60. package/skills/document/layouts/big-stat.tpl +1 -1
  61. package/skills/document/layouts/capacity.json +1 -0
  62. package/skills/document/layouts/cards-3.tpl +1 -1
  63. package/skills/document/layouts/chart-full.tpl +1 -1
  64. package/skills/document/layouts/chart-kpi.tpl +1 -1
  65. package/skills/document/layouts/comparison.tpl +1 -1
  66. package/skills/document/layouts/data-table.tpl +1 -1
  67. package/skills/document/layouts/funnel.tpl +1 -1
  68. package/skills/document/layouts/gantt.tpl +1 -1
  69. package/skills/document/layouts/hub-flow.tpl +4 -0
  70. package/skills/document/layouts/icon-row.tpl +1 -1
  71. package/skills/document/layouts/image-grid.tpl +1 -1
  72. package/skills/document/layouts/matrix-2x2.tpl +1 -1
  73. package/skills/document/layouts/mock-qa.tpl +4 -0
  74. package/skills/document/layouts/profiles.json +2 -2
  75. package/skills/document/layouts/pyramid.tpl +1 -1
  76. package/skills/document/layouts/radar.tpl +1 -1
  77. package/skills/document/layouts/ring-progress.tpl +1 -1
  78. package/skills/document/layouts/slots.json +1 -1
  79. package/skills/document/layouts/steps-vertical.tpl +1 -1
  80. package/skills/document/layouts/steps.tpl +1 -1
  81. package/skills/document/layouts/three-kpi.tpl +1 -1
  82. package/skills/document/layouts/timeline.tpl +1 -1
  83. package/skills/document/layouts/toc-panel.tpl +2 -2
  84. package/skills/document/layouts/two-kpi.tpl +2 -2
  85. package/skills/document/layouts/versus.tpl +1 -1
  86. package/skills/document/motion/README.md +3 -2
  87. package/skills/document/office.md +3 -3
  88. package/skills/document/packs/README.md +1 -1
  89. package/skills/document/packs/board-brief/backdrop.html +2 -0
  90. package/skills/document/packs/board-brief/pack.css +2 -1
  91. package/skills/document/packs/mckinsey-photo/backdrop.html +2 -0
  92. package/skills/document/packs/mckinsey-photo/pack.css +2 -1
  93. package/skills/document/packs/press-room/backdrop.html +2 -0
  94. package/skills/document/packs/press-room/pack.css +2 -1
  95. package/skills/document/packs/prismatic-3d/backdrop.html +2 -0
  96. package/skills/document/packs/prismatic-3d/pack.css +4 -1
  97. package/skills/document/pdf.md +60 -43
  98. package/skills/document/rewrite.md +257 -309
  99. package/skills/document/scenarios.json +1307 -0
  100. package/skills/document/scenarios.schema.json +104 -0
  101. package/skills/document/styles/README.md +15 -25
  102. package/skills/document/styles/_backdrops/rimlight.html +3 -2
  103. package/skills/document/styles/_contract-defaults.css +31 -2
  104. package/skills/document/styles/_samples/_groups/biz-premium.webp +0 -0
  105. package/skills/document/styles/_samples/mckinsey.webp +0 -0
  106. package/skills/document/styles/anthropic/skin.css +6 -3
  107. package/skills/document/styles/apple/skin.css +4 -3
  108. package/skills/document/styles/bistro-warm/backdrop.html +2 -0
  109. package/skills/document/styles/bistro-warm/skin.css +4 -1
  110. package/skills/document/styles/black-gold/skin.css +6 -5
  111. package/skills/document/styles/bold-clash/backdrop.html +2 -0
  112. package/skills/document/styles/bold-clash/skin.css +2 -1
  113. package/skills/document/styles/branding/skin.css +4 -4
  114. package/skills/document/styles/campus-teal/backdrop.html +2 -0
  115. package/skills/document/styles/campus-teal/skin.css +2 -1
  116. package/skills/document/styles/cinereel/skin.css +7 -3
  117. package/skills/document/styles/clinic-care/backdrop.html +2 -0
  118. package/skills/document/styles/clinic-care/skin.css +2 -1
  119. package/skills/document/styles/cool/skin.css +6 -5
  120. package/skills/document/styles/dark-glass/backdrop.html +2 -0
  121. package/skills/document/styles/dark-glass/skin.css +2 -1
  122. package/skills/document/styles/gov-brief/backdrop.html +2 -0
  123. package/skills/document/styles/gov-brief/skin.css +2 -1
  124. package/skills/document/styles/grid-tech/backdrop.html +2 -0
  125. package/skills/document/styles/grid-tech/skin.css +2 -1
  126. package/skills/document/styles/hand-drawn/skin.css +3 -3
  127. package/skills/document/styles/ivory-gold/skin.css +7 -6
  128. package/skills/document/styles/legal-slate/backdrop.html +2 -0
  129. package/skills/document/styles/legal-slate/skin.css +2 -1
  130. package/skills/document/styles/magazine-ink/skin.css +4 -3
  131. package/skills/document/styles/mckinsey/backdrop.html +7 -5
  132. package/skills/document/styles/mckinsey/skin.css +2 -1
  133. package/skills/document/styles/memphis/backdrop.html +2 -0
  134. package/skills/document/styles/memphis/skin.css +2 -1
  135. package/skills/document/styles/mono/skin.css +6 -2
  136. package/skills/document/styles/neon-cyber/backdrop.html +2 -0
  137. package/skills/document/styles/neon-cyber/skin.css +2 -1
  138. package/skills/document/styles/olive-gold/skin.css +7 -6
  139. package/skills/document/styles/organic-earth/backdrop.html +2 -0
  140. package/skills/document/styles/organic-earth/skin.css +2 -1
  141. package/skills/document/styles/pastel-saas/backdrop.html +2 -0
  142. package/skills/document/styles/pastel-saas/skin.css +2 -1
  143. package/skills/document/styles/portfolio-noir/backdrop.html +2 -0
  144. package/skills/document/styles/portfolio-noir/skin.css +2 -1
  145. package/skills/document/styles/retro-press/backdrop.html +2 -0
  146. package/skills/document/styles/retro-press/skin.css +2 -1
  147. package/skills/document/styles/sport-arena/backdrop.html +2 -0
  148. package/skills/document/styles/sport-arena/skin.css +4 -1
  149. package/skills/document/styles/swiss-grid/skin.css +10 -5
  150. package/skills/document/styles/wedding-blush/backdrop.html +2 -0
  151. package/skills/document/styles/wedding-blush/skin.css +4 -1
  152. package/skills/document/theme-system.md +85 -77
  153. package/skills/document/themes/README.md +7 -5
  154. package/skills/document/vision.md +8 -5
  155. package/skills/document/xlsx.md +15 -25
  156. package/skills/grill-me/SKILL.md +6 -0
  157. package/skills/grill-me/agents/openai.yaml +5 -0
  158. package/skills/grilling/SKILL.md +28 -0
  159. package/skills/grilling/agents/openai.yaml +3 -0
@@ -0,0 +1,771 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+
4
+ // apps/admin/data/skills.lib/document/bin-src/deck-chart.mjs
5
+ var import_promises = require("node:fs/promises");
6
+
7
+ // packages/core/dist/document/html-escape.js
8
+ function escapeHtml(value) {
9
+ return String(value).replace(/[&<>"']/g, (c) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" })[c]);
10
+ }
11
+
12
+ // packages/core/dist/document/deck-chart-svg.js
13
+ var SVG_CHART_TYPES = ["bar", "barH", "line", "area", "pie", "doughnut", "radar", "scatter"];
14
+ var RADAR_MAX_DIMS = 8;
15
+ var RADAR_MAX_SERIES = 3;
16
+ var SINGLE = "var(--chart-1, var(--accent))";
17
+ var RAMP = [
18
+ "var(--series-1, var(--c1, var(--accent)))",
19
+ "var(--series-2, var(--c2, var(--muted)))",
20
+ "var(--series-3, var(--c3, var(--title)))",
21
+ "var(--series-4, var(--c4, var(--body)))"
22
+ ];
23
+ var MAX_SERIES = RAMP.length;
24
+ var ACCENT = "var(--chart-accent, var(--accent))";
25
+ var SURFACE = "var(--chart-surface, var(--slide-bg, #fff))";
26
+ var TXT = "var(--chart-ink, var(--body, currentColor))";
27
+ var MUTED = "var(--chart-muted, var(--muted, currentColor))";
28
+ var TITLE = "var(--chart-title, var(--title, currentColor))";
29
+ var GRID = "var(--chart-grid, var(--hair, var(--muted)))";
30
+ var r1 = (n) => String(Math.round(n * 10) / 10);
31
+ var esc = (s) => escapeHtml(s);
32
+ function textW(s, size) {
33
+ let w = 0;
34
+ for (const ch of s) {
35
+ const cp = ch.codePointAt(0);
36
+ w += cp >= 11904 ? 1 : ch === " " ? 0.3 : 0.56;
37
+ }
38
+ return w * size;
39
+ }
40
+ function formatValue(v) {
41
+ const rounded = Math.abs(v) >= 100 ? Math.round(v) : Math.round(v * 100) / 100;
42
+ const [int, frac] = String(Math.abs(rounded)).split(".");
43
+ const grouped = int.length > 4 ? int.replace(/\B(?=(\d{3})+(?!\d))/g, ",") : int;
44
+ return `${rounded < 0 ? "-" : ""}${grouped}${frac ? `.${frac}` : ""}`;
45
+ }
46
+ function niceNum(x, round) {
47
+ const exp = Math.floor(Math.log10(x));
48
+ const f = x / 10 ** exp;
49
+ const nf = round ? f < 1.5 ? 1 : f < 3 ? 2 : f < 7 ? 5 : 10 : f <= 1 ? 1 : f <= 2 ? 2 : f <= 5 ? 5 : 10;
50
+ return nf * 10 ** exp;
51
+ }
52
+ function niceScale(min, max, target = 5) {
53
+ if (!Number.isFinite(min) || !Number.isFinite(max))
54
+ return { lo: 0, hi: 1, ticks: [0, 1] };
55
+ if (min === max) {
56
+ if (min === 0)
57
+ return { lo: 0, hi: 1, ticks: [0, 0.25, 0.5, 0.75, 1] };
58
+ if (min > 0)
59
+ min = 0;
60
+ else
61
+ max = 0;
62
+ }
63
+ const step = niceNum(niceNum(max - min, false) / (target - 1), true);
64
+ const lo = Math.floor(min / step) * step;
65
+ const hi = Math.ceil(max / step) * step;
66
+ const ticks = [];
67
+ for (let t = lo; t <= hi + step / 2; t += step)
68
+ ticks.push(Math.round(t / step) * step);
69
+ return { lo, hi, ticks };
70
+ }
71
+ function barPath(x, y, w, h, end, radius = 4) {
72
+ if (w <= 0 || h <= 0)
73
+ return "";
74
+ const r = Math.min(radius, w / 2, h / 2);
75
+ const R = (n) => r1(n);
76
+ switch (end) {
77
+ case "top":
78
+ return `M${R(x)},${R(y + h)}V${R(y + r)}Q${R(x)},${R(y)} ${R(x + r)},${R(y)}H${R(x + w - r)}Q${R(x + w)},${R(y)} ${R(x + w)},${R(y + r)}V${R(y + h)}Z`;
79
+ case "bottom":
80
+ return `M${R(x)},${R(y)}V${R(y + h - r)}Q${R(x)},${R(y + h)} ${R(x + r)},${R(y + h)}H${R(x + w - r)}Q${R(x + w)},${R(y + h)} ${R(x + w)},${R(y + h - r)}V${R(y)}Z`;
81
+ case "right":
82
+ return `M${R(x)},${R(y)}H${R(x + w - r)}Q${R(x + w)},${R(y)} ${R(x + w)},${R(y + r)}V${R(y + h - r)}Q${R(x + w)},${R(y + h)} ${R(x + w - r)},${R(y + h)}H${R(x)}Z`;
83
+ case "left":
84
+ return `M${R(x + w)},${R(y)}H${R(x + r)}Q${R(x)},${R(y)} ${R(x)},${R(y + r)}V${R(y + h - r)}Q${R(x)},${R(y + h)} ${R(x + r)},${R(y + h)}H${R(x + w)}Z`;
85
+ }
86
+ }
87
+ function normalize(spec, warnings, errors) {
88
+ let type = String(spec?.type ?? "bar");
89
+ if (type === "bar" && spec.horizontal === true)
90
+ type = "barH";
91
+ if (type === "column")
92
+ type = "bar";
93
+ if (!SVG_CHART_TYPES.includes(type)) {
94
+ errors.push(`chart type "${type}" has no SVG renderer (${SVG_CHART_TYPES.join(" | ")}) \u2014 keep it native (render:"native")`);
95
+ return null;
96
+ }
97
+ if (type === "radar") {
98
+ const dims = Array.isArray(spec.labels) ? spec.labels.length : 0;
99
+ if (dims && dims < 3)
100
+ errors.push(`radar needs at least 3 dimensions (got ${dims}) \u2014 use a bar chart`);
101
+ if (dims > RADAR_MAX_DIMS)
102
+ errors.push(`radar takes at most ${RADAR_MAX_DIMS} dimensions (got ${dims}) \u2014 use barH, or group the dimensions`);
103
+ const raw = Array.isArray(spec.series) ? spec.series.filter((s) => s && Array.isArray(s.values)) : [];
104
+ if (raw.length > RADAR_MAX_SERIES)
105
+ errors.push(`radar overlays at most ${RADAR_MAX_SERIES} series (got ${raw.length}) \u2014 more polygons are unreadable; split the chart`);
106
+ raw.forEach((s, i) => {
107
+ if (!s.values.length)
108
+ errors.push(`radar series ${i + 1}${s.name ? ` "${s.name}"` : ""} has no values`);
109
+ });
110
+ }
111
+ const labels = Array.isArray(spec.labels) ? spec.labels.map((l) => String(l ?? "")) : [];
112
+ const rawSeries = Array.isArray(spec.series) ? spec.series.filter((s) => s && Array.isArray(s.values)) : [];
113
+ if (!labels.length)
114
+ errors.push("chart needs labels (one per category / slice)");
115
+ if (!rawSeries.length)
116
+ errors.push("chart needs at least one series with values");
117
+ if (errors.length)
118
+ return null;
119
+ const series = rawSeries.map((s, i) => {
120
+ const name = String(s.name ?? "");
121
+ const at = `series ${i + 1}${name ? ` "${name}"` : ""}`;
122
+ const vals = s.values.map((v) => typeof v === "string" ? Number(v.replace(/[,%\s]/g, "")) : Number(v));
123
+ if (vals.length !== labels.length) {
124
+ warnings.push(`${at} has ${vals.length} value(s) for ${labels.length} label(s) \u2014 ${vals.length < labels.length ? "missing ones drawn as 0" : "extras ignored"}`);
125
+ }
126
+ const bad = vals.filter((v) => !Number.isFinite(v)).length;
127
+ if (bad)
128
+ warnings.push(`${at} has ${bad} non-numeric value(s) \u2014 drawn as 0`);
129
+ return { name, values: labels.map((_, j) => Number.isFinite(vals[j]) ? vals[j] : 0) };
130
+ });
131
+ const pie = type === "pie" || type === "doughnut";
132
+ if (!pie && series.length > MAX_SERIES) {
133
+ errors.push(`${series.length} series \u2014 a chart carries at most ${MAX_SERIES} (the skin's --c1..--c4); fold the rest into "\u5176\u4ED6" or split the chart`);
134
+ return null;
135
+ }
136
+ if (pie && series.length > 1)
137
+ warnings.push(`${type} draws only the first series (${series.length} given)`);
138
+ return { type, labels, series };
139
+ }
140
+ function renderChartSvg(spec, opts) {
141
+ const warnings = [];
142
+ const errors = [];
143
+ const W = Math.max(120, Math.round(opts.width));
144
+ const H = Math.max(80, Math.round(opts.height));
145
+ const base = Math.max(12, Math.min(18, Math.round(Math.min(W / 2, H) / 22)));
146
+ const unit = typeof spec?.unit === "string" ? spec.unit : "";
147
+ const label = (v) => `${formatValue(v)}${unit}`;
148
+ let type;
149
+ let body;
150
+ let names;
151
+ if (String(spec?.type ?? "") === "scatter") {
152
+ const sc = normalizeScatter(spec, warnings, errors);
153
+ if (!sc)
154
+ return { svg: "", warnings, errors };
155
+ type = "scatter";
156
+ body = scatterBody(sc, W, H, base, spec);
157
+ names = sc.series.map((s) => s.name).filter(Boolean);
158
+ } else {
159
+ const n = normalize(spec, warnings, errors);
160
+ if (!n)
161
+ return { svg: "", warnings, errors };
162
+ type = n.type;
163
+ names = n.labels;
164
+ body = n.type === "radar" ? radarBody(n, W, H, base, spec, label, warnings) : n.type === "pie" || n.type === "doughnut" ? pieBody(n, W, H, base, spec, label, warnings) : n.type === "barH" ? barHBody(n, W, H, base, spec, label, warnings) : n.type === "bar" ? barBody(n, W, H, base, spec, label, warnings) : lineBody(n, W, H, base, spec, label);
165
+ }
166
+ const size = opts.fluid === false ? `width="${W}" height="${H}"` : `width="100%" height="100%"`;
167
+ const aria = esc(`${type} chart: ${names.slice(0, 12).join(", ")}`);
168
+ const svg = `<svg xmlns="http://www.w3.org/2000/svg" class="talos-chart-svg" data-chart-type="${type}" ${size} viewBox="0 0 ${W} ${H}" preserveAspectRatio="xMidYMid meet" role="img" aria-label="${aria}" style="display:block;overflow:visible;font-family:var(--font-body, inherit);font-size:${base}px">${body}</svg>`;
169
+ return { svg, warnings, errors };
170
+ }
171
+ function legendRow(names, colors, x, y, base, maxW, centre = false) {
172
+ const size = Math.round(base * 0.75);
173
+ const rowH = Math.round(base * 1.6);
174
+ const rows = [[]];
175
+ let rowW = 0;
176
+ names.forEach((name, i) => {
177
+ const w = size + 6 + textW(name, base) + 18;
178
+ if (rowW > 0 && rowW + w > maxW) {
179
+ rows.push([]);
180
+ rowW = 0;
181
+ }
182
+ rows[rows.length - 1].push({ name, color: colors[i], w });
183
+ rowW += w;
184
+ });
185
+ const parts = [];
186
+ rows.forEach((row, ri) => {
187
+ const used = row.reduce((sum, it) => sum + it.w, 0) - 18;
188
+ let cx = centre ? x + Math.max(0, (maxW - used) / 2) : x;
189
+ const cy = y + ri * rowH;
190
+ for (const it of row) {
191
+ parts.push(`<rect x="${r1(cx)}" y="${r1(cy + (rowH - size) / 2 - 2)}" width="${size}" height="${size}" rx="2" style="fill:${it.color}"/>`);
192
+ parts.push(`<text x="${r1(cx + size + 6)}" y="${r1(cy + rowH / 2 + base * 0.35 - 2)}" style="fill:${TXT}">${esc(it.name)}</text>`);
193
+ cx += it.w;
194
+ }
195
+ });
196
+ return [parts.join(""), rows.length * rowH];
197
+ }
198
+ function seriesColors(n) {
199
+ return n.series.length === 1 ? [SINGLE] : n.series.map((_, i) => RAMP[i]);
200
+ }
201
+ function highlightSet(spec, count, warnings) {
202
+ if (spec.highlight === void 0)
203
+ return null;
204
+ const list = (Array.isArray(spec.highlight) ? spec.highlight : [spec.highlight]).map(Number);
205
+ const ok = list.filter((i) => Number.isInteger(i) && i >= 0 && i < count);
206
+ if (ok.length !== list.length)
207
+ warnings.push(`highlight indexes must be 0..${count - 1} \u2014 ignoring the rest`);
208
+ return ok.length ? new Set(ok) : null;
209
+ }
210
+ function labelled(values, showValues, few) {
211
+ if (showValues === false)
212
+ return /* @__PURE__ */ new Set();
213
+ if (showValues === true || values.length <= few)
214
+ return new Set(values.map((_, i) => i));
215
+ let hi = 0;
216
+ values.forEach((v, i) => {
217
+ if (v > values[hi])
218
+ hi = i;
219
+ });
220
+ return /* @__PURE__ */ new Set([hi, values.length - 1]);
221
+ }
222
+ function yAxis(ticks, toY, x0, x1, base, unit) {
223
+ return ticks.map((t) => {
224
+ const y = r1(toY(t));
225
+ return `<line x1="${r1(x0)}" x2="${r1(x1)}" y1="${y}" y2="${y}" style="stroke:${GRID};stroke-width:1" shape-rendering="crispEdges"/><text x="${r1(x0 - 8)}" y="${r1(toY(t) + base * 0.35)}" text-anchor="end" style="fill:${MUTED};font-size:${base - 1}px">${esc(formatValue(t) + (t === ticks[ticks.length - 1] ? unit : ""))}</text>`;
226
+ }).join("");
227
+ }
228
+ function barBody(n, W, H, base, spec, label, warnings) {
229
+ const stacked = spec.stacked === true && n.series.length > 1;
230
+ const colors = seriesColors(n);
231
+ const parts = [];
232
+ let top = 8;
233
+ if (n.series.length > 1) {
234
+ const [lg, h] = legendRow(n.series.map((s) => s.name || "\u2014"), colors, 0, 0, base, W);
235
+ parts.push(lg);
236
+ top = h + 8;
237
+ }
238
+ const totals = n.labels.map((_, j) => n.series.reduce((a, s) => a + Math.max(0, s.values[j]), 0));
239
+ const all = stacked ? totals : n.series.flatMap((s) => s.values);
240
+ if (stacked && n.series.some((s) => s.values.some((v) => v < 0)))
241
+ warnings.push("stacked bars cannot show negative values \u2014 drawn as 0");
242
+ const sc = niceScale(Math.min(0, ...all), Math.max(0, ...all));
243
+ const tickW = Math.max(...sc.ticks.map((t, i) => textW(formatValue(t) + (i === sc.ticks.length - 1 ? spec.unit ?? "" : ""), base - 1)));
244
+ const x0 = Math.ceil(tickW + 14);
245
+ const x1 = W - 4;
246
+ const y0 = top + base * 1.6;
247
+ const y1 = H - base * 2;
248
+ const toY = (v) => y1 - (v - sc.lo) / (sc.hi - sc.lo) * (y1 - y0);
249
+ parts.push(yAxis(sc.ticks, toY, x0, x1, base, spec.unit ?? ""));
250
+ const band = (x1 - x0) / n.labels.length;
251
+ const groupW = Math.min(band * 0.64, stacked ? 72 : 72 * n.series.length);
252
+ const barW = stacked ? groupW : (groupW - 2 * (n.series.length - 1)) / n.series.length;
253
+ const hl = n.series.length === 1 && !stacked ? highlightSet(spec, n.labels.length, warnings) : null;
254
+ const tooLong = n.labels.some((l) => textW(l, base) > band - 4);
255
+ if (tooLong)
256
+ warnings.push(`category labels are wider than their ${Math.round(band)}px column \u2014 use type "barH" or shorter labels`);
257
+ n.labels.forEach((lab, j) => {
258
+ const gx = x0 + band * j + (band - groupW) / 2;
259
+ if (stacked) {
260
+ let acc = 0;
261
+ n.series.forEach((s, i) => {
262
+ const v = Math.max(0, s.values[j]);
263
+ if (v <= 0)
264
+ return;
265
+ const yTop = toY(acc + v);
266
+ const yBot = toY(acc);
267
+ const isTop = n.series.slice(i + 1).every((t) => Math.max(0, t.values[j]) === 0);
268
+ const h = yBot - yTop - (acc > 0 ? 2 : 0);
269
+ parts.push(`<path d="${barPath(gx, yTop, barW, h, "top", isTop ? 4 : 0)}" style="fill:${colors[i]}"/>`);
270
+ acc += v;
271
+ });
272
+ } else {
273
+ n.series.forEach((s, i) => {
274
+ const v = s.values[j];
275
+ const x = gx + i * (barW + 2);
276
+ const yv = toY(v);
277
+ const yz = toY(0);
278
+ const fill = hl ? hl.has(j) ? ACCENT : MUTED : colors[i];
279
+ const op = hl && !hl.has(j) ? ";fill-opacity:.45" : "";
280
+ if (v >= 0)
281
+ parts.push(`<path d="${barPath(x, yv, barW, yz - yv, "top")}" style="fill:${fill}${op}"/>`);
282
+ else
283
+ parts.push(`<path d="${barPath(x, yz, barW, yv - yz, "bottom")}" style="fill:${fill}${op}"/>`);
284
+ });
285
+ }
286
+ parts.push(`<text x="${r1(x0 + band * j + band / 2)}" y="${r1(y1 + base * 1.4)}" text-anchor="middle" style="fill:${MUTED}">${esc(lab)}</text>`);
287
+ });
288
+ if (stacked || n.series.length === 1) {
289
+ const vals = stacked ? totals : n.series[0].values;
290
+ for (const j of labelled(vals, spec.showValues, 12)) {
291
+ const v = vals[j];
292
+ const y = v >= 0 ? toY(v) - 6 : toY(v) + base + 2;
293
+ parts.push(`<text x="${r1(x0 + band * j + band / 2)}" y="${r1(y)}" text-anchor="middle" style="fill:${TXT};font-weight:600">${esc(label(v))}</text>`);
294
+ }
295
+ } else if (spec.showValues === true) {
296
+ n.labels.forEach((_, j) => {
297
+ const gx = x0 + band * j + (band - groupW) / 2;
298
+ n.series.forEach((s, i) => {
299
+ const v = s.values[j];
300
+ const y = v >= 0 ? toY(v) - 5 : toY(v) + base;
301
+ parts.push(`<text x="${r1(gx + i * (barW + 2) + barW / 2)}" y="${r1(y)}" text-anchor="middle" style="fill:${TXT};font-size:${base - 2}px">${esc(label(v))}</text>`);
302
+ });
303
+ });
304
+ }
305
+ parts.push(`<line x1="${r1(x0)}" x2="${r1(x1)}" y1="${r1(toY(0))}" y2="${r1(toY(0))}" style="stroke:${MUTED};stroke-width:1" shape-rendering="crispEdges"/>`);
306
+ return parts.join("");
307
+ }
308
+ function barHBody(n, W, H, base, spec, label, warnings) {
309
+ const stacked = spec.stacked === true && n.series.length > 1;
310
+ const colors = seriesColors(n);
311
+ const parts = [];
312
+ let top = 4;
313
+ if (n.series.length > 1) {
314
+ const [lg, h] = legendRow(n.series.map((s) => s.name || "\u2014"), colors, 0, 0, base, W);
315
+ parts.push(lg);
316
+ top = h + 6;
317
+ }
318
+ const totals = n.labels.map((_, j) => n.series.reduce((a, s) => a + Math.max(0, s.values[j]), 0));
319
+ const vals = stacked ? totals : n.series.flatMap((s) => s.values);
320
+ if (vals.some((v) => v < 0))
321
+ warnings.push("barH draws negative values as 0 \u2014 use a vertical bar for a diverging series");
322
+ const sc = niceScale(0, Math.max(0, ...vals));
323
+ const labelW = Math.min(W * 0.38, Math.max(...n.labels.map((l) => textW(l, base))) + 12);
324
+ const valueW = Math.max(...(stacked ? totals : n.series.flatMap((s) => s.values)).map((v) => textW(label(Math.max(0, v)), base))) + 12;
325
+ const x0 = labelW;
326
+ const x1 = W - valueW;
327
+ const toX = (v) => x0 + Math.max(0, v) / (sc.hi || 1) * (x1 - x0);
328
+ const band = (H - top) / n.labels.length;
329
+ const groupH = Math.min(band * 0.62, stacked ? 44 : 44 * n.series.length);
330
+ const barH = stacked ? groupH : (groupH - 2 * (n.series.length - 1)) / n.series.length;
331
+ const hl = n.series.length === 1 && !stacked ? highlightSet(spec, n.labels.length, warnings) : null;
332
+ const shown = labelled(stacked ? totals : n.series[0].values, n.series.length === 1 || stacked ? spec.showValues : spec.showValues === true, 16);
333
+ n.labels.forEach((lab, j) => {
334
+ const gy = top + band * j + (band - groupH) / 2;
335
+ parts.push(`<text x="${r1(x0 - 12)}" y="${r1(top + band * j + band / 2 + base * 0.35)}" text-anchor="end" style="fill:${TXT}">${esc(lab)}</text>`);
336
+ if (stacked) {
337
+ let acc = 0;
338
+ n.series.forEach((s, i) => {
339
+ const v = Math.max(0, s.values[j]);
340
+ if (v <= 0)
341
+ return;
342
+ const xa = toX(acc) + (acc > 0 ? 2 : 0);
343
+ const isEnd = n.series.slice(i + 1).every((t) => Math.max(0, t.values[j]) === 0);
344
+ parts.push(`<path d="${barPath(xa, gy, toX(acc + v) - xa, barH, "right", isEnd ? 4 : 0)}" style="fill:${colors[i]}"/>`);
345
+ acc += v;
346
+ });
347
+ if (shown.has(j))
348
+ parts.push(`<text x="${r1(toX(totals[j]) + 8)}" y="${r1(gy + barH / 2 + base * 0.35)}" style="fill:${TXT};font-weight:600">${esc(label(totals[j]))}</text>`);
349
+ } else {
350
+ n.series.forEach((s, i) => {
351
+ const v = s.values[j];
352
+ const y = gy + i * (barH + 2);
353
+ const fill = hl ? hl.has(j) ? ACCENT : MUTED : colors[i];
354
+ const op = hl && !hl.has(j) ? ";fill-opacity:.45" : "";
355
+ parts.push(`<path d="${barPath(x0, y, toX(v) - x0, barH, "right")}" style="fill:${fill}${op}"/>`);
356
+ if (shown.has(j) && (n.series.length === 1 || spec.showValues === true)) {
357
+ parts.push(`<text x="${r1(toX(v) + 8)}" y="${r1(y + barH / 2 + base * 0.35)}" style="fill:${TXT};font-weight:600">${esc(label(v))}</text>`);
358
+ }
359
+ });
360
+ }
361
+ });
362
+ parts.push(`<line x1="${r1(x0)}" x2="${r1(x0)}" y1="${r1(top)}" y2="${r1(H)}" style="stroke:${MUTED};stroke-width:1" shape-rendering="crispEdges"/>`);
363
+ return parts.join("");
364
+ }
365
+ function lineBody(n, W, H, base, spec, label) {
366
+ const colors = seriesColors(n);
367
+ const parts = [];
368
+ let top = 8;
369
+ if (n.series.length > 1) {
370
+ const [lg, h] = legendRow(n.series.map((s) => s.name || "\u2014"), colors, 0, 0, base, W);
371
+ parts.push(lg);
372
+ top = h + 8;
373
+ }
374
+ const all = n.series.flatMap((s) => s.values);
375
+ const min = Math.min(...all);
376
+ const max = Math.max(...all);
377
+ const zoom = n.type === "line" && spec.zeroBaseline !== true && min > 0 && min > max * 0.6;
378
+ const sc = niceScale(zoom ? min : Math.min(0, min), Math.max(0, max));
379
+ const tickW = Math.max(...sc.ticks.map((t, i) => textW(formatValue(t) + (i === sc.ticks.length - 1 ? spec.unit ?? "" : ""), base - 1)));
380
+ const endW = Math.max(...n.series.map((s) => textW(label(s.values[s.values.length - 1]), base))) + 14;
381
+ const x0 = Math.ceil(tickW + 14);
382
+ const x1 = W - endW;
383
+ const y0 = top + base;
384
+ const y1 = H - base * 2;
385
+ const toY = (v) => y1 - (v - sc.lo) / (sc.hi - sc.lo) * (y1 - y0);
386
+ const inset = Math.min(24, (x1 - x0) / (2 * n.labels.length));
387
+ const toX = (j) => n.labels.length === 1 ? (x0 + x1) / 2 : x0 + inset + j * (x1 - x0 - 2 * inset) / (n.labels.length - 1);
388
+ parts.push(yAxis(sc.ticks, toY, x0, x1, base, spec.unit ?? ""));
389
+ const every = Math.max(1, Math.ceil(n.labels.reduce((m, l) => Math.max(m, textW(l, base)), 0) / Math.max(1, (x1 - x0) / n.labels.length - 6)));
390
+ n.labels.forEach((lab, j) => {
391
+ if (j % every !== 0 && j !== n.labels.length - 1)
392
+ return;
393
+ parts.push(`<text x="${r1(toX(j))}" y="${r1(y1 + base * 1.4)}" text-anchor="middle" style="fill:${MUTED}">${esc(lab)}</text>`);
394
+ });
395
+ n.series.forEach((s, i) => {
396
+ const pts = s.values.map((v, j) => `${r1(toX(j))},${r1(toY(v))}`);
397
+ if (n.type === "area") {
398
+ parts.push(`<path d="M${r1(toX(0))},${r1(toY(Math.max(sc.lo, 0)))}L${pts.join("L")}L${r1(toX(s.values.length - 1))},${r1(toY(Math.max(sc.lo, 0)))}Z" style="fill:${colors[i]};fill-opacity:.12"/>`);
399
+ }
400
+ parts.push(`<path d="M${pts.join("L")}" style="fill:none;stroke:${colors[i]};stroke-width:2.5;stroke-linejoin:round;stroke-linecap:round"/>`);
401
+ const marks = s.values.length <= 12 || spec.showValues === true ? s.values.map((_, j) => j) : [s.values.length - 1];
402
+ for (const j of marks) {
403
+ parts.push(`<circle cx="${r1(toX(j))}" cy="${r1(toY(s.values[j]))}" r="4.5" style="fill:${colors[i]};stroke:${SURFACE};stroke-width:2"/>`);
404
+ }
405
+ const lab = spec.showValues === false ? [] : spec.showValues === true ? s.values.map((_, j) => j) : [s.values.length - 1];
406
+ for (const j of lab) {
407
+ const end = j === s.values.length - 1;
408
+ parts.push(`<text x="${r1(toX(j) + (end ? 10 : 0))}" y="${r1(toY(s.values[j]) + (end ? base * 0.35 : -10))}" text-anchor="${end ? "start" : "middle"}" style="fill:${TXT};font-weight:600">${esc(label(s.values[j]))}</text>`);
409
+ }
410
+ });
411
+ return parts.join("");
412
+ }
413
+ function arc(cx, cy, r, a0, a1) {
414
+ const p = (a) => [cx + r * Math.sin(a), cy - r * Math.cos(a)];
415
+ const [xa, ya] = p(a0);
416
+ const [xb, yb] = p(a1);
417
+ return `${r1(xa)},${r1(ya)} A${r1(r)},${r1(r)} 0 ${a1 - a0 > Math.PI ? 1 : 0} 1 ${r1(xb)},${r1(yb)}`;
418
+ }
419
+ function pieBody(n, W, H, base, spec, label, warnings) {
420
+ let slices = n.labels.map((name, j) => ({ name, value: n.series[0].values[j] }));
421
+ if (slices.some((s) => s.value < 0))
422
+ warnings.push("pie/doughnut slices cannot be negative \u2014 drawn as 0");
423
+ slices = slices.map((s) => ({ ...s, value: Math.max(0, s.value) }));
424
+ let other = false;
425
+ if (slices.length > MAX_SERIES) {
426
+ const isOther = (name) => /^(其他|其它|others?)$/i.test(name.trim());
427
+ const sorted = [...slices].sort((a2, b) => Number(isOther(a2.name)) - Number(isOther(b.name)) || b.value - a2.value);
428
+ const rest = sorted.slice(MAX_SERIES - 1);
429
+ slices = [...sorted.slice(0, MAX_SERIES - 1), { name: "\u5176\u4ED6", value: rest.reduce((a2, s) => a2 + s.value, 0) }];
430
+ other = true;
431
+ warnings.push(`${n.labels.length} slices \u2014 the ${rest.length} smallest are folded into "\u5176\u4ED6" (a chart carries ${MAX_SERIES} colors)`);
432
+ }
433
+ const total = slices.reduce((a2, s) => a2 + s.value, 0);
434
+ const colors = slices.map((_, i) => other && i === slices.length - 1 ? MUTED : RAMP[i]);
435
+ const percentData = (spec.unit ?? "").trim() === "%";
436
+ const pctOf = (v) => total > 0 ? Math.round(v / total * 1e3) / 10 : 0;
437
+ const legendText = (v) => percentData ? label(v) : spec.showValues === false ? `${pctOf(v)}%` : `${label(v)} \xB7 ${pctOf(v)}%`;
438
+ const legendW = Math.min(W * 0.45, Math.max(...slices.map((s) => textW(`${s.name} ${legendText(s.value)}`, base))) + base + 16);
439
+ const r = Math.max(20, Math.min((W - legendW - 32) / 2, H / 2 - 4));
440
+ const left = Math.max(0, (W - (2 * r + 32 + legendW)) / 2);
441
+ const cx = left + r + 4;
442
+ const cy = H / 2;
443
+ const parts = [];
444
+ const hole = n.type === "doughnut" ? r * 0.58 : 0;
445
+ let a = 0;
446
+ slices.forEach((s, i) => {
447
+ if (s.value <= 0 || total <= 0)
448
+ return;
449
+ const sweep = s.value / total * Math.PI * 2;
450
+ const a1 = a + sweep;
451
+ if (sweep >= Math.PI * 2 - 1e-6) {
452
+ parts.push(`<circle cx="${r1(cx)}" cy="${r1(cy)}" r="${r1(r)}" style="fill:${colors[i]}"/>`);
453
+ } else if (hole) {
454
+ const p = (rad, ang) => `${r1(cx + rad * Math.sin(ang))},${r1(cy - rad * Math.cos(ang))}`;
455
+ parts.push(`<path d="M${arc(cx, cy, r, a, a1)} L${p(hole, a1)} A${r1(hole)},${r1(hole)} 0 ${sweep > Math.PI ? 1 : 0} 0 ${p(hole, a)}Z" style="fill:${colors[i]};stroke:${SURFACE};stroke-width:2;stroke-linejoin:round"/>`);
456
+ } else {
457
+ parts.push(`<path d="M${r1(cx)},${r1(cy)} L${arc(cx, cy, r, a, a1)}Z" style="fill:${colors[i]};stroke:${SURFACE};stroke-width:2;stroke-linejoin:round"/>`);
458
+ }
459
+ a = a1;
460
+ });
461
+ const center = spec.centerLabel ?? (percentData ? "" : label(total));
462
+ if (hole && center) {
463
+ parts.push(`<text x="${r1(cx)}" y="${r1(cy + base * 0.6)}" text-anchor="middle" style="fill:${TITLE};font-weight:700;font-size:${Math.round(Math.min(hole * 0.55, base * 2))}px">${esc(center)}</text>`);
464
+ }
465
+ const lx = cx + r + 28;
466
+ const rowH = Math.round(base * 1.9);
467
+ const ly = cy - slices.length * rowH / 2;
468
+ slices.forEach((s, i) => {
469
+ const y = ly + i * rowH;
470
+ parts.push(`<rect x="${r1(lx)}" y="${r1(y + rowH / 2 - base * 0.4)}" width="${r1(base * 0.8)}" height="${r1(base * 0.8)}" rx="2" style="fill:${colors[i]}"/>`);
471
+ parts.push(`<text x="${r1(lx + base + 8)}" y="${r1(y + rowH / 2 + base * 0.35)}" style="fill:${TXT}">${esc(s.name)}<tspan dx="8" style="fill:${MUTED}">${esc(legendText(s.value))}</tspan></text>`);
472
+ });
473
+ return parts.join("");
474
+ }
475
+ function radarBody(n, W, H, base, spec, label, warnings) {
476
+ const colors = seriesColors(n);
477
+ const parts = [];
478
+ let top = 0;
479
+ if (n.series.length > 1) {
480
+ const [lg, h] = legendRow(n.series.map((s) => s.name || "\u2014"), colors, 0, 0, base, W, true);
481
+ parts.push(lg);
482
+ top = h + 4;
483
+ }
484
+ const dataMax = Math.max(0, ...n.series.flatMap((s) => s.values));
485
+ const given = Number(spec.max);
486
+ const explicit = spec.max !== void 0 && Number.isFinite(given) && given > 0;
487
+ if (spec.max !== void 0 && !explicit)
488
+ warnings.push(`radar max must be a positive number \u2014 using a scale over the data`);
489
+ const sc = niceScale(0, explicit ? given : dataMax || 1);
490
+ const max = explicit ? given : sc.hi;
491
+ const ticks = explicit ? [0.25, 0.5, 0.75, 1].map((f) => f * max) : sc.ticks.filter((t) => t > 0);
492
+ let low = 0;
493
+ let high = 0;
494
+ const series = n.series.map((s) => ({
495
+ name: s.name,
496
+ values: s.values.map((v) => {
497
+ if (v < 0) {
498
+ low++;
499
+ return 0;
500
+ }
501
+ if (v > max) {
502
+ high++;
503
+ return max;
504
+ }
505
+ return v;
506
+ })
507
+ }));
508
+ if (low)
509
+ warnings.push(`radar: ${low} negative value(s) drawn at the centre (0) \u2014 a radar's scale starts at 0`);
510
+ if (high)
511
+ warnings.push(`radar: ${high} value(s) above max ${formatValue(max)} drawn on the outer ring \u2014 raise "max" or fix the data`);
512
+ const single = n.series.length === 1;
513
+ const showVal = single && spec.showValues !== false;
514
+ const dimText = (j) => n.labels[j] + (showVal ? ` ${label(n.series[0].values[j])}` : "");
515
+ const labelW = Math.max(...n.labels.map((_, j) => textW(dimText(j), base)));
516
+ const dims = n.labels.length;
517
+ const ang = (j) => j * 2 * Math.PI / dims;
518
+ const sideW = Math.max(...n.labels.map((_, j) => Math.abs(Math.sin(ang(j))) > 0.2 ? textW(dimText(j), base) : textW(dimText(j), base) / 2));
519
+ const r = Math.max(30, Math.min((H - top) / 2 - base * 1.8, W / 2 - Math.min(sideW, labelW) - 14));
520
+ const cx = W / 2;
521
+ const cy = top + (H - top) / 2;
522
+ const pt = (j, rad) => [cx + rad * Math.sin(ang(j)), cy - rad * Math.cos(ang(j))];
523
+ const poly = (rad) => n.labels.map((_, j) => pt(j, rad(j)).map(r1).join(",")).join(" ");
524
+ for (const t of ticks) {
525
+ parts.push(`<polygon points="${poly(() => t / max * r)}" style="fill:none;stroke:${GRID};stroke-width:1"/>`);
526
+ }
527
+ n.labels.forEach((_, j) => {
528
+ const [x, y] = pt(j, r);
529
+ parts.push(`<line x1="${r1(cx)}" y1="${r1(cy)}" x2="${r1(x)}" y2="${r1(y)}" style="stroke:${GRID};stroke-width:1"/>`);
530
+ });
531
+ parts.push(radarTicks(series, ticks, max, r, cx, cy, dims, base, spec.unit ?? ""));
532
+ series.forEach((s, i) => {
533
+ const pts = poly((j) => s.values[j] / max * r);
534
+ parts.push(`<polygon points="${pts}" style="fill:${colors[i]};fill-opacity:${single ? ".18" : ".1"};stroke:${colors[i]};stroke-width:2.5;stroke-linejoin:round"/>`);
535
+ s.values.forEach((v, j) => {
536
+ const [x, y] = pt(j, v / max * r);
537
+ parts.push(`<circle cx="${r1(x)}" cy="${r1(y)}" r="4" style="fill:${colors[i]};stroke:${SURFACE};stroke-width:2"/>`);
538
+ });
539
+ });
540
+ n.labels.forEach((name, j) => {
541
+ const sin = Math.sin(ang(j));
542
+ const cos = Math.cos(ang(j));
543
+ const [x, y] = pt(j, r + 14);
544
+ const anchor = sin > 0.2 ? "start" : sin < -0.2 ? "end" : "middle";
545
+ const dy = cos > 0.2 ? -2 : cos < -0.2 ? base * 0.9 : base * 0.35;
546
+ const value = showVal ? `<tspan dx="6" style="fill:${TITLE};font-weight:700">${esc(label(n.series[0].values[j]))}</tspan>` : "";
547
+ parts.push(`<text x="${r1(x)}" y="${r1(y + dy)}" text-anchor="${anchor}" style="fill:${TXT}">${esc(name)}${value}</text>`);
548
+ });
549
+ return parts.join("");
550
+ }
551
+ function segHitsBox(ax, ay, bx, by, box) {
552
+ const dx = bx - ax;
553
+ const dy = by - ay;
554
+ let t0 = 0;
555
+ let t1 = 1;
556
+ for (const [p, q] of [[-dx, ax - box.x0], [dx, box.x1 - ax], [-dy, ay - box.y0], [dy, box.y1 - ay]]) {
557
+ if (p === 0) {
558
+ if (q < 0)
559
+ return false;
560
+ continue;
561
+ }
562
+ const t = q / p;
563
+ if (p < 0) {
564
+ if (t > t1)
565
+ return false;
566
+ if (t > t0)
567
+ t0 = t;
568
+ } else {
569
+ if (t < t0)
570
+ return false;
571
+ if (t < t1)
572
+ t1 = t;
573
+ }
574
+ }
575
+ return true;
576
+ }
577
+ function radarTicks(series, ticks, max, r, cx, cy, dims, base, unit) {
578
+ const size = base - 3;
579
+ const ang = (j) => j * 2 * Math.PI / dims;
580
+ const at = (j, rad) => [cx + rad * Math.sin(ang(j)), cy - rad * Math.cos(ang(j))];
581
+ const edge = Math.cos(Math.PI / dims);
582
+ const gap = ticks.length > 1 ? (ticks[1] - ticks[0]) / max * r * edge : Infinity;
583
+ const step = gap < size * 1.6 ? 2 : 1;
584
+ const shown = ticks.map((t, i) => ({ t, i })).filter(({ i }) => (ticks.length - 1 - i) % step === 0);
585
+ const segs = [];
586
+ const dots = [];
587
+ for (const s of series) {
588
+ const pts = s.values.map((v, j) => at(j, v / max * r));
589
+ pts.forEach((p, j) => {
590
+ const q = pts[(j + 1) % pts.length];
591
+ segs.push([p[0], p[1], q[0], q[1]]);
592
+ dots.push(p);
593
+ });
594
+ }
595
+ const DOT = 6;
596
+ const PAD = 2;
597
+ const place = (wedge, t, last) => {
598
+ const mid = ang(wedge) + Math.PI / dims;
599
+ const sin = Math.sin(mid);
600
+ const cos = Math.cos(mid);
601
+ const rad = t / max * r * edge;
602
+ const px = cx + rad * sin;
603
+ const py = cy - rad * cos;
604
+ const text = formatValue(t) + (last ? unit : "");
605
+ const w = textW(text, size);
606
+ const anchor = sin < -0.2 ? "end" : sin > 0.2 ? "start" : "middle";
607
+ const x = px + (anchor === "end" ? -3 : anchor === "start" ? 3 : 0);
608
+ const y = cos > 0.2 ? py - 3 : cos < -0.2 ? py + size + 1 : py + size * 0.35;
609
+ const x0 = anchor === "end" ? x - w : anchor === "start" ? x : x - w / 2;
610
+ const box = { x0: x0 - PAD, y0: y - size * 0.8 - PAD, x1: x0 + w + PAD, y1: y + size * 0.2 + PAD };
611
+ const hit = segs.some(([ax, ay, bx, by]) => segHitsBox(ax, ay, bx, by, box)) || dots.some(([dx, dy]) => Math.hypot(Math.max(box.x0 - dx, 0, dx - box.x1), Math.max(box.y0 - dy, 0, dy - box.y1)) < DOT);
612
+ return { x, y, anchor, text, hit };
613
+ };
614
+ const order = [dims - 1, ...Array.from({ length: dims - 1 }, (_, j) => j)];
615
+ let best = order[0];
616
+ let bestHits = Infinity;
617
+ for (const wedge of order) {
618
+ const hits = shown.reduce((n, { t, i }) => n + (place(wedge, t, i === ticks.length - 1).hit ? 1 : 0), 0);
619
+ if (hits < bestHits) {
620
+ best = wedge;
621
+ bestHits = hits;
622
+ }
623
+ if (!hits)
624
+ break;
625
+ }
626
+ return shown.map(({ t, i }) => ({ last: i === ticks.length - 1, ...place(best, t, i === ticks.length - 1) })).filter((l) => l.last || !l.hit).map((l) => `<text x="${r1(l.x)}" y="${r1(l.y)}" text-anchor="${l.anchor}" style="fill:${MUTED};font-size:${size}px;paint-order:stroke;stroke:${SURFACE};stroke-width:3px;stroke-linejoin:round">${esc(l.text)}</text>`).join("");
627
+ }
628
+ var toNum = (v) => typeof v === "number" ? v : typeof v === "string" && v.trim() ? Number(v.replace(/[,%\s]/g, "")) : NaN;
629
+ function normalizeScatter(spec, warnings, errors) {
630
+ const raw = Array.isArray(spec.series) ? spec.series.filter((s) => s && typeof s === "object") : [];
631
+ if (!raw.length) {
632
+ errors.push("scatter needs at least one series with points ([x, y] or {x, y, label})");
633
+ return null;
634
+ }
635
+ if (raw.length > MAX_SERIES) {
636
+ errors.push(`${raw.length} series \u2014 a chart carries at most ${MAX_SERIES} (the skin's --c1..--c4); merge or split the chart`);
637
+ return null;
638
+ }
639
+ const labels = Array.isArray(spec.labels) ? spec.labels : [];
640
+ const series = raw.map((s, i) => {
641
+ const name = String(s.name ?? "");
642
+ const at = `scatter series ${i + 1}${name ? ` "${name}"` : ""}`;
643
+ let rows = Array.isArray(s.points) ? s.points : [];
644
+ if (!Array.isArray(s.points) && Array.isArray(s.values))
645
+ rows = s.values.map((y, j) => [labels[j], y]);
646
+ let bad = 0;
647
+ const points = [];
648
+ for (const p of rows) {
649
+ const [x, y, lab] = Array.isArray(p) ? [p[0], p[1], p[2]] : p && typeof p === "object" ? [p.x, p.y, p.label] : [NaN, NaN, ""];
650
+ const nx = toNum(x);
651
+ const ny = toNum(y);
652
+ if (!Number.isFinite(nx) || !Number.isFinite(ny)) {
653
+ bad++;
654
+ continue;
655
+ }
656
+ points.push({ x: nx, y: ny, label: lab == null ? "" : String(lab) });
657
+ }
658
+ if (bad)
659
+ warnings.push(`${at}: ${bad} point(s) without a numeric x and y \u2014 dropped`);
660
+ if (!rows.length)
661
+ warnings.push(`${at} has no points`);
662
+ return { name, points };
663
+ });
664
+ if (!series.some((s) => s.points.length)) {
665
+ errors.push("scatter has no plottable point \u2014 give series[].points as [x, y] or {x, y, label}");
666
+ return null;
667
+ }
668
+ return { series };
669
+ }
670
+ function axisScale(vals) {
671
+ const min = Math.min(...vals);
672
+ const max = Math.max(...vals);
673
+ const zoom = min > 0 && min > max * 0.6 || max < 0 && max < min * 0.6;
674
+ return niceScale(zoom ? min : Math.min(0, min), zoom ? max : Math.max(0, max));
675
+ }
676
+ function scatterBody(d, W, H, base, spec) {
677
+ const colors = d.series.length === 1 ? [SINGLE] : d.series.map((_, i) => RAMP[i]);
678
+ const parts = [];
679
+ let top = 8;
680
+ if (d.series.length > 1) {
681
+ const [lg, h] = legendRow(d.series.map((s) => s.name || "\u2014"), colors, 0, 0, base, W);
682
+ parts.push(lg);
683
+ top = h + 8;
684
+ }
685
+ const all = d.series.flatMap((s) => s.points);
686
+ const xs = axisScale(all.map((p) => p.x));
687
+ const ys = axisScale(all.map((p) => p.y));
688
+ const unit = spec.unit ?? "";
689
+ const xUnit = spec.xUnit ?? "";
690
+ if (spec.yLabel) {
691
+ parts.push(`<text x="0" y="${r1(top + base * 0.8)}" style="fill:${MUTED};font-size:${base - 1}px">${esc(spec.yLabel)}</text>`);
692
+ top += base * 1.4;
693
+ }
694
+ const tickW = Math.max(...ys.ticks.map((t, i) => textW(formatValue(t) + (i === ys.ticks.length - 1 ? unit : ""), base - 1)));
695
+ const lastX = formatValue(xs.ticks[xs.ticks.length - 1]) + xUnit;
696
+ const x0 = Math.ceil(tickW + 14);
697
+ const x1 = W - Math.max(8, textW(lastX, base - 1) / 2 + 2);
698
+ const y0 = top + base * 0.6;
699
+ const y1 = H - base * (spec.xLabel ? 3.2 : 2);
700
+ const toX = (v) => x0 + (v - xs.lo) / (xs.hi - xs.lo) * (x1 - x0);
701
+ const toY = (v) => y1 - (v - ys.lo) / (ys.hi - ys.lo) * (y1 - y0);
702
+ parts.push(yAxis(ys.ticks, toY, x0, x1, base, unit));
703
+ xs.ticks.forEach((t, i) => {
704
+ const x = r1(toX(t));
705
+ parts.push(`<line x1="${x}" x2="${x}" y1="${r1(y0)}" y2="${r1(y1)}" style="stroke:${GRID};stroke-width:1" shape-rendering="crispEdges"/>`);
706
+ parts.push(`<text x="${x}" y="${r1(y1 + base * 1.4)}" text-anchor="middle" style="fill:${MUTED};font-size:${base - 1}px">${esc(formatValue(t) + (i === xs.ticks.length - 1 ? xUnit : ""))}</text>`);
707
+ });
708
+ if (spec.xLabel) {
709
+ parts.push(`<text x="${r1(x1)}" y="${r1(H - base * 0.4)}" text-anchor="end" style="fill:${MUTED};font-size:${base - 1}px">${esc(spec.xLabel)}</text>`);
710
+ }
711
+ const ax = r1(toX(xs.lo <= 0 && xs.hi >= 0 ? 0 : xs.lo));
712
+ const ay = r1(toY(ys.lo <= 0 && ys.hi >= 0 ? 0 : ys.lo));
713
+ parts.push(`<line x1="${r1(x0)}" x2="${r1(x1)}" y1="${ay}" y2="${ay}" style="stroke:${MUTED};stroke-width:1" shape-rendering="crispEdges"/>`);
714
+ parts.push(`<line x1="${ax}" x2="${ax}" y1="${r1(y0)}" y2="${r1(y1)}" style="stroke:${MUTED};stroke-width:1" shape-rendering="crispEdges"/>`);
715
+ const dense = all.length > 60;
716
+ d.series.forEach((s, i) => {
717
+ for (const p of s.points) {
718
+ parts.push(`<circle cx="${r1(toX(p.x))}" cy="${r1(toY(p.y))}" r="${dense ? 4 : 6}" style="fill:${colors[i]};fill-opacity:.85;stroke:${SURFACE};stroke-width:1.5"/>`);
719
+ }
720
+ });
721
+ if (spec.showValues !== false) {
722
+ d.series.forEach((s) => {
723
+ for (const p of s.points) {
724
+ if (!p.label)
725
+ continue;
726
+ parts.push(`<text x="${r1(toX(p.x) + 9)}" y="${r1(toY(p.y) + base * 0.35 - 1)}" style="fill:${TXT};font-size:${base - 2}px">${esc(p.label)}</text>`);
727
+ }
728
+ });
729
+ }
730
+ return parts.join("");
731
+ }
732
+
733
+ // apps/admin/data/skills.lib/document/bin-src/deck-chart.mjs
734
+ function die(msg, code = 1) {
735
+ process.stderr.write(`deck-chart: ${msg}
736
+ `);
737
+ process.exit(code);
738
+ }
739
+ async function main() {
740
+ const argv = process.argv.slice(2);
741
+ const flag = (k) => {
742
+ const i = argv.indexOf(`--${k}`);
743
+ return i >= 0 ? argv[i + 1] : void 0;
744
+ };
745
+ const pos = argv.filter((a, i) => !a.startsWith("--") && !argv[i - 1]?.startsWith("--"));
746
+ const [chartPath, outPath] = pos;
747
+ const width = Number(flag("width"));
748
+ const height = Number(flag("height"));
749
+ if (!chartPath || !(width > 0) || !(height > 0)) die("usage: deck-chart <chart.json> [out.svg] --width <px> --height <px>", 64);
750
+ let spec;
751
+ try {
752
+ spec = JSON.parse(await (0, import_promises.readFile)(chartPath, "utf8"));
753
+ } catch (e) {
754
+ die(`cannot read chart ${chartPath}: ${e.message}`, 66);
755
+ }
756
+ const r = renderChartSvg(spec, { width, height, fluid: false });
757
+ for (const w of r.warnings) process.stderr.write(`deck-chart: ${w}
758
+ `);
759
+ if (r.errors.length) {
760
+ for (const e of r.errors) process.stderr.write(`deck-chart: ERROR ${e}
761
+ `);
762
+ die(`${r.errors.length} chart error(s) \u2014 nothing written`, 1);
763
+ }
764
+ if (outPath) {
765
+ await (0, import_promises.writeFile)(outPath, r.svg + "\n", "utf8");
766
+ process.stdout.write(JSON.stringify({ ok: true, out: outPath, width, height, warnings: r.warnings }) + "\n");
767
+ } else {
768
+ process.stdout.write(r.svg + "\n");
769
+ }
770
+ }
771
+ main().catch((e) => die(e.message, 1));