@ojiepermana/angular 22.0.1 → 22.0.29

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 (513) hide show
  1. package/README.md +61 -311
  2. package/fesm2022/ojiepermana-angular-chart.mjs +10 -0
  3. package/fesm2022/ojiepermana-angular-component.mjs +10 -0
  4. package/fesm2022/ojiepermana-angular-navigation.mjs +10 -0
  5. package/fesm2022/ojiepermana-angular-sdk.mjs +10 -0
  6. package/fesm2022/ojiepermana-angular-theme.mjs +4 -384
  7. package/fesm2022/ojiepermana-angular.mjs +15 -16
  8. package/package.json +25 -425
  9. package/types/ojiepermana-angular-chart.d.ts +1 -0
  10. package/types/ojiepermana-angular-component.d.ts +1 -0
  11. package/types/ojiepermana-angular-navigation.d.ts +1 -0
  12. package/types/ojiepermana-angular-sdk.d.ts +1 -0
  13. package/types/ojiepermana-angular-theme.d.ts +1 -116
  14. package/types/ojiepermana-angular.d.ts +5 -3
  15. package/.npmignore +0 -2
  16. package/collection.json +0 -30
  17. package/component/accordion/README.md +0 -195
  18. package/component/accordion/package.json +0 -4
  19. package/component/alert/README.md +0 -182
  20. package/component/alert/package.json +0 -4
  21. package/component/alert-dialog/README.md +0 -239
  22. package/component/alert-dialog/package.json +0 -4
  23. package/component/aspect-ratio/README.md +0 -112
  24. package/component/aspect-ratio/package.json +0 -4
  25. package/component/avatar/README.md +0 -176
  26. package/component/avatar/package.json +0 -4
  27. package/component/badge/README.md +0 -133
  28. package/component/badge/package.json +0 -4
  29. package/component/breadcrumb/README.md +0 -216
  30. package/component/breadcrumb/package.json +0 -4
  31. package/component/button/README.md +0 -139
  32. package/component/button/package.json +0 -4
  33. package/component/button-group/README.md +0 -208
  34. package/component/button-group/package.json +0 -4
  35. package/component/calendar/README.md +0 -132
  36. package/component/calendar/package.json +0 -4
  37. package/component/card/README.md +0 -220
  38. package/component/card/package.json +0 -4
  39. package/component/carousel/README.md +0 -276
  40. package/component/carousel/package.json +0 -4
  41. package/component/chart/README.md +0 -249
  42. package/component/chart/area/package.json +0 -4
  43. package/component/chart/bar/package.json +0 -4
  44. package/component/chart/line/package.json +0 -4
  45. package/component/chart/package.json +0 -4
  46. package/component/chart/pie/package.json +0 -4
  47. package/component/chart/radar/package.json +0 -4
  48. package/component/chart/radial/package.json +0 -4
  49. package/component/chart/scatter/package.json +0 -4
  50. package/component/checkbox/README.md +0 -149
  51. package/component/checkbox/package.json +0 -4
  52. package/component/collapsible/README.md +0 -195
  53. package/component/collapsible/package.json +0 -4
  54. package/component/combobox/README.md +0 -198
  55. package/component/combobox/package.json +0 -4
  56. package/component/command/README.md +0 -275
  57. package/component/command/package.json +0 -4
  58. package/component/composer/README.md +0 -235
  59. package/component/composer/package.json +0 -4
  60. package/component/context-menu/README.md +0 -267
  61. package/component/context-menu/package.json +0 -4
  62. package/component/date-picker/README.md +0 -177
  63. package/component/date-picker/package.json +0 -4
  64. package/component/dialog/README.md +0 -237
  65. package/component/dialog/package.json +0 -4
  66. package/component/drawer/README.md +0 -145
  67. package/component/drawer/package.json +0 -4
  68. package/component/dropdown-menu/README.md +0 -311
  69. package/component/dropdown-menu/package.json +0 -4
  70. package/component/editor/README.md +0 -136
  71. package/component/editor/package.json +0 -4
  72. package/component/empty/README.md +0 -183
  73. package/component/empty/package.json +0 -4
  74. package/component/form/README.md +0 -210
  75. package/component/form/package.json +0 -4
  76. package/component/hover-card/README.md +0 -146
  77. package/component/hover-card/package.json +0 -4
  78. package/component/input/README.md +0 -159
  79. package/component/input/package.json +0 -4
  80. package/component/input-group/README.md +0 -239
  81. package/component/input-group/package.json +0 -4
  82. package/component/input-otp/README.md +0 -278
  83. package/component/input-otp/package.json +0 -4
  84. package/component/item/README.md +0 -247
  85. package/component/item/package.json +0 -4
  86. package/component/kanban/README.md +0 -81
  87. package/component/kanban/package.json +0 -4
  88. package/component/kbd/README.md +0 -139
  89. package/component/kbd/package.json +0 -4
  90. package/component/label/README.md +0 -136
  91. package/component/label/package.json +0 -4
  92. package/component/menubar/README.md +0 -269
  93. package/component/menubar/package.json +0 -4
  94. package/component/native-select/README.md +0 -176
  95. package/component/native-select/package.json +0 -4
  96. package/component/navigation-menu/README.md +0 -160
  97. package/component/navigation-menu/package.json +0 -4
  98. package/component/pagination/README.md +0 -144
  99. package/component/pagination/package.json +0 -4
  100. package/component/pillbox/README.md +0 -67
  101. package/component/pillbox/package.json +0 -4
  102. package/component/popover/README.md +0 -43
  103. package/component/popover/package.json +0 -4
  104. package/component/progress/README.md +0 -160
  105. package/component/progress/package.json +0 -4
  106. package/component/radio/README.md +0 -209
  107. package/component/radio/package.json +0 -4
  108. package/component/resizable/README.md +0 -168
  109. package/component/resizable/package.json +0 -4
  110. package/component/scroll-area/README.md +0 -143
  111. package/component/scroll-area/package.json +0 -4
  112. package/component/select/README.md +0 -174
  113. package/component/select/package.json +0 -4
  114. package/component/separator/README.md +0 -170
  115. package/component/separator/package.json +0 -4
  116. package/component/sheet/README.md +0 -183
  117. package/component/sheet/package.json +0 -4
  118. package/component/skeleton/README.md +0 -158
  119. package/component/skeleton/package.json +0 -4
  120. package/component/slider/README.md +0 -207
  121. package/component/slider/package.json +0 -4
  122. package/component/spinner/README.md +0 -160
  123. package/component/spinner/package.json +0 -4
  124. package/component/switch/README.md +0 -166
  125. package/component/switch/package.json +0 -4
  126. package/component/table/README.md +0 -291
  127. package/component/table/package.json +0 -4
  128. package/component/tabs/README.md +0 -219
  129. package/component/tabs/package.json +0 -4
  130. package/component/textarea/README.md +0 -154
  131. package/component/textarea/package.json +0 -4
  132. package/component/timeline/README.md +0 -94
  133. package/component/timeline/package.json +0 -4
  134. package/component/toast/README.md +0 -321
  135. package/component/toast/package.json +0 -4
  136. package/component/toggle/README.md +0 -131
  137. package/component/toggle/package.json +0 -4
  138. package/component/toggle-group/README.md +0 -206
  139. package/component/toggle-group/package.json +0 -4
  140. package/component/tooltip/README.md +0 -211
  141. package/component/tooltip/package.json +0 -4
  142. package/component/utils/package.json +0 -4
  143. package/fesm2022/ojiepermana-angular-component-accordion.mjs +0 -189
  144. package/fesm2022/ojiepermana-angular-component-accordion.mjs.map +0 -1
  145. package/fesm2022/ojiepermana-angular-component-alert-dialog.mjs +0 -276
  146. package/fesm2022/ojiepermana-angular-component-alert-dialog.mjs.map +0 -1
  147. package/fesm2022/ojiepermana-angular-component-alert.mjs +0 -99
  148. package/fesm2022/ojiepermana-angular-component-alert.mjs.map +0 -1
  149. package/fesm2022/ojiepermana-angular-component-aspect-ratio.mjs +0 -37
  150. package/fesm2022/ojiepermana-angular-component-aspect-ratio.mjs.map +0 -1
  151. package/fesm2022/ojiepermana-angular-component-avatar.mjs +0 -139
  152. package/fesm2022/ojiepermana-angular-component-avatar.mjs.map +0 -1
  153. package/fesm2022/ojiepermana-angular-component-badge.mjs +0 -50
  154. package/fesm2022/ojiepermana-angular-component-badge.mjs.map +0 -1
  155. package/fesm2022/ojiepermana-angular-component-breadcrumb.mjs +0 -200
  156. package/fesm2022/ojiepermana-angular-component-breadcrumb.mjs.map +0 -1
  157. package/fesm2022/ojiepermana-angular-component-button-group.mjs +0 -103
  158. package/fesm2022/ojiepermana-angular-component-button-group.mjs.map +0 -1
  159. package/fesm2022/ojiepermana-angular-component-button.mjs +0 -68
  160. package/fesm2022/ojiepermana-angular-component-button.mjs.map +0 -1
  161. package/fesm2022/ojiepermana-angular-component-calendar.mjs +0 -88
  162. package/fesm2022/ojiepermana-angular-component-calendar.mjs.map +0 -1
  163. package/fesm2022/ojiepermana-angular-component-card.mjs +0 -152
  164. package/fesm2022/ojiepermana-angular-component-card.mjs.map +0 -1
  165. package/fesm2022/ojiepermana-angular-component-carousel.mjs +0 -334
  166. package/fesm2022/ojiepermana-angular-component-carousel.mjs.map +0 -1
  167. package/fesm2022/ojiepermana-angular-component-chart-area.mjs +0 -6
  168. package/fesm2022/ojiepermana-angular-component-chart-area.mjs.map +0 -1
  169. package/fesm2022/ojiepermana-angular-component-chart-bar.mjs +0 -6
  170. package/fesm2022/ojiepermana-angular-component-chart-bar.mjs.map +0 -1
  171. package/fesm2022/ojiepermana-angular-component-chart-line.mjs +0 -6
  172. package/fesm2022/ojiepermana-angular-component-chart-line.mjs.map +0 -1
  173. package/fesm2022/ojiepermana-angular-component-chart-pie.mjs +0 -6
  174. package/fesm2022/ojiepermana-angular-component-chart-pie.mjs.map +0 -1
  175. package/fesm2022/ojiepermana-angular-component-chart-radar.mjs +0 -6
  176. package/fesm2022/ojiepermana-angular-component-chart-radar.mjs.map +0 -1
  177. package/fesm2022/ojiepermana-angular-component-chart-radial.mjs +0 -6
  178. package/fesm2022/ojiepermana-angular-component-chart-radial.mjs.map +0 -1
  179. package/fesm2022/ojiepermana-angular-component-chart-scatter.mjs +0 -6
  180. package/fesm2022/ojiepermana-angular-component-chart-scatter.mjs.map +0 -1
  181. package/fesm2022/ojiepermana-angular-component-chart.mjs +0 -3925
  182. package/fesm2022/ojiepermana-angular-component-chart.mjs.map +0 -1
  183. package/fesm2022/ojiepermana-angular-component-checkbox.mjs +0 -114
  184. package/fesm2022/ojiepermana-angular-component-checkbox.mjs.map +0 -1
  185. package/fesm2022/ojiepermana-angular-component-collapsible.mjs +0 -124
  186. package/fesm2022/ojiepermana-angular-component-collapsible.mjs.map +0 -1
  187. package/fesm2022/ojiepermana-angular-component-combobox.mjs +0 -272
  188. package/fesm2022/ojiepermana-angular-component-combobox.mjs.map +0 -1
  189. package/fesm2022/ojiepermana-angular-component-command.mjs +0 -293
  190. package/fesm2022/ojiepermana-angular-component-command.mjs.map +0 -1
  191. package/fesm2022/ojiepermana-angular-component-composer.mjs +0 -352
  192. package/fesm2022/ojiepermana-angular-component-composer.mjs.map +0 -1
  193. package/fesm2022/ojiepermana-angular-component-context-menu.mjs +0 -103
  194. package/fesm2022/ojiepermana-angular-component-context-menu.mjs.map +0 -1
  195. package/fesm2022/ojiepermana-angular-component-date-picker.mjs +0 -170
  196. package/fesm2022/ojiepermana-angular-component-date-picker.mjs.map +0 -1
  197. package/fesm2022/ojiepermana-angular-component-dialog.mjs +0 -279
  198. package/fesm2022/ojiepermana-angular-component-dialog.mjs.map +0 -1
  199. package/fesm2022/ojiepermana-angular-component-drawer.mjs +0 -6
  200. package/fesm2022/ojiepermana-angular-component-drawer.mjs.map +0 -1
  201. package/fesm2022/ojiepermana-angular-component-dropdown-menu.mjs +0 -492
  202. package/fesm2022/ojiepermana-angular-component-dropdown-menu.mjs.map +0 -1
  203. package/fesm2022/ojiepermana-angular-component-editor.mjs +0 -717
  204. package/fesm2022/ojiepermana-angular-component-editor.mjs.map +0 -1
  205. package/fesm2022/ojiepermana-angular-component-empty.mjs +0 -145
  206. package/fesm2022/ojiepermana-angular-component-empty.mjs.map +0 -1
  207. package/fesm2022/ojiepermana-angular-component-form.mjs +0 -366
  208. package/fesm2022/ojiepermana-angular-component-form.mjs.map +0 -1
  209. package/fesm2022/ojiepermana-angular-component-hover-card.mjs +0 -297
  210. package/fesm2022/ojiepermana-angular-component-hover-card.mjs.map +0 -1
  211. package/fesm2022/ojiepermana-angular-component-input-group.mjs +0 -179
  212. package/fesm2022/ojiepermana-angular-component-input-group.mjs.map +0 -1
  213. package/fesm2022/ojiepermana-angular-component-input-otp.mjs +0 -514
  214. package/fesm2022/ojiepermana-angular-component-input-otp.mjs.map +0 -1
  215. package/fesm2022/ojiepermana-angular-component-input.mjs +0 -45
  216. package/fesm2022/ojiepermana-angular-component-input.mjs.map +0 -1
  217. package/fesm2022/ojiepermana-angular-component-item.mjs +0 -264
  218. package/fesm2022/ojiepermana-angular-component-item.mjs.map +0 -1
  219. package/fesm2022/ojiepermana-angular-component-kanban.mjs +0 -314
  220. package/fesm2022/ojiepermana-angular-component-kanban.mjs.map +0 -1
  221. package/fesm2022/ojiepermana-angular-component-kbd.mjs +0 -55
  222. package/fesm2022/ojiepermana-angular-component-kbd.mjs.map +0 -1
  223. package/fesm2022/ojiepermana-angular-component-label.mjs +0 -33
  224. package/fesm2022/ojiepermana-angular-component-label.mjs.map +0 -1
  225. package/fesm2022/ojiepermana-angular-component-menubar.mjs +0 -308
  226. package/fesm2022/ojiepermana-angular-component-menubar.mjs.map +0 -1
  227. package/fesm2022/ojiepermana-angular-component-native-select.mjs +0 -67
  228. package/fesm2022/ojiepermana-angular-component-native-select.mjs.map +0 -1
  229. package/fesm2022/ojiepermana-angular-component-navigation-menu.mjs +0 -413
  230. package/fesm2022/ojiepermana-angular-component-navigation-menu.mjs.map +0 -1
  231. package/fesm2022/ojiepermana-angular-component-pagination.mjs +0 -226
  232. package/fesm2022/ojiepermana-angular-component-pagination.mjs.map +0 -1
  233. package/fesm2022/ojiepermana-angular-component-pillbox.mjs +0 -812
  234. package/fesm2022/ojiepermana-angular-component-pillbox.mjs.map +0 -1
  235. package/fesm2022/ojiepermana-angular-component-popover.mjs +0 -169
  236. package/fesm2022/ojiepermana-angular-component-popover.mjs.map +0 -1
  237. package/fesm2022/ojiepermana-angular-component-progress.mjs +0 -60
  238. package/fesm2022/ojiepermana-angular-component-progress.mjs.map +0 -1
  239. package/fesm2022/ojiepermana-angular-component-radio.mjs +0 -122
  240. package/fesm2022/ojiepermana-angular-component-radio.mjs.map +0 -1
  241. package/fesm2022/ojiepermana-angular-component-resizable.mjs +0 -481
  242. package/fesm2022/ojiepermana-angular-component-resizable.mjs.map +0 -1
  243. package/fesm2022/ojiepermana-angular-component-scroll-area.mjs +0 -54
  244. package/fesm2022/ojiepermana-angular-component-scroll-area.mjs.map +0 -1
  245. package/fesm2022/ojiepermana-angular-component-select.mjs +0 -176
  246. package/fesm2022/ojiepermana-angular-component-select.mjs.map +0 -1
  247. package/fesm2022/ojiepermana-angular-component-separator.mjs +0 -37
  248. package/fesm2022/ojiepermana-angular-component-separator.mjs.map +0 -1
  249. package/fesm2022/ojiepermana-angular-component-sheet.mjs +0 -284
  250. package/fesm2022/ojiepermana-angular-component-sheet.mjs.map +0 -1
  251. package/fesm2022/ojiepermana-angular-component-skeleton.mjs +0 -31
  252. package/fesm2022/ojiepermana-angular-component-skeleton.mjs.map +0 -1
  253. package/fesm2022/ojiepermana-angular-component-slider.mjs +0 -423
  254. package/fesm2022/ojiepermana-angular-component-slider.mjs.map +0 -1
  255. package/fesm2022/ojiepermana-angular-component-spinner.mjs +0 -60
  256. package/fesm2022/ojiepermana-angular-component-spinner.mjs.map +0 -1
  257. package/fesm2022/ojiepermana-angular-component-switch.mjs +0 -116
  258. package/fesm2022/ojiepermana-angular-component-switch.mjs.map +0 -1
  259. package/fesm2022/ojiepermana-angular-component-table.mjs +0 -155
  260. package/fesm2022/ojiepermana-angular-component-table.mjs.map +0 -1
  261. package/fesm2022/ojiepermana-angular-component-tabs.mjs +0 -272
  262. package/fesm2022/ojiepermana-angular-component-tabs.mjs.map +0 -1
  263. package/fesm2022/ojiepermana-angular-component-textarea.mjs +0 -39
  264. package/fesm2022/ojiepermana-angular-component-textarea.mjs.map +0 -1
  265. package/fesm2022/ojiepermana-angular-component-timeline.mjs +0 -237
  266. package/fesm2022/ojiepermana-angular-component-timeline.mjs.map +0 -1
  267. package/fesm2022/ojiepermana-angular-component-toast.mjs +0 -71
  268. package/fesm2022/ojiepermana-angular-component-toast.mjs.map +0 -1
  269. package/fesm2022/ojiepermana-angular-component-toggle-group.mjs +0 -289
  270. package/fesm2022/ojiepermana-angular-component-toggle-group.mjs.map +0 -1
  271. package/fesm2022/ojiepermana-angular-component-toggle.mjs +0 -82
  272. package/fesm2022/ojiepermana-angular-component-toggle.mjs.map +0 -1
  273. package/fesm2022/ojiepermana-angular-component-tooltip.mjs +0 -354
  274. package/fesm2022/ojiepermana-angular-component-tooltip.mjs.map +0 -1
  275. package/fesm2022/ojiepermana-angular-component-utils.mjs +0 -13
  276. package/fesm2022/ojiepermana-angular-component-utils.mjs.map +0 -1
  277. package/fesm2022/ojiepermana-angular-generator-api.mjs +0 -68
  278. package/fesm2022/ojiepermana-angular-generator-api.mjs.map +0 -1
  279. package/fesm2022/ojiepermana-angular-layout-component.mjs +0 -602
  280. package/fesm2022/ojiepermana-angular-layout-component.mjs.map +0 -1
  281. package/fesm2022/ojiepermana-angular-layout-provider.mjs +0 -21
  282. package/fesm2022/ojiepermana-angular-layout-provider.mjs.map +0 -1
  283. package/fesm2022/ojiepermana-angular-layout-services.mjs +0 -116
  284. package/fesm2022/ojiepermana-angular-layout-services.mjs.map +0 -1
  285. package/fesm2022/ojiepermana-angular-layout-shell.mjs +0 -48
  286. package/fesm2022/ojiepermana-angular-layout-shell.mjs.map +0 -1
  287. package/fesm2022/ojiepermana-angular-layout-token-directive.mjs +0 -30
  288. package/fesm2022/ojiepermana-angular-layout-token-directive.mjs.map +0 -1
  289. package/fesm2022/ojiepermana-angular-layout-token.mjs +0 -33
  290. package/fesm2022/ojiepermana-angular-layout-token.mjs.map +0 -1
  291. package/fesm2022/ojiepermana-angular-layout-type-empty.mjs +0 -49
  292. package/fesm2022/ojiepermana-angular-layout-type-empty.mjs.map +0 -1
  293. package/fesm2022/ojiepermana-angular-layout-type-horizontal.mjs +0 -128
  294. package/fesm2022/ojiepermana-angular-layout-type-horizontal.mjs.map +0 -1
  295. package/fesm2022/ojiepermana-angular-layout-type-vertical.mjs +0 -123
  296. package/fesm2022/ojiepermana-angular-layout-type-vertical.mjs.map +0 -1
  297. package/fesm2022/ojiepermana-angular-layout.mjs +0 -485
  298. package/fesm2022/ojiepermana-angular-layout.mjs.map +0 -1
  299. package/fesm2022/ojiepermana-angular-navigation-demo-data.mjs +0 -334
  300. package/fesm2022/ojiepermana-angular-navigation-demo-data.mjs.map +0 -1
  301. package/fesm2022/ojiepermana-angular-navigation-icon.mjs +0 -63
  302. package/fesm2022/ojiepermana-angular-navigation-icon.mjs.map +0 -1
  303. package/fesm2022/ojiepermana-angular-navigation-item.mjs +0 -559
  304. package/fesm2022/ojiepermana-angular-navigation-item.mjs.map +0 -1
  305. package/fesm2022/ojiepermana-angular-navigation-service.mjs +0 -213
  306. package/fesm2022/ojiepermana-angular-navigation-service.mjs.map +0 -1
  307. package/fesm2022/ojiepermana-angular-navigation-sidebar.mjs +0 -401
  308. package/fesm2022/ojiepermana-angular-navigation-sidebar.mjs.map +0 -1
  309. package/fesm2022/ojiepermana-angular-navigation-topbar.mjs +0 -670
  310. package/fesm2022/ojiepermana-angular-navigation-topbar.mjs.map +0 -1
  311. package/fesm2022/ojiepermana-angular-navigation-types.mjs +0 -4
  312. package/fesm2022/ojiepermana-angular-navigation-types.mjs.map +0 -1
  313. package/fesm2022/ojiepermana-angular-theme-provider.mjs +0 -35
  314. package/fesm2022/ojiepermana-angular-theme-provider.mjs.map +0 -1
  315. package/fesm2022/ojiepermana-angular-theme-services.mjs +0 -294
  316. package/fesm2022/ojiepermana-angular-theme-services.mjs.map +0 -1
  317. package/fesm2022/ojiepermana-angular-theme-token.mjs +0 -56
  318. package/fesm2022/ojiepermana-angular-theme-token.mjs.map +0 -1
  319. package/fesm2022/ojiepermana-angular-theme.mjs.map +0 -1
  320. package/fesm2022/ojiepermana-angular.mjs.map +0 -1
  321. package/generator/api/README.md +0 -252
  322. package/generator/api/bin/package.json +0 -3
  323. package/generator/api/bin/schematics/init/index.js +0 -90
  324. package/generator/api/bin/schematics/ng-add/index.js +0 -131
  325. package/generator/api/bin/schematics/sdk/index.js +0 -76
  326. package/generator/api/bin/src/config/loader.js +0 -41
  327. package/generator/api/bin/src/config/schema.js +0 -57
  328. package/generator/api/bin/src/emit/client.js +0 -248
  329. package/generator/api/bin/src/emit/metadata.js +0 -295
  330. package/generator/api/bin/src/emit/models.js +0 -106
  331. package/generator/api/bin/src/emit/navigation.js +0 -56
  332. package/generator/api/bin/src/emit/operations.js +0 -122
  333. package/generator/api/bin/src/emit/public-api.js +0 -54
  334. package/generator/api/bin/src/emit/services.js +0 -87
  335. package/generator/api/bin/src/engine.js +0 -65
  336. package/generator/api/bin/src/layout/per-domain.js +0 -359
  337. package/generator/api/bin/src/parser/bundle.js +0 -25
  338. package/generator/api/bin/src/parser/ir.js +0 -320
  339. package/generator/api/bin/src/parser/types.js +0 -7
  340. package/generator/api/bin/src/render/template.js +0 -58
  341. package/generator/api/bin/src/writer/index.js +0 -278
  342. package/generator/api/package.json +0 -4
  343. package/generator/api/schematics/init/schema.json +0 -19
  344. package/generator/api/schematics/ng-add/schema.json +0 -14
  345. package/generator/api/schematics/sdk/schema.json +0 -19
  346. package/generator/api/sdk.config.example.json +0 -24
  347. package/generator/guide/README.md +0 -84
  348. package/generator/guide/bin/package.json +0 -3
  349. package/generator/guide/bin/schematics/build/index.js +0 -36
  350. package/generator/guide/bin/schematics/init/index.js +0 -70
  351. package/generator/guide/bin/src/config/loader.js +0 -50
  352. package/generator/guide/bin/src/config/schema.js +0 -12
  353. package/generator/guide/bin/src/engine/component.js +0 -74
  354. package/generator/guide/bin/src/engine/frontmatter.js +0 -42
  355. package/generator/guide/bin/src/engine/index.js +0 -42
  356. package/generator/guide/bin/src/engine/naming.js +0 -39
  357. package/generator/guide/bin/src/engine/render.js +0 -36
  358. package/generator/guide/bin/src/engine/routes.js +0 -106
  359. package/generator/guide/bin/src/engine/walk.js +0 -35
  360. package/generator/guide/guide.config.example.json +0 -9
  361. package/generator/guide/schematics/build/schema.json +0 -14
  362. package/generator/guide/schematics/init/schema.json +0 -19
  363. package/layout/component/package.json +0 -4
  364. package/layout/package.json +0 -4
  365. package/layout/provider/package.json +0 -4
  366. package/layout/services/package.json +0 -4
  367. package/layout/shell/package.json +0 -4
  368. package/layout/token/directive/package.json +0 -4
  369. package/layout/token/package.json +0 -4
  370. package/layout/type/empty/package.json +0 -4
  371. package/layout/type/horizontal/package.json +0 -4
  372. package/layout/type/vertical/package.json +0 -4
  373. package/navigation/demo-data/package.json +0 -4
  374. package/navigation/icon/package.json +0 -4
  375. package/navigation/item/package.json +0 -4
  376. package/navigation/service/package.json +0 -4
  377. package/navigation/sidebar/package.json +0 -4
  378. package/navigation/topbar/README.md +0 -196
  379. package/navigation/topbar/package.json +0 -4
  380. package/navigation/types/package.json +0 -4
  381. package/theme/README.md +0 -174
  382. package/theme/package.json +0 -4
  383. package/theme/provider/package.json +0 -4
  384. package/theme/services/package.json +0 -4
  385. package/theme/styles/foundation/components.css +0 -81
  386. package/theme/styles/foundation/layers.css +0 -15
  387. package/theme/styles/foundation/tokens.css +0 -55
  388. package/theme/styles/index.css +0 -37
  389. package/theme/styles/integrations/material/autocomplete.css +0 -178
  390. package/theme/styles/integrations/material/button.css +0 -468
  391. package/theme/styles/integrations/material/dialog.css +0 -152
  392. package/theme/styles/integrations/material/select.css +0 -175
  393. package/theme/styles/integrations/material/slide-toggle.css +0 -234
  394. package/theme/styles/integrations/material/slider.css +0 -194
  395. package/theme/styles/integrations/material/tabs.css +0 -229
  396. package/theme/styles/integrations/material.css +0 -264
  397. package/theme/styles/integrations/tailwind.css +0 -114
  398. package/theme/styles/variants/color/amber.css +0 -31
  399. package/theme/styles/variants/color/base.css +0 -36
  400. package/theme/styles/variants/color/blue.css +0 -31
  401. package/theme/styles/variants/color/cyan.css +0 -31
  402. package/theme/styles/variants/color/emerald.css +0 -31
  403. package/theme/styles/variants/color/fuchsia.css +0 -31
  404. package/theme/styles/variants/color/green.css +0 -31
  405. package/theme/styles/variants/color/index.css +0 -22
  406. package/theme/styles/variants/color/indigo.css +0 -31
  407. package/theme/styles/variants/color/lime.css +0 -31
  408. package/theme/styles/variants/color/orange.css +0 -31
  409. package/theme/styles/variants/color/pink.css +0 -31
  410. package/theme/styles/variants/color/purple.css +0 -31
  411. package/theme/styles/variants/color/red.css +0 -31
  412. package/theme/styles/variants/color/rose.css +0 -31
  413. package/theme/styles/variants/color/sky.css +0 -31
  414. package/theme/styles/variants/color/teal.css +0 -31
  415. package/theme/styles/variants/color/violet.css +0 -31
  416. package/theme/styles/variants/color/yellow.css +0 -31
  417. package/theme/styles/variants/mode/dark.css +0 -20
  418. package/theme/styles/variants/mode/index.css +0 -6
  419. package/theme/styles/variants/mode/light.css +0 -24
  420. package/theme/styles/variants/style/brutal.css +0 -50
  421. package/theme/styles/variants/style/default.css +0 -54
  422. package/theme/styles/variants/style/index.css +0 -8
  423. package/theme/styles/variants/style/sharp.css +0 -50
  424. package/theme/styles/variants/style/soft.css +0 -50
  425. package/theme/token/package.json +0 -4
  426. package/types/ojiepermana-angular-component-accordion.d.ts +0 -51
  427. package/types/ojiepermana-angular-component-alert-dialog.d.ts +0 -93
  428. package/types/ojiepermana-angular-component-alert.d.ts +0 -37
  429. package/types/ojiepermana-angular-component-aspect-ratio.d.ts +0 -12
  430. package/types/ojiepermana-angular-component-avatar.d.ts +0 -51
  431. package/types/ojiepermana-angular-component-badge.d.ts +0 -19
  432. package/types/ojiepermana-angular-component-breadcrumb.d.ts +0 -46
  433. package/types/ojiepermana-angular-component-button-group.d.ts +0 -26
  434. package/types/ojiepermana-angular-component-button.d.ts +0 -22
  435. package/types/ojiepermana-angular-component-calendar.d.ts +0 -33
  436. package/types/ojiepermana-angular-component-card.d.ts +0 -60
  437. package/types/ojiepermana-angular-component-carousel.d.ts +0 -86
  438. package/types/ojiepermana-angular-component-chart-area.d.ts +0 -1
  439. package/types/ojiepermana-angular-component-chart-bar.d.ts +0 -1
  440. package/types/ojiepermana-angular-component-chart-line.d.ts +0 -1
  441. package/types/ojiepermana-angular-component-chart-pie.d.ts +0 -1
  442. package/types/ojiepermana-angular-component-chart-radar.d.ts +0 -1
  443. package/types/ojiepermana-angular-component-chart-radial.d.ts +0 -1
  444. package/types/ojiepermana-angular-component-chart-scatter.d.ts +0 -1
  445. package/types/ojiepermana-angular-component-chart.d.ts +0 -1094
  446. package/types/ojiepermana-angular-component-checkbox.d.ts +0 -35
  447. package/types/ojiepermana-angular-component-collapsible.d.ts +0 -42
  448. package/types/ojiepermana-angular-component-combobox.d.ts +0 -50
  449. package/types/ojiepermana-angular-component-command.d.ts +0 -99
  450. package/types/ojiepermana-angular-component-composer.d.ts +0 -90
  451. package/types/ojiepermana-angular-component-context-menu.d.ts +0 -35
  452. package/types/ojiepermana-angular-component-date-picker.d.ts +0 -41
  453. package/types/ojiepermana-angular-component-dialog.d.ts +0 -87
  454. package/types/ojiepermana-angular-component-drawer.d.ts +0 -1
  455. package/types/ojiepermana-angular-component-dropdown-menu.d.ts +0 -137
  456. package/types/ojiepermana-angular-component-editor.d.ts +0 -123
  457. package/types/ojiepermana-angular-component-empty.d.ts +0 -50
  458. package/types/ojiepermana-angular-component-form.d.ts +0 -141
  459. package/types/ojiepermana-angular-component-hover-card.d.ts +0 -74
  460. package/types/ojiepermana-angular-component-input-group.d.ts +0 -51
  461. package/types/ojiepermana-angular-component-input-otp.d.ts +0 -136
  462. package/types/ojiepermana-angular-component-input.d.ts +0 -16
  463. package/types/ojiepermana-angular-component-item.d.ts +0 -88
  464. package/types/ojiepermana-angular-component-kanban.d.ts +0 -70
  465. package/types/ojiepermana-angular-component-kbd.d.ts +0 -16
  466. package/types/ojiepermana-angular-component-label.d.ts +0 -11
  467. package/types/ojiepermana-angular-component-menubar.d.ts +0 -67
  468. package/types/ojiepermana-angular-component-native-select.d.ts +0 -26
  469. package/types/ojiepermana-angular-component-navigation-menu.d.ts +0 -96
  470. package/types/ojiepermana-angular-component-pagination.d.ts +0 -33
  471. package/types/ojiepermana-angular-component-pillbox.d.ts +0 -157
  472. package/types/ojiepermana-angular-component-popover.d.ts +0 -43
  473. package/types/ojiepermana-angular-component-progress.d.ts +0 -17
  474. package/types/ojiepermana-angular-component-radio.d.ts +0 -40
  475. package/types/ojiepermana-angular-component-resizable.d.ts +0 -99
  476. package/types/ojiepermana-angular-component-scroll-area.d.ts +0 -19
  477. package/types/ojiepermana-angular-component-select.d.ts +0 -57
  478. package/types/ojiepermana-angular-component-separator.d.ts +0 -14
  479. package/types/ojiepermana-angular-component-sheet.d.ts +0 -76
  480. package/types/ojiepermana-angular-component-skeleton.d.ts +0 -10
  481. package/types/ojiepermana-angular-component-slider.d.ts +0 -74
  482. package/types/ojiepermana-angular-component-spinner.d.ts +0 -13
  483. package/types/ojiepermana-angular-component-switch.d.ts +0 -40
  484. package/types/ojiepermana-angular-component-table.d.ts +0 -52
  485. package/types/ojiepermana-angular-component-tabs.d.ts +0 -92
  486. package/types/ojiepermana-angular-component-textarea.d.ts +0 -12
  487. package/types/ojiepermana-angular-component-timeline.d.ts +0 -63
  488. package/types/ojiepermana-angular-component-toast.d.ts +0 -38
  489. package/types/ojiepermana-angular-component-toggle-group.d.ts +0 -89
  490. package/types/ojiepermana-angular-component-toggle.d.ts +0 -25
  491. package/types/ojiepermana-angular-component-tooltip.d.ts +0 -89
  492. package/types/ojiepermana-angular-component-utils.d.ts +0 -5
  493. package/types/ojiepermana-angular-generator-api.d.ts +0 -86
  494. package/types/ojiepermana-angular-layout-component.d.ts +0 -205
  495. package/types/ojiepermana-angular-layout-provider.d.ts +0 -6
  496. package/types/ojiepermana-angular-layout-services.d.ts +0 -25
  497. package/types/ojiepermana-angular-layout-shell.d.ts +0 -8
  498. package/types/ojiepermana-angular-layout-token-directive.d.ts +0 -13
  499. package/types/ojiepermana-angular-layout-token.d.ts +0 -36
  500. package/types/ojiepermana-angular-layout-type-empty.d.ts +0 -22
  501. package/types/ojiepermana-angular-layout-type-horizontal.d.ts +0 -36
  502. package/types/ojiepermana-angular-layout-type-vertical.d.ts +0 -38
  503. package/types/ojiepermana-angular-layout.d.ts +0 -164
  504. package/types/ojiepermana-angular-navigation-demo-data.d.ts +0 -5
  505. package/types/ojiepermana-angular-navigation-icon.d.ts +0 -17
  506. package/types/ojiepermana-angular-navigation-item.d.ts +0 -54
  507. package/types/ojiepermana-angular-navigation-service.d.ts +0 -77
  508. package/types/ojiepermana-angular-navigation-sidebar.d.ts +0 -75
  509. package/types/ojiepermana-angular-navigation-topbar.d.ts +0 -74
  510. package/types/ojiepermana-angular-navigation-types.d.ts +0 -135
  511. package/types/ojiepermana-angular-theme-provider.d.ts +0 -11
  512. package/types/ojiepermana-angular-theme-services.d.ts +0 -55
  513. 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