@blotch/dolan 22.2.1 → 22.2.3
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 +32 -10
- package/dist/commands/add.js.map +1 -1
- package/dist/commands/add.test.js +129 -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 +41 -27
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/doctor.test.js +93 -9
- 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 +149 -40
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/init.test.js +285 -26
- 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/update.d.ts +6 -0
- package/dist/commands/update.js +59 -24
- package/dist/commands/update.js.map +1 -1
- package/dist/commands/update.test.js +273 -31
- package/dist/commands/update.test.js.map +1 -1
- package/dist/index.js +10 -3
- package/dist/index.js.map +1 -1
- package/dist/registry.d.ts +8 -0
- package/dist/registry.js +32 -24
- package/dist/registry.js.map +1 -1
- package/dist/registry.test.js +76 -37
- package/dist/registry.test.js.map +1 -1
- package/dist/testing/set-input-signal.d.ts +13 -0
- package/dist/testing/set-input-signal.js +20 -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/action-sheet/action-sheet.test.ts +48 -1
- package/dist/ui/components/action-sheet/action-sheet.ts +37 -9
- package/dist/ui/components/alert-dialog/alert-dialog.test.ts +49 -2
- package/dist/ui/components/alert-dialog/alert-dialog.ts +43 -12
- package/dist/ui/components/area-chart/area-chart-render.test.ts +52 -110
- package/dist/ui/components/area-chart/area-chart.test.ts +59 -146
- 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/bar-chart/bar-chart-render.test.ts +47 -163
- package/dist/ui/components/bar-chart/bar-chart.test.ts +82 -58
- 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 +191 -9
- package/dist/ui/components/bottom-sheet/bottom-sheet.ts +136 -20
- package/dist/ui/components/button/button-render.test.ts +19 -0
- package/dist/ui/components/button/button.test.ts +2 -15
- package/dist/ui/components/button/button.ts +18 -2
- package/dist/ui/components/button-group/button-group.test.ts +2 -21
- package/dist/ui/components/candlestick-chart/candlestick-chart-render.test.ts +48 -163
- package/dist/ui/components/candlestick-chart/candlestick-chart.test.ts +107 -85
- 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/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 +139 -0
- package/dist/ui/components/chart/chart-presets.ts +271 -0
- package/dist/ui/components/chart/chart-render.test.ts +730 -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 +198 -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.ts +921 -0
- package/dist/ui/components/chart/index.ts +24 -0
- package/dist/ui/components/dialog/dialog.test.ts +49 -2
- package/dist/ui/components/dialog/dialog.ts +43 -12
- package/dist/ui/components/icon/icon-theme.test.ts +133 -0
- package/dist/ui/components/icon/icon-theme.ts +204 -0
- package/dist/ui/components/icon/icon.test.ts +51 -2
- package/dist/ui/components/icon/icon.ts +38 -21
- package/dist/ui/components/line-chart/index.ts +2 -3
- package/dist/ui/components/line-chart/line-chart-render.test.ts +69 -107
- package/dist/ui/components/line-chart/line-chart.test.ts +63 -67
- package/dist/ui/components/line-chart/line-chart.ts +83 -241
- package/dist/ui/components/list/list.test.ts +69 -2
- package/dist/ui/components/list/list.ts +21 -15
- package/dist/ui/components/nav-drawer/nav-drawer.test.ts +80 -1
- package/dist/ui/components/nav-drawer/nav-drawer.ts +66 -11
- package/dist/ui/components/pagination/pagination.test.ts +62 -0
- package/dist/ui/components/pagination/pagination.ts +55 -8
- package/dist/ui/components/scatter-chart/index.ts +2 -3
- package/dist/ui/components/scatter-chart/scatter-chart-render.test.ts +51 -144
- package/dist/ui/components/scatter-chart/scatter-chart.test.ts +57 -47
- package/dist/ui/components/scatter-chart/scatter-chart.ts +104 -250
- package/dist/ui/components/scroll-area/scroll-area.test.ts +37 -2
- package/dist/ui/components/scroll-area/scroll-area.ts +8 -8
- package/dist/ui/components/spinner/spinner.test.ts +27 -0
- package/dist/ui/components/spinner/spinner.ts +33 -23
- package/dist/ui/components/toast/toast.test.ts +37 -0
- package/dist/ui/components/toast/toast.ts +20 -4
- package/dist/ui/theme/chart-colors.test.ts +30 -0
- package/dist/ui/theme/dark.css +8 -0
- package/dist/ui/theme/default.css +8 -0
- package/dist/ui/theme/icon-colors.test.ts +30 -0
- package/dist/ui/theme/tailwind-plugin.ts +10 -1
- package/dist/utils/installed.d.ts +17 -0
- package/dist/utils/installed.js +27 -0
- package/dist/utils/installed.js.map +1 -0
- package/dist/utils/installed.test.d.ts +1 -0
- package/dist/utils/installed.test.js +41 -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 +23 -3
- package/dist/utils/resolve-paths.js.map +1 -1
- package/dist/utils/resolve-paths.test.js +34 -2
- package/dist/utils/resolve-paths.test.js.map +1 -1
- package/package.json +5 -1
- package/dist/ui/components/cartesian-chart/cartesian-chart-render.test.ts +0 -188
- package/dist/ui/components/cartesian-chart/cartesian-chart.test.ts +0 -549
- package/dist/ui/components/cartesian-chart/cartesian-chart.ts +0 -1140
- package/dist/ui/components/cartesian-chart/index.ts +0 -18
|
@@ -2,348 +2,133 @@ 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';
|
|
10
|
-
|
|
11
8
|
import {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
} from '
|
|
18
|
-
import {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
*/
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
const last = points[points.length - 1];
|
|
44
|
-
if (x < first.x || x > last.x) return null;
|
|
45
|
-
for (let i = 0; i < points.length - 1; i++) {
|
|
46
|
-
const p1 = points[i];
|
|
47
|
-
const p2 = points[i + 1];
|
|
48
|
-
if (x >= p1.x && x <= p2.x) {
|
|
49
|
-
const span = p2.x - p1.x;
|
|
50
|
-
if (span === 0) return p1.y; // duplicate x — avoid divide-by-zero
|
|
51
|
-
return p1.y + ((x - p1.x) / span) * (p2.y - p1.y);
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
/* v8 ignore next 1 */
|
|
55
|
-
return last.y; // defensive: x === last.x but the loop missed it
|
|
56
|
-
};
|
|
9
|
+
areaY,
|
|
10
|
+
d3Curve,
|
|
11
|
+
defineChart,
|
|
12
|
+
dot,
|
|
13
|
+
lineY,
|
|
14
|
+
} from '@tanstack/charts/universal';
|
|
15
|
+
import type { ChartMark, ChartPoint } from '@tanstack/charts/types';
|
|
16
|
+
import { curveMonotoneX } from 'd3-shape';
|
|
17
|
+
|
|
18
|
+
import { UiChart, tooltip } from '../chart/chart';
|
|
19
|
+
import {
|
|
20
|
+
type CartesianPresetOptions,
|
|
21
|
+
PRESET_CHART_BINDINGS,
|
|
22
|
+
UiCartesianPreset,
|
|
23
|
+
type XYDatum,
|
|
24
|
+
presetAxes,
|
|
25
|
+
} from '../chart/chart-presets';
|
|
26
|
+
|
|
27
|
+
export type AreaChartOptions = {
|
|
28
|
+
readonly data: readonly XYDatum[];
|
|
29
|
+
/** Any CSS color; theme tokens like `var(--primary)` follow dark mode. */
|
|
30
|
+
readonly color?: string;
|
|
31
|
+
readonly strokeWidth?: number;
|
|
32
|
+
readonly dotRadius?: number;
|
|
33
|
+
readonly showDots?: boolean;
|
|
34
|
+
/** Opacity of the fill under the line, `0`–`1`. */
|
|
35
|
+
readonly fillOpacity?: number;
|
|
36
|
+
/** The value the area fills to; it's always kept in view. */
|
|
37
|
+
readonly baseline?: number;
|
|
38
|
+
readonly smooth?: boolean;
|
|
39
|
+
} & CartesianPresetOptions;
|
|
57
40
|
|
|
58
41
|
/**
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
*
|
|
62
|
-
* Lynx has no filled-polygon primitive: `clip-path` supports no `polygon()` and
|
|
63
|
-
* its inline `<svg>` is unreliable, so an area is approximated as a run of
|
|
64
|
-
* adjacent rectangles, one per `stripWidth` band, each reaching from the
|
|
65
|
-
* interpolated line height down to `baseY`. The crisp line is drawn on top (see
|
|
66
|
-
* `UiAreaSeries`), which hides the columns' stepped upper edge. Sampling at each
|
|
67
|
-
* strip's center keeps that step within about `stripWidth / 2 × slope` px.
|
|
68
|
-
*
|
|
69
|
-
* Guards: fewer than two points, or a non-positive `plotWidth`/`stripWidth`,
|
|
70
|
-
* yield no columns; near-zero-height columns (line meeting the baseline) are
|
|
71
|
-
* skipped so we never emit a degenerate 0-height view.
|
|
42
|
+
* The definition `<ui-area-chart>` renders: a filled area down to the
|
|
43
|
+
* baseline, the line on top, and optional dots. Values below the baseline fill
|
|
44
|
+
* upward to it.
|
|
72
45
|
*/
|
|
73
|
-
export const
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
46
|
+
export const areaChartDefinition = (options: AreaChartOptions) => {
|
|
47
|
+
const {
|
|
48
|
+
data,
|
|
49
|
+
color = 'var(--primary)',
|
|
50
|
+
strokeWidth = 2,
|
|
51
|
+
dotRadius = 3,
|
|
52
|
+
showDots = true,
|
|
53
|
+
fillOpacity = 0.2,
|
|
54
|
+
baseline = 0,
|
|
55
|
+
smooth = false,
|
|
56
|
+
} = options;
|
|
57
|
+
const curve = smooth ? d3Curve(curveMonotoneX) : undefined;
|
|
58
|
+
const marks: ChartMark<XYDatum, any, any, any, any, any, any>[] = [
|
|
59
|
+
areaY(data, {
|
|
60
|
+
x: 'x',
|
|
61
|
+
y: 'y',
|
|
62
|
+
y1: baseline,
|
|
63
|
+
fill: color,
|
|
64
|
+
fillOpacity,
|
|
65
|
+
curve,
|
|
66
|
+
}),
|
|
67
|
+
lineY(data, { x: 'x', y: 'y', stroke: color, strokeWidth, curve }),
|
|
68
|
+
];
|
|
69
|
+
if (showDots && dotRadius > 0) {
|
|
70
|
+
marks.push(dot(data, { x: 'x', y: 'y', r: dotRadius, fill: color }));
|
|
96
71
|
}
|
|
97
|
-
return
|
|
72
|
+
return defineChart({
|
|
73
|
+
marks,
|
|
74
|
+
scales: presetAxes(options, {
|
|
75
|
+
x: data.map((d) => d.x),
|
|
76
|
+
y: data.map((d) => d.y),
|
|
77
|
+
yInclude: [baseline],
|
|
78
|
+
}),
|
|
79
|
+
tooltip: options.tooltip === false ? false : tooltip,
|
|
80
|
+
});
|
|
98
81
|
};
|
|
99
82
|
|
|
100
|
-
// ---------------------------------------------------------------------------
|
|
101
|
-
// Rendering helpers (mirror the equivalents in line-chart.ts — kept local so
|
|
102
|
-
// this module stays self-contained when scaffolded by `dolan add`)
|
|
103
|
-
// ---------------------------------------------------------------------------
|
|
104
|
-
|
|
105
|
-
/**
|
|
106
|
-
* Formats a pixel value for an inline style, avoiding scientific notation.
|
|
107
|
-
*/
|
|
108
|
-
const px = (n: number): string => `${n.toFixed(2)}px`;
|
|
109
|
-
|
|
110
|
-
/**
|
|
111
|
-
* Default axis label formatter — plain JS only (no `Intl`, absent in Lynx).
|
|
112
|
-
*/
|
|
113
|
-
const defaultTickFormat = (value: number): string =>
|
|
114
|
-
String(Math.round(value * 100) / 100);
|
|
115
|
-
|
|
116
|
-
const clamp = (value: number, lo: number, hi: number): number =>
|
|
117
|
-
Math.max(lo, Math.min(hi, value));
|
|
118
|
-
|
|
119
83
|
/**
|
|
120
|
-
*
|
|
121
|
-
*
|
|
122
|
-
* ~250px plot near ~83 views, and the crisp line drawn on top hides the residual
|
|
123
|
-
* stepping. Lynx offers no polygon `clip-path` or reliable `<svg>` fill, so the
|
|
124
|
-
* area is rasterized this way rather than drawn as a single shape.
|
|
125
|
-
*/
|
|
126
|
-
const STRIP_WIDTH = 3;
|
|
127
|
-
|
|
128
|
-
/**
|
|
129
|
-
* A single area series drawn inside a `<ui-cartesian-chart>`: a translucent fill
|
|
130
|
-
* beneath a line. It reuses `<ui-line-series>` for the crisp line + dots and adds
|
|
131
|
-
* only the fill — an area chart is, structurally, a line chart with the region
|
|
132
|
-
* down to a baseline filled in.
|
|
133
|
-
*
|
|
134
|
-
* ## Why the host is `position: absolute; width/height: 100%`
|
|
135
|
-
* Identical to `UiLineSeries`: Lynx resolves an absolute child against its
|
|
136
|
-
* **direct parent** (there is no `position: static`), so the host itself must be
|
|
137
|
-
* the plot-sized containing block. Both the fill layer and the nested
|
|
138
|
-
* `<ui-line-series>` are direct children of this host and fill it exactly.
|
|
84
|
+
* A single-series area chart. For stacked or several areas, build a
|
|
85
|
+
* definition with `areaY` and render it with `<ui-chart>`.
|
|
139
86
|
*
|
|
140
|
-
*
|
|
141
|
-
*
|
|
142
|
-
*
|
|
143
|
-
* through this host to the `UiCartesianChart` that `<ui-area-series>` is projected
|
|
144
|
-
* into — the same `inject(UiCartesianChart)` that `UiLineSeries` already relies
|
|
145
|
-
* on, one level deeper.
|
|
87
|
+
* ```html
|
|
88
|
+
* <ui-area-chart [data]="visitors" [smooth]="true" [fillOpacity]="0.3" />
|
|
89
|
+
* ```
|
|
146
90
|
*/
|
|
147
91
|
@Component({
|
|
148
|
-
selector: 'ui-area-
|
|
149
|
-
|
|
150
|
-
imports: [LYNX_ELEMENTS, UiLineSeries],
|
|
92
|
+
selector: 'ui-area-chart',
|
|
93
|
+
imports: [UiChart],
|
|
151
94
|
encapsulation: ViewEncapsulation.None,
|
|
152
|
-
host: {
|
|
153
|
-
style:
|
|
154
|
-
'position: absolute; left: 0px; top: 0px; width: 100%; height: 100%;',
|
|
155
|
-
'[class]': 'userClass()',
|
|
156
|
-
},
|
|
157
95
|
template: `
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
<view [style]="fillLayerStyle()">
|
|
163
|
-
@for (column of columns(); track $index) {
|
|
164
|
-
<view [style]="column.style"></view>
|
|
165
|
-
}
|
|
166
|
-
</view>
|
|
167
|
-
<ui-line-series
|
|
168
|
-
[data]="data()"
|
|
169
|
-
[color]="color()"
|
|
170
|
-
[strokeWidth]="strokeWidth()"
|
|
171
|
-
[dotRadius]="dotRadius()"
|
|
172
|
-
[showDots]="showDots()"
|
|
173
|
-
[smooth]="smooth()"
|
|
174
|
-
(pointTap)="pointTap.emit($event)"
|
|
96
|
+
<ui-chart
|
|
97
|
+
[definition]="definition()"
|
|
98
|
+
${PRESET_CHART_BINDINGS}
|
|
99
|
+
(pointSelect)="onSelect($event)"
|
|
175
100
|
/>
|
|
176
101
|
`,
|
|
177
102
|
})
|
|
178
|
-
export class
|
|
179
|
-
readonly
|
|
180
|
-
|
|
181
|
-
readonly data = input.required<ChartPoint[]>();
|
|
103
|
+
export class UiAreaChart extends UiCartesianPreset<XYDatum> {
|
|
104
|
+
readonly data = input.required<readonly XYDatum[]>();
|
|
182
105
|
readonly color = input('var(--primary)');
|
|
183
106
|
readonly strokeWidth = input(2);
|
|
184
107
|
readonly dotRadius = input(3);
|
|
185
108
|
readonly showDots = input(true);
|
|
186
109
|
readonly fillOpacity = input(0.2);
|
|
187
|
-
/** Data-space y the fill descends to (the flat edge of the area). */
|
|
188
110
|
readonly baseline = input(0);
|
|
189
|
-
/** Fill under a smooth (monotone-cubic) curve instead of straight segments.
|
|
190
|
-
* Forwarded to the nested line so the crisp edge matches the fill's top. */
|
|
191
111
|
readonly smooth = input(false);
|
|
192
|
-
readonly userClass = input<string>('', { alias: 'class' });
|
|
193
|
-
|
|
194
|
-
readonly pointTap = output<ChartPoint>();
|
|
195
|
-
|
|
196
|
-
// Project each data point into plot-local pixel space via the shared scales.
|
|
197
|
-
readonly #pixelPoints = computed(() => {
|
|
198
|
-
const scaleX = this.#chart.xScale();
|
|
199
|
-
const scaleY = this.#chart.yScale();
|
|
200
|
-
return this.data().map((point) => ({
|
|
201
|
-
x: scaleX(point.x),
|
|
202
|
-
y: scaleY(point.y),
|
|
203
|
-
}));
|
|
204
|
-
});
|
|
205
112
|
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
113
|
+
/** The tapped point's datum. */
|
|
114
|
+
readonly pointTap = output<XYDatum>();
|
|
115
|
+
|
|
116
|
+
protected readonly definition = computed(() =>
|
|
117
|
+
areaChartDefinition({
|
|
118
|
+
...this.commonOptions(),
|
|
119
|
+
data: this.data(),
|
|
120
|
+
color: this.color(),
|
|
121
|
+
strokeWidth: this.strokeWidth(),
|
|
122
|
+
dotRadius: this.dotRadius(),
|
|
123
|
+
showDots: this.showDots(),
|
|
124
|
+
fillOpacity: this.fillOpacity(),
|
|
125
|
+
baseline: this.baseline(),
|
|
126
|
+
smooth: this.smooth(),
|
|
127
|
+
}),
|
|
209
128
|
);
|
|
210
129
|
|
|
211
|
-
protected
|
|
212
|
-
const
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
const baseY = clamp(
|
|
216
|
-
this.#chart.yScale()(this.baseline()),
|
|
217
|
-
0,
|
|
218
|
-
this.#chart.plotHeight(),
|
|
219
|
-
);
|
|
220
|
-
// In smooth mode the fill traces the same monotone-cubic curve as the line:
|
|
221
|
-
// sampling the polyline densely means `computeAreaColumns` (which linearly
|
|
222
|
-
// interpolates the height at each strip) closely follows the curve's top edge.
|
|
223
|
-
const pixels = this.#pixelPoints();
|
|
224
|
-
const line = this.smooth() ? sampleSmoothLine(pixels) : pixels;
|
|
225
|
-
return computeAreaColumns(
|
|
226
|
-
line,
|
|
227
|
-
baseY,
|
|
228
|
-
this.#chart.plotWidth(),
|
|
229
|
-
STRIP_WIDTH,
|
|
230
|
-
).map((c) => ({
|
|
231
|
-
style:
|
|
232
|
-
`position: absolute; left: ${px(c.x)}; top: ${px(c.top)}; ` +
|
|
233
|
-
`width: ${px(c.width)}; height: ${px(c.height)}; background-color: ${fill};`,
|
|
234
|
-
}));
|
|
235
|
-
});
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
/**
|
|
239
|
-
* A ready-to-use area chart for the common single-series case. Composes a
|
|
240
|
-
* `<ui-cartesian-chart>` with one `<ui-area-series>` and auto-derives rounded
|
|
241
|
-
* axis domains + ticks from the data. For multiple series or full control over
|
|
242
|
-
* the axes, use `<ui-cartesian-chart>` with `<ui-area-series>` children directly.
|
|
243
|
-
*/
|
|
244
|
-
@Component({
|
|
245
|
-
selector: 'ui-area-chart',
|
|
246
|
-
standalone: true,
|
|
247
|
-
imports: [LYNX_ELEMENTS, UiCartesianChart, UiAreaSeries],
|
|
248
|
-
encapsulation: ViewEncapsulation.None,
|
|
249
|
-
template: `
|
|
250
|
-
<ui-cartesian-chart
|
|
251
|
-
[xDomain]="xAxis().domain"
|
|
252
|
-
[yDomain]="yAxis().domain"
|
|
253
|
-
[xTicks]="xAxis().ticks"
|
|
254
|
-
[yTicks]="yAxis().ticks"
|
|
255
|
-
[width]="width()"
|
|
256
|
-
[height]="height()"
|
|
257
|
-
[tickCount]="tickCount()"
|
|
258
|
-
[showXGrid]="showXGrid()"
|
|
259
|
-
[xAxisLabel]="xAxisLabel()"
|
|
260
|
-
[yAxisLabel]="yAxisLabel()"
|
|
261
|
-
[padding]="padding()"
|
|
262
|
-
[xTickFormat]="xTickFormat()"
|
|
263
|
-
[yTickFormat]="yTickFormat()"
|
|
264
|
-
[zoomable]="zoomable()"
|
|
265
|
-
[zoomAxes]="zoomAxes()"
|
|
266
|
-
[showZoomControls]="showZoomControls()"
|
|
267
|
-
[minZoom]="minZoom()"
|
|
268
|
-
[maxZoom]="maxZoom()"
|
|
269
|
-
[zoomStep]="zoomStep()"
|
|
270
|
-
[class]="userClass()"
|
|
271
|
-
>
|
|
272
|
-
<ui-area-series
|
|
273
|
-
[data]="data()"
|
|
274
|
-
[color]="color()"
|
|
275
|
-
[strokeWidth]="strokeWidth()"
|
|
276
|
-
[dotRadius]="dotRadius()"
|
|
277
|
-
[showDots]="showDots()"
|
|
278
|
-
[fillOpacity]="fillOpacity()"
|
|
279
|
-
[baseline]="baseline()"
|
|
280
|
-
[smooth]="smooth()"
|
|
281
|
-
(pointTap)="pointTap.emit($event)"
|
|
282
|
-
/>
|
|
283
|
-
</ui-cartesian-chart>
|
|
284
|
-
`,
|
|
285
|
-
})
|
|
286
|
-
export class UiAreaChart {
|
|
287
|
-
readonly data = input.required<ChartPoint[]>();
|
|
288
|
-
readonly color = input('var(--primary)');
|
|
289
|
-
readonly width = input(320);
|
|
290
|
-
readonly height = input(200);
|
|
291
|
-
readonly tickCount = input(5);
|
|
292
|
-
readonly strokeWidth = input(2);
|
|
293
|
-
readonly dotRadius = input(3);
|
|
294
|
-
readonly showDots = input(true);
|
|
295
|
-
readonly fillOpacity = input(0.2);
|
|
296
|
-
readonly baseline = input(0);
|
|
297
|
-
/** Fill under a smooth (monotone-cubic) curve instead of straight segments. */
|
|
298
|
-
readonly smooth = input(false);
|
|
299
|
-
/** Draw vertical gridlines at each x tick. */
|
|
300
|
-
readonly showXGrid = input(false);
|
|
301
|
-
/** Title for the x-axis (centered below the tick labels). */
|
|
302
|
-
readonly xAxisLabel = input<string>('');
|
|
303
|
-
/** Title for the y-axis (rotated in the left gutter). */
|
|
304
|
-
readonly yAxisLabel = input<string>('');
|
|
305
|
-
/** Inner inset of the plot area, each side a fraction (0–1) — see {@link ChartPadding}. */
|
|
306
|
-
readonly padding = input<ChartPadding>({});
|
|
307
|
-
/** Force the x-axis lower/upper bound instead of deriving it from the data.
|
|
308
|
-
* Omitted bounds fall back to the data extremes. */
|
|
309
|
-
readonly xMin = input<number | undefined>(undefined);
|
|
310
|
-
readonly xMax = input<number | undefined>(undefined);
|
|
311
|
-
/** Force the y-axis lower/upper bound. The `baseline` is still folded in, so
|
|
312
|
-
* `yMin` only lowers the floor further — it can't clip the fill's base off. */
|
|
313
|
-
readonly yMin = input<number | undefined>(undefined);
|
|
314
|
-
readonly yMax = input<number | undefined>(undefined);
|
|
315
|
-
readonly xTickFormat = input<(value: number) => string>(defaultTickFormat);
|
|
316
|
-
readonly yTickFormat = input<(value: number) => string>(defaultTickFormat);
|
|
317
|
-
readonly userClass = input<string>('', { alias: 'class' });
|
|
318
|
-
|
|
319
|
-
// Pan/zoom — forwarded to the underlying <ui-cartesian-chart>. Off by default.
|
|
320
|
-
readonly zoomable = input(false);
|
|
321
|
-
readonly zoomAxes = input<'x' | 'y' | 'xy'>('xy');
|
|
322
|
-
readonly showZoomControls = input(true);
|
|
323
|
-
readonly minZoom = input(1);
|
|
324
|
-
readonly maxZoom = input(8);
|
|
325
|
-
readonly zoomStep = input(1.4);
|
|
326
|
-
|
|
327
|
-
readonly pointTap = output<ChartPoint>();
|
|
328
|
-
|
|
329
|
-
// Auto-compute rounded axes from the data extremes, letting an explicit
|
|
330
|
-
// min/max override either end. `Math.min/max(...[])` yield ±Infinity for empty
|
|
331
|
-
// data, which `niceScale` handles by padding.
|
|
332
|
-
protected readonly xAxis = computed(() => {
|
|
333
|
-
const xs = this.data().map((p) => p.x);
|
|
334
|
-
const lo = this.xMin() ?? Math.min(...xs);
|
|
335
|
-
const hi = this.xMax() ?? Math.max(...xs);
|
|
336
|
-
return niceScale(lo, hi, this.tickCount());
|
|
337
|
-
});
|
|
338
|
-
// Fold the baseline into the y-extent so the axis always spans the whole filled
|
|
339
|
-
// region: a positive-only series still shows the baseline at the bottom, and
|
|
340
|
-
// negative values pull the domain below it. An explicit yMin/yMax overrides the
|
|
341
|
-
// corresponding data-derived end (the baseline still participates in the other).
|
|
342
|
-
protected readonly yAxis = computed(() => {
|
|
343
|
-
const ys = this.data().map((p) => p.y);
|
|
344
|
-
const base = this.baseline();
|
|
345
|
-
const lo = this.yMin() ?? Math.min(base, ...ys);
|
|
346
|
-
const hi = this.yMax() ?? Math.max(base, ...ys);
|
|
347
|
-
return niceScale(lo, hi, this.tickCount());
|
|
348
|
-
});
|
|
130
|
+
protected onSelect(point: ChartPoint | null): void {
|
|
131
|
+
const datum = this.datumOf(point);
|
|
132
|
+
if (datum) this.pointTap.emit(datum);
|
|
133
|
+
}
|
|
349
134
|
}
|