@blotch/dolan 22.2.2 → 22.2.4

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 (259) hide show
  1. package/dist/commands/add.d.ts +6 -2
  2. package/dist/commands/add.js +41 -11
  3. package/dist/commands/add.js.map +1 -1
  4. package/dist/commands/add.test.js +162 -9
  5. package/dist/commands/add.test.js.map +1 -1
  6. package/dist/commands/diff.d.ts +2 -1
  7. package/dist/commands/diff.js +38 -25
  8. package/dist/commands/diff.js.map +1 -1
  9. package/dist/commands/diff.test.js +72 -11
  10. package/dist/commands/diff.test.js.map +1 -1
  11. package/dist/commands/doctor.js +228 -34
  12. package/dist/commands/doctor.js.map +1 -1
  13. package/dist/commands/doctor.test.js +367 -10
  14. package/dist/commands/doctor.test.js.map +1 -1
  15. package/dist/commands/eject.js +7 -0
  16. package/dist/commands/eject.js.map +1 -1
  17. package/dist/commands/eject.test.js +39 -0
  18. package/dist/commands/eject.test.js.map +1 -1
  19. package/dist/commands/info.js +3 -2
  20. package/dist/commands/info.js.map +1 -1
  21. package/dist/commands/init.d.ts +37 -1
  22. package/dist/commands/init.js +169 -40
  23. package/dist/commands/init.js.map +1 -1
  24. package/dist/commands/init.test.js +392 -27
  25. package/dist/commands/init.test.js.map +1 -1
  26. package/dist/commands/install-packages.d.ts +6 -0
  27. package/dist/commands/install-packages.js +20 -0
  28. package/dist/commands/install-packages.js.map +1 -0
  29. package/dist/commands/install-packages.test.d.ts +1 -0
  30. package/dist/commands/install-packages.test.js +55 -0
  31. package/dist/commands/install-packages.test.js.map +1 -0
  32. package/dist/commands/list.js +21 -14
  33. package/dist/commands/list.js.map +1 -1
  34. package/dist/commands/list.test.js +62 -5
  35. package/dist/commands/list.test.js.map +1 -1
  36. package/dist/commands/outdated.js +11 -10
  37. package/dist/commands/outdated.js.map +1 -1
  38. package/dist/commands/outdated.test.js +20 -5
  39. package/dist/commands/outdated.test.js.map +1 -1
  40. package/dist/commands/remove.d.ts +4 -0
  41. package/dist/commands/remove.js +17 -4
  42. package/dist/commands/remove.js.map +1 -1
  43. package/dist/commands/remove.test.js +53 -2
  44. package/dist/commands/remove.test.js.map +1 -1
  45. package/dist/commands/theme.js +1 -1
  46. package/dist/commands/theme.js.map +1 -1
  47. package/dist/commands/update.d.ts +11 -0
  48. package/dist/commands/update.js +174 -58
  49. package/dist/commands/update.js.map +1 -1
  50. package/dist/commands/update.test.js +587 -33
  51. package/dist/commands/update.test.js.map +1 -1
  52. package/dist/index.js +7 -0
  53. package/dist/index.js.map +1 -1
  54. package/dist/lockfile.d.ts +7 -0
  55. package/dist/lockfile.js +36 -0
  56. package/dist/lockfile.js.map +1 -1
  57. package/dist/lockfile.test.d.ts +1 -0
  58. package/dist/lockfile.test.js +25 -0
  59. package/dist/lockfile.test.js.map +1 -0
  60. package/dist/registry.d.ts +14 -0
  61. package/dist/registry.js +78 -24
  62. package/dist/registry.js.map +1 -1
  63. package/dist/registry.test.js +119 -38
  64. package/dist/registry.test.js.map +1 -1
  65. package/dist/test-utils.d.ts +5 -0
  66. package/dist/test-utils.js +6 -1
  67. package/dist/test-utils.js.map +1 -1
  68. package/dist/testing/fire-lynx-event.d.ts +14 -0
  69. package/dist/testing/fire-lynx-event.js +19 -0
  70. package/dist/testing/fire-lynx-event.js.map +1 -0
  71. package/dist/testing/fire-lynx-event.test.d.ts +1 -0
  72. package/dist/testing/fire-lynx-event.test.js +44 -0
  73. package/dist/testing/fire-lynx-event.test.js.map +1 -0
  74. package/dist/testing/lynx-main-thread.d.ts +22 -0
  75. package/dist/testing/lynx-main-thread.js +11 -0
  76. package/dist/testing/lynx-main-thread.js.map +1 -0
  77. package/dist/testing/lynx-main-thread.test.d.ts +1 -0
  78. package/dist/testing/lynx-main-thread.test.js +24 -0
  79. package/dist/testing/lynx-main-thread.test.js.map +1 -0
  80. package/dist/testing/set-input-signal.d.ts +14 -0
  81. package/dist/testing/set-input-signal.js +21 -0
  82. package/dist/testing/set-input-signal.js.map +1 -0
  83. package/dist/testing/set-input-signal.test.d.ts +1 -0
  84. package/dist/testing/set-input-signal.test.js +20 -0
  85. package/dist/testing/set-input-signal.test.js.map +1 -0
  86. package/dist/ui/components/accordion/accordion.test.ts +158 -144
  87. package/dist/ui/components/accordion/accordion.ts +19 -14
  88. package/dist/ui/components/action-sheet/action-sheet.test.ts +283 -151
  89. package/dist/ui/components/action-sheet/action-sheet.ts +77 -43
  90. package/dist/ui/components/alert/alert.test.ts +67 -19
  91. package/dist/ui/components/alert/alert.ts +14 -16
  92. package/dist/ui/components/alert-dialog/alert-dialog.test.ts +208 -94
  93. package/dist/ui/components/alert-dialog/alert-dialog.ts +75 -38
  94. package/dist/ui/components/area-chart/area-chart.test.ts +134 -119
  95. package/dist/ui/components/area-chart/area-chart.ts +100 -315
  96. package/dist/ui/components/area-chart/index.ts +2 -3
  97. package/dist/ui/components/avatar/avatar.test.ts +89 -45
  98. package/dist/ui/components/badge/badge.test.ts +57 -23
  99. package/dist/ui/components/badge/badge.ts +12 -14
  100. package/dist/ui/components/bar-chart/bar-chart.test.ts +172 -45
  101. package/dist/ui/components/bar-chart/bar-chart.ts +140 -294
  102. package/dist/ui/components/bar-chart/index.ts +3 -3
  103. package/dist/ui/components/bottom-sheet/bottom-sheet.test.ts +386 -128
  104. package/dist/ui/components/bottom-sheet/bottom-sheet.ts +170 -53
  105. package/dist/ui/components/button/button.test.ts +611 -2
  106. package/dist/ui/components/button/button.ts +434 -49
  107. package/dist/ui/components/button/index.ts +2 -0
  108. package/dist/ui/components/button-group/button-group.test.ts +484 -2
  109. package/dist/ui/components/button-group/button-group.ts +380 -68
  110. package/dist/ui/components/button-group/index.ts +3 -0
  111. package/dist/ui/components/candlestick-chart/candlestick-chart.test.ts +183 -58
  112. package/dist/ui/components/candlestick-chart/candlestick-chart.ts +150 -323
  113. package/dist/ui/components/candlestick-chart/index.ts +3 -4
  114. package/dist/ui/components/card/card.test.ts +82 -62
  115. package/dist/ui/components/chart/chart-focus.test.ts +132 -0
  116. package/dist/ui/components/chart/chart-focus.ts +115 -0
  117. package/dist/ui/components/chart/chart-presets.test.ts +335 -0
  118. package/dist/ui/components/chart/chart-presets.ts +278 -0
  119. package/dist/ui/components/chart/chart-scene.test.ts +589 -0
  120. package/dist/ui/components/chart/chart-scene.ts +483 -0
  121. package/dist/ui/components/chart/chart-theme.test.ts +85 -0
  122. package/dist/ui/components/chart/chart-theme.ts +210 -0
  123. package/dist/ui/components/chart/chart-zoom.test.ts +159 -0
  124. package/dist/ui/components/chart/chart-zoom.ts +162 -0
  125. package/dist/ui/components/chart/chart.test.ts +1017 -0
  126. package/dist/ui/components/chart/chart.ts +934 -0
  127. package/dist/ui/components/chart/index.ts +24 -0
  128. package/dist/ui/components/checkbox/checkbox.test.ts +124 -42
  129. package/dist/ui/components/checkbox/checkbox.ts +23 -23
  130. package/dist/ui/components/collapsible/collapsible.test.ts +158 -81
  131. package/dist/ui/components/collapsible/collapsible.ts +19 -14
  132. package/dist/ui/components/context-menu/context-menu.test.ts +224 -0
  133. package/dist/ui/components/context-menu/context-menu.ts +110 -0
  134. package/dist/ui/components/context-menu/index.ts +1 -0
  135. package/dist/ui/components/dialog/dialog.test.ts +213 -108
  136. package/dist/ui/components/dialog/dialog.ts +72 -35
  137. package/dist/ui/components/dock/dock-scroll.test.ts +130 -0
  138. package/dist/ui/components/dock/dock-scroll.ts +133 -0
  139. package/dist/ui/components/dock/dock.test.ts +749 -0
  140. package/dist/ui/components/dock/dock.ts +960 -0
  141. package/dist/ui/components/dock/index.ts +2 -0
  142. package/dist/ui/components/empty-state/empty-state.test.ts +28 -39
  143. package/dist/ui/components/glass/concentric-radius.test.ts +13 -0
  144. package/dist/ui/components/glass/concentric-radius.ts +17 -0
  145. package/dist/ui/components/glass/glass-design.test.ts +78 -0
  146. package/dist/ui/components/glass/glass-design.ts +119 -0
  147. package/dist/ui/components/glass/glass.test.ts +338 -0
  148. package/dist/ui/components/glass/glass.ts +360 -0
  149. package/dist/ui/components/glass/index.ts +18 -0
  150. package/dist/ui/components/icon/icon-theme.test.ts +169 -0
  151. package/dist/ui/components/icon/icon-theme.ts +227 -0
  152. package/dist/ui/components/icon/icon.test.ts +85 -25
  153. package/dist/ui/components/icon/icon.ts +38 -21
  154. package/dist/ui/components/input/input.test.ts +178 -101
  155. package/dist/ui/components/input/input.ts +15 -12
  156. package/dist/ui/components/label/label.test.ts +11 -24
  157. package/dist/ui/components/line-chart/index.ts +2 -3
  158. package/dist/ui/components/line-chart/line-chart.test.ts +149 -56
  159. package/dist/ui/components/line-chart/line-chart.ts +83 -241
  160. package/dist/ui/components/list/list.test.ts +119 -21
  161. package/dist/ui/components/list/list.ts +21 -15
  162. package/dist/ui/components/menu/index.ts +25 -0
  163. package/dist/ui/components/menu/menu-placement.test.ts +153 -0
  164. package/dist/ui/components/menu/menu-placement.ts +159 -0
  165. package/dist/ui/components/menu/menu-popup.test.ts +632 -0
  166. package/dist/ui/components/menu/menu-popup.ts +544 -0
  167. package/dist/ui/components/menu/menu.test.ts +429 -0
  168. package/dist/ui/components/menu/menu.ts +415 -0
  169. package/dist/ui/components/nav-drawer/nav-drawer.test.ts +435 -191
  170. package/dist/ui/components/nav-drawer/nav-drawer.ts +127 -56
  171. package/dist/ui/components/pagination/pagination.test.ts +177 -65
  172. package/dist/ui/components/pagination/pagination.ts +55 -8
  173. package/dist/ui/components/progress/progress.test.ts +102 -46
  174. package/dist/ui/components/progress/progress.ts +52 -54
  175. package/dist/ui/components/radio-group/radio-group.test.ts +128 -83
  176. package/dist/ui/components/radio-group/radio-group.ts +24 -24
  177. package/dist/ui/components/scatter-chart/index.ts +2 -3
  178. package/dist/ui/components/scatter-chart/scatter-chart.test.ts +131 -26
  179. package/dist/ui/components/scatter-chart/scatter-chart.ts +104 -250
  180. package/dist/ui/components/scroll-area/scroll-area.test.ts +95 -24
  181. package/dist/ui/components/scroll-area/scroll-area.ts +8 -8
  182. package/dist/ui/components/select/select.test.ts +138 -78
  183. package/dist/ui/components/select/select.ts +17 -11
  184. package/dist/ui/components/separator/separator.test.ts +18 -17
  185. package/dist/ui/components/sheet/sheet.test.ts +64 -11
  186. package/dist/ui/components/sheet/sheet.ts +9 -1
  187. package/dist/ui/components/skeleton/skeleton.test.ts +24 -7
  188. package/dist/ui/components/skeleton/skeleton.ts +10 -16
  189. package/dist/ui/components/spinner/spinner.test.ts +67 -22
  190. package/dist/ui/components/spinner/spinner.ts +48 -41
  191. package/dist/ui/components/switch/switch.test.ts +116 -46
  192. package/dist/ui/components/switch/switch.ts +8 -13
  193. package/dist/ui/components/tab-bar/index.ts +1 -0
  194. package/dist/ui/components/tab-bar/tab-bar.test.ts +280 -0
  195. package/dist/ui/components/tab-bar/tab-bar.ts +230 -0
  196. package/dist/ui/components/tabs/tabs.test.ts +215 -157
  197. package/dist/ui/components/tabs/tabs.ts +110 -118
  198. package/dist/ui/components/textarea/textarea.test.ts +206 -100
  199. package/dist/ui/components/textarea/textarea.ts +14 -12
  200. package/dist/ui/components/toast/toast.test.ts +436 -315
  201. package/dist/ui/components/toast/toast.ts +62 -55
  202. package/dist/ui/components/toggle/toggle.test.ts +247 -140
  203. package/dist/ui/theme/chart-colors.test.ts +35 -0
  204. package/dist/ui/theme/dark.css +46 -2
  205. package/dist/ui/theme/default.css +63 -7
  206. package/dist/ui/theme/icon-colors.test.ts +35 -0
  207. package/dist/ui/theme/rgb-channels.test.ts +84 -0
  208. package/dist/ui/theme/tailwind-build.test.ts +114 -0
  209. package/dist/ui/theme/tailwind-css.test.ts +83 -0
  210. package/dist/ui/theme/tailwind-plugin.test.ts +42 -2
  211. package/dist/ui/theme/tailwind-plugin.ts +98 -25
  212. package/dist/ui/theme/tailwind.css +113 -0
  213. package/dist/ui/utils/animate.test.ts +13 -1
  214. package/dist/utils/analyze.d.ts +14 -0
  215. package/dist/utils/analyze.js +15 -0
  216. package/dist/utils/analyze.js.map +1 -1
  217. package/dist/utils/analyze.test.js +14 -1
  218. package/dist/utils/analyze.test.js.map +1 -1
  219. package/dist/utils/installed.d.ts +29 -0
  220. package/dist/utils/installed.js +43 -0
  221. package/dist/utils/installed.js.map +1 -0
  222. package/dist/utils/installed.test.d.ts +1 -0
  223. package/dist/utils/installed.test.js +57 -0
  224. package/dist/utils/installed.test.js.map +1 -0
  225. package/dist/utils/interactive.d.ts +9 -0
  226. package/dist/utils/interactive.js +10 -0
  227. package/dist/utils/interactive.js.map +1 -0
  228. package/dist/utils/interactive.test.d.ts +1 -0
  229. package/dist/utils/interactive.test.js +24 -0
  230. package/dist/utils/interactive.test.js.map +1 -0
  231. package/dist/utils/packages.d.ts +36 -0
  232. package/dist/utils/packages.js +70 -0
  233. package/dist/utils/packages.js.map +1 -0
  234. package/dist/utils/packages.test.d.ts +1 -0
  235. package/dist/utils/packages.test.js +89 -0
  236. package/dist/utils/packages.test.js.map +1 -0
  237. package/dist/utils/resolve-paths.d.ts +17 -1
  238. package/dist/utils/resolve-paths.js +21 -3
  239. package/dist/utils/resolve-paths.js.map +1 -1
  240. package/dist/utils/resolve-paths.test.js +38 -2
  241. package/dist/utils/resolve-paths.test.js.map +1 -1
  242. package/dist/utils/tailwind.d.ts +25 -0
  243. package/dist/utils/tailwind.js +51 -0
  244. package/dist/utils/tailwind.js.map +1 -0
  245. package/dist/utils/tailwind.test.d.ts +1 -0
  246. package/dist/utils/tailwind.test.js +71 -0
  247. package/dist/utils/tailwind.test.js.map +1 -0
  248. package/package.json +13 -1
  249. package/dist/ui/components/area-chart/area-chart-render.test.ts +0 -121
  250. package/dist/ui/components/bar-chart/bar-chart-render.test.ts +0 -174
  251. package/dist/ui/components/button/button-render.test.ts +0 -113
  252. package/dist/ui/components/button-group/button-group-render.test.ts +0 -146
  253. package/dist/ui/components/candlestick-chart/candlestick-chart-render.test.ts +0 -174
  254. package/dist/ui/components/cartesian-chart/cartesian-chart-render.test.ts +0 -188
  255. package/dist/ui/components/cartesian-chart/cartesian-chart.test.ts +0 -525
  256. package/dist/ui/components/cartesian-chart/cartesian-chart.ts +0 -1140
  257. package/dist/ui/components/cartesian-chart/index.ts +0 -18
  258. package/dist/ui/components/line-chart/line-chart-render.test.ts +0 -121
  259. package/dist/ui/components/scatter-chart/scatter-chart-render.test.ts +0 -157
