agentcss 0.0.0-stage → 0.11.3

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 (274) hide show
  1. package/01-tokens/README.md +236 -0
  2. package/01-tokens/behavior/dismissable.js +132 -0
  3. package/01-tokens/behavior/emit.js +76 -0
  4. package/01-tokens/behavior/focus-return.js +81 -0
  5. package/01-tokens/behavior/roving.js +152 -0
  6. package/01-tokens/behavior/typeahead.js +124 -0
  7. package/01-tokens/focus-ring-demo.html +133 -0
  8. package/01-tokens/focus-ring.css +102 -0
  9. package/01-tokens/page.css +244 -0
  10. package/01-tokens/polyfill.js +116 -0
  11. package/01-tokens/responsive-demo.html +171 -0
  12. package/01-tokens/scrollbar-demo.html +194 -0
  13. package/01-tokens/scrollbar.css +179 -0
  14. package/01-tokens/theme-toggle.js +103 -0
  15. package/01-tokens/tokens.css +972 -0
  16. package/01-tokens/typography.css +156 -0
  17. package/02-primitives/badge/README.md +71 -0
  18. package/02-primitives/badge/badge.css +141 -0
  19. package/02-primitives/badge/demo.html +101 -0
  20. package/02-primitives/button/README.md +99 -0
  21. package/02-primitives/button/button.css +353 -0
  22. package/02-primitives/button/demo.html +200 -0
  23. package/02-primitives/card/README.md +145 -0
  24. package/02-primitives/card/card.css +246 -0
  25. package/02-primitives/card/demo.html +170 -0
  26. package/02-primitives/choice/README.md +161 -0
  27. package/02-primitives/choice/choice.css +395 -0
  28. package/02-primitives/choice/demo.html +277 -0
  29. package/02-primitives/combobox/README.md +79 -0
  30. package/02-primitives/combobox/combobox.css +223 -0
  31. package/02-primitives/combobox/combobox.js +485 -0
  32. package/02-primitives/combobox/demo.html +109 -0
  33. package/02-primitives/date-range/README.md +100 -0
  34. package/02-primitives/date-range/date-range.css +206 -0
  35. package/02-primitives/date-range/date-range.js +301 -0
  36. package/02-primitives/date-range/demo.html +104 -0
  37. package/02-primitives/input/README.md +170 -0
  38. package/02-primitives/input/demo.html +246 -0
  39. package/02-primitives/input/input.css +239 -0
  40. package/02-primitives/popover/README.md +80 -0
  41. package/02-primitives/popover/demo.html +150 -0
  42. package/02-primitives/popover/popover.css +187 -0
  43. package/02-primitives/popover/popover.js +331 -0
  44. package/02-primitives/progress/README.md +82 -0
  45. package/02-primitives/progress/demo.html +154 -0
  46. package/02-primitives/progress/progress.css +172 -0
  47. package/02-primitives/select/README.md +95 -0
  48. package/02-primitives/select/demo.html +121 -0
  49. package/02-primitives/select/select.css +151 -0
  50. package/02-primitives/select/select.js +549 -0
  51. package/02-primitives/separator/README.md +63 -0
  52. package/02-primitives/separator/demo.html +84 -0
  53. package/02-primitives/separator/separator.css +73 -0
  54. package/02-primitives/skeleton/README.md +69 -0
  55. package/02-primitives/skeleton/demo.html +110 -0
  56. package/02-primitives/skeleton/skeleton.css +96 -0
  57. package/02-primitives/switch/README.md +65 -0
  58. package/02-primitives/switch/demo.html +115 -0
  59. package/02-primitives/switch/switch.css +370 -0
  60. package/03-patterns/accordion/README.md +121 -0
  61. package/03-patterns/accordion/accordion.css +453 -0
  62. package/03-patterns/accordion/accordion.js +369 -0
  63. package/03-patterns/accordion/demo.html +223 -0
  64. package/03-patterns/content/README.md +103 -0
  65. package/03-patterns/content/content.css +286 -0
  66. package/03-patterns/content/demo.html +88 -0
  67. package/03-patterns/drawer/README.md +79 -0
  68. package/03-patterns/drawer/demo.html +101 -0
  69. package/03-patterns/drawer/drawer.css +129 -0
  70. package/03-patterns/drawer/drawer.js +258 -0
  71. package/03-patterns/dropdown/README.md +78 -0
  72. package/03-patterns/dropdown/demo.html +101 -0
  73. package/03-patterns/dropdown/dropdown.css +135 -0
  74. package/03-patterns/dropdown/dropdown.js +451 -0
  75. package/03-patterns/form-validation/README.md +147 -0
  76. package/03-patterns/form-validation/demo.html +280 -0
  77. package/03-patterns/form-validation/form-validation.css +263 -0
  78. package/03-patterns/list/README.md +150 -0
  79. package/03-patterns/list/demo.html +225 -0
  80. package/03-patterns/list/flip.js +201 -0
  81. package/03-patterns/list/list.css +162 -0
  82. package/03-patterns/nav/README.md +225 -0
  83. package/03-patterns/nav/demo.html +131 -0
  84. package/03-patterns/nav/nav.css +314 -0
  85. package/03-patterns/nav/nav.js +430 -0
  86. package/03-patterns/nav/toc.js +111 -0
  87. package/03-patterns/overlay/README.md +196 -0
  88. package/03-patterns/overlay/demo.html +241 -0
  89. package/03-patterns/overlay/overlay.css +319 -0
  90. package/03-patterns/overlay/overlay.js +563 -0
  91. package/03-patterns/pagination/README.md +114 -0
  92. package/03-patterns/pagination/demo.html +165 -0
  93. package/03-patterns/pagination/pagination.css +195 -0
  94. package/03-patterns/pagination/pagination.js +395 -0
  95. package/03-patterns/reveal/README.md +91 -0
  96. package/03-patterns/reveal/demo.html +106 -0
  97. package/03-patterns/reveal/reveal.css +93 -0
  98. package/03-patterns/reveal/reveal.js +172 -0
  99. package/03-patterns/states/README.md +141 -0
  100. package/03-patterns/states/demo.html +240 -0
  101. package/03-patterns/states/states.css +252 -0
  102. package/03-patterns/tabs/README.md +84 -0
  103. package/03-patterns/tabs/demo.html +139 -0
  104. package/03-patterns/tabs/tabs.css +172 -0
  105. package/03-patterns/tabs/tabs.js +307 -0
  106. package/03-patterns/tooltip/README.md +96 -0
  107. package/03-patterns/tooltip/demo.html +78 -0
  108. package/03-patterns/tooltip/tooltip.css +89 -0
  109. package/03-patterns/tooltip/tooltip.js +288 -0
  110. package/03-patterns/tree/README.md +94 -0
  111. package/03-patterns/tree/demo.html +138 -0
  112. package/03-patterns/tree/tree.css +113 -0
  113. package/03-patterns/tree/tree.js +456 -0
  114. package/04-recipes/analysis-report/README.md +80 -0
  115. package/04-recipes/analysis-report/analysis-report.css +187 -0
  116. package/04-recipes/analysis-report/demo.html +274 -0
  117. package/04-recipes/data-showcase/README.md +45 -0
  118. package/04-recipes/data-showcase/demo.html +247 -0
  119. package/04-recipes/detail-page/README.md +93 -0
  120. package/04-recipes/detail-page/demo.html +55 -0
  121. package/04-recipes/detail-page/detail-page.css +190 -0
  122. package/04-recipes/detail-page/full.html +60 -0
  123. package/04-recipes/detail-page/null.html +60 -0
  124. package/04-recipes/detail-page/partial.html +60 -0
  125. package/04-recipes/detail-page/verify-skeleton.py +19 -0
  126. package/04-recipes/longform/README.md +93 -0
  127. package/04-recipes/longform/agc-memory.svg +4 -0
  128. package/04-recipes/longform/content.html +610 -0
  129. package/04-recipes/longform/content.md +51 -0
  130. package/04-recipes/longform/longform.html +627 -0
  131. package/04-recipes/report/README.md +176 -0
  132. package/04-recipes/report/demo.html +355 -0
  133. package/04-recipes/report/report.css +364 -0
  134. package/04-recipes/table/README.md +103 -0
  135. package/04-recipes/table/demo.html +455 -0
  136. package/04-recipes/table/table.css +372 -0
  137. package/04-recipes/tierA-tokens.css +189 -0
  138. package/09-assets/README.md +74 -0
  139. package/09-assets/bar/README.md +94 -0
  140. package/09-assets/bar/bar.css +166 -0
  141. package/09-assets/bar/bar.js +320 -0
  142. package/09-assets/bar/demo.html +176 -0
  143. package/09-assets/echarts-adapter/demo.html +192 -0
  144. package/09-assets/echarts-adapter/echarts-adapter.js +277 -0
  145. package/09-assets/model-viewer/README.md +155 -0
  146. package/09-assets/model-viewer/demo.html +105 -0
  147. package/09-assets/model-viewer/model-viewer.css +143 -0
  148. package/09-assets/model-viewer/model-viewer.js +423 -0
  149. package/09-assets/scientific-plot/README.md +94 -0
  150. package/09-assets/scientific-plot/demo.html +252 -0
  151. package/09-assets/scientific-plot/scientific-plot.css +141 -0
  152. package/09-assets/scientific-plot/scientific-plot.js +610 -0
  153. package/09-assets/sparkline/README.md +153 -0
  154. package/09-assets/sparkline/demo.html +163 -0
  155. package/09-assets/sparkline/sparkline.css +158 -0
  156. package/09-assets/sparkline/sparkline.js +508 -0
  157. package/API.md +332 -0
  158. package/CHANGELOG.md +3138 -0
  159. package/CODE_OF_CONDUCT.md +64 -0
  160. package/CONTRIBUTING.md +89 -0
  161. package/ERRORS.md +1658 -0
  162. package/LICENSE +21 -0
  163. package/README.md +497 -2
  164. package/SECURITY.md +72 -0
  165. package/START-HERE.md +164 -0
  166. package/SUPPORT.md +109 -0
  167. package/adapters/presentation/README.md +113 -0
  168. package/adapters/presentation/check.js +101 -0
  169. package/adapters/presentation/deck.schema.json +74 -0
  170. package/adapters/presentation/to-doc.js +138 -0
  171. package/ai/START-HERE.md +104 -0
  172. package/ai/behaviors.json +380 -0
  173. package/ai/behaviors.schema.json +61 -0
  174. package/ai/capabilities.json +56 -0
  175. package/ai/cli.js +387 -0
  176. package/ai/components.json +1944 -0
  177. package/ai/components.meta.json +168 -0
  178. package/ai/components.schema.json +220 -0
  179. package/ai/contract.schema.json +147 -0
  180. package/ai/deprecations.json +55 -0
  181. package/ai/deprecations.schema.json +85 -0
  182. package/ai/motion-policy.json +22 -0
  183. package/ai/patch.js +323 -0
  184. package/ai/patch.schema.json +63 -0
  185. package/ai/profiles.json +92 -0
  186. package/ai/tokens.audience.json +55 -0
  187. package/ai/tokens.audience.schema.json +73 -0
  188. package/ai/tokens.json +762 -0
  189. package/ai/tokens.schema.json +58 -0
  190. package/ai/tokens.tree.json +752 -0
  191. package/ai/tokens.tree.schema.json +56 -0
  192. package/ai/validate.js +383 -0
  193. package/dist/01-tokens/behavior/dismissable.js +60 -0
  194. package/dist/01-tokens/behavior/emit.js +21 -0
  195. package/dist/01-tokens/behavior/focus-return.js +31 -0
  196. package/dist/01-tokens/behavior/roving.js +77 -0
  197. package/dist/01-tokens/behavior/typeahead.js +61 -0
  198. package/dist/01-tokens/focus-ring.css +1 -0
  199. package/dist/01-tokens/page.css +1 -0
  200. package/dist/01-tokens/polyfill.js +78 -0
  201. package/dist/01-tokens/scrollbar.css +1 -0
  202. package/dist/01-tokens/theme-toggle.js +61 -0
  203. package/dist/01-tokens/tokens.css +1 -0
  204. package/dist/01-tokens/typography.css +1 -0
  205. package/dist/02-primitives/badge/badge.css +1 -0
  206. package/dist/02-primitives/button/button.css +1 -0
  207. package/dist/02-primitives/card/card.css +1 -0
  208. package/dist/02-primitives/choice/choice.css +1 -0
  209. package/dist/02-primitives/combobox/combobox.css +1 -0
  210. package/dist/02-primitives/combobox/combobox.js +439 -0
  211. package/dist/02-primitives/date-range/date-range.css +1 -0
  212. package/dist/02-primitives/date-range/date-range.js +267 -0
  213. package/dist/02-primitives/input/input.css +1 -0
  214. package/dist/02-primitives/popover/popover.css +1 -0
  215. package/dist/02-primitives/popover/popover.js +257 -0
  216. package/dist/02-primitives/progress/progress.css +1 -0
  217. package/dist/02-primitives/select/select.css +1 -0
  218. package/dist/02-primitives/select/select.js +449 -0
  219. package/dist/02-primitives/separator/separator.css +1 -0
  220. package/dist/02-primitives/skeleton/skeleton.css +1 -0
  221. package/dist/02-primitives/switch/switch.css +1 -0
  222. package/dist/03-patterns/accordion/accordion.css +1 -0
  223. package/dist/03-patterns/accordion/accordion.js +219 -0
  224. package/dist/03-patterns/content/content.css +1 -0
  225. package/dist/03-patterns/drawer/drawer.css +1 -0
  226. package/dist/03-patterns/drawer/drawer.js +229 -0
  227. package/dist/03-patterns/dropdown/dropdown.css +1 -0
  228. package/dist/03-patterns/dropdown/dropdown.js +365 -0
  229. package/dist/03-patterns/form-validation/form-validation.css +1 -0
  230. package/dist/03-patterns/list/flip.js +117 -0
  231. package/dist/03-patterns/list/list.css +1 -0
  232. package/dist/03-patterns/nav/nav.css +1 -0
  233. package/dist/03-patterns/nav/nav.js +322 -0
  234. package/dist/03-patterns/nav/toc.js +76 -0
  235. package/dist/03-patterns/overlay/overlay.css +1 -0
  236. package/dist/03-patterns/overlay/overlay.js +398 -0
  237. package/dist/03-patterns/pagination/pagination.css +1 -0
  238. package/dist/03-patterns/pagination/pagination.js +273 -0
  239. package/dist/03-patterns/reveal/reveal.css +1 -0
  240. package/dist/03-patterns/reveal/reveal.js +138 -0
  241. package/dist/03-patterns/states/states.css +1 -0
  242. package/dist/03-patterns/tabs/tabs.css +1 -0
  243. package/dist/03-patterns/tabs/tabs.js +242 -0
  244. package/dist/03-patterns/tooltip/tooltip.css +1 -0
  245. package/dist/03-patterns/tooltip/tooltip.js +213 -0
  246. package/dist/03-patterns/tree/tree.css +1 -0
  247. package/dist/03-patterns/tree/tree.js +383 -0
  248. package/dist/04-recipes/analysis-report/analysis-report.css +1 -0
  249. package/dist/04-recipes/detail-page/detail-page.css +1 -0
  250. package/dist/04-recipes/report/report.css +1 -0
  251. package/dist/04-recipes/table/table.css +1 -0
  252. package/dist/04-recipes/tierA-tokens.css +1 -0
  253. package/dist/09-assets/bar/bar.css +1 -0
  254. package/dist/09-assets/bar/bar.js +222 -0
  255. package/dist/09-assets/echarts-adapter/echarts-adapter.js +185 -0
  256. package/dist/09-assets/model-viewer/model-viewer.css +1 -0
  257. package/dist/09-assets/model-viewer/model-viewer.js +320 -0
  258. package/dist/09-assets/scientific-plot/scientific-plot.css +1 -0
  259. package/dist/09-assets/scientific-plot/scientific-plot.js +568 -0
  260. package/dist/09-assets/sparkline/sparkline.css +1 -0
  261. package/dist/09-assets/sparkline/sparkline.js +265 -0
  262. package/dist/COSTS.md +56 -0
  263. package/docs/AI-CONTRACT.md +197 -0
  264. package/docs/BACKLOG.md +127 -0
  265. package/docs/BENCHMARK.md +252 -0
  266. package/docs/GUIDANCE.md +154 -0
  267. package/docs/INVARIANT.md +372 -0
  268. package/docs/LAYER-BASELINE.md +158 -0
  269. package/docs/RELEASING.md +311 -0
  270. package/docs/RFC-THEME-SCOPE.md +141 -0
  271. package/docs/ROADMAP.md +182 -0
  272. package/index.html +82 -0
  273. package/package.json +107 -4
  274. package/types/index.d.ts +310 -0
