@keyfunc/keybricks-ds 1.0.0

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 (418) hide show
  1. package/DEPLOY.md +204 -0
  2. package/DESIGN.md +269 -0
  3. package/INTEGRATION.md +417 -0
  4. package/SKILL.md +32 -0
  5. package/_adherence.oxlintrc.json +1469 -0
  6. package/_scripts/build-index.mjs +80 -0
  7. package/_scripts/build-tokens.mjs +135 -0
  8. package/assets/icons/icon-index.js +1 -0
  9. package/assets/icons/icon-index.json +1 -0
  10. package/assets/icons/unicons-index.js +1 -0
  11. package/assets/icons/unicons-index.json +1 -0
  12. package/assets/keybricks-logo.png +0 -0
  13. package/components/_cards/accordion.card.html +32 -0
  14. package/components/_cards/action-chip.card.html +22 -0
  15. package/components/_cards/activity.card.html +34 -0
  16. package/components/_cards/alert.card.html +32 -0
  17. package/components/_cards/app-chrome.card.html +36 -0
  18. package/components/_cards/banner.card.html +21 -0
  19. package/components/_cards/brand-visual.card.html +43 -0
  20. package/components/_cards/breadcrumb.card.html +23 -0
  21. package/components/_cards/button-1-base.card.html +39 -0
  22. package/components/_cards/button-2-states.card.html +56 -0
  23. package/components/_cards/button-3-sizes-icons.card.html +32 -0
  24. package/components/_cards/button-4-group.card.html +47 -0
  25. package/components/_cards/canvas-zoom-controls.card.html +19 -0
  26. package/components/_cards/cards-indicators.card.html +59 -0
  27. package/components/_cards/charts-1-sparkline.card.html +55 -0
  28. package/components/_cards/charts-10-gauge.card.html +31 -0
  29. package/components/_cards/charts-11-funnel.card.html +28 -0
  30. package/components/_cards/charts-12-treemap.card.html +23 -0
  31. package/components/_cards/charts-14-radar.card.html +31 -0
  32. package/components/_cards/charts-15-distribution.card.html +32 -0
  33. package/components/_cards/charts-16-pareto.card.html +25 -0
  34. package/components/_cards/charts-17-candlestick.card.html +34 -0
  35. package/components/_cards/charts-18-sankey.card.html +29 -0
  36. package/components/_cards/charts-19-chord.card.html +31 -0
  37. package/components/_cards/charts-2-line.card.html +46 -0
  38. package/components/_cards/charts-20-sunburst.card.html +38 -0
  39. package/components/_cards/charts-21-worldmap.card.html +33 -0
  40. package/components/_cards/charts-22-gantt.card.html +37 -0
  41. package/components/_cards/charts-23-bar3d.card.html +26 -0
  42. package/components/_cards/charts-24-pie3d.card.html +32 -0
  43. package/components/_cards/charts-25-surface3d.card.html +24 -0
  44. package/components/_cards/charts-26-scatter3d.card.html +24 -0
  45. package/components/_cards/charts-27-ribbon3d.card.html +23 -0
  46. package/components/_cards/charts-3-bar.card.html +71 -0
  47. package/components/_cards/charts-4-barlist.card.html +30 -0
  48. package/components/_cards/charts-5-donut.card.html +35 -0
  49. package/components/_cards/charts-6-scatter.card.html +26 -0
  50. package/components/_cards/charts-7-heatmap.card.html +28 -0
  51. package/components/_cards/charts-8-waterfall.card.html +25 -0
  52. package/components/_cards/charts-9-bullet.card.html +31 -0
  53. package/components/_cards/charts-99-fullpage.card.html +194 -0
  54. package/components/_cards/checkbox.card.html +34 -0
  55. package/components/_cards/codeblock.card.html +19 -0
  56. package/components/_cards/combobox.card.html +25 -0
  57. package/components/_cards/context-menu.card.html +21 -0
  58. package/components/_cards/datatable.card.html +48 -0
  59. package/components/_cards/date-picker.card.html +31 -0
  60. package/components/_cards/diffvalue.card.html +21 -0
  61. package/components/_cards/divider.card.html +24 -0
  62. package/components/_cards/drawer.card.html +30 -0
  63. package/components/_cards/dropdown-menu.card.html +38 -0
  64. package/components/_cards/dropzone.card.html +22 -0
  65. package/components/_cards/empty-state.card.html +29 -0
  66. package/components/_cards/filter-bar.card.html +34 -0
  67. package/components/_cards/icon-button.card.html +27 -0
  68. package/components/_cards/icons-1-core.card.html +49 -0
  69. package/components/_cards/icons-2-feather.card.html +39 -0
  70. package/components/_cards/icons-3-unicons.card.html +44 -0
  71. package/components/_cards/input.card.html +25 -0
  72. package/components/_cards/kbd.card.html +20 -0
  73. package/components/_cards/keyvalue.card.html +23 -0
  74. package/components/_cards/listrow.card.html +30 -0
  75. package/components/_cards/loading.card.html +33 -0
  76. package/components/_cards/log-viewer.card.html +23 -0
  77. package/components/_cards/modal.card.html +35 -0
  78. package/components/_cards/multi-select.card.html +26 -0
  79. package/components/_cards/number-stepper.card.html +24 -0
  80. package/components/_cards/pagination.card.html +25 -0
  81. package/components/_cards/people.card.html +46 -0
  82. package/components/_cards/pipeline-node.card.html +76 -0
  83. package/components/_cards/pipeline-palette.card.html +54 -0
  84. package/components/_cards/search.card.html +23 -0
  85. package/components/_cards/select.card.html +23 -0
  86. package/components/_cards/slider.card.html +25 -0
  87. package/components/_cards/smart-table.card.html +37 -0
  88. package/components/_cards/spreadsheet.card.html +31 -0
  89. package/components/_cards/stepper.card.html +25 -0
  90. package/components/_cards/tabs.card.html +25 -0
  91. package/components/_cards/tag.card.html +26 -0
  92. package/components/_cards/textarea.card.html +27 -0
  93. package/components/_cards/timeline.card.html +32 -0
  94. package/components/_cards/toast.card.html +33 -0
  95. package/components/_cards/toggles.card.html +68 -0
  96. package/components/_cards/tooltip.card.html +22 -0
  97. package/components/_cards/tree.card.html +150 -0
  98. package/components/_shared/frame-theme.js +16 -0
  99. package/components/_shared/src-brand.js +1 -0
  100. package/components/_shared/src-buttons.js +1 -0
  101. package/components/_shared/src-charts.js +1 -0
  102. package/components/_shared/src-data.js +1 -0
  103. package/components/_shared/src-display.js +1 -0
  104. package/components/_shared/src-feedback.js +1 -0
  105. package/components/_shared/src-forms.js +1 -0
  106. package/components/_shared/src-icons.js +1 -0
  107. package/components/_shared/src-navigation.js +1 -0
  108. package/components/_shared/src-pipeline.js +1 -0
  109. package/components/_shared/src-tabs.js +1 -0
  110. package/components/_shared/svg-download.js +19 -0
  111. package/components/_shared/view-source.js +185 -0
  112. package/components/_vue/index.mjs +114 -0
  113. package/components/brand/BrandVisual.d.ts +17 -0
  114. package/components/brand/BrandVisual.jsx +29 -0
  115. package/components/brand/BrandVisual.vue +34 -0
  116. package/components/brand/_dir.card.html +13 -0
  117. package/components/brand/brandMotifs.js +210 -0
  118. package/components/buttons/ActionChip.d.ts +10 -0
  119. package/components/buttons/ActionChip.jsx +20 -0
  120. package/components/buttons/ActionChip.vue +18 -0
  121. package/components/buttons/Button.d.ts +40 -0
  122. package/components/buttons/Button.jsx +101 -0
  123. package/components/buttons/Button.vue +87 -0
  124. package/components/buttons/IconButton.d.ts +26 -0
  125. package/components/buttons/IconButton.jsx +28 -0
  126. package/components/buttons/IconButton.vue +21 -0
  127. package/components/buttons/_dir.card.html +13 -0
  128. package/components/buttons/buttons.prompt.md +19 -0
  129. package/components/charts/Bar3D.d.ts +15 -0
  130. package/components/charts/Bar3D.jsx +93 -0
  131. package/components/charts/Bar3D.vue +79 -0
  132. package/components/charts/BarChart.d.ts +23 -0
  133. package/components/charts/BarChart.jsx +135 -0
  134. package/components/charts/BarChart.vue +139 -0
  135. package/components/charts/BarList.d.ts +13 -0
  136. package/components/charts/BarList.jsx +31 -0
  137. package/components/charts/BarList.vue +25 -0
  138. package/components/charts/BoxPlot.d.ts +18 -0
  139. package/components/charts/BoxPlot.jsx +62 -0
  140. package/components/charts/BoxPlot.vue +58 -0
  141. package/components/charts/BulletChart.d.ts +12 -0
  142. package/components/charts/BulletChart.jsx +41 -0
  143. package/components/charts/BulletChart.vue +33 -0
  144. package/components/charts/CandlestickChart.d.ts +15 -0
  145. package/components/charts/CandlestickChart.jsx +63 -0
  146. package/components/charts/CandlestickChart.vue +62 -0
  147. package/components/charts/ChartKit.d.ts +8 -0
  148. package/components/charts/ChartKit.jsx +76 -0
  149. package/components/charts/ChartKit.vue +47 -0
  150. package/components/charts/ChordChart.d.ts +13 -0
  151. package/components/charts/ChordChart.jsx +55 -0
  152. package/components/charts/ChordChart.vue +54 -0
  153. package/components/charts/DonutChart.d.ts +15 -0
  154. package/components/charts/DonutChart.jsx +73 -0
  155. package/components/charts/DonutChart.vue +61 -0
  156. package/components/charts/FunnelChart.d.ts +11 -0
  157. package/components/charts/FunnelChart.jsx +37 -0
  158. package/components/charts/FunnelChart.vue +29 -0
  159. package/components/charts/GanttChart.d.ts +20 -0
  160. package/components/charts/GanttChart.jsx +80 -0
  161. package/components/charts/GanttChart.vue +60 -0
  162. package/components/charts/GaugeChart.d.ts +13 -0
  163. package/components/charts/GaugeChart.jsx +33 -0
  164. package/components/charts/GaugeChart.vue +29 -0
  165. package/components/charts/HeatmapChart.d.ts +13 -0
  166. package/components/charts/HeatmapChart.jsx +62 -0
  167. package/components/charts/HeatmapChart.vue +38 -0
  168. package/components/charts/Histogram.d.ts +16 -0
  169. package/components/charts/Histogram.jsx +56 -0
  170. package/components/charts/Histogram.vue +53 -0
  171. package/components/charts/LineChart.d.ts +17 -0
  172. package/components/charts/LineChart.jsx +84 -0
  173. package/components/charts/LineChart.vue +86 -0
  174. package/components/charts/ParetoChart.d.ts +13 -0
  175. package/components/charts/ParetoChart.jsx +54 -0
  176. package/components/charts/ParetoChart.vue +55 -0
  177. package/components/charts/Pie3D.d.ts +17 -0
  178. package/components/charts/Pie3D.jsx +145 -0
  179. package/components/charts/Pie3D.vue +115 -0
  180. package/components/charts/RadarChart.d.ts +14 -0
  181. package/components/charts/RadarChart.jsx +79 -0
  182. package/components/charts/RadarChart.vue +73 -0
  183. package/components/charts/Ribbon3D.d.ts +13 -0
  184. package/components/charts/Ribbon3D.jsx +89 -0
  185. package/components/charts/Ribbon3D.vue +79 -0
  186. package/components/charts/SankeyChart.d.ts +15 -0
  187. package/components/charts/SankeyChart.jsx +59 -0
  188. package/components/charts/SankeyChart.vue +53 -0
  189. package/components/charts/Scatter3D.d.ts +12 -0
  190. package/components/charts/Scatter3D.jsx +69 -0
  191. package/components/charts/Scatter3D.vue +59 -0
  192. package/components/charts/ScatterChart.d.ts +12 -0
  193. package/components/charts/ScatterChart.jsx +53 -0
  194. package/components/charts/ScatterChart.vue +57 -0
  195. package/components/charts/Sparkline.d.ts +19 -0
  196. package/components/charts/Sparkline.jsx +63 -0
  197. package/components/charts/Sparkline.vue +54 -0
  198. package/components/charts/SunburstChart.d.ts +12 -0
  199. package/components/charts/SunburstChart.jsx +61 -0
  200. package/components/charts/SunburstChart.vue +60 -0
  201. package/components/charts/Surface3D.d.ts +13 -0
  202. package/components/charts/Surface3D.jsx +70 -0
  203. package/components/charts/Surface3D.vue +63 -0
  204. package/components/charts/TreemapChart.d.ts +11 -0
  205. package/components/charts/TreemapChart.jsx +35 -0
  206. package/components/charts/TreemapChart.vue +27 -0
  207. package/components/charts/WaterfallChart.d.ts +11 -0
  208. package/components/charts/WaterfallChart.jsx +59 -0
  209. package/components/charts/WaterfallChart.vue +46 -0
  210. package/components/charts/WorldMap.d.ts +21 -0
  211. package/components/charts/WorldMap.jsx +107 -0
  212. package/components/charts/WorldMap.vue +73 -0
  213. package/components/charts/_dir.card.html +7 -0
  214. package/components/charts/chart-3d.js +84 -0
  215. package/components/charts/chart-geometry.js +223 -0
  216. package/components/charts/charts.prompt.md +24 -0
  217. package/components/data/CodeBlock.d.ts +9 -0
  218. package/components/data/CodeBlock.jsx +19 -0
  219. package/components/data/CodeBlock.vue +22 -0
  220. package/components/data/DataTable.d.ts +28 -0
  221. package/components/data/DataTable.jsx +32 -0
  222. package/components/data/DataTable.vue +25 -0
  223. package/components/data/DiffValue.d.ts +11 -0
  224. package/components/data/DiffValue.jsx +15 -0
  225. package/components/data/DiffValue.vue +13 -0
  226. package/components/data/FilterBar.d.ts +16 -0
  227. package/components/data/FilterBar.jsx +22 -0
  228. package/components/data/FilterBar.vue +23 -0
  229. package/components/data/KeyValue.d.ts +16 -0
  230. package/components/data/KeyValue.jsx +25 -0
  231. package/components/data/KeyValue.vue +26 -0
  232. package/components/data/ListRow.d.ts +14 -0
  233. package/components/data/ListRow.jsx +25 -0
  234. package/components/data/ListRow.vue +25 -0
  235. package/components/data/Pagination.d.ts +12 -0
  236. package/components/data/Pagination.jsx +27 -0
  237. package/components/data/Pagination.vue +24 -0
  238. package/components/data/SmartTable.d.ts +30 -0
  239. package/components/data/SmartTable.jsx +73 -0
  240. package/components/data/SmartTable.vue +74 -0
  241. package/components/data/Spreadsheet.d.ts +33 -0
  242. package/components/data/Spreadsheet.jsx +110 -0
  243. package/components/data/Spreadsheet.vue +95 -0
  244. package/components/data/_dir.card.html +13 -0
  245. package/components/data/data.prompt.md +33 -0
  246. package/components/display/Avatar.d.ts +21 -0
  247. package/components/display/Avatar.jsx +31 -0
  248. package/components/display/Avatar.vue +34 -0
  249. package/components/display/Badge.d.ts +27 -0
  250. package/components/display/Badge.jsx +40 -0
  251. package/components/display/Badge.vue +47 -0
  252. package/components/display/Card.d.ts +27 -0
  253. package/components/display/Card.jsx +27 -0
  254. package/components/display/Card.vue +28 -0
  255. package/components/display/Divider.d.ts +8 -0
  256. package/components/display/Divider.jsx +14 -0
  257. package/components/display/Divider.vue +13 -0
  258. package/components/display/Kbd.d.ts +7 -0
  259. package/components/display/Kbd.jsx +12 -0
  260. package/components/display/Kbd.vue +9 -0
  261. package/components/display/ProgressBar.d.ts +42 -0
  262. package/components/display/ProgressBar.jsx +70 -0
  263. package/components/display/ProgressBar.vue +69 -0
  264. package/components/display/Tag.d.ts +9 -0
  265. package/components/display/Tag.jsx +15 -0
  266. package/components/display/Tag.vue +15 -0
  267. package/components/display/_dir.card.html +13 -0
  268. package/components/display/display.prompt.md +30 -0
  269. package/components/feedback/Accordion.d.ts +13 -0
  270. package/components/feedback/Accordion.jsx +21 -0
  271. package/components/feedback/Accordion.vue +22 -0
  272. package/components/feedback/ActivityItem.d.ts +25 -0
  273. package/components/feedback/ActivityItem.jsx +39 -0
  274. package/components/feedback/ActivityItem.vue +46 -0
  275. package/components/feedback/Alert.d.ts +36 -0
  276. package/components/feedback/Alert.jsx +48 -0
  277. package/components/feedback/Alert.vue +59 -0
  278. package/components/feedback/Banner.d.ts +15 -0
  279. package/components/feedback/Banner.jsx +25 -0
  280. package/components/feedback/Banner.vue +25 -0
  281. package/components/feedback/ContextMenu.d.ts +8 -0
  282. package/components/feedback/ContextMenu.jsx +18 -0
  283. package/components/feedback/ContextMenu.vue +19 -0
  284. package/components/feedback/Drawer.d.ts +18 -0
  285. package/components/feedback/Drawer.jsx +26 -0
  286. package/components/feedback/Drawer.vue +24 -0
  287. package/components/feedback/Menu.d.ts +10 -0
  288. package/components/feedback/Menu.jsx +24 -0
  289. package/components/feedback/Menu.vue +21 -0
  290. package/components/feedback/Modal.d.ts +38 -0
  291. package/components/feedback/Modal.jsx +69 -0
  292. package/components/feedback/Modal.vue +65 -0
  293. package/components/feedback/Stepper.d.ts +9 -0
  294. package/components/feedback/Stepper.jsx +24 -0
  295. package/components/feedback/Stepper.vue +18 -0
  296. package/components/feedback/Timeline.d.ts +15 -0
  297. package/components/feedback/Timeline.jsx +27 -0
  298. package/components/feedback/Timeline.vue +21 -0
  299. package/components/feedback/_dir.card.html +13 -0
  300. package/components/feedback/feedback.prompt.md +35 -0
  301. package/components/forms/Checkbox.d.ts +13 -0
  302. package/components/forms/Checkbox.jsx +17 -0
  303. package/components/forms/Checkbox.vue +18 -0
  304. package/components/forms/Combobox.d.ts +13 -0
  305. package/components/forms/Combobox.jsx +34 -0
  306. package/components/forms/Combobox.vue +35 -0
  307. package/components/forms/DatePicker.d.ts +12 -0
  308. package/components/forms/DatePicker.jsx +48 -0
  309. package/components/forms/DatePicker.vue +46 -0
  310. package/components/forms/Dropzone.d.ts +20 -0
  311. package/components/forms/Dropzone.jsx +36 -0
  312. package/components/forms/Dropzone.vue +28 -0
  313. package/components/forms/Field.d.ts +13 -0
  314. package/components/forms/Field.jsx +13 -0
  315. package/components/forms/Field.vue +12 -0
  316. package/components/forms/Input.d.ts +18 -0
  317. package/components/forms/Input.jsx +35 -0
  318. package/components/forms/Input.vue +38 -0
  319. package/components/forms/MultiSelect.d.ts +14 -0
  320. package/components/forms/MultiSelect.jsx +33 -0
  321. package/components/forms/MultiSelect.vue +36 -0
  322. package/components/forms/NumberStepper.d.ts +13 -0
  323. package/components/forms/NumberStepper.jsx +15 -0
  324. package/components/forms/NumberStepper.vue +17 -0
  325. package/components/forms/Radio.vue +16 -0
  326. package/components/forms/RadioGroup.vue +11 -0
  327. package/components/forms/SearchInput.d.ts +12 -0
  328. package/components/forms/SearchInput.jsx +17 -0
  329. package/components/forms/SearchInput.vue +14 -0
  330. package/components/forms/Select.d.ts +14 -0
  331. package/components/forms/Select.jsx +17 -0
  332. package/components/forms/Select.vue +16 -0
  333. package/components/forms/Slider.d.ts +15 -0
  334. package/components/forms/Slider.jsx +21 -0
  335. package/components/forms/Slider.vue +23 -0
  336. package/components/forms/Switch.d.ts +31 -0
  337. package/components/forms/Switch.jsx +40 -0
  338. package/components/forms/Switch.vue +16 -0
  339. package/components/forms/_dir.card.html +13 -0
  340. package/components/forms/forms.prompt.md +23 -0
  341. package/components/icons/Icon.d.ts +19 -0
  342. package/components/icons/Icon.jsx +93 -0
  343. package/components/icons/Icon.prompt.md +8 -0
  344. package/components/icons/Icon.vue +12 -0
  345. package/components/icons/Unicon.d.ts +12 -0
  346. package/components/icons/Unicon.jsx +16 -0
  347. package/components/icons/Unicon.vue +15 -0
  348. package/components/icons/_dir.card.html +13 -0
  349. package/components/icons/feather-paths.js +2 -0
  350. package/components/icons/icon-paths.js +73 -0
  351. package/components/index.d.ts +87 -0
  352. package/components/index.js +110 -0
  353. package/components/navigation/Breadcrumb.d.ts +24 -0
  354. package/components/navigation/Breadcrumb.jsx +120 -0
  355. package/components/navigation/Breadcrumb.vue +94 -0
  356. package/components/navigation/DocTree.d.ts +18 -0
  357. package/components/navigation/DocTree.jsx +37 -0
  358. package/components/navigation/DocTree.vue +25 -0
  359. package/components/navigation/Sidebar.d.ts +29 -0
  360. package/components/navigation/Sidebar.jsx +75 -0
  361. package/components/navigation/Sidebar.vue +54 -0
  362. package/components/navigation/TopBar.d.ts +27 -0
  363. package/components/navigation/TopBar.jsx +44 -0
  364. package/components/navigation/TopBar.vue +51 -0
  365. package/components/navigation/Tree.d.ts +83 -0
  366. package/components/navigation/Tree.jsx +347 -0
  367. package/components/navigation/Tree.vue +258 -0
  368. package/components/navigation/_dir.card.html +13 -0
  369. package/components/navigation/navigation.prompt.md +22 -0
  370. package/components/pipeline/ConfigPanel.d.ts +18 -0
  371. package/components/pipeline/ConfigPanel.jsx +30 -0
  372. package/components/pipeline/ConfigPanel.vue +32 -0
  373. package/components/pipeline/DataPreview.d.ts +14 -0
  374. package/components/pipeline/DataPreview.jsx +24 -0
  375. package/components/pipeline/DataPreview.vue +21 -0
  376. package/components/pipeline/NodeCard.d.ts +47 -0
  377. package/components/pipeline/NodeCard.jsx +97 -0
  378. package/components/pipeline/NodeCard.vue +75 -0
  379. package/components/pipeline/PaletteItem.vue +15 -0
  380. package/components/pipeline/PaletteListItem.vue +15 -0
  381. package/components/pipeline/PalettePanel.d.ts +20 -0
  382. package/components/pipeline/PalettePanel.jsx +74 -0
  383. package/components/pipeline/PalettePanel.vue +45 -0
  384. package/components/pipeline/PipelineCanvas.d.ts +25 -0
  385. package/components/pipeline/PipelineCanvas.jsx +94 -0
  386. package/components/pipeline/PipelineCanvas.vue +73 -0
  387. package/components/pipeline/ZoomControls.d.ts +9 -0
  388. package/components/pipeline/ZoomControls.jsx +15 -0
  389. package/components/pipeline/ZoomControls.vue +15 -0
  390. package/components/pipeline/_dir.card.html +13 -0
  391. package/components/pipeline/pipeline.prompt.md +22 -0
  392. package/components/tabs/SegmentedControl.d.ts +11 -0
  393. package/components/tabs/SegmentedControl.jsx +21 -0
  394. package/components/tabs/SegmentedControl.vue +15 -0
  395. package/components/tabs/Tabs.d.ts +13 -0
  396. package/components/tabs/Tabs.jsx +52 -0
  397. package/components/tabs/Tabs.vue +39 -0
  398. package/components/tabs/ViewToggle.d.ts +8 -0
  399. package/components/tabs/ViewToggle.jsx +12 -0
  400. package/components/tabs/ViewToggle.vue +12 -0
  401. package/components/tabs/_dir.card.html +13 -0
  402. package/components/tabs/tabs.prompt.md +14 -0
  403. package/package.json +76 -0
  404. package/readme.md +150 -0
  405. package/styles.css +10 -0
  406. package/tokens/alpha.css +27 -0
  407. package/tokens/charts.css +55 -0
  408. package/tokens/colors-extended.css +40 -0
  409. package/tokens/colors.css +39 -0
  410. package/tokens/dark.css +96 -0
  411. package/tokens/fonts.css +4 -0
  412. package/tokens/interaction.css +48 -0
  413. package/tokens/layout.css +30 -0
  414. package/tokens/motion.css +22 -0
  415. package/tokens/tokens.d.ts +680 -0
  416. package/tokens/tokens.js +1018 -0
  417. package/tokens/tokens.json +2928 -0
  418. package/tokens/typography.css +22 -0
