@ojiepermana/angular 22.0.1 → 22.0.27

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 (517) hide show
  1. package/README.md +16 -302
  2. package/fesm2022/ojiepermana-angular-chart.mjs +11 -0
  3. package/fesm2022/ojiepermana-angular-chart.mjs.map +1 -0
  4. package/fesm2022/ojiepermana-angular-component.mjs +11 -0
  5. package/fesm2022/ojiepermana-angular-component.mjs.map +1 -0
  6. package/fesm2022/ojiepermana-angular-navigation.mjs +11 -0
  7. package/fesm2022/ojiepermana-angular-navigation.mjs.map +1 -0
  8. package/fesm2022/ojiepermana-angular-sdk.mjs +11 -0
  9. package/fesm2022/ojiepermana-angular-sdk.mjs.map +1 -0
  10. package/fesm2022/ojiepermana-angular-theme.mjs +3 -382
  11. package/fesm2022/ojiepermana-angular-theme.mjs.map +1 -1
  12. package/fesm2022/ojiepermana-angular.mjs +14 -14
  13. package/fesm2022/ojiepermana-angular.mjs.map +1 -1
  14. package/package.json +25 -425
  15. package/types/ojiepermana-angular-chart.d.ts +1 -0
  16. package/types/ojiepermana-angular-component.d.ts +1 -0
  17. package/types/ojiepermana-angular-navigation.d.ts +1 -0
  18. package/types/ojiepermana-angular-sdk.d.ts +1 -0
  19. package/types/ojiepermana-angular-theme.d.ts +1 -116
  20. package/types/ojiepermana-angular.d.ts +5 -3
  21. package/.npmignore +0 -2
  22. package/collection.json +0 -30
  23. package/component/accordion/README.md +0 -195
  24. package/component/accordion/package.json +0 -4
  25. package/component/alert/README.md +0 -182
  26. package/component/alert/package.json +0 -4
  27. package/component/alert-dialog/README.md +0 -239
  28. package/component/alert-dialog/package.json +0 -4
  29. package/component/aspect-ratio/README.md +0 -112
  30. package/component/aspect-ratio/package.json +0 -4
  31. package/component/avatar/README.md +0 -176
  32. package/component/avatar/package.json +0 -4
  33. package/component/badge/README.md +0 -133
  34. package/component/badge/package.json +0 -4
  35. package/component/breadcrumb/README.md +0 -216
  36. package/component/breadcrumb/package.json +0 -4
  37. package/component/button/README.md +0 -139
  38. package/component/button/package.json +0 -4
  39. package/component/button-group/README.md +0 -208
  40. package/component/button-group/package.json +0 -4
  41. package/component/calendar/README.md +0 -132
  42. package/component/calendar/package.json +0 -4
  43. package/component/card/README.md +0 -220
  44. package/component/card/package.json +0 -4
  45. package/component/carousel/README.md +0 -276
  46. package/component/carousel/package.json +0 -4
  47. package/component/chart/README.md +0 -249
  48. package/component/chart/area/package.json +0 -4
  49. package/component/chart/bar/package.json +0 -4
  50. package/component/chart/line/package.json +0 -4
  51. package/component/chart/package.json +0 -4
  52. package/component/chart/pie/package.json +0 -4
  53. package/component/chart/radar/package.json +0 -4
  54. package/component/chart/radial/package.json +0 -4
  55. package/component/chart/scatter/package.json +0 -4
  56. package/component/checkbox/README.md +0 -149
  57. package/component/checkbox/package.json +0 -4
  58. package/component/collapsible/README.md +0 -195
  59. package/component/collapsible/package.json +0 -4
  60. package/component/combobox/README.md +0 -198
  61. package/component/combobox/package.json +0 -4
  62. package/component/command/README.md +0 -275
  63. package/component/command/package.json +0 -4
  64. package/component/composer/README.md +0 -235
  65. package/component/composer/package.json +0 -4
  66. package/component/context-menu/README.md +0 -267
  67. package/component/context-menu/package.json +0 -4
  68. package/component/date-picker/README.md +0 -177
  69. package/component/date-picker/package.json +0 -4
  70. package/component/dialog/README.md +0 -237
  71. package/component/dialog/package.json +0 -4
  72. package/component/drawer/README.md +0 -145
  73. package/component/drawer/package.json +0 -4
  74. package/component/dropdown-menu/README.md +0 -311
  75. package/component/dropdown-menu/package.json +0 -4
  76. package/component/editor/README.md +0 -136
  77. package/component/editor/package.json +0 -4
  78. package/component/empty/README.md +0 -183
  79. package/component/empty/package.json +0 -4
  80. package/component/form/README.md +0 -210
  81. package/component/form/package.json +0 -4
  82. package/component/hover-card/README.md +0 -146
  83. package/component/hover-card/package.json +0 -4
  84. package/component/input/README.md +0 -159
  85. package/component/input/package.json +0 -4
  86. package/component/input-group/README.md +0 -239
  87. package/component/input-group/package.json +0 -4
  88. package/component/input-otp/README.md +0 -278
  89. package/component/input-otp/package.json +0 -4
  90. package/component/item/README.md +0 -247
  91. package/component/item/package.json +0 -4
  92. package/component/kanban/README.md +0 -81
  93. package/component/kanban/package.json +0 -4
  94. package/component/kbd/README.md +0 -139
  95. package/component/kbd/package.json +0 -4
  96. package/component/label/README.md +0 -136
  97. package/component/label/package.json +0 -4
  98. package/component/menubar/README.md +0 -269
  99. package/component/menubar/package.json +0 -4
  100. package/component/native-select/README.md +0 -176
  101. package/component/native-select/package.json +0 -4
  102. package/component/navigation-menu/README.md +0 -160
  103. package/component/navigation-menu/package.json +0 -4
  104. package/component/pagination/README.md +0 -144
  105. package/component/pagination/package.json +0 -4
  106. package/component/pillbox/README.md +0 -67
  107. package/component/pillbox/package.json +0 -4
  108. package/component/popover/README.md +0 -43
  109. package/component/popover/package.json +0 -4
  110. package/component/progress/README.md +0 -160
  111. package/component/progress/package.json +0 -4
  112. package/component/radio/README.md +0 -209
  113. package/component/radio/package.json +0 -4
  114. package/component/resizable/README.md +0 -168
  115. package/component/resizable/package.json +0 -4
  116. package/component/scroll-area/README.md +0 -143
  117. package/component/scroll-area/package.json +0 -4
  118. package/component/select/README.md +0 -174
  119. package/component/select/package.json +0 -4
  120. package/component/separator/README.md +0 -170
  121. package/component/separator/package.json +0 -4
  122. package/component/sheet/README.md +0 -183
  123. package/component/sheet/package.json +0 -4
  124. package/component/skeleton/README.md +0 -158
  125. package/component/skeleton/package.json +0 -4
  126. package/component/slider/README.md +0 -207
  127. package/component/slider/package.json +0 -4
  128. package/component/spinner/README.md +0 -160
  129. package/component/spinner/package.json +0 -4
  130. package/component/switch/README.md +0 -166
  131. package/component/switch/package.json +0 -4
  132. package/component/table/README.md +0 -291
  133. package/component/table/package.json +0 -4
  134. package/component/tabs/README.md +0 -219
  135. package/component/tabs/package.json +0 -4
  136. package/component/textarea/README.md +0 -154
  137. package/component/textarea/package.json +0 -4
  138. package/component/timeline/README.md +0 -94
  139. package/component/timeline/package.json +0 -4
  140. package/component/toast/README.md +0 -321
  141. package/component/toast/package.json +0 -4
  142. package/component/toggle/README.md +0 -131
  143. package/component/toggle/package.json +0 -4
  144. package/component/toggle-group/README.md +0 -206
  145. package/component/toggle-group/package.json +0 -4
  146. package/component/tooltip/README.md +0 -211
  147. package/component/tooltip/package.json +0 -4
  148. package/component/utils/package.json +0 -4
  149. package/fesm2022/ojiepermana-angular-component-accordion.mjs +0 -189
  150. package/fesm2022/ojiepermana-angular-component-accordion.mjs.map +0 -1
  151. package/fesm2022/ojiepermana-angular-component-alert-dialog.mjs +0 -276
  152. package/fesm2022/ojiepermana-angular-component-alert-dialog.mjs.map +0 -1
  153. package/fesm2022/ojiepermana-angular-component-alert.mjs +0 -99
  154. package/fesm2022/ojiepermana-angular-component-alert.mjs.map +0 -1
  155. package/fesm2022/ojiepermana-angular-component-aspect-ratio.mjs +0 -37
  156. package/fesm2022/ojiepermana-angular-component-aspect-ratio.mjs.map +0 -1
  157. package/fesm2022/ojiepermana-angular-component-avatar.mjs +0 -139
  158. package/fesm2022/ojiepermana-angular-component-avatar.mjs.map +0 -1
  159. package/fesm2022/ojiepermana-angular-component-badge.mjs +0 -50
  160. package/fesm2022/ojiepermana-angular-component-badge.mjs.map +0 -1
  161. package/fesm2022/ojiepermana-angular-component-breadcrumb.mjs +0 -200
  162. package/fesm2022/ojiepermana-angular-component-breadcrumb.mjs.map +0 -1
  163. package/fesm2022/ojiepermana-angular-component-button-group.mjs +0 -103
  164. package/fesm2022/ojiepermana-angular-component-button-group.mjs.map +0 -1
  165. package/fesm2022/ojiepermana-angular-component-button.mjs +0 -68
  166. package/fesm2022/ojiepermana-angular-component-button.mjs.map +0 -1
  167. package/fesm2022/ojiepermana-angular-component-calendar.mjs +0 -88
  168. package/fesm2022/ojiepermana-angular-component-calendar.mjs.map +0 -1
  169. package/fesm2022/ojiepermana-angular-component-card.mjs +0 -152
  170. package/fesm2022/ojiepermana-angular-component-card.mjs.map +0 -1
  171. package/fesm2022/ojiepermana-angular-component-carousel.mjs +0 -334
  172. package/fesm2022/ojiepermana-angular-component-carousel.mjs.map +0 -1
  173. package/fesm2022/ojiepermana-angular-component-chart-area.mjs +0 -6
  174. package/fesm2022/ojiepermana-angular-component-chart-area.mjs.map +0 -1
  175. package/fesm2022/ojiepermana-angular-component-chart-bar.mjs +0 -6
  176. package/fesm2022/ojiepermana-angular-component-chart-bar.mjs.map +0 -1
  177. package/fesm2022/ojiepermana-angular-component-chart-line.mjs +0 -6
  178. package/fesm2022/ojiepermana-angular-component-chart-line.mjs.map +0 -1
  179. package/fesm2022/ojiepermana-angular-component-chart-pie.mjs +0 -6
  180. package/fesm2022/ojiepermana-angular-component-chart-pie.mjs.map +0 -1
  181. package/fesm2022/ojiepermana-angular-component-chart-radar.mjs +0 -6
  182. package/fesm2022/ojiepermana-angular-component-chart-radar.mjs.map +0 -1
  183. package/fesm2022/ojiepermana-angular-component-chart-radial.mjs +0 -6
  184. package/fesm2022/ojiepermana-angular-component-chart-radial.mjs.map +0 -1
  185. package/fesm2022/ojiepermana-angular-component-chart-scatter.mjs +0 -6
  186. package/fesm2022/ojiepermana-angular-component-chart-scatter.mjs.map +0 -1
  187. package/fesm2022/ojiepermana-angular-component-chart.mjs +0 -3925
  188. package/fesm2022/ojiepermana-angular-component-chart.mjs.map +0 -1
  189. package/fesm2022/ojiepermana-angular-component-checkbox.mjs +0 -114
  190. package/fesm2022/ojiepermana-angular-component-checkbox.mjs.map +0 -1
  191. package/fesm2022/ojiepermana-angular-component-collapsible.mjs +0 -124
  192. package/fesm2022/ojiepermana-angular-component-collapsible.mjs.map +0 -1
  193. package/fesm2022/ojiepermana-angular-component-combobox.mjs +0 -272
  194. package/fesm2022/ojiepermana-angular-component-combobox.mjs.map +0 -1
  195. package/fesm2022/ojiepermana-angular-component-command.mjs +0 -293
  196. package/fesm2022/ojiepermana-angular-component-command.mjs.map +0 -1
  197. package/fesm2022/ojiepermana-angular-component-composer.mjs +0 -352
  198. package/fesm2022/ojiepermana-angular-component-composer.mjs.map +0 -1
  199. package/fesm2022/ojiepermana-angular-component-context-menu.mjs +0 -103
  200. package/fesm2022/ojiepermana-angular-component-context-menu.mjs.map +0 -1
  201. package/fesm2022/ojiepermana-angular-component-date-picker.mjs +0 -170
  202. package/fesm2022/ojiepermana-angular-component-date-picker.mjs.map +0 -1
  203. package/fesm2022/ojiepermana-angular-component-dialog.mjs +0 -279
  204. package/fesm2022/ojiepermana-angular-component-dialog.mjs.map +0 -1
  205. package/fesm2022/ojiepermana-angular-component-drawer.mjs +0 -6
  206. package/fesm2022/ojiepermana-angular-component-drawer.mjs.map +0 -1
  207. package/fesm2022/ojiepermana-angular-component-dropdown-menu.mjs +0 -492
  208. package/fesm2022/ojiepermana-angular-component-dropdown-menu.mjs.map +0 -1
  209. package/fesm2022/ojiepermana-angular-component-editor.mjs +0 -717
  210. package/fesm2022/ojiepermana-angular-component-editor.mjs.map +0 -1
  211. package/fesm2022/ojiepermana-angular-component-empty.mjs +0 -145
  212. package/fesm2022/ojiepermana-angular-component-empty.mjs.map +0 -1
  213. package/fesm2022/ojiepermana-angular-component-form.mjs +0 -366
  214. package/fesm2022/ojiepermana-angular-component-form.mjs.map +0 -1
  215. package/fesm2022/ojiepermana-angular-component-hover-card.mjs +0 -297
  216. package/fesm2022/ojiepermana-angular-component-hover-card.mjs.map +0 -1
  217. package/fesm2022/ojiepermana-angular-component-input-group.mjs +0 -179
  218. package/fesm2022/ojiepermana-angular-component-input-group.mjs.map +0 -1
  219. package/fesm2022/ojiepermana-angular-component-input-otp.mjs +0 -514
  220. package/fesm2022/ojiepermana-angular-component-input-otp.mjs.map +0 -1
  221. package/fesm2022/ojiepermana-angular-component-input.mjs +0 -45
  222. package/fesm2022/ojiepermana-angular-component-input.mjs.map +0 -1
  223. package/fesm2022/ojiepermana-angular-component-item.mjs +0 -264
  224. package/fesm2022/ojiepermana-angular-component-item.mjs.map +0 -1
  225. package/fesm2022/ojiepermana-angular-component-kanban.mjs +0 -314
  226. package/fesm2022/ojiepermana-angular-component-kanban.mjs.map +0 -1
  227. package/fesm2022/ojiepermana-angular-component-kbd.mjs +0 -55
  228. package/fesm2022/ojiepermana-angular-component-kbd.mjs.map +0 -1
  229. package/fesm2022/ojiepermana-angular-component-label.mjs +0 -33
  230. package/fesm2022/ojiepermana-angular-component-label.mjs.map +0 -1
  231. package/fesm2022/ojiepermana-angular-component-menubar.mjs +0 -308
  232. package/fesm2022/ojiepermana-angular-component-menubar.mjs.map +0 -1
  233. package/fesm2022/ojiepermana-angular-component-native-select.mjs +0 -67
  234. package/fesm2022/ojiepermana-angular-component-native-select.mjs.map +0 -1
  235. package/fesm2022/ojiepermana-angular-component-navigation-menu.mjs +0 -413
  236. package/fesm2022/ojiepermana-angular-component-navigation-menu.mjs.map +0 -1
  237. package/fesm2022/ojiepermana-angular-component-pagination.mjs +0 -226
  238. package/fesm2022/ojiepermana-angular-component-pagination.mjs.map +0 -1
  239. package/fesm2022/ojiepermana-angular-component-pillbox.mjs +0 -812
  240. package/fesm2022/ojiepermana-angular-component-pillbox.mjs.map +0 -1
  241. package/fesm2022/ojiepermana-angular-component-popover.mjs +0 -169
  242. package/fesm2022/ojiepermana-angular-component-popover.mjs.map +0 -1
  243. package/fesm2022/ojiepermana-angular-component-progress.mjs +0 -60
  244. package/fesm2022/ojiepermana-angular-component-progress.mjs.map +0 -1
  245. package/fesm2022/ojiepermana-angular-component-radio.mjs +0 -122
  246. package/fesm2022/ojiepermana-angular-component-radio.mjs.map +0 -1
  247. package/fesm2022/ojiepermana-angular-component-resizable.mjs +0 -481
  248. package/fesm2022/ojiepermana-angular-component-resizable.mjs.map +0 -1
  249. package/fesm2022/ojiepermana-angular-component-scroll-area.mjs +0 -54
  250. package/fesm2022/ojiepermana-angular-component-scroll-area.mjs.map +0 -1
  251. package/fesm2022/ojiepermana-angular-component-select.mjs +0 -176
  252. package/fesm2022/ojiepermana-angular-component-select.mjs.map +0 -1
  253. package/fesm2022/ojiepermana-angular-component-separator.mjs +0 -37
  254. package/fesm2022/ojiepermana-angular-component-separator.mjs.map +0 -1
  255. package/fesm2022/ojiepermana-angular-component-sheet.mjs +0 -284
  256. package/fesm2022/ojiepermana-angular-component-sheet.mjs.map +0 -1
  257. package/fesm2022/ojiepermana-angular-component-skeleton.mjs +0 -31
  258. package/fesm2022/ojiepermana-angular-component-skeleton.mjs.map +0 -1
  259. package/fesm2022/ojiepermana-angular-component-slider.mjs +0 -423
  260. package/fesm2022/ojiepermana-angular-component-slider.mjs.map +0 -1
  261. package/fesm2022/ojiepermana-angular-component-spinner.mjs +0 -60
  262. package/fesm2022/ojiepermana-angular-component-spinner.mjs.map +0 -1
  263. package/fesm2022/ojiepermana-angular-component-switch.mjs +0 -116
  264. package/fesm2022/ojiepermana-angular-component-switch.mjs.map +0 -1
  265. package/fesm2022/ojiepermana-angular-component-table.mjs +0 -155
  266. package/fesm2022/ojiepermana-angular-component-table.mjs.map +0 -1
  267. package/fesm2022/ojiepermana-angular-component-tabs.mjs +0 -272
  268. package/fesm2022/ojiepermana-angular-component-tabs.mjs.map +0 -1
  269. package/fesm2022/ojiepermana-angular-component-textarea.mjs +0 -39
  270. package/fesm2022/ojiepermana-angular-component-textarea.mjs.map +0 -1
  271. package/fesm2022/ojiepermana-angular-component-timeline.mjs +0 -237
  272. package/fesm2022/ojiepermana-angular-component-timeline.mjs.map +0 -1
  273. package/fesm2022/ojiepermana-angular-component-toast.mjs +0 -71
  274. package/fesm2022/ojiepermana-angular-component-toast.mjs.map +0 -1
  275. package/fesm2022/ojiepermana-angular-component-toggle-group.mjs +0 -289
  276. package/fesm2022/ojiepermana-angular-component-toggle-group.mjs.map +0 -1
  277. package/fesm2022/ojiepermana-angular-component-toggle.mjs +0 -82
  278. package/fesm2022/ojiepermana-angular-component-toggle.mjs.map +0 -1
  279. package/fesm2022/ojiepermana-angular-component-tooltip.mjs +0 -354
  280. package/fesm2022/ojiepermana-angular-component-tooltip.mjs.map +0 -1
  281. package/fesm2022/ojiepermana-angular-component-utils.mjs +0 -13
  282. package/fesm2022/ojiepermana-angular-component-utils.mjs.map +0 -1
  283. package/fesm2022/ojiepermana-angular-generator-api.mjs +0 -68
  284. package/fesm2022/ojiepermana-angular-generator-api.mjs.map +0 -1
  285. package/fesm2022/ojiepermana-angular-layout-component.mjs +0 -602
  286. package/fesm2022/ojiepermana-angular-layout-component.mjs.map +0 -1
  287. package/fesm2022/ojiepermana-angular-layout-provider.mjs +0 -21
  288. package/fesm2022/ojiepermana-angular-layout-provider.mjs.map +0 -1
  289. package/fesm2022/ojiepermana-angular-layout-services.mjs +0 -116
  290. package/fesm2022/ojiepermana-angular-layout-services.mjs.map +0 -1
  291. package/fesm2022/ojiepermana-angular-layout-shell.mjs +0 -48
  292. package/fesm2022/ojiepermana-angular-layout-shell.mjs.map +0 -1
  293. package/fesm2022/ojiepermana-angular-layout-token-directive.mjs +0 -30
  294. package/fesm2022/ojiepermana-angular-layout-token-directive.mjs.map +0 -1
  295. package/fesm2022/ojiepermana-angular-layout-token.mjs +0 -33
  296. package/fesm2022/ojiepermana-angular-layout-token.mjs.map +0 -1
  297. package/fesm2022/ojiepermana-angular-layout-type-empty.mjs +0 -49
  298. package/fesm2022/ojiepermana-angular-layout-type-empty.mjs.map +0 -1
  299. package/fesm2022/ojiepermana-angular-layout-type-horizontal.mjs +0 -128
  300. package/fesm2022/ojiepermana-angular-layout-type-horizontal.mjs.map +0 -1
  301. package/fesm2022/ojiepermana-angular-layout-type-vertical.mjs +0 -123
  302. package/fesm2022/ojiepermana-angular-layout-type-vertical.mjs.map +0 -1
  303. package/fesm2022/ojiepermana-angular-layout.mjs +0 -485
  304. package/fesm2022/ojiepermana-angular-layout.mjs.map +0 -1
  305. package/fesm2022/ojiepermana-angular-navigation-demo-data.mjs +0 -334
  306. package/fesm2022/ojiepermana-angular-navigation-demo-data.mjs.map +0 -1
  307. package/fesm2022/ojiepermana-angular-navigation-icon.mjs +0 -63
  308. package/fesm2022/ojiepermana-angular-navigation-icon.mjs.map +0 -1
  309. package/fesm2022/ojiepermana-angular-navigation-item.mjs +0 -559
  310. package/fesm2022/ojiepermana-angular-navigation-item.mjs.map +0 -1
  311. package/fesm2022/ojiepermana-angular-navigation-service.mjs +0 -213
  312. package/fesm2022/ojiepermana-angular-navigation-service.mjs.map +0 -1
  313. package/fesm2022/ojiepermana-angular-navigation-sidebar.mjs +0 -401
  314. package/fesm2022/ojiepermana-angular-navigation-sidebar.mjs.map +0 -1
  315. package/fesm2022/ojiepermana-angular-navigation-topbar.mjs +0 -670
  316. package/fesm2022/ojiepermana-angular-navigation-topbar.mjs.map +0 -1
  317. package/fesm2022/ojiepermana-angular-navigation-types.mjs +0 -4
  318. package/fesm2022/ojiepermana-angular-navigation-types.mjs.map +0 -1
  319. package/fesm2022/ojiepermana-angular-theme-provider.mjs +0 -35
  320. package/fesm2022/ojiepermana-angular-theme-provider.mjs.map +0 -1
  321. package/fesm2022/ojiepermana-angular-theme-services.mjs +0 -294
  322. package/fesm2022/ojiepermana-angular-theme-services.mjs.map +0 -1
  323. package/fesm2022/ojiepermana-angular-theme-token.mjs +0 -56
  324. package/fesm2022/ojiepermana-angular-theme-token.mjs.map +0 -1
  325. package/generator/api/README.md +0 -252
  326. package/generator/api/bin/package.json +0 -3
  327. package/generator/api/bin/schematics/init/index.js +0 -90
  328. package/generator/api/bin/schematics/ng-add/index.js +0 -131
  329. package/generator/api/bin/schematics/sdk/index.js +0 -76
  330. package/generator/api/bin/src/config/loader.js +0 -41
  331. package/generator/api/bin/src/config/schema.js +0 -57
  332. package/generator/api/bin/src/emit/client.js +0 -248
  333. package/generator/api/bin/src/emit/metadata.js +0 -295
  334. package/generator/api/bin/src/emit/models.js +0 -106
  335. package/generator/api/bin/src/emit/navigation.js +0 -56
  336. package/generator/api/bin/src/emit/operations.js +0 -122
  337. package/generator/api/bin/src/emit/public-api.js +0 -54
  338. package/generator/api/bin/src/emit/services.js +0 -87
  339. package/generator/api/bin/src/engine.js +0 -65
  340. package/generator/api/bin/src/layout/per-domain.js +0 -359
  341. package/generator/api/bin/src/parser/bundle.js +0 -25
  342. package/generator/api/bin/src/parser/ir.js +0 -320
  343. package/generator/api/bin/src/parser/types.js +0 -7
  344. package/generator/api/bin/src/render/template.js +0 -58
  345. package/generator/api/bin/src/writer/index.js +0 -278
  346. package/generator/api/package.json +0 -4
  347. package/generator/api/schematics/init/schema.json +0 -19
  348. package/generator/api/schematics/ng-add/schema.json +0 -14
  349. package/generator/api/schematics/sdk/schema.json +0 -19
  350. package/generator/api/sdk.config.example.json +0 -24
  351. package/generator/guide/README.md +0 -84
  352. package/generator/guide/bin/package.json +0 -3
  353. package/generator/guide/bin/schematics/build/index.js +0 -36
  354. package/generator/guide/bin/schematics/init/index.js +0 -70
  355. package/generator/guide/bin/src/config/loader.js +0 -50
  356. package/generator/guide/bin/src/config/schema.js +0 -12
  357. package/generator/guide/bin/src/engine/component.js +0 -74
  358. package/generator/guide/bin/src/engine/frontmatter.js +0 -42
  359. package/generator/guide/bin/src/engine/index.js +0 -42
  360. package/generator/guide/bin/src/engine/naming.js +0 -39
  361. package/generator/guide/bin/src/engine/render.js +0 -36
  362. package/generator/guide/bin/src/engine/routes.js +0 -106
  363. package/generator/guide/bin/src/engine/walk.js +0 -35
  364. package/generator/guide/guide.config.example.json +0 -9
  365. package/generator/guide/schematics/build/schema.json +0 -14
  366. package/generator/guide/schematics/init/schema.json +0 -19
  367. package/layout/component/package.json +0 -4
  368. package/layout/package.json +0 -4
  369. package/layout/provider/package.json +0 -4
  370. package/layout/services/package.json +0 -4
  371. package/layout/shell/package.json +0 -4
  372. package/layout/token/directive/package.json +0 -4
  373. package/layout/token/package.json +0 -4
  374. package/layout/type/empty/package.json +0 -4
  375. package/layout/type/horizontal/package.json +0 -4
  376. package/layout/type/vertical/package.json +0 -4
  377. package/navigation/demo-data/package.json +0 -4
  378. package/navigation/icon/package.json +0 -4
  379. package/navigation/item/package.json +0 -4
  380. package/navigation/service/package.json +0 -4
  381. package/navigation/sidebar/package.json +0 -4
  382. package/navigation/topbar/README.md +0 -196
  383. package/navigation/topbar/package.json +0 -4
  384. package/navigation/types/package.json +0 -4
  385. package/theme/README.md +0 -174
  386. package/theme/package.json +0 -4
  387. package/theme/provider/package.json +0 -4
  388. package/theme/services/package.json +0 -4
  389. package/theme/styles/foundation/components.css +0 -81
  390. package/theme/styles/foundation/layers.css +0 -15
  391. package/theme/styles/foundation/tokens.css +0 -55
  392. package/theme/styles/index.css +0 -37
  393. package/theme/styles/integrations/material/autocomplete.css +0 -178
  394. package/theme/styles/integrations/material/button.css +0 -468
  395. package/theme/styles/integrations/material/dialog.css +0 -152
  396. package/theme/styles/integrations/material/select.css +0 -175
  397. package/theme/styles/integrations/material/slide-toggle.css +0 -234
  398. package/theme/styles/integrations/material/slider.css +0 -194
  399. package/theme/styles/integrations/material/tabs.css +0 -229
  400. package/theme/styles/integrations/material.css +0 -264
  401. package/theme/styles/integrations/tailwind.css +0 -114
  402. package/theme/styles/variants/color/amber.css +0 -31
  403. package/theme/styles/variants/color/base.css +0 -36
  404. package/theme/styles/variants/color/blue.css +0 -31
  405. package/theme/styles/variants/color/cyan.css +0 -31
  406. package/theme/styles/variants/color/emerald.css +0 -31
  407. package/theme/styles/variants/color/fuchsia.css +0 -31
  408. package/theme/styles/variants/color/green.css +0 -31
  409. package/theme/styles/variants/color/index.css +0 -22
  410. package/theme/styles/variants/color/indigo.css +0 -31
  411. package/theme/styles/variants/color/lime.css +0 -31
  412. package/theme/styles/variants/color/orange.css +0 -31
  413. package/theme/styles/variants/color/pink.css +0 -31
  414. package/theme/styles/variants/color/purple.css +0 -31
  415. package/theme/styles/variants/color/red.css +0 -31
  416. package/theme/styles/variants/color/rose.css +0 -31
  417. package/theme/styles/variants/color/sky.css +0 -31
  418. package/theme/styles/variants/color/teal.css +0 -31
  419. package/theme/styles/variants/color/violet.css +0 -31
  420. package/theme/styles/variants/color/yellow.css +0 -31
  421. package/theme/styles/variants/mode/dark.css +0 -20
  422. package/theme/styles/variants/mode/index.css +0 -6
  423. package/theme/styles/variants/mode/light.css +0 -24
  424. package/theme/styles/variants/style/brutal.css +0 -50
  425. package/theme/styles/variants/style/default.css +0 -54
  426. package/theme/styles/variants/style/index.css +0 -8
  427. package/theme/styles/variants/style/sharp.css +0 -50
  428. package/theme/styles/variants/style/soft.css +0 -50
  429. package/theme/token/package.json +0 -4
  430. package/types/ojiepermana-angular-component-accordion.d.ts +0 -51
  431. package/types/ojiepermana-angular-component-alert-dialog.d.ts +0 -93
  432. package/types/ojiepermana-angular-component-alert.d.ts +0 -37
  433. package/types/ojiepermana-angular-component-aspect-ratio.d.ts +0 -12
  434. package/types/ojiepermana-angular-component-avatar.d.ts +0 -51
  435. package/types/ojiepermana-angular-component-badge.d.ts +0 -19
  436. package/types/ojiepermana-angular-component-breadcrumb.d.ts +0 -46
  437. package/types/ojiepermana-angular-component-button-group.d.ts +0 -26
  438. package/types/ojiepermana-angular-component-button.d.ts +0 -22
  439. package/types/ojiepermana-angular-component-calendar.d.ts +0 -33
  440. package/types/ojiepermana-angular-component-card.d.ts +0 -60
  441. package/types/ojiepermana-angular-component-carousel.d.ts +0 -86
  442. package/types/ojiepermana-angular-component-chart-area.d.ts +0 -1
  443. package/types/ojiepermana-angular-component-chart-bar.d.ts +0 -1
  444. package/types/ojiepermana-angular-component-chart-line.d.ts +0 -1
  445. package/types/ojiepermana-angular-component-chart-pie.d.ts +0 -1
  446. package/types/ojiepermana-angular-component-chart-radar.d.ts +0 -1
  447. package/types/ojiepermana-angular-component-chart-radial.d.ts +0 -1
  448. package/types/ojiepermana-angular-component-chart-scatter.d.ts +0 -1
  449. package/types/ojiepermana-angular-component-chart.d.ts +0 -1094
  450. package/types/ojiepermana-angular-component-checkbox.d.ts +0 -35
  451. package/types/ojiepermana-angular-component-collapsible.d.ts +0 -42
  452. package/types/ojiepermana-angular-component-combobox.d.ts +0 -50
  453. package/types/ojiepermana-angular-component-command.d.ts +0 -99
  454. package/types/ojiepermana-angular-component-composer.d.ts +0 -90
  455. package/types/ojiepermana-angular-component-context-menu.d.ts +0 -35
  456. package/types/ojiepermana-angular-component-date-picker.d.ts +0 -41
  457. package/types/ojiepermana-angular-component-dialog.d.ts +0 -87
  458. package/types/ojiepermana-angular-component-drawer.d.ts +0 -1
  459. package/types/ojiepermana-angular-component-dropdown-menu.d.ts +0 -137
  460. package/types/ojiepermana-angular-component-editor.d.ts +0 -123
  461. package/types/ojiepermana-angular-component-empty.d.ts +0 -50
  462. package/types/ojiepermana-angular-component-form.d.ts +0 -141
  463. package/types/ojiepermana-angular-component-hover-card.d.ts +0 -74
  464. package/types/ojiepermana-angular-component-input-group.d.ts +0 -51
  465. package/types/ojiepermana-angular-component-input-otp.d.ts +0 -136
  466. package/types/ojiepermana-angular-component-input.d.ts +0 -16
  467. package/types/ojiepermana-angular-component-item.d.ts +0 -88
  468. package/types/ojiepermana-angular-component-kanban.d.ts +0 -70
  469. package/types/ojiepermana-angular-component-kbd.d.ts +0 -16
  470. package/types/ojiepermana-angular-component-label.d.ts +0 -11
  471. package/types/ojiepermana-angular-component-menubar.d.ts +0 -67
  472. package/types/ojiepermana-angular-component-native-select.d.ts +0 -26
  473. package/types/ojiepermana-angular-component-navigation-menu.d.ts +0 -96
  474. package/types/ojiepermana-angular-component-pagination.d.ts +0 -33
  475. package/types/ojiepermana-angular-component-pillbox.d.ts +0 -157
  476. package/types/ojiepermana-angular-component-popover.d.ts +0 -43
  477. package/types/ojiepermana-angular-component-progress.d.ts +0 -17
  478. package/types/ojiepermana-angular-component-radio.d.ts +0 -40
  479. package/types/ojiepermana-angular-component-resizable.d.ts +0 -99
  480. package/types/ojiepermana-angular-component-scroll-area.d.ts +0 -19
  481. package/types/ojiepermana-angular-component-select.d.ts +0 -57
  482. package/types/ojiepermana-angular-component-separator.d.ts +0 -14
  483. package/types/ojiepermana-angular-component-sheet.d.ts +0 -76
  484. package/types/ojiepermana-angular-component-skeleton.d.ts +0 -10
  485. package/types/ojiepermana-angular-component-slider.d.ts +0 -74
  486. package/types/ojiepermana-angular-component-spinner.d.ts +0 -13
  487. package/types/ojiepermana-angular-component-switch.d.ts +0 -40
  488. package/types/ojiepermana-angular-component-table.d.ts +0 -52
  489. package/types/ojiepermana-angular-component-tabs.d.ts +0 -92
  490. package/types/ojiepermana-angular-component-textarea.d.ts +0 -12
  491. package/types/ojiepermana-angular-component-timeline.d.ts +0 -63
  492. package/types/ojiepermana-angular-component-toast.d.ts +0 -38
  493. package/types/ojiepermana-angular-component-toggle-group.d.ts +0 -89
  494. package/types/ojiepermana-angular-component-toggle.d.ts +0 -25
  495. package/types/ojiepermana-angular-component-tooltip.d.ts +0 -89
  496. package/types/ojiepermana-angular-component-utils.d.ts +0 -5
  497. package/types/ojiepermana-angular-generator-api.d.ts +0 -86
  498. package/types/ojiepermana-angular-layout-component.d.ts +0 -205
  499. package/types/ojiepermana-angular-layout-provider.d.ts +0 -6
  500. package/types/ojiepermana-angular-layout-services.d.ts +0 -25
  501. package/types/ojiepermana-angular-layout-shell.d.ts +0 -8
  502. package/types/ojiepermana-angular-layout-token-directive.d.ts +0 -13
  503. package/types/ojiepermana-angular-layout-token.d.ts +0 -36
  504. package/types/ojiepermana-angular-layout-type-empty.d.ts +0 -22
  505. package/types/ojiepermana-angular-layout-type-horizontal.d.ts +0 -36
  506. package/types/ojiepermana-angular-layout-type-vertical.d.ts +0 -38
  507. package/types/ojiepermana-angular-layout.d.ts +0 -164
  508. package/types/ojiepermana-angular-navigation-demo-data.d.ts +0 -5
  509. package/types/ojiepermana-angular-navigation-icon.d.ts +0 -17
  510. package/types/ojiepermana-angular-navigation-item.d.ts +0 -54
  511. package/types/ojiepermana-angular-navigation-service.d.ts +0 -77
  512. package/types/ojiepermana-angular-navigation-sidebar.d.ts +0 -75
  513. package/types/ojiepermana-angular-navigation-topbar.d.ts +0 -74
  514. package/types/ojiepermana-angular-navigation-types.d.ts +0 -135
  515. package/types/ojiepermana-angular-theme-provider.d.ts +0 -11
  516. package/types/ojiepermana-angular-theme-services.d.ts +0 -55
  517. package/types/ojiepermana-angular-theme-token.d.ts +0 -57
