decantr 0.9.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 (382) hide show
  1. package/AGENTS.md +868 -0
  2. package/CHANGELOG.md +255 -0
  3. package/CLAUDE.md +178 -0
  4. package/LICENSE +21 -0
  5. package/README.md +229 -0
  6. package/cli/art.js +127 -0
  7. package/cli/commands/a11y.js +61 -0
  8. package/cli/commands/audit.js +225 -0
  9. package/cli/commands/build.js +38 -0
  10. package/cli/commands/dev.js +18 -0
  11. package/cli/commands/doctor.js +197 -0
  12. package/cli/commands/figma-sync.js +48 -0
  13. package/cli/commands/figma-tokens.js +55 -0
  14. package/cli/commands/generate.js +26 -0
  15. package/cli/commands/init.js +116 -0
  16. package/cli/commands/lint.js +209 -0
  17. package/cli/commands/mcp.js +530 -0
  18. package/cli/commands/migrate.js +175 -0
  19. package/cli/commands/test.js +38 -0
  20. package/cli/commands/validate.js +354 -0
  21. package/cli/index.js +113 -0
  22. package/package.json +95 -0
  23. package/reference/atoms.md +517 -0
  24. package/reference/behaviors.md +384 -0
  25. package/reference/build-tooling.md +275 -0
  26. package/reference/color-guidelines.md +965 -0
  27. package/reference/component-lifecycle.md +137 -0
  28. package/reference/compound-spacing.md +95 -0
  29. package/reference/decantation-process.md +499 -0
  30. package/reference/dev-server-routes.md +93 -0
  31. package/reference/form-system.md +253 -0
  32. package/reference/i18n.md +336 -0
  33. package/reference/icons.md +576 -0
  34. package/reference/llm-primer.md +953 -0
  35. package/reference/plugins.md +252 -0
  36. package/reference/registry-consumption.md +76 -0
  37. package/reference/router.md +217 -0
  38. package/reference/shells.md +116 -0
  39. package/reference/spatial-guidelines.md +541 -0
  40. package/reference/ssr.md +234 -0
  41. package/reference/state-data.md +215 -0
  42. package/reference/state-patterns.md +166 -0
  43. package/reference/state.md +194 -0
  44. package/reference/style-system.md +110 -0
  45. package/reference/tokens.md +460 -0
  46. package/src/app.js +19 -0
  47. package/src/chart/_animate.js +266 -0
  48. package/src/chart/_base.js +109 -0
  49. package/src/chart/_data.js +209 -0
  50. package/src/chart/_format.js +106 -0
  51. package/src/chart/_interact.js +364 -0
  52. package/src/chart/_palette.js +105 -0
  53. package/src/chart/_renderer.js +52 -0
  54. package/src/chart/_scene.js +262 -0
  55. package/src/chart/_shared.js +371 -0
  56. package/src/chart/index.js +637 -0
  57. package/src/chart/layouts/_layout-base.js +328 -0
  58. package/src/chart/layouts/cartesian.js +148 -0
  59. package/src/chart/layouts/hierarchy.js +562 -0
  60. package/src/chart/layouts/polar.js +101 -0
  61. package/src/chart/renderers/canvas.js +179 -0
  62. package/src/chart/renderers/svg.js +256 -0
  63. package/src/chart/renderers/webgpu.js +715 -0
  64. package/src/chart/types/_type-base.js +26 -0
  65. package/src/chart/types/area.js +134 -0
  66. package/src/chart/types/bar.js +173 -0
  67. package/src/chart/types/box-plot.js +125 -0
  68. package/src/chart/types/bubble.js +63 -0
  69. package/src/chart/types/candlestick.js +115 -0
  70. package/src/chart/types/chord.js +85 -0
  71. package/src/chart/types/combination.js +108 -0
  72. package/src/chart/types/funnel.js +68 -0
  73. package/src/chart/types/gauge.js +163 -0
  74. package/src/chart/types/heatmap.js +98 -0
  75. package/src/chart/types/histogram.js +71 -0
  76. package/src/chart/types/line.js +111 -0
  77. package/src/chart/types/org-chart.js +93 -0
  78. package/src/chart/types/pie.js +81 -0
  79. package/src/chart/types/radar.js +96 -0
  80. package/src/chart/types/radial.js +68 -0
  81. package/src/chart/types/range-area.js +55 -0
  82. package/src/chart/types/range-bar.js +61 -0
  83. package/src/chart/types/sankey.js +73 -0
  84. package/src/chart/types/scatter.js +66 -0
  85. package/src/chart/types/sparkline.js +81 -0
  86. package/src/chart/types/sunburst.js +69 -0
  87. package/src/chart/types/swimlane.js +88 -0
  88. package/src/chart/types/treemap.js +62 -0
  89. package/src/chart/types/waterfall.js +100 -0
  90. package/src/components/_base.js +1658 -0
  91. package/src/components/_behaviors.js +1140 -0
  92. package/src/components/_primitives.js +534 -0
  93. package/src/components/_qr-encoder.js +539 -0
  94. package/src/components/accordion.js +207 -0
  95. package/src/components/affix.js +62 -0
  96. package/src/components/alert-dialog.js +75 -0
  97. package/src/components/alert.js +47 -0
  98. package/src/components/aspect-ratio.js +24 -0
  99. package/src/components/avatar-group.js +55 -0
  100. package/src/components/avatar.js +38 -0
  101. package/src/components/back-top.js +75 -0
  102. package/src/components/badge.js +74 -0
  103. package/src/components/banner.js +68 -0
  104. package/src/components/breadcrumb.js +162 -0
  105. package/src/components/button.js +115 -0
  106. package/src/components/calendar.js +131 -0
  107. package/src/components/card.js +192 -0
  108. package/src/components/carousel.js +98 -0
  109. package/src/components/cascader.js +261 -0
  110. package/src/components/checkbox.js +80 -0
  111. package/src/components/chip.js +81 -0
  112. package/src/components/code-block.js +82 -0
  113. package/src/components/collapsible.js +50 -0
  114. package/src/components/color-palette.js +438 -0
  115. package/src/components/color-picker.js +314 -0
  116. package/src/components/combobox.js +181 -0
  117. package/src/components/command.js +174 -0
  118. package/src/components/comment.js +206 -0
  119. package/src/components/context-menu.js +76 -0
  120. package/src/components/data-table.js +724 -0
  121. package/src/components/date-picker.js +217 -0
  122. package/src/components/date-range-picker.js +244 -0
  123. package/src/components/datetime-picker.js +271 -0
  124. package/src/components/descriptions.js +68 -0
  125. package/src/components/drawer.js +179 -0
  126. package/src/components/dropdown.js +88 -0
  127. package/src/components/empty.js +41 -0
  128. package/src/components/float-button.js +90 -0
  129. package/src/components/form.js +106 -0
  130. package/src/components/hover-card.js +49 -0
  131. package/src/components/icon.js +87 -0
  132. package/src/components/image.js +97 -0
  133. package/src/components/index.js +117 -0
  134. package/src/components/input-group.js +75 -0
  135. package/src/components/input-number.js +155 -0
  136. package/src/components/input-otp.js +178 -0
  137. package/src/components/input.js +91 -0
  138. package/src/components/kbd.js +36 -0
  139. package/src/components/label.js +25 -0
  140. package/src/components/list.js +118 -0
  141. package/src/components/masked-input.js +236 -0
  142. package/src/components/mentions.js +165 -0
  143. package/src/components/menu.js +259 -0
  144. package/src/components/message.js +80 -0
  145. package/src/components/modal.js +147 -0
  146. package/src/components/navigation-menu.js +166 -0
  147. package/src/components/notification.js +84 -0
  148. package/src/components/pagination.js +104 -0
  149. package/src/components/placeholder.js +132 -0
  150. package/src/components/popconfirm.js +70 -0
  151. package/src/components/popover.js +58 -0
  152. package/src/components/progress.js +61 -0
  153. package/src/components/qrcode.js +251 -0
  154. package/src/components/radiogroup.js +120 -0
  155. package/src/components/range-slider.js +176 -0
  156. package/src/components/rate.js +186 -0
  157. package/src/components/resizable.js +83 -0
  158. package/src/components/result.js +57 -0
  159. package/src/components/scroll-area.js +43 -0
  160. package/src/components/segmented.js +97 -0
  161. package/src/components/select.js +165 -0
  162. package/src/components/separator.js +31 -0
  163. package/src/components/shell.js +407 -0
  164. package/src/components/skeleton.js +39 -0
  165. package/src/components/slider.js +141 -0
  166. package/src/components/sortable-list.js +176 -0
  167. package/src/components/space.js +42 -0
  168. package/src/components/spinner.js +112 -0
  169. package/src/components/splitter.js +147 -0
  170. package/src/components/statistic.js +136 -0
  171. package/src/components/steps.js +99 -0
  172. package/src/components/switch.js +95 -0
  173. package/src/components/table.js +44 -0
  174. package/src/components/tabs.js +216 -0
  175. package/src/components/tag.js +115 -0
  176. package/src/components/textarea.js +82 -0
  177. package/src/components/time-picker.js +153 -0
  178. package/src/components/time-range-picker.js +170 -0
  179. package/src/components/timeline.js +226 -0
  180. package/src/components/toast.js +71 -0
  181. package/src/components/toggle.js +213 -0
  182. package/src/components/tooltip.js +57 -0
  183. package/src/components/tour.js +159 -0
  184. package/src/components/transfer.js +163 -0
  185. package/src/components/tree-select.js +274 -0
  186. package/src/components/tree.js +141 -0
  187. package/src/components/typography.js +136 -0
  188. package/src/components/upload.js +118 -0
  189. package/src/components/visually-hidden.js +20 -0
  190. package/src/components/watermark.js +124 -0
  191. package/src/core/index.js +539 -0
  192. package/src/core/lifecycle.js +69 -0
  193. package/src/css/atoms.js +651 -0
  194. package/src/css/components.js +940 -0
  195. package/src/css/derive.js +1296 -0
  196. package/src/css/index.js +265 -0
  197. package/src/css/runtime.js +268 -0
  198. package/src/css/styles/addons/bioluminescent.js +93 -0
  199. package/src/css/styles/addons/clay.js +70 -0
  200. package/src/css/styles/addons/clean.js +57 -0
  201. package/src/css/styles/addons/command-center.js +143 -0
  202. package/src/css/styles/addons/dopamine.js +83 -0
  203. package/src/css/styles/addons/editorial.js +80 -0
  204. package/src/css/styles/addons/glassmorphism.js +99 -0
  205. package/src/css/styles/addons/liquid-glass.js +105 -0
  206. package/src/css/styles/addons/prismatic.js +100 -0
  207. package/src/css/styles/addons/retro.js +63 -0
  208. package/src/css/styles/auradecantism.js +96 -0
  209. package/src/css/theme-registry.js +444 -0
  210. package/src/data/entity.js +281 -0
  211. package/src/data/index.js +13 -0
  212. package/src/data/persist.js +225 -0
  213. package/src/data/query.js +839 -0
  214. package/src/data/realtime.js +299 -0
  215. package/src/data/url.js +177 -0
  216. package/src/data/worker.js +134 -0
  217. package/src/explorer/archetypes.js +243 -0
  218. package/src/explorer/atoms.js +228 -0
  219. package/src/explorer/charts.js +497 -0
  220. package/src/explorer/components.js +129 -0
  221. package/src/explorer/foundations.js +949 -0
  222. package/src/explorer/icons.js +178 -0
  223. package/src/explorer/patterns.js +247 -0
  224. package/src/explorer/recipes.js +194 -0
  225. package/src/explorer/shared/pattern-examples.js +1337 -0
  226. package/src/explorer/shared/showcase-renderer.js +958 -0
  227. package/src/explorer/shared/spec-table.js +41 -0
  228. package/src/explorer/shared/usage-links.js +87 -0
  229. package/src/explorer/shell-config.js +10 -0
  230. package/src/explorer/shells.js +551 -0
  231. package/src/explorer/styles.js +161 -0
  232. package/src/explorer/tokens.js +262 -0
  233. package/src/explorer/tools.js +525 -0
  234. package/src/form/index.js +804 -0
  235. package/src/i18n/index.js +251 -0
  236. package/src/icons/essential.js +479 -0
  237. package/src/icons/index.js +53 -0
  238. package/src/plugins/index.js +282 -0
  239. package/src/registry/archetypes/content-site.json +71 -0
  240. package/src/registry/archetypes/docs-explorer.json +23 -0
  241. package/src/registry/archetypes/ecommerce.json +104 -0
  242. package/src/registry/archetypes/financial-dashboard.json +77 -0
  243. package/src/registry/archetypes/index.json +41 -0
  244. package/src/registry/archetypes/portfolio.json +82 -0
  245. package/src/registry/archetypes/recipe-community.json +159 -0
  246. package/src/registry/archetypes/saas-dashboard.json +86 -0
  247. package/src/registry/architect/cross-cutting.json +45 -0
  248. package/src/registry/architect/domains/ecommerce.json +294 -0
  249. package/src/registry/architect/domains/financial-services.json +302 -0
  250. package/src/registry/architect/index.json +26 -0
  251. package/src/registry/architect/traits.json +379 -0
  252. package/src/registry/atoms.json +16 -0
  253. package/src/registry/chart-showcase.json +160 -0
  254. package/src/registry/chart.json +136 -0
  255. package/src/registry/components.json +8616 -0
  256. package/src/registry/core.json +216 -0
  257. package/src/registry/css.json +319 -0
  258. package/src/registry/data.json +135 -0
  259. package/src/registry/foundations.json +11 -0
  260. package/src/registry/icons.json +463 -0
  261. package/src/registry/index.json +101 -0
  262. package/src/registry/patterns/activity-feed.json +37 -0
  263. package/src/registry/patterns/article-content.json +27 -0
  264. package/src/registry/patterns/auth-form.json +37 -0
  265. package/src/registry/patterns/author-card.json +20 -0
  266. package/src/registry/patterns/card-grid.json +127 -0
  267. package/src/registry/patterns/category-nav.json +26 -0
  268. package/src/registry/patterns/chart-grid.json +36 -0
  269. package/src/registry/patterns/chat-interface.json +37 -0
  270. package/src/registry/patterns/checklist-card.json +55 -0
  271. package/src/registry/patterns/comparison-panel.json +27 -0
  272. package/src/registry/patterns/component-showcase.json +24 -0
  273. package/src/registry/patterns/contact-form.json +31 -0
  274. package/src/registry/patterns/cta-section.json +20 -0
  275. package/src/registry/patterns/data-table.json +37 -0
  276. package/src/registry/patterns/detail-header.json +83 -0
  277. package/src/registry/patterns/detail-panel.json +27 -0
  278. package/src/registry/patterns/explorer-shell.json +22 -0
  279. package/src/registry/patterns/filter-bar.json +33 -0
  280. package/src/registry/patterns/filter-sidebar.json +27 -0
  281. package/src/registry/patterns/form-sections.json +110 -0
  282. package/src/registry/patterns/goal-tracker.json +27 -0
  283. package/src/registry/patterns/hero.json +107 -0
  284. package/src/registry/patterns/index.json +47 -0
  285. package/src/registry/patterns/kpi-grid.json +36 -0
  286. package/src/registry/patterns/media-gallery.json +20 -0
  287. package/src/registry/patterns/order-history.json +20 -0
  288. package/src/registry/patterns/pagination.json +19 -0
  289. package/src/registry/patterns/photo-to-recipe.json +36 -0
  290. package/src/registry/patterns/pipeline-tracker.json +28 -0
  291. package/src/registry/patterns/post-list.json +27 -0
  292. package/src/registry/patterns/pricing-table.json +32 -0
  293. package/src/registry/patterns/scorecard.json +28 -0
  294. package/src/registry/patterns/search-bar.json +20 -0
  295. package/src/registry/patterns/specimen-grid.json +19 -0
  296. package/src/registry/patterns/stat-card.json +55 -0
  297. package/src/registry/patterns/stats-bar.json +55 -0
  298. package/src/registry/patterns/steps-card.json +55 -0
  299. package/src/registry/patterns/table-of-contents.json +19 -0
  300. package/src/registry/patterns/testimonials.json +21 -0
  301. package/src/registry/patterns/timeline.json +27 -0
  302. package/src/registry/patterns/token-inspector.json +21 -0
  303. package/src/registry/patterns/wizard.json +27 -0
  304. package/src/registry/recipe-auradecantism.json +69 -0
  305. package/src/registry/recipe-clean.json +65 -0
  306. package/src/registry/recipe-command-center.json +78 -0
  307. package/src/registry/router.json +73 -0
  308. package/src/registry/schema/README.md +197 -0
  309. package/src/registry/skeletons.json +259 -0
  310. package/src/registry/state.json +137 -0
  311. package/src/registry/tokens.json +40 -0
  312. package/src/router/hash.js +17 -0
  313. package/src/router/history.js +18 -0
  314. package/src/router/index.js +598 -0
  315. package/src/ssr/index.js +922 -0
  316. package/src/state/arrays.js +181 -0
  317. package/src/state/devtools.js +647 -0
  318. package/src/state/index.js +498 -0
  319. package/src/state/middleware.js +288 -0
  320. package/src/state/scheduler.js +206 -0
  321. package/src/state/store.js +300 -0
  322. package/src/tags/index.js +19 -0
  323. package/src/tannins/auth.js +396 -0
  324. package/src/test/dom.js +352 -0
  325. package/src/test/index.js +62 -0
  326. package/src/test/state.js +306 -0
  327. package/tools/a11y-audit.js +487 -0
  328. package/tools/analyzer.js +315 -0
  329. package/tools/audit.js +706 -0
  330. package/tools/builder.js +1422 -0
  331. package/tools/css-extract.js +188 -0
  332. package/tools/dev-server.js +316 -0
  333. package/tools/dts-gen.js +1260 -0
  334. package/tools/figma-components.js +329 -0
  335. package/tools/figma-patterns.js +516 -0
  336. package/tools/figma-plugin/code.js +453 -0
  337. package/tools/figma-plugin/manifest.json +14 -0
  338. package/tools/figma-plugin/ui.html +268 -0
  339. package/tools/figma-render.js +293 -0
  340. package/tools/figma-tokens.js +712 -0
  341. package/tools/figma-upload.js +318 -0
  342. package/tools/generate.js +738 -0
  343. package/tools/icons.js +133 -0
  344. package/tools/init-templates.js +265 -0
  345. package/tools/install-hooks.sh +5 -0
  346. package/tools/migrations/0.5.0.js +53 -0
  347. package/tools/migrations/0.6.0.js +95 -0
  348. package/tools/minify.js +170 -0
  349. package/tools/pre-commit +4 -0
  350. package/tools/registry.js +662 -0
  351. package/tools/reset-playground.js +61 -0
  352. package/tools/starter-templates/content-site/app.js +49 -0
  353. package/tools/starter-templates/content-site/essence.js +19 -0
  354. package/tools/starter-templates/content-site/pages.js +31 -0
  355. package/tools/starter-templates/ecommerce/app.js +50 -0
  356. package/tools/starter-templates/ecommerce/essence.js +19 -0
  357. package/tools/starter-templates/ecommerce/pages.js +31 -0
  358. package/tools/starter-templates/landing-page/app.js +38 -0
  359. package/tools/starter-templates/landing-page/essence.js +18 -0
  360. package/tools/starter-templates/landing-page/pages.js +21 -0
  361. package/tools/starter-templates/portfolio/app.js +45 -0
  362. package/tools/starter-templates/portfolio/essence.js +19 -0
  363. package/tools/starter-templates/portfolio/pages.js +33 -0
  364. package/tools/starter-templates/saas-dashboard/app.js +70 -0
  365. package/tools/starter-templates/saas-dashboard/essence.js +19 -0
  366. package/tools/starter-templates/saas-dashboard/pages.js +31 -0
  367. package/tools/verify-pack.js +203 -0
  368. package/types/chart.d.ts +77 -0
  369. package/types/components.d.ts +587 -0
  370. package/types/core.d.ts +89 -0
  371. package/types/css.d.ts +149 -0
  372. package/types/data.d.ts +238 -0
  373. package/types/form.d.ts +164 -0
  374. package/types/i18n.d.ts +51 -0
  375. package/types/icons.d.ts +27 -0
  376. package/types/index.d.ts +13 -0
  377. package/types/router.d.ts +116 -0
  378. package/types/ssr.d.ts +102 -0
  379. package/types/state.d.ts +83 -0
  380. package/types/tags.d.ts +62 -0
  381. package/types/tannins.d.ts +63 -0
  382. package/types/test.d.ts +48 -0