@@ -2,326 +2,172 @@ import {
2
2
  Component,
3
3
  ViewEncapsulation,
4
4
  computed,
5
- inject,
6
5
  input,
7
6
  output,
8
7
  } from '@angular/core';
9
- import { LYNX_ELEMENTS } from '@blotch/angular-lynx';
8
+ import { barY, defineChart, rect } from '@tanstack/charts/universal';
9
+ import { scaleBand } from '@tanstack/charts/scales/band';
10
+ import type { ChartPoint } from '@tanstack/charts/types';
10
11
 
12
+ import { UiChart, tooltip } from '../chart/chart';
11
13
  import {
12
- type ChartPadding,
13
- type ChartPoint,
14
- UiCartesianChart,
15
- niceScale,
16
- } from '../cartesian-chart/cartesian-chart';
17
-
18
- // ---------------------------------------------------------------------------
19
- // Types
20
- // ---------------------------------------------------------------------------
21
-
22
- // A bar is just an (x, y) datum — x is the category position, y the bar's value —
23
- // so it reuses the shared `ChartPoint` rather than defining its own. That also
24
- // lets a bar series and a line/area series share one dataset on the same axes.
25
-
26
- /** One bar after projection into plot-local pixel space. */
27
- type ProjectedBar = {
28
- /** Center x of the bar, in px. */
29
- cx: number;
30
- /** Pixel y of the bar's value (its far edge from the baseline). */
31
- valueY: number;
14
+ type CartesianPresetOptions,
15
+ PRESET_CHART_BINDINGS,
16
+ UiCartesianPreset,
17
+ minSpacing,
18
+ presetAxes,
19
+ } from '../chart/chart-presets';
20
+
21
+ /** One bar: a category (`'Mon'`) or a number on a continuous x axis. */
22
+ export type BarDatum = {
23
+ readonly x: number | string;
24
+ readonly y: number;
32
25
  };
