@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.
- package/dist/commands/add.d.ts +6 -2
- package/dist/commands/add.js +41 -11
- package/dist/commands/add.js.map +1 -1
- package/dist/commands/add.test.js +162 -9
- package/dist/commands/add.test.js.map +1 -1
- package/dist/commands/diff.d.ts +2 -1
- package/dist/commands/diff.js +38 -25
- package/dist/commands/diff.js.map +1 -1
- package/dist/commands/diff.test.js +72 -11
- package/dist/commands/diff.test.js.map +1 -1
- package/dist/commands/doctor.js +228 -34
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/doctor.test.js +367 -10
- package/dist/commands/doctor.test.js.map +1 -1
- package/dist/commands/eject.js +7 -0
- package/dist/commands/eject.js.map +1 -1
- package/dist/commands/eject.test.js +39 -0
- package/dist/commands/eject.test.js.map +1 -1
- package/dist/commands/info.js +3 -2
- package/dist/commands/info.js.map +1 -1
- package/dist/commands/init.d.ts +37 -1
- package/dist/commands/init.js +169 -40
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/init.test.js +392 -27
- package/dist/commands/init.test.js.map +1 -1
- package/dist/commands/install-packages.d.ts +6 -0
- package/dist/commands/install-packages.js +20 -0
- package/dist/commands/install-packages.js.map +1 -0
- package/dist/commands/install-packages.test.d.ts +1 -0
- package/dist/commands/install-packages.test.js +55 -0
- package/dist/commands/install-packages.test.js.map +1 -0
- package/dist/commands/list.js +21 -14
- package/dist/commands/list.js.map +1 -1
- package/dist/commands/list.test.js +62 -5
- package/dist/commands/list.test.js.map +1 -1
- package/dist/commands/outdated.js +11 -10
- package/dist/commands/outdated.js.map +1 -1
- package/dist/commands/outdated.test.js +20 -5
- package/dist/commands/outdated.test.js.map +1 -1
- package/dist/commands/remove.d.ts +4 -0
- package/dist/commands/remove.js +17 -4
- package/dist/commands/remove.js.map +1 -1
- package/dist/commands/remove.test.js +53 -2
- package/dist/commands/remove.test.js.map +1 -1
- package/dist/commands/theme.js +1 -1
- package/dist/commands/theme.js.map +1 -1
- package/dist/commands/update.d.ts +11 -0
- package/dist/commands/update.js +174 -58
- package/dist/commands/update.js.map +1 -1
- package/dist/commands/update.test.js +587 -33
- package/dist/commands/update.test.js.map +1 -1
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -1
- package/dist/lockfile.d.ts +7 -0
- package/dist/lockfile.js +36 -0
- package/dist/lockfile.js.map +1 -1
- package/dist/lockfile.test.d.ts +1 -0
- package/dist/lockfile.test.js +25 -0
- package/dist/lockfile.test.js.map +1 -0
- package/dist/registry.d.ts +14 -0
- package/dist/registry.js +78 -24
- package/dist/registry.js.map +1 -1
- package/dist/registry.test.js +119 -38
- package/dist/registry.test.js.map +1 -1
- package/dist/test-utils.d.ts +5 -0
- package/dist/test-utils.js +6 -1
- package/dist/test-utils.js.map +1 -1
- package/dist/testing/fire-lynx-event.d.ts +14 -0
- package/dist/testing/fire-lynx-event.js +19 -0
- package/dist/testing/fire-lynx-event.js.map +1 -0
- package/dist/testing/fire-lynx-event.test.d.ts +1 -0
- package/dist/testing/fire-lynx-event.test.js +44 -0
- package/dist/testing/fire-lynx-event.test.js.map +1 -0
- package/dist/testing/lynx-main-thread.d.ts +22 -0
- package/dist/testing/lynx-main-thread.js +11 -0
- package/dist/testing/lynx-main-thread.js.map +1 -0
- package/dist/testing/lynx-main-thread.test.d.ts +1 -0
- package/dist/testing/lynx-main-thread.test.js +24 -0
- package/dist/testing/lynx-main-thread.test.js.map +1 -0
- package/dist/testing/set-input-signal.d.ts +14 -0
- package/dist/testing/set-input-signal.js +21 -0
- package/dist/testing/set-input-signal.js.map +1 -0
- package/dist/testing/set-input-signal.test.d.ts +1 -0
- package/dist/testing/set-input-signal.test.js +20 -0
- package/dist/testing/set-input-signal.test.js.map +1 -0
- package/dist/ui/components/accordion/accordion.test.ts +158 -144
- package/dist/ui/components/accordion/accordion.ts +19 -14
- package/dist/ui/components/action-sheet/action-sheet.test.ts +283 -151
- package/dist/ui/components/action-sheet/action-sheet.ts +77 -43
- package/dist/ui/components/alert/alert.test.ts +67 -19
- package/dist/ui/components/alert/alert.ts +14 -16
- package/dist/ui/components/alert-dialog/alert-dialog.test.ts +208 -94
- package/dist/ui/components/alert-dialog/alert-dialog.ts +75 -38
- package/dist/ui/components/area-chart/area-chart.test.ts +134 -119
- package/dist/ui/components/area-chart/area-chart.ts +100 -315
- package/dist/ui/components/area-chart/index.ts +2 -3
- package/dist/ui/components/avatar/avatar.test.ts +89 -45
- package/dist/ui/components/badge/badge.test.ts +57 -23
- package/dist/ui/components/badge/badge.ts +12 -14
- package/dist/ui/components/bar-chart/bar-chart.test.ts +172 -45
- package/dist/ui/components/bar-chart/bar-chart.ts +140 -294
- package/dist/ui/components/bar-chart/index.ts +3 -3
- package/dist/ui/components/bottom-sheet/bottom-sheet.test.ts +386 -128
- package/dist/ui/components/bottom-sheet/bottom-sheet.ts +170 -53
- package/dist/ui/components/button/button.test.ts +611 -2
- package/dist/ui/components/button/button.ts +434 -49
- package/dist/ui/components/button/index.ts +2 -0
- package/dist/ui/components/button-group/button-group.test.ts +484 -2
- package/dist/ui/components/button-group/button-group.ts +380 -68
- package/dist/ui/components/button-group/index.ts +3 -0
- package/dist/ui/components/candlestick-chart/candlestick-chart.test.ts +183 -58
- package/dist/ui/components/candlestick-chart/candlestick-chart.ts +150 -323
- package/dist/ui/components/candlestick-chart/index.ts +3 -4
- package/dist/ui/components/card/card.test.ts +82 -62
- package/dist/ui/components/chart/chart-focus.test.ts +132 -0
- package/dist/ui/components/chart/chart-focus.ts +115 -0
- package/dist/ui/components/chart/chart-presets.test.ts +335 -0
- package/dist/ui/components/chart/chart-presets.ts +278 -0
- package/dist/ui/components/chart/chart-scene.test.ts +589 -0
- package/dist/ui/components/chart/chart-scene.ts +483 -0
- package/dist/ui/components/chart/chart-theme.test.ts +85 -0
- package/dist/ui/components/chart/chart-theme.ts +210 -0
- package/dist/ui/components/chart/chart-zoom.test.ts +159 -0
- package/dist/ui/components/chart/chart-zoom.ts +162 -0
- package/dist/ui/components/chart/chart.test.ts +1017 -0
- package/dist/ui/components/chart/chart.ts +934 -0
- package/dist/ui/components/chart/index.ts +24 -0
- package/dist/ui/components/checkbox/checkbox.test.ts +124 -42
- package/dist/ui/components/checkbox/checkbox.ts +23 -23
- package/dist/ui/components/collapsible/collapsible.test.ts +158 -81
- package/dist/ui/components/collapsible/collapsible.ts +19 -14
- package/dist/ui/components/context-menu/context-menu.test.ts +224 -0
- package/dist/ui/components/context-menu/context-menu.ts +110 -0
- package/dist/ui/components/context-menu/index.ts +1 -0
- package/dist/ui/components/dialog/dialog.test.ts +213 -108
- package/dist/ui/components/dialog/dialog.ts +72 -35
- package/dist/ui/components/dock/dock-scroll.test.ts +130 -0
- package/dist/ui/components/dock/dock-scroll.ts +133 -0
- package/dist/ui/components/dock/dock.test.ts +749 -0
- package/dist/ui/components/dock/dock.ts +960 -0
- package/dist/ui/components/dock/index.ts +2 -0
- package/dist/ui/components/empty-state/empty-state.test.ts +28 -39
- package/dist/ui/components/glass/concentric-radius.test.ts +13 -0
- package/dist/ui/components/glass/concentric-radius.ts +17 -0
- package/dist/ui/components/glass/glass-design.test.ts +78 -0
- package/dist/ui/components/glass/glass-design.ts +119 -0
- package/dist/ui/components/glass/glass.test.ts +338 -0
- package/dist/ui/components/glass/glass.ts +360 -0
- package/dist/ui/components/glass/index.ts +18 -0
- package/dist/ui/components/icon/icon-theme.test.ts +169 -0
- package/dist/ui/components/icon/icon-theme.ts +227 -0
- package/dist/ui/components/icon/icon.test.ts +85 -25
- package/dist/ui/components/icon/icon.ts +38 -21
- package/dist/ui/components/input/input.test.ts +178 -101
- package/dist/ui/components/input/input.ts +15 -12
- package/dist/ui/components/label/label.test.ts +11 -24
- package/dist/ui/components/line-chart/index.ts +2 -3
- package/dist/ui/components/line-chart/line-chart.test.ts +149 -56
- package/dist/ui/components/line-chart/line-chart.ts +83 -241
- package/dist/ui/components/list/list.test.ts +119 -21
- package/dist/ui/components/list/list.ts +21 -15
- package/dist/ui/components/menu/index.ts +25 -0
- package/dist/ui/components/menu/menu-placement.test.ts +153 -0
- package/dist/ui/components/menu/menu-placement.ts +159 -0
- package/dist/ui/components/menu/menu-popup.test.ts +632 -0
- package/dist/ui/components/menu/menu-popup.ts +544 -0
- package/dist/ui/components/menu/menu.test.ts +429 -0
- package/dist/ui/components/menu/menu.ts +415 -0
- package/dist/ui/components/nav-drawer/nav-drawer.test.ts +435 -191
- package/dist/ui/components/nav-drawer/nav-drawer.ts +127 -56
- package/dist/ui/components/pagination/pagination.test.ts +177 -65
- package/dist/ui/components/pagination/pagination.ts +55 -8
- package/dist/ui/components/progress/progress.test.ts +102 -46
- package/dist/ui/components/progress/progress.ts +52 -54
- package/dist/ui/components/radio-group/radio-group.test.ts +128 -83
- package/dist/ui/components/radio-group/radio-group.ts +24 -24
- package/dist/ui/components/scatter-chart/index.ts +2 -3
- package/dist/ui/components/scatter-chart/scatter-chart.test.ts +131 -26
- package/dist/ui/components/scatter-chart/scatter-chart.ts +104 -250
- package/dist/ui/components/scroll-area/scroll-area.test.ts +95 -24
- package/dist/ui/components/scroll-area/scroll-area.ts +8 -8
- package/dist/ui/components/select/select.test.ts +138 -78
- package/dist/ui/components/select/select.ts +17 -11
- package/dist/ui/components/separator/separator.test.ts +18 -17
- package/dist/ui/components/sheet/sheet.test.ts +64 -11
- package/dist/ui/components/sheet/sheet.ts +9 -1
- package/dist/ui/components/skeleton/skeleton.test.ts +24 -7
- package/dist/ui/components/skeleton/skeleton.ts +10 -16
- package/dist/ui/components/spinner/spinner.test.ts +67 -22
- package/dist/ui/components/spinner/spinner.ts +48 -41
- package/dist/ui/components/switch/switch.test.ts +116 -46
- package/dist/ui/components/switch/switch.ts +8 -13
- package/dist/ui/components/tab-bar/index.ts +1 -0
- package/dist/ui/components/tab-bar/tab-bar.test.ts +280 -0
- package/dist/ui/components/tab-bar/tab-bar.ts +230 -0
- package/dist/ui/components/tabs/tabs.test.ts +215 -157
- package/dist/ui/components/tabs/tabs.ts +110 -118
- package/dist/ui/components/textarea/textarea.test.ts +206 -100
- package/dist/ui/components/textarea/textarea.ts +14 -12
- package/dist/ui/components/toast/toast.test.ts +436 -315
- package/dist/ui/components/toast/toast.ts +62 -55
- package/dist/ui/components/toggle/toggle.test.ts +247 -140
- package/dist/ui/theme/chart-colors.test.ts +35 -0
- package/dist/ui/theme/dark.css +46 -2
- package/dist/ui/theme/default.css +63 -7
- package/dist/ui/theme/icon-colors.test.ts +35 -0
- package/dist/ui/theme/rgb-channels.test.ts +84 -0
- package/dist/ui/theme/tailwind-build.test.ts +114 -0
- package/dist/ui/theme/tailwind-css.test.ts +83 -0
- package/dist/ui/theme/tailwind-plugin.test.ts +42 -2
- package/dist/ui/theme/tailwind-plugin.ts +98 -25
- package/dist/ui/theme/tailwind.css +113 -0
- package/dist/ui/utils/animate.test.ts +13 -1
- package/dist/utils/analyze.d.ts +14 -0
- package/dist/utils/analyze.js +15 -0
- package/dist/utils/analyze.js.map +1 -1
- package/dist/utils/analyze.test.js +14 -1
- package/dist/utils/analyze.test.js.map +1 -1
- package/dist/utils/installed.d.ts +29 -0
- package/dist/utils/installed.js +43 -0
- package/dist/utils/installed.js.map +1 -0
- package/dist/utils/installed.test.d.ts +1 -0
- package/dist/utils/installed.test.js +57 -0
- package/dist/utils/installed.test.js.map +1 -0
- package/dist/utils/interactive.d.ts +9 -0
- package/dist/utils/interactive.js +10 -0
- package/dist/utils/interactive.js.map +1 -0
- package/dist/utils/interactive.test.d.ts +1 -0
- package/dist/utils/interactive.test.js +24 -0
- package/dist/utils/interactive.test.js.map +1 -0
- package/dist/utils/packages.d.ts +36 -0
- package/dist/utils/packages.js +70 -0
- package/dist/utils/packages.js.map +1 -0
- package/dist/utils/packages.test.d.ts +1 -0
- package/dist/utils/packages.test.js +89 -0
- package/dist/utils/packages.test.js.map +1 -0
- package/dist/utils/resolve-paths.d.ts +17 -1
- package/dist/utils/resolve-paths.js +21 -3
- package/dist/utils/resolve-paths.js.map +1 -1
- package/dist/utils/resolve-paths.test.js +38 -2
- package/dist/utils/resolve-paths.test.js.map +1 -1
- package/dist/utils/tailwind.d.ts +25 -0
- package/dist/utils/tailwind.js +51 -0
- package/dist/utils/tailwind.js.map +1 -0
- package/dist/utils/tailwind.test.d.ts +1 -0
- package/dist/utils/tailwind.test.js +71 -0
- package/dist/utils/tailwind.test.js.map +1 -0
- package/package.json +13 -1
- package/dist/ui/components/area-chart/area-chart-render.test.ts +0 -121
- package/dist/ui/components/bar-chart/bar-chart-render.test.ts +0 -174
- package/dist/ui/components/button/button-render.test.ts +0 -113
- package/dist/ui/components/button-group/button-group-render.test.ts +0 -146
- package/dist/ui/components/candlestick-chart/candlestick-chart-render.test.ts +0 -174
- package/dist/ui/components/cartesian-chart/cartesian-chart-render.test.ts +0 -188
- package/dist/ui/components/cartesian-chart/cartesian-chart.test.ts +0 -525
- package/dist/ui/components/cartesian-chart/cartesian-chart.ts +0 -1140
- package/dist/ui/components/cartesian-chart/index.ts +0 -18
- package/dist/ui/components/line-chart/line-chart-render.test.ts +0 -121
- package/dist/ui/components/scatter-chart/scatter-chart-render.test.ts +0 -157
|
@@ -2,354 +2,181 @@ 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 {
|
|
10
|
-
|
|
8
|
+
import { defineChart, link, rect } from '@tanstack/charts/universal';
|
|
9
|
+
import type {
|
|
10
|
+
ChartPoint,
|
|
11
|
+
ChartTooltipContent,
|
|
12
|
+
ChartTooltipContentContext,
|
|
13
|
+
} from '@tanstack/charts/types';
|
|
14
|
+
|
|
15
|
+
import { UiChart, tooltip } from '../chart/chart';
|
|
11
16
|
import {
|
|
12
|
-
type
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
*/
|
|
27
|
-
export type CandlestickPoint = {
|
|
28
|
-
x: number;
|
|
29
|
-
open: number;
|
|
30
|
-
high: number;
|
|
31
|
-
low: number;
|
|
32
|
-
close: number;
|
|
33
|
-
};
|
|
34
|
-
|
|
35
|
-
/** One candle after projection into plot-local pixel space. */
|
|
36
|
-
type ProjectedCandle = {
|
|
37
|
-
/** Center x of the candle, in px. */
|
|
38
|
-
cx: number;
|
|
39
|
-
openY: number;
|
|
40
|
-
highY: number;
|
|
41
|
-
lowY: number;
|
|
42
|
-
closeY: number;
|
|
43
|
-
/** `true` when the period closed at or above its open (a rising candle). */
|
|
44
|
-
up: boolean;
|
|
17
|
+
type CartesianPresetOptions,
|
|
18
|
+
PRESET_CHART_BINDINGS,
|
|
19
|
+
UiCartesianPreset,
|
|
20
|
+
minSpacing,
|
|
21
|
+
presetAxes,
|
|
22
|
+
} from '../chart/chart-presets';
|
|
23
|
+
|
|
24
|
+
/** One period's open, high, low and close (OHLC) at x. */
|
|
25
|
+
export type CandlestickDatum = {
|
|
26
|
+
readonly x: number;
|
|
27
|
+
readonly open: number;
|
|
28
|
+
readonly high: number;
|
|
29
|
+
readonly low: number;
|
|
30
|
+
readonly close: number;
|
|
45
31
|
};
|
|
46
32
|
|
|
47
|
-
|
|
48
|
-
|
|
33
|
+
export type CandlestickChartOptions = {
|
|
34
|
+
readonly data: readonly CandlestickDatum[];
|
|
35
|
+
/** Color of a candle that closed at or above its open. */
|
|
36
|
+
readonly upColor?: string;
|
|
37
|
+
/** Color of a candle that closed below its open. */
|
|
38
|
+
readonly downColor?: string;
|
|
39
|
+
/** Width of the high–low wick in px. */
|
|
40
|
+
readonly wickWidth?: number;
|
|
41
|
+
/** Body width as a fraction of the space each candle has, `0`–`1`. */
|
|
42
|
+
readonly bodyWidthRatio?: number;
|
|
43
|
+
} & CartesianPresetOptions;
|
|
49
44
|
|
|
50
|
-
|
|
51
|
-
* Render geometry for one candle: a thin vertical **wick** (high→low) and a
|
|
52
|
-
* **body** (open↔close), both plain axis-aligned rectangles. `up` is echoed from
|
|
53
|
-
* the input so the caller can pick the fill color.
|
|
54
|
-
*/
|
|
55
|
-
export type CandleShape = {
|
|
56
|
-
wick: Rect;
|
|
57
|
-
body: Rect;
|
|
58
|
-
up: boolean;
|
|
59
|
-
};
|
|
45
|
+
const isUp = (d: CandlestickDatum): boolean => d.close >= d.open;
|
|
60
46
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
47
|
+
const isCandle = (datum: unknown): datum is CandlestickDatum =>
|
|
48
|
+
typeof datum === 'object' &&
|
|
49
|
+
datum !== null &&
|
|
50
|
+
'open' in datum &&
|
|
51
|
+
'close' in datum;
|
|
64
52
|
|
|
65
53
|
/**
|
|
66
|
-
*
|
|
67
|
-
*
|
|
68
|
-
*
|
|
69
|
-
* axis-aligned box, so this is a straight geometric mapping.
|
|
70
|
-
*
|
|
71
|
-
* Screen y grows *downward*, so a higher price projects to a *smaller* y. Hence
|
|
72
|
-
* the wick's top is `min(highY, lowY)` and the body's top is `min(openY,
|
|
73
|
-
* closeY)` — taking the pixel min picks the visually-higher (larger-value) edge.
|
|
74
|
-
*
|
|
75
|
-
* A doji (open === close) would give a zero-height body that renders invisibly,
|
|
76
|
-
* so the body height is clamped to `minBodyHeight` (a thin line, like a bar).
|
|
77
|
-
* Wick and body are both centered on the candle's `cx`.
|
|
78
|
-
*
|
|
79
|
-
* @param bodyWidth width of the body rectangle in px (the wick stays `wickWidth`).
|
|
80
|
-
* @param minBodyHeight floor for the body height so a doji is still visible.
|
|
54
|
+
* The definition `<ui-candlestick-chart>` renders: a wick from low to high and
|
|
55
|
+
* a body from open to close per period, colored by direction. The tooltip lists
|
|
56
|
+
* all four prices.
|
|
81
57
|
*/
|
|
82
|
-
export const
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
58
|
+
export const candlestickChartDefinition = (
|
|
59
|
+
options: CandlestickChartOptions,
|
|
60
|
+
) => {
|
|
61
|
+
const {
|
|
62
|
+
data,
|
|
63
|
+
upColor = 'rgba(34, 197, 94, 1)',
|
|
64
|
+
downColor = 'rgba(239, 68, 68, 1)',
|
|
65
|
+
wickWidth = 1,
|
|
66
|
+
bodyWidthRatio = 0.6,
|
|
67
|
+
} = options;
|
|
68
|
+
const xs = data.map((d) => d.x);
|
|
69
|
+
const spacing = minSpacing(xs);
|
|
70
|
+
const half = (Math.min(1, Math.max(0, bodyWidthRatio)) * spacing) / 2;
|
|
71
|
+
return defineChart({
|
|
72
|
+
marks: [
|
|
73
|
+
link(data, {
|
|
74
|
+
x1: 'x',
|
|
75
|
+
y1: 'low',
|
|
76
|
+
x2: 'x',
|
|
77
|
+
y2: 'high',
|
|
78
|
+
stroke: (d) => (isUp(d) ? upColor : downColor),
|
|
79
|
+
strokeWidth: wickWidth,
|
|
80
|
+
}),
|
|
81
|
+
rect(data, {
|
|
82
|
+
x1: (d) => d.x - half,
|
|
83
|
+
x2: (d) => d.x + half,
|
|
84
|
+
y1: 'open',
|
|
85
|
+
y2: 'close',
|
|
86
|
+
// rect's fill is a single color, so direction goes through the color
|
|
87
|
+
// scale below.
|
|
88
|
+
color: (d) => (isUp(d) ? 'up' : 'down'),
|
|
89
|
+
}),
|
|
90
|
+
],
|
|
91
|
+
scales: presetAxes(options, {
|
|
92
|
+
x: xs,
|
|
93
|
+
y: data.flatMap((d) => [d.low, d.high]),
|
|
94
|
+
// Half a slot of room either side so the edge candles fit.
|
|
95
|
+
xMargin: spacing / 2,
|
|
96
|
+
}),
|
|
97
|
+
color: { domain: ['up', 'down'], range: [upColor, downColor] },
|
|
98
|
+
tooltip:
|
|
99
|
+
options.tooltip === false
|
|
100
|
+
? false
|
|
101
|
+
: {
|
|
102
|
+
use: tooltip,
|
|
103
|
+
// Typed for any point, not just candles: a definition built on
|
|
104
|
+
// this one can add marks (a moving average), whose points get a
|
|
105
|
+
// plain x/y tooltip.
|
|
106
|
+
content: (
|
|
107
|
+
points: readonly ChartPoint[],
|
|
108
|
+
context: ChartTooltipContentContext,
|
|
109
|
+
): ChartTooltipContent => {
|
|
110
|
+
const point = points[0];
|
|
111
|
+
const d = point?.datum;
|
|
112
|
+
if (!point || !isCandle(d)) {
|
|
113
|
+
return {
|
|
114
|
+
title: point ? context.formatX(point.xValue) : undefined,
|
|
115
|
+
rows: points.map((p) => ({
|
|
116
|
+
label: context.yLabel,
|
|
117
|
+
value: context.formatY(p.yValue),
|
|
118
|
+
})),
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
return {
|
|
122
|
+
title: String(d.x),
|
|
123
|
+
color: isUp(d) ? upColor : downColor,
|
|
124
|
+
rows: [
|
|
125
|
+
{ label: 'Open', value: String(d.open) },
|
|
126
|
+
{ label: 'High', value: String(d.high) },
|
|
127
|
+
{ label: 'Low', value: String(d.low) },
|
|
128
|
+
{ label: 'Close', value: String(d.close) },
|
|
129
|
+
],
|
|
130
|
+
};
|
|
131
|
+
},
|
|
132
|
+
},
|
|
108
133
|
});
|
|
109
|
-
|
|
110
|
-
// ---------------------------------------------------------------------------
|
|
111
|
-
// Rendering helpers (mirror the equivalents in line-chart.ts / area-chart.ts —
|
|
112
|
-
// kept local so this module stays self-contained when scaffolded by `dolan add`)
|
|
113
|
-
// ---------------------------------------------------------------------------
|
|
114
|
-
|
|
115
|
-
/**
|
|
116
|
-
* Formats a pixel value for an inline style, avoiding scientific notation.
|
|
117
|
-
*/
|
|
118
|
-
const px = (n: number): string => `${n.toFixed(2)}px`;
|
|
119
|
-
|
|
120
|
-
/**
|
|
121
|
-
* Default axis label formatter — plain JS only (no `Intl`, absent in Lynx).
|
|
122
|
-
*/
|
|
123
|
-
const defaultTickFormat = (value: number): string =>
|
|
124
|
-
String(Math.round(value * 100) / 100);
|
|
125
|
-
|
|
126
|
-
// Fallback body width (px) when there is only a single candle, so there is no
|
|
127
|
-
// neighbor to derive a spacing from. Matches the React source's `font(8)`.
|
|
128
|
-
const SINGLE_CANDLE_BODY_WIDTH = 8;
|
|
134
|
+
};
|
|
129
135
|
|
|
130
136
|
/**
|
|
131
|
-
*
|
|
132
|
-
*
|
|
133
|
-
*
|
|
137
|
+
* An OHLC candlestick chart. For a moving average or volume bars over the
|
|
138
|
+
* candles, build a definition with `link`, `rect` and `lineY` and render it
|
|
139
|
+
* with `<ui-chart>`.
|
|
134
140
|
*
|
|
135
|
-
*
|
|
136
|
-
*
|
|
137
|
-
*
|
|
138
|
-
* the plot-sized containing block. Every wick and body is a direct child of it.
|
|
139
|
-
*
|
|
140
|
-
* ## Differentiating up vs down
|
|
141
|
-
* On a color screen, the fill color carries direction — up candles use
|
|
142
|
-
* `upColor`, down candles `downColor`. (The React original also supported SVG
|
|
143
|
-
* *fill patterns* for e-ink displays where color can't be relied on; Lynx has
|
|
144
|
-
* no SVG patterns, so that is dropped in favor of solid color.)
|
|
145
|
-
*/
|
|
146
|
-
@Component({
|
|
147
|
-
selector: 'ui-candlestick-series',
|
|
148
|
-
standalone: true,
|
|
149
|
-
imports: [LYNX_ELEMENTS],
|
|
150
|
-
encapsulation: ViewEncapsulation.None,
|
|
151
|
-
host: {
|
|
152
|
-
style:
|
|
153
|
-
'position: absolute; left: 0px; top: 0px; width: 100%; height: 100%;',
|
|
154
|
-
'[class]': 'userClass()',
|
|
155
|
-
},
|
|
156
|
-
template: `
|
|
157
|
-
<!-- Wick first so the body paints on top of it (Lynx paint order follows
|
|
158
|
-
source order; z-index is unnecessary). The body is the tap target. -->
|
|
159
|
-
@for (candle of candles(); track $index) {
|
|
160
|
-
<view [style]="candle.wickStyle"></view>
|
|
161
|
-
<view
|
|
162
|
-
[style]="candle.bodyStyle"
|
|
163
|
-
(bindtap)="onCandleTap(candle.point)"
|
|
164
|
-
></view>
|
|
165
|
-
}
|
|
166
|
-
`,
|
|
167
|
-
})
|
|
168
|
-
export class UiCandlestickSeries {
|
|
169
|
-
readonly #chart = inject(UiCartesianChart);
|
|
170
|
-
|
|
171
|
-
readonly data = input.required<CandlestickPoint[]>();
|
|
172
|
-
/** Fill for rising candles (close ≥ open). Fixed data hue, not theme chrome. */
|
|
173
|
-
readonly upColor = input('rgba(34, 197, 94, 1)');
|
|
174
|
-
/** Fill for falling candles (close < open). */
|
|
175
|
-
readonly downColor = input('rgba(239, 68, 68, 1)');
|
|
176
|
-
/** Width of the high–low wick in px. */
|
|
177
|
-
readonly wickWidth = input(1);
|
|
178
|
-
/** Body width as a fraction of the spacing between adjacent candles. 0.6
|
|
179
|
-
* leaves a ~40% gap between bodies, the conventional candlestick look. */
|
|
180
|
-
readonly bodyWidthRatio = input(0.6);
|
|
181
|
-
readonly userClass = input<string>('', { alias: 'class' });
|
|
182
|
-
|
|
183
|
-
readonly candleTap = output<CandlestickPoint>();
|
|
184
|
-
|
|
185
|
-
// Project each OHLC point into plot-local pixel space via the shared scales.
|
|
186
|
-
// Reading the chart's scale signals here makes this recompute whenever the
|
|
187
|
-
// domain or plot size changes.
|
|
188
|
-
readonly #projected = computed(() => {
|
|
189
|
-
const scaleX = this.#chart.xScale();
|
|
190
|
-
const scaleY = this.#chart.yScale();
|
|
191
|
-
return this.data().map((point) => ({
|
|
192
|
-
cx: scaleX(point.x),
|
|
193
|
-
openY: scaleY(point.open),
|
|
194
|
-
highY: scaleY(point.high),
|
|
195
|
-
lowY: scaleY(point.low),
|
|
196
|
-
closeY: scaleY(point.close),
|
|
197
|
-
up: point.close >= point.open,
|
|
198
|
-
point,
|
|
199
|
-
}));
|
|
200
|
-
});
|
|
201
|
-
|
|
202
|
-
// Body width = a fraction of the pixel spacing between candle centers, so
|
|
203
|
-
// bodies scale with the plot and never overlap. With one candle there is no
|
|
204
|
-
// spacing to measure, so fall back to a fixed width.
|
|
205
|
-
readonly #bodyWidth = computed(() => {
|
|
206
|
-
const projected = this.#projected();
|
|
207
|
-
const n = projected.length;
|
|
208
|
-
if (n < 2) return SINGLE_CANDLE_BODY_WIDTH;
|
|
209
|
-
const span = Math.abs(projected[n - 1].cx - projected[0].cx);
|
|
210
|
-
return (span / (n - 1)) * this.bodyWidthRatio();
|
|
211
|
-
});
|
|
212
|
-
|
|
213
|
-
protected readonly candles = computed(() => {
|
|
214
|
-
const projected = this.#projected();
|
|
215
|
-
const up = this.upColor();
|
|
216
|
-
const down = this.downColor();
|
|
217
|
-
// computeCandles works purely from the projected pixel fields; zip its output
|
|
218
|
-
// back with each source point so the body's tap can emit the original data.
|
|
219
|
-
return computeCandles(projected, this.#bodyWidth(), this.wickWidth()).map(
|
|
220
|
-
(shape, i) => {
|
|
221
|
-
const fill = shape.up ? up : down;
|
|
222
|
-
return {
|
|
223
|
-
point: projected[i].point,
|
|
224
|
-
wickStyle:
|
|
225
|
-
`position: absolute; left: ${px(shape.wick.left)}; top: ${px(shape.wick.top)}; ` +
|
|
226
|
-
`width: ${px(shape.wick.width)}; height: ${px(shape.wick.height)}; ` +
|
|
227
|
-
`background-color: ${fill};`,
|
|
228
|
-
bodyStyle:
|
|
229
|
-
`position: absolute; left: ${px(shape.body.left)}; top: ${px(shape.body.top)}; ` +
|
|
230
|
-
`width: ${px(shape.body.width)}; height: ${px(shape.body.height)}; ` +
|
|
231
|
-
`background-color: ${fill};`,
|
|
232
|
-
};
|
|
233
|
-
},
|
|
234
|
-
);
|
|
235
|
-
});
|
|
236
|
-
|
|
237
|
-
protected onCandleTap(point: CandlestickPoint): void {
|
|
238
|
-
this.candleTap.emit(point);
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
/**
|
|
243
|
-
* A ready-to-use candlestick (OHLC) chart for the common single-series case.
|
|
244
|
-
* Composes a `<ui-cartesian-chart>` with one `<ui-candlestick-series>` and
|
|
245
|
-
* auto-derives rounded axis domains + ticks from the data. For a moving-average
|
|
246
|
-
* line on top, or full control over the axes, use `<ui-cartesian-chart>` with a
|
|
247
|
-
* `<ui-candlestick-series>` (and optionally a `<ui-line-series>`) directly.
|
|
141
|
+
* ```html
|
|
142
|
+
* <ui-candlestick-chart [data]="prices" [zoomable]="true" zoomAxes="x" />
|
|
143
|
+
* ```
|
|
248
144
|
*/
|
|
249
145
|
@Component({
|
|
250
146
|
selector: 'ui-candlestick-chart',
|
|
251
|
-
|
|
252
|
-
imports: [LYNX_ELEMENTS, UiCartesianChart, UiCandlestickSeries],
|
|
147
|
+
imports: [UiChart],
|
|
253
148
|
encapsulation: ViewEncapsulation.None,
|
|
254
149
|
template: `
|
|
255
|
-
<ui-
|
|
256
|
-
[
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
[width]="width()"
|
|
261
|
-
[height]="height()"
|
|
262
|
-
[tickCount]="tickCount()"
|
|
263
|
-
[showXGrid]="showXGrid()"
|
|
264
|
-
[xAxisLabel]="xAxisLabel()"
|
|
265
|
-
[yAxisLabel]="yAxisLabel()"
|
|
266
|
-
[padding]="effectivePadding()"
|
|
267
|
-
[xTickFormat]="xTickFormat()"
|
|
268
|
-
[yTickFormat]="yTickFormat()"
|
|
269
|
-
[zoomable]="zoomable()"
|
|
270
|
-
[zoomAxes]="zoomAxes()"
|
|
271
|
-
[showZoomControls]="showZoomControls()"
|
|
272
|
-
[minZoom]="minZoom()"
|
|
273
|
-
[maxZoom]="maxZoom()"
|
|
274
|
-
[zoomStep]="zoomStep()"
|
|
275
|
-
[class]="userClass()"
|
|
276
|
-
>
|
|
277
|
-
<ui-candlestick-series
|
|
278
|
-
[data]="data()"
|
|
279
|
-
[upColor]="upColor()"
|
|
280
|
-
[downColor]="downColor()"
|
|
281
|
-
[wickWidth]="wickWidth()"
|
|
282
|
-
[bodyWidthRatio]="bodyWidthRatio()"
|
|
283
|
-
(candleTap)="candleTap.emit($event)"
|
|
284
|
-
/>
|
|
285
|
-
</ui-cartesian-chart>
|
|
150
|
+
<ui-chart
|
|
151
|
+
[definition]="definition()"
|
|
152
|
+
${PRESET_CHART_BINDINGS}
|
|
153
|
+
(pointSelect)="onSelect($event)"
|
|
154
|
+
/>
|
|
286
155
|
`,
|
|
287
156
|
})
|
|
288
|
-
export class UiCandlestickChart {
|
|
289
|
-
readonly data = input.required<
|
|
157
|
+
export class UiCandlestickChart extends UiCartesianPreset<CandlestickDatum> {
|
|
158
|
+
readonly data = input.required<readonly CandlestickDatum[]>();
|
|
290
159
|
readonly upColor = input('rgba(34, 197, 94, 1)');
|
|
291
160
|
readonly downColor = input('rgba(239, 68, 68, 1)');
|
|
292
|
-
readonly width = input(320);
|
|
293
|
-
readonly height = input(200);
|
|
294
|
-
readonly tickCount = input(5);
|
|
295
161
|
readonly wickWidth = input(1);
|
|
296
162
|
readonly bodyWidthRatio = input(0.6);
|
|
297
|
-
/** Draw vertical gridlines at each x tick. */
|
|
298
|
-
readonly showXGrid = input(false);
|
|
299
|
-
/** Title for the x-axis (centered below the tick labels). */
|
|
300
|
-
readonly xAxisLabel = input<string>('');
|
|
301
|
-
/** Title for the y-axis (rotated in the left gutter). */
|
|
302
|
-
readonly yAxisLabel = input<string>('');
|
|
303
|
-
/** Inner inset of the plot area — merged over the auto half-candle padding
|
|
304
|
-
* (below), so any side you set wins. See {@link ChartPadding}. */
|
|
305
|
-
readonly padding = input<ChartPadding>({});
|
|
306
|
-
/** Force the x-axis lower/upper bound instead of deriving it from the data.
|
|
307
|
-
* Omitted bounds fall back to the data extremes. */
|
|
308
|
-
readonly xMin = input<number | undefined>(undefined);
|
|
309
|
-
readonly xMax = input<number | undefined>(undefined);
|
|
310
|
-
/** Force the y-axis lower/upper bound (e.g. to compare two charts on one scale). */
|
|
311
|
-
readonly yMin = input<number | undefined>(undefined);
|
|
312
|
-
readonly yMax = input<number | undefined>(undefined);
|
|
313
|
-
readonly xTickFormat = input<(value: number) => string>(defaultTickFormat);
|
|
314
|
-
readonly yTickFormat = input<(value: number) => string>(defaultTickFormat);
|
|
315
|
-
readonly userClass = input<string>('', { alias: 'class' });
|
|
316
|
-
|
|
317
|
-
// Pan/zoom — forwarded to the underlying <ui-cartesian-chart>. Off by default
|
|
318
|
-
// so a candlestick chart stays static unless opted in.
|
|
319
|
-
readonly zoomable = input(false);
|
|
320
|
-
readonly zoomAxes = input<'x' | 'y' | 'xy'>('xy');
|
|
321
|
-
readonly showZoomControls = input(true);
|
|
322
|
-
readonly minZoom = input(1);
|
|
323
|
-
readonly maxZoom = input(8);
|
|
324
|
-
readonly zoomStep = input(1.4);
|
|
325
163
|
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
const
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
return niceScale(lo, hi, this.tickCount());
|
|
345
|
-
});
|
|
346
|
-
|
|
347
|
-
// A candle body is centered on its x, so the first and last bodies would spill
|
|
348
|
-
// over the plot edges. Insetting the scales by half a candle's spacing keeps
|
|
349
|
-
// them fully inside. User `padding` is spread last, so an explicit side wins.
|
|
350
|
-
protected readonly effectivePadding = computed<ChartPadding>(() => {
|
|
351
|
-
const n = this.data().length;
|
|
352
|
-
const half = n > 1 ? 0.5 / (n - 1) : 0;
|
|
353
|
-
return { left: half, right: half, ...this.padding() };
|
|
354
|
-
});
|
|
164
|
+
/** The tapped candle's datum. */
|
|
165
|
+
readonly candleTap = output<CandlestickDatum>();
|
|
166
|
+
|
|
167
|
+
protected readonly definition = computed(() =>
|
|
168
|
+
candlestickChartDefinition({
|
|
169
|
+
...this.commonOptions(),
|
|
170
|
+
data: this.data(),
|
|
171
|
+
upColor: this.upColor(),
|
|
172
|
+
downColor: this.downColor(),
|
|
173
|
+
wickWidth: this.wickWidth(),
|
|
174
|
+
bodyWidthRatio: this.bodyWidthRatio(),
|
|
175
|
+
}),
|
|
176
|
+
);
|
|
177
|
+
|
|
178
|
+
protected onSelect(point: ChartPoint | null): void {
|
|
179
|
+
const datum = this.datumOf(point);
|
|
180
|
+
if (datum) this.candleTap.emit(datum);
|
|
181
|
+
}
|
|
355
182
|
}
|