@@ -0,0 +1,562 @@
1
+ /**
2
+ * Hierarchical layout algorithms.
3
+ * Treemap squarification, sunburst rings, sankey relaxation, chord matrix, Walker tree.
4
+ * @module layouts/hierarchy
5
+ */
6
+
7
+ // --- Flat → Tree conversion ---
8
+
9
+ /**
10
+ * Convert flat array with id/parent/value fields into a tree.
11
+ * @param {Object[]} data
12
+ * @param {string} idField
13
+ * @param {string} parentField
14
+ * @param {string} valueField
15
+ * @returns {Object} root node { id, value, children, data, depth }
16
+ */
17
+ export function buildHierarchy(data, idField, parentField, valueField) {
18
+ const map = new Map();
19
+ const roots = [];
20
+
21
+ // Create nodes
22
+ for (const d of data) {
23
+ map.set(d[idField], { id: d[idField], value: +d[valueField] || 0, children: [], data: d, depth: 0 });
24
+ }
25
+
26
+ // Link parent → child
27
+ for (const d of data) {
28
+ const node = map.get(d[idField]);
29
+ const parentId = d[parentField];
30
+ if (parentId != null && map.has(parentId)) {
31
+ map.get(parentId).children.push(node);
32
+ } else {
33
+ roots.push(node);
34
+ }
35
+ }
36
+
37
+ // Compute depths and roll up values
38
+ function walk(node, depth) {
39
+ node.depth = depth;
40
+ if (node.children.length > 0) {
41
+ let sum = 0;
42
+ for (const child of node.children) {
43
+ walk(child, depth + 1);
44
+ sum += child.value;
45
+ }
46
+ if (node.value === 0) node.value = sum;
47
+ }
48
+ }
49
+
50
+ // Wrap in virtual root if multiple roots
51
+ const root = roots.length === 1 ? roots[0] : { id: '__root', value: 0, children: roots, data: null, depth: 0 };
52
+ walk(root, 0);
53
+ return root;
54
+ }
55
+
56
+ // --- Treemap: squarification ---
57
+
58
+ /**
59
+ * Squarified treemap layout (Bruls-Huizing-van-Wijk algorithm).
60
+ * @param {Object} root — tree from buildHierarchy
61
+ * @param {number} x
62
+ * @param {number} y
63
+ * @param {number} w
64
+ * @param {number} h
65
+ * @param {string} [algorithm='squarify'] — 'squarify'|'binary'|'slice'
66
+ * @returns {{ id, x, y, w, h, value, depth, data, children }[]}
67
+ */
68
+ export function treemapLayout(root, x, y, w, h, algorithm = 'squarify') {
69
+ const result = [];
70
+
71
+ function squarify(node, bx, by, bw, bh) {
72
+ result.push({ id: node.id, x: bx, y: by, w: bw, h: bh, value: node.value, depth: node.depth, data: node.data });
73
+
74
+ if (!node.children.length) return;
75
+
76
+ const children = [...node.children].sort((a, b) => b.value - a.value);
77
+ const total = children.reduce((s, c) => s + c.value, 0) || 1;
78
+
79
+ if (algorithm === 'slice') {
80
+ layoutSlice(children, total, bx, by, bw, bh);
81
+ } else if (algorithm === 'binary') {
82
+ layoutBinary(children, total, bx, by, bw, bh);
83
+ } else {
84
+ layoutSquarify(children, total, bx, by, bw, bh);
85
+ }
86
+ }
87
+
88
+ function layoutSquarify(children, total, bx, by, bw, bh) {
89
+ let cx = bx, cy = by, cw = bw, ch = bh, remaining = total;
90
+
91
+ let row = [], rowValue = 0;
92
+ for (let i = 0; i < children.length; i++) {
93
+ const child = children[i];
94
+ const prev = worstAspect(row, rowValue, cw, ch, remaining);
95
+ row.push(child);
96
+ rowValue += child.value;
97
+ const curr = worstAspect(row, rowValue, cw, ch, remaining);
98
+
99
+ if (row.length > 1 && curr > prev) {
100
+ // Roll back — lay out previous row
101
+ row.pop();
102
+ rowValue -= child.value;
103
+ const laid = layRow(row, rowValue, cx, cy, cw, ch, remaining);
104
+ cx = laid.cx; cy = laid.cy; cw = laid.cw; ch = laid.ch;
105
+ remaining -= rowValue;
106
+ row = [child];
107
+ rowValue = child.value;
108
+ }
109
+ }
110
+
111
+ if (row.length) layRow(row, rowValue, cx, cy, cw, ch, remaining);
112
+ }
113
+
114
+ function worstAspect(row, rowValue, cw, ch, remaining) {
115
+ if (!row.length) return Infinity;
116
+ const area = (rowValue / (remaining || 1)) * cw * ch;
117
+ const side = cw >= ch ? area / ch : area / cw;
118
+ let worst = 0;
119
+ for (const c of row) {
120
+ const cellArea = (c.value / (rowValue || 1)) * area;
121
+ const dim = cellArea / side;
122
+ const aspect = Math.max(side / dim, dim / side);
123
+ if (aspect > worst) worst = aspect;
124
+ }
125
+ return worst;
126
+ }
127
+
128
+ function layRow(row, rowValue, cx, cy, cw, ch, remaining) {
129
+ const area = (rowValue / (remaining || 1)) * cw * ch;
130
+ const isHorizontal = cw >= ch;
131
+ const side = isHorizontal ? area / ch : area / cw;
132
+
133
+ let offset = 0;
134
+ for (const child of row) {
135
+ const frac = child.value / (rowValue || 1);
136
+ if (isHorizontal) {
137
+ const cellH = ch * frac;
138
+ squarify(child, cx, cy + offset, side, cellH);
139
+ offset += cellH;
140
+ } else {
141
+ const cellW = cw * frac;
142
+ squarify(child, cx + offset, cy, cellW, side);
143
+ offset += cellW;
144
+ }
145
+ }
146
+
147
+ if (isHorizontal) return { cx: cx + side, cy, cw: cw - side, ch };
148
+ return { cx, cy: cy + side, cw, ch: ch - side };
149
+ }
150
+
151
+ function layoutSlice(children, total, bx, by, bw, bh) {
152
+ let offset = 0;
153
+ for (const child of children) {
154
+ const frac = child.value / total;
155
+ const cellH = bh * frac;
156
+ squarify(child, bx, by + offset, bw, cellH);
157
+ offset += cellH;
158
+ }
159
+ }
160
+
161
+ function layoutBinary(children, total, bx, by, bw, bh) {
162
+ if (children.length <= 1) {
163
+ if (children[0]) squarify(children[0], bx, by, bw, bh);
164
+ return;
165
+ }
166
+ // Split into two groups of roughly equal value
167
+ let halfValue = total / 2, sum = 0, splitIdx = 0;
168
+ for (let i = 0; i < children.length; i++) {
169
+ sum += children[i].value;
170
+ if (sum >= halfValue) { splitIdx = i + 1; break; }
171
+ }
172
+ splitIdx = Math.max(1, Math.min(children.length - 1, splitIdx));
173
+ const left = children.slice(0, splitIdx);
174
+ const right = children.slice(splitIdx);
175
+ const leftTotal = left.reduce((s, c) => s + c.value, 0);
176
+ const rightTotal = right.reduce((s, c) => s + c.value, 0);
177
+
178
+ if (bw >= bh) {
179
+ const leftW = bw * (leftTotal / total);
180
+ layoutBinary(left, leftTotal, bx, by, leftW, bh);
181
+ layoutBinary(right, rightTotal, bx + leftW, by, bw - leftW, bh);
182
+ } else {
183
+ const leftH = bh * (leftTotal / total);
184
+ layoutBinary(left, leftTotal, bx, by, bw, leftH);
185
+ layoutBinary(right, rightTotal, bx, by + leftH, bw, bh - leftH);
186
+ }
187
+ }
188
+
189
+ squarify(root, x, y, w, h);
190
+ return result;
191
+ }
192
+
193
+ // --- Sunburst: concentric ring layout ---
194
+
195
+ /**
196
+ * Sunburst ring layout from tree.
197
+ * @param {Object} root — tree from buildHierarchy
198
+ * @param {number} cx — center x
199
+ * @param {number} cy — center y
200
+ * @param {number} outerRadius
201
+ * @param {number} innerRadius — center hole radius
202
+ * @param {number} maxDepth — max visible depth
203
+ * @returns {{ id, startAngle, endAngle, innerR, outerR, value, depth, data }[]}
204
+ */
205
+ export function sunburstLayout(root, cx, cy, outerRadius, innerRadius, maxDepth = 4) {
206
+ const result = [];
207
+ const ringWidth = (outerRadius - innerRadius) / maxDepth;
208
+
209
+ function walk(node, sa, ea, depth) {
210
+ if (depth > maxDepth) return;
211
+ const innerR = innerRadius + (depth - 1) * ringWidth;
212
+ const outerR = innerRadius + depth * ringWidth;
213
+
214
+ if (depth > 0) {
215
+ result.push({
216
+ id: node.id, startAngle: sa, endAngle: ea,
217
+ innerR, outerR, value: node.value, depth,
218
+ data: node.data, cx, cy
219
+ });
220
+ }
221
+
222
+ if (!node.children.length) return;
223
+ const total = node.children.reduce((s, c) => s + c.value, 0) || 1;
224
+ let angle = sa;
225
+ for (const child of node.children) {
226
+ const sweep = (child.value / total) * (ea - sa);
227
+ walk(child, angle, angle + sweep, depth + 1);
228
+ angle += sweep;
229
+ }
230
+ }
231
+
232
+ walk(root, -Math.PI / 2, Math.PI * 3 / 2, 0);
233
+ return result;
234
+ }
235
+
236
+ // --- Sankey: iterative relaxation ---
237
+
238
+ /**
239
+ * Sankey diagram layout.
240
+ * @param {Object[]} nodes — [{ id, label }]
241
+ * @param {Object[]} links — [{ source, target, value }]
242
+ * @param {number} width
243
+ * @param {number} height
244
+ * @param {Object} [opts]
245
+ * @param {number} [opts.nodeWidth=20]
246
+ * @param {number} [opts.nodePadding=12]
247
+ * @param {number} [opts.iterations=32]
248
+ * @returns {{ nodes: Object[], links: Object[] }}
249
+ */
250
+ export function sankeyLayout(nodes, links, width, height, opts = {}) {
251
+ const nodeWidth = opts.nodeWidth || 20;
252
+ const nodePadding = opts.nodePadding || 12;
253
+ const iterations = opts.iterations || 32;
254
+
255
+ // Build adjacency — normalize string nodes to {id} objects
256
+ const nodeMap = new Map();
257
+ for (const n of nodes) {
258
+ const node = typeof n === 'string' ? { id: n, label: n } : n;
259
+ nodeMap.set(node.id, { ...node, sourceLinks: [], targetLinks: [], value: 0, x: 0, y: 0, dy: 0 });
260
+ }
261
+ const processedLinks = links.map(l => ({
262
+ source: nodeMap.get(l.source), target: nodeMap.get(l.target),
263
+ value: l.value, sy: 0, ty: 0, dy: 0
264
+ }));
265
+ for (const l of processedLinks) {
266
+ if (l.source) l.source.sourceLinks.push(l);
267
+ if (l.target) l.target.targetLinks.push(l);
268
+ }
269
+
270
+ // Compute node values
271
+ for (const [, n] of nodeMap) {
272
+ const srcVal = n.sourceLinks.reduce((s, l) => s + l.value, 0);
273
+ const tgtVal = n.targetLinks.reduce((s, l) => s + l.value, 0);
274
+ n.value = Math.max(srcVal, tgtVal);
275
+ }
276
+
277
+ // Assign columns via BFS
278
+ const allNodes = [...nodeMap.values()];
279
+ const roots = allNodes.filter(n => n.targetLinks.length === 0);
280
+ const visited = new Set();
281
+ const queue = roots.map(n => ({ node: n, col: 0 }));
282
+ let maxCol = 0;
283
+ while (queue.length) {
284
+ const { node, col } = queue.shift();
285
+ if (visited.has(node.id)) continue;
286
+ visited.add(node.id);
287
+ node.col = col;
288
+ if (col > maxCol) maxCol = col;
289
+ for (const l of node.sourceLinks) {
290
+ if (l.target && !visited.has(l.target.id)) {
291
+ queue.push({ node: l.target, col: col + 1 });
292
+ }
293
+ }
294
+ }
295
+
296
+ // X positions
297
+ const colWidth = maxCol > 0 ? (width - nodeWidth) / maxCol : 0;
298
+ for (const n of allNodes) n.x = n.col * colWidth;
299
+
300
+ // Y positions — spread evenly in columns, then relax
301
+ const cols = new Map();
302
+ for (const n of allNodes) {
303
+ if (!cols.has(n.col)) cols.set(n.col, []);
304
+ cols.get(n.col).push(n);
305
+ }
306
+
307
+ const totalValue = Math.max(...allNodes.map(n => n.value), 1);
308
+ for (const [, col] of cols) {
309
+ const colTotal = col.reduce((s, n) => s + n.value, 0);
310
+ const scale = (height - (col.length - 1) * nodePadding) / (colTotal || 1);
311
+ let y = 0;
312
+ for (const n of col) {
313
+ n.y = y;
314
+ n.dy = Math.max(1, n.value * scale);
315
+ y += n.dy + nodePadding;
316
+ }
317
+ }
318
+
319
+ // Gauss-Seidel relaxation
320
+ for (let iter = 0; iter < iterations; iter++) {
321
+ for (const n of allNodes) {
322
+ if (n.targetLinks.length) {
323
+ let sum = 0, weight = 0;
324
+ for (const l of n.targetLinks) {
325
+ sum += (l.source.y + l.sy + l.dy / 2) * l.value;
326
+ weight += l.value;
327
+ }
328
+ if (weight) n.y = sum / weight - n.dy / 2;
329
+ }
330
+ }
331
+ // Resolve overlaps within columns
332
+ for (const [, col] of cols) {
333
+ col.sort((a, b) => a.y - b.y);
334
+ let y = 0;
335
+ for (const n of col) {
336
+ if (n.y < y) n.y = y;
337
+ y = n.y + n.dy + nodePadding;
338
+ }
339
+ // Push back if overflow
340
+ const overflow = y - nodePadding - height;
341
+ if (overflow > 0) {
342
+ for (let i = col.length - 1; i >= 0; i--) {
343
+ col[i].y = Math.max(0, col[i].y - overflow * ((i + 1) / col.length));
344
+ }
345
+ }
346
+ }
347
+ }
348
+
349
+ // Compute link vertical positions
350
+ for (const n of allNodes) {
351
+ n.sourceLinks.sort((a, b) => a.target.y - b.target.y);
352
+ n.targetLinks.sort((a, b) => a.source.y - b.source.y);
353
+ let sy = 0, ty = 0;
354
+ for (const l of n.sourceLinks) {
355
+ const scale = n.dy / (n.value || 1);
356
+ l.sy = sy;
357
+ l.dy = l.value * scale;
358
+ sy += l.dy;
359
+ }
360
+ for (const l of n.targetLinks) {
361
+ const scale = n.dy / (n.value || 1);
362
+ l.ty = ty;
363
+ l.dy = l.dy || l.value * scale;
364
+ ty += l.value * scale;
365
+ }
366
+ }
367
+
368
+ return {
369
+ nodes: allNodes.map(n => ({ id: n.id, label: n.label || n.id, x: n.x, y: n.y, w: nodeWidth, h: n.dy, value: n.value, data: n })),
370
+ links: processedLinks.map(l => ({
371
+ source: l.source, target: l.target, value: l.value,
372
+ x0: l.source.x + nodeWidth, y0: l.source.y + l.sy + l.dy / 2,
373
+ x1: l.target.x, y1: l.target.y + l.ty + l.dy / 2,
374
+ width: Math.max(1, l.dy)
375
+ }))
376
+ };
377
+ }
378
+
379
+ // --- Chord: relationship matrix ---
380
+
381
+ /**
382
+ * Chord diagram layout from matrix.
383
+ * @param {number[][]} matrix — NxN flow matrix
384
+ * @param {string[]} labels — N labels
385
+ * @param {number} cx
386
+ * @param {number} cy
387
+ * @param {number} outerR
388
+ * @param {number} innerR
389
+ * @returns {{ arcs: Object[], ribbons: Object[] }}
390
+ */
391
+ export function chordLayout(matrix, labels, cx, cy, outerR, innerR) {
392
+ const n = matrix.length;
393
+ const totals = matrix.map(row => row.reduce((s, v) => s + v, 0));
394
+ const grandTotal = totals.reduce((s, v) => s + v, 0) || 1;
395
+ const padAngle = 0.04;
396
+ const totalAngle = Math.PI * 2 - padAngle * n;
397
+
398
+ // Compute arcs
399
+ const arcs = [];
400
+ let angle = 0;
401
+ for (let i = 0; i < n; i++) {
402
+ const sweep = (totals[i] / grandTotal) * totalAngle;
403
+ arcs.push({
404
+ index: i, label: labels[i] || `${i}`,
405
+ startAngle: angle, endAngle: angle + sweep,
406
+ value: totals[i], cx, cy, innerR, outerR
407
+ });
408
+ angle += sweep + padAngle;
409
+ }
410
+
411
+ // Compute ribbons
412
+ const ribbons = [];
413
+ const sourceOffsets = new Array(n).fill(0);
414
+ const targetOffsets = new Array(n).fill(0);
415
+
416
+ for (let i = 0; i < n; i++) {
417
+ for (let j = 0; j < n; j++) {
418
+ if (matrix[i][j] <= 0) continue;
419
+ const value = matrix[i][j];
420
+ const srcStart = arcs[i].startAngle + (sourceOffsets[i] / (totals[i] || 1)) * (arcs[i].endAngle - arcs[i].startAngle);
421
+ const srcEnd = srcStart + (value / (totals[i] || 1)) * (arcs[i].endAngle - arcs[i].startAngle);
422
+ const tgtStart = arcs[j].startAngle + (targetOffsets[j] / (totals[j] || 1)) * (arcs[j].endAngle - arcs[j].startAngle);
423
+ const tgtEnd = tgtStart + (value / (totals[j] || 1)) * (arcs[j].endAngle - arcs[j].startAngle);
424
+
425
+ ribbons.push({
426
+ source: { index: i, startAngle: srcStart, endAngle: srcEnd },
427
+ target: { index: j, startAngle: tgtStart, endAngle: tgtEnd },
428
+ value, cx, cy, r: innerR
429
+ });
430
+
431
+ sourceOffsets[i] += value;
432
+ targetOffsets[j] += value;
433
+ }
434
+ }
435
+
436
+ return { arcs, ribbons };
437
+ }
438
+
439
+ // --- Walker's tree layout (for org charts) ---
440
+
441
+ /**
442
+ * Walker's algorithm for aesthetically pleasing tree drawing.
443
+ * @param {Object} root — tree from buildHierarchy
444
+ * @param {number} width
445
+ * @param {number} height
446
+ * @param {string} [orientation='top-down'] — 'top-down'|'left-right'|'radial'
447
+ * @param {Object} [opts]
448
+ * @param {number} [opts.nodeWidth=120]
449
+ * @param {number} [opts.nodeHeight=60]
450
+ * @param {number} [opts.siblingGap=24]
451
+ * @param {number} [opts.levelGap=80]
452
+ * @returns {{ nodes: Object[], edges: Object[] }}
453
+ */
454
+ export function walkerLayout(root, width, height, orientation = 'top-down', opts = {}) {
455
+ const nodeWidth = opts.nodeWidth || 120;
456
+ const nodeHeight = opts.nodeHeight || 60;
457
+ const siblingGap = opts.siblingGap || 24;
458
+ const levelGap = opts.levelGap || 80;
459
+
460
+ // First pass: assign preliminary x
461
+ function firstWalk(node) {
462
+ if (!node.children.length) {
463
+ node._x = 0;
464
+ return;
465
+ }
466
+ for (const child of node.children) firstWalk(child);
467
+
468
+ // Position node centered above children
469
+ const firstChild = node.children[0];
470
+ const lastChild = node.children[node.children.length - 1];
471
+ node._x = (firstChild._x + lastChild._x) / 2;
472
+ }
473
+
474
+ // Second pass: spread siblings
475
+ function spreadSiblings(nodes) {
476
+ for (let i = 1; i < nodes.length; i++) {
477
+ const gap = nodes[i]._x - nodes[i - 1]._x;
478
+ if (gap < nodeWidth + siblingGap) {
479
+ const shift = nodeWidth + siblingGap - gap;
480
+ for (let j = i; j < nodes.length; j++) {
481
+ nodes[j]._x += shift;
482
+ }
483
+ }
484
+ }
485
+ }
486
+
487
+ // Collect nodes by level
488
+ const levels = [];
489
+ function collectLevels(node, depth) {
490
+ if (!levels[depth]) levels[depth] = [];
491
+ levels[depth].push(node);
492
+ for (const child of node.children) collectLevels(child, depth + 1);
493
+ }
494
+
495
+ firstWalk(root);
496
+ collectLevels(root, 0);
497
+
498
+ // Spread siblings at each level
499
+ for (const level of levels) spreadSiblings(level);
500
+
501
+ // Re-center parents after spreading
502
+ for (let d = levels.length - 2; d >= 0; d--) {
503
+ for (const node of levels[d]) {
504
+ if (node.children.length) {
505
+ const first = node.children[0];
506
+ const last = node.children[node.children.length - 1];
507
+ node._x = (first._x + last._x) / 2;
508
+ }
509
+ }
510
+ }
511
+
512
+ // Find bounds and normalize
513
+ let minX = Infinity, maxX = -Infinity;
514
+ for (const level of levels) {
515
+ for (const n of level) {
516
+ if (n._x < minX) minX = n._x;
517
+ if (n._x > maxX) maxX = n._x;
518
+ }
519
+ }
520
+ const treeWidth = maxX - minX + nodeWidth;
521
+ const treeHeight = levels.length * (nodeHeight + levelGap) - levelGap;
522
+ const scaleX = Math.min(1, (width - 40) / (treeWidth || 1));
523
+ const scaleY = Math.min(1, (height - 40) / (treeHeight || 1));
524
+ const offsetX = (width - treeWidth * scaleX) / 2 - minX * scaleX;
525
+ const offsetY = 20;
526
+
527
+ // Build output
528
+ const resultNodes = [];
529
+ const edges = [];
530
+
531
+ function buildOutput(node, depth) {
532
+ let nx, ny;
533
+ if (orientation === 'left-right') {
534
+ nx = offsetY + depth * (nodeHeight + levelGap) * scaleY;
535
+ ny = offsetX + node._x * scaleX;
536
+ } else {
537
+ nx = offsetX + node._x * scaleX;
538
+ ny = offsetY + depth * (nodeHeight + levelGap) * scaleY;
539
+ }
540
+
541
+ resultNodes.push({
542
+ id: node.id, x: nx, y: ny, w: nodeWidth * scaleX, h: nodeHeight * scaleY,
543
+ value: node.value, depth, data: node.data, children: node.children
544
+ });
545
+
546
+ for (const child of node.children) {
547
+ const cn = buildOutput(child, depth + 1);
548
+ edges.push({
549
+ x0: orientation === 'left-right' ? nx + nodeHeight * scaleY : nx + nodeWidth * scaleX / 2,
550
+ y0: orientation === 'left-right' ? ny + nodeWidth * scaleX / 2 : ny + nodeHeight * scaleY,
551
+ x1: orientation === 'left-right' ? cn.x : cn.x + nodeWidth * scaleX / 2,
552
+ y1: orientation === 'left-right' ? cn.y + nodeWidth * scaleX / 2 : cn.y,
553
+ source: node.id, target: child.id
554
+ });
555
+ }
556
+
557
+ return { x: nx, y: ny };
558
+ }
559
+
560
+ buildOutput(root, 0);
561
+ return { nodes: resultNodes, edges };
562
+ }
@@ -0,0 +1,101 @@
1
+ /**
2
+ * Shared polar coordinate layout.
3
+ * Used by pie, radar, radial, gauge, funnel.
4
+ * Handles: center point, available radius, angular scale, radial scale.
5
+ * @module layouts/polar
6
+ */
7
+
8
+ import { scaleLinear } from '../_shared.js';
9
+ import { MARGINS_PIE, innerDimensions } from './_layout-base.js';
10
+
11
+ /**
12
+ * Build polar coordinate system.
13
+ * @param {number} width
14
+ * @param {number} height
15
+ * @param {Object} [opts]
16
+ * @param {number} [opts.startAngle=-PI/2] — start angle (radians, -PI/2 = 12 o'clock)
17
+ * @param {number} [opts.endAngle=3PI/2] — end angle (full circle by default)
18
+ * @param {number} [opts.innerRadiusRatio=0] — 0=full, 0.55=donut
19
+ * @param {number} [opts.padding=8] — px padding from edges
20
+ * @param {Object} [opts.margins] — override margins
21
+ * @returns {{ cx, cy, radius, innerRadius, startAngle, endAngle, angularScale, radialScale, innerW, innerH, margins }}
22
+ */
23
+ export function polar(width, height, opts = {}) {
24
+ const margins = opts.margins || MARGINS_PIE;
25
+ const { innerW, innerH } = innerDimensions(width, height, margins);
26
+ const padding = opts.padding != null ? opts.padding : 8;
27
+
28
+ const cx = margins.left + innerW / 2;
29
+ const cy = margins.top + innerH / 2;
30
+ const radius = Math.max(0, Math.min(innerW, innerH) / 2 - padding);
31
+ const innerRadiusRatio = opts.innerRadiusRatio || 0;
32
+ const innerRadius = radius * innerRadiusRatio;
33
+
34
+ const startAngle = opts.startAngle != null ? opts.startAngle : -Math.PI / 2;
35
+ const endAngle = opts.endAngle != null ? opts.endAngle : Math.PI * 3 / 2;
36
+ const totalAngle = endAngle - startAngle;
37
+
38
+ /**
39
+ * Angular scale — maps [0, total] to [startAngle, endAngle].
40
+ * @param {number} v — value from 0 to total
41
+ * @param {number} total — sum of all values
42
+ * @returns {number} angle in radians
43
+ */
44
+ function angularScale(v, total) {
45
+ return startAngle + (v / (total || 1)) * totalAngle;
46
+ }
47
+
48
+ /**
49
+ * Radial scale — maps [domain min, domain max] to [innerRadius, radius].
50
+ * @param {number[]} domain
51
+ * @returns {Function}
52
+ */
53
+ function radialScale(domain) {
54
+ return scaleLinear(domain, [innerRadius, radius]);
55
+ }
56
+
57
+ return {
58
+ cx, cy, radius, innerRadius,
59
+ startAngle, endAngle, totalAngle,
60
+ angularScale, radialScale,
61
+ innerW, innerH, margins,
62
+ width, height
63
+ };
64
+ }
65
+
66
+ /**
67
+ * Compute point on polar coordinates.
68
+ * @param {number} cx — center x
69
+ * @param {number} cy — center y
70
+ * @param {number} r — radius
71
+ * @param {number} angle — angle in radians
72
+ * @returns {{ x: number, y: number }}
73
+ */
74
+ export function polarPoint(cx, cy, r, angle) {
75
+ return {
76
+ x: cx + r * Math.cos(angle),
77
+ y: cy + r * Math.sin(angle)
78
+ };
79
+ }
80
+
81
+ /**
82
+ * Build slice angles from data values.
83
+ * @param {number[]} values
84
+ * @param {number} startAngle
85
+ * @param {number} totalAngle
86
+ * @returns {{ startAngle: number, endAngle: number, percentage: number }[]}
87
+ */
88
+ export function sliceAngles(values, startAngle, totalAngle) {
89
+ let total = 0;
90
+ for (let i = 0; i < values.length; i++) total += Math.abs(values[i]) || 0;
91
+ if (total === 0) total = 1;
92
+
93
+ let angle = startAngle;
94
+ return values.map(v => {
95
+ const val = Math.abs(v) || 0;
96
+ const sweep = (val / total) * totalAngle;
97
+ const sa = angle;
98
+ angle += sweep;
99
+ return { startAngle: sa, endAngle: sa + sweep, percentage: (val / total) * 100 };
100
+ });
101
+ }