@@ -0,0 +1,568 @@
1
+ (function (global) {
2
+ 'use strict';
3
+
4
+ var DEFAULT_MARGIN = { top: 18, right: 22, bottom: 56, left: 68 };
5
+ var SERIES_CLASSES = 5;
6
+ var DASHES = [[], [7, 4], [2, 3], [10, 4, 2, 4], [1, 3]];
7
+
8
+ var plotUid = 0;
9
+
10
+ function isNum(v) { return typeof v === 'number' && isFinite(v); }
11
+
12
+ function esc(s) {
13
+ return String(s == null ? '' : s)
14
+ .replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;')
15
+ .replace(/"/g, '&quot;');
16
+ }
17
+
18
+ /** 数字格式化:科学图里小数位是**信息**,不能顺手 toLocaleString 了事 */
19
+ function fmt(v, p) {
20
+ if (!isNum(v)) return '';
21
+ if (p === undefined || p === null) {
22
+ /* 默认:够用就行,但不要出现 1.0000000000000002 */
23
+ return String(+v.toPrecision(12));
24
+ }
25
+ return v.toFixed(p);
26
+ }
27
+
28
+ function cssVar(name, fallback) {
29
+ if (typeof getComputedStyle !== 'function' || !document.documentElement) return fallback;
30
+ var v = getComputedStyle(document.documentElement).getPropertyValue(name);
31
+ v = v ? v.trim() : '';
32
+ return v || fallback;
33
+ }
34
+
35
+ /* --------------------------------------------------------------- 比例尺 */
36
+
37
+ /**
38
+ * 造一个比例尺。
39
+ * @param {{scale?:string, domain:number[]}} axis
40
+ * @param {number[]} range 像素范围 [起, 止]
41
+ * @returns {{to:function(number):?number, type:string, domain:number[]}}
42
+ *
43
+ * 🔴 对数轴:domain 必须 > 0。给 0 或负数直接抛错 ——
44
+ * "悄悄把 0 当 1e-9" 会画出一条看起来正常、实际错误的曲线。
45
+ */
46
+ function makeScale(axis, range) {
47
+ var type = (axis && axis.scale) || 'linear';
48
+ var d = (axis && axis.domain) || [];
49
+ var d0 = d[0], d1 = d[1];
50
+ var r0 = range[0], r1 = range[1];
51
+
52
+ if (type !== 'linear' && type !== 'log') {
53
+ throw new Error('scientific-plot: 未知的 scale「' + type + '」(只支持 linear / log)');
54
+ }
55
+ if (!isNum(d0) || !isNum(d1)) {
56
+ throw new Error('scientific-plot: ' + type + ' 轴必须给 domain:[min,max]');
57
+ }
58
+ if (type === 'log' && (d0 <= 0 || d1 <= 0)) {
59
+ throw new Error('scientific-plot: 对数轴的 domain 必须 > 0(收到 [' + d0 + ', ' + d1 + '])');
60
+ }
61
+ if (d0 === d1) {
62
+ throw new Error('scientific-plot: domain 的上下限相同(' + d0 + '),无法定标');
63
+ }
64
+
65
+ var to;
66
+ if (type === 'linear') {
67
+ to = function (v) { return r0 + (v - d0) / (d1 - d0) * (r1 - r0); };
68
+ } else {
69
+ var l0 = Math.log(d0), l1 = Math.log(d1);
70
+ to = function (v) {
71
+ if (!isNum(v) || v <= 0) return null; /* 非正数在对数轴上**没有位置** */
72
+ return r0 + (Math.log(v) - l0) / (l1 - l0) * (r1 - r0);
73
+ };
74
+ }
75
+
76
+ return {
77
+ to: to, type: type, domain: [d0, d1],
78
+ /* 反向:像素 → 值(给十字线一类的交互留口子,目前 demo 用不到) */
79
+ invert: function (px) {
80
+ var t = (px - r0) / (r1 - r0);
81
+ return type === 'linear' ? d0 + t * (d1 - d0) : Math.exp(l0 + t * (l1 - l0));
82
+ }
83
+ };
84
+ }
85
+
86
+ /* ----------------------------------------------------------------- 刻度 */
87
+
88
+ /** 线性轴的"好看"刻度:1 / 2 / 5 × 10^n */
89
+ function linearTicks(d0, d1, target) {
90
+ target = target || 6;
91
+ var span = d1 - d0;
92
+ if (!isNum(span) || span <= 0) return [d0];
93
+ var step = Math.pow(10, Math.floor(Math.log(span / target) / Math.LN10));
94
+ var err = (span / target) / step;
95
+ if (err >= 7.5) step *= 10;
96
+ else if (err >= 3.5) step *= 5;
97
+ else if (err >= 1.5) step *= 2;
98
+
99
+ var out = [];
100
+ var t = Math.ceil(d0 / step) * step;
101
+ /* 用乘法推进而不是累加,避免浮点误差把刻度漂到 0.30000000000000004 */
102
+ var i = 0;
103
+ while (t <= d1 + step * 1e-9 && i < 200) {
104
+ out.push(+t.toPrecision(12));
105
+ i++;
106
+ t = Math.ceil(d0 / step) * step + i * step;
107
+ }
108
+ return out;
109
+ }
110
+
111
+ /** 对数轴刻度:主刻度是 10 的幂;跨度小时补 2× / 5× 次刻度 */
112
+ function logTicks(d0, d1) {
113
+ var e0 = Math.floor(Math.log(d0) / Math.LN10);
114
+ var e1 = Math.ceil(Math.log(d1) / Math.LN10);
115
+ var decades = e1 - e0;
116
+ /* 跨度 ≥ 6 个数量级时只保留 10 的幂 —— 否则标签会挤成一团 */
117
+ var mantissas = decades >= 6 ? [1] : [1, 2, 5];
118
+
119
+ var out = [];
120
+ for (var e = e0; e <= e1; e++) {
121
+ var base = Math.pow(10, e);
122
+ for (var i = 0; i < mantissas.length; i++) {
123
+ var v = base * mantissas[i];
124
+ if (v >= d0 * (1 - 1e-9) && v <= d1 * (1 + 1e-9)) out.push(v);
125
+ }
126
+ }
127
+ /* 排序 + 按精度去重:浮点会把同一个刻度算出两份(1000 与 1000.0000000000001) */
128
+ out.sort(function (a, b) { return a - b; });
129
+ var seen = {}, uniq = [];
130
+ out.forEach(function (v) {
131
+ var k = v.toPrecision(12);
132
+ if (!seen[k]) { seen[k] = 1; uniq.push(v); }
133
+ });
134
+ return uniq;
135
+ }
136
+
137
+ /* ------------------------------------------------------------ 序列分组 */
138
+
139
+ /**
140
+ * 把数据按"缺失"切成若干连续段。
141
+ * 🔴 这是"缺失值 ≠ 零"的落地点:跨过 null 连线 = 编造了一段不存在的趋势。
142
+ */
143
+ function splitByGaps(data, xAccessor, yAccessor) {
144
+ var segs = [], cur = [];
145
+ (data || []).forEach(function (p) {
146
+ var x = xAccessor(p), y = yAccessor(p);
147
+ if (!isNum(x) || !isNum(y)) {
148
+ if (cur.length) { segs.push(cur); cur = []; }
149
+ } else {
150
+ cur.push([x, y, p]);
151
+ }
152
+ });
153
+ if (cur.length) segs.push(cur);
154
+ return segs;
155
+ }
156
+
157
+ /* ------------------------------------------------------------- 采样函数 */
158
+
159
+ /** 把函数采样成数据点。**不做表达式求值** —— 只调用调用方给的真函数。 */
160
+ function sampleFunction(fn, a, b, n) {
161
+ n = n || 200;
162
+ var out = [];
163
+ if (typeof fn !== 'function') return out;
164
+ for (var i = 0; i <= n; i++) {
165
+ var x = a + (b - a) * (i / n);
166
+ var y;
167
+ try { y = fn(x); } catch (e) { y = null; }
168
+ out.push([x, isNum(y) ? y : null]);
169
+ }
170
+ return out;
171
+ }
172
+
173
+ /**
174
+ * 把一段数据切成"坐标都有效"的连续子段。
175
+ *
176
+ * 🔴 为什么不能直接 `xs.to(x) + ',' + ys.to(y)` 拼字符串:
177
+ * `makeScale.to()` 对**对数轴上的非正数返回 null**(它在那条轴上没有位置),
178
+ * 直接拼 ⇒ 产出 `128,null` ⇒ 浏览器把**整条** polyline 判为非法而丢弃,
179
+ * 读者看到的是"这条曲线不存在",而不是"这里有几个点画不出来"。
180
+ * ⇒ 必须**在点级别**断,而且要把断掉的点**计数**告诉调用方。
181
+ *
182
+ * @param {Array} seg [[x,y],…],不含缺失值
183
+ * @param {Object} tally { n: number } —— 被跳过的点数累加器(可为 null)
184
+ * @returns {Array} 若干段 [[[px,py],…],…],每段 ≥1 个点
185
+ */
186
+ function toRuns(seg, xs, ys, tally) {
187
+ var runs = [], cur = [];
188
+ (seg || []).forEach(function (q) {
189
+ var px = xs.to(q[0]), py = ys.to(q[1]);
190
+ /* 对数轴 ≤0 ⇒ null;非有限值 ⇒ NaN/Infinity。**两者都不能进 graphics */
191
+ if (px == null || py == null || !isFinite(px) || !isFinite(py)) {
192
+ if (tally) tally.n++;
193
+ if (cur.length) runs.push(cur);
194
+ cur = [];
195
+ return;
196
+ }
197
+ cur.push([px, py]);
198
+ });
199
+ if (cur.length) runs.push(cur);
200
+ return runs;
201
+ }
202
+
203
+ /* ------------------------------------------------------------- 主渲染 */
204
+
205
+ function render(el, spec) {
206
+ if (!el) throw new Error('scientific-plot: 缺少挂载元素');
207
+ spec = spec || {};
208
+
209
+ var W = spec.width || 640;
210
+ var H = spec.height || 400;
211
+ var m = {};
212
+ var mk = spec.margin || {};
213
+ m.top = mk.top === undefined ? DEFAULT_MARGIN.top : mk.top;
214
+ m.right = mk.right === undefined ? DEFAULT_MARGIN.right : mk.right;
215
+ m.bottom = mk.bottom === undefined ? DEFAULT_MARGIN.bottom : mk.bottom;
216
+ m.left = mk.left === undefined ? DEFAULT_MARGIN.left : mk.left;
217
+
218
+ var x0 = m.left, x1 = W - m.right;
219
+ var yTop = m.top, yBot = H - m.bottom;
220
+
221
+ var xs = makeScale(spec.x, [x0, x1]);
222
+ /* y 轴像素方向朝上 ⇒ range 反过来给 */
223
+ var ys = makeScale(spec.y, [yBot, yTop]);
224
+
225
+ var parts = [];
226
+ var label = [];
227
+
228
+ /* ---------- 网格 ---------- */
229
+ var xt = xs.type === 'log' ? logTicks(xs.domain[0], xs.domain[1])
230
+ : linearTicks(xs.domain[0], xs.domain[1]);
231
+ var yt = ys.type === 'log' ? logTicks(ys.domain[0], ys.domain[1])
232
+ : linearTicks(ys.domain[0], ys.domain[1]);
233
+
234
+ if (spec.grid !== false) {
235
+ xt.forEach(function (v) {
236
+ var px = xs.to(v); if (px == null) return;
237
+ parts.push('<line class="splot__grid" x1="' + px + '" y1="' + yTop +
238
+ '" x2="' + px + '" y2="' + yBot + '"/>');
239
+ });
240
+ yt.forEach(function (v) {
241
+ var py = ys.to(v); if (py == null) return;
242
+ parts.push('<line class="splot__grid" x1="' + x0 + '" y1="' + py +
243
+ '" x2="' + x1 + '" y2="' + py + '"/>');
244
+ });
245
+ }
246
+
247
+ /* ---------- 轴 ---------- */
248
+ parts.push('<line class="splot__axis" x1="' + x0 + '" y1="' + yBot +
249
+ '" x2="' + x1 + '" y2="' + yBot + '"/>');
250
+ parts.push('<line class="splot__axis" x1="' + x0 + '" y1="' + yTop +
251
+ '" x2="' + x0 + '" y2="' + yBot + '"/>');
252
+
253
+ /* ---------- 刻度与标签 ---------- */
254
+ xt.forEach(function (v) {
255
+ var px = xs.to(v); if (px == null) return;
256
+ parts.push('<line class="splot__tick" x1="' + px + '" y1="' + yBot +
257
+ '" x2="' + px + '" y2="' + (yBot + 5) + '"/>');
258
+ parts.push('<text class="splot__tick-label" x="' + px + '" y="' + (yBot + 20) +
259
+ '" text-anchor="middle">' + esc(fmtTick(v, xs.type)) + '</text>');
260
+ });
261
+ yt.forEach(function (v) {
262
+ var py = ys.to(v); if (py == null) return;
263
+ parts.push('<line class="splot__tick" x1="' + (x0 - 5) + '" y1="' + py +
264
+ '" x2="' + x0 + '" y2="' + py + '"/>');
265
+ parts.push('<text class="splot__tick-label" x="' + (x0 - 9) + '" y="' + (py + 4) +
266
+ '" text-anchor="end">' + esc(fmtTick(v, ys.type)) + '</text>');
267
+ });
268
+
269
+ /* ---------- 轴名与单位(原则①) ---------- */
270
+ var xl = axisTitle(spec.x, 'x');
271
+ if (xl) {
272
+ parts.push('<text class="splot__axis-label" x="' + ((x0 + x1) / 2) + '" y="' +
273
+ (H - 12) + '" text-anchor="middle">' + esc(xl) + '</text>');
274
+ }
275
+ var yl = axisTitle(spec.y, 'y');
276
+ if (yl) {
277
+ parts.push('<text class="splot__axis-label" transform="translate(16,' +
278
+ ((yTop + yBot) / 2) + ') rotate(-90)" text-anchor="middle">' +
279
+ esc(yl) + '</text>');
280
+ }
281
+
282
+ /* ---------- 数据序列 ---------- */
283
+ var series = spec.series || [];
284
+ var dparts = []; /* 数据层单独收集 ⇒ 统一挂裁剪。见下方 clipId */
285
+ var tally = { n: 0 }; /* 画不出来的点数(对数轴非正 / 非有限值) */
286
+
287
+ /* 🔴🔴 系列样式身份(外部评审 VIZ-REPORT-01 · P0-2)
288
+ ------------------------------------------------------------------
289
+ 修复前每个系列都拿 `'splot__s' + (colorIdx % 5)`,而 `colorIdx` 从头到尾
290
+ 是 0 ⇒ 六条 series 全是 s0,同色同线型,读者分不出哪条是哪条材料。
291
+
292
+ ⚠️ 修法**不能**是"在循环末尾加一行 colorIdx++":
293
+ 那样每条 *记录* 都会吃掉一个色号,同一材料的「区间带」和「曲线」
294
+ 会变成两个颜色 —— 错得更隐蔽(读者会以为是两种材料)。
295
+
296
+ ⇒ 正确做法是给**逻辑系列**发身份:
297
+ · 有 `id`(或 `name`)⇒ 身份就是它,**带和线共用同一个色号**
298
+ · 没有 ⇒ 这条记录自成一个系列(至少彼此能分辨)
299
+ 这条规则同时也决定了图例怎么排(同一身份只出现一次)。 */
300
+ var styleIds = {}; /* 身份 → 色号 */
301
+ var styleSeq = 0; /* 已发出的最大色号 +1 */
302
+ function styleIndexOf(s) {
303
+ var key = (s.id != null && s.id !== '') ? s.id : s.name;
304
+ if (key == null || key === '') return styleSeq++;
305
+ var k = String(key);
306
+ if (!Object.prototype.hasOwnProperty.call(styleIds, k)) styleIds[k] = styleSeq++;
307
+ return styleIds[k];
308
+ }
309
+ var legendOrder = []; /* [{ key, index }] 按首次出现顺序 */
310
+ var drawn = 0; /* 画出来的系列条数(给 aria-label 与门禁用) */
311
+ /* 像素坐标是否可用:null(对数轴非正)与 NaN/Infinity 都不许进图形 */
312
+ function okPx(v) { return v != null && isFinite(v); }
313
+
314
+ series.forEach(function (s) {
315
+ var st = styleIndexOf(s);
316
+ var cls = 'splot__s' + (st % SERIES_CLASSES);
317
+ if (s.name != null && s.name !== '') {
318
+ var key = String(s.id != null && s.id !== '' ? s.id : s.name);
319
+ var seen = legendOrder.some(function (o) { return o.key === key; });
320
+ if (!seen) legendOrder.push({ key: key, name: String(s.name), index: st });
321
+ }
322
+ var dMin = s.samples || 200;
323
+ var data = s.type === 'function'
324
+ ? sampleFunction(s.fn, (s.domain || xs.domain)[0], (s.domain || xs.domain)[1], dMin)
325
+ : (s.data || []);
326
+
327
+ if (s.type === 'band') {
328
+ /* 置信区间带:上下两条边 + 中间填充
329
+ 🔴 与折线同一条纪律:**不能跨过画不出来的点**。
330
+ 一路 push 到同一个 polygon ⇒ 遇到对数轴上 ≤0 的边界时,
331
+ 这段"根本没有数据"的区间会被**连成一块完整的色带**,
332
+ 等于向读者宣称那里有观测支撑。 ⇒ 按连续可用段切开。 */
333
+ var bRuns = [], bCur = [];
334
+ data.forEach(function (p) {
335
+ var px = xs.to(p[0]), hi = ys.to(p[2]), lo = ys.to(p[1]);
336
+ if (!okPx(px) || !okPx(hi) || !okPx(lo)) {
337
+ tally.n++;
338
+ if (bCur.length >= 2) bRuns.push(bCur);
339
+ bCur = [];
340
+ return;
341
+ }
342
+ bCur.push([px, hi, lo]);
343
+ });
344
+ if (bCur.length >= 2) bRuns.push(bCur);
345
+
346
+ bRuns.forEach(function (run) {
347
+ var up = [], dn = [];
348
+ run.forEach(function (c) { up.push(c[0] + ',' + c[1]); });
349
+ for (var j = run.length - 1; j >= 0; j--) dn.push(run[j][0] + ',' + run[j][2]);
350
+ dparts.push('<polygon class="splot__band ' + cls + '" points="' +
351
+ up.join(' ') + ' ' + dn.join(' ') + '"/>');
352
+ });
353
+ if (bRuns.length) drawn++;
354
+ return;
355
+ }
356
+
357
+ if (s.type === 'errorbar') {
358
+ /* 误差棒:只画调用方给的误差,**不计算** */
359
+ data.forEach(function (p) {
360
+ var px = xs.to(p[0]);
361
+ if (!okPx(px) || !isNum(p[1])) { tally.n++; return; }
362
+ var py = ys.to(p[1]); if (!okPx(py)) { tally.n++; return; }
363
+ /* 两种写法都支持:
364
+ [x, y, yerr] —— 对称误差,上下各延 yerr
365
+ [x, y, lo, hi] —— 非对称区间(调用方自己算好的边界)
366
+ 🔴 本模块只画给定的边界,**不替调用方计算**标准误或置信区间。 */
367
+ var yLo = p.length > 3 ? p[2] : p[1] - (p[2] || 0);
368
+ var yHi = p.length > 3 ? p[3] : p[1] + (p[2] || 0);
369
+ var pa = ys.to(yLo), pb = ys.to(yHi);
370
+ if (!okPx(pa) || !okPx(pb)) { tally.n++; return; }
371
+ dparts.push('<line class="splot__err ' + cls + '" x1="' + px + '" y1="' + pa +
372
+ '" x2="' + px + '" y2="' + pb + '"/>');
373
+ dparts.push('<line class="splot__err ' + cls + '" x1="' + (px - 4) + '" y1="' + pa +
374
+ '" x2="' + (px + 4) + '" y2="' + pa + '"/>');
375
+ dparts.push('<line class="splot__err ' + cls + '" x1="' + (px - 4) + '" y1="' + pb +
376
+ '" x2="' + (px + 4) + '" y2="' + pb + '"/>');
377
+ });
378
+ drawn++;
379
+ return;
380
+ }
381
+
382
+ if (s.type === 'line' || s.type === 'function') {
383
+ /* 🔴 ① 按缺失分段,**不跨 null 连线**(原则②)
384
+ ② 段内再按"坐标算不算得出来"切开(对数轴 ≤0 / 非有限值) */
385
+ var segs = splitByGaps(data, function (p) { return p[0]; }, function (p) { return p[1]; });
386
+ segs.forEach(function (seg) {
387
+ toRuns(seg, xs, ys, tally).forEach(function (run) {
388
+ if (run.length < 2) {
389
+ /* 单个孤立点也要能看见,否则它就从图上消失了 */
390
+ dparts.push('<circle class="splot__pt ' + cls + '" cx="' + run[0][0] +
391
+ '" cy="' + run[0][1] + '" r="2.5"/>');
392
+ return;
393
+ }
394
+ var pts = run.map(function (c) { return c[0] + ',' + c[1]; }).join(' ');
395
+ dparts.push('<polyline class="splot__line ' + cls + '" points="' + pts +
396
+ '"' + dashAttr(st) + '/>');
397
+ });
398
+ });
399
+ drawn++;
400
+ return;
401
+ }
402
+
403
+ if (s.type === 'scatter') {
404
+ data.forEach(function (p) {
405
+ var px = xs.to(p[0]);
406
+ if (!okPx(px) || !isNum(p[1])) { tally.n++; return; }
407
+ var py = ys.to(p[1]); if (!okPx(py)) { tally.n++; return; }
408
+ dparts.push('<circle class="splot__pt ' + cls + '" cx="' + px + '" cy="' + py +
409
+ '" r="3"/>');
410
+ });
411
+ drawn++;
412
+ return;
413
+ }
414
+ });
415
+
416
+ /* ---------- 🔴 裁剪:数据不许画到坐标域之外 ----------
417
+ SVG 的 `overflow` 默认是 visible:一个超出 domain 的散点会直接落到
418
+ 轴外侧、甚至压到相邻的卡片上(09-assets/bar 真机踩过同一个坑)。
419
+ ⇒ 用 clipPath 把**数据层**锁在坐标矩形内;网格与轴不参与(它们本来就在边上)。
420
+ ⚠️ id 必须每次渲染都换新的:同一页多张图共用 id ⇒ 后面的图会被前面的裁掉。 */
421
+ var clipId = 'splot-clip-' + (++plotUid);
422
+ parts.push('<defs><clipPath id="' + clipId + '"><rect x="' + x0 + '" y="' + yTop +
423
+ '" width="' + (x1 - x0) + '" height="' + (yBot - yTop) +
424
+ '"/></clipPath></defs>');
425
+ parts.push('<g clip-path="url(#' + clipId + ')">' + dparts.join('') + '</g>');
426
+
427
+ /* ---------- 参考线 ---------- */
428
+ (spec.refs || []).forEach(function (r) {
429
+ var isY = r.axis === 'y';
430
+ var p = isY ? ys.to(r.value) : xs.to(r.value);
431
+ if (p == null) return;
432
+ if (isY) {
433
+ parts.push('<line class="splot__ref" x1="' + x0 + '" y1="' + p +
434
+ '" x2="' + x1 + '" y2="' + p + '"/>');
435
+ if (r.label) {
436
+ parts.push('<text class="splot__ref-label" x="' + (x1 - 4) + '" y="' + (p - 5) +
437
+ '" text-anchor="end">' + esc(r.label) + '</text>');
438
+ }
439
+ } else {
440
+ parts.push('<line class="splot__ref" x1="' + p + '" y1="' + yTop +
441
+ '" x2="' + p + '" y2="' + yBot + '"/>');
442
+ if (r.label) {
443
+ parts.push('<text class="splot__ref-label" x="' + (p + 5) + '" y="' + (yTop + 12) +
444
+ '">' + esc(r.label) + '</text>');
445
+ }
446
+ }
447
+ });
448
+
449
+ /* ---------- 点标注 ---------- */
450
+ (spec.annotations || []).forEach(function (a) {
451
+ var px = xs.to(a.x), py = ys.to(a.y);
452
+ if (px == null || py == null) return;
453
+ parts.push('<circle class="splot__anno-dot" cx="' + px + '" cy="' + py + '" r="3.5"/>');
454
+ parts.push('<text class="splot__anno" x="' + (px + 7) + '" y="' + (py - 6) + '">' +
455
+ esc(a.text) + '</text>');
456
+ });
457
+
458
+ /* ---------- 截断提示(原则③) ---------- */
459
+ if (spec.truncated) {
460
+ parts.push('<text class="splot__note" x="' + x1 + '" y="' + (yTop - 4) +
461
+ '" text-anchor="end">⚠ 轴已截断,非从零起</text>');
462
+ }
463
+
464
+ /* 🔴 有画不出来的点就**明说**(对数轴 ≤0 / 非有限值)
465
+ —— 静默丢弃会让读者以为"这条曲线整个就是这个形状" */
466
+ if (tally.n > 0) {
467
+ parts.push('<text class="splot__note" x="' + x0 + '" y="' + (yTop - 4) + '">⚠ ' +
468
+ tally.n + ' 个点不在数轴范围内,未绘制</text>');
469
+ }
470
+
471
+ /* ---------- 文本等价信息(原则⑤) ---------- */
472
+ label.push(spec.title || '科学绘图');
473
+ if (xl) label.push('横轴:' + xl);
474
+ if (yl) label.push('纵轴:' + yl);
475
+ label.push('共 ' + drawn + ' 个数据系列');
476
+ /* 系列名要进 aria-label:只用颜色区分 ⇔ 读屏用户完全无从分辨 */
477
+ if (legendOrder.length) {
478
+ label.push('系列:' + legendOrder.map(function (o) { return o.name; }).join('、'));
479
+ }
480
+ if (tally.n > 0) label.push('有 ' + tally.n + ' 个点不在轴范围内未绘制');
481
+ if (spec.desc) label.push(spec.desc);
482
+
483
+ var svg = '<svg class="splot ' + esc(spec.className || '') + '" viewBox="0 0 ' + W + ' ' + H +
484
+ '" width="' + W + '" height="' + H + '" role="img" aria-label="' +
485
+ esc(label.join('。')) + '">' + parts.join('') + '</svg>';
486
+
487
+ /* ---------- 图例(B7:颜色之外必须有名字) ---------- *
488
+ * 🔴 什么时候出:**有两个以上有名字的系列**时自动出。
489
+ * 三条曲线的颜色不同、但没有任何文字告诉你谁是谁 ⇒ 这张图回答不了问题。
490
+ * ⇒ 想关就显式传 `legend: false`。
491
+ * ⚠️ 同一身份(带 + 线)只出现**一次**,并且色块取的就是它自己那条线的色号。 */
492
+ var legendHtml = '';
493
+ var wantLegend = spec.legend === undefined
494
+ ? legendOrder.length >= 2
495
+ : !!spec.legend;
496
+ if (wantLegend && legendOrder.length) {
497
+ var items = legendOrder.map(function (o) {
498
+ var c = 'splot__s' + (o.index % SERIES_CLASSES);
499
+ /* 用一小段**真的线**做色标,`--splot-c` 由上面的系列类给
500
+ ⇒ 图例的色和曲线/色带的色来自同一份声明,不可能对不上 */
501
+ return '<li class="splot__legend-item">' +
502
+ '<svg class="splot__legend-mark ' + c + '" width="20" height="10" ' +
503
+ 'viewBox="0 0 20 10" aria-hidden="true" focusable="false">' +
504
+ '<line x1="1" y1="5" x2="19" y2="5" stroke-width="2" stroke-linecap="round"' +
505
+ dashAttr(o.index) + '/></svg>' +
506
+ '<span>' + esc(o.name) + '</span></li>';
507
+ });
508
+ legendHtml = '<ul class="splot__legend">' + items.join('') + '</ul>';
509
+ }
510
+
511
+ el.innerHTML = svg + legendHtml;
512
+ return {
513
+ svg: el.firstChild,
514
+ x: xs, y: ys,
515
+ ticks: { x: xt, y: yt },
516
+ series: drawn,
517
+ dropped: tally.n,
518
+ legend: el.querySelector('.splot__legend'),
519
+ styles: legendOrder.map(function (o) { return o.index; }),
520
+ };
521
+ }
522
+
523
+ /* ------------------------------------------------------------- 小助手 */
524
+
525
+ function axisTitle(axis, fallback) {
526
+ if (!axis) return '';
527
+ var t = axis.label || axis.title || fallback;
528
+ if (axis.unit) t += ' / ' + axis.unit;
529
+ return t;
530
+ }
531
+
532
+ function fmtTick(v, type) {
533
+ if (type === 'log') {
534
+ if (v >= 1e4 || v < 1e-3) {
535
+ var e = Math.round(Math.log(v) / Math.LN10);
536
+ return '1e' + e;
537
+ }
538
+ return String(+v.toPrecision(4));
539
+ }
540
+ if (Math.abs(v) >= 1e5 || (v !== 0 && Math.abs(v) < 1e-3)) {
541
+ return (+v.toExponential(1)).toString();
542
+ }
543
+ return String(+v.toPrecision(6));
544
+ }
545
+
546
+ function dashAttr(idx) {
547
+ var d = DASHES[idx % DASHES.length];
548
+ if (!d || !d.length) return '';
549
+ return ' stroke-dasharray="' + d.join(' ') + '"';
550
+ }
551
+
552
+ /* --------------------------------------------------------------- 导出 */
553
+
554
+ global.ScientificPlot = {
555
+ render: render,
556
+ /* 给门禁/单测用的真值入口 —— 判据要能直接调它,而不是靠读 DOM 反推 */
557
+ __internals: {
558
+ makeScale: makeScale,
559
+ linearTicks: linearTicks,
560
+ logTicks: logTicks,
561
+ splitByGaps: splitByGaps,
562
+ toRuns: toRuns,
563
+ sampleFunction: sampleFunction,
564
+ SERIES_CLASSES: SERIES_CLASSES
565
+ }
566
+ };
567
+
568
+ })(typeof window !== 'undefined' ? window : this);
@@ -0,0 +1 @@
1
+ .sparkline {display:block;min-width:0;width:100%;height:var(--spark-h, 32px);overflow:hidden;margin:var(--sp-1) 0;}.sparkline svg {display:block;width:100%;overflow:hidden;}.spark-line {stroke:var(--spark-c, var(--accent));fill:none;}.spark-area {stroke:none;fill:var(--spark-c, var(--accent));}.spark-bar {stroke:none;fill:var(--spark-c, var(--accent));}.spark-dot {stroke:none;fill:var(--spark-c, var(--accent));}.row > .sparkline, td > .sparkline, .spark {margin:0;}.spark-anim {stroke-dasharray:var(--spark-len, 1000);stroke-dashoffset:0;animation:spark-draw var(--dur-slow, 600ms) var(--ease-out) forwards;}@keyframes spark-draw {from {stroke-dashoffset:var(--spark-len, 1000);}to {stroke-dashoffset:0;}}@media (prefers-reduced-motion:reduce) {.spark-anim {animation:none;stroke-dashoffset:0;}}@media print {.spark-anim {animation:none;stroke-dashoffset:0;}}