33
26
 
34
- /** An axis-aligned rectangle in plot-local pixel space. */
35
- export type BarShape = {
36
- left: number;
37
- top: number;
38
- width: number;
39
- height: number;
40
- };
41
-
42
- // ---------------------------------------------------------------------------
43
- // Pure geometry (exported so it can be unit-tested without Angular)
44
- // ---------------------------------------------------------------------------
27
+ export type BarChartOptions = {
28
+ readonly data: readonly BarDatum[];
29
+ /** Any CSS color; theme tokens like `var(--primary)` follow dark mode. */
30
+ readonly color?: string;
31
+ /** One color per bar, cycling; overrides `color` when not empty. */
32
+ readonly colors?: readonly string[];
33
+ /** Bar width as a fraction of the space each bar has, `0`–`1`. */
34
+ readonly barWidthRatio?: number;
35
+ /** The value bars grow from; negative values grow down from it. */
36
+ readonly baseline?: number;
37
+ /** Corner radius in px. */
38
+ readonly radius?: number;
39
+ } & CartesianPresetOptions;
45
40
 
46
41
  /**
47
- * Turns projected bars — already in plot-local pixel space — into rectangles
48
- * ready to render as `<view>`s. A bar is the simplest chart mark on Lynx: one
49
- * axis-aligned box from the shared `baseY` (the baseline's pixel y) to the
50
- * value, so this is a straight geometric mapping with no rotation or fill
51
- * rasterization (unlike the line and area series).
52
- *
53
- * Screen y grows *downward*, so a value above the baseline has a *smaller*
54
- * `valueY` than `baseY`. Taking `top = min(valueY, baseY)` therefore anchors the
55
- * rectangle at whichever edge is visually higher, which makes bars for negative
56
- * values (value below the baseline) grow *downward* from the baseline correctly.
57
- *
58
- * Unlike a candlestick doji, `minBarHeight` defaults to `0`: a bar's height *is*
59
- * its datum, so a value exactly on the baseline should draw nothing rather than
60
- * a misleading sliver. Callers wanting every bar guaranteed visible can raise it.
42
+ * The definition `<ui-bar-chart>` renders.
61
43
  *
62
- * @param baseY pixel y of the baseline every bar grows from.
63
- * @param barWidth width of each bar in px.
64
- * @param minBarHeight floor for the bar height (0 = honest zero-height at baseline).
44
+ * With category labels (any string `x`) the bars sit on a band scale. With
45
+ * numbers they sit on a linear axis at their values, `barWidthRatio` of the
46
+ * smallest gap wide, so a sparse series keeps its spacing and the chart can
47
+ * pan and zoom along x.
65
48
  */