@@ -0,0 +1,69 @@
1
+ import React from "react";
2
+ import { fmtNum } from "./chart-geometry.js";
3
+ import { makeProj, dragRotate } from "./chart-3d.js";
4
+ import { ChartTooltip, wrapStyle } from "./ChartKit.jsx";
5
+
6
+ /**
7
+ * 3D scatter — points in x/y/z with group coloring; drag to orbit. Depth-sorted circles
8
+ * with radius attenuated by distance so nearer points read closer.
9
+ */
10
+ export function Scatter3D({ points = [], width = 420, height = 300, yaw: yaw0 = 35, pitch: pitch0 = 28, r = 4, xLabel = "x", yLabel = "y", zLabel = "z", legend = true, interactive = true, valueFormat = fmtNum, style }) {
11
+ const [rot, setRot] = React.useState({ yaw: yaw0, pitch: pitch0 });
12
+ const [hover, setHover] = React.useState(null);
13
+ const names = [...new Set(points.map((p) => p.series).filter(Boolean))];
14
+ const norm = (arr) => { const l = Math.min(...arr), h = Math.max(...arr), s = h - l || 1; return (v) => (v - l) / s - 0.5; };
15
+ const nx = norm(points.map((p) => p.x)), ny = norm(points.map((p) => p.y)), nz = norm(points.map((p) => p.z));
16
+ const scale = Math.min(width, height) * 0.44;
17
+ const P = makeProj(width / 2, height / 2, scale, rot.yaw, rot.pitch);
18
+ const dots = points.map((p, i) => ({ p, i, ...P(nx(p.x), ny(p.y), nz(p.z)) })).sort((a, b) => b.depth - a.depth);
19
+ // Cube edges for orientation
20
+ const C = [-0.5, 0.5];
21
+ const edges = [];
22
+ C.forEach((a) => C.forEach((b) => { edges.push([P(a, b, -0.5), P(a, b, 0.5)], [P(a, -0.5, b), P(a, 0.5, b)], [P(-0.5, a, b), P(0.5, a, b)]); }));
23
+ const colorOf = (p) => p.color || `var(--series-${(names.indexOf(p.series) % 10) + 1})`;
24
+ /* Three axis edges meet at visible cube corners; names sit outside the cube along the
25
+ screen-space outward direction, so they never land inside the plot at any rotation. */
26
+ const axes3 = [
27
+ { a: P(-0.5, -0.5, 0.5), b: P(0.5, -0.5, 0.5), name: xLabel },
28
+ { a: P(-0.5, -0.5, 0.5), b: P(-0.5, 0.5, 0.5), name: yLabel },
29
+ { a: P(0.5, -0.5, 0.5), b: P(0.5, -0.5, -0.5), name: zLabel },
30
+ ].map((ax) => {
31
+ const mx = (ax.a.x + ax.b.x) / 2, my = (ax.a.y + ax.b.y) / 2;
32
+ const dx = mx - width / 2, dy = my - height / 2;
33
+ const len = Math.hypot(dx, dy) || 1;
34
+ return { ...ax, nx: mx + (dx / len) * 18, ny: my + (dy / len) * 18 };
35
+ });
36
+ const handlers = interactive ? dragRotate(() => rot, setRot) : {};
37
+ return (
38
+ <div style={{ ...wrapStyle, ...style }}>
39
+ <svg width={width} height={height} {...handlers} role="img" style={{ cursor: interactive ? "grab" : "default", touchAction: "none" }}>
40
+ {edges.map(([a, b], i) => <line key={i} x1={a.x} y1={a.y} x2={b.x} y2={b.y} stroke="var(--chart-axis)" strokeWidth="1" />)}
41
+ {axes3.map((ax, i) => <line key={"ae" + i} x1={ax.a.x} y1={ax.a.y} x2={ax.b.x} y2={ax.b.y} stroke="var(--chart-guide)" strokeWidth="1" />)}
42
+ {axes3.map((ax, i) => (
43
+ <text key={"at" + i} x={ax.nx} y={ax.ny + 3} textAnchor="middle" style={{ fontFamily: "var(--font-sans)", fontSize: 10, fill: "var(--text-secondary)" }}>{ax.name}</text>
44
+ ))}
45
+ {dots.map((d) => (
46
+ <circle key={d.i} cx={d.x} cy={d.y} r={r * (1.18 - (d.depth + 0.75) * 0.35) * (hover === d.i ? 1.5 : 1)}
47
+ fill={colorOf(d.p)} opacity={hover === d.i ? 1 : (0.85 - (d.depth + 0.75) * 0.28) * (hover == null ? 1 : 0.45)}
48
+ stroke={hover === d.i ? "var(--bg-surface)" : "none"} strokeWidth="1.5"
49
+ onMouseEnter={interactive ? () => setHover(d.i) : undefined} onMouseLeave={interactive ? () => setHover(null) : undefined} />
50
+ ))}
51
+ </svg>
52
+ {hover != null && interactive ? (() => {
53
+ const d = dots.find((x) => x.i === hover), p = d.p;
54
+ return <ChartTooltip x={d.x} y={d.y} flip={d.x > width * 0.6} title={p.label || p.series}
55
+ rows={[{ label: "x", value: valueFormat(p.x) }, { label: "y", value: valueFormat(p.y) }, { label: "z", value: valueFormat(p.z) }]} />;
56
+ })() : null}
57
+ {legend && names.length > 1 ? (
58
+ <div style={{ display: "flex", gap: 14, paddingTop: 6, lineHeight: "normal" }}>
59
+ {names.map((nm, i) => (
60
+ <span key={nm} style={{ display: "inline-flex", alignItems: "center", gap: 6 }}>
61
+ <span style={{ width: 8, height: 8, borderRadius: 4, background: `var(--series-${(i % 10) + 1})` }} />
62
+ <span style={{ fontFamily: "var(--font-sans)", fontSize: "var(--text-xs)", color: "var(--text-secondary)" }}>{nm}</span>
63
+ </span>
64
+ ))}
65
+ </div>
66
+ ) : null}
67
+ </div>
68
+ );
69
+ }
@@ -0,0 +1,59 @@
1
+ <script setup>
2
+ import { ref, computed } from 'vue'
3
+ import { fmtNum, SERIES } from './chart-geometry.js'
4
+ import { makeProj } from './chart-3d.js'
5
+ // 3D scatter in an orientation cube; drag to orbit. Depth attenuates radius and opacity.
6
+ const props = defineProps({ points:{type:Array,default:()=>[]}, width:{type:Number,default:420}, height:{type:Number,default:300}, yaw:{type:Number,default:35}, pitch:{type:Number,default:28}, r:{type:Number,default:4}, xLabel:{type:String,default:'x'}, yLabel:{type:String,default:'y'}, zLabel:{type:String,default:'z'}, legend:{type:Boolean,default:true}, interactive:{type:Boolean,default:true}, valueFormat:{type:Function,default:fmtNum}, style:Object })
7
+ const rot = ref({ yaw: props.yaw, pitch: props.pitch })
8
+ const hover = ref(null)
9
+ function down(e){ e.currentTarget.__kbDrag = { x:e.clientX, y:e.clientY, ...rot.value }; e.currentTarget.setPointerCapture && e.currentTarget.setPointerCapture(e.pointerId) }
10
+ function move(e){ const s = e.currentTarget.__kbDrag; if(!s) return; rot.value = { yaw: s.yaw+(e.clientX-s.x)*0.45, pitch: Math.max(8, Math.min(80, s.pitch+(e.clientY-s.y)*0.35)) } }
11
+ function up(e){ e.currentTarget.__kbDrag = null }
12
+ const g = computed(() => {
13
+ const names = [...new Set(props.points.map(p=>p.series).filter(Boolean))]
14
+ const norm = arr => { const l=Math.min(...arr), h=Math.max(...arr), s=h-l||1; return v=>(v-l)/s-0.5 }
15
+ const nx = norm(props.points.map(p=>p.x)), ny = norm(props.points.map(p=>p.y)), nz = norm(props.points.map(p=>p.z))
16
+ const scale = Math.min(props.width, props.height)*0.44
17
+ const P = makeProj(props.width/2, props.height/2, scale, rot.value.yaw, rot.value.pitch)
18
+ const dots = props.points.map((p,i)=>({ p, i, ...P(nx(p.x), ny(p.y), nz(p.z)),
19
+ color: p.color || SERIES[(p.series?names.indexOf(p.series):0)%SERIES.length] })).sort((a,b)=>b.depth-a.depth)
20
+ const C = [-0.5,0.5]; const edges = []
21
+ C.forEach(a=>C.forEach(b=>{ edges.push([P(a,b,-0.5),P(a,b,0.5)],[P(a,-0.5,b),P(a,0.5,b)],[P(-0.5,a,b),P(0.5,a,b)]) }))
22
+ // Axis names sit outside the cube along the screen-space outward direction.
23
+ const axes3 = [
24
+ { a:P(-0.5,-0.5,0.5), b:P(0.5,-0.5,0.5), name:props.xLabel },
25
+ { a:P(-0.5,-0.5,0.5), b:P(-0.5,0.5,0.5), name:props.yLabel },
26
+ { a:P(0.5,-0.5,0.5), b:P(0.5,-0.5,-0.5), name:props.zLabel },
27
+ ].map(ax=>{
28
+ const mx=(ax.a.x+ax.b.x)/2, my=(ax.a.y+ax.b.y)/2
29
+ const dx=mx-props.width/2, dy=my-props.height/2, len=Math.hypot(dx,dy)||1
30
+ return { ...ax, nx: mx+(dx/len)*18, ny: my+(dy/len)*18 }
31
+ })
32
+ return { names, dots, edges, axes3 }
33
+ })
34
+ </script>
35
+ <template>
36
+ <div :style="{position:'relative',display:'inline-block',lineHeight:0,...style}">
37
+ <svg :width="width" :height="height" @pointerdown="down" @pointermove="move" @pointerup="up" @pointerleave="up"
38
+ role="img" :style="{cursor:interactive?'grab':'default',touchAction:'none'}">
39
+ <line v-for="(e,i) in g.edges" :key="'e'+i" :x1="e[0].x" :y1="e[0].y" :x2="e[1].x" :y2="e[1].y" stroke="var(--chart-axis)" stroke-width="1"/>
40
+ <line v-for="(ax,i) in g.axes3" :key="'ae'+i" :x1="ax.a.x" :y1="ax.a.y" :x2="ax.b.x" :y2="ax.b.y" stroke="var(--chart-guide)" stroke-width="1"/>
41
+ <text v-for="(ax,i) in g.axes3" :key="'at'+i" :x="ax.nx" :y="ax.ny+3" text-anchor="middle" style="font-family:var(--font-sans);font-size:10px;fill:var(--text-secondary)">{{ ax.name }}</text>
42
+ <circle v-for="d in g.dots" :key="d.i" :cx="d.x" :cy="d.y"
43
+ :r="r*(1.18-(d.depth+0.75)*0.35)*(hover===d.i?1.5:1)" :fill="d.color"
44
+ :opacity="hover===d.i ? 1 : (0.85-(d.depth+0.75)*0.28)*(hover==null?1:0.45)"
45
+ :stroke="hover===d.i?'var(--bg-surface)':'none'" stroke-width="1.5"
46
+ @mouseenter="interactive && (hover=d.i)" @mouseleave="interactive && (hover=null)"/>
47
+ </svg>
48
+ <div v-if="hover!=null && interactive" :style="{position:'absolute',left:g.dots.find(d=>d.i===hover).x+'px',top:g.dots.find(d=>d.i===hover).y+'px',transform:g.dots.find(d=>d.i===hover).x>width*0.6?'translate(calc(-100% - 10px),-50%)':'translate(10px,-50%)',pointerEvents:'none',zIndex:5,background:'var(--bg-surface)',border:'1px solid var(--border-card)',borderRadius:'var(--radius-md)',boxShadow:'var(--shadow-menu)',padding:'7px 10px',lineHeight:'normal',whiteSpace:'nowrap'}">
49
+ <div style="font-family:var(--font-sans);font-size:var(--text-2xs);color:var(--text-muted);padding-bottom:3px">{{ points[hover].label || points[hover].series }}</div>
50
+ <div style="font-family:var(--font-mono);font-size:var(--text-xs);color:var(--text-heading)">{{ xLabel }} {{ valueFormat(points[hover].x) }} · {{ yLabel }} {{ valueFormat(points[hover].y) }} · {{ zLabel }} {{ valueFormat(points[hover].z) }}</div>
51
+ </div>
52
+ <div v-if="legend && g.names.length>1" style="display:flex;gap:14px;padding-top:6px;line-height:normal">
53
+ <span v-for="(nm,i) in g.names" :key="nm" style="display:inline-flex;align-items:center;gap:6px">
54
+ <span :style="{width:'8px',height:'8px',borderRadius:'4px',background:SERIES[i%SERIES.length]}"/>
55
+ <span style="font-family:var(--font-sans);font-size:var(--text-xs);color:var(--text-secondary)">{{ nm }}</span>
56
+ </span>
57
+ </div>
58
+ </div>
59
+ </template>
@@ -0,0 +1,12 @@
1
+ import * as React from "react";
2
+ export interface ScatterPoint { x: number; y: number; /** Bubble size in data units */ r?: number; label?: string; series?: string; color?: string; }
3
+ /** Scatter / bubble with nearest-point tooltip. */
4
+ export interface ScatterChartProps {
5
+ points?: ScatterPoint[];
6
+ width?: number; height?: number;
7
+ xFormat?: (v: number) => string; yFormat?: (v: number) => string;
8
+ xLabel?: string; yLabel?: string;
9
+ axes?: boolean; legend?: boolean; interactive?: boolean;
10
+ style?: React.CSSProperties;
11
+ }
12
+ export declare function ScatterChart(props: ScatterChartProps): JSX.Element;
@@ -0,0 +1,53 @@
1
+ import React from "react";
2
+ import { fmtNum, extent, scaleLin, niceTicks, seriesColor } from "./chart-geometry.js";
3
+ import { ChartLegend, ChartTooltip, PlotGrid, wrapStyle } from "./ChartKit.jsx";
4
+
5
+ /**
6
+ * Scatter / bubble. points: [{x, y, r?, label?, series?}] — r in data units scaled to
7
+ * 3–14px bubbles. Hover pins a tooltip to the nearest point.
8
+ */
9
+ export function ScatterChart({ points = [], width = 560, height = 260, xFormat = fmtNum, yFormat = fmtNum, xLabel, yLabel, axes = true, legend = true, interactive = true, style }) {
10
+ const [hover, setHover] = React.useState(null);
11
+ const names = [...new Set(points.map((p) => p.series).filter(Boolean))];
12
+ const pad = { l: axes ? 44 : 4, r: 10, t: 10, b: axes ? 26 : 4 };
13
+ const [xl, xh] = extent([points.map((p) => p.x)], false);
14
+ const [yl, yh] = extent([points.map((p) => p.y)], false);
15
+ const tx = niceTicks(xl, xh, 5), ty = niceTicks(yl, yh, 4);
16
+ const sx = scaleLin(tx.lo, tx.hi, pad.l + 6, width - pad.r - 6);
17
+ const sy = scaleLin(ty.lo, ty.hi, height - pad.b - 6, pad.t + 6);
18
+ const rs = points.map((p) => p.r != null ? p.r : 1);
19
+ const [rl, rh] = extent([rs], false);
20
+ const sr = (r) => 3 + ((r - rl) / (rh - rl || 1)) * 11;
21
+ const colorOf = (p) => p.color || seriesColor(p.series ? names.indexOf(p.series) : 0);
22
+ const move = (e) => {
23
+ if (!interactive) return;
24
+ const rc = e.currentTarget.getBoundingClientRect();
25
+ const mx = e.clientX - rc.left, my = e.clientY - rc.top;
26
+ let best = null, bd = 24 * 24;
27
+ points.forEach((p, i) => { const d = (sx(p.x) - mx) ** 2 + (sy(p.y) - my) ** 2; if (d < bd) { bd = d; best = i; } });
28
+ setHover(best);
29
+ };
30
+ const h = hover != null ? points[hover] : null;
31
+ return (
32
+ <div style={{ ...wrapStyle, ...style }}>
33
+ <svg width={width} height={height} onMouseMove={move} onMouseLeave={() => setHover(null)} role="img">
34
+ {axes ? <g>
35
+ <PlotGrid w={width} h={height} pad={pad} ticks={ty.ticks} sy={sy} xLabels={[]} sxLabel={() => 0} yFormat={yFormat} />
36
+ {tx.ticks.map((t) => <text key={t} x={sx(t)} y={height - pad.b + 16} textAnchor="middle" style={{ fontFamily: "var(--font-mono)", fontSize: 10, fill: "var(--chart-label)" }}>{xFormat(t)}</text>)}
37
+ {xLabel ? <text x={width - pad.r} y={height - 2} textAnchor="end" style={{ fontFamily: "var(--font-sans)", fontSize: 10, fill: "var(--chart-label)" }}>{xLabel}</text> : null}
38
+ {yLabel ? <text x={pad.l} y={pad.t - 1} style={{ fontFamily: "var(--font-sans)", fontSize: 10, fill: "var(--chart-label)" }}>{yLabel}</text> : null}
39
+ </g> : null}
40
+ {points.map((p, i) => (
41
+ <circle key={i} cx={sx(p.x)} cy={sy(p.y)} r={sr(p.r != null ? p.r : 1)} fill={colorOf(p)}
42
+ opacity={hover == null ? 0.55 : hover === i ? 0.9 : 0.2} stroke={hover === i ? "var(--bg-surface)" : "none"} strokeWidth="1.5"
43
+ style={{ transition: "opacity var(--dur-fast) var(--ease-out)" }} />
44
+ ))}
45
+ </svg>
46
+ {h && interactive ? (
47
+ <ChartTooltip x={sx(h.x)} y={sy(h.y)} flip={sx(h.x) > width * 0.62} title={h.label || h.series}
48
+ rows={[{ label: xLabel || "x", value: xFormat(h.x) }, { label: yLabel || "y", value: yFormat(h.y) }].concat(h.r != null ? [{ label: "size", value: fmtNum(h.r) }] : [])} />
49
+ ) : null}
50
+ {legend && names.length > 1 ? <ChartLegend style={{ paddingTop: 8 }} items={names.map((nm, i) => ({ label: nm, color: seriesColor(i) }))} /> : null}
51
+ </div>
52
+ );
53
+ }
@@ -0,0 +1,57 @@
1
+ <script setup>
2
+ import { ref, computed } from 'vue'
3
+ import { fmtNum, extent, scaleLin, niceTicks, SERIES } from './chart-geometry.js'
4
+ // Scatter / bubble with nearest-point tooltip.
5
+ const props = defineProps({ points:{type:Array,default:()=>[]}, width:{type:Number,default:560}, height:{type:Number,default:260}, xFormat:{type:Function,default:fmtNum}, yFormat:{type:Function,default:fmtNum}, xLabel:String, yLabel:String, axes:{type:Boolean,default:true}, legend:{type:Boolean,default:true}, interactive:{type:Boolean,default:true}, style:Object })
6
+ const hover = ref(null)
7
+ const g = computed(() => {
8
+ const names = [...new Set(props.points.map(p=>p.series).filter(Boolean))]
9
+ const pad = { l: props.axes?44:4, r:10, t:10, b: props.axes?26:4 }
10
+ const [xl,xh] = extent([props.points.map(p=>p.x)], false)
11
+ const [yl,yh] = extent([props.points.map(p=>p.y)], false)
12
+ const tx = niceTicks(xl,xh,5), ty = niceTicks(yl,yh,4)
13
+ const sx = scaleLin(tx.lo,tx.hi,pad.l+6,props.width-pad.r-6)
14
+ const sy = scaleLin(ty.lo,ty.hi,props.height-pad.b-6,pad.t+6)
15
+ const rs = props.points.map(p=>p.r!=null?p.r:1)
16
+ const [rl,rh] = extent([rs], false)
17
+ return { names, pad, tx, ty, sx, sy,
18
+ dots: props.points.map((p,i)=>({ p, i, cx:sx(p.x), cy:sy(p.y), r: 3+(((p.r!=null?p.r:1)-rl)/(rh-rl||1))*11,
19
+ color: p.color || SERIES[(p.series?names.indexOf(p.series):0)%SERIES.length] })) }
20
+ })
21
+ function move(e){
22
+ if (!props.interactive) return
23
+ const rc = e.currentTarget.getBoundingClientRect()
24
+ const mx = e.clientX-rc.left, my = e.clientY-rc.top
25
+ let best=null, bd=576
26
+ g.value.dots.forEach(d=>{ const dd=(d.cx-mx)**2+(d.cy-my)**2; if(dd<bd){bd=dd;best=d.i} })
27
+ hover.value = best
28
+ }
29
+ </script>
30
+ <template>
31
+ <div :style="{position:'relative',display:'inline-block',lineHeight:0,...style}">
32
+ <svg :width="width" :height="height" @mousemove="move" @mouseleave="hover=null" role="img">
33
+ <template v-if="axes">
34
+ <g v-for="t in g.ty.ticks" :key="t">
35
+ <line :x1="g.pad.l" :x2="width-g.pad.r" :y1="g.sy(t)" :y2="g.sy(t)" :stroke="t===0?'var(--chart-axis)':'var(--chart-grid)'" stroke-width="1"/>
36
+ <text :x="g.pad.l-8" :y="g.sy(t)+3" text-anchor="end" style="font-family:var(--font-mono);font-size:10px;fill:var(--chart-label)">{{ yFormat(t) }}</text>
37
+ </g>
38
+ <text v-for="t in g.tx.ticks" :key="'x'+t" :x="g.sx(t)" :y="height-g.pad.b+16" text-anchor="middle" style="font-family:var(--font-mono);font-size:10px;fill:var(--chart-label)">{{ xFormat(t) }}</text>
39
+ <text v-if="xLabel" :x="width-g.pad.r" :y="height-2" text-anchor="end" style="font-family:var(--font-sans);font-size:10px;fill:var(--chart-label)">{{ xLabel }}</text>
40
+ <text v-if="yLabel" :x="g.pad.l" :y="g.pad.t-1" style="font-family:var(--font-sans);font-size:10px;fill:var(--chart-label)">{{ yLabel }}</text>
41
+ </template>
42
+ <circle v-for="d in g.dots" :key="d.i" :cx="d.cx" :cy="d.cy" :r="d.r" :fill="d.color"
43
+ :opacity="hover==null?.55:hover===d.i?.9:.2" :stroke="hover===d.i?'var(--bg-surface)':'none'" stroke-width="1.5"
44
+ style="transition:opacity var(--dur-fast) var(--ease-out)"/>
45
+ </svg>
46
+ <div v-if="hover!=null && interactive" :style="{position:'absolute',left:g.dots[hover].cx+'px',top:g.dots[hover].cy+'px',transform:g.dots[hover].cx>width*0.62?'translate(calc(-100% - 10px),-50%)':'translate(10px,-50%)',pointerEvents:'none',zIndex:5,background:'var(--bg-surface)',border:'1px solid var(--border-card)',borderRadius:'var(--radius-md)',boxShadow:'var(--shadow-menu)',padding:'7px 10px',lineHeight:'normal'}">
47
+ <div style="font-family:var(--font-sans);font-size:var(--text-2xs);color:var(--text-muted);padding-bottom:4px;white-space:nowrap">{{ points[hover].label || points[hover].series }}</div>
48
+ <div style="font-family:var(--font-mono);font-size:var(--text-xs);color:var(--text-heading);white-space:nowrap">{{ (xLabel||'x')+' '+xFormat(points[hover].x) }} · {{ (yLabel||'y')+' '+yFormat(points[hover].y) }}</div>
49
+ </div>
50
+ <div v-if="legend && g.names.length>1" style="display:flex;gap:14px;padding-top:8px;line-height:normal">
51
+ <span v-for="(nm,i) in g.names" :key="nm" style="display:inline-flex;align-items:center;gap:6px">
52
+ <span :style="{width:'8px',height:'8px',borderRadius:'2px',background:SERIES[i%SERIES.length]}"/>
53
+ <span style="font-family:var(--font-sans);font-size:var(--text-xs);color:var(--text-secondary)">{{ nm }}</span>
54
+ </span>
55
+ </div>
56
+ </div>
57
+ </template>
@@ -0,0 +1,19 @@
1
+ import * as React from "react";
2
+ /** Micro trend for stat tiles, table cells, list rows — no axes, no labels. */
3
+ export interface SparklineProps {
4
+ values?: number[];
5
+ mode?: "line" | "area" | "bars";
6
+ curve?: "linear" | "smooth" | "step";
7
+ /** "auto" colors by direction (green up / red down) */
8
+ tone?: "default" | "auto" | "pos" | "neg" | "muted";
9
+ width?: number; height?: number; strokeWidth?: number;
10
+ /** End-point dot (line/area) */
11
+ dot?: boolean;
12
+ /** Dashed reference value */
13
+ baseline?: number;
14
+ /** Hover readout (on by default even at tile size) */ interactive?: boolean;
15
+ /** Point labels shown in the hover chip */ labels?: string[];
16
+ valueFormat?: (v: number) => string;
17
+ style?: React.CSSProperties;
18
+ }
19
+ export declare function Sparkline(props: SparklineProps): JSX.Element;
@@ -0,0 +1,63 @@
1
+ import React from "react";
2
+ import { extent, scaleLin, shapePath, closeToBaseline, fmtNum } from "./chart-geometry.js";
3
+
4
+ /**
5
+ * Micro line/area/bar chart for stat tiles, table cells and list rows. No axes, no labels.
6
+ * mode: "line" | "area" | "bars". curve: "linear" | "smooth" | "step".
7
+ * tone: "default" (series-1) | "auto" (green up / red down) | "pos" | "neg" | "muted".
8
+ * Interactive by default: hovering snaps to the nearest point and shows a compact value chip.
9
+ */
10
+ export function Sparkline({ values = [], mode = "line", curve = "linear", tone = "default", width = 96, height = 28, strokeWidth = 1.5, dot = true, baseline, interactive = true, labels, valueFormat = fmtNum, style }) {
11
+ const [hover, setHover] = React.useState(null);
12
+ const uid = React.useId ? React.useId().replace(/:/g, "") : "kbspark";
13
+ const [lo, hi] = extent([values], mode === "bars");
14
+ const pad = strokeWidth + 1.5;
15
+ const sx = scaleLin(0, values.length - 1 || 1, pad, width - pad);
16
+ const sy = scaleLin(lo, hi, height - pad, pad);
17
+ const up = values.length > 1 && values[values.length - 1] >= values[0];
18
+ const color = tone === "auto" ? (up ? "var(--chart-pos)" : "var(--chart-neg)")
19
+ : tone === "pos" ? "var(--chart-pos)" : tone === "neg" ? "var(--chart-neg)"
20
+ : tone === "muted" ? "var(--chart-guide)" : "var(--series-1)";
21
+ const pts = values.map((v, i) => [sx(i), sy(v)]);
22
+ const d = shapePath(pts, curve);
23
+ const bw = Math.max(2, (width - pad * 2) / (values.length || 1) - 2);
24
+ const move = (e) => {
25
+ if (!interactive) return;
26
+ const r = e.currentTarget.getBoundingClientRect();
27
+ const i = Math.round(((e.clientX - r.left - pad) / (width - pad * 2)) * (values.length - 1 || 1));
28
+ setHover(Math.max(0, Math.min(values.length - 1, i)));
29
+ };
30
+ const chip = hover != null && interactive ? (
31
+ <span style={{ position: "absolute", left: Math.min(Math.max(sx(hover), 18), width - 18), top: -4, transform: "translate(-50%,-100%)", pointerEvents: "none", whiteSpace: "nowrap", background: "var(--bg-surface)", border: "1px solid var(--border-card)", borderRadius: "var(--radius-sm)", boxShadow: "var(--shadow-menu)", padding: "2px 6px", fontFamily: "var(--font-mono)", fontSize: 10, lineHeight: "14px", color: "var(--text-heading)", zIndex: 5 }}>
32
+ {labels && labels[hover] ? <span style={{ color: "var(--text-muted)", paddingRight: 5 }}>{labels[hover]}</span> : null}
33
+ {valueFormat(values[hover])}
34
+ </span>
35
+ ) : null;
36
+ return (
37
+ <span style={{ position: "relative", display: "inline-block", verticalAlign: "middle", lineHeight: 0, ...style }}>
38
+ <svg width={width} height={height} onMouseMove={move} onMouseLeave={() => setHover(null)} role="img" style={{ display: "block", overflow: "visible" }}>
39
+ {mode === "area" ? (
40
+ <defs>
41
+ <linearGradient id={uid} x1="0" y1="0" x2="0" y2="1">
42
+ <stop offset="0%" stopColor={color} stopOpacity="0.38" />
43
+ <stop offset="100%" stopColor={color} stopOpacity="0.02" />
44
+ </linearGradient>
45
+ </defs>
46
+ ) : null}
47
+ {mode === "bars" ? values.map((v, i) => {
48
+ const y = sy(Math.max(0, v)), y0 = sy(Math.min(0, v));
49
+ return <rect key={i} x={sx(i) - bw / 2} y={y} width={bw} height={Math.max(1.5, y0 - y)} rx="1" fill={v < 0 ? "var(--chart-neg)" : color} opacity={hover === i ? 1 : hover != null ? 0.4 : i === values.length - 1 ? 1 : 0.55} />;
50
+ }) : (
51
+ <>
52
+ {baseline != null ? <line x1={pad} x2={width - pad} y1={sy(baseline)} y2={sy(baseline)} stroke="var(--chart-guide)" strokeWidth="1" strokeDasharray="2 3" /> : null}
53
+ {mode === "area" ? <path d={closeToBaseline(d, pts, height - pad)} fill={"url(#" + uid + ")"} /> : null}
54
+ <path d={d} fill="none" stroke={color} strokeWidth={strokeWidth} strokeLinejoin="round" strokeLinecap="round" />
55
+ {dot && pts.length ? <circle cx={pts[pts.length - 1][0]} cy={pts[pts.length - 1][1]} r={strokeWidth + 0.8} fill={color} /> : null}
56
+ {hover != null && interactive ? <circle cx={sx(hover)} cy={sy(values[hover])} r={strokeWidth + 1.4} fill={color} stroke="var(--bg-surface)" strokeWidth="1.5" /> : null}
57
+ </>
58
+ )}
59
+ </svg>
60
+ {chip}
61
+ </span>
62
+ );
63
+ }
@@ -0,0 +1,54 @@
1
+ <script setup>
2
+ import { computed, ref } from 'vue'
3
+ import { extent, scaleLin, shapePath, closeToBaseline, fmtNum } from './chart-geometry.js'
4
+ // Micro line/area/bar trend — no axes, no labels. tone "auto" = green up / red down.
5
+ const props = defineProps({ values:{type:Array,default:()=>[]}, mode:{type:String,default:'line'}, curve:{type:String,default:'linear'}, tone:{type:String,default:'default'}, width:{type:Number,default:96}, height:{type:Number,default:28}, strokeWidth:{type:Number,default:1.5}, dot:{type:Boolean,default:true}, baseline:Number, interactive:{type:Boolean,default:true}, labels:Array, valueFormat:{type:Function,default:fmtNum}, style:Object })
6
+ const hover = ref(null)
7
+ const uid = 'kbspark-' + Math.random().toString(36).slice(2, 8)
8
+ function move(e){
9
+ if (!props.interactive) return
10
+ const r = e.currentTarget.getBoundingClientRect()
11
+ const pad = props.strokeWidth + 1.5
12
+ const i = Math.round(((e.clientX-r.left-pad)/(props.width-pad*2))*(props.values.length-1||1))
13
+ hover.value = Math.max(0, Math.min(props.values.length-1, i))
14
+ }
15
+ const g = computed(() => {
16
+ const [lo,hi] = extent([props.values], props.mode==='bars')
17
+ const pad = props.strokeWidth + 1.5
18
+ const sx = scaleLin(0, props.values.length-1||1, pad, props.width-pad)
19
+ const sy = scaleLin(lo, hi, props.height-pad, pad)
20
+ const up = props.values.length>1 && props.values[props.values.length-1] >= props.values[0]
21
+ const color = props.tone==='auto' ? (up?'var(--chart-pos)':'var(--chart-neg)') : props.tone==='pos'?'var(--chart-pos)' : props.tone==='neg'?'var(--chart-neg)' : props.tone==='muted'?'var(--chart-guide)':'var(--series-1)'
22
+ const pts = props.values.map((v,i)=>[sx(i),sy(v)])
23
+ const bw = Math.max(2,(props.width-pad*2)/props.values.length-2)
24
+ return { pad, sx, sy, color, pts, bw,
25
+ line: shapePath(pts, props.curve), area: closeToBaseline(shapePath(pts, props.curve), pts, props.height-pad),
26
+ baseY: props.baseline!=null ? sy(props.baseline) : null,
27
+ bars: props.values.map((v,i)=>({x:sx(i)-bw/2,y:sy(Math.max(0,v)),h:Math.max(1.5,sy(Math.min(0,v))-sy(Math.max(0,v))),neg:v<0,last:i===props.values.length-1})) }
28
+ })
29
+ </script>
30
+ <template>
31
+ <span :style="{position:'relative',display:'inline-block',verticalAlign:'middle',lineHeight:0,...style}">
32
+ <svg :width="width" :height="height" @mousemove="move" @mouseleave="hover=null" role="img" style="display:block;overflow:visible">
33
+ <defs v-if="mode==='area'">
34
+ <linearGradient :id="uid" x1="0" y1="0" x2="0" y2="1">
35
+ <stop offset="0%" :stop-color="g.color" stop-opacity="0.38"/>
36
+ <stop offset="100%" :stop-color="g.color" stop-opacity="0.02"/>
37
+ </linearGradient>
38
+ </defs>
39
+ <template v-if="mode==='bars'">
40
+ <rect v-for="(b,i) in g.bars" :key="i" :x="b.x" :y="b.y" :width="g.bw" :height="b.h" rx="1" :fill="b.neg?'var(--chart-neg)':g.color" :opacity="hover===i?1:(hover!=null?.4:(b.last?1:.55))"/>
41
+ </template>
42
+ <template v-else>
43
+ <line v-if="g.baseY!=null" :x1="g.pad" :x2="width-g.pad" :y1="g.baseY" :y2="g.baseY" stroke="var(--chart-guide)" stroke-width="1" stroke-dasharray="2 3"/>
44
+ <path v-if="mode==='area'" :d="g.area" :fill="'url(#'+uid+')'"/>
45
+ <path :d="g.line" fill="none" :stroke="g.color" :stroke-width="strokeWidth" stroke-linejoin="round" stroke-linecap="round"/>
46
+ <circle v-if="dot && g.pts.length" :cx="g.pts[g.pts.length-1][0]" :cy="g.pts[g.pts.length-1][1]" :r="strokeWidth+0.8" :fill="g.color"/>
47
+ <circle v-if="hover!=null && interactive" :cx="g.sx(hover)" :cy="g.sy(values[hover])" :r="strokeWidth+1.4" :fill="g.color" stroke="var(--bg-surface)" stroke-width="1.5"/>
48
+ </template>
49
+ </svg>
50
+ <span v-if="hover!=null && interactive" :style="{position:'absolute',left:Math.min(Math.max(g.sx(hover),18),width-18)+'px',top:'-4px',transform:'translate(-50%,-100%)',pointerEvents:'none',whiteSpace:'nowrap',background:'var(--bg-surface)',border:'1px solid var(--border-card)',borderRadius:'var(--radius-sm)',boxShadow:'var(--shadow-menu)',padding:'2px 6px',fontFamily:'var(--font-mono)',fontSize:'10px',lineHeight:'14px',color:'var(--text-heading)',zIndex:5}">
51
+ <span v-if="labels && labels[hover]" style="color:var(--text-muted);padding-right:5px">{{ labels[hover] }}</span>{{ valueFormat(values[hover]) }}
52
+ </span>
53
+ </span>
54
+ </template>
@@ -0,0 +1,12 @@
1
+ import * as React from "react";
2
+ export interface SunburstNode { label: string; value?: number; children?: SunburstNode[]; }
3
+ /** Hierarchy as concentric rings; click to zoom a branch, click the center to go back. */
4
+ export interface SunburstChartProps {
5
+ data?: SunburstNode;
6
+ size?: number;
7
+ /** Ring thickness; derived from depth when omitted */ ringWidth?: number;
8
+ valueFormat?: (v: number) => string;
9
+ interactive?: boolean;
10
+ style?: React.CSSProperties;
11
+ }
12
+ export declare function SunburstChart(props: SunburstChartProps): JSX.Element;
@@ -0,0 +1,61 @@
1
+ import React from "react";
2
+ import { fmtNum, arcPath, polar, seriesColor } from "./chart-geometry.js";
3
+ import { ChartTooltip, wrapStyle } from "./ChartKit.jsx";
4
+
5
+ /**
6
+ * Sunburst — a hierarchy as concentric rings (the radial treemap). data: a root
7
+ * {label, children:[{label, value, children?}]} tree; leaf values roll up. Clicking a
8
+ * ring zooms into that branch, and the center returns to the parent.
9
+ */
10
+ export function SunburstChart({ data, size = 320, ringWidth, valueFormat = fmtNum, interactive = true, style }) {
11
+ const [zoom, setZoom] = React.useState([]);
12
+ const [hover, setHover] = React.useState(null);
13
+ const roll = (n) => (n.children && n.children.length ? { ...n, value: n.children.reduce((s, c) => s + roll(c).value, 0), children: n.children.map(roll) } : { ...n, value: n.value || 0 });
14
+ const rooted = data ? roll(data) : { label: "", value: 0, children: [] };
15
+ const focus = zoom.reduce((n, i) => (n.children ? n.children[i] : n), rooted);
16
+ const depthOf = (n) => (n.children && n.children.length ? 1 + Math.max(...n.children.map(depthOf)) : 0);
17
+ const rings = Math.max(1, depthOf(focus));
18
+ const rInner = size * 0.17;
19
+ const rw = ringWidth || (size / 2 - 6 - rInner) / rings;
20
+ const segs = [];
21
+ const walk = (node, depth, a0, a1, path, colorIdx) => {
22
+ (node.children || []).forEach((c, ci) => {
23
+ const span = (c.value / (node.value || 1)) * (a1 - a0);
24
+ const idx = depth === 0 ? ci : colorIdx;
25
+ segs.push({ node: c, depth, a0, a1: a0 + span, path: [...path, ci], colorIdx: idx });
26
+ walk(c, depth + 1, a0, a0 + span, [...path, ci], idx);
27
+ a0 += span;
28
+ });
29
+ };
30
+ walk(focus, 0, 0, 360, [], 0);
31
+ const h = hover != null ? segs[hover] : null;
32
+ const tipAt = h ? polar(size / 2, size / 2, rInner + rw * (h.depth + 0.5), (h.a0 + h.a1) / 2) : null;
33
+ return (
34
+ <div style={{ ...wrapStyle, ...style }}>
35
+ <div style={{ position: "relative", width: size, height: size }}>
36
+ <svg width={size} height={size} role="img">
37
+ {segs.map((s, i) => (
38
+ <path key={i} d={arcPath(size / 2, size / 2, rInner + rw * (s.depth + 1) - 1.5, rInner + rw * s.depth, s.a0 + 0.35, Math.max(s.a0 + 0.5, s.a1 - 0.35))}
39
+ fill={seriesColor(s.colorIdx)} opacity={hover == null ? 0.92 - s.depth * 0.16 : hover === i ? 1 : 0.28}
40
+ onMouseEnter={interactive ? () => setHover(i) : undefined} onMouseLeave={interactive ? () => setHover(null) : undefined}
41
+ onClick={interactive && s.node.children ? () => { setZoom([...zoom, ...s.path]); setHover(null); } : undefined}
42
+ style={{ transition: "opacity var(--dur-fast) var(--ease-out)", cursor: interactive && s.node.children ? "pointer" : "default" }} />
43
+ ))}
44
+ <circle cx={size / 2} cy={size / 2} r={rInner - 2} fill="var(--bg-surface)" stroke="var(--border-subtle)" strokeWidth="1"
45
+ onClick={interactive && zoom.length ? () => setZoom(zoom.slice(0, -1)) : undefined} style={{ cursor: interactive && zoom.length ? "pointer" : "default" }} />
46
+ </svg>
47
+ <div style={{ position: "absolute", inset: 0, display: "flex", flexDirection: "column", alignItems: "center", justifyContent: "center", pointerEvents: "none", lineHeight: "normal", gap: 2 }}>
48
+ <span style={{ fontFamily: "var(--font-sans)", fontWeight: "var(--weight-light)", fontSize: Math.round(size / 13), color: "var(--text-heading)" }}>{valueFormat(focus.value)}</span>
49
+ <span style={{ fontFamily: "var(--font-sans)", fontSize: "var(--text-2xs)", color: "var(--text-muted)", maxWidth: rInner * 1.7, textAlign: "center", whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" }}>
50
+ {zoom.length ? focus.label : focus.label || "Total"}
51
+ </span>
52
+ {zoom.length ? <span style={{ fontFamily: "var(--font-sans)", fontSize: 9, color: "var(--action-primary)" }}>↑ back</span> : null}
53
+ </div>
54
+ {h && interactive ? (
55
+ <ChartTooltip x={tipAt[0]} y={tipAt[1]} flip={tipAt[0] > size * 0.55} title={h.node.label}
56
+ rows={[{ label: "value", value: valueFormat(h.node.value), color: seriesColor(h.colorIdx) }, { label: "of parent", value: ((h.node.value / (focus.value || 1)) * 100).toFixed(1) + "%" }]} />
57
+ ) : null}
58
+ </div>
59
+ </div>
60
+ );
61
+ }
@@ -0,0 +1,60 @@
1
+ <script setup>
2
+ import { ref, computed } from 'vue'
3
+ import { fmtNum, arcPath, polar, SERIES } from './chart-geometry.js'
4
+ // Hierarchy as concentric rings; click to zoom a branch, click the center to go back.
5
+ const props = defineProps({ data:Object, size:{type:Number,default:320}, ringWidth:Number, valueFormat:{type:Function,default:fmtNum}, interactive:{type:Boolean,default:true}, style:Object })
6
+ const zoom = ref([]); const hover = ref(null)
7
+ function roll(n){ return n.children && n.children.length ? Object.assign({}, n, { value: n.children.reduce((s,c)=>s+roll(c).value,0), children: n.children.map(roll) }) : Object.assign({}, n, { value: n.value||0 }) }
8
+ const g = computed(() => {
9
+ const rooted = props.data ? roll(props.data) : { label:'', value:0, children:[] }
10
+ const focus = zoom.value.reduce((n,i)=> n.children ? n.children[i] : n, rooted)
11
+ const depthOf = n => n.children && n.children.length ? 1+Math.max(...n.children.map(depthOf)) : 0
12
+ const rings = Math.max(1, depthOf(focus))
13
+ const rInner = props.size*0.17
14
+ const rw = props.ringWidth || (props.size/2-6-rInner)/rings
15
+ const segs = []
16
+ const walk = (node, depth, a0, a1, path, colorIdx) => {
17
+ (node.children||[]).forEach((c,ci)=>{
18
+ const span = (c.value/(node.value||1))*(a1-a0)
19
+ const idx = depth===0 ? ci : colorIdx
20
+ segs.push({ node:c, depth, a0, a1:a0+span, path:[...path,ci], colorIdx:idx,
21
+ d: arcPath(props.size/2, props.size/2, rInner+rw*(depth+1)-1.5, rInner+rw*depth, a0+0.35, Math.max(a0+0.5, a0+span-0.35)),
22
+ color: SERIES[idx%SERIES.length] })
23
+ walk(c, depth+1, a0, a0+span, [...path,ci], idx)
24
+ a0 += span
25
+ })
26
+ }
27
+ walk(focus, 0, 0, 360, [], 0)
28
+ return { focus, rInner, rw, segs }
29
+ })
30
+ const tip = computed(()=>{
31
+ if (hover.value==null) return null
32
+ const s = g.value.segs[hover.value]
33
+ const p = polar(props.size/2, props.size/2, g.value.rInner+g.value.rw*(s.depth+0.5), (s.a0+s.a1)/2)
34
+ return { x:p[0], y:p[1], title:s.node.label, value: props.valueFormat(s.node.value)+' · '+((s.node.value/(g.value.focus.value||1))*100).toFixed(1)+'%' }
35
+ })
36
+ </script>
37
+ <template>
38
+ <div :style="{position:'relative',display:'inline-block',lineHeight:0,...style}">
39
+ <div :style="{position:'relative',width:size+'px',height:size+'px'}">
40
+ <svg :width="size" :height="size" role="img">
41
+ <path v-for="(s,i) in g.segs" :key="i" :d="s.d" :fill="s.color"
42
+ :opacity="hover==null ? 0.92-s.depth*0.16 : (hover===i?1:0.28)"
43
+ @mouseenter="interactive && (hover=i)" @mouseleave="interactive && (hover=null)"
44
+ @click="interactive && s.node.children && (zoom=[...zoom,...s.path], hover=null)"
45
+ :style="{transition:'opacity var(--dur-fast) var(--ease-out)',cursor:interactive&&s.node.children?'pointer':'default'}"/>
46
+ <circle :cx="size/2" :cy="size/2" :r="g.rInner-2" fill="var(--bg-surface)" stroke="var(--border-subtle)" stroke-width="1"
47
+ @click="interactive && zoom.length && (zoom=zoom.slice(0,-1))" :style="{cursor:interactive&&zoom.length?'pointer':'default'}"/>
48
+ </svg>
49
+ <div style="position:absolute;inset:0;display:flex;flex-direction:column;align-items:center;justify-content:center;pointer-events:none;line-height:normal;gap:2px">
50
+ <span :style="{fontFamily:'var(--font-sans)',fontWeight:'var(--weight-light)',fontSize:Math.round(size/13)+'px',color:'var(--text-heading)'}">{{ valueFormat(g.focus.value) }}</span>
51
+ <span :style="{fontFamily:'var(--font-sans)',fontSize:'var(--text-2xs)',color:'var(--text-muted)',maxWidth:g.rInner*1.7+'px',textAlign:'center',whiteSpace:'nowrap',overflow:'hidden',textOverflow:'ellipsis'}">{{ g.focus.label || 'Total' }}</span>
52
+ <span v-if="zoom.length" style="font-family:var(--font-sans);font-size:9px;color:var(--action-primary)">↑ back</span>
53
+ </div>
54
+ <div v-if="tip && interactive" :style="{position:'absolute',left:tip.x+'px',top:tip.y+'px',transform:tip.x>size*0.55?'translate(calc(-100% - 10px),-50%)':'translate(10px,-50%)',pointerEvents:'none',zIndex:5,background:'var(--bg-surface)',border:'1px solid var(--border-card)',borderRadius:'var(--radius-md)',boxShadow:'var(--shadow-menu)',padding:'7px 10px',lineHeight:'normal',whiteSpace:'nowrap'}">
55
+ <div style="font-family:var(--font-sans);font-size:var(--text-2xs);color:var(--text-muted);padding-bottom:3px">{{ tip.title }}</div>
56
+ <div style="font-family:var(--font-mono);font-size:var(--text-xs);color:var(--text-heading)">{{ tip.value }}</div>
57
+ </div>
58
+ </div>
59
+ </div>
60
+ </template>
@@ -0,0 +1,13 @@
1
+ import * as React from "react";
2
+ /** 3D surface z = f(x,y) over a grid; drag to orbit. Cells shade on the heat ramp by height. */
3
+ export interface Surface3DProps {
4
+ /** values[row][col] */ values?: number[][];
5
+ width?: number; height?: number;
6
+ /** Initial orbit angles (degrees) */ yaw?: number; pitch?: number;
7
+ /** Vertical exaggeration, 0–1 */ zScale?: number;
8
+ /** Cell outlines */ wire?: boolean;
9
+ /** Drag-to-orbit */ interactive?: boolean;
10
+ valueFormat?: (v: number) => string;
11
+ style?: React.CSSProperties;
12
+ }
13
+ export declare function Surface3D(props: Surface3DProps): JSX.Element;
@@ -0,0 +1,70 @@
1
+ import React from "react";
2
+ import { fmtNum, niceTicks } from "./chart-geometry.js";
3
+ import { makeProj, facePath, faceDepth, shade, lightOf, dragRotate, heatColor } from "./chart-3d.js";
4
+ import { ChartTooltip, wrapStyle } from "./ChartKit.jsx";
5
+
6
+ /**
7
+ * 3D surface — z = f(x, y) over a grid. values[row][col]; drag to orbit. Cells shade on
8
+ * the sequential heat ramp by height with light from the upper left, so the shape reads
9
+ * in both themes without a legend.
10
+ */
11
+ export function Surface3D({ values = [], width = 420, height = 300, yaw: yaw0 = 38, pitch: pitch0 = 32, zScale = 0.55, wire = true, xLabel = "x", yLabel = "y", interactive = true, valueFormat = fmtNum, style }) {
12
+ const [rot, setRot] = React.useState({ yaw: yaw0, pitch: pitch0 });
13
+ const [hover, setHover] = React.useState(null);
14
+ const rows = values.length, cols = values[0] ? values[0].length : 0;
15
+ let lo = Infinity, hi = -Infinity;
16
+ values.forEach((r) => r.forEach((v) => { if (v < lo) lo = v; if (v > hi) hi = v; }));
17
+ const span = hi - lo || 1;
18
+ const scale = Math.min(width, height) * 0.42;
19
+ const P = makeProj(width / 2, height / 2 + height * 0.05, scale, rot.yaw, rot.pitch);
20
+ const pt = (r, c) => P((c / (cols - 1)) - 0.5, (((values[r][c] - lo) / span) - 0.5) * zScale * 2, (r / (rows - 1)) - 0.5);
21
+ const quads = [];
22
+ for (let r = 0; r < rows - 1; r++) for (let c = 0; c < cols - 1; c++) {
23
+ const pts = [pt(r, c), pt(r, c + 1), pt(r + 1, c + 1), pt(r + 1, c)];
24
+ const avg = (values[r][c] + values[r][c + 1] + values[r + 1][c] + values[r + 1][c + 1]) / 4;
25
+ const t = (avg - lo) / span;
26
+ quads.push({ pts, t, avg, r, c, depth: faceDepth(pts), light: lightOf(pts[0], pts[1], pts[3]) });
27
+ }
28
+ quads.sort((a, b) => b.depth - a.depth);
29
+ /* Base frame + three axes meeting at the front-left corner: X along the columns,
30
+ Y along the rows (depth), Z vertical with real ticks. */
31
+ const yB = -zScale - 0.06;
32
+ const base = [P(-0.5, yB, -0.5), P(0.5, yB, -0.5), P(0.5, yB, 0.5), P(-0.5, yB, 0.5)];
33
+ const basePath = "M" + base.map((p) => p.x.toFixed(1) + " " + p.y.toFixed(1)).join(" L") + " Z";
34
+ const corner = P(-0.5, yB, 0.5);
35
+ const axes3 = [
36
+ { a: corner, b: P(0.5, yB, 0.5), label: xLabel, lp: P(0, yB - 0.16, 0.5), anchor: "middle" },
37
+ { a: corner, b: P(-0.5, yB, -0.5), label: yLabel, lp: P(-0.62, yB - 0.12, 0), anchor: "end" },
38
+ { a: corner, b: P(-0.5, zScale, 0.5) },
39
+ ];
40
+ const zt = niceTicks(lo, hi, 3);
41
+ const zTicks = zt.ticks.filter((t) => t >= lo && t <= hi).map((t) => ({
42
+ p: P(-0.5, ((t - lo) / span - 0.5) * zScale * 2, 0.5), v: valueFormat(t),
43
+ }));
44
+ const handlers = interactive ? dragRotate(() => rot, setRot) : {};
45
+ return (
46
+ <div style={{ ...wrapStyle, ...style }}>
47
+ <svg width={width} height={height} {...handlers} role="img" style={{ cursor: interactive ? "grab" : "default", touchAction: "none" }}>
48
+ <path d={basePath} fill="var(--bg-inset)" stroke="var(--chart-axis)" strokeWidth="1" />
49
+ {axes3.map((ax, i) => <line key={"ae" + i} x1={ax.a.x} y1={ax.a.y} x2={ax.b.x} y2={ax.b.y} stroke="var(--chart-guide)" strokeWidth="1" />)}
50
+ {axes3.map((ax, i) => ax.label ? <text key={"al" + i} x={ax.lp.x} y={ax.lp.y + 3} textAnchor={ax.anchor} style={{ fontFamily: "var(--font-sans)", fontSize: 10, fill: "var(--text-secondary)" }}>{ax.label}</text> : null)}
51
+ {zTicks.map((t, i) => (
52
+ <g key={"zt" + i}>
53
+ <line x1={t.p.x - 4} y1={t.p.y} x2={t.p.x} y2={t.p.y} stroke="var(--chart-guide)" strokeWidth="1" />
54
+ <text x={t.p.x - 7} y={t.p.y + 3} textAnchor="end" style={{ fontFamily: "var(--font-mono)", fontSize: 9, fill: "var(--chart-label)" }}>{t.v}</text>
55
+ </g>
56
+ ))}
57
+ {quads.map((q, i) => (
58
+ <path key={i} d={facePath(q.pts)} fill={shade(heatColor(q.t), Math.max(-0.35, Math.min(0.3, q.light * 0.4)) + (hover && hover.i === i ? 0.18 : 0))}
59
+ stroke={wire ? "var(--bg-surface)" : "none"} strokeWidth="0.5"
60
+ onMouseEnter={interactive ? () => setHover({ i, q, x: q.pts.reduce((s, p) => s + p.x, 0) / 4, y: q.pts.reduce((s, p) => s + p.y, 0) / 4 }) : undefined}
61
+ onMouseLeave={interactive ? () => setHover(null) : undefined} />
62
+ ))}
63
+ </svg>
64
+ {hover && interactive ? (
65
+ <ChartTooltip x={hover.x} y={hover.y} flip={hover.x > width * 0.6} title={"row " + (hover.q.r + 1) + " · col " + (hover.q.c + 1)}
66
+ rows={[{ label: "value", value: valueFormat(hover.q.avg), color: heatColor(hover.q.t) }]} />
67
+ ) : null}
68
+ </div>
69
+ );
70
+ }