@@ -1,3925 +0,0 @@
1
- import * as i0 from '@angular/core';
2
- import { signal, computed, Injectable, inject, ElementRef, Renderer2, effect, ChangeDetectionStrategy, Component, NgZone, PLATFORM_ID, DestroyRef, input, afterNextRender, viewChild, Directive, contentChild, TemplateRef, output } from '@angular/core';
3
- import { isPlatformBrowser, NgTemplateOutlet, NgComponentOutlet } from '@angular/common';
4
- import { scaleBand, scaleLinear, scalePoint } from 'd3-scale';
5
- import { min, max, extent } from 'd3-array';
6
- import { stack, curveLinear, curveStep, curveMonotoneX, line, area, stackOffsetExpand, pie, arc, curveCardinalClosed, curveLinearClosed, lineRadial } from 'd3-shape';
7
-
8
- /** CSS selector under which a chart instance is scoped. */
9
- const CHART_DATA_ATTRIBUTE = 'data-chart';
10
- /** Default color schemes supported by the generated `<style>` block. */
11
- const CHART_THEMES = [
12
- { key: 'light', selector: '' },
13
- { key: 'dark', selector: '[data-mode="dark"]' },
14
- ];
15
- /**
16
- * Generate the CSS rule-set for a chart instance: one `--color-<key>` per
17
- * series, scoped to the owning `[data-chart]` element, with optional dark
18
- * variant via `[data-mode="dark"] [data-chart="…"]`.
19
- *
20
- * Series without any color are skipped (consumer can fall back to a default).
21
- *
22
- * @param chartId Unique chart id (used as attribute value).
23
- * @param config Series configuration map.
24
- */
25
- function buildChartCss(chartId, config) {
26
- const entries = Object.entries(config).filter(([, cfg]) => cfg.color || cfg.theme);
27
- if (entries.length === 0) {
28
- return '';
29
- }
30
- return CHART_THEMES.map(({ key, selector }) => {
31
- const vars = entries
32
- .map(([seriesKey, cfg]) => {
33
- const value = cfg.theme?.[key] ?? cfg.color;
34
- return value ? ` --color-${escapeCssIdent(seriesKey)}: ${value};` : '';
35
- })
36
- .filter(Boolean)
37
- .join('\n');
38
- if (!vars) {
39
- return '';
40
- }
41
- const scope = selector
42
- ? `${selector} [${CHART_DATA_ATTRIBUTE}="${chartId}"]`
43
- : `[${CHART_DATA_ATTRIBUTE}="${chartId}"]`;
44
- return `${scope} {\n${vars}\n}`;
45
- })
46
- .filter(Boolean)
47
- .join('\n');
48
- }
49
- /**
50
- * Escape a string so it is safe to use as a CSS custom-property identifier.
51
- * Allows `[A-Za-z0-9_-]`; everything else becomes `_`.
52
- */
53
- function escapeCssIdent(input) {
54
- return input.replace(/[^a-zA-Z0-9_-]/g, '_');
55
- }
56
- /** Resolve the `var(--color-<key>)` reference for a given series. */
57
- function seriesColorVar(seriesKey) {
58
- return `var(--color-${seriesKey.replace(/[^a-zA-Z0-9_-]/g, '_')})`;
59
- }
60
-
61
- /**
62
- * Shared chart state provided by `ChartContainer` to all nested chart
63
- * components (axes, grid, tooltip, legend, chart types).
64
- *
65
- * All state is exposed as signals so consumers can derive computed state
66
- * (scales, visible series, tooltip position) without manual subscriptions.
67
- */
68
- class ChartContext {
69
- /** Stable instance id — used in the `data-chart` attribute and CSS scope. */
70
- id = signal('', /* @ts-ignore */
71
- ...(ngDevMode ? [{ debugName: "id" }] : /* istanbul ignore next */ []));
72
- /** User-provided series config. */
73
- config = signal({}, /* @ts-ignore */
74
- ...(ngDevMode ? [{ debugName: "config" }] : /* istanbul ignore next */ []));
75
- /** Measured render-area dimensions (ResizeObserver-driven). */
76
- dimensions = signal({ width: 0, height: 0 }, /* @ts-ignore */
77
- ...(ngDevMode ? [{ debugName: "dimensions" }] : /* istanbul ignore next */ []));
78
- /** Currently highlighted data point (tooltip / crosshair). */
79
- activePoint = signal(null, /* @ts-ignore */
80
- ...(ngDevMode ? [{ debugName: "activePoint" }] : /* istanbul ignore next */ []));
81
- /** Series keys the user has toggled off via legend. */
82
- hiddenSeries = signal(new Set(), /* @ts-ignore */
83
- ...(ngDevMode ? [{ debugName: "hiddenSeries" }] : /* istanbul ignore next */ []));
84
- /** Ordered list of series keys (from `config`). */
85
- seriesKeys = computed(() => Object.keys(this.config()), /* @ts-ignore */
86
- ...(ngDevMode ? [{ debugName: "seriesKeys" }] : /* istanbul ignore next */ []));
87
- /** Series keys currently visible (config order minus `hiddenSeries`). */
88
- visibleSeriesKeys = computed(() => {
89
- const hidden = this.hiddenSeries();
90
- return this.seriesKeys().filter((k) => !hidden.has(k));
91
- }, /* @ts-ignore */
92
- ...(ngDevMode ? [{ debugName: "visibleSeriesKeys" }] : /* istanbul ignore next */ []));
93
- /** Toggle visibility of a series. */
94
- toggleSeries(key) {
95
- this.hiddenSeries.update((set) => {
96
- const next = new Set(set);
97
- if (next.has(key)) {
98
- next.delete(key);
99
- }
100
- else {
101
- next.add(key);
102
- }
103
- return next;
104
- });
105
- }
106
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.0", ngImport: i0, type: ChartContext, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
107
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.0", ngImport: i0, type: ChartContext });
108
- }
109
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.0", ngImport: i0, type: ChartContext, decorators: [{
110
- type: Injectable
111
- }] });
112
-
113
- /**
114
- * Emits a scoped `<style>` block mapping every series key in the current
115
- * `ChartConfig` to a `--color-<key>` CSS custom property, scoped to
116
- * `[data-chart="<id>"]`. Dark-mode values are scoped under `[data-mode="dark"]`.
117
- *
118
- * Implemented as an empty component that injects a real `<style>` element
119
- * into its host via `Renderer2`. We avoid rendering `<style>` directly in a
120
- * template — Angular hoists those into component CSS and strips
121
- * interpolations from them.
122
- *
123
- * Consumers never instantiate this directly — `ChartContainer` renders it.
124
- */
125
- class ChartStyle {
126
- ctx = inject(ChartContext);
127
- host = inject((ElementRef));
128
- renderer = inject(Renderer2);
129
- styleEl = null;
130
- css = computed(() => buildChartCss(this.ctx.id(), this.ctx.config()), /* @ts-ignore */
131
- ...(ngDevMode ? [{ debugName: "css" }] : /* istanbul ignore next */ []));
132
- constructor() {
133
- effect(() => {
134
- const css = this.css();
135
- if (!this.styleEl) {
136
- this.styleEl = this.renderer.createElement('style');
137
- this.renderer.appendChild(this.host.nativeElement, this.styleEl);
138
- }
139
- this.styleEl.textContent = css;
140
- });
141
- }
142
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.0", ngImport: i0, type: ChartStyle, deps: [], target: i0.ɵɵFactoryTarget.Component });
143
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "22.0.0", type: ChartStyle, isStandalone: true, selector: "ui-chart-style", ngImport: i0, template: '', isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
144
- }
145
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.0", ngImport: i0, type: ChartStyle, decorators: [{
146
- type: Component,
147
- args: [{
148
- selector: 'ui-chart-style',
149
- changeDetection: ChangeDetectionStrategy.OnPush,
150
- template: '',
151
- }]
152
- }], ctorParameters: () => [] });
153
-
154
- let chartIdCounter = 0;
155
- /**
156
- * Root of every chart. Provides `ChartContext` to descendants, reflects the
157
- * chart id via `data-chart`, injects the per-instance CSS-variable
158
- * `<style>` block, and tracks its render-area dimensions via
159
- * `ResizeObserver`.
160
- *
161
- * Usage:
162
- * ```html
163
- * <ui-chart-container [config]="cfg">
164
- * <ui-bar-chart [data]="data" />
165
- * </ui-chart-container>
166
- * ```
167
- */
168
- class ChartContainer {
169
- ctx = inject(ChartContext);
170
- host = inject((ElementRef));
171
- zone = inject(NgZone);
172
- platformId = inject(PLATFORM_ID);
173
- destroyRef = inject(DestroyRef);
174
- /** Series configuration. Required for color / label resolution. */
175
- config = input.required(/* @ts-ignore */
176
- ...(ngDevMode ? [{ debugName: "config" }] : /* istanbul ignore next */ []));
177
- /**
178
- * Tailwind aspect-ratio utility for the container. Defaults to `aspect-video`
179
- * for cartesian charts; override with `aspect-square` for radial / pie layouts.
180
- */
181
- aspect = input('aspect-video', /* @ts-ignore */
182
- ...(ngDevMode ? [{ debugName: "aspect" }] : /* istanbul ignore next */ []));
183
- hostClass = computed(() => `relative flex ${this.aspect()} justify-center text-xs`, /* @ts-ignore */
184
- ...(ngDevMode ? [{ debugName: "hostClass" }] : /* istanbul ignore next */ []));
185
- /**
186
- * Optional explicit id override. When omitted, a stable auto-id is
187
- * generated (`chart-<n>`), unique across the document.
188
- */
189
- chartId = input(null, /* @ts-ignore */
190
- ...(ngDevMode ? [{ debugName: "chartId" }] : /* istanbul ignore next */ []));
191
- constructor() {
192
- const autoId = `chart-${++chartIdCounter}`;
193
- // Sync id + config into the shared context.
194
- effect(() => {
195
- this.ctx.id.set(this.chartId() ?? autoId);
196
- });
197
- effect(() => {
198
- this.ctx.config.set(this.config());
199
- });
200
- // Observe host size (browser only; client-only is a confirmed constraint).
201
- if (isPlatformBrowser(this.platformId)) {
202
- afterNextRender(() => this.observeSize());
203
- }
204
- }
205
- observeSize() {
206
- const el = this.host.nativeElement;
207
- if (typeof ResizeObserver === 'undefined') {
208
- const rect = el.getBoundingClientRect();
209
- this.ctx.dimensions.set({ width: rect.width, height: rect.height });
210
- return;
211
- }
212
- const observer = new ResizeObserver((entries) => {
213
- for (const entry of entries) {
214
- const { width, height } = entry.contentRect;
215
- // Avoid NgZone churn — dimensions signal drives CD on its own.
216
- this.zone.run(() => this.ctx.dimensions.set({ width, height }));
217
- }
218
- });
219
- observer.observe(el);
220
- this.destroyRef.onDestroy(() => observer.disconnect());
221
- }
222
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.0", ngImport: i0, type: ChartContainer, deps: [], target: i0.ɵɵFactoryTarget.Component });
223
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.0.0", type: ChartContainer, isStandalone: true, selector: "ui-chart-container", inputs: { config: { classPropertyName: "config", publicName: "config", isSignal: true, isRequired: true, transformFunction: null }, aspect: { classPropertyName: "aspect", publicName: "aspect", isSignal: true, isRequired: false, transformFunction: null }, chartId: { classPropertyName: "chartId", publicName: "chartId", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "attr.data-chart": "ctx.id()", "class": "hostClass()" } }, providers: [ChartContext], ngImport: i0, template: `
224
- <ui-chart-style />
225
- <ng-content />
226
- `, isInline: true, dependencies: [{ kind: "component", type: ChartStyle, selector: "ui-chart-style" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
227
- }
228
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.0", ngImport: i0, type: ChartContainer, decorators: [{
229
- type: Component,
230
- args: [{
231
- selector: 'ui-chart-container',
232
- changeDetection: ChangeDetectionStrategy.OnPush,
233
- providers: [ChartContext],
234
- imports: [ChartStyle],
235
- host: {
236
- '[attr.data-chart]': 'ctx.id()',
237
- '[class]': 'hostClass()',
238
- },
239
- template: `
240
- <ui-chart-style />
241
- <ng-content />
242
- `,
243
- }]
244
- }], ctorParameters: () => [], propDecorators: { config: [{ type: i0.Input, args: [{ isSignal: true, alias: "config", required: true }] }], aspect: [{ type: i0.Input, args: [{ isSignal: true, alias: "aspect", required: false }] }], chartId: [{ type: i0.Input, args: [{ isSignal: true, alias: "chartId", required: false }] }] } });
245
-
246
- /**
247
- * Cartesian plotting frame shared between a chart type (Bar, Line, Area…)
248
- * and its axis / grid primitives.
249
- *
250
- * The owning chart component provides an instance and publishes its scales;
251
- * descendants read them via signals and re-render when dimensions or data
252
- * change.
253
- */
254
- class CartesianContext {
255
- /** Inner width (outer width − margin.left − margin.right). */
256
- innerWidth = signal(0, /* @ts-ignore */
257
- ...(ngDevMode ? [{ debugName: "innerWidth" }] : /* istanbul ignore next */ []));
258
- /** Inner height (outer height − margin.top − margin.bottom). */
259
- innerHeight = signal(0, /* @ts-ignore */
260
- ...(ngDevMode ? [{ debugName: "innerHeight" }] : /* istanbul ignore next */ []));
261
- /** Margins around the plotting area. */
262
- margin = signal({ top: 8, right: 8, bottom: 24, left: 40 }, /* @ts-ignore */
263
- ...(ngDevMode ? [{ debugName: "margin" }] : /* istanbul ignore next */ []));
264
- /** Layout orientation (drives which axis holds the band scale). */
265
- orientation = signal('vertical', /* @ts-ignore */
266
- ...(ngDevMode ? [{ debugName: "orientation" }] : /* istanbul ignore next */ []));
267
- /** Band (categorical) scale. */
268
- categoryScale = signal(null, /* @ts-ignore */
269
- ...(ngDevMode ? [{ debugName: "categoryScale" }] : /* istanbul ignore next */ []));
270
- /** Linear (numeric) scale. */
271
- valueScale = signal(null, /* @ts-ignore */
272
- ...(ngDevMode ? [{ debugName: "valueScale" }] : /* istanbul ignore next */ []));
273
- /** Ordered category domain (e.g. x labels for vertical, y labels for horizontal). */
274
- categories = signal([], /* @ts-ignore */
275
- ...(ngDevMode ? [{ debugName: "categories" }] : /* istanbul ignore next */ []));
276
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.0", ngImport: i0, type: CartesianContext, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
277
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.0", ngImport: i0, type: CartesianContext });
278
- }
279
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.0", ngImport: i0, type: CartesianContext, decorators: [{
280
- type: Injectable
281
- }] });
282
- /** Resolve the scale that maps to the X axis for a given orientation. */
283
- function xScale(ctx) {
284
- return ctx.orientation() === 'vertical' ? ctx.categoryScale : ctx.valueScale;
285
- }
286
- /** Resolve the scale that maps to the Y axis for a given orientation. */
287
- function yScale(ctx) {
288
- return ctx.orientation() === 'vertical' ? ctx.valueScale : ctx.categoryScale;
289
- }
290
-
291
- /** Produce evenly-spaced ticks for a band (categorical) scale. */
292
- function bandTicks(scale) {
293
- const half = scale.bandwidth() / 2;
294
- return scale.domain().map((value) => ({
295
- value,
296
- offset: (scale(value) ?? 0) + half,
297
- label: value,
298
- }));
299
- }
300
- /**
301
- * Produce ticks for a linear scale.
302
- *
303
- * @param scale The linear scale.
304
- * @param count Approximate number of ticks (hint, d3 may return fewer/more).
305
- * @param formatter Label formatter.
306
- */
307
- function linearTicks(scale, count = 5, formatter = (v) => String(v)) {
308
- return scale.ticks(count).map((value) => ({
309
- value,
310
- offset: scale(value),
311
- label: formatter(value),
312
- }));
313
- }
314
-
315
- function clamp$1(value, min, max) {
316
- return Math.min(max, Math.max(min, value));
317
- }
318
- function normalizeIndexRange(start, end, maxCount) {
319
- if (maxCount <= 0) {
320
- return null;
321
- }
322
- const lo = clamp$1(Math.floor(Math.min(start, end)), 0, maxCount - 1);
323
- const hi = clamp$1(Math.floor(Math.max(start, end)), 0, maxCount - 1);
324
- return { startIndex: lo, endIndex: hi };
325
- }
326
- function effectiveIndexRange(range, maxCount) {
327
- if (maxCount <= 0) {
328
- return null;
329
- }
330
- return range
331
- ? normalizeIndexRange(range.startIndex, range.endIndex, maxCount)
332
- : { startIndex: 0, endIndex: maxCount - 1 };
333
- }
334
- function indexRangeSize(range, maxCount) {
335
- const effective = effectiveIndexRange(range, maxCount);
336
- return effective ? effective.endIndex - effective.startIndex + 1 : 0;
337
- }
338
- function sliceByIndexRange(data, range) {
339
- if (!range) {
340
- return data;
341
- }
342
- return data.slice(range.startIndex, range.endIndex + 1);
343
- }
344
- function zoomIndexRange(current, maxCount, anchorIndex, factor) {
345
- const base = effectiveIndexRange(current, maxCount);
346
- if (!base) {
347
- return null;
348
- }
349
- const currentSize = base.endIndex - base.startIndex + 1;
350
- const nextSize = clamp$1(Math.round(currentSize * factor), 2, maxCount);
351
- if (nextSize >= maxCount) {
352
- return null;
353
- }
354
- const boundedAnchor = clamp$1(anchorIndex, base.startIndex, base.endIndex);
355
- const ratio = currentSize <= 1 ? 0.5 : (boundedAnchor - base.startIndex) / (currentSize - 1);
356
- let start = Math.round(boundedAnchor - ratio * (nextSize - 1));
357
- start = clamp$1(start, 0, maxCount - nextSize);
358
- return { startIndex: start, endIndex: start + nextSize - 1 };
359
- }
360
- function panIndexRange(current, maxCount, deltaSteps) {
361
- const base = effectiveIndexRange(current, maxCount);
362
- if (!base) {
363
- return null;
364
- }
365
- const size = base.endIndex - base.startIndex + 1;
366
- if (size >= maxCount) {
367
- return null;
368
- }
369
- const start = clamp$1(base.startIndex + deltaSteps, 0, maxCount - size);
370
- return { startIndex: start, endIndex: start + size - 1 };
371
- }
372
- function normalizeNumericDomain(a, b) {
373
- if (a === b) {
374
- return [a - 1, b + 1];
375
- }
376
- return a < b ? [a, b] : [b, a];
377
- }
378
- function zoomNumericDomain(current, full, anchor, factor) {
379
- const currentWidth = current[1] - current[0];
380
- const fullWidth = full[1] - full[0];
381
- const nextWidth = clamp$1(currentWidth * factor, fullWidth / 50, fullWidth);
382
- if (nextWidth >= fullWidth) {
383
- return full;
384
- }
385
- const ratio = currentWidth === 0 ? 0.5 : (anchor - current[0]) / currentWidth;
386
- let start = anchor - ratio * nextWidth;
387
- start = clamp$1(start, full[0], full[1] - nextWidth);
388
- return [start, start + nextWidth];
389
- }
390
- function panNumericDomain(current, full, delta) {
391
- const width = current[1] - current[0];
392
- const start = clamp$1(current[0] + delta, full[0], full[1] - width);
393
- return [start, start + width];
394
- }
395
-
396
- /**
397
- * X axis for a cartesian chart. Reads scales from `CartesianContext`.
398
- *
399
- * Renders as `<svg:g>` — must be placed inside the owning chart's SVG.
400
- */
401
- class ChartAxisX {
402
- ctx = inject(CartesianContext);
403
- /** Approximate tick count for linear (value) scales. */
404
- tickCount = input(5, /* @ts-ignore */
405
- ...(ngDevMode ? [{ debugName: "tickCount" }] : /* istanbul ignore next */ []));
406
- /** Show 6-px tick marks between the axis line and the labels. */
407
- tickLine = input(true, /* @ts-ignore */
408
- ...(ngDevMode ? [{ debugName: "tickLine" }] : /* istanbul ignore next */ []));
409
- /** Formatter for numeric tick labels. */
410
- tickFormat = input((v) => String(v), /* @ts-ignore */
411
- ...(ngDevMode ? [{ debugName: "tickFormat" }] : /* istanbul ignore next */ []));
412
- innerWidth = this.ctx.innerWidth;
413
- transform = computed(() => `translate(0,${this.ctx.innerHeight()})`, /* @ts-ignore */
414
- ...(ngDevMode ? [{ debugName: "transform" }] : /* istanbul ignore next */ []));
415
- ticks = computed(() => {
416
- const horizontal = this.ctx.orientation() === 'horizontal';
417
- if (horizontal) {
418
- const scale = this.ctx.valueScale();
419
- return scale ? linearTicks(scale, this.tickCount(), this.tickFormat()) : [];
420
- }
421
- const scale = this.ctx.categoryScale();
422
- return scale ? bandTicks(scale) : [];
423
- }, /* @ts-ignore */
424
- ...(ngDevMode ? [{ debugName: "ticks" }] : /* istanbul ignore next */ []));
425
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.0", ngImport: i0, type: ChartAxisX, deps: [], target: i0.ɵɵFactoryTarget.Component });
426
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.0", type: ChartAxisX, isStandalone: true, selector: "svg:g[ui-chart-axis-x]", inputs: { tickCount: { classPropertyName: "tickCount", publicName: "tickCount", isSignal: true, isRequired: false, transformFunction: null }, tickLine: { classPropertyName: "tickLine", publicName: "tickLine", isSignal: true, isRequired: false, transformFunction: null }, tickFormat: { classPropertyName: "tickFormat", publicName: "tickFormat", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "attr.transform": "transform()" }, classAttribute: "chart-axis chart-axis-x text-muted-foreground" }, ngImport: i0, template: `
427
- <svg:line class="stroke-border" [attr.x1]="0" [attr.x2]="innerWidth()" y1="0" y2="0" />
428
- @for (t of ticks(); track t.value) {
429
- <svg:g [attr.transform]="'translate(' + t.offset + ',0)'">
430
- @if (tickLine()) {
431
- <svg:line class="stroke-border" y1="0" y2="6" />
432
- }
433
- <svg:text
434
- class="fill-current"
435
- y="18"
436
- text-anchor="middle"
437
- style="font-size: var(--text-xs); font-family: var(--font-sans)">
438
- {{ t.label }}
439
- </svg:text>
440
- </svg:g>
441
- }
442
- `, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
443
- }
444
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.0", ngImport: i0, type: ChartAxisX, decorators: [{
445
- type: Component,
446
- args: [{
447
- selector: 'svg:g[ui-chart-axis-x]',
448
- changeDetection: ChangeDetectionStrategy.OnPush,
449
- host: {
450
- class: 'chart-axis chart-axis-x text-muted-foreground',
451
- '[attr.transform]': 'transform()',
452
- },
453
- template: `
454
- <svg:line class="stroke-border" [attr.x1]="0" [attr.x2]="innerWidth()" y1="0" y2="0" />
455
- @for (t of ticks(); track t.value) {
456
- <svg:g [attr.transform]="'translate(' + t.offset + ',0)'">
457
- @if (tickLine()) {
458
- <svg:line class="stroke-border" y1="0" y2="6" />
459
- }
460
- <svg:text
461
- class="fill-current"
462
- y="18"
463
- text-anchor="middle"
464
- style="font-size: var(--text-xs); font-family: var(--font-sans)">
465
- {{ t.label }}
466
- </svg:text>
467
- </svg:g>
468
- }
469
- `,
470
- }]
471
- }], propDecorators: { tickCount: [{ type: i0.Input, args: [{ isSignal: true, alias: "tickCount", required: false }] }], tickLine: [{ type: i0.Input, args: [{ isSignal: true, alias: "tickLine", required: false }] }], tickFormat: [{ type: i0.Input, args: [{ isSignal: true, alias: "tickFormat", required: false }] }] } });
472
-
473
- /**
474
- * Y axis for a cartesian chart. Reads scales from `CartesianContext`.
475
- *
476
- * Renders as `<svg:g>` — must be placed inside the owning chart's SVG.
477
- */
478
- class ChartAxisY {
479
- ctx = inject(CartesianContext);
480
- tickCount = input(5, /* @ts-ignore */
481
- ...(ngDevMode ? [{ debugName: "tickCount" }] : /* istanbul ignore next */ []));
482
- tickLine = input(true, /* @ts-ignore */
483
- ...(ngDevMode ? [{ debugName: "tickLine" }] : /* istanbul ignore next */ []));
484
- tickFormat = input((v) => String(v), /* @ts-ignore */
485
- ...(ngDevMode ? [{ debugName: "tickFormat" }] : /* istanbul ignore next */ []));
486
- innerHeight = this.ctx.innerHeight;
487
- ticks = computed(() => {
488
- const horizontal = this.ctx.orientation() === 'horizontal';
489
- if (horizontal) {
490
- const scale = this.ctx.categoryScale();
491
- return scale ? bandTicks(scale) : [];
492
- }
493
- const scale = this.ctx.valueScale();
494
- return scale ? linearTicks(scale, this.tickCount(), this.tickFormat()) : [];
495
- }, /* @ts-ignore */
496
- ...(ngDevMode ? [{ debugName: "ticks" }] : /* istanbul ignore next */ []));
497
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.0", ngImport: i0, type: ChartAxisY, deps: [], target: i0.ɵɵFactoryTarget.Component });
498
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.0", type: ChartAxisY, isStandalone: true, selector: "svg:g[ui-chart-axis-y]", inputs: { tickCount: { classPropertyName: "tickCount", publicName: "tickCount", isSignal: true, isRequired: false, transformFunction: null }, tickLine: { classPropertyName: "tickLine", publicName: "tickLine", isSignal: true, isRequired: false, transformFunction: null }, tickFormat: { classPropertyName: "tickFormat", publicName: "tickFormat", isSignal: true, isRequired: false, transformFunction: null } }, host: { classAttribute: "chart-axis chart-axis-y text-muted-foreground" }, ngImport: i0, template: `
499
- <svg:line class="stroke-border" x1="0" x2="0" [attr.y1]="0" [attr.y2]="innerHeight()" />
500
- @for (t of ticks(); track t.value) {
501
- <svg:g [attr.transform]="'translate(0,' + t.offset + ')'">
502
- @if (tickLine()) {
503
- <svg:line class="stroke-border" x1="-6" x2="0" />
504
- }
505
- <svg:text
506
- class="fill-current"
507
- x="-8"
508
- dy="0.32em"
509
- text-anchor="end"
510
- style="font-size: var(--text-xs); font-family: var(--font-sans)">
511
- {{ t.label }}
512
- </svg:text>
513
- </svg:g>
514
- }
515
- `, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
516
- }
517
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.0", ngImport: i0, type: ChartAxisY, decorators: [{
518
- type: Component,
519
- args: [{
520
- selector: 'svg:g[ui-chart-axis-y]',
521
- changeDetection: ChangeDetectionStrategy.OnPush,
522
- host: {
523
- class: 'chart-axis chart-axis-y text-muted-foreground',
524
- },
525
- template: `
526
- <svg:line class="stroke-border" x1="0" x2="0" [attr.y1]="0" [attr.y2]="innerHeight()" />
527
- @for (t of ticks(); track t.value) {
528
- <svg:g [attr.transform]="'translate(0,' + t.offset + ')'">
529
- @if (tickLine()) {
530
- <svg:line class="stroke-border" x1="-6" x2="0" />
531
- }
532
- <svg:text
533
- class="fill-current"
534
- x="-8"
535
- dy="0.32em"
536
- text-anchor="end"
537
- style="font-size: var(--text-xs); font-family: var(--font-sans)">
538
- {{ t.label }}
539
- </svg:text>
540
- </svg:g>
541
- }
542
- `,
543
- }]
544
- }], propDecorators: { tickCount: [{ type: i0.Input, args: [{ isSignal: true, alias: "tickCount", required: false }] }], tickLine: [{ type: i0.Input, args: [{ isSignal: true, alias: "tickLine", required: false }] }], tickFormat: [{ type: i0.Input, args: [{ isSignal: true, alias: "tickFormat", required: false }] }] } });
545
-
546
- /**
547
- * Horizontal / vertical grid lines for the cartesian plotting area.
548
- *
549
- * Reads tick positions from `CartesianContext.valueScale`. Direction of the
550
- * grid lines follows `orientation`:
551
- * - vertical → horizontal grid lines (one per y-tick)
552
- * - horizontal → vertical grid lines (one per x-tick)
553
- */
554
- class ChartGrid {
555
- ctx = inject(CartesianContext);
556
- tickCount = input(5, /* @ts-ignore */
557
- ...(ngDevMode ? [{ debugName: "tickCount" }] : /* istanbul ignore next */ []));
558
- ticks = computed(() => {
559
- const scale = this.ctx.valueScale();
560
- return scale ? linearTicks(scale, this.tickCount()) : [];
561
- }, /* @ts-ignore */
562
- ...(ngDevMode ? [{ debugName: "ticks" }] : /* istanbul ignore next */ []));
563
- line = (offset) => {
564
- if (this.ctx.orientation() === 'vertical') {
565
- return { x1: 0, x2: this.ctx.innerWidth(), y1: offset, y2: offset };
566
- }
567
- return { x1: offset, x2: offset, y1: 0, y2: this.ctx.innerHeight() };
568
- };
569
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.0", ngImport: i0, type: ChartGrid, deps: [], target: i0.ɵɵFactoryTarget.Component });
570
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.0", type: ChartGrid, isStandalone: true, selector: "svg:g[ui-chart-grid]", inputs: { tickCount: { classPropertyName: "tickCount", publicName: "tickCount", isSignal: true, isRequired: false, transformFunction: null } }, host: { classAttribute: "chart-grid text-border" }, ngImport: i0, template: `
571
- @for (t of ticks(); track t.value) {
572
- <svg:line
573
- class="stroke-border"
574
- stroke-dasharray="3 3"
575
- [attr.x1]="line(t.offset).x1"
576
- [attr.x2]="line(t.offset).x2"
577
- [attr.y1]="line(t.offset).y1"
578
- [attr.y2]="line(t.offset).y2" />
579
- }
580
- `, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
581
- }
582
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.0", ngImport: i0, type: ChartGrid, decorators: [{
583
- type: Component,
584
- args: [{
585
- selector: 'svg:g[ui-chart-grid]',
586
- changeDetection: ChangeDetectionStrategy.OnPush,
587
- host: {
588
- class: 'chart-grid text-border',
589
- },
590
- template: `
591
- @for (t of ticks(); track t.value) {
592
- <svg:line
593
- class="stroke-border"
594
- stroke-dasharray="3 3"
595
- [attr.x1]="line(t.offset).x1"
596
- [attr.x2]="line(t.offset).x2"
597
- [attr.y1]="line(t.offset).y1"
598
- [attr.y2]="line(t.offset).y2" />
599
- }
600
- `,
601
- }]
602
- }], propDecorators: { tickCount: [{ type: i0.Input, args: [{ isSignal: true, alias: "tickCount", required: false }] }] } });
603
-
604
- /**
605
- * Crosshair primitive — a line drawn through the currently active data point
606
- * perpendicular to the categorical axis. Reads `activePoint` from
607
- * `ChartContext` and the scales from `CartesianContext`.
608
- *
609
- * Place inside a cartesian chart's SVG inner group.
610
- */
611
- class ChartCrosshair {
612
- root = inject(ChartContext);
613
- cart = inject(CartesianContext);
614
- line = computed(() => {
615
- const active = this.root.activePoint();
616
- const scale = this.cart.categoryScale();
617
- const categories = this.cart.categories();
618
- if (!active || !scale || active.index < 0 || active.index >= categories.length) {
619
- return null;
620
- }
621
- const base = scale(categories[active.index]) ?? 0;
622
- const pos = base + scale.bandwidth() / 2;
623
- if (this.cart.orientation() === 'vertical') {
624
- return { x1: pos, x2: pos, y1: 0, y2: this.cart.innerHeight() };
625
- }
626
- return { x1: 0, x2: this.cart.innerWidth(), y1: pos, y2: pos };
627
- }, /* @ts-ignore */
628
- ...(ngDevMode ? [{ debugName: "line" }] : /* istanbul ignore next */ []));
629
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.0", ngImport: i0, type: ChartCrosshair, deps: [], target: i0.ɵɵFactoryTarget.Component });
630
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.0", type: ChartCrosshair, isStandalone: true, selector: "svg:g[ui-chart-crosshair]", host: { classAttribute: "chart-crosshair" }, ngImport: i0, template: `
631
- @if (line(); as l) {
632
- <svg:line
633
- class="stroke-border"
634
- stroke-dasharray="3 3"
635
- [attr.x1]="l.x1"
636
- [attr.x2]="l.x2"
637
- [attr.y1]="l.y1"
638
- [attr.y2]="l.y2" />
639
- }
640
- `, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
641
- }
642
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.0", ngImport: i0, type: ChartCrosshair, decorators: [{
643
- type: Component,
644
- args: [{
645
- selector: 'svg:g[ui-chart-crosshair]',
646
- changeDetection: ChangeDetectionStrategy.OnPush,
647
- host: { class: 'chart-crosshair' },
648
- template: `
649
- @if (line(); as l) {
650
- <svg:line
651
- class="stroke-border"
652
- stroke-dasharray="3 3"
653
- [attr.x1]="l.x1"
654
- [attr.x2]="l.x2"
655
- [attr.y1]="l.y1"
656
- [attr.y2]="l.y2" />
657
- }
658
- `,
659
- }]
660
- }] });
661
-
662
- class CategoricalViewportContext {
663
- dataCount = signal(0, /* @ts-ignore */
664
- ...(ngDevMode ? [{ debugName: "dataCount" }] : /* istanbul ignore next */ []));
665
- brushRange = signal(null, /* @ts-ignore */
666
- ...(ngDevMode ? [{ debugName: "brushRange" }] : /* istanbul ignore next */ []));
667
- zoomRange = signal(null, /* @ts-ignore */
668
- ...(ngDevMode ? [{ debugName: "zoomRange" }] : /* istanbul ignore next */ []));
669
- hasZoom = computed(() => {
670
- const range = this.zoomRange();
671
- const count = this.dataCount();
672
- return !!range && count > 0 && (range.startIndex > 0 || range.endIndex < count - 1);
673
- }, /* @ts-ignore */
674
- ...(ngDevMode ? [{ debugName: "hasZoom" }] : /* istanbul ignore next */ []));
675
- resetZoom() {
676
- this.brushRange.set(null);
677
- this.zoomRange.set(null);
678
- }
679
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.0", ngImport: i0, type: CategoricalViewportContext, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
680
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.0", ngImport: i0, type: CategoricalViewportContext });
681
- }
682
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.0", ngImport: i0, type: CategoricalViewportContext, decorators: [{
683
- type: Injectable
684
- }] });
685
-
686
- class ScatterViewportContext {
687
- innerWidth = signal(0, /* @ts-ignore */
688
- ...(ngDevMode ? [{ debugName: "innerWidth" }] : /* istanbul ignore next */ []));
689
- innerHeight = signal(0, /* @ts-ignore */
690
- ...(ngDevMode ? [{ debugName: "innerHeight" }] : /* istanbul ignore next */ []));
691
- fullXDomain = signal(null, /* @ts-ignore */
692
- ...(ngDevMode ? [{ debugName: "fullXDomain" }] : /* istanbul ignore next */ []));
693
- fullYDomain = signal(null, /* @ts-ignore */
694
- ...(ngDevMode ? [{ debugName: "fullYDomain" }] : /* istanbul ignore next */ []));
695
- zoomXDomain = signal(null, /* @ts-ignore */
696
- ...(ngDevMode ? [{ debugName: "zoomXDomain" }] : /* istanbul ignore next */ []));
697
- zoomYDomain = signal(null, /* @ts-ignore */
698
- ...(ngDevMode ? [{ debugName: "zoomYDomain" }] : /* istanbul ignore next */ []));
699
- xScale = signal(null, /* @ts-ignore */
700
- ...(ngDevMode ? [{ debugName: "xScale" }] : /* istanbul ignore next */ []));
701
- yScale = signal(null, /* @ts-ignore */
702
- ...(ngDevMode ? [{ debugName: "yScale" }] : /* istanbul ignore next */ []));
703
- hasZoom = computed(() => this.zoomXDomain() !== null || this.zoomYDomain() !== null, /* @ts-ignore */
704
- ...(ngDevMode ? [{ debugName: "hasZoom" }] : /* istanbul ignore next */ []));
705
- resetZoom() {
706
- this.zoomXDomain.set(null);
707
- this.zoomYDomain.set(null);
708
- }
709
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.0", ngImport: i0, type: ScatterViewportContext, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
710
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.0", ngImport: i0, type: ScatterViewportContext });
711
- }
712
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.0", ngImport: i0, type: ScatterViewportContext, decorators: [{
713
- type: Injectable
714
- }] });
715
-
716
- /**
717
- * Given a pointer event's local (x, y) relative to the chart's inner group,
718
- * resolve the nearest category index along the categorical axis.
719
- *
720
- * @returns index into `ctx.categories()` (or −1 if no scale / no data).
721
- */
722
- function nearestCategoryIndex(ctx, localX, localY) {
723
- const scale = ctx.categoryScale();
724
- const categories = ctx.categories();
725
- if (!scale || categories.length === 0)
726
- return -1;
727
- const isVertical = ctx.orientation() === 'vertical';
728
- const pointerAlong = isVertical ? localX : localY;
729
- const bandwidth = scale.bandwidth();
730
- // scale.step() is only defined for band scales; fall back to bandwidth.
731
- const step = scale.step?.() ?? bandwidth;
732
- let bestIndex = -1;
733
- let bestDelta = Infinity;
734
- for (let i = 0; i < categories.length; i++) {
735
- const base = scale(categories[i]) ?? 0;
736
- const center = base + bandwidth / 2;
737
- const delta = Math.abs(pointerAlong - center);
738
- if (delta < bestDelta) {
739
- bestDelta = delta;
740
- bestIndex = i;
741
- }
742
- }
743
- // Ignore clicks far outside any band (> 1 step away).
744
- if (bestDelta > step) {
745
- return -1;
746
- }
747
- return bestIndex;
748
- }
749
- /** Resolve the client-space center point of a focused or clicked SVG/HTML element. */
750
- function elementClientCenter(target) {
751
- const el = target;
752
- if (!el || typeof el.getBoundingClientRect !== 'function') {
753
- return null;
754
- }
755
- const rect = el.getBoundingClientRect();
756
- return {
757
- clientX: rect.left + rect.width / 2,
758
- clientY: rect.top + rect.height / 2,
759
- };
760
- }
761
-
762
- function clamp(value, min, max) {
763
- return Math.min(max, Math.max(min, value));
764
- }
765
- function sameDomain(a, b) {
766
- return Math.abs(a[0] - b[0]) < 1e-9 && Math.abs(a[1] - b[1]) < 1e-9;
767
- }
768
- class ChartBrush {
769
- hitbox = viewChild.required('hitbox');
770
- cart = inject(CartesianContext, { optional: true });
771
- categorical = inject(CategoricalViewportContext, { optional: true });
772
- scatter = inject(ScatterViewportContext, { optional: true });
773
- mode = signal(null, /* @ts-ignore */
774
- ...(ngDevMode ? [{ debugName: "mode" }] : /* istanbul ignore next */ []));
775
- activePointerId = signal(null, /* @ts-ignore */
776
- ...(ngDevMode ? [{ debugName: "activePointerId" }] : /* istanbul ignore next */ []));
777
- categoryStartIndex = signal(null, /* @ts-ignore */
778
- ...(ngDevMode ? [{ debugName: "categoryStartIndex" }] : /* istanbul ignore next */ []));
779
- categoryPanStart = signal(null, /* @ts-ignore */
780
- ...(ngDevMode ? [{ debugName: "categoryPanStart" }] : /* istanbul ignore next */ []));
781
- scatterBrush = signal(null, /* @ts-ignore */
782
- ...(ngDevMode ? [{ debugName: "scatterBrush" }] : /* istanbul ignore next */ []));
783
- scatterPan = signal(null, /* @ts-ignore */
784
- ...(ngDevMode ? [{ debugName: "scatterPan" }] : /* istanbul ignore next */ []));
785
- width = computed(() => this.scatter?.innerWidth() ?? this.cart?.innerWidth() ?? 0, /* @ts-ignore */
786
- ...(ngDevMode ? [{ debugName: "width" }] : /* istanbul ignore next */ []));
787
- height = computed(() => this.scatter?.innerHeight() ?? this.cart?.innerHeight() ?? 0, /* @ts-ignore */
788
- ...(ngDevMode ? [{ debugName: "height" }] : /* istanbul ignore next */ []));
789
- categoryPreview = computed(() => {
790
- const cart = this.cart;
791
- const viewport = this.categorical;
792
- const range = viewport?.brushRange();
793
- const scale = cart?.categoryScale();
794
- const categories = cart?.categories() ?? [];
795
- if (!cart || !viewport || !range || !scale || categories.length === 0) {
796
- return null;
797
- }
798
- const visibleStart = viewport.zoomRange()?.startIndex ?? 0;
799
- const startVisible = range.startIndex - visibleStart;
800
- const endVisible = range.endIndex - visibleStart;
801
- if (startVisible < 0 || endVisible >= categories.length) {
802
- return null;
803
- }
804
- const first = scale(categories[startVisible]) ?? 0;
805
- const last = (scale(categories[endVisible]) ?? 0) + scale.bandwidth();
806
- if (cart.orientation() === 'vertical') {
807
- return { x: Math.min(first, last), y: 0, width: Math.abs(last - first), height: cart.innerHeight() };
808
- }
809
- return { x: 0, y: Math.min(first, last), width: cart.innerWidth(), height: Math.abs(last - first) };
810
- }, /* @ts-ignore */
811
- ...(ngDevMode ? [{ debugName: "categoryPreview" }] : /* istanbul ignore next */ []));
812
- scatterPreviewRect = computed(() => {
813
- const preview = this.scatterBrush();
814
- if (!preview) {
815
- return null;
816
- }
817
- return {
818
- x: Math.min(preview.start.x, preview.current.x),
819
- y: Math.min(preview.start.y, preview.current.y),
820
- width: Math.abs(preview.current.x - preview.start.x),
821
- height: Math.abs(preview.current.y - preview.start.y),
822
- };
823
- }, /* @ts-ignore */
824
- ...(ngDevMode ? [{ debugName: "scatterPreviewRect" }] : /* istanbul ignore next */ []));
825
- onPointerDown(event) {
826
- if (this.activePointerId() != null) {
827
- return;
828
- }
829
- const local = this.localPoint(event);
830
- if (!local) {
831
- return;
832
- }
833
- if (this.scatter) {
834
- if (!this.scatter.fullXDomain() || !this.scatter.fullYDomain()) {
835
- return;
836
- }
837
- event.preventDefault();
838
- this.activePointerId.set(event.pointerId);
839
- this.hitbox().nativeElement.setPointerCapture(event.pointerId);
840
- this.onScatterPointerDown(event, local);
841
- return;
842
- }
843
- if (!this.cart || !this.categorical) {
844
- return;
845
- }
846
- const index = nearestCategoryIndex({
847
- categoryScale: this.cart.categoryScale,
848
- categories: this.cart.categories,
849
- orientation: this.cart.orientation,
850
- }, local.x, local.y);
851
- if (index < 0) {
852
- return;
853
- }
854
- event.preventDefault();
855
- this.activePointerId.set(event.pointerId);
856
- this.hitbox().nativeElement.setPointerCapture(event.pointerId);
857
- const visibleStart = this.categorical.zoomRange()?.startIndex ?? 0;
858
- const absoluteIndex = visibleStart + index;
859
- if (event.pointerType === 'touch' && this.categorical.hasZoom()) {
860
- this.mode.set('category-pan');
861
- this.categoryPanStart.set({ coord: this.pointerAxis(local), range: this.categorical.zoomRange() });
862
- return;
863
- }
864
- this.mode.set('category-brush');
865
- this.categoryStartIndex.set(absoluteIndex);
866
- this.categorical.brushRange.set({ startIndex: absoluteIndex, endIndex: absoluteIndex });
867
- }
868
- onPointerMove(event) {
869
- if (this.activePointerId() !== event.pointerId) {
870
- return;
871
- }
872
- const local = this.localPoint(event);
873
- if (!local) {
874
- return;
875
- }
876
- if (this.mode() === 'category-brush' && this.cart && this.categorical) {
877
- const startIndex = this.categoryStartIndex();
878
- if (startIndex == null) {
879
- return;
880
- }
881
- const index = nearestCategoryIndex({
882
- categoryScale: this.cart.categoryScale,
883
- categories: this.cart.categories,
884
- orientation: this.cart.orientation,
885
- }, local.x, local.y);
886
- if (index < 0) {
887
- return;
888
- }
889
- const visibleStart = this.categorical.zoomRange()?.startIndex ?? 0;
890
- this.categorical.brushRange.set(normalizeIndexRange(startIndex, visibleStart + index, this.categorical.dataCount()));
891
- return;
892
- }
893
- if (this.mode() === 'category-pan' && this.cart && this.categorical) {
894
- const pan = this.categoryPanStart();
895
- if (!pan) {
896
- return;
897
- }
898
- const scale = this.cart.categoryScale();
899
- const step = scale?.step?.() ?? scale?.bandwidth() ?? 0;
900
- if (step <= 0) {
901
- return;
902
- }
903
- const deltaSteps = Math.round((pan.coord - this.pointerAxis(local)) / step);
904
- this.categorical.zoomRange.set(panIndexRange(pan.range, this.categorical.dataCount(), deltaSteps));
905
- return;
906
- }
907
- if (this.mode() === 'scatter-brush') {
908
- const preview = this.scatterBrush();
909
- if (!preview) {
910
- return;
911
- }
912
- this.scatterBrush.set({ start: preview.start, current: local });
913
- return;
914
- }
915
- if (this.mode() === 'scatter-pan' && this.scatter) {
916
- const pan = this.scatterPan();
917
- const xScale = this.scatter.xScale();
918
- const yScale = this.scatter.yScale();
919
- const fullX = this.scatter.fullXDomain();
920
- const fullY = this.scatter.fullYDomain();
921
- if (!pan || !xScale || !yScale || !fullX || !fullY) {
922
- return;
923
- }
924
- const deltaX = xScale.invert(pan.start.x) - xScale.invert(local.x);
925
- const deltaY = yScale.invert(pan.start.y) - yScale.invert(local.y);
926
- this.scatter.zoomXDomain.set(panNumericDomain(pan.xDomain, fullX, deltaX));
927
- this.scatter.zoomYDomain.set(panNumericDomain(pan.yDomain, fullY, deltaY));
928
- }
929
- }
930
- onPointerUp(event) {
931
- if (event && this.activePointerId() !== event.pointerId) {
932
- return;
933
- }
934
- if (this.mode() === 'category-brush' && this.categorical) {
935
- const range = this.categorical.brushRange();
936
- if (range && indexRangeSize(range, this.categorical.dataCount()) > 1) {
937
- this.categorical.zoomRange.set(range);
938
- }
939
- this.categorical.brushRange.set(null);
940
- }
941
- if (this.mode() === 'scatter-brush' && this.scatter) {
942
- const preview = this.scatterBrush();
943
- const xScale = this.scatter.xScale();
944
- const yScale = this.scatter.yScale();
945
- const fullX = this.scatter.fullXDomain();
946
- const fullY = this.scatter.fullYDomain();
947
- if (preview && xScale && yScale && fullX && fullY) {
948
- const width = Math.abs(preview.current.x - preview.start.x);
949
- const height = Math.abs(preview.current.y - preview.start.y);
950
- if (width >= 6 && height >= 6) {
951
- const nextX = normalizeNumericDomain(xScale.invert(preview.start.x), xScale.invert(preview.current.x));
952
- const nextY = normalizeNumericDomain(yScale.invert(preview.start.y), yScale.invert(preview.current.y));
953
- this.scatter.zoomXDomain.set(sameDomain(nextX, fullX) ? null : nextX);
954
- this.scatter.zoomYDomain.set(sameDomain(nextY, fullY) ? null : nextY);
955
- }
956
- }
957
- this.scatterBrush.set(null);
958
- }
959
- this.mode.set(null);
960
- this.categoryStartIndex.set(null);
961
- this.categoryPanStart.set(null);
962
- this.scatterPan.set(null);
963
- if (event && this.hitbox().nativeElement.hasPointerCapture(event.pointerId)) {
964
- this.hitbox().nativeElement.releasePointerCapture(event.pointerId);
965
- }
966
- this.activePointerId.set(null);
967
- }
968
- onPointerCancel(event) {
969
- if (this.activePointerId() !== event.pointerId) {
970
- return;
971
- }
972
- if (this.hitbox().nativeElement.hasPointerCapture(event.pointerId)) {
973
- this.hitbox().nativeElement.releasePointerCapture(event.pointerId);
974
- }
975
- this.mode.set(null);
976
- this.categorical?.brushRange.set(null);
977
- this.scatterBrush.set(null);
978
- this.categoryStartIndex.set(null);
979
- this.categoryPanStart.set(null);
980
- this.scatterPan.set(null);
981
- this.activePointerId.set(null);
982
- }
983
- onWheel(event) {
984
- const local = this.localPoint(event);
985
- if (!local) {
986
- return;
987
- }
988
- event.preventDefault();
989
- if (this.scatter) {
990
- const xScale = this.scatter.xScale();
991
- const yScale = this.scatter.yScale();
992
- const fullX = this.scatter.fullXDomain();
993
- const fullY = this.scatter.fullYDomain();
994
- if (!xScale || !yScale || !fullX || !fullY) {
995
- return;
996
- }
997
- const factor = event.deltaY < 0 ? 0.8 : 1.25;
998
- const currentX = this.scatter.zoomXDomain() ?? fullX;
999
- const currentY = this.scatter.zoomYDomain() ?? fullY;
1000
- const nextX = zoomNumericDomain(currentX, fullX, xScale.invert(local.x), factor);
1001
- const nextY = zoomNumericDomain(currentY, fullY, yScale.invert(local.y), factor);
1002
- this.scatter.zoomXDomain.set(sameDomain(nextX, fullX) ? null : nextX);
1003
- this.scatter.zoomYDomain.set(sameDomain(nextY, fullY) ? null : nextY);
1004
- return;
1005
- }
1006
- if (!this.cart || !this.categorical) {
1007
- return;
1008
- }
1009
- const visibleIndex = nearestCategoryIndex({
1010
- categoryScale: this.cart.categoryScale,
1011
- categories: this.cart.categories,
1012
- orientation: this.cart.orientation,
1013
- }, local.x, local.y);
1014
- if (visibleIndex < 0) {
1015
- return;
1016
- }
1017
- const anchor = (this.categorical.zoomRange()?.startIndex ?? 0) + visibleIndex;
1018
- const factor = event.deltaY < 0 ? 0.75 : 1.25;
1019
- this.categorical.zoomRange.set(zoomIndexRange(this.categorical.zoomRange(), this.categorical.dataCount(), anchor, factor));
1020
- this.categorical.brushRange.set(null);
1021
- }
1022
- resetZoom() {
1023
- this.categorical?.resetZoom();
1024
- this.scatter?.resetZoom();
1025
- this.scatterBrush.set(null);
1026
- }
1027
- onScatterPointerDown(event, local) {
1028
- const fullX = this.scatter?.fullXDomain();
1029
- const fullY = this.scatter?.fullYDomain();
1030
- if (!this.scatter || !fullX || !fullY) {
1031
- return;
1032
- }
1033
- if (event.pointerType === 'touch' && this.scatter.hasZoom()) {
1034
- this.mode.set('scatter-pan');
1035
- this.scatterPan.set({
1036
- start: local,
1037
- xDomain: this.scatter.zoomXDomain() ?? fullX,
1038
- yDomain: this.scatter.zoomYDomain() ?? fullY,
1039
- });
1040
- return;
1041
- }
1042
- this.mode.set('scatter-brush');
1043
- this.scatterBrush.set({ start: local, current: local });
1044
- }
1045
- localPoint(event) {
1046
- const hitbox = this.hitbox()?.nativeElement;
1047
- const width = this.width();
1048
- const height = this.height();
1049
- if (!hitbox || width <= 0 || height <= 0) {
1050
- return null;
1051
- }
1052
- const rect = hitbox.getBoundingClientRect();
1053
- if (rect.width <= 0 || rect.height <= 0) {
1054
- return null;
1055
- }
1056
- return {
1057
- x: clamp(((event.clientX - rect.left) / rect.width) * width, 0, width),
1058
- y: clamp(((event.clientY - rect.top) / rect.height) * height, 0, height),
1059
- };
1060
- }
1061
- pointerAxis(local) {
1062
- return this.cart?.orientation() === 'horizontal' ? local.y : local.x;
1063
- }
1064
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.0", ngImport: i0, type: ChartBrush, deps: [], target: i0.ɵɵFactoryTarget.Component });
1065
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.0", type: ChartBrush, isStandalone: true, selector: "svg:g[ui-chart-brush]", host: { listeners: { "window:pointermove": "onPointerMove($event)", "window:pointerup": "onPointerUp($event)", "window:pointercancel": "onPointerCancel($event)" }, classAttribute: "chart-brush" }, viewQueries: [{ propertyName: "hitbox", first: true, predicate: ["hitbox"], descendants: true, isSignal: true }], ngImport: i0, template: `
1066
- <svg:rect
1067
- #hitbox
1068
- class="fill-transparent touch-none"
1069
- x="0"
1070
- y="0"
1071
- [attr.width]="width()"
1072
- [attr.height]="height()"
1073
- (pointerdown)="onPointerDown($event)"
1074
- (pointermove)="onPointerMove($event)"
1075
- (pointerup)="onPointerUp($event)"
1076
- (pointercancel)="onPointerCancel($event)"
1077
- (wheel)="onWheel($event)"
1078
- (dblclick)="resetZoom()" />
1079
-
1080
- @if (categoryPreview(); as rect) {
1081
- <svg:rect
1082
- class="fill-foreground/10 stroke-foreground/30"
1083
- [attr.x]="rect.x"
1084
- [attr.y]="rect.y"
1085
- [attr.width]="rect.width"
1086
- [attr.height]="rect.height"
1087
- stroke-dasharray="4 3" />
1088
- }
1089
-
1090
- @if (scatterPreviewRect(); as rect) {
1091
- <svg:rect
1092
- class="fill-foreground/10 stroke-foreground/30"
1093
- [attr.x]="rect.x"
1094
- [attr.y]="rect.y"
1095
- [attr.width]="rect.width"
1096
- [attr.height]="rect.height"
1097
- stroke-dasharray="4 3" />
1098
- }
1099
- `, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
1100
- }
1101
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.0", ngImport: i0, type: ChartBrush, decorators: [{
1102
- type: Component,
1103
- args: [{
1104
- selector: 'svg:g[ui-chart-brush]',
1105
- changeDetection: ChangeDetectionStrategy.OnPush,
1106
- host: {
1107
- class: 'chart-brush',
1108
- '(window:pointermove)': 'onPointerMove($event)',
1109
- '(window:pointerup)': 'onPointerUp($event)',
1110
- '(window:pointercancel)': 'onPointerCancel($event)',
1111
- },
1112
- template: `
1113
- <svg:rect
1114
- #hitbox
1115
- class="fill-transparent touch-none"
1116
- x="0"
1117
- y="0"
1118
- [attr.width]="width()"
1119
- [attr.height]="height()"
1120
- (pointerdown)="onPointerDown($event)"
1121
- (pointermove)="onPointerMove($event)"
1122
- (pointerup)="onPointerUp($event)"
1123
- (pointercancel)="onPointerCancel($event)"
1124
- (wheel)="onWheel($event)"
1125
- (dblclick)="resetZoom()" />
1126
-
1127
- @if (categoryPreview(); as rect) {
1128
- <svg:rect
1129
- class="fill-foreground/10 stroke-foreground/30"
1130
- [attr.x]="rect.x"
1131
- [attr.y]="rect.y"
1132
- [attr.width]="rect.width"
1133
- [attr.height]="rect.height"
1134
- stroke-dasharray="4 3" />
1135
- }
1136
-
1137
- @if (scatterPreviewRect(); as rect) {
1138
- <svg:rect
1139
- class="fill-foreground/10 stroke-foreground/30"
1140
- [attr.x]="rect.x"
1141
- [attr.y]="rect.y"
1142
- [attr.width]="rect.width"
1143
- [attr.height]="rect.height"
1144
- stroke-dasharray="4 3" />
1145
- }
1146
- `,
1147
- }]
1148
- }], propDecorators: { hitbox: [{ type: i0.ViewChild, args: ['hitbox', { isSignal: true }] }] } });
1149
-
1150
- /**
1151
- * Attach to a chart's `<svg:svg>` to publish pointer-driven active-point
1152
- * state into the surrounding `ChartContext`.
1153
- *
1154
- * Computes the local (x, y) of the pointer relative to the inner plotting
1155
- * area, resolves the nearest category, and updates
1156
- * `ChartContext.activePoint`. Clears it on `pointerleave`.
1157
- */
1158
- class ChartPointerTracker {
1159
- root = inject(ChartContext);
1160
- cart = inject(CartesianContext);
1161
- viewport = inject(CategoricalViewportContext, { optional: true });
1162
- onMove(event) {
1163
- const target = event.currentTarget;
1164
- if (!target)
1165
- return;
1166
- const rect = target.getBoundingClientRect();
1167
- if (rect.width === 0 || rect.height === 0)
1168
- return;
1169
- const { width, height } = this.root.dimensions();
1170
- // Map client coords → viewBox coords (SVG uses `0 0 width height`,
1171
- // preserveAspectRatio="none" so axes scale independently).
1172
- const scaleX = width / rect.width;
1173
- const scaleY = height / rect.height;
1174
- const viewX = (event.clientX - rect.left) * scaleX;
1175
- const viewY = (event.clientY - rect.top) * scaleY;
1176
- const margin = this.cart.margin();
1177
- const localX = viewX - margin.left;
1178
- const localY = viewY - margin.top;
1179
- const index = nearestCategoryIndex({
1180
- categoryScale: this.cart.categoryScale,
1181
- categories: this.cart.categories,
1182
- orientation: this.cart.orientation,
1183
- }, localX, localY);
1184
- if (index < 0) {
1185
- this.root.activePoint.set(null);
1186
- return;
1187
- }
1188
- this.root.activePoint.set({
1189
- index,
1190
- datumIndex: (this.viewport?.zoomRange()?.startIndex ?? 0) + index,
1191
- clientX: event.clientX,
1192
- clientY: event.clientY,
1193
- });
1194
- }
1195
- onLeave() {
1196
- this.root.activePoint.set(null);
1197
- }
1198
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.0", ngImport: i0, type: ChartPointerTracker, deps: [], target: i0.ɵɵFactoryTarget.Directive });
1199
- static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.0.0", type: ChartPointerTracker, isStandalone: true, selector: "svg:svg[uiChartPointerTracker]", host: { listeners: { "pointermove": "onMove($event)", "pointerleave": "onLeave()" } }, ngImport: i0 });
1200
- }
1201
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.0", ngImport: i0, type: ChartPointerTracker, decorators: [{
1202
- type: Directive,
1203
- args: [{
1204
- selector: 'svg:svg[uiChartPointerTracker]',
1205
- host: {
1206
- '(pointermove)': 'onMove($event)',
1207
- '(pointerleave)': 'onLeave()',
1208
- },
1209
- }]
1210
- }] });
1211
-
1212
- /** Locate the chart container DOM in order to position the tooltip. */
1213
- function containerRect(el) {
1214
- // Climb to the nearest `[data-chart]` (the ChartContainer host).
1215
- let node = el;
1216
- while (node && !node.hasAttribute('data-chart')) {
1217
- node = node.parentElement;
1218
- }
1219
- return node?.getBoundingClientRect() ?? null;
1220
- }
1221
- /**
1222
- * Tooltip overlay — renders the default tooltip card (or a user-supplied
1223
- * template) anchored to the currently active data point.
1224
- *
1225
- * Shadcn-compatible knobs:
1226
- * - `indicator="line"` / `"none"` / `"dashed"` — swap the row glyph
1227
- * - `hideLabel` — drop the header row
1228
- * - `label="Activities"` — override the header text
1229
- * - `labelKey="activities"` — resolve the header from `config[labelKey].label`
1230
- * - `labelFormatter` — transform the header (e.g. ISO date → long date)
1231
- * - `formatter` — format per-row values (e.g. `"380 kcal"`)
1232
- * - `valueKey` — for pie/radial/radar, read row value from a single field
1233
- * - Icons are picked up automatically from `config[key].icon`.
1234
- */
1235
- class ChartTooltip {
1236
- root = inject(ChartContext);
1237
- host = inject((ElementRef));
1238
- /** Data key on each datum whose value is the category label (x-axis). */
1239
- xKey = input(null, /* @ts-ignore */
1240
- ...(ngDevMode ? [{ debugName: "xKey" }] : /* istanbul ignore next */ []));
1241
- /** Data source (optional — if omitted tooltip reads from chart data via activePoint.datumIndex). */
1242
- data = input(null, /* @ts-ignore */
1243
- ...(ngDevMode ? [{ debugName: "data" }] : /* istanbul ignore next */ []));
1244
- /**
1245
- * Optional key for per-datum value lookup (pie / radial / radar datasets
1246
- * store values on a single field like `visitors` instead of one field per
1247
- * series). When set and `activePoint.seriesKey` is active, the tooltip row
1248
- * reads `datum[valueKey]` for its value.
1249
- */
1250
- valueKey = input(null, /* @ts-ignore */
1251
- ...(ngDevMode ? [{ debugName: "valueKey" }] : /* istanbul ignore next */ []));
1252
- /** Indicator variant next to each row. Default `'dot'`. */
1253
- indicator = input('dot', /* @ts-ignore */
1254
- ...(ngDevMode ? [{ debugName: "indicator" }] : /* istanbul ignore next */ []));
1255
- /** Hide the header label entirely. */
1256
- hideLabel = input(false, /* @ts-ignore */
1257
- ...(ngDevMode ? [{ debugName: "hideLabel" }] : /* istanbul ignore next */ []));
1258
- /** Override the header label with a fixed string. Takes precedence over `labelKey`. */
1259
- label = input(null, /* @ts-ignore */
1260
- ...(ngDevMode ? [{ debugName: "label" }] : /* istanbul ignore next */ []));
1261
- /**
1262
- * Resolve the header label from `config[labelKey].label`. Useful when the
1263
- * header should come from a config entry rather than the datum itself
1264
- * (shadcn's "Custom label" variant).
1265
- */
1266
- labelKey = input(null, /* @ts-ignore */
1267
- ...(ngDevMode ? [{ debugName: "labelKey" }] : /* istanbul ignore next */ []));
1268
- /** Transform the final header string (e.g. ISO date → long date). */
1269
- labelFormatter = input(null, /* @ts-ignore */
1270
- ...(ngDevMode ? [{ debugName: "labelFormatter" }] : /* istanbul ignore next */ []));
1271
- /** Format each row's value (return string — HTML is not interpreted). */
1272
- formatter = input(null, /* @ts-ignore */
1273
- ...(ngDevMode ? [{ debugName: "formatter" }] : /* istanbul ignore next */ []));
1274
- customTpl = contentChild((TemplateRef), /* @ts-ignore */
1275
- ...(ngDevMode ? [{ debugName: "customTpl" }] : /* istanbul ignore next */ []));
1276
- visible = computed(() => this.root.activePoint() !== null, /* @ts-ignore */
1277
- ...(ngDevMode ? [{ debugName: "visible" }] : /* istanbul ignore next */ []));
1278
- payload = computed(() => {
1279
- const active = this.root.activePoint();
1280
- const rows = this.data();
1281
- if (!active || !rows)
1282
- return null;
1283
- const dataIndex = active.datumIndex != null && active.datumIndex < rows.length ? active.datumIndex : active.index;
1284
- if (dataIndex < 0 || dataIndex >= rows.length)
1285
- return null;
1286
- const cfg = this.root.config();
1287
- const visibleKeys = this.root.visibleSeriesKeys();
1288
- const datum = rows[dataIndex];
1289
- const xKey = this.xKey();
1290
- const category = xKey && xKey in datum ? String(datum[xKey]) : String(active.index);
1291
- // When the active point targets a single series (pie/radial/radar hover),
1292
- // collapse the tooltip to just that row.
1293
- const activeSeriesKey = active.seriesKey && visibleKeys.includes(active.seriesKey) ? active.seriesKey : null;
1294
- const keys = activeSeriesKey ? [activeSeriesKey] : visibleKeys;
1295
- const valueKey = this.valueKey();
1296
- const tooltipRows = keys.map((k) => {
1297
- // For single-slice hover on pie/radial/radar the value lives on a
1298
- // shared `valueKey` field, not on a per-series column.
1299
- const rawValue = valueKey != null && activeSeriesKey === k ? datum[valueKey] : datum[k];
1300
- return {
1301
- seriesKey: k,
1302
- label: cfg[k]?.label ?? k,
1303
- value: rawValue,
1304
- color: seriesColorVar(k),
1305
- icon: cfg[k]?.icon,
1306
- };
1307
- });
1308
- return { category, datum, rows: tooltipRows };
1309
- }, /* @ts-ignore */
1310
- ...(ngDevMode ? [{ debugName: "payload" }] : /* istanbul ignore next */ []));
1311
- /** Resolve the header string honoring `label`, `labelKey`, then `labelFormatter`. */
1312
- headerText(p) {
1313
- if (this.hideLabel())
1314
- return null;
1315
- const override = this.label();
1316
- const labelKey = this.labelKey();
1317
- const cfg = this.root.config();
1318
- const fromKey = labelKey ? (cfg[labelKey]?.label ?? labelKey) : null;
1319
- const raw = override ?? fromKey ?? p.category;
1320
- const fmt = this.labelFormatter();
1321
- return fmt ? fmt(raw, p) : raw;
1322
- }
1323
- /** Apply per-row value formatter (or stringify). */
1324
- formatRow(row, p) {
1325
- const fmt = this.formatter();
1326
- if (fmt)
1327
- return fmt(row.value, row, p);
1328
- return row.value == null ? '' : String(row.value);
1329
- }
1330
- position = computed(() => {
1331
- const active = this.root.activePoint();
1332
- if (!active || active.clientX == null || active.clientY == null) {
1333
- return { x: 0, y: 0 };
1334
- }
1335
- // Map client coords → offset within the chart container (the element
1336
- // marked with `data-chart`, which is our positioning ancestor).
1337
- const rect = containerRect(this.host.nativeElement);
1338
- if (!rect)
1339
- return { x: 0, y: 0 };
1340
- const tooltip = this.host.nativeElement.querySelector('[role="tooltip"]');
1341
- const tooltipWidth = tooltip?.offsetWidth ?? 128;
1342
- const tooltipHeight = tooltip?.offsetHeight ?? 0;
1343
- const padding = 8;
1344
- const minX = padding + tooltipWidth / 2;
1345
- const maxX = Math.max(minX, rect.width - padding - tooltipWidth / 2);
1346
- const x = Math.min(maxX, Math.max(minX, active.clientX - rect.left));
1347
- // Y is the tooltip's bottom edge because the card is translated upward.
1348
- const minY = padding + tooltipHeight;
1349
- const y = Math.max(minY, active.clientY - rect.top - padding);
1350
- return { x, y };
1351
- }, /* @ts-ignore */
1352
- ...(ngDevMode ? [{ debugName: "position" }] : /* istanbul ignore next */ []));
1353
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.0", ngImport: i0, type: ChartTooltip, deps: [], target: i0.ɵɵFactoryTarget.Component });
1354
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.0", type: ChartTooltip, isStandalone: true, selector: "ui-chart-tooltip", inputs: { xKey: { classPropertyName: "xKey", publicName: "xKey", isSignal: true, isRequired: false, transformFunction: null }, data: { classPropertyName: "data", publicName: "data", isSignal: true, isRequired: false, transformFunction: null }, valueKey: { classPropertyName: "valueKey", publicName: "valueKey", isSignal: true, isRequired: false, transformFunction: null }, indicator: { classPropertyName: "indicator", publicName: "indicator", isSignal: true, isRequired: false, transformFunction: null }, hideLabel: { classPropertyName: "hideLabel", publicName: "hideLabel", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, labelKey: { classPropertyName: "labelKey", publicName: "labelKey", isSignal: true, isRequired: false, transformFunction: null }, labelFormatter: { classPropertyName: "labelFormatter", publicName: "labelFormatter", isSignal: true, isRequired: false, transformFunction: null }, formatter: { classPropertyName: "formatter", publicName: "formatter", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "attr.aria-hidden": "!visible()" }, classAttribute: "pointer-events-none absolute inset-0 z-10" }, queries: [{ propertyName: "customTpl", first: true, predicate: (TemplateRef), descendants: true, isSignal: true }], ngImport: i0, template: `
1355
- @if (payload(); as p) {
1356
- <div
1357
- role="tooltip"
1358
- class="pointer-events-none absolute grid min-w-32 max-w-72 -translate-x-1/2 -translate-y-full gap-1.5 rounded-lg border border-border/60 bg-background px-3 py-1.5 text-xs shadow-md"
1359
- [style.left.px]="position().x"
1360
- [style.top.px]="position().y">
1361
- @if (customTpl(); as tpl) {
1362
- <ng-container *ngTemplateOutlet="tpl; context: { $implicit: p }" />
1363
- } @else {
1364
- @if (!hideLabel() && headerText(p); as header) {
1365
- <div class="font-medium">{{ header }}</div>
1366
- }
1367
- <ul class="grid gap-1.5">
1368
- @for (row of p.rows; track row.seriesKey) {
1369
- <li class="flex w-full flex-wrap items-stretch gap-2">
1370
- <span class="flex flex-1 items-center gap-1.5">
1371
- @switch (indicator()) {
1372
- @case ('dot') {
1373
- <span
1374
- class="h-2.5 w-2.5 shrink-0 rounded-sm"
1375
- [style.background]="row.color"
1376
- [style.borderColor]="row.color"></span>
1377
- }
1378
- @case ('line') {
1379
- <span class="h-full min-h-4 w-1 shrink-0 rounded-sm" [style.background]="row.color"></span>
1380
- }
1381
- @case ('dashed') {
1382
- <span
1383
- class="h-0 w-3 shrink-0 self-center border-t-2 border-dashed"
1384
- [style.borderColor]="row.color"></span>
1385
- }
1386
- }
1387
- @if (row.icon; as icon) {
1388
- <span class="mr-1 inline-flex items-center text-muted-foreground">
1389
- <ng-container *ngComponentOutlet="icon" />
1390
- </span>
1391
- }
1392
- <span class="text-muted-foreground">{{ row.label }}</span>
1393
- </span>
1394
- <span class="font-mono font-medium tabular-nums text-foreground">
1395
- {{ formatRow(row, p) }}
1396
- </span>
1397
- </li>
1398
- }
1399
- </ul>
1400
- }
1401
- </div>
1402
- }
1403
-
1404
- <!-- Live region — announces category changes to AT. -->
1405
- <div class="sr-only" aria-live="polite" aria-atomic="true">
1406
- @if (payload(); as p) {
1407
- {{ p.category }}
1408
- }
1409
- </div>
1410
- `, isInline: true, dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: NgComponentOutlet, selector: "[ngComponentOutlet]", inputs: ["ngComponentOutlet", "ngComponentOutletInputs", "ngComponentOutletInjector", "ngComponentOutletEnvironmentInjector", "ngComponentOutletContent", "ngComponentOutletNgModule"], exportAs: ["ngComponentOutlet"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1411
- }
1412
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.0", ngImport: i0, type: ChartTooltip, decorators: [{
1413
- type: Component,
1414
- args: [{
1415
- selector: 'ui-chart-tooltip',
1416
- changeDetection: ChangeDetectionStrategy.OnPush,
1417
- imports: [NgTemplateOutlet, NgComponentOutlet],
1418
- host: {
1419
- class: 'pointer-events-none absolute inset-0 z-10',
1420
- '[attr.aria-hidden]': '!visible()',
1421
- },
1422
- template: `
1423
- @if (payload(); as p) {
1424
- <div
1425
- role="tooltip"
1426
- class="pointer-events-none absolute grid min-w-32 max-w-72 -translate-x-1/2 -translate-y-full gap-1.5 rounded-lg border border-border/60 bg-background px-3 py-1.5 text-xs shadow-md"
1427
- [style.left.px]="position().x"
1428
- [style.top.px]="position().y">
1429
- @if (customTpl(); as tpl) {
1430
- <ng-container *ngTemplateOutlet="tpl; context: { $implicit: p }" />
1431
- } @else {
1432
- @if (!hideLabel() && headerText(p); as header) {
1433
- <div class="font-medium">{{ header }}</div>
1434
- }
1435
- <ul class="grid gap-1.5">
1436
- @for (row of p.rows; track row.seriesKey) {
1437
- <li class="flex w-full flex-wrap items-stretch gap-2">
1438
- <span class="flex flex-1 items-center gap-1.5">
1439
- @switch (indicator()) {
1440
- @case ('dot') {
1441
- <span
1442
- class="h-2.5 w-2.5 shrink-0 rounded-sm"
1443
- [style.background]="row.color"
1444
- [style.borderColor]="row.color"></span>
1445
- }
1446
- @case ('line') {
1447
- <span class="h-full min-h-4 w-1 shrink-0 rounded-sm" [style.background]="row.color"></span>
1448
- }
1449
- @case ('dashed') {
1450
- <span
1451
- class="h-0 w-3 shrink-0 self-center border-t-2 border-dashed"
1452
- [style.borderColor]="row.color"></span>
1453
- }
1454
- }
1455
- @if (row.icon; as icon) {
1456
- <span class="mr-1 inline-flex items-center text-muted-foreground">
1457
- <ng-container *ngComponentOutlet="icon" />
1458
- </span>
1459
- }
1460
- <span class="text-muted-foreground">{{ row.label }}</span>
1461
- </span>
1462
- <span class="font-mono font-medium tabular-nums text-foreground">
1463
- {{ formatRow(row, p) }}
1464
- </span>
1465
- </li>
1466
- }
1467
- </ul>
1468
- }
1469
- </div>
1470
- }
1471
-
1472
- <!-- Live region — announces category changes to AT. -->
1473
- <div class="sr-only" aria-live="polite" aria-atomic="true">
1474
- @if (payload(); as p) {
1475
- {{ p.category }}
1476
- }
1477
- </div>
1478
- `,
1479
- }]
1480
- }], propDecorators: { xKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "xKey", required: false }] }], data: [{ type: i0.Input, args: [{ isSignal: true, alias: "data", required: false }] }], valueKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "valueKey", required: false }] }], indicator: [{ type: i0.Input, args: [{ isSignal: true, alias: "indicator", required: false }] }], hideLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "hideLabel", required: false }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], labelKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "labelKey", required: false }] }], labelFormatter: [{ type: i0.Input, args: [{ isSignal: true, alias: "labelFormatter", required: false }] }], formatter: [{ type: i0.Input, args: [{ isSignal: true, alias: "formatter", required: false }] }], customTpl: [{ type: i0.ContentChild, args: [i0.forwardRef(() => TemplateRef), { isSignal: true }] }] } });
1481
-
1482
- /**
1483
- * Legend — renders a list of series swatches. Clicking an item toggles
1484
- * visibility via `ChartContext.toggleSeries`.
1485
- *
1486
- * Place as a child of `<ui-chart-container>`:
1487
- * ```html
1488
- * <ui-chart-container [config]="cfg">
1489
- * <ui-bar-chart ... />
1490
- * <ui-chart-legend />
1491
- * </ui-chart-container>
1492
- * ```
1493
- */
1494
- class ChartLegend {
1495
- root = inject(ChartContext);
1496
- items = computed(() => {
1497
- const cfg = this.root.config();
1498
- const hidden = this.root.hiddenSeries();
1499
- return this.root.seriesKeys().map((key) => ({
1500
- seriesKey: key,
1501
- label: cfg[key]?.label ?? key,
1502
- color: seriesColorVar(key),
1503
- hidden: hidden.has(key),
1504
- }));
1505
- }, /* @ts-ignore */
1506
- ...(ngDevMode ? [{ debugName: "items" }] : /* istanbul ignore next */ []));
1507
- toggle(key) {
1508
- this.root.toggleSeries(key);
1509
- }
1510
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.0", ngImport: i0, type: ChartLegend, deps: [], target: i0.ɵɵFactoryTarget.Component });
1511
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.0", type: ChartLegend, isStandalone: true, selector: "ui-chart-legend", host: { classAttribute: "block pt-3" }, ngImport: i0, template: `
1512
- <ul class="flex flex-wrap items-center justify-center gap-3 text-xs" aria-label="Toggle chart series visibility">
1513
- @for (item of items(); track item.seriesKey) {
1514
- <li>
1515
- <button
1516
- type="button"
1517
- class="inline-flex min-h-11 items-center gap-2 rounded-md px-2.5 py-1.5 outline-none transition-opacity focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background"
1518
- [class.opacity-50]="item.hidden"
1519
- [attr.aria-pressed]="!item.hidden"
1520
- [attr.aria-label]="(item.hidden ? 'Show ' : 'Hide ') + item.label"
1521
- (click)="toggle(item.seriesKey)">
1522
- <span class="inline-block h-2.5 w-2.5 rounded-sm" [style.background]="item.color"></span>
1523
- <span class="text-muted-foreground">{{ item.label }}</span>
1524
- </button>
1525
- </li>
1526
- }
1527
- </ul>
1528
- `, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
1529
- }
1530
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.0", ngImport: i0, type: ChartLegend, decorators: [{
1531
- type: Component,
1532
- args: [{
1533
- selector: 'ui-chart-legend',
1534
- changeDetection: ChangeDetectionStrategy.OnPush,
1535
- host: { class: 'block pt-3' },
1536
- template: `
1537
- <ul class="flex flex-wrap items-center justify-center gap-3 text-xs" aria-label="Toggle chart series visibility">
1538
- @for (item of items(); track item.seriesKey) {
1539
- <li>
1540
- <button
1541
- type="button"
1542
- class="inline-flex min-h-11 items-center gap-2 rounded-md px-2.5 py-1.5 outline-none transition-opacity focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background"
1543
- [class.opacity-50]="item.hidden"
1544
- [attr.aria-pressed]="!item.hidden"
1545
- [attr.aria-label]="(item.hidden ? 'Show ' : 'Hide ') + item.label"
1546
- (click)="toggle(item.seriesKey)">
1547
- <span class="inline-block h-2.5 w-2.5 rounded-sm" [style.background]="item.color"></span>
1548
- <span class="text-muted-foreground">{{ item.label }}</span>
1549
- </button>
1550
- </li>
1551
- }
1552
- </ul>
1553
- `,
1554
- }]
1555
- }] });
1556
-
1557
- function formatNumber(value) {
1558
- return Math.abs(value) >= 10 ? value.toFixed(0) : value.toFixed(1);
1559
- }
1560
- class ChartZoomControls {
1561
- categorical = inject(CategoricalViewportContext, { optional: true });
1562
- scatter = inject(ScatterViewportContext, { optional: true });
1563
- status = computed(() => {
1564
- if (this.categorical?.hasZoom()) {
1565
- const range = this.categorical.zoomRange();
1566
- const count = this.categorical.dataCount();
1567
- if (!range) {
1568
- return null;
1569
- }
1570
- return `Showing ${range.startIndex + 1}-${range.endIndex + 1} of ${count}`;
1571
- }
1572
- if (this.scatter?.hasZoom()) {
1573
- const xDomain = this.scatter.zoomXDomain() ?? this.scatter.fullXDomain();
1574
- const yDomain = this.scatter.zoomYDomain() ?? this.scatter.fullYDomain();
1575
- if (!xDomain || !yDomain) {
1576
- return null;
1577
- }
1578
- return `X ${formatNumber(xDomain[0])}-${formatNumber(xDomain[1])} · Y ${formatNumber(yDomain[0])}-${formatNumber(yDomain[1])}`;
1579
- }
1580
- return null;
1581
- }, /* @ts-ignore */
1582
- ...(ngDevMode ? [{ debugName: "status" }] : /* istanbul ignore next */ []));
1583
- hint = computed(() => {
1584
- if (this.scatter) {
1585
- return 'Drag to brush a region. Use the wheel to zoom and one-finger touch drag to pan while zoomed.';
1586
- }
1587
- return 'Drag to select a category range. Use the wheel to zoom and one-finger touch drag to pan while zoomed.';
1588
- }, /* @ts-ignore */
1589
- ...(ngDevMode ? [{ debugName: "hint" }] : /* istanbul ignore next */ []));
1590
- resetZoom() {
1591
- this.categorical?.resetZoom();
1592
- this.scatter?.resetZoom();
1593
- }
1594
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.0", ngImport: i0, type: ChartZoomControls, deps: [], target: i0.ɵɵFactoryTarget.Component });
1595
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.0", type: ChartZoomControls, isStandalone: true, selector: "ui-chart-zoom-controls", host: { classAttribute: "mt-3 flex flex-wrap items-center justify-between gap-3 text-xs text-muted-foreground" }, ngImport: i0, template: `
1596
- <p>{{ hint() }}</p>
1597
-
1598
- @if (status(); as currentStatus) {
1599
- <div class="flex flex-wrap items-center gap-2">
1600
- <span class="rounded-full border border-border bg-background px-2.5 py-1 font-medium text-foreground">
1601
- {{ currentStatus }}
1602
- </span>
1603
- <button
1604
- type="button"
1605
- class="inline-flex min-h-11 items-center rounded-md border border-border bg-background px-3 text-foreground transition-colors hover:bg-muted focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background"
1606
- (click)="resetZoom()">
1607
- Reset zoom
1608
- </button>
1609
- </div>
1610
- }
1611
- `, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
1612
- }
1613
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.0", ngImport: i0, type: ChartZoomControls, decorators: [{
1614
- type: Component,
1615
- args: [{
1616
- selector: 'ui-chart-zoom-controls',
1617
- changeDetection: ChangeDetectionStrategy.OnPush,
1618
- host: { class: 'mt-3 flex flex-wrap items-center justify-between gap-3 text-xs text-muted-foreground' },
1619
- template: `
1620
- <p>{{ hint() }}</p>
1621
-
1622
- @if (status(); as currentStatus) {
1623
- <div class="flex flex-wrap items-center gap-2">
1624
- <span class="rounded-full border border-border bg-background px-2.5 py-1 font-medium text-foreground">
1625
- {{ currentStatus }}
1626
- </span>
1627
- <button
1628
- type="button"
1629
- class="inline-flex min-h-11 items-center rounded-md border border-border bg-background px-3 text-foreground transition-colors hover:bg-muted focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background"
1630
- (click)="resetZoom()">
1631
- Reset zoom
1632
- </button>
1633
- </div>
1634
- }
1635
- `,
1636
- }]
1637
- }] });
1638
-
1639
- class PieCenter {
1640
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.0", ngImport: i0, type: PieCenter, deps: [], target: i0.ɵɵFactoryTarget.Component });
1641
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "22.0.0", type: PieCenter, isStandalone: true, selector: "ui-pie-center", host: { classAttribute: "flex max-w-[10rem] flex-col items-center justify-center text-center" }, ngImport: i0, template: `<ng-content />`, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
1642
- }
1643
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.0", ngImport: i0, type: PieCenter, decorators: [{
1644
- type: Component,
1645
- args: [{
1646
- selector: 'ui-pie-center',
1647
- changeDetection: ChangeDetectionStrategy.OnPush,
1648
- host: {
1649
- class: 'flex max-w-[10rem] flex-col items-center justify-center text-center',
1650
- },
1651
- template: `<ng-content />`,
1652
- }]
1653
- }] });
1654
-
1655
- class RadialCenter {
1656
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.0", ngImport: i0, type: RadialCenter, deps: [], target: i0.ɵɵFactoryTarget.Component });
1657
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "22.0.0", type: RadialCenter, isStandalone: true, selector: "ui-radial-center", host: { classAttribute: "flex max-w-[10rem] flex-col items-center justify-center text-center" }, ngImport: i0, template: `<ng-content />`, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
1658
- }
1659
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.0", ngImport: i0, type: RadialCenter, decorators: [{
1660
- type: Component,
1661
- args: [{
1662
- selector: 'ui-radial-center',
1663
- changeDetection: ChangeDetectionStrategy.OnPush,
1664
- host: {
1665
- class: 'flex max-w-[10rem] flex-col items-center justify-center text-center',
1666
- },
1667
- template: `<ng-content />`,
1668
- }]
1669
- }] });
1670
-
1671
- /** Read a numeric value from a datum, tolerating strings. */
1672
- function readNumber$1(datum, key) {
1673
- const raw = datum[key];
1674
- if (typeof raw === 'number' && Number.isFinite(raw)) {
1675
- return raw;
1676
- }
1677
- if (typeof raw === 'string') {
1678
- const n = Number(raw);
1679
- return Number.isFinite(n) ? n : 0;
1680
- }
1681
- return 0;
1682
- }
1683
- function resolveBarColor(datum, seriesKey, colorKey) {
1684
- if (!colorKey) {
1685
- return seriesColorVar(seriesKey);
1686
- }
1687
- const raw = datum[colorKey];
1688
- if (typeof raw !== 'string' || raw.length === 0) {
1689
- return seriesColorVar(seriesKey);
1690
- }
1691
- if (raw.startsWith('var(') ||
1692
- raw.startsWith('#') ||
1693
- raw.startsWith('rgb') ||
1694
- raw.startsWith('hsl') ||
1695
- raw.includes('(')) {
1696
- return raw;
1697
- }
1698
- return seriesColorVar(raw);
1699
- }
1700
- function isActiveBar(datum, activeKey, activeValue) {
1701
- if (!activeKey || activeValue === undefined) {
1702
- return false;
1703
- }
1704
- const value = datum[activeKey];
1705
- if (typeof value === 'number' && typeof activeValue === 'number') {
1706
- return value === activeValue;
1707
- }
1708
- return String(value ?? '') === String(activeValue);
1709
- }
1710
- /** Build all bar rectangles for the given config. */
1711
- function computeBarLayout(input) {
1712
- const { data, xKey, seriesKeys, variant, orientation, innerWidth, innerHeight, bandPadding, groupPadding, colorKey, activeKey, activeValue, } = input;
1713
- const categories = data.map((d) => String(d[xKey] ?? ''));
1714
- const isVertical = orientation === 'vertical';
1715
- const categoryScale = scaleBand()
1716
- .domain(categories)
1717
- .range(isVertical ? [0, innerWidth] : [0, innerHeight])
1718
- .padding(bandPadding);
1719
- const valueScale = scaleLinear();
1720
- if (variant === 'stacked' && seriesKeys.length > 0) {
1721
- return stackedLayout({
1722
- data,
1723
- xKey,
1724
- seriesKeys,
1725
- orientation,
1726
- innerWidth,
1727
- innerHeight,
1728
- categoryScale,
1729
- valueScale,
1730
- categories,
1731
- colorKey,
1732
- activeKey,
1733
- activeValue,
1734
- });
1735
- }
1736
- return groupedLayout({
1737
- data,
1738
- xKey,
1739
- seriesKeys,
1740
- orientation,
1741
- innerWidth,
1742
- innerHeight,
1743
- categoryScale,
1744
- valueScale,
1745
- groupPadding,
1746
- categories,
1747
- colorKey,
1748
- activeKey,
1749
- activeValue,
1750
- });
1751
- }
1752
- function groupedLayout(input) {
1753
- const { data, seriesKeys, orientation, innerWidth, innerHeight, categoryScale, valueScale, groupPadding, categories, colorKey, activeKey, activeValue, } = input;
1754
- const isVertical = orientation === 'vertical';
1755
- const minValue = min(data, (d) => min(seriesKeys, (k) => readNumber$1(d, k)) ?? 0) ?? 0;
1756
- const maxValue = max(data, (d) => max(seriesKeys, (k) => readNumber$1(d, k)) ?? 0) ?? 0;
1757
- const domainMin = Math.min(0, minValue);
1758
- const domainMax = Math.max(0, maxValue, domainMin === 0 ? 1 : 0);
1759
- valueScale
1760
- .domain([domainMin, domainMax])
1761
- .nice()
1762
- .range(isVertical ? [innerHeight, 0] : [0, innerWidth]);
1763
- const baseline = valueScale(0);
1764
- const subScale = scaleBand()
1765
- .domain(seriesKeys)
1766
- .range([0, categoryScale.bandwidth()])
1767
- .padding(groupPadding);
1768
- const bars = [];
1769
- data.forEach((datum, datumIndex) => {
1770
- const category = categories[datumIndex];
1771
- const bandStart = categoryScale(category) ?? 0;
1772
- seriesKeys.forEach((seriesKey) => {
1773
- const value = readNumber$1(datum, seriesKey);
1774
- const sub = subScale(seriesKey) ?? 0;
1775
- const scaledValue = valueScale(value);
1776
- const color = resolveBarColor(datum, seriesKey, colorKey);
1777
- const active = isActiveBar(datum, activeKey, activeValue);
1778
- const rect = isVertical
1779
- ? {
1780
- key: `${datumIndex}-${seriesKey}`,
1781
- seriesKey,
1782
- datumIndex,
1783
- category,
1784
- value,
1785
- x: bandStart + sub,
1786
- y: Math.min(scaledValue, baseline),
1787
- width: subScale.bandwidth(),
1788
- height: Math.abs(baseline - scaledValue),
1789
- color,
1790
- active,
1791
- }
1792
- : {
1793
- key: `${datumIndex}-${seriesKey}`,
1794
- seriesKey,
1795
- datumIndex,
1796
- category,
1797
- value,
1798
- x: Math.min(scaledValue, baseline),
1799
- y: bandStart + sub,
1800
- width: Math.abs(baseline - scaledValue),
1801
- height: subScale.bandwidth(),
1802
- color,
1803
- active,
1804
- };
1805
- bars.push(rect);
1806
- });
1807
- });
1808
- return { bars, categoryScale, valueScale, categories };
1809
- }
1810
- function stackedLayout(input) {
1811
- const { data, seriesKeys, orientation, innerWidth, innerHeight, categoryScale, valueScale, categories, colorKey, activeKey, activeValue, } = input;
1812
- const isVertical = orientation === 'vertical';
1813
- const normalized = data.map((d) => {
1814
- const out = {};
1815
- for (const k of seriesKeys) {
1816
- out[k] = readNumber$1(d, k);
1817
- }
1818
- return out;
1819
- });
1820
- const series = stack().keys(seriesKeys)(normalized);
1821
- const maxTotal = max(series[series.length - 1] ?? [], (p) => p[1]) ?? 0;
1822
- valueScale
1823
- .domain([0, maxTotal === 0 ? 1 : maxTotal])
1824
- .nice()
1825
- .range(isVertical ? [innerHeight, 0] : [0, innerWidth]);
1826
- const bars = [];
1827
- series.forEach((layer) => {
1828
- const seriesKey = layer.key;
1829
- layer.forEach((point, datumIndex) => {
1830
- const [lower, upper] = point;
1831
- const value = upper - lower;
1832
- const category = categories[datumIndex];
1833
- const bandStart = categoryScale(category) ?? 0;
1834
- const color = resolveBarColor(data[datumIndex], seriesKey, colorKey);
1835
- const active = isActiveBar(data[datumIndex], activeKey, activeValue);
1836
- const rect = isVertical
1837
- ? {
1838
- key: `${datumIndex}-${seriesKey}`,
1839
- seriesKey,
1840
- datumIndex,
1841
- category,
1842
- value,
1843
- x: bandStart,
1844
- y: valueScale(upper),
1845
- width: categoryScale.bandwidth(),
1846
- height: valueScale(lower) - valueScale(upper),
1847
- color,
1848
- active,
1849
- }
1850
- : {
1851
- key: `${datumIndex}-${seriesKey}`,
1852
- seriesKey,
1853
- datumIndex,
1854
- category,
1855
- value,
1856
- x: valueScale(lower),
1857
- y: bandStart,
1858
- width: valueScale(upper) - valueScale(lower),
1859
- height: categoryScale.bandwidth(),
1860
- color,
1861
- active,
1862
- };
1863
- bars.push(rect);
1864
- });
1865
- });
1866
- return { bars, categoryScale, valueScale, categories };
1867
- }
1868
-
1869
- const DEFAULT_MARGIN$6 = { top: 8, right: 8, bottom: 24, left: 40 };
1870
- const defaultBarValueFormatter = (value) => `${value}`;
1871
- /**
1872
- * Bar chart — composable within `<ui-chart-container>`.
1873
- *
1874
- * Layout variants (via inputs):
1875
- * - `orientation`: `'vertical'` (default) or `'horizontal'`
1876
- * - `variant`: `'grouped'` (default) or `'stacked'`
1877
- */
1878
- class BarChart {
1879
- root = inject(ChartContext);
1880
- cart = inject(CartesianContext);
1881
- data = input.required(/* @ts-ignore */
1882
- ...(ngDevMode ? [{ debugName: "data" }] : /* istanbul ignore next */ []));
1883
- xKey = input.required(/* @ts-ignore */
1884
- ...(ngDevMode ? [{ debugName: "xKey" }] : /* istanbul ignore next */ []));
1885
- orientation = input('vertical', /* @ts-ignore */
1886
- ...(ngDevMode ? [{ debugName: "orientation" }] : /* istanbul ignore next */ []));
1887
- variant = input('grouped', /* @ts-ignore */
1888
- ...(ngDevMode ? [{ debugName: "variant" }] : /* istanbul ignore next */ []));
1889
- margin = input(DEFAULT_MARGIN$6, /* @ts-ignore */
1890
- ...(ngDevMode ? [{ debugName: "margin" }] : /* istanbul ignore next */ []));
1891
- bandPadding = input(0.2, /* @ts-ignore */
1892
- ...(ngDevMode ? [{ debugName: "bandPadding" }] : /* istanbul ignore next */ []));
1893
- groupPadding = input(0.05, /* @ts-ignore */
1894
- ...(ngDevMode ? [{ debugName: "groupPadding" }] : /* istanbul ignore next */ []));
1895
- cornerRadius = input(4, /* @ts-ignore */
1896
- ...(ngDevMode ? [{ debugName: "cornerRadius" }] : /* istanbul ignore next */ []));
1897
- colorKey = input(undefined, /* @ts-ignore */
1898
- ...(ngDevMode ? [{ debugName: "colorKey" }] : /* istanbul ignore next */ []));
1899
- activeKey = input(undefined, /* @ts-ignore */
1900
- ...(ngDevMode ? [{ debugName: "activeKey" }] : /* istanbul ignore next */ []));
1901
- activeValue = input(undefined, /* @ts-ignore */
1902
- ...(ngDevMode ? [{ debugName: "activeValue" }] : /* istanbul ignore next */ []));
1903
- showValueLabels = input(false, /* @ts-ignore */
1904
- ...(ngDevMode ? [{ debugName: "showValueLabels" }] : /* istanbul ignore next */ []));
1905
- valueLabelFormat = input(defaultBarValueFormatter, /* @ts-ignore */
1906
- ...(ngDevMode ? [{ debugName: "valueLabelFormat" }] : /* istanbul ignore next */ []));
1907
- barClick = output();
1908
- innerWidth = computed(() => Math.max(0, this.root.dimensions().width - this.margin().left - this.margin().right), /* @ts-ignore */
1909
- ...(ngDevMode ? [{ debugName: "innerWidth" }] : /* istanbul ignore next */ []));
1910
- innerHeight = computed(() => Math.max(0, this.root.dimensions().height - this.margin().top - this.margin().bottom), /* @ts-ignore */
1911
- ...(ngDevMode ? [{ debugName: "innerHeight" }] : /* istanbul ignore next */ []));
1912
- layout = computed(() => computeBarLayout({
1913
- data: this.data(),
1914
- xKey: this.xKey(),
1915
- seriesKeys: this.root.visibleSeriesKeys(),
1916
- variant: this.variant(),
1917
- orientation: this.orientation(),
1918
- innerWidth: this.innerWidth(),
1919
- innerHeight: this.innerHeight(),
1920
- bandPadding: this.bandPadding(),
1921
- groupPadding: this.groupPadding(),
1922
- colorKey: this.colorKey(),
1923
- activeKey: this.activeKey(),
1924
- activeValue: this.activeValue(),
1925
- }), /* @ts-ignore */
1926
- ...(ngDevMode ? [{ debugName: "layout" }] : /* istanbul ignore next */ []));
1927
- bars = computed(() => this.layout().bars, /* @ts-ignore */
1928
- ...(ngDevMode ? [{ debugName: "bars" }] : /* istanbul ignore next */ []));
1929
- viewBox = computed(() => {
1930
- const { width, height } = this.root.dimensions();
1931
- return `0 0 ${Math.max(0, width)} ${Math.max(0, height)}`;
1932
- }, /* @ts-ignore */
1933
- ...(ngDevMode ? [{ debugName: "viewBox" }] : /* istanbul ignore next */ []));
1934
- innerTransform = computed(() => `translate(${this.margin().left},${this.margin().top})`, /* @ts-ignore */
1935
- ...(ngDevMode ? [{ debugName: "innerTransform" }] : /* istanbul ignore next */ []));
1936
- ariaSummary = computed(() => {
1937
- const keys = this.root.visibleSeriesKeys();
1938
- const n = this.data().length;
1939
- return `Bar chart, ${n} categories, ${keys.length} series: ${keys.join(', ')}.`;
1940
- }, /* @ts-ignore */
1941
- ...(ngDevMode ? [{ debugName: "ariaSummary" }] : /* istanbul ignore next */ []));
1942
- constructor() {
1943
- effect(() => {
1944
- const layout = this.layout();
1945
- this.cart.orientation.set(this.orientation());
1946
- this.cart.margin.set(this.margin());
1947
- this.cart.innerWidth.set(this.innerWidth());
1948
- this.cart.innerHeight.set(this.innerHeight());
1949
- this.cart.categoryScale.set(layout.categoryScale);
1950
- this.cart.valueScale.set(layout.valueScale);
1951
- this.cart.categories.set(layout.categories);
1952
- });
1953
- }
1954
- emitClick(bar) {
1955
- this.barClick.emit({
1956
- seriesKey: bar.seriesKey,
1957
- datumIndex: bar.datumIndex,
1958
- category: bar.category,
1959
- value: bar.value,
1960
- datum: this.data()[bar.datumIndex],
1961
- });
1962
- }
1963
- setActivePoint(event, bar) {
1964
- const center = elementClientCenter(event.currentTarget);
1965
- this.root.activePoint.set({
1966
- index: bar.datumIndex,
1967
- seriesKey: bar.seriesKey,
1968
- clientX: center?.clientX,
1969
- clientY: center?.clientY,
1970
- });
1971
- }
1972
- clearActivePoint() {
1973
- this.root.activePoint.set(null);
1974
- }
1975
- barOpacity(bar) {
1976
- return this.activeKey() && this.activeValue() !== undefined ? (bar.active ? 1 : 0.42) : 1;
1977
- }
1978
- formatValueLabel(bar) {
1979
- return this.valueLabelFormat()(bar.value);
1980
- }
1981
- barLabelX(bar) {
1982
- if (this.orientation() === 'vertical') {
1983
- return bar.x + bar.width / 2;
1984
- }
1985
- return bar.value >= 0 ? bar.x + bar.width + 6 : bar.x - 6;
1986
- }
1987
- barLabelY(bar) {
1988
- if (this.orientation() === 'vertical') {
1989
- return bar.value >= 0 ? bar.y - 8 : bar.y + bar.height + 10;
1990
- }
1991
- return bar.y + bar.height / 2;
1992
- }
1993
- barLabelAnchor(bar) {
1994
- if (this.orientation() === 'vertical') {
1995
- return 'middle';
1996
- }
1997
- return bar.value >= 0 ? 'start' : 'end';
1998
- }
1999
- barAriaLabel(bar) {
2000
- const label = this.root.config()[bar.seriesKey]?.label ?? bar.seriesKey;
2001
- return `${label} in ${bar.category}: ${bar.value}`;
2002
- }
2003
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.0", ngImport: i0, type: BarChart, deps: [], target: i0.ɵɵFactoryTarget.Component });
2004
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.0", type: BarChart, isStandalone: true, selector: "ui-bar-chart", inputs: { data: { classPropertyName: "data", publicName: "data", isSignal: true, isRequired: true, transformFunction: null }, xKey: { classPropertyName: "xKey", publicName: "xKey", isSignal: true, isRequired: true, transformFunction: null }, orientation: { classPropertyName: "orientation", publicName: "orientation", isSignal: true, isRequired: false, transformFunction: null }, variant: { classPropertyName: "variant", publicName: "variant", isSignal: true, isRequired: false, transformFunction: null }, margin: { classPropertyName: "margin", publicName: "margin", isSignal: true, isRequired: false, transformFunction: null }, bandPadding: { classPropertyName: "bandPadding", publicName: "bandPadding", isSignal: true, isRequired: false, transformFunction: null }, groupPadding: { classPropertyName: "groupPadding", publicName: "groupPadding", isSignal: true, isRequired: false, transformFunction: null }, cornerRadius: { classPropertyName: "cornerRadius", publicName: "cornerRadius", isSignal: true, isRequired: false, transformFunction: null }, colorKey: { classPropertyName: "colorKey", publicName: "colorKey", isSignal: true, isRequired: false, transformFunction: null }, activeKey: { classPropertyName: "activeKey", publicName: "activeKey", isSignal: true, isRequired: false, transformFunction: null }, activeValue: { classPropertyName: "activeValue", publicName: "activeValue", isSignal: true, isRequired: false, transformFunction: null }, showValueLabels: { classPropertyName: "showValueLabels", publicName: "showValueLabels", isSignal: true, isRequired: false, transformFunction: null }, valueLabelFormat: { classPropertyName: "valueLabelFormat", publicName: "valueLabelFormat", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { barClick: "barClick" }, host: { classAttribute: "relative block h-full w-full" }, providers: [CartesianContext], ngImport: i0, template: `
2005
- <svg:svg
2006
- uiChartPointerTracker
2007
- class="block h-full w-full overflow-visible"
2008
- [attr.viewBox]="viewBox()"
2009
- preserveAspectRatio="none"
2010
- role="img"
2011
- [attr.aria-label]="ariaSummary()">
2012
- <svg:g [attr.transform]="innerTransform()">
2013
- <ng-content select="svg\\:g[ui-chart-grid]" />
2014
- <svg:g class="chart-bars">
2015
- @for (bar of bars(); track bar.key) {
2016
- <svg:rect
2017
- class="chart-bar cursor-pointer outline-none transition-opacity hover:opacity-80"
2018
- [attr.x]="bar.x"
2019
- [attr.y]="bar.y"
2020
- [attr.width]="bar.width"
2021
- [attr.height]="bar.height"
2022
- [attr.rx]="cornerRadius()"
2023
- [attr.ry]="cornerRadius()"
2024
- [attr.fill]="bar.color"
2025
- [attr.opacity]="barOpacity(bar)"
2026
- [attr.stroke]="bar.active ? 'hsl(var(--foreground))' : null"
2027
- [attr.stroke-width]="bar.active ? 1.5 : null"
2028
- [attr.aria-label]="barAriaLabel(bar)"
2029
- tabindex="0"
2030
- (focus)="setActivePoint($event, bar)"
2031
- (blur)="clearActivePoint()"
2032
- (click)="emitClick(bar)"
2033
- (keydown.enter)="emitClick(bar)"
2034
- (keydown.space)="emitClick(bar); $event.preventDefault()" />
2035
- @if (showValueLabels() && bar.height > 0 && bar.width > 0) {
2036
- <svg:text
2037
- class="chart-bar-value pointer-events-none fill-muted-foreground text-[10px]"
2038
- [attr.x]="barLabelX(bar)"
2039
- [attr.y]="barLabelY(bar)"
2040
- [attr.text-anchor]="barLabelAnchor(bar)"
2041
- dominant-baseline="middle">
2042
- {{ formatValueLabel(bar) }}
2043
- </svg:text>
2044
- }
2045
- }
2046
- </svg:g>
2047
- <ng-content select="svg\\:g[ui-chart-axis-x]" />
2048
- <ng-content select="svg\\:g[ui-chart-axis-y]" />
2049
- <ng-content select="svg\\:g[ui-chart-crosshair]" />
2050
- <ng-content />
2051
- </svg:g>
2052
- </svg:svg>
2053
- <ng-content select="ui-chart-tooltip" />
2054
- <ng-content select="ui-chart-legend" />
2055
- `, isInline: true, dependencies: [{ kind: "directive", type: ChartPointerTracker, selector: "svg:svg[uiChartPointerTracker]" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
2056
- }
2057
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.0", ngImport: i0, type: BarChart, decorators: [{
2058
- type: Component,
2059
- args: [{
2060
- selector: 'ui-bar-chart',
2061
- changeDetection: ChangeDetectionStrategy.OnPush,
2062
- providers: [CartesianContext],
2063
- imports: [ChartPointerTracker],
2064
- host: { class: 'relative block h-full w-full' },
2065
- template: `
2066
- <svg:svg
2067
- uiChartPointerTracker
2068
- class="block h-full w-full overflow-visible"
2069
- [attr.viewBox]="viewBox()"
2070
- preserveAspectRatio="none"
2071
- role="img"
2072
- [attr.aria-label]="ariaSummary()">
2073
- <svg:g [attr.transform]="innerTransform()">
2074
- <ng-content select="svg\\:g[ui-chart-grid]" />
2075
- <svg:g class="chart-bars">
2076
- @for (bar of bars(); track bar.key) {
2077
- <svg:rect
2078
- class="chart-bar cursor-pointer outline-none transition-opacity hover:opacity-80"
2079
- [attr.x]="bar.x"
2080
- [attr.y]="bar.y"
2081
- [attr.width]="bar.width"
2082
- [attr.height]="bar.height"
2083
- [attr.rx]="cornerRadius()"
2084
- [attr.ry]="cornerRadius()"
2085
- [attr.fill]="bar.color"
2086
- [attr.opacity]="barOpacity(bar)"
2087
- [attr.stroke]="bar.active ? 'hsl(var(--foreground))' : null"
2088
- [attr.stroke-width]="bar.active ? 1.5 : null"
2089
- [attr.aria-label]="barAriaLabel(bar)"
2090
- tabindex="0"
2091
- (focus)="setActivePoint($event, bar)"
2092
- (blur)="clearActivePoint()"
2093
- (click)="emitClick(bar)"
2094
- (keydown.enter)="emitClick(bar)"
2095
- (keydown.space)="emitClick(bar); $event.preventDefault()" />
2096
- @if (showValueLabels() && bar.height > 0 && bar.width > 0) {
2097
- <svg:text
2098
- class="chart-bar-value pointer-events-none fill-muted-foreground text-[10px]"
2099
- [attr.x]="barLabelX(bar)"
2100
- [attr.y]="barLabelY(bar)"
2101
- [attr.text-anchor]="barLabelAnchor(bar)"
2102
- dominant-baseline="middle">
2103
- {{ formatValueLabel(bar) }}
2104
- </svg:text>
2105
- }
2106
- }
2107
- </svg:g>
2108
- <ng-content select="svg\\:g[ui-chart-axis-x]" />
2109
- <ng-content select="svg\\:g[ui-chart-axis-y]" />
2110
- <ng-content select="svg\\:g[ui-chart-crosshair]" />
2111
- <ng-content />
2112
- </svg:g>
2113
- </svg:svg>
2114
- <ng-content select="ui-chart-tooltip" />
2115
- <ng-content select="ui-chart-legend" />
2116
- `,
2117
- }]
2118
- }], ctorParameters: () => [], propDecorators: { data: [{ type: i0.Input, args: [{ isSignal: true, alias: "data", required: true }] }], xKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "xKey", required: true }] }], orientation: [{ type: i0.Input, args: [{ isSignal: true, alias: "orientation", required: false }] }], variant: [{ type: i0.Input, args: [{ isSignal: true, alias: "variant", required: false }] }], margin: [{ type: i0.Input, args: [{ isSignal: true, alias: "margin", required: false }] }], bandPadding: [{ type: i0.Input, args: [{ isSignal: true, alias: "bandPadding", required: false }] }], groupPadding: [{ type: i0.Input, args: [{ isSignal: true, alias: "groupPadding", required: false }] }], cornerRadius: [{ type: i0.Input, args: [{ isSignal: true, alias: "cornerRadius", required: false }] }], colorKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "colorKey", required: false }] }], activeKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "activeKey", required: false }] }], activeValue: [{ type: i0.Input, args: [{ isSignal: true, alias: "activeValue", required: false }] }], showValueLabels: [{ type: i0.Input, args: [{ isSignal: true, alias: "showValueLabels", required: false }] }], valueLabelFormat: [{ type: i0.Input, args: [{ isSignal: true, alias: "valueLabelFormat", required: false }] }], barClick: [{ type: i0.Output, args: ["barClick"] }] } });
2119
-
2120
- function curveFor(curve) {
2121
- switch (curve) {
2122
- case 'monotone':
2123
- return curveMonotoneX;
2124
- case 'step':
2125
- return curveStep;
2126
- case 'linear':
2127
- default:
2128
- return curveLinear;
2129
- }
2130
- }
2131
- function readNumber(datum, key) {
2132
- const raw = datum[key];
2133
- if (typeof raw === 'number' && Number.isFinite(raw))
2134
- return raw;
2135
- if (typeof raw === 'string') {
2136
- const n = Number(raw);
2137
- return Number.isFinite(n) ? n : 0;
2138
- }
2139
- return 0;
2140
- }
2141
- /** Build category + value scales for point-based charts (line / area). */
2142
- function buildCartesianScales(input) {
2143
- const { data, xKey, seriesKeys, orientation, innerWidth, innerHeight } = input;
2144
- const isVertical = orientation === 'vertical';
2145
- const categories = data.map((d) => String(d[xKey] ?? ''));
2146
- const categoryScale = scalePoint()
2147
- .domain(categories)
2148
- .range(isVertical ? [0, innerWidth] : [0, innerHeight])
2149
- .padding(0.5);
2150
- const maxValue = max(data, (d) => max(seriesKeys, (k) => readNumber(d, k)) ?? 0) ?? 0;
2151
- const valueScale = scaleLinear()
2152
- .domain([0, maxValue === 0 ? 1 : maxValue])
2153
- .nice()
2154
- .range(isVertical ? [innerHeight, 0] : [0, innerWidth]);
2155
- return { categories, categoryScale, valueScale };
2156
- }
2157
- /** Compute line-chart geometry. */
2158
- function computeLineLayout(input) {
2159
- const { data, seriesKeys, orientation, curve } = input;
2160
- const { categories, categoryScale, valueScale } = buildCartesianScales(input);
2161
- const isVertical = orientation === 'vertical';
2162
- const allPoints = [];
2163
- const series = [];
2164
- for (const seriesKey of seriesKeys) {
2165
- const points = data.map((d, datumIndex) => {
2166
- const value = readNumber(d, seriesKey);
2167
- const category = categories[datumIndex];
2168
- const c = categoryScale(category) ?? 0;
2169
- const v = valueScale(value);
2170
- return {
2171
- seriesKey,
2172
- datumIndex,
2173
- category,
2174
- value,
2175
- x: isVertical ? c : v,
2176
- y: isVertical ? v : c,
2177
- };
2178
- });
2179
- allPoints.push(...points);
2180
- const generator = line()
2181
- .x((p) => p.x)
2182
- .y((p) => p.y)
2183
- .curve(curveFor(curve));
2184
- series.push({
2185
- seriesKey,
2186
- color: seriesColorVar(seriesKey),
2187
- linePath: generator(points) ?? '',
2188
- points,
2189
- });
2190
- }
2191
- return {
2192
- series,
2193
- points: allPoints,
2194
- categoryScale,
2195
- valueScale,
2196
- categories,
2197
- };
2198
- }
2199
- /** Compute area-chart geometry (single or stacked). */
2200
- function computeAreaLayout(input) {
2201
- if (input.stacked && input.seriesKeys.length > 0) {
2202
- return computeStackedArea(input);
2203
- }
2204
- return computeSingleArea(input);
2205
- }
2206
- function computeSingleArea(input) {
2207
- const { data, seriesKeys, orientation, curve } = input;
2208
- const { categories, categoryScale, valueScale } = buildCartesianScales(input);
2209
- const isVertical = orientation === 'vertical';
2210
- const baseline = isVertical ? valueScale(0) : valueScale(0);
2211
- const allPoints = [];
2212
- const series = [];
2213
- for (const seriesKey of seriesKeys) {
2214
- const points = data.map((d, datumIndex) => {
2215
- const value = readNumber(d, seriesKey);
2216
- const category = categories[datumIndex];
2217
- const c = categoryScale(category) ?? 0;
2218
- const v = valueScale(value);
2219
- return {
2220
- seriesKey,
2221
- datumIndex,
2222
- category,
2223
- value,
2224
- x: isVertical ? c : v,
2225
- y: isVertical ? v : c,
2226
- };
2227
- });
2228
- allPoints.push(...points);
2229
- const lineGen = line()
2230
- .x((p) => p.x)
2231
- .y((p) => p.y)
2232
- .curve(curveFor(curve));
2233
- const areaGen = isVertical
2234
- ? area()
2235
- .x((p) => p.x)
2236
- .y0(baseline)
2237
- .y1((p) => p.y)
2238
- .curve(curveFor(curve))
2239
- : area()
2240
- .y((p) => p.y)
2241
- .x0(baseline)
2242
- .x1((p) => p.x)
2243
- .curve(curveFor(curve));
2244
- series.push({
2245
- seriesKey,
2246
- color: seriesColorVar(seriesKey),
2247
- linePath: lineGen(points) ?? '',
2248
- areaPath: areaGen(points) ?? '',
2249
- points,
2250
- });
2251
- }
2252
- return {
2253
- series,
2254
- points: allPoints,
2255
- categoryScale,
2256
- valueScale,
2257
- categories,
2258
- stacked: false,
2259
- };
2260
- }
2261
- function computeStackedArea(input) {
2262
- const { data, seriesKeys, orientation, innerWidth, innerHeight, xKey, curve, expanded } = input;
2263
- const isVertical = orientation === 'vertical';
2264
- const categories = data.map((d) => String(d[xKey] ?? ''));
2265
- const categoryScale = scalePoint()
2266
- .domain(categories)
2267
- .range(isVertical ? [0, innerWidth] : [0, innerHeight])
2268
- .padding(0.5);
2269
- const normalized = data.map((d) => {
2270
- const out = {};
2271
- for (const k of seriesKeys)
2272
- out[k] = readNumber(d, k);
2273
- return out;
2274
- });
2275
- const stackGenerator = stack().keys(seriesKeys);
2276
- if (expanded) {
2277
- stackGenerator.offset(stackOffsetExpand);
2278
- }
2279
- const stackSeries = stackGenerator(normalized);
2280
- const maxTotal = expanded ? 1 : (max(stackSeries[stackSeries.length - 1] ?? [], (p) => p[1]) ?? 0);
2281
- const valueScale = scaleLinear()
2282
- .domain([0, maxTotal === 0 ? 1 : maxTotal])
2283
- .range(isVertical ? [innerHeight, 0] : [0, innerWidth]);
2284
- if (!expanded) {
2285
- valueScale.nice();
2286
- }
2287
- const allPoints = [];
2288
- const series = stackSeries.map((layer) => {
2289
- const seriesKey = layer.key;
2290
- const points = layer.map((p, datumIndex) => {
2291
- const category = categories[datumIndex];
2292
- const c = categoryScale(category) ?? 0;
2293
- const upper = valueScale(p[1]);
2294
- return {
2295
- seriesKey,
2296
- datumIndex,
2297
- category,
2298
- value: p[1] - p[0],
2299
- x: isVertical ? c : upper,
2300
- y: isVertical ? upper : c,
2301
- };
2302
- });
2303
- allPoints.push(...points);
2304
- const lineGen = line()
2305
- .x(([x]) => x)
2306
- .y(([, y]) => y)
2307
- .curve(curveFor(curve));
2308
- const areaGen = isVertical
2309
- ? area()
2310
- .x(([x]) => x)
2311
- .y0(([, , y0]) => y0)
2312
- .y1(([, y1]) => y1)
2313
- .curve(curveFor(curve))
2314
- : area()
2315
- .y(([x]) => x)
2316
- .x0(([, , x0]) => x0)
2317
- .x1(([, x1]) => x1)
2318
- .curve(curveFor(curve));
2319
- const tuples = layer.map((p, i) => {
2320
- const c = categoryScale(categories[i]) ?? 0;
2321
- return [c, valueScale(p[1]), valueScale(p[0])];
2322
- });
2323
- return {
2324
- seriesKey,
2325
- color: seriesColorVar(seriesKey),
2326
- linePath: lineGen(tuples) ?? '',
2327
- areaPath: areaGen(tuples) ?? '',
2328
- points,
2329
- };
2330
- });
2331
- return {
2332
- series,
2333
- points: allPoints,
2334
- categoryScale,
2335
- valueScale,
2336
- categories,
2337
- stacked: true,
2338
- };
2339
- }
2340
-
2341
- /**
2342
- * Publish a `scalePoint` from line/area layouts to the `CartesianContext`,
2343
- * which expects a `scaleBand`. We adapt by building a band with zero padding
2344
- * centered on the same positions, so axis ticks render at the correct
2345
- * offsets.
2346
- *
2347
- * This keeps axes/grid primitives agnostic to whether the underlying chart
2348
- * uses `scaleBand` (bar) or `scalePoint` (line/area).
2349
- */
2350
- function pointToBandAdapter(pointScale, range) {
2351
- return scaleBand().domain(pointScale.domain()).range(range).padding(0);
2352
- }
2353
- /** Recreate a linear scale with the same domain/range (handy for effects). */
2354
- function cloneLinear(scale) {
2355
- return scaleLinear().domain(scale.domain()).range(scale.range());
2356
- }
2357
- function provideCartesianFromLineLayout(ctx, layout, orientation, innerWidth, innerHeight) {
2358
- const range = orientation === 'vertical' ? [0, innerWidth] : [0, innerHeight];
2359
- ctx.orientation.set(orientation);
2360
- ctx.innerWidth.set(innerWidth);
2361
- ctx.innerHeight.set(innerHeight);
2362
- ctx.categoryScale.set(pointToBandAdapter(layout.categoryScale, range));
2363
- ctx.valueScale.set(cloneLinear(layout.valueScale));
2364
- ctx.categories.set(layout.categories);
2365
- }
2366
-
2367
- const DEFAULT_MARGIN$5 = { top: 8, right: 8, bottom: 24, left: 40 };
2368
- const defaultLineValueFormatter = (value) => `${value}`;
2369
- class LineChart {
2370
- root = inject(ChartContext);
2371
- cart = inject(CartesianContext);
2372
- viewport = inject(CategoricalViewportContext);
2373
- data = input.required(/* @ts-ignore */
2374
- ...(ngDevMode ? [{ debugName: "data" }] : /* istanbul ignore next */ []));
2375
- xKey = input.required(/* @ts-ignore */
2376
- ...(ngDevMode ? [{ debugName: "xKey" }] : /* istanbul ignore next */ []));
2377
- orientation = input('vertical', /* @ts-ignore */
2378
- ...(ngDevMode ? [{ debugName: "orientation" }] : /* istanbul ignore next */ []));
2379
- curve = input('monotone', /* @ts-ignore */
2380
- ...(ngDevMode ? [{ debugName: "curve" }] : /* istanbul ignore next */ []));
2381
- margin = input(DEFAULT_MARGIN$5, /* @ts-ignore */
2382
- ...(ngDevMode ? [{ debugName: "margin" }] : /* istanbul ignore next */ []));
2383
- strokeWidth = input(2, /* @ts-ignore */
2384
- ...(ngDevMode ? [{ debugName: "strokeWidth" }] : /* istanbul ignore next */ []));
2385
- showDots = input(true, /* @ts-ignore */
2386
- ...(ngDevMode ? [{ debugName: "showDots" }] : /* istanbul ignore next */ []));
2387
- dotRadius = input(3, /* @ts-ignore */
2388
- ...(ngDevMode ? [{ debugName: "dotRadius" }] : /* istanbul ignore next */ []));
2389
- dotColorKey = input(undefined, /* @ts-ignore */
2390
- ...(ngDevMode ? [{ debugName: "dotColorKey" }] : /* istanbul ignore next */ []));
2391
- dotStrokeColor = input(undefined, /* @ts-ignore */
2392
- ...(ngDevMode ? [{ debugName: "dotStrokeColor" }] : /* istanbul ignore next */ []));
2393
- dotStrokeWidth = input(0, /* @ts-ignore */
2394
- ...(ngDevMode ? [{ debugName: "dotStrokeWidth" }] : /* istanbul ignore next */ []));
2395
- showValueLabels = input(false, /* @ts-ignore */
2396
- ...(ngDevMode ? [{ debugName: "showValueLabels" }] : /* istanbul ignore next */ []));
2397
- valueLabelFormat = input(defaultLineValueFormatter, /* @ts-ignore */
2398
- ...(ngDevMode ? [{ debugName: "valueLabelFormat" }] : /* istanbul ignore next */ []));
2399
- pointClick = output();
2400
- innerWidth = computed(() => Math.max(0, this.root.dimensions().width - this.margin().left - this.margin().right), /* @ts-ignore */
2401
- ...(ngDevMode ? [{ debugName: "innerWidth" }] : /* istanbul ignore next */ []));
2402
- innerHeight = computed(() => Math.max(0, this.root.dimensions().height - this.margin().top - this.margin().bottom), /* @ts-ignore */
2403
- ...(ngDevMode ? [{ debugName: "innerHeight" }] : /* istanbul ignore next */ []));
2404
- visibleStartIndex = computed(() => this.viewport.zoomRange()?.startIndex ?? 0, /* @ts-ignore */
2405
- ...(ngDevMode ? [{ debugName: "visibleStartIndex" }] : /* istanbul ignore next */ []));
2406
- visibleData = computed(() => sliceByIndexRange(this.data(), this.viewport.zoomRange()), /* @ts-ignore */
2407
- ...(ngDevMode ? [{ debugName: "visibleData" }] : /* istanbul ignore next */ []));
2408
- layout = computed(() => computeLineLayout({
2409
- data: this.visibleData(),
2410
- xKey: this.xKey(),
2411
- seriesKeys: this.root.visibleSeriesKeys(),
2412
- orientation: this.orientation(),
2413
- innerWidth: this.innerWidth(),
2414
- innerHeight: this.innerHeight(),
2415
- curve: this.curve(),
2416
- }), /* @ts-ignore */
2417
- ...(ngDevMode ? [{ debugName: "layout" }] : /* istanbul ignore next */ []));
2418
- series = computed(() => this.layout().series, /* @ts-ignore */
2419
- ...(ngDevMode ? [{ debugName: "series" }] : /* istanbul ignore next */ []));
2420
- viewBox = computed(() => {
2421
- const { width, height } = this.root.dimensions();
2422
- return `0 0 ${Math.max(0, width)} ${Math.max(0, height)}`;
2423
- }, /* @ts-ignore */
2424
- ...(ngDevMode ? [{ debugName: "viewBox" }] : /* istanbul ignore next */ []));
2425
- innerTransform = computed(() => `translate(${this.margin().left},${this.margin().top})`, /* @ts-ignore */
2426
- ...(ngDevMode ? [{ debugName: "innerTransform" }] : /* istanbul ignore next */ []));
2427
- ariaSummary = computed(() => {
2428
- const keys = this.root.visibleSeriesKeys();
2429
- return `Line chart, ${this.visibleData().length} points, ${keys.length} series: ${keys.join(', ')}.`;
2430
- }, /* @ts-ignore */
2431
- ...(ngDevMode ? [{ debugName: "ariaSummary" }] : /* istanbul ignore next */ []));
2432
- constructor() {
2433
- effect(() => {
2434
- const layout = this.layout();
2435
- this.viewport.dataCount.set(this.data().length);
2436
- provideCartesianFromLineLayout(this.cart, layout, this.orientation(), this.innerWidth(), this.innerHeight());
2437
- this.cart.margin.set(this.margin());
2438
- });
2439
- }
2440
- emitClick(p) {
2441
- const datumIndex = this.visibleStartIndex() + p.datumIndex;
2442
- this.pointClick.emit({
2443
- seriesKey: p.seriesKey,
2444
- datumIndex,
2445
- category: p.category,
2446
- value: p.value,
2447
- datum: this.data()[datumIndex],
2448
- });
2449
- }
2450
- setActivePoint(event, p) {
2451
- const center = elementClientCenter(event.currentTarget);
2452
- const datumIndex = this.visibleStartIndex() + p.datumIndex;
2453
- this.root.activePoint.set({
2454
- index: p.datumIndex,
2455
- datumIndex,
2456
- seriesKey: p.seriesKey,
2457
- clientX: center?.clientX,
2458
- clientY: center?.clientY,
2459
- });
2460
- }
2461
- clearActivePoint() {
2462
- this.root.activePoint.set(null);
2463
- }
2464
- dotFill(point, fallbackColor) {
2465
- const colorKey = this.dotColorKey();
2466
- if (!colorKey) {
2467
- return fallbackColor;
2468
- }
2469
- const datum = this.visibleData()[point.datumIndex];
2470
- const raw = datum?.[colorKey];
2471
- if (typeof raw !== 'string' || raw.length === 0) {
2472
- return fallbackColor;
2473
- }
2474
- if (raw.startsWith('var(') ||
2475
- raw.startsWith('#') ||
2476
- raw.startsWith('rgb') ||
2477
- raw.startsWith('hsl') ||
2478
- raw.includes('(')) {
2479
- return raw;
2480
- }
2481
- return `var(--color-${raw.replace(/[^a-zA-Z0-9_-]/g, '_')})`;
2482
- }
2483
- formatValueLabel(point) {
2484
- return this.valueLabelFormat()(point.value);
2485
- }
2486
- labelX(point) {
2487
- return this.orientation() === 'vertical' ? point.x : point.x + this.dotRadius() + 6;
2488
- }
2489
- labelY(point) {
2490
- return this.orientation() === 'vertical' ? point.y - this.dotRadius() - 8 : point.y;
2491
- }
2492
- labelAnchor() {
2493
- return this.orientation() === 'vertical' ? 'middle' : 'start';
2494
- }
2495
- pointAriaLabel(p) {
2496
- const label = this.root.config()[p.seriesKey]?.label ?? p.seriesKey;
2497
- return `${label} at ${p.category}: ${p.value}`;
2498
- }
2499
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.0", ngImport: i0, type: LineChart, deps: [], target: i0.ɵɵFactoryTarget.Component });
2500
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.0", type: LineChart, isStandalone: true, selector: "ui-line-chart", inputs: { data: { classPropertyName: "data", publicName: "data", isSignal: true, isRequired: true, transformFunction: null }, xKey: { classPropertyName: "xKey", publicName: "xKey", isSignal: true, isRequired: true, transformFunction: null }, orientation: { classPropertyName: "orientation", publicName: "orientation", isSignal: true, isRequired: false, transformFunction: null }, curve: { classPropertyName: "curve", publicName: "curve", isSignal: true, isRequired: false, transformFunction: null }, margin: { classPropertyName: "margin", publicName: "margin", isSignal: true, isRequired: false, transformFunction: null }, strokeWidth: { classPropertyName: "strokeWidth", publicName: "strokeWidth", isSignal: true, isRequired: false, transformFunction: null }, showDots: { classPropertyName: "showDots", publicName: "showDots", isSignal: true, isRequired: false, transformFunction: null }, dotRadius: { classPropertyName: "dotRadius", publicName: "dotRadius", isSignal: true, isRequired: false, transformFunction: null }, dotColorKey: { classPropertyName: "dotColorKey", publicName: "dotColorKey", isSignal: true, isRequired: false, transformFunction: null }, dotStrokeColor: { classPropertyName: "dotStrokeColor", publicName: "dotStrokeColor", isSignal: true, isRequired: false, transformFunction: null }, dotStrokeWidth: { classPropertyName: "dotStrokeWidth", publicName: "dotStrokeWidth", isSignal: true, isRequired: false, transformFunction: null }, showValueLabels: { classPropertyName: "showValueLabels", publicName: "showValueLabels", isSignal: true, isRequired: false, transformFunction: null }, valueLabelFormat: { classPropertyName: "valueLabelFormat", publicName: "valueLabelFormat", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { pointClick: "pointClick" }, host: { classAttribute: "relative block h-full w-full" }, providers: [CartesianContext, CategoricalViewportContext], ngImport: i0, template: `
2501
- <svg:svg
2502
- uiChartPointerTracker
2503
- class="block h-full w-full overflow-visible"
2504
- [attr.viewBox]="viewBox()"
2505
- preserveAspectRatio="none"
2506
- role="img"
2507
- [attr.aria-label]="ariaSummary()">
2508
- <svg:g [attr.transform]="innerTransform()">
2509
- <ng-content select="svg\\:g[ui-chart-grid]" />
2510
- <svg:g class="chart-lines">
2511
- @for (s of series(); track s.seriesKey) {
2512
- <svg:path
2513
- class="chart-line"
2514
- fill="none"
2515
- stroke-linecap="round"
2516
- stroke-linejoin="round"
2517
- [attr.stroke]="s.color"
2518
- [attr.stroke-width]="strokeWidth()"
2519
- [attr.d]="s.linePath" />
2520
- @if (showDots()) {
2521
- @for (p of s.points; track p.datumIndex) {
2522
- <svg:circle
2523
- class="chart-dot cursor-pointer outline-none"
2524
- [attr.cx]="p.x"
2525
- [attr.cy]="p.y"
2526
- [attr.r]="dotRadius()"
2527
- [attr.fill]="dotFill(p, s.color)"
2528
- [attr.stroke]="dotStrokeColor()"
2529
- [attr.stroke-width]="dotStrokeWidth() || null"
2530
- [attr.aria-label]="pointAriaLabel(p)"
2531
- tabindex="0"
2532
- (focus)="setActivePoint($event, p)"
2533
- (blur)="clearActivePoint()"
2534
- (click)="emitClick(p)"
2535
- (keydown.enter)="emitClick(p)"
2536
- (keydown.space)="emitClick(p); $event.preventDefault()" />
2537
- @if (showValueLabels()) {
2538
- <svg:text
2539
- class="chart-line-value pointer-events-none fill-muted-foreground text-[10px]"
2540
- [attr.x]="labelX(p)"
2541
- [attr.y]="labelY(p)"
2542
- [attr.text-anchor]="labelAnchor()"
2543
- dominant-baseline="middle">
2544
- {{ formatValueLabel(p) }}
2545
- </svg:text>
2546
- }
2547
- }
2548
- }
2549
- }
2550
- </svg:g>
2551
- <ng-content select="svg\\:g[ui-chart-axis-x]" />
2552
- <ng-content select="svg\\:g[ui-chart-axis-y]" />
2553
- <ng-content select="svg\\:g[ui-chart-crosshair]" />
2554
- <ng-content select="svg:g[ui-chart-brush]" />
2555
- </svg:g>
2556
- </svg:svg>
2557
- <ng-content select="ui-chart-tooltip" />
2558
- <ng-content select="ui-chart-legend" />
2559
- <ng-content select="ui-chart-zoom-controls" />
2560
- `, isInline: true, dependencies: [{ kind: "directive", type: ChartPointerTracker, selector: "svg:svg[uiChartPointerTracker]" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
2561
- }
2562
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.0", ngImport: i0, type: LineChart, decorators: [{
2563
- type: Component,
2564
- args: [{
2565
- selector: 'ui-line-chart',
2566
- changeDetection: ChangeDetectionStrategy.OnPush,
2567
- providers: [CartesianContext, CategoricalViewportContext],
2568
- imports: [ChartPointerTracker],
2569
- host: { class: 'relative block h-full w-full' },
2570
- template: `
2571
- <svg:svg
2572
- uiChartPointerTracker
2573
- class="block h-full w-full overflow-visible"
2574
- [attr.viewBox]="viewBox()"
2575
- preserveAspectRatio="none"
2576
- role="img"
2577
- [attr.aria-label]="ariaSummary()">
2578
- <svg:g [attr.transform]="innerTransform()">
2579
- <ng-content select="svg\\:g[ui-chart-grid]" />
2580
- <svg:g class="chart-lines">
2581
- @for (s of series(); track s.seriesKey) {
2582
- <svg:path
2583
- class="chart-line"
2584
- fill="none"
2585
- stroke-linecap="round"
2586
- stroke-linejoin="round"
2587
- [attr.stroke]="s.color"
2588
- [attr.stroke-width]="strokeWidth()"
2589
- [attr.d]="s.linePath" />
2590
- @if (showDots()) {
2591
- @for (p of s.points; track p.datumIndex) {
2592
- <svg:circle
2593
- class="chart-dot cursor-pointer outline-none"
2594
- [attr.cx]="p.x"
2595
- [attr.cy]="p.y"
2596
- [attr.r]="dotRadius()"
2597
- [attr.fill]="dotFill(p, s.color)"
2598
- [attr.stroke]="dotStrokeColor()"
2599
- [attr.stroke-width]="dotStrokeWidth() || null"
2600
- [attr.aria-label]="pointAriaLabel(p)"
2601
- tabindex="0"
2602
- (focus)="setActivePoint($event, p)"
2603
- (blur)="clearActivePoint()"
2604
- (click)="emitClick(p)"
2605
- (keydown.enter)="emitClick(p)"
2606
- (keydown.space)="emitClick(p); $event.preventDefault()" />
2607
- @if (showValueLabels()) {
2608
- <svg:text
2609
- class="chart-line-value pointer-events-none fill-muted-foreground text-[10px]"
2610
- [attr.x]="labelX(p)"
2611
- [attr.y]="labelY(p)"
2612
- [attr.text-anchor]="labelAnchor()"
2613
- dominant-baseline="middle">
2614
- {{ formatValueLabel(p) }}
2615
- </svg:text>
2616
- }
2617
- }
2618
- }
2619
- }
2620
- </svg:g>
2621
- <ng-content select="svg\\:g[ui-chart-axis-x]" />
2622
- <ng-content select="svg\\:g[ui-chart-axis-y]" />
2623
- <ng-content select="svg\\:g[ui-chart-crosshair]" />
2624
- <ng-content select="svg:g[ui-chart-brush]" />
2625
- </svg:g>
2626
- </svg:svg>
2627
- <ng-content select="ui-chart-tooltip" />
2628
- <ng-content select="ui-chart-legend" />
2629
- <ng-content select="ui-chart-zoom-controls" />
2630
- `,
2631
- }]
2632
- }], ctorParameters: () => [], propDecorators: { data: [{ type: i0.Input, args: [{ isSignal: true, alias: "data", required: true }] }], xKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "xKey", required: true }] }], orientation: [{ type: i0.Input, args: [{ isSignal: true, alias: "orientation", required: false }] }], curve: [{ type: i0.Input, args: [{ isSignal: true, alias: "curve", required: false }] }], margin: [{ type: i0.Input, args: [{ isSignal: true, alias: "margin", required: false }] }], strokeWidth: [{ type: i0.Input, args: [{ isSignal: true, alias: "strokeWidth", required: false }] }], showDots: [{ type: i0.Input, args: [{ isSignal: true, alias: "showDots", required: false }] }], dotRadius: [{ type: i0.Input, args: [{ isSignal: true, alias: "dotRadius", required: false }] }], dotColorKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "dotColorKey", required: false }] }], dotStrokeColor: [{ type: i0.Input, args: [{ isSignal: true, alias: "dotStrokeColor", required: false }] }], dotStrokeWidth: [{ type: i0.Input, args: [{ isSignal: true, alias: "dotStrokeWidth", required: false }] }], showValueLabels: [{ type: i0.Input, args: [{ isSignal: true, alias: "showValueLabels", required: false }] }], valueLabelFormat: [{ type: i0.Input, args: [{ isSignal: true, alias: "valueLabelFormat", required: false }] }], pointClick: [{ type: i0.Output, args: ["pointClick"] }] } });
2633
-
2634
- const DEFAULT_MARGIN$4 = { top: 8, right: 8, bottom: 24, left: 40 };
2635
- /**
2636
- * Area chart — composable within `<ui-chart-container>`.
2637
- *
2638
- * - `stacked=true` stacks series along the value axis.
2639
- * - `gradient=true` fills each area with a vertical linear-gradient from
2640
- * the series color (top) to transparent (bottom). The gradient is
2641
- * emitted as `<defs><linearGradient>` per series, unique per chart id.
2642
- */
2643
- class AreaChart {
2644
- root = inject(ChartContext);
2645
- cart = inject(CartesianContext);
2646
- viewport = inject(CategoricalViewportContext);
2647
- data = input.required(/* @ts-ignore */
2648
- ...(ngDevMode ? [{ debugName: "data" }] : /* istanbul ignore next */ []));
2649
- xKey = input.required(/* @ts-ignore */
2650
- ...(ngDevMode ? [{ debugName: "xKey" }] : /* istanbul ignore next */ []));
2651
- orientation = input('vertical', /* @ts-ignore */
2652
- ...(ngDevMode ? [{ debugName: "orientation" }] : /* istanbul ignore next */ []));
2653
- stacked = input(false, /* @ts-ignore */
2654
- ...(ngDevMode ? [{ debugName: "stacked" }] : /* istanbul ignore next */ []));
2655
- expanded = input(false, /* @ts-ignore */
2656
- ...(ngDevMode ? [{ debugName: "expanded" }] : /* istanbul ignore next */ []));
2657
- gradient = input(true, /* @ts-ignore */
2658
- ...(ngDevMode ? [{ debugName: "gradient" }] : /* istanbul ignore next */ []));
2659
- curve = input('monotone', /* @ts-ignore */
2660
- ...(ngDevMode ? [{ debugName: "curve" }] : /* istanbul ignore next */ []));
2661
- margin = input(DEFAULT_MARGIN$4, /* @ts-ignore */
2662
- ...(ngDevMode ? [{ debugName: "margin" }] : /* istanbul ignore next */ []));
2663
- strokeWidth = input(2, /* @ts-ignore */
2664
- ...(ngDevMode ? [{ debugName: "strokeWidth" }] : /* istanbul ignore next */ []));
2665
- showDots = input(false, /* @ts-ignore */
2666
- ...(ngDevMode ? [{ debugName: "showDots" }] : /* istanbul ignore next */ []));
2667
- dotRadius = input(3, /* @ts-ignore */
2668
- ...(ngDevMode ? [{ debugName: "dotRadius" }] : /* istanbul ignore next */ []));
2669
- pointClick = output();
2670
- innerWidth = computed(() => Math.max(0, this.root.dimensions().width - this.margin().left - this.margin().right), /* @ts-ignore */
2671
- ...(ngDevMode ? [{ debugName: "innerWidth" }] : /* istanbul ignore next */ []));
2672
- innerHeight = computed(() => Math.max(0, this.root.dimensions().height - this.margin().top - this.margin().bottom), /* @ts-ignore */
2673
- ...(ngDevMode ? [{ debugName: "innerHeight" }] : /* istanbul ignore next */ []));
2674
- visibleStartIndex = computed(() => this.viewport.zoomRange()?.startIndex ?? 0, /* @ts-ignore */
2675
- ...(ngDevMode ? [{ debugName: "visibleStartIndex" }] : /* istanbul ignore next */ []));
2676
- visibleData = computed(() => sliceByIndexRange(this.data(), this.viewport.zoomRange()), /* @ts-ignore */
2677
- ...(ngDevMode ? [{ debugName: "visibleData" }] : /* istanbul ignore next */ []));
2678
- layout = computed(() => computeAreaLayout({
2679
- data: this.visibleData(),
2680
- xKey: this.xKey(),
2681
- seriesKeys: this.root.visibleSeriesKeys(),
2682
- orientation: this.orientation(),
2683
- innerWidth: this.innerWidth(),
2684
- innerHeight: this.innerHeight(),
2685
- curve: this.curve(),
2686
- stacked: this.stacked(),
2687
- expanded: this.expanded(),
2688
- }), /* @ts-ignore */
2689
- ...(ngDevMode ? [{ debugName: "layout" }] : /* istanbul ignore next */ []));
2690
- series = computed(() => this.layout().series, /* @ts-ignore */
2691
- ...(ngDevMode ? [{ debugName: "series" }] : /* istanbul ignore next */ []));
2692
- viewBox = computed(() => {
2693
- const { width, height } = this.root.dimensions();
2694
- return `0 0 ${Math.max(0, width)} ${Math.max(0, height)}`;
2695
- }, /* @ts-ignore */
2696
- ...(ngDevMode ? [{ debugName: "viewBox" }] : /* istanbul ignore next */ []));
2697
- innerTransform = computed(() => `translate(${this.margin().left},${this.margin().top})`, /* @ts-ignore */
2698
- ...(ngDevMode ? [{ debugName: "innerTransform" }] : /* istanbul ignore next */ []));
2699
- ariaSummary = computed(() => {
2700
- const keys = this.root.visibleSeriesKeys();
2701
- return `Area chart, ${this.visibleData().length} points, ${keys.length} series: ${keys.join(', ')}.`;
2702
- }, /* @ts-ignore */
2703
- ...(ngDevMode ? [{ debugName: "ariaSummary" }] : /* istanbul ignore next */ []));
2704
- constructor() {
2705
- effect(() => {
2706
- const layout = this.layout();
2707
- this.viewport.dataCount.set(this.data().length);
2708
- provideCartesianFromLineLayout(this.cart, layout, this.orientation(), this.innerWidth(), this.innerHeight());
2709
- this.cart.margin.set(this.margin());
2710
- });
2711
- }
2712
- gradientId(seriesKey) {
2713
- return `${this.root.id()}-grad-${seriesKey.replace(/[^a-zA-Z0-9_-]/g, '_')}`;
2714
- }
2715
- areaFill(seriesKey, color) {
2716
- return this.gradient() ? `url(#${this.gradientId(seriesKey)})` : color;
2717
- }
2718
- emitClick(p) {
2719
- const datumIndex = this.visibleStartIndex() + p.datumIndex;
2720
- this.pointClick.emit({
2721
- seriesKey: p.seriesKey,
2722
- datumIndex,
2723
- category: p.category,
2724
- value: p.value,
2725
- datum: this.data()[datumIndex],
2726
- });
2727
- }
2728
- setActivePoint(event, p) {
2729
- const center = elementClientCenter(event.currentTarget);
2730
- const datumIndex = this.visibleStartIndex() + p.datumIndex;
2731
- this.root.activePoint.set({
2732
- index: p.datumIndex,
2733
- datumIndex,
2734
- seriesKey: p.seriesKey,
2735
- clientX: center?.clientX,
2736
- clientY: center?.clientY,
2737
- });
2738
- }
2739
- clearActivePoint() {
2740
- this.root.activePoint.set(null);
2741
- }
2742
- pointAriaLabel(p) {
2743
- const label = this.root.config()[p.seriesKey]?.label ?? p.seriesKey;
2744
- return `${label} at ${p.category}: ${p.value}`;
2745
- }
2746
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.0", ngImport: i0, type: AreaChart, deps: [], target: i0.ɵɵFactoryTarget.Component });
2747
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.0", type: AreaChart, isStandalone: true, selector: "ui-area-chart", inputs: { data: { classPropertyName: "data", publicName: "data", isSignal: true, isRequired: true, transformFunction: null }, xKey: { classPropertyName: "xKey", publicName: "xKey", isSignal: true, isRequired: true, transformFunction: null }, orientation: { classPropertyName: "orientation", publicName: "orientation", isSignal: true, isRequired: false, transformFunction: null }, stacked: { classPropertyName: "stacked", publicName: "stacked", isSignal: true, isRequired: false, transformFunction: null }, expanded: { classPropertyName: "expanded", publicName: "expanded", isSignal: true, isRequired: false, transformFunction: null }, gradient: { classPropertyName: "gradient", publicName: "gradient", isSignal: true, isRequired: false, transformFunction: null }, curve: { classPropertyName: "curve", publicName: "curve", isSignal: true, isRequired: false, transformFunction: null }, margin: { classPropertyName: "margin", publicName: "margin", isSignal: true, isRequired: false, transformFunction: null }, strokeWidth: { classPropertyName: "strokeWidth", publicName: "strokeWidth", isSignal: true, isRequired: false, transformFunction: null }, showDots: { classPropertyName: "showDots", publicName: "showDots", isSignal: true, isRequired: false, transformFunction: null }, dotRadius: { classPropertyName: "dotRadius", publicName: "dotRadius", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { pointClick: "pointClick" }, host: { classAttribute: "relative block h-full w-full" }, providers: [CartesianContext, CategoricalViewportContext], ngImport: i0, template: `
2748
- <svg:svg
2749
- uiChartPointerTracker
2750
- class="block h-full w-full overflow-visible"
2751
- [attr.viewBox]="viewBox()"
2752
- preserveAspectRatio="none"
2753
- role="img"
2754
- [attr.aria-label]="ariaSummary()">
2755
- @if (gradient()) {
2756
- <svg:defs>
2757
- @for (s of series(); track s.seriesKey) {
2758
- <svg:linearGradient [attr.id]="gradientId(s.seriesKey)" x1="0" y1="0" x2="0" y2="1">
2759
- <svg:stop offset="0%" [attr.stop-color]="s.color" stop-opacity="0.4" />
2760
- <svg:stop offset="100%" [attr.stop-color]="s.color" stop-opacity="0" />
2761
- </svg:linearGradient>
2762
- }
2763
- </svg:defs>
2764
- }
2765
- <svg:g [attr.transform]="innerTransform()">
2766
- <ng-content select="svg\\:g[ui-chart-grid]" />
2767
- <svg:g class="chart-areas">
2768
- @for (s of series(); track s.seriesKey) {
2769
- <svg:path
2770
- class="chart-area"
2771
- [attr.d]="s.areaPath"
2772
- [attr.fill]="areaFill(s.seriesKey, s.color)"
2773
- stroke="none" />
2774
- <svg:path
2775
- class="chart-area-stroke"
2776
- [attr.d]="s.linePath"
2777
- [attr.stroke]="s.color"
2778
- [attr.stroke-width]="strokeWidth()"
2779
- fill="none"
2780
- stroke-linecap="round"
2781
- stroke-linejoin="round" />
2782
- @if (showDots()) {
2783
- @for (p of s.points; track p.datumIndex) {
2784
- <svg:circle
2785
- class="chart-dot cursor-pointer outline-none"
2786
- [attr.cx]="p.x"
2787
- [attr.cy]="p.y"
2788
- [attr.r]="dotRadius()"
2789
- [attr.fill]="s.color"
2790
- [attr.aria-label]="pointAriaLabel(p)"
2791
- tabindex="0"
2792
- (focus)="setActivePoint($event, p)"
2793
- (blur)="clearActivePoint()"
2794
- (click)="emitClick(p)"
2795
- (keydown.enter)="emitClick(p)"
2796
- (keydown.space)="emitClick(p); $event.preventDefault()" />
2797
- }
2798
- }
2799
- }
2800
- </svg:g>
2801
- <ng-content select="svg\\:g[ui-chart-axis-x]" />
2802
- <ng-content select="svg\\:g[ui-chart-axis-y]" />
2803
- <ng-content select="svg\\:g[ui-chart-crosshair]" />
2804
- <ng-content select="svg:g[ui-chart-brush]" />
2805
- </svg:g>
2806
- </svg:svg>
2807
- <ng-content select="ui-chart-tooltip" />
2808
- <ng-content select="ui-chart-legend" />
2809
- <ng-content select="ui-chart-zoom-controls" />
2810
- `, isInline: true, dependencies: [{ kind: "directive", type: ChartPointerTracker, selector: "svg:svg[uiChartPointerTracker]" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
2811
- }
2812
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.0", ngImport: i0, type: AreaChart, decorators: [{
2813
- type: Component,
2814
- args: [{
2815
- selector: 'ui-area-chart',
2816
- changeDetection: ChangeDetectionStrategy.OnPush,
2817
- providers: [CartesianContext, CategoricalViewportContext],
2818
- imports: [ChartPointerTracker],
2819
- host: { class: 'relative block h-full w-full' },
2820
- template: `
2821
- <svg:svg
2822
- uiChartPointerTracker
2823
- class="block h-full w-full overflow-visible"
2824
- [attr.viewBox]="viewBox()"
2825
- preserveAspectRatio="none"
2826
- role="img"
2827
- [attr.aria-label]="ariaSummary()">
2828
- @if (gradient()) {
2829
- <svg:defs>
2830
- @for (s of series(); track s.seriesKey) {
2831
- <svg:linearGradient [attr.id]="gradientId(s.seriesKey)" x1="0" y1="0" x2="0" y2="1">
2832
- <svg:stop offset="0%" [attr.stop-color]="s.color" stop-opacity="0.4" />
2833
- <svg:stop offset="100%" [attr.stop-color]="s.color" stop-opacity="0" />
2834
- </svg:linearGradient>
2835
- }
2836
- </svg:defs>
2837
- }
2838
- <svg:g [attr.transform]="innerTransform()">
2839
- <ng-content select="svg\\:g[ui-chart-grid]" />
2840
- <svg:g class="chart-areas">
2841
- @for (s of series(); track s.seriesKey) {
2842
- <svg:path
2843
- class="chart-area"
2844
- [attr.d]="s.areaPath"
2845
- [attr.fill]="areaFill(s.seriesKey, s.color)"
2846
- stroke="none" />
2847
- <svg:path
2848
- class="chart-area-stroke"
2849
- [attr.d]="s.linePath"
2850
- [attr.stroke]="s.color"
2851
- [attr.stroke-width]="strokeWidth()"
2852
- fill="none"
2853
- stroke-linecap="round"
2854
- stroke-linejoin="round" />
2855
- @if (showDots()) {
2856
- @for (p of s.points; track p.datumIndex) {
2857
- <svg:circle
2858
- class="chart-dot cursor-pointer outline-none"
2859
- [attr.cx]="p.x"
2860
- [attr.cy]="p.y"
2861
- [attr.r]="dotRadius()"
2862
- [attr.fill]="s.color"
2863
- [attr.aria-label]="pointAriaLabel(p)"
2864
- tabindex="0"
2865
- (focus)="setActivePoint($event, p)"
2866
- (blur)="clearActivePoint()"
2867
- (click)="emitClick(p)"
2868
- (keydown.enter)="emitClick(p)"
2869
- (keydown.space)="emitClick(p); $event.preventDefault()" />
2870
- }
2871
- }
2872
- }
2873
- </svg:g>
2874
- <ng-content select="svg\\:g[ui-chart-axis-x]" />
2875
- <ng-content select="svg\\:g[ui-chart-axis-y]" />
2876
- <ng-content select="svg\\:g[ui-chart-crosshair]" />
2877
- <ng-content select="svg:g[ui-chart-brush]" />
2878
- </svg:g>
2879
- </svg:svg>
2880
- <ng-content select="ui-chart-tooltip" />
2881
- <ng-content select="ui-chart-legend" />
2882
- <ng-content select="ui-chart-zoom-controls" />
2883
- `,
2884
- }]
2885
- }], ctorParameters: () => [], propDecorators: { data: [{ type: i0.Input, args: [{ isSignal: true, alias: "data", required: true }] }], xKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "xKey", required: true }] }], orientation: [{ type: i0.Input, args: [{ isSignal: true, alias: "orientation", required: false }] }], stacked: [{ type: i0.Input, args: [{ isSignal: true, alias: "stacked", required: false }] }], expanded: [{ type: i0.Input, args: [{ isSignal: true, alias: "expanded", required: false }] }], gradient: [{ type: i0.Input, args: [{ isSignal: true, alias: "gradient", required: false }] }], curve: [{ type: i0.Input, args: [{ isSignal: true, alias: "curve", required: false }] }], margin: [{ type: i0.Input, args: [{ isSignal: true, alias: "margin", required: false }] }], strokeWidth: [{ type: i0.Input, args: [{ isSignal: true, alias: "strokeWidth", required: false }] }], showDots: [{ type: i0.Input, args: [{ isSignal: true, alias: "showDots", required: false }] }], dotRadius: [{ type: i0.Input, args: [{ isSignal: true, alias: "dotRadius", required: false }] }], pointClick: [{ type: i0.Output, args: ["pointClick"] }] } });
2886
-
2887
- function computePieLayout(input) {
2888
- const { data, valueKey, nameKey, seriesKeys, innerWidth, innerHeight, innerRadius, padAngle, cornerRadius, startAngle, endAngle, activeIndex, activeOffset = 12, } = input;
2889
- const outerRadius = Math.max(0, Math.min(innerWidth, innerHeight) / 2);
2890
- const centerX = innerWidth / 2;
2891
- const centerY = innerHeight / 2;
2892
- if (data.length === 0 || outerRadius === 0) {
2893
- return { slices: [], centerX, centerY, outerRadius };
2894
- }
2895
- const pieGen = pie()
2896
- .value((d) => Number(d[valueKey] ?? 0))
2897
- .sort(null)
2898
- .padAngle(padAngle)
2899
- .startAngle(startAngle)
2900
- .endAngle(endAngle);
2901
- const arcGen = arc()
2902
- .innerRadius(innerRadius)
2903
- .outerRadius(outerRadius)
2904
- .cornerRadius(cornerRadius);
2905
- const arcs = pieGen(data);
2906
- const slices = arcs.map((a, i) => {
2907
- const d = a.data;
2908
- const key = seriesKeys?.[i] ?? String(d[nameKey] ?? i);
2909
- const centroid = arcGen.centroid(a);
2910
- const magnitude = Math.hypot(centroid[0], centroid[1]) || 1;
2911
- const isActive = i === activeIndex;
2912
- return {
2913
- seriesKey: key,
2914
- name: String(d[nameKey] ?? key),
2915
- value: Number(d[valueKey] ?? 0),
2916
- datumIndex: i,
2917
- color: seriesColorVar(key),
2918
- arcPath: arcGen(a) ?? '',
2919
- centroid,
2920
- startAngle: a.startAngle,
2921
- endAngle: a.endAngle,
2922
- translateX: isActive ? (centroid[0] / magnitude) * activeOffset : 0,
2923
- translateY: isActive ? (centroid[1] / magnitude) * activeOffset : 0,
2924
- };
2925
- });
2926
- return { slices, centerX, centerY, outerRadius };
2927
- }
2928
-
2929
- const DEFAULT_MARGIN$3 = { top: 8, right: 8, bottom: 8, left: 8 };
2930
- class PieChart {
2931
- root = inject(ChartContext);
2932
- data = input.required(/* @ts-ignore */
2933
- ...(ngDevMode ? [{ debugName: "data" }] : /* istanbul ignore next */ []));
2934
- valueKey = input.required(/* @ts-ignore */
2935
- ...(ngDevMode ? [{ debugName: "valueKey" }] : /* istanbul ignore next */ []));
2936
- nameKey = input.required(/* @ts-ignore */
2937
- ...(ngDevMode ? [{ debugName: "nameKey" }] : /* istanbul ignore next */ []));
2938
- seriesKeys = input(undefined, /* @ts-ignore */
2939
- ...(ngDevMode ? [{ debugName: "seriesKeys" }] : /* istanbul ignore next */ []));
2940
- margin = input(DEFAULT_MARGIN$3, /* @ts-ignore */
2941
- ...(ngDevMode ? [{ debugName: "margin" }] : /* istanbul ignore next */ []));
2942
- innerRadius = input(0, /* @ts-ignore */
2943
- ...(ngDevMode ? [{ debugName: "innerRadius" }] : /* istanbul ignore next */ []));
2944
- padAngle = input(0.01, /* @ts-ignore */
2945
- ...(ngDevMode ? [{ debugName: "padAngle" }] : /* istanbul ignore next */ []));
2946
- cornerRadius = input(0, /* @ts-ignore */
2947
- ...(ngDevMode ? [{ debugName: "cornerRadius" }] : /* istanbul ignore next */ []));
2948
- startAngle = input(-Math.PI / 2, /* @ts-ignore */
2949
- ...(ngDevMode ? [{ debugName: "startAngle" }] : /* istanbul ignore next */ []));
2950
- endAngle = input((3 * Math.PI) / 2, /* @ts-ignore */
2951
- ...(ngDevMode ? [{ debugName: "endAngle" }] : /* istanbul ignore next */ []));
2952
- showLabels = input(false, /* @ts-ignore */
2953
- ...(ngDevMode ? [{ debugName: "showLabels" }] : /* istanbul ignore next */ []));
2954
- activeIndex = input(undefined, /* @ts-ignore */
2955
- ...(ngDevMode ? [{ debugName: "activeIndex" }] : /* istanbul ignore next */ []));
2956
- activeOffset = input(12, /* @ts-ignore */
2957
- ...(ngDevMode ? [{ debugName: "activeOffset" }] : /* istanbul ignore next */ []));
2958
- sliceClick = output();
2959
- innerWidth = computed(() => Math.max(0, this.root.dimensions().width - this.margin().left - this.margin().right), /* @ts-ignore */
2960
- ...(ngDevMode ? [{ debugName: "innerWidth" }] : /* istanbul ignore next */ []));
2961
- innerHeight = computed(() => Math.max(0, this.root.dimensions().height - this.margin().top - this.margin().bottom), /* @ts-ignore */
2962
- ...(ngDevMode ? [{ debugName: "innerHeight" }] : /* istanbul ignore next */ []));
2963
- layout = computed(() => computePieLayout({
2964
- data: this.data(),
2965
- valueKey: this.valueKey(),
2966
- nameKey: this.nameKey(),
2967
- seriesKeys: this.seriesKeys(),
2968
- innerWidth: this.innerWidth(),
2969
- innerHeight: this.innerHeight(),
2970
- innerRadius: this.innerRadius(),
2971
- padAngle: this.padAngle(),
2972
- cornerRadius: this.cornerRadius(),
2973
- startAngle: this.startAngle(),
2974
- endAngle: this.endAngle(),
2975
- activeIndex: this.activeIndex(),
2976
- activeOffset: this.activeOffset(),
2977
- }), /* @ts-ignore */
2978
- ...(ngDevMode ? [{ debugName: "layout" }] : /* istanbul ignore next */ []));
2979
- viewBox = computed(() => {
2980
- const { width, height } = this.root.dimensions();
2981
- return `0 0 ${Math.max(0, width)} ${Math.max(0, height)}`;
2982
- }, /* @ts-ignore */
2983
- ...(ngDevMode ? [{ debugName: "viewBox" }] : /* istanbul ignore next */ []));
2984
- innerTransform = computed(() => `translate(${this.margin().left},${this.margin().top})`, /* @ts-ignore */
2985
- ...(ngDevMode ? [{ debugName: "innerTransform" }] : /* istanbul ignore next */ []));
2986
- ariaSummary = computed(() => `Pie chart, ${this.data().length} slices.`, /* @ts-ignore */
2987
- ...(ngDevMode ? [{ debugName: "ariaSummary" }] : /* istanbul ignore next */ []));
2988
- sliceAriaLabel(s) {
2989
- return `${s.name}: ${s.value}`;
2990
- }
2991
- sliceTransform(s) {
2992
- return s.translateX || s.translateY ? `translate(${s.translateX}, ${s.translateY})` : null;
2993
- }
2994
- emitClick(s) {
2995
- this.sliceClick.emit({
2996
- seriesKey: s.seriesKey,
2997
- name: s.name,
2998
- value: s.value,
2999
- datumIndex: s.datumIndex,
3000
- datum: this.data()[s.datumIndex],
3001
- });
3002
- }
3003
- setActive(event, s) {
3004
- const clientX = event instanceof PointerEvent ? event.clientX : event.target.getBoundingClientRect().left;
3005
- const clientY = event instanceof PointerEvent ? event.clientY : event.target.getBoundingClientRect().top;
3006
- this.root.activePoint.set({
3007
- index: s.datumIndex,
3008
- datumIndex: s.datumIndex,
3009
- seriesKey: s.seriesKey,
3010
- clientX,
3011
- clientY,
3012
- });
3013
- }
3014
- clearActive() {
3015
- this.root.activePoint.set(null);
3016
- }
3017
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.0", ngImport: i0, type: PieChart, deps: [], target: i0.ɵɵFactoryTarget.Component });
3018
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.0", type: PieChart, isStandalone: true, selector: "ui-pie-chart", inputs: { data: { classPropertyName: "data", publicName: "data", isSignal: true, isRequired: true, transformFunction: null }, valueKey: { classPropertyName: "valueKey", publicName: "valueKey", isSignal: true, isRequired: true, transformFunction: null }, nameKey: { classPropertyName: "nameKey", publicName: "nameKey", isSignal: true, isRequired: true, transformFunction: null }, seriesKeys: { classPropertyName: "seriesKeys", publicName: "seriesKeys", isSignal: true, isRequired: false, transformFunction: null }, margin: { classPropertyName: "margin", publicName: "margin", isSignal: true, isRequired: false, transformFunction: null }, innerRadius: { classPropertyName: "innerRadius", publicName: "innerRadius", isSignal: true, isRequired: false, transformFunction: null }, padAngle: { classPropertyName: "padAngle", publicName: "padAngle", isSignal: true, isRequired: false, transformFunction: null }, cornerRadius: { classPropertyName: "cornerRadius", publicName: "cornerRadius", isSignal: true, isRequired: false, transformFunction: null }, startAngle: { classPropertyName: "startAngle", publicName: "startAngle", isSignal: true, isRequired: false, transformFunction: null }, endAngle: { classPropertyName: "endAngle", publicName: "endAngle", isSignal: true, isRequired: false, transformFunction: null }, showLabels: { classPropertyName: "showLabels", publicName: "showLabels", isSignal: true, isRequired: false, transformFunction: null }, activeIndex: { classPropertyName: "activeIndex", publicName: "activeIndex", isSignal: true, isRequired: false, transformFunction: null }, activeOffset: { classPropertyName: "activeOffset", publicName: "activeOffset", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { sliceClick: "sliceClick" }, host: { classAttribute: "relative block h-full w-full" }, ngImport: i0, template: `
3019
- <svg:svg
3020
- class="block h-full w-full overflow-visible"
3021
- [attr.viewBox]="viewBox()"
3022
- preserveAspectRatio="xMidYMid meet"
3023
- role="img"
3024
- [attr.aria-label]="ariaSummary()">
3025
- <svg:g [attr.transform]="innerTransform()">
3026
- <svg:g [attr.transform]="'translate(' + layout().centerX + ',' + layout().centerY + ')'">
3027
- @for (s of layout().slices; track s.seriesKey) {
3028
- <svg:path
3029
- class="chart-slice cursor-pointer transition-opacity hover:opacity-80"
3030
- [attr.d]="s.arcPath"
3031
- [attr.fill]="s.color"
3032
- [attr.transform]="sliceTransform(s)"
3033
- [attr.aria-label]="sliceAriaLabel(s)"
3034
- tabindex="0"
3035
- (click)="emitClick(s)"
3036
- (keydown.enter)="emitClick(s)"
3037
- (keydown.space)="emitClick(s); $event.preventDefault()"
3038
- (pointerenter)="setActive($event, s)"
3039
- (pointermove)="setActive($event, s)"
3040
- (pointerleave)="clearActive()"
3041
- (focus)="setActive($event, s)"
3042
- (blur)="clearActive()" />
3043
- }
3044
- @if (showLabels()) {
3045
- @for (s of layout().slices; track s.seriesKey) {
3046
- <svg:text
3047
- class="chart-slice-label fill-foreground text-[10px]"
3048
- text-anchor="middle"
3049
- dominant-baseline="middle"
3050
- [attr.x]="s.centroid[0] + s.translateX"
3051
- [attr.y]="s.centroid[1] + s.translateY">
3052
- {{ s.value }}
3053
- </svg:text>
3054
- }
3055
- }
3056
- </svg:g>
3057
- </svg:g>
3058
- </svg:svg>
3059
- <div class="pointer-events-none absolute inset-0 flex items-center justify-center">
3060
- <ng-content select="ui-pie-center" />
3061
- </div>
3062
- <ng-content select="ui-chart-tooltip" />
3063
- <ng-content select="ui-chart-legend" />
3064
- `, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
3065
- }
3066
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.0", ngImport: i0, type: PieChart, decorators: [{
3067
- type: Component,
3068
- args: [{
3069
- selector: 'ui-pie-chart',
3070
- changeDetection: ChangeDetectionStrategy.OnPush,
3071
- host: { class: 'relative block h-full w-full' },
3072
- template: `
3073
- <svg:svg
3074
- class="block h-full w-full overflow-visible"
3075
- [attr.viewBox]="viewBox()"
3076
- preserveAspectRatio="xMidYMid meet"
3077
- role="img"
3078
- [attr.aria-label]="ariaSummary()">
3079
- <svg:g [attr.transform]="innerTransform()">
3080
- <svg:g [attr.transform]="'translate(' + layout().centerX + ',' + layout().centerY + ')'">
3081
- @for (s of layout().slices; track s.seriesKey) {
3082
- <svg:path
3083
- class="chart-slice cursor-pointer transition-opacity hover:opacity-80"
3084
- [attr.d]="s.arcPath"
3085
- [attr.fill]="s.color"
3086
- [attr.transform]="sliceTransform(s)"
3087
- [attr.aria-label]="sliceAriaLabel(s)"
3088
- tabindex="0"
3089
- (click)="emitClick(s)"
3090
- (keydown.enter)="emitClick(s)"
3091
- (keydown.space)="emitClick(s); $event.preventDefault()"
3092
- (pointerenter)="setActive($event, s)"
3093
- (pointermove)="setActive($event, s)"
3094
- (pointerleave)="clearActive()"
3095
- (focus)="setActive($event, s)"
3096
- (blur)="clearActive()" />
3097
- }
3098
- @if (showLabels()) {
3099
- @for (s of layout().slices; track s.seriesKey) {
3100
- <svg:text
3101
- class="chart-slice-label fill-foreground text-[10px]"
3102
- text-anchor="middle"
3103
- dominant-baseline="middle"
3104
- [attr.x]="s.centroid[0] + s.translateX"
3105
- [attr.y]="s.centroid[1] + s.translateY">
3106
- {{ s.value }}
3107
- </svg:text>
3108
- }
3109
- }
3110
- </svg:g>
3111
- </svg:g>
3112
- </svg:svg>
3113
- <div class="pointer-events-none absolute inset-0 flex items-center justify-center">
3114
- <ng-content select="ui-pie-center" />
3115
- </div>
3116
- <ng-content select="ui-chart-tooltip" />
3117
- <ng-content select="ui-chart-legend" />
3118
- `,
3119
- }]
3120
- }], propDecorators: { data: [{ type: i0.Input, args: [{ isSignal: true, alias: "data", required: true }] }], valueKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "valueKey", required: true }] }], nameKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "nameKey", required: true }] }], seriesKeys: [{ type: i0.Input, args: [{ isSignal: true, alias: "seriesKeys", required: false }] }], margin: [{ type: i0.Input, args: [{ isSignal: true, alias: "margin", required: false }] }], innerRadius: [{ type: i0.Input, args: [{ isSignal: true, alias: "innerRadius", required: false }] }], padAngle: [{ type: i0.Input, args: [{ isSignal: true, alias: "padAngle", required: false }] }], cornerRadius: [{ type: i0.Input, args: [{ isSignal: true, alias: "cornerRadius", required: false }] }], startAngle: [{ type: i0.Input, args: [{ isSignal: true, alias: "startAngle", required: false }] }], endAngle: [{ type: i0.Input, args: [{ isSignal: true, alias: "endAngle", required: false }] }], showLabels: [{ type: i0.Input, args: [{ isSignal: true, alias: "showLabels", required: false }] }], activeIndex: [{ type: i0.Input, args: [{ isSignal: true, alias: "activeIndex", required: false }] }], activeOffset: [{ type: i0.Input, args: [{ isSignal: true, alias: "activeOffset", required: false }] }], sliceClick: [{ type: i0.Output, args: ["sliceClick"] }] } });
3121
-
3122
- function computeRadarLayout(input) {
3123
- const { data, axisKey, seriesKeys, innerWidth, innerHeight, levels, curve, grid } = input;
3124
- const radius = Math.max(0, Math.min(innerWidth, innerHeight) / 2);
3125
- const centerX = innerWidth / 2;
3126
- const centerY = innerHeight / 2;
3127
- if (data.length === 0 || seriesKeys.length === 0 || radius === 0) {
3128
- return { centerX, centerY, radius, axes: [], levels: [], maxValue: 0, series: [], grid };
3129
- }
3130
- const maxValue = input.maxValue ?? max(data, (d) => max(seriesKeys, (k) => Number(d[k] ?? 0)) ?? 0) ?? 1;
3131
- const angleStep = (2 * Math.PI) / data.length;
3132
- const angleFor = (i) => i * angleStep - Math.PI / 2;
3133
- const axes = data.map((d, i) => {
3134
- const angle = angleFor(i);
3135
- return {
3136
- name: String(d[axisKey] ?? i),
3137
- angle,
3138
- x: Math.cos(angle) * radius,
3139
- y: Math.sin(angle) * radius,
3140
- };
3141
- });
3142
- const levelValues = Array.from({ length: levels }, (_, index) => {
3143
- const value = ((index + 1) / levels) * maxValue;
3144
- const levelRadius = ((index + 1) / levels) * radius;
3145
- return {
3146
- value,
3147
- radius: levelRadius,
3148
- path: polygonPath(axes.map((axis) => ({
3149
- x: Math.cos(axis.angle) * levelRadius,
3150
- y: Math.sin(axis.angle) * levelRadius,
3151
- }))),
3152
- };
3153
- });
3154
- const curveFactory = curve === 'cardinal' ? curveCardinalClosed : curveLinearClosed;
3155
- const radial = lineRadial()
3156
- .angle((point) => point.angle + Math.PI / 2)
3157
- .radius((point) => (point.value / maxValue) * radius)
3158
- .curve(curveFactory);
3159
- const series = seriesKeys.map((seriesKey) => {
3160
- const points = data.map((datum, index) => {
3161
- const value = Number(datum[seriesKey] ?? 0);
3162
- const angle = angleFor(index);
3163
- const pointRadius = (value / maxValue) * radius;
3164
- return {
3165
- axis: String(datum[axisKey] ?? index),
3166
- value,
3167
- x: Math.cos(angle) * pointRadius,
3168
- y: Math.sin(angle) * pointRadius,
3169
- };
3170
- });
3171
- return {
3172
- seriesKey,
3173
- color: seriesColorVar(seriesKey),
3174
- path: radial(data.map((datum, index) => ({ angle: angleFor(index), value: Number(datum[seriesKey] ?? 0) }))) ?? '',
3175
- points,
3176
- };
3177
- });
3178
- return { centerX, centerY, radius, axes, levels: levelValues, maxValue, series, grid };
3179
- }
3180
- function polygonPath(points) {
3181
- if (points.length === 0) {
3182
- return '';
3183
- }
3184
- const [first, ...rest] = points;
3185
- return `M ${first.x} ${first.y} ${rest.map((point) => `L ${point.x} ${point.y}`).join(' ')} Z`;
3186
- }
3187
-
3188
- const DEFAULT_MARGIN$2 = { top: 24, right: 24, bottom: 24, left: 24 };
3189
- class RadarChart {
3190
- root = inject(ChartContext);
3191
- data = input.required(/* @ts-ignore */
3192
- ...(ngDevMode ? [{ debugName: "data" }] : /* istanbul ignore next */ []));
3193
- axisKey = input.required(/* @ts-ignore */
3194
- ...(ngDevMode ? [{ debugName: "axisKey" }] : /* istanbul ignore next */ []));
3195
- margin = input(DEFAULT_MARGIN$2, /* @ts-ignore */
3196
- ...(ngDevMode ? [{ debugName: "margin" }] : /* istanbul ignore next */ []));
3197
- curve = input('linear', /* @ts-ignore */
3198
- ...(ngDevMode ? [{ debugName: "curve" }] : /* istanbul ignore next */ []));
3199
- levels = input(4, /* @ts-ignore */
3200
- ...(ngDevMode ? [{ debugName: "levels" }] : /* istanbul ignore next */ []));
3201
- maxValue = input(undefined, /* @ts-ignore */
3202
- ...(ngDevMode ? [{ debugName: "maxValue" }] : /* istanbul ignore next */ []));
3203
- strokeWidth = input(2, /* @ts-ignore */
3204
- ...(ngDevMode ? [{ debugName: "strokeWidth" }] : /* istanbul ignore next */ []));
3205
- fillOpacity = input(0.2, /* @ts-ignore */
3206
- ...(ngDevMode ? [{ debugName: "fillOpacity" }] : /* istanbul ignore next */ []));
3207
- showLabels = input(true, /* @ts-ignore */
3208
- ...(ngDevMode ? [{ debugName: "showLabels" }] : /* istanbul ignore next */ []));
3209
- showDots = input(true, /* @ts-ignore */
3210
- ...(ngDevMode ? [{ debugName: "showDots" }] : /* istanbul ignore next */ []));
3211
- dotRadius = input(3, /* @ts-ignore */
3212
- ...(ngDevMode ? [{ debugName: "dotRadius" }] : /* istanbul ignore next */ []));
3213
- grid = input('circle', /* @ts-ignore */
3214
- ...(ngDevMode ? [{ debugName: "grid" }] : /* istanbul ignore next */ []));
3215
- gridFilled = input(false, /* @ts-ignore */
3216
- ...(ngDevMode ? [{ debugName: "gridFilled" }] : /* istanbul ignore next */ []));
3217
- showAxes = input(true, /* @ts-ignore */
3218
- ...(ngDevMode ? [{ debugName: "showAxes" }] : /* istanbul ignore next */ []));
3219
- linesOnly = input(false, /* @ts-ignore */
3220
- ...(ngDevMode ? [{ debugName: "linesOnly" }] : /* istanbul ignore next */ []));
3221
- innerWidth = computed(() => Math.max(0, this.root.dimensions().width - this.margin().left - this.margin().right), /* @ts-ignore */
3222
- ...(ngDevMode ? [{ debugName: "innerWidth" }] : /* istanbul ignore next */ []));
3223
- innerHeight = computed(() => Math.max(0, this.root.dimensions().height - this.margin().top - this.margin().bottom), /* @ts-ignore */
3224
- ...(ngDevMode ? [{ debugName: "innerHeight" }] : /* istanbul ignore next */ []));
3225
- layout = computed(() => computeRadarLayout({
3226
- data: this.data(),
3227
- axisKey: this.axisKey(),
3228
- seriesKeys: this.root.visibleSeriesKeys(),
3229
- innerWidth: this.innerWidth(),
3230
- innerHeight: this.innerHeight(),
3231
- maxValue: this.maxValue(),
3232
- levels: this.levels(),
3233
- curve: this.curve(),
3234
- grid: this.grid(),
3235
- }), /* @ts-ignore */
3236
- ...(ngDevMode ? [{ debugName: "layout" }] : /* istanbul ignore next */ []));
3237
- viewBox = computed(() => {
3238
- const { width, height } = this.root.dimensions();
3239
- return `0 0 ${Math.max(0, width)} ${Math.max(0, height)}`;
3240
- }, /* @ts-ignore */
3241
- ...(ngDevMode ? [{ debugName: "viewBox" }] : /* istanbul ignore next */ []));
3242
- innerTransform = computed(() => `translate(${this.margin().left},${this.margin().top})`, /* @ts-ignore */
3243
- ...(ngDevMode ? [{ debugName: "innerTransform" }] : /* istanbul ignore next */ []));
3244
- ariaSummary = computed(() => {
3245
- const keys = this.root.visibleSeriesKeys();
3246
- return `Radar chart, ${this.data().length} axes, ${keys.length} series.`;
3247
- }, /* @ts-ignore */
3248
- ...(ngDevMode ? [{ debugName: "ariaSummary" }] : /* istanbul ignore next */ []));
3249
- gridFill() {
3250
- return this.gridFilled() ? 'hsl(var(--muted))' : 'none';
3251
- }
3252
- gridFillOpacity(levelIndex) {
3253
- return this.gridFilled() ? Math.max(0.06, 0.18 - levelIndex * 0.02) : null;
3254
- }
3255
- dotAriaLabel(seriesKey, p) {
3256
- const label = this.root.config()[seriesKey]?.label ?? seriesKey;
3257
- return `${label} at ${p.axis}: ${p.value}`;
3258
- }
3259
- setActive(event, seriesKey, index) {
3260
- const clientX = event instanceof PointerEvent ? event.clientX : event.target.getBoundingClientRect().left;
3261
- const clientY = event instanceof PointerEvent ? event.clientY : event.target.getBoundingClientRect().top;
3262
- this.root.activePoint.set({ index, datumIndex: index, seriesKey, clientX, clientY });
3263
- }
3264
- clearActive() {
3265
- this.root.activePoint.set(null);
3266
- }
3267
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.0", ngImport: i0, type: RadarChart, deps: [], target: i0.ɵɵFactoryTarget.Component });
3268
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.0", type: RadarChart, isStandalone: true, selector: "ui-radar-chart", inputs: { data: { classPropertyName: "data", publicName: "data", isSignal: true, isRequired: true, transformFunction: null }, axisKey: { classPropertyName: "axisKey", publicName: "axisKey", isSignal: true, isRequired: true, transformFunction: null }, margin: { classPropertyName: "margin", publicName: "margin", isSignal: true, isRequired: false, transformFunction: null }, curve: { classPropertyName: "curve", publicName: "curve", isSignal: true, isRequired: false, transformFunction: null }, levels: { classPropertyName: "levels", publicName: "levels", isSignal: true, isRequired: false, transformFunction: null }, maxValue: { classPropertyName: "maxValue", publicName: "maxValue", isSignal: true, isRequired: false, transformFunction: null }, strokeWidth: { classPropertyName: "strokeWidth", publicName: "strokeWidth", isSignal: true, isRequired: false, transformFunction: null }, fillOpacity: { classPropertyName: "fillOpacity", publicName: "fillOpacity", isSignal: true, isRequired: false, transformFunction: null }, showLabels: { classPropertyName: "showLabels", publicName: "showLabels", isSignal: true, isRequired: false, transformFunction: null }, showDots: { classPropertyName: "showDots", publicName: "showDots", isSignal: true, isRequired: false, transformFunction: null }, dotRadius: { classPropertyName: "dotRadius", publicName: "dotRadius", isSignal: true, isRequired: false, transformFunction: null }, grid: { classPropertyName: "grid", publicName: "grid", isSignal: true, isRequired: false, transformFunction: null }, gridFilled: { classPropertyName: "gridFilled", publicName: "gridFilled", isSignal: true, isRequired: false, transformFunction: null }, showAxes: { classPropertyName: "showAxes", publicName: "showAxes", isSignal: true, isRequired: false, transformFunction: null }, linesOnly: { classPropertyName: "linesOnly", publicName: "linesOnly", isSignal: true, isRequired: false, transformFunction: null } }, host: { classAttribute: "relative block h-full w-full" }, ngImport: i0, template: `
3269
- <svg:svg
3270
- class="block h-full w-full overflow-visible"
3271
- [attr.viewBox]="viewBox()"
3272
- preserveAspectRatio="xMidYMid meet"
3273
- role="img"
3274
- [attr.aria-label]="ariaSummary()">
3275
- <svg:g [attr.transform]="innerTransform()">
3276
- <svg:g class="chart-radar" [attr.transform]="'translate(' + layout().centerX + ',' + layout().centerY + ')'">
3277
- @if (layout().grid !== 'none') {
3278
- @for (level of layout().levels; track level.value; let levelIndex = $index) {
3279
- @if (layout().grid === 'circle') {
3280
- <svg:circle
3281
- class="chart-radar-level stroke-border"
3282
- [attr.fill]="gridFill()"
3283
- [attr.fill-opacity]="gridFillOpacity(levelIndex)"
3284
- stroke-dasharray="2 2"
3285
- cx="0"
3286
- cy="0"
3287
- [attr.r]="level.radius" />
3288
- } @else {
3289
- <svg:path
3290
- class="chart-radar-level stroke-border"
3291
- [attr.d]="level.path"
3292
- [attr.fill]="gridFill()"
3293
- [attr.fill-opacity]="gridFillOpacity(levelIndex)"
3294
- stroke-dasharray="2 2" />
3295
- }
3296
- }
3297
- }
3298
- @if (showAxes()) {
3299
- @for (axis of layout().axes; track axis.name) {
3300
- <svg:line class="chart-radar-axis stroke-border" x1="0" y1="0" [attr.x2]="axis.x" [attr.y2]="axis.y" />
3301
- @if (showLabels()) {
3302
- <svg:text
3303
- class="chart-radar-axis-label fill-muted-foreground text-[10px]"
3304
- text-anchor="middle"
3305
- dominant-baseline="middle"
3306
- [attr.x]="axis.x * 1.12"
3307
- [attr.y]="axis.y * 1.12">
3308
- {{ axis.name }}
3309
- </svg:text>
3310
- }
3311
- }
3312
- }
3313
- @for (s of layout().series; track s.seriesKey) {
3314
- <svg:path
3315
- class="chart-radar-series"
3316
- [attr.d]="s.path"
3317
- [attr.stroke]="s.color"
3318
- [attr.fill]="linesOnly() ? 'none' : s.color"
3319
- [attr.fill-opacity]="linesOnly() ? null : fillOpacity()"
3320
- [attr.stroke-width]="strokeWidth()"
3321
- stroke-linejoin="round" />
3322
- @if (showDots()) {
3323
- @for (p of s.points; track p.axis; let i = $index) {
3324
- <svg:circle
3325
- class="chart-radar-dot cursor-pointer"
3326
- [attr.cx]="p.x"
3327
- [attr.cy]="p.y"
3328
- [attr.r]="dotRadius()"
3329
- [attr.fill]="s.color"
3330
- tabindex="0"
3331
- [attr.aria-label]="dotAriaLabel(s.seriesKey, p)"
3332
- (pointerenter)="setActive($event, s.seriesKey, i)"
3333
- (pointermove)="setActive($event, s.seriesKey, i)"
3334
- (pointerleave)="clearActive()"
3335
- (focus)="setActive($event, s.seriesKey, i)"
3336
- (blur)="clearActive()" />
3337
- }
3338
- }
3339
- }
3340
- </svg:g>
3341
- <ng-content />
3342
- </svg:g>
3343
- </svg:svg>
3344
- <ng-content select="ui-chart-tooltip" />
3345
- <ng-content select="ui-chart-legend" />
3346
- `, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
3347
- }
3348
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.0", ngImport: i0, type: RadarChart, decorators: [{
3349
- type: Component,
3350
- args: [{
3351
- selector: 'ui-radar-chart',
3352
- changeDetection: ChangeDetectionStrategy.OnPush,
3353
- host: { class: 'relative block h-full w-full' },
3354
- template: `
3355
- <svg:svg
3356
- class="block h-full w-full overflow-visible"
3357
- [attr.viewBox]="viewBox()"
3358
- preserveAspectRatio="xMidYMid meet"
3359
- role="img"
3360
- [attr.aria-label]="ariaSummary()">
3361
- <svg:g [attr.transform]="innerTransform()">
3362
- <svg:g class="chart-radar" [attr.transform]="'translate(' + layout().centerX + ',' + layout().centerY + ')'">
3363
- @if (layout().grid !== 'none') {
3364
- @for (level of layout().levels; track level.value; let levelIndex = $index) {
3365
- @if (layout().grid === 'circle') {
3366
- <svg:circle
3367
- class="chart-radar-level stroke-border"
3368
- [attr.fill]="gridFill()"
3369
- [attr.fill-opacity]="gridFillOpacity(levelIndex)"
3370
- stroke-dasharray="2 2"
3371
- cx="0"
3372
- cy="0"
3373
- [attr.r]="level.radius" />
3374
- } @else {
3375
- <svg:path
3376
- class="chart-radar-level stroke-border"
3377
- [attr.d]="level.path"
3378
- [attr.fill]="gridFill()"
3379
- [attr.fill-opacity]="gridFillOpacity(levelIndex)"
3380
- stroke-dasharray="2 2" />
3381
- }
3382
- }
3383
- }
3384
- @if (showAxes()) {
3385
- @for (axis of layout().axes; track axis.name) {
3386
- <svg:line class="chart-radar-axis stroke-border" x1="0" y1="0" [attr.x2]="axis.x" [attr.y2]="axis.y" />
3387
- @if (showLabels()) {
3388
- <svg:text
3389
- class="chart-radar-axis-label fill-muted-foreground text-[10px]"
3390
- text-anchor="middle"
3391
- dominant-baseline="middle"
3392
- [attr.x]="axis.x * 1.12"
3393
- [attr.y]="axis.y * 1.12">
3394
- {{ axis.name }}
3395
- </svg:text>
3396
- }
3397
- }
3398
- }
3399
- @for (s of layout().series; track s.seriesKey) {
3400
- <svg:path
3401
- class="chart-radar-series"
3402
- [attr.d]="s.path"
3403
- [attr.stroke]="s.color"
3404
- [attr.fill]="linesOnly() ? 'none' : s.color"
3405
- [attr.fill-opacity]="linesOnly() ? null : fillOpacity()"
3406
- [attr.stroke-width]="strokeWidth()"
3407
- stroke-linejoin="round" />
3408
- @if (showDots()) {
3409
- @for (p of s.points; track p.axis; let i = $index) {
3410
- <svg:circle
3411
- class="chart-radar-dot cursor-pointer"
3412
- [attr.cx]="p.x"
3413
- [attr.cy]="p.y"
3414
- [attr.r]="dotRadius()"
3415
- [attr.fill]="s.color"
3416
- tabindex="0"
3417
- [attr.aria-label]="dotAriaLabel(s.seriesKey, p)"
3418
- (pointerenter)="setActive($event, s.seriesKey, i)"
3419
- (pointermove)="setActive($event, s.seriesKey, i)"
3420
- (pointerleave)="clearActive()"
3421
- (focus)="setActive($event, s.seriesKey, i)"
3422
- (blur)="clearActive()" />
3423
- }
3424
- }
3425
- }
3426
- </svg:g>
3427
- <ng-content />
3428
- </svg:g>
3429
- </svg:svg>
3430
- <ng-content select="ui-chart-tooltip" />
3431
- <ng-content select="ui-chart-legend" />
3432
- `,
3433
- }]
3434
- }], propDecorators: { data: [{ type: i0.Input, args: [{ isSignal: true, alias: "data", required: true }] }], axisKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "axisKey", required: true }] }], margin: [{ type: i0.Input, args: [{ isSignal: true, alias: "margin", required: false }] }], curve: [{ type: i0.Input, args: [{ isSignal: true, alias: "curve", required: false }] }], levels: [{ type: i0.Input, args: [{ isSignal: true, alias: "levels", required: false }] }], maxValue: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxValue", required: false }] }], strokeWidth: [{ type: i0.Input, args: [{ isSignal: true, alias: "strokeWidth", required: false }] }], fillOpacity: [{ type: i0.Input, args: [{ isSignal: true, alias: "fillOpacity", required: false }] }], showLabels: [{ type: i0.Input, args: [{ isSignal: true, alias: "showLabels", required: false }] }], showDots: [{ type: i0.Input, args: [{ isSignal: true, alias: "showDots", required: false }] }], dotRadius: [{ type: i0.Input, args: [{ isSignal: true, alias: "dotRadius", required: false }] }], grid: [{ type: i0.Input, args: [{ isSignal: true, alias: "grid", required: false }] }], gridFilled: [{ type: i0.Input, args: [{ isSignal: true, alias: "gridFilled", required: false }] }], showAxes: [{ type: i0.Input, args: [{ isSignal: true, alias: "showAxes", required: false }] }], linesOnly: [{ type: i0.Input, args: [{ isSignal: true, alias: "linesOnly", required: false }] }] } });
3435
-
3436
- function computeRadialLayout(input) {
3437
- const { data, nameKey, valueKey, innerWidth, innerHeight, seriesKeys, trackPadding, startAngle, endAngle, cornerRadius, } = input;
3438
- const outerRadius = Math.max(0, Math.min(innerWidth, innerHeight) / 2);
3439
- const centerX = innerWidth / 2;
3440
- const centerY = innerHeight / 2;
3441
- if (data.length === 0 || outerRadius === 0) {
3442
- return { centerX, centerY, outerRadius, bars: [], maxValue: 0 };
3443
- }
3444
- const maxValue = input.maxValue ?? max(data, (d) => Number(d[valueKey] ?? 0)) ?? 1;
3445
- const trackCount = data.length;
3446
- const availableRadius = outerRadius - (trackCount - 1) * trackPadding;
3447
- const trackThickness = availableRadius / trackCount;
3448
- const bars = data.map((d, i) => {
3449
- const inner = i * (trackThickness + trackPadding);
3450
- const outer = inner + trackThickness;
3451
- const value = Number(d[valueKey] ?? 0);
3452
- const pct = maxValue === 0 ? 0 : value / maxValue;
3453
- const sweep = (endAngle - startAngle) * pct;
3454
- const valueEndAngle = startAngle + sweep;
3455
- const key = seriesKeys?.[i] ?? String(d[nameKey] ?? i);
3456
- const arcGen = arc().innerRadius(inner).outerRadius(outer).cornerRadius(cornerRadius);
3457
- return {
3458
- seriesKey: key,
3459
- name: String(d[nameKey] ?? key),
3460
- value,
3461
- datumIndex: i,
3462
- color: seriesColorVar(key),
3463
- arcPath: arcGen.startAngle(startAngle).endAngle(valueEndAngle)(null) ?? '',
3464
- backgroundPath: arcGen.startAngle(startAngle).endAngle(endAngle)(null) ?? '',
3465
- innerRadius: inner,
3466
- outerRadius: outer,
3467
- endAngle: valueEndAngle,
3468
- };
3469
- });
3470
- return { centerX, centerY, outerRadius, bars, maxValue };
3471
- }
3472
-
3473
- const DEFAULT_MARGIN$1 = { top: 8, right: 8, bottom: 8, left: 8 };
3474
- const defaultRadialValueFormatter = (value) => `${value}`;
3475
- class RadialChart {
3476
- root = inject(ChartContext);
3477
- data = input.required(/* @ts-ignore */
3478
- ...(ngDevMode ? [{ debugName: "data" }] : /* istanbul ignore next */ []));
3479
- nameKey = input.required(/* @ts-ignore */
3480
- ...(ngDevMode ? [{ debugName: "nameKey" }] : /* istanbul ignore next */ []));
3481
- valueKey = input.required(/* @ts-ignore */
3482
- ...(ngDevMode ? [{ debugName: "valueKey" }] : /* istanbul ignore next */ []));
3483
- seriesKeys = input(undefined, /* @ts-ignore */
3484
- ...(ngDevMode ? [{ debugName: "seriesKeys" }] : /* istanbul ignore next */ []));
3485
- margin = input(DEFAULT_MARGIN$1, /* @ts-ignore */
3486
- ...(ngDevMode ? [{ debugName: "margin" }] : /* istanbul ignore next */ []));
3487
- trackPadding = input(4, /* @ts-ignore */
3488
- ...(ngDevMode ? [{ debugName: "trackPadding" }] : /* istanbul ignore next */ []));
3489
- cornerRadius = input(8, /* @ts-ignore */
3490
- ...(ngDevMode ? [{ debugName: "cornerRadius" }] : /* istanbul ignore next */ []));
3491
- startAngle = input(-Math.PI / 2, /* @ts-ignore */
3492
- ...(ngDevMode ? [{ debugName: "startAngle" }] : /* istanbul ignore next */ []));
3493
- endAngle = input((3 * Math.PI) / 2, /* @ts-ignore */
3494
- ...(ngDevMode ? [{ debugName: "endAngle" }] : /* istanbul ignore next */ []));
3495
- maxValue = input(undefined, /* @ts-ignore */
3496
- ...(ngDevMode ? [{ debugName: "maxValue" }] : /* istanbul ignore next */ []));
3497
- showTrack = input(true, /* @ts-ignore */
3498
- ...(ngDevMode ? [{ debugName: "showTrack" }] : /* istanbul ignore next */ []));
3499
- showValueLabels = input(false, /* @ts-ignore */
3500
- ...(ngDevMode ? [{ debugName: "showValueLabels" }] : /* istanbul ignore next */ []));
3501
- valueLabelFormat = input(defaultRadialValueFormatter, /* @ts-ignore */
3502
- ...(ngDevMode ? [{ debugName: "valueLabelFormat" }] : /* istanbul ignore next */ []));
3503
- barClick = output();
3504
- innerWidth = computed(() => Math.max(0, this.root.dimensions().width - this.margin().left - this.margin().right), /* @ts-ignore */
3505
- ...(ngDevMode ? [{ debugName: "innerWidth" }] : /* istanbul ignore next */ []));
3506
- innerHeight = computed(() => Math.max(0, this.root.dimensions().height - this.margin().top - this.margin().bottom), /* @ts-ignore */
3507
- ...(ngDevMode ? [{ debugName: "innerHeight" }] : /* istanbul ignore next */ []));
3508
- layout = computed(() => computeRadialLayout({
3509
- data: this.data(),
3510
- nameKey: this.nameKey(),
3511
- valueKey: this.valueKey(),
3512
- seriesKeys: this.seriesKeys(),
3513
- innerWidth: this.innerWidth(),
3514
- innerHeight: this.innerHeight(),
3515
- trackPadding: this.trackPadding(),
3516
- startAngle: this.startAngle(),
3517
- endAngle: this.endAngle(),
3518
- cornerRadius: this.cornerRadius(),
3519
- maxValue: this.maxValue(),
3520
- }), /* @ts-ignore */
3521
- ...(ngDevMode ? [{ debugName: "layout" }] : /* istanbul ignore next */ []));
3522
- viewBox = computed(() => {
3523
- const { width, height } = this.root.dimensions();
3524
- return `0 0 ${Math.max(0, width)} ${Math.max(0, height)}`;
3525
- }, /* @ts-ignore */
3526
- ...(ngDevMode ? [{ debugName: "viewBox" }] : /* istanbul ignore next */ []));
3527
- innerTransform = computed(() => `translate(${this.margin().left},${this.margin().top})`, /* @ts-ignore */
3528
- ...(ngDevMode ? [{ debugName: "innerTransform" }] : /* istanbul ignore next */ []));
3529
- ariaSummary = computed(() => `Radial bar chart, ${this.data().length} tracks.`, /* @ts-ignore */
3530
- ...(ngDevMode ? [{ debugName: "ariaSummary" }] : /* istanbul ignore next */ []));
3531
- barAriaLabel(b) {
3532
- return `${b.name}: ${b.value}`;
3533
- }
3534
- formatValueLabel(b) {
3535
- return this.valueLabelFormat()(b.value, b.name);
3536
- }
3537
- barLabelX(b) {
3538
- const radius = (b.innerRadius + b.outerRadius) / 2 + 10;
3539
- return Math.sin(b.endAngle) * radius;
3540
- }
3541
- barLabelY(b) {
3542
- const radius = (b.innerRadius + b.outerRadius) / 2 + 10;
3543
- return -Math.cos(b.endAngle) * radius;
3544
- }
3545
- barLabelAnchor(b) {
3546
- return this.barLabelX(b) >= 0 ? 'start' : 'end';
3547
- }
3548
- emitClick(b) {
3549
- this.barClick.emit({
3550
- seriesKey: b.seriesKey,
3551
- name: b.name,
3552
- value: b.value,
3553
- datumIndex: b.datumIndex,
3554
- datum: this.data()[b.datumIndex],
3555
- });
3556
- }
3557
- setActive(event, b) {
3558
- const clientX = event instanceof PointerEvent ? event.clientX : event.target.getBoundingClientRect().left;
3559
- const clientY = event instanceof PointerEvent ? event.clientY : event.target.getBoundingClientRect().top;
3560
- this.root.activePoint.set({
3561
- index: b.datumIndex,
3562
- datumIndex: b.datumIndex,
3563
- seriesKey: b.seriesKey,
3564
- clientX,
3565
- clientY,
3566
- });
3567
- }
3568
- clearActive() {
3569
- this.root.activePoint.set(null);
3570
- }
3571
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.0", ngImport: i0, type: RadialChart, deps: [], target: i0.ɵɵFactoryTarget.Component });
3572
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.0", type: RadialChart, isStandalone: true, selector: "ui-radial-chart", inputs: { data: { classPropertyName: "data", publicName: "data", isSignal: true, isRequired: true, transformFunction: null }, nameKey: { classPropertyName: "nameKey", publicName: "nameKey", isSignal: true, isRequired: true, transformFunction: null }, valueKey: { classPropertyName: "valueKey", publicName: "valueKey", isSignal: true, isRequired: true, transformFunction: null }, seriesKeys: { classPropertyName: "seriesKeys", publicName: "seriesKeys", isSignal: true, isRequired: false, transformFunction: null }, margin: { classPropertyName: "margin", publicName: "margin", isSignal: true, isRequired: false, transformFunction: null }, trackPadding: { classPropertyName: "trackPadding", publicName: "trackPadding", isSignal: true, isRequired: false, transformFunction: null }, cornerRadius: { classPropertyName: "cornerRadius", publicName: "cornerRadius", isSignal: true, isRequired: false, transformFunction: null }, startAngle: { classPropertyName: "startAngle", publicName: "startAngle", isSignal: true, isRequired: false, transformFunction: null }, endAngle: { classPropertyName: "endAngle", publicName: "endAngle", isSignal: true, isRequired: false, transformFunction: null }, maxValue: { classPropertyName: "maxValue", publicName: "maxValue", isSignal: true, isRequired: false, transformFunction: null }, showTrack: { classPropertyName: "showTrack", publicName: "showTrack", isSignal: true, isRequired: false, transformFunction: null }, showValueLabels: { classPropertyName: "showValueLabels", publicName: "showValueLabels", isSignal: true, isRequired: false, transformFunction: null }, valueLabelFormat: { classPropertyName: "valueLabelFormat", publicName: "valueLabelFormat", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { barClick: "barClick" }, host: { classAttribute: "relative block h-full w-full" }, ngImport: i0, template: `
3573
- <svg:svg
3574
- class="block h-full w-full overflow-visible"
3575
- [attr.viewBox]="viewBox()"
3576
- preserveAspectRatio="xMidYMid meet"
3577
- role="img"
3578
- [attr.aria-label]="ariaSummary()">
3579
- <svg:g [attr.transform]="innerTransform()">
3580
- <svg:g [attr.transform]="'translate(' + layout().centerX + ',' + layout().centerY + ')'">
3581
- @for (b of layout().bars; track b.seriesKey) {
3582
- @if (showTrack()) {
3583
- <svg:path class="chart-radial-track fill-muted" [attr.d]="b.backgroundPath" />
3584
- }
3585
- <svg:path
3586
- class="chart-radial-bar cursor-pointer transition-opacity hover:opacity-80"
3587
- [attr.d]="b.arcPath"
3588
- [attr.fill]="b.color"
3589
- [attr.aria-label]="barAriaLabel(b)"
3590
- tabindex="0"
3591
- (click)="emitClick(b)"
3592
- (keydown.enter)="emitClick(b)"
3593
- (keydown.space)="emitClick(b); $event.preventDefault()"
3594
- (pointerenter)="setActive($event, b)"
3595
- (pointermove)="setActive($event, b)"
3596
- (pointerleave)="clearActive()"
3597
- (focus)="setActive($event, b)"
3598
- (blur)="clearActive()" />
3599
- @if (showValueLabels()) {
3600
- <svg:text
3601
- class="chart-radial-value pointer-events-none fill-muted-foreground text-[10px]"
3602
- [attr.x]="barLabelX(b)"
3603
- [attr.y]="barLabelY(b)"
3604
- [attr.text-anchor]="barLabelAnchor(b)"
3605
- dominant-baseline="middle">
3606
- {{ formatValueLabel(b) }}
3607
- </svg:text>
3608
- }
3609
- }
3610
- </svg:g>
3611
- </svg:g>
3612
- </svg:svg>
3613
- <div class="pointer-events-none absolute inset-0 flex items-center justify-center">
3614
- <ng-content select="ui-radial-center" />
3615
- </div>
3616
- <ng-content select="ui-chart-tooltip" />
3617
- <ng-content select="ui-chart-legend" />
3618
- `, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
3619
- }
3620
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.0", ngImport: i0, type: RadialChart, decorators: [{
3621
- type: Component,
3622
- args: [{
3623
- selector: 'ui-radial-chart',
3624
- changeDetection: ChangeDetectionStrategy.OnPush,
3625
- host: { class: 'relative block h-full w-full' },
3626
- template: `
3627
- <svg:svg
3628
- class="block h-full w-full overflow-visible"
3629
- [attr.viewBox]="viewBox()"
3630
- preserveAspectRatio="xMidYMid meet"
3631
- role="img"
3632
- [attr.aria-label]="ariaSummary()">
3633
- <svg:g [attr.transform]="innerTransform()">
3634
- <svg:g [attr.transform]="'translate(' + layout().centerX + ',' + layout().centerY + ')'">
3635
- @for (b of layout().bars; track b.seriesKey) {
3636
- @if (showTrack()) {
3637
- <svg:path class="chart-radial-track fill-muted" [attr.d]="b.backgroundPath" />
3638
- }
3639
- <svg:path
3640
- class="chart-radial-bar cursor-pointer transition-opacity hover:opacity-80"
3641
- [attr.d]="b.arcPath"
3642
- [attr.fill]="b.color"
3643
- [attr.aria-label]="barAriaLabel(b)"
3644
- tabindex="0"
3645
- (click)="emitClick(b)"
3646
- (keydown.enter)="emitClick(b)"
3647
- (keydown.space)="emitClick(b); $event.preventDefault()"
3648
- (pointerenter)="setActive($event, b)"
3649
- (pointermove)="setActive($event, b)"
3650
- (pointerleave)="clearActive()"
3651
- (focus)="setActive($event, b)"
3652
- (blur)="clearActive()" />
3653
- @if (showValueLabels()) {
3654
- <svg:text
3655
- class="chart-radial-value pointer-events-none fill-muted-foreground text-[10px]"
3656
- [attr.x]="barLabelX(b)"
3657
- [attr.y]="barLabelY(b)"
3658
- [attr.text-anchor]="barLabelAnchor(b)"
3659
- dominant-baseline="middle">
3660
- {{ formatValueLabel(b) }}
3661
- </svg:text>
3662
- }
3663
- }
3664
- </svg:g>
3665
- </svg:g>
3666
- </svg:svg>
3667
- <div class="pointer-events-none absolute inset-0 flex items-center justify-center">
3668
- <ng-content select="ui-radial-center" />
3669
- </div>
3670
- <ng-content select="ui-chart-tooltip" />
3671
- <ng-content select="ui-chart-legend" />
3672
- `,
3673
- }]
3674
- }], propDecorators: { data: [{ type: i0.Input, args: [{ isSignal: true, alias: "data", required: true }] }], nameKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "nameKey", required: true }] }], valueKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "valueKey", required: true }] }], seriesKeys: [{ type: i0.Input, args: [{ isSignal: true, alias: "seriesKeys", required: false }] }], margin: [{ type: i0.Input, args: [{ isSignal: true, alias: "margin", required: false }] }], trackPadding: [{ type: i0.Input, args: [{ isSignal: true, alias: "trackPadding", required: false }] }], cornerRadius: [{ type: i0.Input, args: [{ isSignal: true, alias: "cornerRadius", required: false }] }], startAngle: [{ type: i0.Input, args: [{ isSignal: true, alias: "startAngle", required: false }] }], endAngle: [{ type: i0.Input, args: [{ isSignal: true, alias: "endAngle", required: false }] }], maxValue: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxValue", required: false }] }], showTrack: [{ type: i0.Input, args: [{ isSignal: true, alias: "showTrack", required: false }] }], showValueLabels: [{ type: i0.Input, args: [{ isSignal: true, alias: "showValueLabels", required: false }] }], valueLabelFormat: [{ type: i0.Input, args: [{ isSignal: true, alias: "valueLabelFormat", required: false }] }], barClick: [{ type: i0.Output, args: ["barClick"] }] } });
3675
-
3676
- function nice(extent) {
3677
- const [lo, hi] = extent;
3678
- if (lo === undefined || hi === undefined)
3679
- return [0, 1];
3680
- if (lo === hi)
3681
- return [lo - 1, hi + 1];
3682
- return [lo, hi];
3683
- }
3684
- function resolveScatterDomains(input) {
3685
- const xValues = input.data.map((d) => Number(d[input.xKey] ?? 0));
3686
- const yValues = input.data.map((d) => Number(d[input.yKey] ?? 0));
3687
- return {
3688
- xDomain: (input.xDomain ?? nice(extent(xValues))),
3689
- yDomain: (input.yDomain ?? nice(extent(yValues))),
3690
- };
3691
- }
3692
- function computeScatterLayout(input) {
3693
- const { data, xKey, yKey, sizeKey, seriesKey, seriesKeys, innerWidth, innerHeight, minPointRadius, maxPointRadius } = input;
3694
- const xValues = data.map((d) => Number(d[xKey] ?? 0));
3695
- const yValues = data.map((d) => Number(d[yKey] ?? 0));
3696
- const sizeValues = sizeKey ? data.map((d) => Number(d[sizeKey] ?? 0)) : [];
3697
- const { xDomain, yDomain } = resolveScatterDomains(input);
3698
- const xScale = scaleLinear()
3699
- .domain(xDomain)
3700
- .range([0, innerWidth]);
3701
- const yScale = scaleLinear()
3702
- .domain(yDomain)
3703
- .range([innerHeight, 0]);
3704
- let sizeScale = null;
3705
- if (sizeKey && sizeValues.length > 0) {
3706
- const [sLo, sHi] = extent(sizeValues);
3707
- sizeScale = scaleLinear()
3708
- .domain([sLo ?? 0, sHi ?? 1])
3709
- .range([minPointRadius, maxPointRadius]);
3710
- }
3711
- const fallbackKey = seriesKeys[0] ?? 'value';
3712
- const points = data.flatMap((d, i) => {
3713
- const key = seriesKey ? String(d[seriesKey] ?? fallbackKey) : fallbackKey;
3714
- const sz = sizeKey ? Number(d[sizeKey] ?? 0) : 0;
3715
- const rawX = xValues[i];
3716
- const rawY = yValues[i];
3717
- if (rawX < xDomain[0] || rawX > xDomain[1] || rawY < yDomain[0] || rawY > yDomain[1]) {
3718
- return [];
3719
- }
3720
- return [
3721
- {
3722
- seriesKey: key,
3723
- color: seriesColorVar(key),
3724
- x: xScale(rawX),
3725
- y: yScale(rawY),
3726
- radius: sizeScale ? sizeScale(sz) : minPointRadius,
3727
- datumIndex: i,
3728
- rawX,
3729
- rawY,
3730
- rawSize: sz,
3731
- },
3732
- ];
3733
- });
3734
- return { points, xScale, yScale, xDomain, yDomain };
3735
- }
3736
-
3737
- const DEFAULT_MARGIN = { top: 8, right: 8, bottom: 24, left: 40 };
3738
- /**
3739
- * Scatter chart — one dot per datum. Both axes are linear; color can
3740
- * come from a fixed series key or per-row via `seriesKey` field. Point
3741
- * radius optionally scales with `sizeKey`.
3742
- */
3743
- class ScatterChart {
3744
- root = inject(ChartContext);
3745
- viewport = inject(ScatterViewportContext);
3746
- data = input.required(/* @ts-ignore */
3747
- ...(ngDevMode ? [{ debugName: "data" }] : /* istanbul ignore next */ []));
3748
- xKey = input.required(/* @ts-ignore */
3749
- ...(ngDevMode ? [{ debugName: "xKey" }] : /* istanbul ignore next */ []));
3750
- yKey = input.required(/* @ts-ignore */
3751
- ...(ngDevMode ? [{ debugName: "yKey" }] : /* istanbul ignore next */ []));
3752
- /** Optional numeric field to drive point radius. */
3753
- sizeKey = input(undefined, /* @ts-ignore */
3754
- ...(ngDevMode ? [{ debugName: "sizeKey" }] : /* istanbul ignore next */ []));
3755
- /** Optional field on each datum used as color key. */
3756
- seriesKey = input(undefined, /* @ts-ignore */
3757
- ...(ngDevMode ? [{ debugName: "seriesKey" }] : /* istanbul ignore next */ []));
3758
- margin = input(DEFAULT_MARGIN, /* @ts-ignore */
3759
- ...(ngDevMode ? [{ debugName: "margin" }] : /* istanbul ignore next */ []));
3760
- minPointRadius = input(3, /* @ts-ignore */
3761
- ...(ngDevMode ? [{ debugName: "minPointRadius" }] : /* istanbul ignore next */ []));
3762
- maxPointRadius = input(12, /* @ts-ignore */
3763
- ...(ngDevMode ? [{ debugName: "maxPointRadius" }] : /* istanbul ignore next */ []));
3764
- xDomain = input(undefined, /* @ts-ignore */
3765
- ...(ngDevMode ? [{ debugName: "xDomain" }] : /* istanbul ignore next */ []));
3766
- yDomain = input(undefined, /* @ts-ignore */
3767
- ...(ngDevMode ? [{ debugName: "yDomain" }] : /* istanbul ignore next */ []));
3768
- pointClick = output();
3769
- innerWidth = computed(() => Math.max(0, this.root.dimensions().width - this.margin().left - this.margin().right), /* @ts-ignore */
3770
- ...(ngDevMode ? [{ debugName: "innerWidth" }] : /* istanbul ignore next */ []));
3771
- innerHeight = computed(() => Math.max(0, this.root.dimensions().height - this.margin().top - this.margin().bottom), /* @ts-ignore */
3772
- ...(ngDevMode ? [{ debugName: "innerHeight" }] : /* istanbul ignore next */ []));
3773
- resolvedDomains = computed(() => resolveScatterDomains({
3774
- data: this.data(),
3775
- xKey: this.xKey(),
3776
- yKey: this.yKey(),
3777
- xDomain: this.xDomain(),
3778
- yDomain: this.yDomain(),
3779
- }), /* @ts-ignore */
3780
- ...(ngDevMode ? [{ debugName: "resolvedDomains" }] : /* istanbul ignore next */ []));
3781
- currentXDomain = computed(() => this.viewport.zoomXDomain() ?? this.resolvedDomains().xDomain, /* @ts-ignore */
3782
- ...(ngDevMode ? [{ debugName: "currentXDomain" }] : /* istanbul ignore next */ []));
3783
- currentYDomain = computed(() => this.viewport.zoomYDomain() ?? this.resolvedDomains().yDomain, /* @ts-ignore */
3784
- ...(ngDevMode ? [{ debugName: "currentYDomain" }] : /* istanbul ignore next */ []));
3785
- layout = computed(() => computeScatterLayout({
3786
- data: this.data(),
3787
- xKey: this.xKey(),
3788
- yKey: this.yKey(),
3789
- sizeKey: this.sizeKey(),
3790
- seriesKey: this.seriesKey(),
3791
- seriesKeys: this.root.visibleSeriesKeys(),
3792
- innerWidth: this.innerWidth(),
3793
- innerHeight: this.innerHeight(),
3794
- minPointRadius: this.minPointRadius(),
3795
- maxPointRadius: this.maxPointRadius(),
3796
- xDomain: this.currentXDomain(),
3797
- yDomain: this.currentYDomain(),
3798
- }), /* @ts-ignore */
3799
- ...(ngDevMode ? [{ debugName: "layout" }] : /* istanbul ignore next */ []));
3800
- viewBox = computed(() => {
3801
- const { width, height } = this.root.dimensions();
3802
- return `0 0 ${Math.max(0, width)} ${Math.max(0, height)}`;
3803
- }, /* @ts-ignore */
3804
- ...(ngDevMode ? [{ debugName: "viewBox" }] : /* istanbul ignore next */ []));
3805
- innerTransform = computed(() => `translate(${this.margin().left},${this.margin().top})`, /* @ts-ignore */
3806
- ...(ngDevMode ? [{ debugName: "innerTransform" }] : /* istanbul ignore next */ []));
3807
- clipPathId = computed(() => `${this.root.id()}-scatter-clip`, /* @ts-ignore */
3808
- ...(ngDevMode ? [{ debugName: "clipPathId" }] : /* istanbul ignore next */ []));
3809
- ariaSummary = computed(() => `Scatter chart, ${this.data().length} points.`, /* @ts-ignore */
3810
- ...(ngDevMode ? [{ debugName: "ariaSummary" }] : /* istanbul ignore next */ []));
3811
- constructor() {
3812
- effect(() => {
3813
- const domains = this.resolvedDomains();
3814
- const layout = this.layout();
3815
- this.viewport.innerWidth.set(this.innerWidth());
3816
- this.viewport.innerHeight.set(this.innerHeight());
3817
- this.viewport.fullXDomain.set(domains.xDomain);
3818
- this.viewport.fullYDomain.set(domains.yDomain);
3819
- this.viewport.xScale.set(layout.xScale);
3820
- this.viewport.yScale.set(layout.yScale);
3821
- });
3822
- }
3823
- pointAriaLabel(p) {
3824
- return `${p.seriesKey}: x=${p.rawX}, y=${p.rawY}`;
3825
- }
3826
- emitClick(p) {
3827
- this.pointClick.emit({
3828
- seriesKey: p.seriesKey,
3829
- datumIndex: p.datumIndex,
3830
- x: p.rawX,
3831
- y: p.rawY,
3832
- datum: this.data()[p.datumIndex],
3833
- });
3834
- }
3835
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.0", ngImport: i0, type: ScatterChart, deps: [], target: i0.ɵɵFactoryTarget.Component });
3836
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.0", type: ScatterChart, isStandalone: true, selector: "ui-scatter-chart", inputs: { data: { classPropertyName: "data", publicName: "data", isSignal: true, isRequired: true, transformFunction: null }, xKey: { classPropertyName: "xKey", publicName: "xKey", isSignal: true, isRequired: true, transformFunction: null }, yKey: { classPropertyName: "yKey", publicName: "yKey", isSignal: true, isRequired: true, transformFunction: null }, sizeKey: { classPropertyName: "sizeKey", publicName: "sizeKey", isSignal: true, isRequired: false, transformFunction: null }, seriesKey: { classPropertyName: "seriesKey", publicName: "seriesKey", isSignal: true, isRequired: false, transformFunction: null }, margin: { classPropertyName: "margin", publicName: "margin", isSignal: true, isRequired: false, transformFunction: null }, minPointRadius: { classPropertyName: "minPointRadius", publicName: "minPointRadius", isSignal: true, isRequired: false, transformFunction: null }, maxPointRadius: { classPropertyName: "maxPointRadius", publicName: "maxPointRadius", isSignal: true, isRequired: false, transformFunction: null }, xDomain: { classPropertyName: "xDomain", publicName: "xDomain", isSignal: true, isRequired: false, transformFunction: null }, yDomain: { classPropertyName: "yDomain", publicName: "yDomain", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { pointClick: "pointClick" }, host: { classAttribute: "relative block h-full w-full" }, providers: [ScatterViewportContext], ngImport: i0, template: `
3837
- <svg:svg
3838
- class="block h-full w-full overflow-visible"
3839
- [attr.viewBox]="viewBox()"
3840
- preserveAspectRatio="none"
3841
- role="img"
3842
- [attr.aria-label]="ariaSummary()">
3843
- <svg:defs>
3844
- <svg:clipPath [attr.id]="clipPathId()">
3845
- <svg:rect x="0" y="0" [attr.width]="innerWidth()" [attr.height]="innerHeight()" />
3846
- </svg:clipPath>
3847
- </svg:defs>
3848
- <svg:g [attr.transform]="innerTransform()">
3849
- <svg:g class="chart-scatter" [attr.clip-path]="'url(#' + clipPathId() + ')'">
3850
- @for (p of layout().points; track p.datumIndex) {
3851
- <svg:circle
3852
- class="chart-scatter-point cursor-pointer transition-opacity hover:opacity-80"
3853
- [attr.cx]="p.x"
3854
- [attr.cy]="p.y"
3855
- [attr.r]="p.radius"
3856
- [attr.fill]="p.color"
3857
- [attr.aria-label]="pointAriaLabel(p)"
3858
- tabindex="0"
3859
- (click)="emitClick(p)"
3860
- (keydown.enter)="emitClick(p)"
3861
- (keydown.space)="emitClick(p); $event.preventDefault()" />
3862
- }
3863
- </svg:g>
3864
- <ng-content select="svg:g[ui-chart-brush]" />
3865
- </svg:g>
3866
- </svg:svg>
3867
- <ng-content select="ui-chart-legend" />
3868
- <ng-content select="ui-chart-zoom-controls" />
3869
- `, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
3870
- }
3871
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.0", ngImport: i0, type: ScatterChart, decorators: [{
3872
- type: Component,
3873
- args: [{
3874
- selector: 'ui-scatter-chart',
3875
- changeDetection: ChangeDetectionStrategy.OnPush,
3876
- providers: [ScatterViewportContext],
3877
- host: { class: 'relative block h-full w-full' },
3878
- template: `
3879
- <svg:svg
3880
- class="block h-full w-full overflow-visible"
3881
- [attr.viewBox]="viewBox()"
3882
- preserveAspectRatio="none"
3883
- role="img"
3884
- [attr.aria-label]="ariaSummary()">
3885
- <svg:defs>
3886
- <svg:clipPath [attr.id]="clipPathId()">
3887
- <svg:rect x="0" y="0" [attr.width]="innerWidth()" [attr.height]="innerHeight()" />
3888
- </svg:clipPath>
3889
- </svg:defs>
3890
- <svg:g [attr.transform]="innerTransform()">
3891
- <svg:g class="chart-scatter" [attr.clip-path]="'url(#' + clipPathId() + ')'">
3892
- @for (p of layout().points; track p.datumIndex) {
3893
- <svg:circle
3894
- class="chart-scatter-point cursor-pointer transition-opacity hover:opacity-80"
3895
- [attr.cx]="p.x"
3896
- [attr.cy]="p.y"
3897
- [attr.r]="p.radius"
3898
- [attr.fill]="p.color"
3899
- [attr.aria-label]="pointAriaLabel(p)"
3900
- tabindex="0"
3901
- (click)="emitClick(p)"
3902
- (keydown.enter)="emitClick(p)"
3903
- (keydown.space)="emitClick(p); $event.preventDefault()" />
3904
- }
3905
- </svg:g>
3906
- <ng-content select="svg:g[ui-chart-brush]" />
3907
- </svg:g>
3908
- </svg:svg>
3909
- <ng-content select="ui-chart-legend" />
3910
- <ng-content select="ui-chart-zoom-controls" />
3911
- `,
3912
- }]
3913
- }], ctorParameters: () => [], propDecorators: { data: [{ type: i0.Input, args: [{ isSignal: true, alias: "data", required: true }] }], xKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "xKey", required: true }] }], yKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "yKey", required: true }] }], sizeKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "sizeKey", required: false }] }], seriesKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "seriesKey", required: false }] }], margin: [{ type: i0.Input, args: [{ isSignal: true, alias: "margin", required: false }] }], minPointRadius: [{ type: i0.Input, args: [{ isSignal: true, alias: "minPointRadius", required: false }] }], maxPointRadius: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxPointRadius", required: false }] }], xDomain: [{ type: i0.Input, args: [{ isSignal: true, alias: "xDomain", required: false }] }], yDomain: [{ type: i0.Input, args: [{ isSignal: true, alias: "yDomain", required: false }] }], pointClick: [{ type: i0.Output, args: ["pointClick"] }] } });
3914
-
3915
- /*
3916
- * Public API Surface of @ojiepermana/angular/component/chart
3917
- */
3918
- // Core
3919
-
3920
- /**
3921
- * Generated bundle index. Do not edit.
3922
- */
3923
-
3924
- export { AreaChart, BarChart, CHART_DATA_ATTRIBUTE, CHART_THEMES, CartesianContext, ChartAxisX, ChartAxisY, ChartBrush, ChartContainer, ChartContext, ChartCrosshair, ChartGrid, ChartLegend, ChartPointerTracker, ChartStyle, ChartTooltip, ChartZoomControls, LineChart, PieCenter, PieChart, RadarChart, RadialCenter, RadialChart, ScatterChart, bandTicks, buildCartesianScales, buildChartCss, cloneLinear, computeAreaLayout, computeBarLayout, computeLineLayout, computePieLayout, computeRadarLayout, computeRadialLayout, computeScatterLayout, effectiveIndexRange, indexRangeSize, linearTicks, normalizeIndexRange, normalizeNumericDomain, panIndexRange, panNumericDomain, pointToBandAdapter, provideCartesianFromLineLayout, resolveScatterDomains, seriesColorVar, sliceByIndexRange, xScale, yScale, zoomIndexRange, zoomNumericDomain };
3925
- //# sourceMappingURL=ojiepermana-angular-component-chart.mjs.map