66
- export const computeBars = (
67
- bars: readonly ProjectedBar[],
68
- baseY: number,
69
- barWidth: number,
70
- minBarHeight = 0,
71
- ): BarShape[] =>
72
- bars.map((b) => {
73
- const top = Math.min(b.valueY, baseY);
74
- const height = Math.max(Math.abs(b.valueY - baseY), minBarHeight);
75
- return { left: b.cx - barWidth / 2, top, width: barWidth, height };
49
+ export const barChartDefinition = (options: BarChartOptions) => {
50
+ const {
51
+ data,
52
+ color = 'var(--primary)',
53
+ colors = [],
54
+ barWidthRatio = 0.7,
55
+ baseline = 0,
56
+ radius = 0,
57
+ } = options;
58
+ const ratio = Math.min(1, Math.max(0, barWidthRatio));
59
+ const categorical = data.some((d) => typeof d.x === 'string');
60
+ const xs = categorical ? [] : data.map((d) => d.x as number);
61
+ const spacing = minSpacing(xs);
62
+ const axes = presetAxes(options, {
63
+ x: xs,
64
+ y: data.map((d) => d.y),
65
+ yInclude: [baseline],
66
+ // Half a slot of room either side so the edge bars fit inside the plot.
67
+ xMargin: spacing / 2,
76
68
  });
77
-
78
- // ---------------------------------------------------------------------------
79
- // Rendering helpers (mirror the equivalents in line-chart.ts / area-chart.ts —
80
- // kept local so this module stays self-contained when scaffolded by `dolan add`)
81
- // ---------------------------------------------------------------------------
82
-
83
- /**
84
- * Formats a pixel value for an inline style, avoiding scientific notation.
85
- */
86
- const px = (n: number): string => `${n.toFixed(2)}px`;
87
-
88
- /**
89
- * Default axis label formatter — plain JS only (no `Intl`, absent in Lynx).
90
- */
91
- const defaultTickFormat = (value: number): string =>
92
- String(Math.round(value * 100) / 100);
93
-
94
- const clamp = (value: number, lo: number, hi: number): number =>
95
- Math.max(lo, Math.min(hi, value));
96
-
97
- // Fallback bar width (px) when there is only a single bar, so there is no
98
- // neighbor to derive a spacing from. Matches the candlestick series' fallback.
99
- const SINGLE_BAR_WIDTH = 24;
100
-
101
- /**
102
- * A single bar series drawn inside a `<ui-cartesian-chart>`. Reads the shared
103
- * scales from the parent chart via DI and renders one `<view>` rectangle per
104
- * datum, from the shared `baseline` up (or down) to the value.
105
- *
106
- * ## Why the host is `position: absolute; width/height: 100%`
107
- * Identical to `UiLineSeries`: Lynx resolves an absolute child against its
108
- * **direct parent** (there is no `position: static`), so the host itself must be
109
- * the plot-sized containing block. Every bar is a direct child of it.
110
- *
111
- * ## Per-bar colors
112
- * `color` fills every bar by default. Pass `colors` to give each bar its own
113
- * hue (a categorical bar chart) — the palette cycles if it is shorter than the
114
- * data, so a handful of colors tint any number of bars.
115
- */
116
- @Component({
117
- selector: 'ui-bar-series',
118
- standalone: true,
119
- imports: [LYNX_ELEMENTS],
120
- encapsulation: ViewEncapsulation.None,
121
- host: {
122
- style:
123
- 'position: absolute; left: 0px; top: 0px; width: 100%; height: 100%;',
124
- '[class]': 'userClass()',
125
- },
126
- template: `
127
- @for (bar of bars(); track $index) {
128
- <view [style]="bar.style" (bindtap)="onBarTap(bar.point)"></view>
129
- }
130
- `,
131
- })
132
- export class UiBarSeries {
133
- readonly #chart = inject(UiCartesianChart);
134
-
135
- readonly data = input.required<ChartPoint[]>();
136
- /** Fill for every bar. Overridden per bar by `colors` when that is set. */
137
- readonly color = input('var(--primary)');
138
- /** Per-bar fills (a categorical palette). Cycles when shorter than the data;
139
- * an empty array falls back to `color`. */
140
- readonly colors = input<string[]>([]);
141
- /** Bar width as a fraction of the spacing between adjacent bars. 0.7 leaves a
142
- * ~30% gap between bars, the conventional bar-chart look. */
143
- readonly barWidthRatio = input(0.7);
144
- /** Data-space y the bars grow from (usually 0). Values below it grow downward. */
145
- readonly baseline = input(0);
146
- /** Corner radius of each bar in px (0 = square corners). */
147
- readonly radius = input(0);
148
- readonly userClass = input<string>('', { alias: 'class' });
149
-
150
- readonly barTap = output<ChartPoint>();
151
-
152
- // Project each datum into plot-local pixel space via the shared scales.
153
- // Reading the chart's scale signals here makes this recompute whenever the
154
- // domain or plot size changes.
155
- readonly #projected = computed(() => {
156
- const scaleX = this.#chart.xScale();
157
- const scaleY = this.#chart.yScale();
158
- return this.data().map((point) => ({
159
- cx: scaleX(point.x),
160
- valueY: scaleY(point.y),
161
- point,
162
- }));
163
- });
164
-
165
- // Bar width = a fraction of the pixel spacing between bar centers, so bars
166
- // scale with the plot and never overlap. With one bar there is no spacing to
167
- // measure, so fall back to a fixed width.
168
- readonly #barWidth = computed(() => {
169
- const projected = this.#projected();
170
- const n = projected.length;
171
- if (n < 2) return SINGLE_BAR_WIDTH;
172
- const span = Math.abs(projected[n - 1].cx - projected[0].cx);
173
- return (span / (n - 1)) * this.barWidthRatio();
174
- });
175
-
176
- protected readonly bars = computed(() => {
177
- const projected = this.#projected();
178
- const fallback = this.color();
179
- const palette = this.colors();
180
- const radius = this.radius();
181
- // Clamp the baseline into the plot so a baseline outside the domain anchors
182
- // the bars to the nearest edge (top/bottom) instead of drawing off-plot.
183
- const baseY = clamp(
184
- this.#chart.yScale()(this.baseline()),
185
- 0,
186
- this.#chart.plotHeight(),
187
- );
188
- // computeBars works purely from the projected pixel fields; zip its output
189
- // back with each source point so a bar's tap can emit the original datum.
190
- return computeBars(projected, baseY, this.#barWidth()).map((shape, i) => {
191
- const fill = palette.length ? palette[i % palette.length] : fallback;
192
- return {
193
- point: projected[i].point,
194
- style:
195
- `position: absolute; left: ${px(shape.left)}; top: ${px(shape.top)}; ` +
196
- `width: ${px(shape.width)}; height: ${px(shape.height)}; ` +
197
- `background-color: ${fill}; border-radius: ${px(radius)};`,
198
- };
69
+ const tooltipOption = options.tooltip === false ? false : tooltip;
70
+
71
+ if (categorical) {
72
+ return defineChart({
73
+ marks: [
74
+ barY(data, {
75
+ x: (d) => String(d.x),
76
+ y: 'y',
77
+ y1: baseline,
78
+ fill: colors.length
79
+ ? (_d, { index }) => colors[index % colors.length]!
80
+ : color,
81
+ radius,
82
+ }),
83
+ ],
84
+ scales: {
85
+ x: {
86
+ scale: () => scaleBand().padding(1 - ratio),
87
+ grid: options.showXGrid ?? false,
88
+ axis: { label: options.xAxisLabel || undefined },
89
+ },
90
+ y: axes.y,
91
+ },
92
+ tooltip: tooltipOption,
199
93
  });
200
- });
201
-
202
- protected onBarTap(point: ChartPoint): void {
203
- this.barTap.emit(point);
204
94
  }
205
- }
95
+
96
+ const half = (ratio * spacing) / 2;
97
+ return defineChart({
98
+ marks: [
99
+ rect(data, {
100
+ x1: (d) => (d.x as number) - half,
101
+ x2: (d) => (d.x as number) + half,
102
+ y1: () => baseline,
103
+ y2: 'y',
104
+ // rect's fill is a single color, so per-bar colors go through the
105
+ // color scale, keyed by bar index.
106
+ ...(colors.length
107
+ ? { color: (_d: BarDatum, { index }: { index: number }) => index }
108
+ : { fill: color }),
109
+ radius,
110
+ }),
111
+ ],
112
+ scales: axes,
113
+ ...(colors.length
114
+ ? {
115
+ color: {
116
+ domain: data.map((_d, index) => index),
117
+ range: data.map((_d, index) => colors[index % colors.length]!),
118
+ },
119
+ }
120
+ : {}),
121
+ tooltip: tooltipOption,
122
+ });
123
+ };
206
124
 
207
125
  /**
208
- * A ready-to-use bar chart for the common single-series case. Composes a
209
- * `<ui-cartesian-chart>` with one `<ui-bar-series>` and auto-derives rounded
210
- * axis domains + ticks from the data. For a line overlay, or full control over
211
- * the axes, use `<ui-cartesian-chart>` with a `<ui-bar-series>` (and optionally
212
- * a `<ui-line-series>`) directly.
126
+ * A bar chart for one series. For grouped or stacked bars, horizontal bars or
127
+ * a line over the bars, build a definition with `barY`/`barX` and render it
128
+ * with `<ui-chart>`.
129
+ *
130
+ * ```html
131
+ * <ui-bar-chart [data]="sales" [radius]="4" />
132
+ * ```
213
133
  */
214
134
  @Component({
215
135
  selector: 'ui-bar-chart',
216
- standalone: true,
217
- imports: [LYNX_ELEMENTS, UiCartesianChart, UiBarSeries],
136
+ imports: [UiChart],
218
137
  encapsulation: ViewEncapsulation.None,
219
138
  template: `
220
- <ui-cartesian-chart
221
- [xDomain]="xAxis().domain"
222
- [yDomain]="yAxis().domain"
223
- [xTicks]="xAxis().ticks"
224
- [yTicks]="yAxis().ticks"
225
- [width]="width()"
226
- [height]="height()"
227
- [tickCount]="tickCount()"
228
- [showXGrid]="showXGrid()"
229
- [xAxisLabel]="xAxisLabel()"
230
- [yAxisLabel]="yAxisLabel()"
231
- [padding]="effectivePadding()"
232
- [xTickFormat]="xTickFormat()"
233
- [yTickFormat]="yTickFormat()"
234
- [zoomable]="zoomable()"
235
- [zoomAxes]="zoomAxes()"
236
- [showZoomControls]="showZoomControls()"
237
- [minZoom]="minZoom()"
238
- [maxZoom]="maxZoom()"
239
- [zoomStep]="zoomStep()"
240
- [class]="userClass()"
241
- >
242
- <ui-bar-series
243
- [data]="data()"
244
- [color]="color()"
245
- [colors]="colors()"
246
- [barWidthRatio]="barWidthRatio()"
247
- [baseline]="baseline()"
248
- [radius]="radius()"
249
- (barTap)="barTap.emit($event)"
250
- />
251
- </ui-cartesian-chart>
139
+ <ui-chart
140
+ [definition]="definition()"
141
+ ${PRESET_CHART_BINDINGS}
142
+ (pointSelect)="onSelect($event)"
143
+ />
252
144
  `,
253
145
  })
254
- export class UiBarChart {
255
- readonly data = input.required<ChartPoint[]>();
146
+ export class UiBarChart extends UiCartesianPreset<BarDatum> {
147
+ readonly data = input.required<readonly BarDatum[]>();
256
148
  readonly color = input('var(--primary)');
257
- readonly colors = input<string[]>([]);
258
- readonly width = input(320);
259
- readonly height = input(200);
260
- readonly tickCount = input(5);
149
+ readonly colors = input<readonly string[]>([]);
261
150
  readonly barWidthRatio = input(0.7);
262
- /** Data-space y the bars grow from (usually 0). Values below it grow downward. */
263
151
  readonly baseline = input(0);
264
- /** Corner radius of each bar in px. */
265
152
  readonly radius = input(0);
266
- /** Draw vertical gridlines at each x tick. */
267
- readonly showXGrid = input(false);
268
- /** Title for the x-axis (centered below the tick labels). */
269
- readonly xAxisLabel = input<string>('');
270
- /** Title for the y-axis (rotated in the left gutter). */
271
- readonly yAxisLabel = input<string>('');
272
- /** Inner inset of the plot area — merged over the auto half-bar padding
273
- * (below), so any side you set wins. See {@link ChartPadding}. */
274
- readonly padding = input<ChartPadding>({});
275
- /** Force the x-axis lower/upper bound instead of deriving it from the data.
276
- * Omitted bounds fall back to the data extremes. */
277
- readonly xMin = input<number | undefined>(undefined);
278
- readonly xMax = input<number | undefined>(undefined);
279
- /** Force the y-axis lower/upper bound. The `baseline` is still folded in, so
280
- * `yMin` only lowers the floor further — it can't clip a bar's base off. */
281
- readonly yMin = input<number | undefined>(undefined);
282
- readonly yMax = input<number | undefined>(undefined);
283
- readonly xTickFormat = input<(value: number) => string>(defaultTickFormat);
284
- readonly yTickFormat = input<(value: number) => string>(defaultTickFormat);
285
- readonly userClass = input<string>('', { alias: 'class' });
286
-
287
- // Pan/zoom — forwarded to the underlying <ui-cartesian-chart>. Off by default
288
- // so a bar chart stays static (and scroll-view friendly) unless opted in.
289
- readonly zoomable = input(false);
290
- readonly zoomAxes = input<'x' | 'y' | 'xy'>('xy');
291
- readonly showZoomControls = input(true);
292
- readonly minZoom = input(1);
293
- readonly maxZoom = input(8);
294
- readonly zoomStep = input(1.4);
295
153
 
296
- readonly barTap = output<ChartPoint>();
297
-
298
- // Auto-compute rounded axes from the data extremes, letting an explicit
299
- // min/max override either end. `Math.min/max(...[])` yield ±Infinity for empty
300
- // data, which `niceScale` handles by padding.
301
- protected readonly xAxis = computed(() => {
302
- const xs = this.data().map((p) => p.x);
303
- const lo = this.xMin() ?? Math.min(...xs);
304
- const hi = this.xMax() ?? Math.max(...xs);
305
- return niceScale(lo, hi, this.tickCount());
306
- });
307
- // Fold the baseline into the y-extent so the axis always spans the whole bar:
308
- // a positive-only series still shows the baseline at the bottom, and negative
309
- // values pull the domain below it. An explicit yMin/yMax overrides the
310
- // corresponding data-derived end (the baseline still participates in the other).
311
- protected readonly yAxis = computed(() => {
312
- const ys = this.data().map((p) => p.y);
313
- const base = this.baseline();
314
- const lo = this.yMin() ?? Math.min(base, ...ys);
315
- const hi = this.yMax() ?? Math.max(base, ...ys);
316
- return niceScale(lo, hi, this.tickCount());
317
- });
318
-
319
- // A bar is centered on its x, so the first and last bars would spill over the
320
- // plot edges. Insetting the scales by half a bar's spacing keeps them fully
321
- // inside. User `padding` is spread last, so an explicit side wins.
322
- protected readonly effectivePadding = computed<ChartPadding>(() => {
323
- const n = this.data().length;
324
- const half = n > 1 ? 0.5 / (n - 1) : 0;
325
- return { left: half, right: half, ...this.padding() };
326
- });
154
+ /** The tapped bar's datum. */
155
+ readonly barTap = output<BarDatum>();
156
+
157
+ protected readonly definition = computed(() =>
158
+ barChartDefinition({
159
+ ...this.commonOptions(),
160
+ data: this.data(),
161
+ color: this.color(),
162
+ colors: this.colors(),
163
+ barWidthRatio: this.barWidthRatio(),
164
+ baseline: this.baseline(),
165
+ radius: this.radius(),
166
+ }),
167
+ );
168
+
169
+ protected onSelect(point: ChartPoint | null): void {
170
+ const datum = this.datumOf(point);
171
+ if (datum) this.barTap.emit(datum);
172
+ }
327
173
  }
@@ -1,6 +1,6 @@
1
1
  export {
2
+ type BarChartOptions,
3
+ type BarDatum,
2
4
  UiBarChart,
3
- UiBarSeries,
4
- computeBars,
5
- type BarShape,
5
+ barChartDefinition,
6
6
  } from './bar-chart';