@nexterajs/ui 0.0.2 → 0.1.0
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/README.md +279 -34
- package/fesm2022/nexterajs-ui-charts.mjs +1096 -0
- package/fesm2022/nexterajs-ui-charts.mjs.map +1 -0
- package/fesm2022/nexterajs-ui-core.mjs +652 -12
- package/fesm2022/nexterajs-ui-core.mjs.map +1 -1
- package/fesm2022/nexterajs-ui-display.mjs +1142 -0
- package/fesm2022/nexterajs-ui-display.mjs.map +1 -0
- package/fesm2022/nexterajs-ui-dropdowns.mjs +321 -33
- package/fesm2022/nexterajs-ui-dropdowns.mjs.map +1 -1
- package/fesm2022/nexterajs-ui-file-uploader.mjs +572 -10
- package/fesm2022/nexterajs-ui-file-uploader.mjs.map +1 -1
- package/fesm2022/nexterajs-ui-forms.mjs +2027 -136
- package/fesm2022/nexterajs-ui-forms.mjs.map +1 -1
- package/fesm2022/nexterajs-ui-grid.mjs +1222 -188
- package/fesm2022/nexterajs-ui-grid.mjs.map +1 -1
- package/fesm2022/nexterajs-ui-intl.mjs +103 -11
- package/fesm2022/nexterajs-ui-intl.mjs.map +1 -1
- package/fesm2022/nexterajs-ui-navigation.mjs +1054 -0
- package/fesm2022/nexterajs-ui-navigation.mjs.map +1 -0
- package/fesm2022/nexterajs-ui-notify.mjs +46 -44
- package/fesm2022/nexterajs-ui-notify.mjs.map +1 -1
- package/fesm2022/nexterajs-ui-overlay.mjs +1671 -0
- package/fesm2022/nexterajs-ui-overlay.mjs.map +1 -0
- package/fesm2022/nexterajs-ui-pivot.mjs +51 -26
- package/fesm2022/nexterajs-ui-pivot.mjs.map +1 -1
- package/fesm2022/nexterajs-ui-shell.mjs +542 -0
- package/fesm2022/nexterajs-ui-shell.mjs.map +1 -0
- package/fesm2022/nexterajs-ui-tree-view.mjs +5 -2
- package/fesm2022/nexterajs-ui-tree-view.mjs.map +1 -1
- package/fesm2022/nexterajs-ui-validation.mjs +42 -11
- package/fesm2022/nexterajs-ui-validation.mjs.map +1 -1
- package/fesm2022/nexterajs-ui.mjs +5 -0
- package/fesm2022/nexterajs-ui.mjs.map +1 -1
- package/package.json +33 -2
- package/styles/_dropdown.scss +5 -5
- package/styles/_editor.scss +52 -17
- package/styles/_mixins.scss +1 -1
- package/styles/_scrollbar.scss +5 -5
- package/styles/_status.scss +21 -0
- package/styles/_toast.scss +14 -14
- package/styles/_tokens.scss +420 -3
- package/styles/_widget.scss +14 -3
- package/styles/index.scss +6 -4
- package/types/nexterajs-ui-charts.d.ts +446 -0
- package/types/nexterajs-ui-core.d.ts +387 -22
- package/types/nexterajs-ui-display.d.ts +720 -0
- package/types/nexterajs-ui-dropdowns.d.ts +191 -48
- package/types/nexterajs-ui-file-uploader.d.ts +236 -7
- package/types/nexterajs-ui-forms.d.ts +675 -30
- package/types/nexterajs-ui-grid.d.ts +448 -93
- package/types/nexterajs-ui-intl.d.ts +39 -4
- package/types/nexterajs-ui-navigation.d.ts +531 -0
- package/types/nexterajs-ui-notify.d.ts +4 -2
- package/types/nexterajs-ui-overlay.d.ts +703 -0
- package/types/nexterajs-ui-pivot.d.ts +56 -48
- package/types/nexterajs-ui-shell.d.ts +294 -0
- package/types/nexterajs-ui-tree-view.d.ts +6 -3
- package/types/nexterajs-ui-validation.d.ts +20 -11
- package/types/nexterajs-ui.d.ts +5 -0
|
@@ -0,0 +1,1096 @@
|
|
|
1
|
+
import * as i0 from '@angular/core';
|
|
2
|
+
import { inject, DestroyRef, afterNextRender, input, ViewEncapsulation, ChangeDetectionStrategy, Component, booleanAttribute, output, ElementRef, numberAttribute, model, signal, computed } from '@angular/core';
|
|
3
|
+
import { NxeMessageService } from '@nexterajs/ui/core';
|
|
4
|
+
import { NXE_NUMBER_SEPARATORS, formatNumber, parseNumberFormat, NXE_DEFAULT_DISPLAY_FORMAT, formatDisplay, parseDateValue } from '@nexterajs/ui/intl';
|
|
5
|
+
import { NxeSkeletonComponent, NxeEmptyStateComponent } from '@nexterajs/ui/display';
|
|
6
|
+
import { DateTime } from 'luxon';
|
|
7
|
+
|
|
8
|
+
// Scale and tick maths shared by the charts. Pure functions: no DOM, no Angular.
|
|
9
|
+
/** Rounds `value` to a 1, 2, 5 or 10 times a power of ten (Heckbert). */
|
|
10
|
+
function niceNumber(value, round) {
|
|
11
|
+
if (!(value > 0) || !isFinite(value)) {
|
|
12
|
+
return 1;
|
|
13
|
+
}
|
|
14
|
+
const exponent = Math.floor(Math.log10(value));
|
|
15
|
+
const fraction = value / Math.pow(10, exponent);
|
|
16
|
+
let nice;
|
|
17
|
+
if (round) {
|
|
18
|
+
nice = fraction < 1.5 ? 1 : fraction < 3 ? 2 : fraction < 7 ? 5 : 10;
|
|
19
|
+
}
|
|
20
|
+
else {
|
|
21
|
+
nice = fraction <= 1 ? 1 : fraction <= 2 ? 2 : fraction <= 5 ? 5 : 10;
|
|
22
|
+
}
|
|
23
|
+
return nice * Math.pow(10, exponent);
|
|
24
|
+
}
|
|
25
|
+
/** Decimal places needed to write multiples of `step` exactly (0.25 -> 2, 5 -> 0). */
|
|
26
|
+
function decimalsOf(step) {
|
|
27
|
+
if (!isFinite(step) || step <= 0) {
|
|
28
|
+
return 0;
|
|
29
|
+
}
|
|
30
|
+
let decimals = 0;
|
|
31
|
+
let scaled = step;
|
|
32
|
+
while (decimals < 10 && Math.abs(Math.round(scaled) - scaled) > 1e-9 * Math.max(1, Math.abs(scaled))) {
|
|
33
|
+
scaled *= 10;
|
|
34
|
+
decimals++;
|
|
35
|
+
}
|
|
36
|
+
return decimals;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Ticks for `[min, max]`: about `targetTicks` of them, spaced 1, 2 or 5 times a power of ten, the
|
|
40
|
+
* first at or below `min` and the last at or above `max`. A flat domain (`min === max`) opens up
|
|
41
|
+
* around the value so the line has somewhere to sit.
|
|
42
|
+
*/
|
|
43
|
+
function niceScale(min, max, targetTicks = 5) {
|
|
44
|
+
let lo = Math.min(min, max);
|
|
45
|
+
let hi = Math.max(min, max);
|
|
46
|
+
if (!isFinite(lo) || !isFinite(hi)) {
|
|
47
|
+
lo = 0;
|
|
48
|
+
hi = 1;
|
|
49
|
+
}
|
|
50
|
+
if (lo === hi) {
|
|
51
|
+
if (lo === 0) {
|
|
52
|
+
hi = 1;
|
|
53
|
+
}
|
|
54
|
+
else {
|
|
55
|
+
const pad = Math.abs(lo) * 0.1;
|
|
56
|
+
lo -= pad;
|
|
57
|
+
hi += pad;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
const count = Math.max(2, Math.floor(targetTicks));
|
|
61
|
+
const step = niceNumber(niceNumber(hi - lo, false) / (count - 1), true);
|
|
62
|
+
const niceMin = Math.floor(lo / step + 1e-9) * step;
|
|
63
|
+
const niceMax = Math.ceil(hi / step - 1e-9) * step;
|
|
64
|
+
const decimals = decimalsOf(step);
|
|
65
|
+
const ticks = [];
|
|
66
|
+
const n = Math.round((niceMax - niceMin) / step);
|
|
67
|
+
for (let i = 0; i <= n; i++) {
|
|
68
|
+
ticks.push(Number((niceMin + i * step).toFixed(decimals)));
|
|
69
|
+
}
|
|
70
|
+
return { min: ticks[0], max: ticks[ticks.length - 1], step, ticks };
|
|
71
|
+
}
|
|
72
|
+
/** Maps `[d0, d1]` onto `[r0, r1]`. A degenerate domain maps to the middle of the range. */
|
|
73
|
+
function linearScale(d0, d1, r0, r1) {
|
|
74
|
+
if (d1 === d0) {
|
|
75
|
+
return () => (r0 + r1) / 2;
|
|
76
|
+
}
|
|
77
|
+
const k = (r1 - r0) / (d1 - d0);
|
|
78
|
+
return (value) => r0 + (value - d0) * k;
|
|
79
|
+
}
|
|
80
|
+
/** A `#,##0.00` style pattern that shows `decimals` places, for tick labels that share a step. */
|
|
81
|
+
function patternForDecimals(decimals) {
|
|
82
|
+
return decimals > 0 ? `#,##0.${'0'.repeat(decimals)}` : '#,##0';
|
|
83
|
+
}
|
|
84
|
+
/** Indexes (0-based) of at most `maxLabels` evenly spaced labels out of `count`, always including the first. */
|
|
85
|
+
function labelIndexes(count, maxLabels) {
|
|
86
|
+
if (count <= 0) {
|
|
87
|
+
return [];
|
|
88
|
+
}
|
|
89
|
+
const step = Math.max(1, Math.ceil(count / Math.max(1, maxLabels)));
|
|
90
|
+
const out = [];
|
|
91
|
+
for (let i = 0; i < count; i += step) {
|
|
92
|
+
out.push(i);
|
|
93
|
+
}
|
|
94
|
+
return out;
|
|
95
|
+
}
|
|
96
|
+
/** Index of the entry of the ascending `xs` nearest to `x`; -1 when `xs` is empty. */
|
|
97
|
+
function nearestIndex(xs, x) {
|
|
98
|
+
if (xs.length === 0) {
|
|
99
|
+
return -1;
|
|
100
|
+
}
|
|
101
|
+
let lo = 0;
|
|
102
|
+
let hi = xs.length - 1;
|
|
103
|
+
while (hi - lo > 1) {
|
|
104
|
+
const mid = (lo + hi) >> 1;
|
|
105
|
+
if (xs[mid] <= x) {
|
|
106
|
+
lo = mid;
|
|
107
|
+
}
|
|
108
|
+
else {
|
|
109
|
+
hi = mid;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
return Math.abs(xs[lo] - x) <= Math.abs(xs[hi] - x) ? lo : hi;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Path geometry for the charts. Pure functions returning SVG path data.
|
|
116
|
+
const TAU = Math.PI * 2;
|
|
117
|
+
const fmt = (n) => String(Math.round(n * 100) / 100);
|
|
118
|
+
/** Splits a series at its gaps: each run of consecutive defined points is one segment. */
|
|
119
|
+
function segments(points) {
|
|
120
|
+
const out = [];
|
|
121
|
+
let run = [];
|
|
122
|
+
for (const p of points) {
|
|
123
|
+
if (p) {
|
|
124
|
+
run.push(p);
|
|
125
|
+
}
|
|
126
|
+
else if (run.length) {
|
|
127
|
+
out.push(run);
|
|
128
|
+
run = [];
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
if (run.length) {
|
|
132
|
+
out.push(run);
|
|
133
|
+
}
|
|
134
|
+
return out;
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* The line through `points`, `null` entries leaving gaps (the pen lifts; it never joins across a
|
|
138
|
+
* missing value). A run of one point draws no line: it shows as a marker.
|
|
139
|
+
*/
|
|
140
|
+
function linePath(points) {
|
|
141
|
+
return segments(points)
|
|
142
|
+
.filter((run) => run.length > 1)
|
|
143
|
+
.map((run) => run.map((p, i) => `${i === 0 ? 'M' : 'L'}${fmt(p.x)} ${fmt(p.y)}`).join(''))
|
|
144
|
+
.join('');
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* The filled area between `top` and `bottom` (same length; `null` in either leaves a gap in the
|
|
148
|
+
* fill too). `bottom` is a baseline y for a plain area, or the lower edge of a stacked band.
|
|
149
|
+
*/
|
|
150
|
+
function areaPath(top, bottom) {
|
|
151
|
+
const base = typeof bottom === 'number' ? top.map((p) => (p ? { x: p.x, y: bottom } : null)) : bottom;
|
|
152
|
+
const out = [];
|
|
153
|
+
let run = [];
|
|
154
|
+
const flush = () => {
|
|
155
|
+
if (run.length > 1) {
|
|
156
|
+
const forward = run.map((r, i) => `${i === 0 ? 'M' : 'L'}${fmt(r.t.x)} ${fmt(r.t.y)}`).join('');
|
|
157
|
+
const back = run
|
|
158
|
+
.slice()
|
|
159
|
+
.reverse()
|
|
160
|
+
.map((r) => `L${fmt(r.b.x)} ${fmt(r.b.y)}`)
|
|
161
|
+
.join('');
|
|
162
|
+
out.push(`${forward}${back}Z`);
|
|
163
|
+
}
|
|
164
|
+
run = [];
|
|
165
|
+
};
|
|
166
|
+
top.forEach((t, i) => {
|
|
167
|
+
const b = base[i];
|
|
168
|
+
if (t && b) {
|
|
169
|
+
run.push({ t, b });
|
|
170
|
+
}
|
|
171
|
+
else {
|
|
172
|
+
flush();
|
|
173
|
+
}
|
|
174
|
+
});
|
|
175
|
+
flush();
|
|
176
|
+
return out.join('');
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Cumulative stack of `rows` (one array of values per series, aligned by index). Missing and
|
|
180
|
+
* negative values count as 0 so the bands above them still sit on the stack. Returns the lower and
|
|
181
|
+
* upper edge per series.
|
|
182
|
+
*/
|
|
183
|
+
function stackValues(rows) {
|
|
184
|
+
const length = rows.reduce((m, r) => Math.max(m, r.length), 0);
|
|
185
|
+
const running = new Array(length).fill(0);
|
|
186
|
+
return rows.map((row) => {
|
|
187
|
+
const lower = running.slice();
|
|
188
|
+
const upper = running.map((total, i) => {
|
|
189
|
+
const v = row[i];
|
|
190
|
+
return total + (typeof v === 'number' && isFinite(v) ? Math.max(0, v) : 0);
|
|
191
|
+
});
|
|
192
|
+
for (let i = 0; i < length; i++) {
|
|
193
|
+
running[i] = upper[i];
|
|
194
|
+
}
|
|
195
|
+
return { lower, upper };
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
function polar(cx, cy, r, angle) {
|
|
199
|
+
// 0 is 12 o'clock, angles grow clockwise.
|
|
200
|
+
return { x: cx + r * Math.sin(angle), y: cy - r * Math.cos(angle) };
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* Ring segments for `values` (zeros and negatives get no arc, `null` in the result). `gap` is the
|
|
204
|
+
* visible gap between neighbours in px, measured on the ring's centre line, so it reads as the 2px
|
|
205
|
+
* surface gap the mark spec asks for. A lone segment that is the whole ring is drawn as two half
|
|
206
|
+
* arcs (an SVG arc cannot start and end on the same point) with no gap.
|
|
207
|
+
*/
|
|
208
|
+
function donutArcs(values, cx, cy, outer, inner, gap = 2) {
|
|
209
|
+
const total = values.reduce((s, v) => s + (v > 0 ? v : 0), 0);
|
|
210
|
+
if (total <= 0) {
|
|
211
|
+
return values.map(() => null);
|
|
212
|
+
}
|
|
213
|
+
const drawn = values.filter((v) => v > 0).length;
|
|
214
|
+
const mid = (outer + inner) / 2;
|
|
215
|
+
const pad = drawn > 1 ? gap / mid : 0;
|
|
216
|
+
let cursor = 0;
|
|
217
|
+
return values.map((v) => {
|
|
218
|
+
if (!(v > 0)) {
|
|
219
|
+
return null;
|
|
220
|
+
}
|
|
221
|
+
const sweep = (v / total) * TAU;
|
|
222
|
+
const a0 = cursor + pad / 2;
|
|
223
|
+
const a1 = Math.max(a0, cursor + sweep - pad / 2);
|
|
224
|
+
const centroid = polar(cx, cy, mid, cursor + sweep / 2);
|
|
225
|
+
cursor += sweep;
|
|
226
|
+
if (a1 - a0 >= TAU - 1e-6) {
|
|
227
|
+
const o0 = polar(cx, cy, outer, 0);
|
|
228
|
+
const o1 = polar(cx, cy, outer, Math.PI);
|
|
229
|
+
const i0 = polar(cx, cy, inner, 0);
|
|
230
|
+
const i1 = polar(cx, cy, inner, Math.PI);
|
|
231
|
+
const path = `M${fmt(o0.x)} ${fmt(o0.y)}A${outer} ${outer} 0 1 1 ${fmt(o1.x)} ${fmt(o1.y)}A${outer} ${outer} 0 1 1 ${fmt(o0.x)} ${fmt(o0.y)}Z` +
|
|
232
|
+
`M${fmt(i0.x)} ${fmt(i0.y)}A${inner} ${inner} 0 1 0 ${fmt(i1.x)} ${fmt(i1.y)}A${inner} ${inner} 0 1 0 ${fmt(i0.x)} ${fmt(i0.y)}Z`;
|
|
233
|
+
return { startAngle: 0, endAngle: TAU, path, centroid };
|
|
234
|
+
}
|
|
235
|
+
const large = a1 - a0 > Math.PI ? 1 : 0;
|
|
236
|
+
const os = polar(cx, cy, outer, a0);
|
|
237
|
+
const oe = polar(cx, cy, outer, a1);
|
|
238
|
+
const ie = polar(cx, cy, inner, a1);
|
|
239
|
+
const is = polar(cx, cy, inner, a0);
|
|
240
|
+
const path = `M${fmt(os.x)} ${fmt(os.y)}A${outer} ${outer} 0 ${large} 1 ${fmt(oe.x)} ${fmt(oe.y)}` +
|
|
241
|
+
`L${fmt(ie.x)} ${fmt(ie.y)}A${inner} ${inner} 0 ${large} 0 ${fmt(is.x)} ${fmt(is.y)}Z`;
|
|
242
|
+
return { startAngle: a0, endAngle: a1, path, centroid };
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
/**
|
|
246
|
+
* Keeps the `max - 1` largest items (in their input order) and folds the rest into one "Other"
|
|
247
|
+
* item last. Returns the live items unchanged when they fit in `max`. Non-positive values never count.
|
|
248
|
+
*/
|
|
249
|
+
function groupOther(items, max, otherLabel) {
|
|
250
|
+
const live = items.filter((i) => i.value > 0 && isFinite(i.value));
|
|
251
|
+
const limit = Math.max(2, Math.floor(max));
|
|
252
|
+
if (live.length <= limit) {
|
|
253
|
+
return live.slice();
|
|
254
|
+
}
|
|
255
|
+
const keepIdx = live
|
|
256
|
+
.map((item, index) => ({ item, index }))
|
|
257
|
+
.sort((a, b) => b.item.value - a.item.value || a.index - b.index)
|
|
258
|
+
.slice(0, limit - 1)
|
|
259
|
+
.map((e) => e.index)
|
|
260
|
+
.sort((a, b) => a - b);
|
|
261
|
+
const kept = keepIdx.map((i) => live[i]);
|
|
262
|
+
const rest = live.filter((_, i) => !keepIdx.includes(i));
|
|
263
|
+
return [...kept, { key: '__other__', label: otherLabel, value: rest.reduce((s, r) => s + r.value, 0), other: true }];
|
|
264
|
+
}
|
|
265
|
+
/**
|
|
266
|
+
* The segment under the point `(x, y)` of a donut drawn by `donutArcs`, or -1 when the point is
|
|
267
|
+
* off the ring (in the hole or outside). Gaps belong to the segment whose share they interrupt, so
|
|
268
|
+
* the pointer never flickers through the 2px surface line.
|
|
269
|
+
*/
|
|
270
|
+
function donutHit(values, cx, cy, outer, inner, x, y) {
|
|
271
|
+
const dx = x - cx;
|
|
272
|
+
const dy = y - cy;
|
|
273
|
+
const r = Math.hypot(dx, dy);
|
|
274
|
+
if (r < inner || r > outer) {
|
|
275
|
+
return -1;
|
|
276
|
+
}
|
|
277
|
+
const total = values.reduce((s, v) => s + (v > 0 ? v : 0), 0);
|
|
278
|
+
if (total <= 0) {
|
|
279
|
+
return -1;
|
|
280
|
+
}
|
|
281
|
+
let angle = Math.atan2(dx, -dy);
|
|
282
|
+
if (angle < 0) {
|
|
283
|
+
angle += TAU;
|
|
284
|
+
}
|
|
285
|
+
let cursor = 0;
|
|
286
|
+
for (let i = 0; i < values.length; i++) {
|
|
287
|
+
if (!(values[i] > 0)) {
|
|
288
|
+
continue;
|
|
289
|
+
}
|
|
290
|
+
cursor += (values[i] / total) * TAU;
|
|
291
|
+
if (angle <= cursor + 1e-9) {
|
|
292
|
+
return i;
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
return -1;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/** How many categorical colours the palette has (`--nxe-ui-chart-1` to `--nxe-ui-chart-8`). */
|
|
299
|
+
const NXE_CHART_SLOTS = 8;
|
|
300
|
+
/** The colour of the grouped "Other" remainder: a neutral, never a categorical slot. */
|
|
301
|
+
const NXE_CHART_OTHER_COLOR = 'var(--nxe-ui-chart-other)';
|
|
302
|
+
/**
|
|
303
|
+
* The CSS colour of palette slot `index` (0-based). Slots are assigned in order and never cycled:
|
|
304
|
+
* past the eighth there is no ninth hue, so the remainder takes the neutral `Other` colour.
|
|
305
|
+
*/
|
|
306
|
+
function nxeChartColor(index) {
|
|
307
|
+
return index >= 0 && index < NXE_CHART_SLOTS ? `var(--nxe-ui-chart-${index + 1})` : NXE_CHART_OTHER_COLOR;
|
|
308
|
+
}
|
|
309
|
+
let sequence = 0;
|
|
310
|
+
/** A document-unique id fragment for aria-describedby targets. */
|
|
311
|
+
function nxeChartId(prefix) {
|
|
312
|
+
return `${prefix}-${++sequence}`;
|
|
313
|
+
}
|
|
314
|
+
/**
|
|
315
|
+
* Number formatting for a chart: separators follow `NXE_NUMBER_SEPARATORS` (which, with
|
|
316
|
+
* `provideNxeMessages({ separators: true })`, follows the active locale). Call inside a `computed`
|
|
317
|
+
* or a template so a locale switch re-renders.
|
|
318
|
+
*/
|
|
319
|
+
function nxeChartNumberFormatter() {
|
|
320
|
+
const separators = inject(NXE_NUMBER_SEPARATORS);
|
|
321
|
+
return (value, pattern) => formatNumber(value, parseNumberFormat(pattern), separators);
|
|
322
|
+
}
|
|
323
|
+
/** The message service, for a chart's strings and its locale. */
|
|
324
|
+
function nxeChartMessages() {
|
|
325
|
+
return inject(NxeMessageService);
|
|
326
|
+
}
|
|
327
|
+
/**
|
|
328
|
+
* Reports the element's width now and whenever it changes (ResizeObserver, browser only). Call in
|
|
329
|
+
* an injection context. No `effect()`: the observer writes straight into whatever signal the
|
|
330
|
+
* callback sets.
|
|
331
|
+
*/
|
|
332
|
+
function nxeObserveWidth(element, onWidth) {
|
|
333
|
+
const destroyRef = inject(DestroyRef);
|
|
334
|
+
afterNextRender(() => {
|
|
335
|
+
onWidth(Math.round(element.getBoundingClientRect().width));
|
|
336
|
+
if (typeof ResizeObserver === 'undefined') {
|
|
337
|
+
return;
|
|
338
|
+
}
|
|
339
|
+
const observer = new ResizeObserver((entries) => {
|
|
340
|
+
const width = entries[0]?.contentRect.width;
|
|
341
|
+
if (typeof width === 'number') {
|
|
342
|
+
onWidth(Math.round(width));
|
|
343
|
+
}
|
|
344
|
+
});
|
|
345
|
+
observer.observe(element);
|
|
346
|
+
destroyRef.onDestroy(() => observer.disconnect());
|
|
347
|
+
});
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
/**
|
|
351
|
+
* The data behind a chart as a real table: the alternative for a reader who cannot see the marks.
|
|
352
|
+
* It is a library-internal native table (the consuming app's own templates still avoid one). The
|
|
353
|
+
* chart keeps it visually hidden, or shows it in place of the plot under "View as table".
|
|
354
|
+
*/
|
|
355
|
+
class NxeChartDataTableComponent {
|
|
356
|
+
caption = input('', /* @ts-ignore */
|
|
357
|
+
...(ngDevMode ? [{ debugName: "caption" }] : /* istanbul ignore next */ []));
|
|
358
|
+
/** Header of the first column (the category or segment). */
|
|
359
|
+
corner = input('', /* @ts-ignore */
|
|
360
|
+
...(ngDevMode ? [{ debugName: "corner" }] : /* istanbul ignore next */ []));
|
|
361
|
+
columns = input([], /* @ts-ignore */
|
|
362
|
+
...(ngDevMode ? [{ debugName: "columns" }] : /* istanbul ignore next */ []));
|
|
363
|
+
rows = input([], /* @ts-ignore */
|
|
364
|
+
...(ngDevMode ? [{ debugName: "rows" }] : /* istanbul ignore next */ []));
|
|
365
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: NxeChartDataTableComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
366
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.5", type: NxeChartDataTableComponent, isStandalone: true, selector: "nxe-chart-data-table", inputs: { caption: { classPropertyName: "caption", publicName: "caption", isSignal: true, isRequired: false, transformFunction: null }, corner: { classPropertyName: "corner", publicName: "corner", isSignal: true, isRequired: false, transformFunction: null }, columns: { classPropertyName: "columns", publicName: "columns", isSignal: true, isRequired: false, transformFunction: null }, rows: { classPropertyName: "rows", publicName: "rows", isSignal: true, isRequired: false, transformFunction: null } }, host: { classAttribute: "nxe-chart-data-table" }, ngImport: i0, template: "<table class=\"nxe-chart-data-table__table\">\r\n <caption>{{ caption() }}</caption>\r\n <thead>\r\n <tr>\r\n <th scope=\"col\">{{ corner() }}</th>\r\n @for (column of columns(); track $index) {\r\n <th scope=\"col\">{{ column }}</th>\r\n }\r\n </tr>\r\n </thead>\r\n <tbody>\r\n @for (row of rows(); track $index) {\r\n <tr>\r\n <th scope=\"row\">{{ row.header }}</th>\r\n @for (cell of row.cells; track $index) {\r\n <td>{{ cell }}</td>\r\n }\r\n </tr>\r\n }\r\n </tbody>\r\n</table>\r\n", styles: [".nxe-chart-data-table{display:block;overflow-x:auto}.nxe-chart-data-table__table{width:100%;border-collapse:collapse;color:var(--nxe-ui-text);font-size:var(--nxe-ui-font-size-sm, 13px);font-variant-numeric:tabular-nums}.nxe-chart-data-table__table caption{padding:0 0 var(--nxe-ui-space-2, 8px);color:var(--nxe-ui-neutral-600);text-align:start}.nxe-chart-data-table__table th,.nxe-chart-data-table__table td{padding:6px 10px;border-bottom:1px solid var(--nxe-ui-border)}.nxe-chart-data-table__table thead th{background:var(--nxe-ui-surface-alt);font-weight:var(--nxe-ui-font-weight-semibold, 600)}.nxe-chart-data-table__table th{text-align:start}.nxe-chart-data-table__table td{text-align:end}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
|
|
367
|
+
}
|
|
368
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: NxeChartDataTableComponent, decorators: [{
|
|
369
|
+
type: Component,
|
|
370
|
+
args: [{ selector: 'nxe-chart-data-table', host: { class: 'nxe-chart-data-table' }, changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, template: "<table class=\"nxe-chart-data-table__table\">\r\n <caption>{{ caption() }}</caption>\r\n <thead>\r\n <tr>\r\n <th scope=\"col\">{{ corner() }}</th>\r\n @for (column of columns(); track $index) {\r\n <th scope=\"col\">{{ column }}</th>\r\n }\r\n </tr>\r\n </thead>\r\n <tbody>\r\n @for (row of rows(); track $index) {\r\n <tr>\r\n <th scope=\"row\">{{ row.header }}</th>\r\n @for (cell of row.cells; track $index) {\r\n <td>{{ cell }}</td>\r\n }\r\n </tr>\r\n }\r\n </tbody>\r\n</table>\r\n", styles: [".nxe-chart-data-table{display:block;overflow-x:auto}.nxe-chart-data-table__table{width:100%;border-collapse:collapse;color:var(--nxe-ui-text);font-size:var(--nxe-ui-font-size-sm, 13px);font-variant-numeric:tabular-nums}.nxe-chart-data-table__table caption{padding:0 0 var(--nxe-ui-space-2, 8px);color:var(--nxe-ui-neutral-600);text-align:start}.nxe-chart-data-table__table th,.nxe-chart-data-table__table td{padding:6px 10px;border-bottom:1px solid var(--nxe-ui-border)}.nxe-chart-data-table__table thead th{background:var(--nxe-ui-surface-alt);font-weight:var(--nxe-ui-font-weight-semibold, 600)}.nxe-chart-data-table__table th{text-align:start}.nxe-chart-data-table__table td{text-align:end}\n"] }]
|
|
371
|
+
}], propDecorators: { caption: [{ type: i0.Input, args: [{ isSignal: true, alias: "caption", required: false }] }], corner: [{ type: i0.Input, args: [{ isSignal: true, alias: "corner", required: false }] }], columns: [{ type: i0.Input, args: [{ isSignal: true, alias: "columns", required: false }] }], rows: [{ type: i0.Input, args: [{ isSignal: true, alias: "rows", required: false }] }] } });
|
|
372
|
+
|
|
373
|
+
/**
|
|
374
|
+
* The legend of a chart: a swatch and a name per series or segment. Each entry is a toggle button
|
|
375
|
+
* (`aria-pressed`, reachable and operable from the keyboard) that asks the chart to hide or show
|
|
376
|
+
* that series. Colour never carries the identity alone: the name is always written.
|
|
377
|
+
*/
|
|
378
|
+
class NxeChartLegendComponent {
|
|
379
|
+
items = input([], /* @ts-ignore */
|
|
380
|
+
...(ngDevMode ? [{ debugName: "items" }] : /* istanbul ignore next */ []));
|
|
381
|
+
/** Accessible name of the list. */
|
|
382
|
+
label = input('', /* @ts-ignore */
|
|
383
|
+
...(ngDevMode ? [{ debugName: "label" }] : /* istanbul ignore next */ []));
|
|
384
|
+
/** `false` renders plain entries instead of toggle buttons. */
|
|
385
|
+
toggleable = input(true, { ...(ngDevMode ? { debugName: "toggleable" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
386
|
+
/** Emits the key of the entry the user toggled. */
|
|
387
|
+
toggled = output();
|
|
388
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: NxeChartLegendComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
389
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.5", type: NxeChartLegendComponent, isStandalone: true, selector: "nxe-chart-legend", inputs: { items: { classPropertyName: "items", publicName: "items", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, toggleable: { classPropertyName: "toggleable", publicName: "toggleable", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { toggled: "toggled" }, host: { classAttribute: "nxe-chart-legend" }, ngImport: i0, template: "<ul class=\"nxe-chart-legend__list\" [attr.aria-label]=\"label() || null\">\r\n @for (item of items(); track item.key) {\r\n <li class=\"nxe-chart-legend__entry\">\r\n @if (toggleable()) {\r\n <button\r\n type=\"button\"\r\n class=\"nxe-chart-legend__item\"\r\n [class.nxe-chart-legend__item--off]=\"item.hidden\"\r\n [attr.aria-pressed]=\"!item.hidden\"\r\n (click)=\"toggled.emit(item.key)\">\r\n <span class=\"nxe-chart-legend__swatch\" aria-hidden=\"true\" [style.--nxe-chart-swatch]=\"item.color\"></span>\r\n <span class=\"nxe-chart-legend__label\">{{ item.label }}</span>\r\n @if (item.value) {\r\n <span class=\"nxe-chart-legend__value\">{{ item.value }}</span>\r\n }\r\n </button>\r\n } @else {\r\n <span class=\"nxe-chart-legend__item\">\r\n <span class=\"nxe-chart-legend__swatch\" aria-hidden=\"true\" [style.--nxe-chart-swatch]=\"item.color\"></span>\r\n <span class=\"nxe-chart-legend__label\">{{ item.label }}</span>\r\n @if (item.value) {\r\n <span class=\"nxe-chart-legend__value\">{{ item.value }}</span>\r\n }\r\n </span>\r\n }\r\n </li>\r\n }\r\n</ul>\r\n", styles: [".nxe-chart-legend{display:block}.nxe-chart-legend__list{display:flex;flex-wrap:wrap;gap:var(--nxe-ui-space-1, 4px) var(--nxe-ui-space-3, 12px);margin:0;padding:0;list-style:none}.nxe-chart-legend__item{display:inline-flex;align-items:center;gap:var(--nxe-ui-space-2, 8px);margin:0;padding:2px 6px;border:0;border-radius:var(--nxe-ui-radius, 4px);background:transparent;color:var(--nxe-ui-text);font:inherit;font-size:var(--nxe-ui-font-size-sm, 13px);line-height:var(--nxe-ui-line-height-sm, 18px)}button.nxe-chart-legend__item{cursor:pointer}button.nxe-chart-legend__item:hover{background:var(--nxe-ui-hover-bg)}button.nxe-chart-legend__item:focus-visible{outline:2px solid var(--nxe-ui-focus-ring-color);outline-offset:1px}.nxe-chart-legend__swatch{flex:none;box-sizing:border-box;width:10px;height:10px;border:2px solid var(--nxe-chart-swatch);border-radius:2px;background:var(--nxe-chart-swatch)}.nxe-chart-legend__item--off{color:var(--nxe-ui-text-muted)}.nxe-chart-legend__item--off .nxe-chart-legend__swatch{background:transparent}.nxe-chart-legend__value{color:var(--nxe-ui-neutral-600);font-variant-numeric:tabular-nums}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
|
|
390
|
+
}
|
|
391
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: NxeChartLegendComponent, decorators: [{
|
|
392
|
+
type: Component,
|
|
393
|
+
args: [{ selector: 'nxe-chart-legend', host: { class: 'nxe-chart-legend' }, changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, template: "<ul class=\"nxe-chart-legend__list\" [attr.aria-label]=\"label() || null\">\r\n @for (item of items(); track item.key) {\r\n <li class=\"nxe-chart-legend__entry\">\r\n @if (toggleable()) {\r\n <button\r\n type=\"button\"\r\n class=\"nxe-chart-legend__item\"\r\n [class.nxe-chart-legend__item--off]=\"item.hidden\"\r\n [attr.aria-pressed]=\"!item.hidden\"\r\n (click)=\"toggled.emit(item.key)\">\r\n <span class=\"nxe-chart-legend__swatch\" aria-hidden=\"true\" [style.--nxe-chart-swatch]=\"item.color\"></span>\r\n <span class=\"nxe-chart-legend__label\">{{ item.label }}</span>\r\n @if (item.value) {\r\n <span class=\"nxe-chart-legend__value\">{{ item.value }}</span>\r\n }\r\n </button>\r\n } @else {\r\n <span class=\"nxe-chart-legend__item\">\r\n <span class=\"nxe-chart-legend__swatch\" aria-hidden=\"true\" [style.--nxe-chart-swatch]=\"item.color\"></span>\r\n <span class=\"nxe-chart-legend__label\">{{ item.label }}</span>\r\n @if (item.value) {\r\n <span class=\"nxe-chart-legend__value\">{{ item.value }}</span>\r\n }\r\n </span>\r\n }\r\n </li>\r\n }\r\n</ul>\r\n", styles: [".nxe-chart-legend{display:block}.nxe-chart-legend__list{display:flex;flex-wrap:wrap;gap:var(--nxe-ui-space-1, 4px) var(--nxe-ui-space-3, 12px);margin:0;padding:0;list-style:none}.nxe-chart-legend__item{display:inline-flex;align-items:center;gap:var(--nxe-ui-space-2, 8px);margin:0;padding:2px 6px;border:0;border-radius:var(--nxe-ui-radius, 4px);background:transparent;color:var(--nxe-ui-text);font:inherit;font-size:var(--nxe-ui-font-size-sm, 13px);line-height:var(--nxe-ui-line-height-sm, 18px)}button.nxe-chart-legend__item{cursor:pointer}button.nxe-chart-legend__item:hover{background:var(--nxe-ui-hover-bg)}button.nxe-chart-legend__item:focus-visible{outline:2px solid var(--nxe-ui-focus-ring-color);outline-offset:1px}.nxe-chart-legend__swatch{flex:none;box-sizing:border-box;width:10px;height:10px;border:2px solid var(--nxe-chart-swatch);border-radius:2px;background:var(--nxe-chart-swatch)}.nxe-chart-legend__item--off{color:var(--nxe-ui-text-muted)}.nxe-chart-legend__item--off .nxe-chart-legend__swatch{background:transparent}.nxe-chart-legend__value{color:var(--nxe-ui-neutral-600);font-variant-numeric:tabular-nums}\n"] }]
|
|
394
|
+
}], propDecorators: { items: [{ type: i0.Input, args: [{ isSignal: true, alias: "items", required: false }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], toggleable: [{ type: i0.Input, args: [{ isSignal: true, alias: "toggleable", required: false }] }], toggled: [{ type: i0.Output, args: ["toggled"] }] } });
|
|
395
|
+
|
|
396
|
+
const MARGIN_TOP = 12;
|
|
397
|
+
const MARGIN_RIGHT = 16;
|
|
398
|
+
const MARGIN_BOTTOM = 30;
|
|
399
|
+
const INSET = 10;
|
|
400
|
+
const FALLBACK_WIDTH = 480;
|
|
401
|
+
const CHAR_WIDTH = 6.6;
|
|
402
|
+
/**
|
|
403
|
+
* `nxe-line-chart` draws one or more series as lines over a category or time x axis.
|
|
404
|
+
*
|
|
405
|
+
* ```html
|
|
406
|
+
* <nxe-line-chart
|
|
407
|
+
* heading="Purchases per month"
|
|
408
|
+
* [categories]="months"
|
|
409
|
+
* [series]="[{ name: 'Total', values: totals }]"
|
|
410
|
+
* xType="time"
|
|
411
|
+
* xFormat="MMM yyyy" />
|
|
412
|
+
* ```
|
|
413
|
+
*
|
|
414
|
+
* It is SVG rendered by the template from computed scales: no chart library. It follows its
|
|
415
|
+
* container's width (ResizeObserver), takes colours from the `--nxe-ui-chart-1..8` tokens in
|
|
416
|
+
* order (so light and dark both validate), writes numbers with `NXE_NUMBER_SEPARATORS` and dates
|
|
417
|
+
* with Luxon in the active locale, leaves a gap where a value is `null`, and shows:
|
|
418
|
+
*
|
|
419
|
+
* - a legend (two series or more) whose entries hide and show a series from the keyboard;
|
|
420
|
+
* - a crosshair tooltip on hover, and on focus: Tab to the plot, then Left, Right, Home, End and
|
|
421
|
+
* Esc move through the points, and the values are announced;
|
|
422
|
+
* - `role="img"` with a text summary (range, first, last, lowest and highest per series), and the
|
|
423
|
+
* data as a visually hidden table that the "View as table" button shows in place of the plot;
|
|
424
|
+
* - `nxe-skeleton` while `loading`, and `nxe-empty-state` when there is nothing to draw.
|
|
425
|
+
*
|
|
426
|
+
* `area` fills under each line (`nxe-area-chart` is the same with `area` on); with `stacked` the
|
|
427
|
+
* series stack and a missing value counts as 0. Categories must be in ascending order on a time
|
|
428
|
+
* axis. Values are numbers in the unit of the axis: scale them first if the unit differs.
|
|
429
|
+
*/
|
|
430
|
+
class NxeLineChartComponent {
|
|
431
|
+
host = inject(ElementRef).nativeElement;
|
|
432
|
+
messages = nxeChartMessages();
|
|
433
|
+
formatter = nxeChartNumberFormatter();
|
|
434
|
+
/** The series to draw, in palette order. Up to 8 take a palette colour; give more a `color`. */
|
|
435
|
+
series = input([], /* @ts-ignore */
|
|
436
|
+
...(ngDevMode ? [{ debugName: "series" }] : /* istanbul ignore next */ []));
|
|
437
|
+
/** One entry per point along the x axis; a series' `values` line up with it by index. */
|
|
438
|
+
categories = input([], /* @ts-ignore */
|
|
439
|
+
...(ngDevMode ? [{ debugName: "categories" }] : /* istanbul ignore next */ []));
|
|
440
|
+
/** `category` spaces points evenly; `time` places them by date (Luxon parses strings, Dates and DateTimes). */
|
|
441
|
+
xType = input('category', /* @ts-ignore */
|
|
442
|
+
...(ngDevMode ? [{ debugName: "xType" }] : /* istanbul ignore next */ []));
|
|
443
|
+
/** Luxon format of a date label, in the active locale. Default `dd-MMM-yyyy`. */
|
|
444
|
+
xFormat = input(NXE_DEFAULT_DISPLAY_FORMAT, /* @ts-ignore */
|
|
445
|
+
...(ngDevMode ? [{ debugName: "xFormat" }] : /* istanbul ignore next */ []));
|
|
446
|
+
/** Number pattern for values in the tooltip, summary and table. Default `#,##0.##`. */
|
|
447
|
+
valueFormat = input('#,##0.##', /* @ts-ignore */
|
|
448
|
+
...(ngDevMode ? [{ debugName: "valueFormat" }] : /* istanbul ignore next */ []));
|
|
449
|
+
/** Number pattern for the y axis; by default as many decimals as the tick step needs. */
|
|
450
|
+
axisFormat = input(undefined, /* @ts-ignore */
|
|
451
|
+
...(ngDevMode ? [{ debugName: "axisFormat" }] : /* istanbul ignore next */ []));
|
|
452
|
+
/** About how many y ticks. */
|
|
453
|
+
yTicks = input(5, { ...(ngDevMode ? { debugName: "yTicks" } : /* istanbul ignore next */ {}), transform: (v) => Math.max(2, Math.floor(numberAttribute(v, 5))) });
|
|
454
|
+
/** Start the y axis at 0 when every value is positive. Areas always do. */
|
|
455
|
+
includeZero = input(true, { ...(ngDevMode ? { debugName: "includeZero" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
456
|
+
/** Fill under each line. */
|
|
457
|
+
area = input(false, { ...(ngDevMode ? { debugName: "area" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
458
|
+
/** With `area`: stack the series instead of overlaying them. */
|
|
459
|
+
stacked = input(false, { ...(ngDevMode ? { debugName: "stacked" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
460
|
+
/** Visible title above the chart; also the start of its accessible summary. */
|
|
461
|
+
heading = input('', /* @ts-ignore */
|
|
462
|
+
...(ngDevMode ? [{ debugName: "heading" }] : /* istanbul ignore next */ []));
|
|
463
|
+
/** Extra sentence for the accessible summary, e.g. what to take away from the chart. */
|
|
464
|
+
description = input('', /* @ts-ignore */
|
|
465
|
+
...(ngDevMode ? [{ debugName: "description" }] : /* istanbul ignore next */ []));
|
|
466
|
+
/** Height of the plot in px. */
|
|
467
|
+
height = input(280, { ...(ngDevMode ? { debugName: "height" } : /* istanbul ignore next */ {}), transform: (v) => Math.max(120, numberAttribute(v, 280)) });
|
|
468
|
+
/** Fixed width in px; by default the chart fills its container. */
|
|
469
|
+
width = input(undefined, { ...(ngDevMode ? { debugName: "width" } : /* istanbul ignore next */ {}), transform: (v) => (v === undefined || v === null || v === '' ? undefined : numberAttribute(v, FALLBACK_WIDTH)) });
|
|
470
|
+
/** Show the skeleton instead of the plot. */
|
|
471
|
+
loading = input(false, { ...(ngDevMode ? { debugName: "loading" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
472
|
+
/** The legend: by default shown for two series or more. */
|
|
473
|
+
showLegend = input('auto', /* @ts-ignore */
|
|
474
|
+
...(ngDevMode ? [{ debugName: "showLegend" }] : /* istanbul ignore next */ []));
|
|
475
|
+
/** Offer the "View as table" button. */
|
|
476
|
+
showTableToggle = input(true, { ...(ngDevMode ? { debugName: "showTableToggle" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
477
|
+
/** Keys of the series the legend has hidden; two-way. */
|
|
478
|
+
hidden = model([], /* @ts-ignore */
|
|
479
|
+
...(ngDevMode ? [{ debugName: "hidden" }] : /* istanbul ignore next */ []));
|
|
480
|
+
/** `nxe-area-chart` turns this on. */
|
|
481
|
+
areaForced = false;
|
|
482
|
+
uid = nxeChartId('nxe-chart');
|
|
483
|
+
measured = signal(0, /* @ts-ignore */
|
|
484
|
+
...(ngDevMode ? [{ debugName: "measured" }] : /* istanbul ignore next */ []));
|
|
485
|
+
hoverIndex = signal(null, /* @ts-ignore */
|
|
486
|
+
...(ngDevMode ? [{ debugName: "hoverIndex" }] : /* istanbul ignore next */ []));
|
|
487
|
+
keyboardIndex = signal(null, /* @ts-ignore */
|
|
488
|
+
...(ngDevMode ? [{ debugName: "keyboardIndex" }] : /* istanbul ignore next */ []));
|
|
489
|
+
tableShown = signal(false, /* @ts-ignore */
|
|
490
|
+
...(ngDevMode ? [{ debugName: "tableShown" }] : /* istanbul ignore next */ []));
|
|
491
|
+
constructor() {
|
|
492
|
+
nxeObserveWidth(this.host, (w) => this.measured.set(w));
|
|
493
|
+
}
|
|
494
|
+
// ---- data ------------------------------------------------------------------------------
|
|
495
|
+
isTime = computed(() => this.xType() === 'time' &&
|
|
496
|
+
this.categories().length > 0 &&
|
|
497
|
+
this.categories().every((c) => this.toDate(c) !== null), /* @ts-ignore */
|
|
498
|
+
...(ngDevMode ? [{ debugName: "isTime" }] : /* istanbul ignore next */ []));
|
|
499
|
+
isArea = computed(() => this.areaForced || this.area(), /* @ts-ignore */
|
|
500
|
+
...(ngDevMode ? [{ debugName: "isArea" }] : /* istanbul ignore next */ []));
|
|
501
|
+
isStacked = computed(() => this.isArea() && this.stacked(), /* @ts-ignore */
|
|
502
|
+
...(ngDevMode ? [{ debugName: "isStacked" }] : /* istanbul ignore next */ []));
|
|
503
|
+
/** Number of points along x. */
|
|
504
|
+
count = computed(() => this.series().reduce((m, s) => Math.max(m, s.values.length), this.categories().length), /* @ts-ignore */
|
|
505
|
+
...(ngDevMode ? [{ debugName: "count" }] : /* istanbul ignore next */ []));
|
|
506
|
+
labels = computed(() => {
|
|
507
|
+
const categories = this.categories();
|
|
508
|
+
const locale = this.messages.locale();
|
|
509
|
+
const format = this.xFormat();
|
|
510
|
+
const time = this.isTime();
|
|
511
|
+
return Array.from({ length: this.count() }, (_, i) => {
|
|
512
|
+
const c = categories[i];
|
|
513
|
+
if (c === undefined) {
|
|
514
|
+
return String(i + 1);
|
|
515
|
+
}
|
|
516
|
+
if (time || c instanceof Date || DateTime.isDateTime(c)) {
|
|
517
|
+
return formatDisplay(this.toDate(c), format, locale);
|
|
518
|
+
}
|
|
519
|
+
return String(c);
|
|
520
|
+
});
|
|
521
|
+
}, /* @ts-ignore */
|
|
522
|
+
...(ngDevMode ? [{ debugName: "labels" }] : /* istanbul ignore next */ []));
|
|
523
|
+
xs = computed(() => {
|
|
524
|
+
const categories = this.categories();
|
|
525
|
+
const time = this.isTime();
|
|
526
|
+
return Array.from({ length: this.count() }, (_, i) => (time ? (this.toDate(categories[i])?.toMillis() ?? i) : i));
|
|
527
|
+
}, /* @ts-ignore */
|
|
528
|
+
...(ngDevMode ? [{ debugName: "xs" }] : /* istanbul ignore next */ []));
|
|
529
|
+
entries = computed(() => {
|
|
530
|
+
const hidden = this.hidden();
|
|
531
|
+
return this.series().map((s, slot) => {
|
|
532
|
+
const key = s.key ?? s.name;
|
|
533
|
+
return { s, key, slot, color: s.color ?? nxeChartColor(slot), hidden: hidden.includes(key) };
|
|
534
|
+
});
|
|
535
|
+
}, /* @ts-ignore */
|
|
536
|
+
...(ngDevMode ? [{ debugName: "entries" }] : /* istanbul ignore next */ []));
|
|
537
|
+
visible = computed(() => this.entries().filter((e) => !e.hidden), /* @ts-ignore */
|
|
538
|
+
...(ngDevMode ? [{ debugName: "visible" }] : /* istanbul ignore next */ []));
|
|
539
|
+
empty = computed(() => !this.series().some((s) => s.values.some((v) => typeof v === 'number' && isFinite(v))), /* @ts-ignore */
|
|
540
|
+
...(ngDevMode ? [{ debugName: "empty" }] : /* istanbul ignore next */ []));
|
|
541
|
+
stack = computed(() => this.isStacked() ? stackValues(this.visible().map((e) => e.s.values)) : [], /* @ts-ignore */
|
|
542
|
+
...(ngDevMode ? [{ debugName: "stack" }] : /* istanbul ignore next */ []));
|
|
543
|
+
// ---- scales ----------------------------------------------------------------------------
|
|
544
|
+
domain = computed(() => {
|
|
545
|
+
let lo = Infinity;
|
|
546
|
+
let hi = -Infinity;
|
|
547
|
+
if (this.isStacked()) {
|
|
548
|
+
lo = 0;
|
|
549
|
+
for (const band of this.stack()) {
|
|
550
|
+
for (const v of band.upper) {
|
|
551
|
+
hi = Math.max(hi, v);
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
else {
|
|
556
|
+
for (const e of this.visible()) {
|
|
557
|
+
for (const v of e.s.values) {
|
|
558
|
+
if (typeof v === 'number' && isFinite(v)) {
|
|
559
|
+
lo = Math.min(lo, v);
|
|
560
|
+
hi = Math.max(hi, v);
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
if (this.isArea() || (this.includeZero() && lo > 0)) {
|
|
565
|
+
lo = Math.min(lo, 0);
|
|
566
|
+
}
|
|
567
|
+
if (this.isArea() && hi < 0) {
|
|
568
|
+
hi = 0;
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
return isFinite(lo) && isFinite(hi) ? niceScale(lo, hi, this.yTicks()) : niceScale(0, 1, this.yTicks());
|
|
572
|
+
}, /* @ts-ignore */
|
|
573
|
+
...(ngDevMode ? [{ debugName: "domain" }] : /* istanbul ignore next */ []));
|
|
574
|
+
axisPattern = computed(() => this.axisFormat() ?? patternForDecimals(decimalsOf(this.domain().step)), /* @ts-ignore */
|
|
575
|
+
...(ngDevMode ? [{ debugName: "axisPattern" }] : /* istanbul ignore next */ []));
|
|
576
|
+
yLabels = computed(() => {
|
|
577
|
+
const pattern = this.axisPattern();
|
|
578
|
+
return this.domain().ticks.map((t) => this.formatter(t, pattern));
|
|
579
|
+
}, /* @ts-ignore */
|
|
580
|
+
...(ngDevMode ? [{ debugName: "yLabels" }] : /* istanbul ignore next */ []));
|
|
581
|
+
svgWidth = computed(() => Math.max(160, this.width() ?? (this.measured() || FALLBACK_WIDTH)), /* @ts-ignore */
|
|
582
|
+
...(ngDevMode ? [{ debugName: "svgWidth" }] : /* istanbul ignore next */ []));
|
|
583
|
+
svgHeight = computed(() => this.height() + MARGIN_TOP + MARGIN_BOTTOM, /* @ts-ignore */
|
|
584
|
+
...(ngDevMode ? [{ debugName: "svgHeight" }] : /* istanbul ignore next */ []));
|
|
585
|
+
left = computed(() => Math.ceil(this.yLabels().reduce((m, l) => Math.max(m, l.length), 1) * CHAR_WIDTH) + 16, /* @ts-ignore */
|
|
586
|
+
...(ngDevMode ? [{ debugName: "left" }] : /* istanbul ignore next */ []));
|
|
587
|
+
plotRight = computed(() => this.svgWidth() - MARGIN_RIGHT, /* @ts-ignore */
|
|
588
|
+
...(ngDevMode ? [{ debugName: "plotRight" }] : /* istanbul ignore next */ []));
|
|
589
|
+
plotBottom = computed(() => MARGIN_TOP + this.height(), /* @ts-ignore */
|
|
590
|
+
...(ngDevMode ? [{ debugName: "plotBottom" }] : /* istanbul ignore next */ []));
|
|
591
|
+
xScale = computed(() => {
|
|
592
|
+
const xs = this.xs();
|
|
593
|
+
const lo = xs.length ? xs[0] : 0;
|
|
594
|
+
const hi = xs.length ? xs[xs.length - 1] : 1;
|
|
595
|
+
return linearScale(lo, hi, this.left() + INSET, this.plotRight() - INSET);
|
|
596
|
+
}, /* @ts-ignore */
|
|
597
|
+
...(ngDevMode ? [{ debugName: "xScale" }] : /* istanbul ignore next */ []));
|
|
598
|
+
yScale = computed(() => {
|
|
599
|
+
const d = this.domain();
|
|
600
|
+
return linearScale(d.min, d.max, this.plotBottom(), MARGIN_TOP);
|
|
601
|
+
}, /* @ts-ignore */
|
|
602
|
+
...(ngDevMode ? [{ debugName: "yScale" }] : /* istanbul ignore next */ []));
|
|
603
|
+
/** Pixel x of each point. */
|
|
604
|
+
xPx = computed(() => {
|
|
605
|
+
const scale = this.xScale();
|
|
606
|
+
return this.xs().map((x) => scale(x));
|
|
607
|
+
}, /* @ts-ignore */
|
|
608
|
+
...(ngDevMode ? [{ debugName: "xPx" }] : /* istanbul ignore next */ []));
|
|
609
|
+
yGrid = computed(() => {
|
|
610
|
+
const scale = this.yScale();
|
|
611
|
+
const labels = this.yLabels();
|
|
612
|
+
return this.domain().ticks.map((tick, i) => ({ y: scale(tick), label: labels[i], zero: tick === 0 }));
|
|
613
|
+
}, /* @ts-ignore */
|
|
614
|
+
...(ngDevMode ? [{ debugName: "yGrid" }] : /* istanbul ignore next */ []));
|
|
615
|
+
xTicks = computed(() => {
|
|
616
|
+
const labels = this.labels();
|
|
617
|
+
const px = this.xPx();
|
|
618
|
+
const widest = labels.reduce((m, l) => Math.max(m, l.length), 1) * CHAR_WIDTH + 18;
|
|
619
|
+
const room = Math.max(1, Math.floor((this.plotRight() - this.left()) / widest));
|
|
620
|
+
return labelIndexes(labels.length, room).map((i) => ({ x: px[i], text: labels[i] }));
|
|
621
|
+
}, /* @ts-ignore */
|
|
622
|
+
...(ngDevMode ? [{ debugName: "xTicks" }] : /* istanbul ignore next */ []));
|
|
623
|
+
// ---- marks -----------------------------------------------------------------------------
|
|
624
|
+
lines = computed(() => {
|
|
625
|
+
const px = this.xPx();
|
|
626
|
+
const y = this.yScale();
|
|
627
|
+
const d = this.domain();
|
|
628
|
+
const base = y(Math.min(Math.max(0, d.min), d.max));
|
|
629
|
+
const stacked = this.isStacked();
|
|
630
|
+
const stack = this.stack();
|
|
631
|
+
const area = this.isArea();
|
|
632
|
+
return this.visible().map((e, band) => {
|
|
633
|
+
const top = [];
|
|
634
|
+
const bottom = [];
|
|
635
|
+
for (let i = 0; i < px.length; i++) {
|
|
636
|
+
const v = e.s.values[i];
|
|
637
|
+
if (stacked) {
|
|
638
|
+
top.push({ x: px[i], y: y(stack[band].upper[i]) });
|
|
639
|
+
bottom.push({ x: px[i], y: y(stack[band].lower[i]) });
|
|
640
|
+
}
|
|
641
|
+
else {
|
|
642
|
+
top.push(typeof v === 'number' && isFinite(v) ? { x: px[i], y: y(v) } : null);
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
const markers = top.flatMap((p, index) => p && (!stacked || typeof e.s.values[index] === 'number') ? [{ x: p.x, y: p.y, index }] : []);
|
|
646
|
+
return {
|
|
647
|
+
key: e.key,
|
|
648
|
+
name: e.s.name,
|
|
649
|
+
color: e.color,
|
|
650
|
+
line: linePath(top),
|
|
651
|
+
area: area ? areaPath(top, stacked ? bottom : base) : '',
|
|
652
|
+
markers,
|
|
653
|
+
};
|
|
654
|
+
});
|
|
655
|
+
}, /* @ts-ignore */
|
|
656
|
+
...(ngDevMode ? [{ debugName: "lines" }] : /* istanbul ignore next */ []));
|
|
657
|
+
/** Markers on every point while there are few enough to tell apart; otherwise only the active one. */
|
|
658
|
+
showAllMarkers = computed(() => {
|
|
659
|
+
const n = this.count();
|
|
660
|
+
return n > 0 && (this.plotRight() - this.left()) / Math.max(1, n - 1) >= 18;
|
|
661
|
+
}, /* @ts-ignore */
|
|
662
|
+
...(ngDevMode ? [{ debugName: "showAllMarkers" }] : /* istanbul ignore next */ []));
|
|
663
|
+
// ---- interaction -----------------------------------------------------------------------
|
|
664
|
+
activeIndex = computed(() => this.hoverIndex() ?? this.keyboardIndex(), /* @ts-ignore */
|
|
665
|
+
...(ngDevMode ? [{ debugName: "activeIndex" }] : /* istanbul ignore next */ []));
|
|
666
|
+
tooltip = computed(() => {
|
|
667
|
+
const index = this.activeIndex();
|
|
668
|
+
if (index === null || index >= this.count()) {
|
|
669
|
+
return null;
|
|
670
|
+
}
|
|
671
|
+
const pattern = this.valueFormat();
|
|
672
|
+
const noValue = this.messages.t('display.noValue');
|
|
673
|
+
const rows = this.visible().map((e) => {
|
|
674
|
+
const v = e.s.values[index];
|
|
675
|
+
return {
|
|
676
|
+
key: e.key,
|
|
677
|
+
name: e.s.name,
|
|
678
|
+
color: e.color,
|
|
679
|
+
value: typeof v === 'number' && isFinite(v) ? this.formatter(v, pattern) : noValue,
|
|
680
|
+
};
|
|
681
|
+
});
|
|
682
|
+
const x = this.xPx()[index];
|
|
683
|
+
const width = this.svgWidth();
|
|
684
|
+
const align = x < 100 ? 'start' : x > width - 100 ? 'end' : 'center';
|
|
685
|
+
return { title: this.labels()[index], rows, x, index, align };
|
|
686
|
+
}, /* @ts-ignore */
|
|
687
|
+
...(ngDevMode ? [{ debugName: "tooltip" }] : /* istanbul ignore next */ []));
|
|
688
|
+
/** What a screen reader hears when keyboard focus lands on a point. */
|
|
689
|
+
announcement = computed(() => {
|
|
690
|
+
const t = this.keyboardIndex() === null ? null : this.tooltip();
|
|
691
|
+
return t ? `${t.title}: ${t.rows.map((r) => `${r.name} ${r.value}`).join(', ')}` : '';
|
|
692
|
+
}, /* @ts-ignore */
|
|
693
|
+
...(ngDevMode ? [{ debugName: "announcement" }] : /* istanbul ignore next */ []));
|
|
694
|
+
onPointerMove(event) {
|
|
695
|
+
const rect = event.currentTarget.getBoundingClientRect();
|
|
696
|
+
const i = nearestIndex(this.xPx(), event.clientX - rect.left);
|
|
697
|
+
this.hoverIndex.set(i < 0 ? null : i);
|
|
698
|
+
}
|
|
699
|
+
onPointerLeave() {
|
|
700
|
+
this.hoverIndex.set(null);
|
|
701
|
+
}
|
|
702
|
+
onFocus() {
|
|
703
|
+
if (this.keyboardIndex() === null && this.hoverIndex() === null) {
|
|
704
|
+
this.keyboardIndex.set(0);
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
onBlur() {
|
|
708
|
+
this.keyboardIndex.set(null);
|
|
709
|
+
}
|
|
710
|
+
onKeydown(event) {
|
|
711
|
+
const last = this.count() - 1;
|
|
712
|
+
const current = this.keyboardIndex() ?? 0;
|
|
713
|
+
let next;
|
|
714
|
+
switch (event.key) {
|
|
715
|
+
case 'ArrowRight':
|
|
716
|
+
case 'ArrowDown':
|
|
717
|
+
next = Math.min(last, current + 1);
|
|
718
|
+
break;
|
|
719
|
+
case 'ArrowLeft':
|
|
720
|
+
case 'ArrowUp':
|
|
721
|
+
next = Math.max(0, current - 1);
|
|
722
|
+
break;
|
|
723
|
+
case 'Home':
|
|
724
|
+
next = 0;
|
|
725
|
+
break;
|
|
726
|
+
case 'End':
|
|
727
|
+
next = last;
|
|
728
|
+
break;
|
|
729
|
+
case 'Escape':
|
|
730
|
+
next = null;
|
|
731
|
+
break;
|
|
732
|
+
default:
|
|
733
|
+
return;
|
|
734
|
+
}
|
|
735
|
+
event.preventDefault();
|
|
736
|
+
this.hoverIndex.set(null);
|
|
737
|
+
this.keyboardIndex.set(next);
|
|
738
|
+
}
|
|
739
|
+
// ---- legend, table, summary ------------------------------------------------------------
|
|
740
|
+
legendItems = computed(() => this.entries().map((e) => ({ key: e.key, label: e.s.name, color: e.color, hidden: e.hidden })), /* @ts-ignore */
|
|
741
|
+
...(ngDevMode ? [{ debugName: "legendItems" }] : /* istanbul ignore next */ []));
|
|
742
|
+
legendShown = computed(() => {
|
|
743
|
+
const mode = this.showLegend();
|
|
744
|
+
return mode === 'auto' ? this.series().length > 1 : mode;
|
|
745
|
+
}, /* @ts-ignore */
|
|
746
|
+
...(ngDevMode ? [{ debugName: "legendShown" }] : /* istanbul ignore next */ []));
|
|
747
|
+
toggleSeries(key) {
|
|
748
|
+
const hidden = this.hidden();
|
|
749
|
+
this.hidden.set(hidden.includes(key) ? hidden.filter((k) => k !== key) : [...hidden, key]);
|
|
750
|
+
}
|
|
751
|
+
tableColumns = computed(() => this.series().map((s) => s.name), /* @ts-ignore */
|
|
752
|
+
...(ngDevMode ? [{ debugName: "tableColumns" }] : /* istanbul ignore next */ []));
|
|
753
|
+
tableRows = computed(() => {
|
|
754
|
+
const labels = this.labels();
|
|
755
|
+
const pattern = this.valueFormat();
|
|
756
|
+
const noValue = this.messages.t('display.noValue');
|
|
757
|
+
return labels.map((header, i) => ({
|
|
758
|
+
header,
|
|
759
|
+
cells: this.series().map((s) => {
|
|
760
|
+
const v = s.values[i];
|
|
761
|
+
return typeof v === 'number' && isFinite(v) ? this.formatter(v, pattern) : noValue;
|
|
762
|
+
}),
|
|
763
|
+
}));
|
|
764
|
+
}, /* @ts-ignore */
|
|
765
|
+
...(ngDevMode ? [{ debugName: "tableRows" }] : /* istanbul ignore next */ []));
|
|
766
|
+
summary = computed(() => {
|
|
767
|
+
const m = this.messages;
|
|
768
|
+
const labels = this.labels();
|
|
769
|
+
const pattern = this.valueFormat();
|
|
770
|
+
const kind = this.isStacked()
|
|
771
|
+
? 'charts.summary.stackedArea'
|
|
772
|
+
: this.isArea()
|
|
773
|
+
? 'charts.summary.area'
|
|
774
|
+
: 'charts.summary.line';
|
|
775
|
+
const parts = [];
|
|
776
|
+
if (this.heading()) {
|
|
777
|
+
parts.push(`${this.heading()}.`);
|
|
778
|
+
}
|
|
779
|
+
parts.push(m.t(kind, this.series().length, labels.length, labels[0] ?? '', labels[labels.length - 1] ?? ''));
|
|
780
|
+
for (const s of this.series()) {
|
|
781
|
+
const nums = s.values.filter((v) => typeof v === 'number' && isFinite(v));
|
|
782
|
+
if (nums.length) {
|
|
783
|
+
const f = (v) => this.formatter(v, pattern);
|
|
784
|
+
parts.push(m.t('charts.summary.series', s.name, f(nums[0]), f(nums[nums.length - 1]), f(Math.min(...nums)), f(Math.max(...nums))));
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
if (this.description()) {
|
|
788
|
+
parts.push(this.description());
|
|
789
|
+
}
|
|
790
|
+
return parts.join(' ');
|
|
791
|
+
}, /* @ts-ignore */
|
|
792
|
+
...(ngDevMode ? [{ debugName: "summary" }] : /* istanbul ignore next */ []));
|
|
793
|
+
toggleTable() {
|
|
794
|
+
this.tableShown.update((v) => !v);
|
|
795
|
+
}
|
|
796
|
+
toDate(value) {
|
|
797
|
+
if (typeof value === 'number') {
|
|
798
|
+
const dt = DateTime.fromMillis(value);
|
|
799
|
+
return dt.isValid ? dt : null;
|
|
800
|
+
}
|
|
801
|
+
return parseDateValue(value);
|
|
802
|
+
}
|
|
803
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: NxeLineChartComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
804
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.5", type: NxeLineChartComponent, isStandalone: true, selector: "nxe-line-chart", inputs: { series: { classPropertyName: "series", publicName: "series", isSignal: true, isRequired: false, transformFunction: null }, categories: { classPropertyName: "categories", publicName: "categories", isSignal: true, isRequired: false, transformFunction: null }, xType: { classPropertyName: "xType", publicName: "xType", isSignal: true, isRequired: false, transformFunction: null }, xFormat: { classPropertyName: "xFormat", publicName: "xFormat", isSignal: true, isRequired: false, transformFunction: null }, valueFormat: { classPropertyName: "valueFormat", publicName: "valueFormat", isSignal: true, isRequired: false, transformFunction: null }, axisFormat: { classPropertyName: "axisFormat", publicName: "axisFormat", isSignal: true, isRequired: false, transformFunction: null }, yTicks: { classPropertyName: "yTicks", publicName: "yTicks", isSignal: true, isRequired: false, transformFunction: null }, includeZero: { classPropertyName: "includeZero", publicName: "includeZero", isSignal: true, isRequired: false, transformFunction: null }, area: { classPropertyName: "area", publicName: "area", isSignal: true, isRequired: false, transformFunction: null }, stacked: { classPropertyName: "stacked", publicName: "stacked", isSignal: true, isRequired: false, transformFunction: null }, heading: { classPropertyName: "heading", publicName: "heading", isSignal: true, isRequired: false, transformFunction: null }, description: { classPropertyName: "description", publicName: "description", isSignal: true, isRequired: false, transformFunction: null }, height: { classPropertyName: "height", publicName: "height", isSignal: true, isRequired: false, transformFunction: null }, width: { classPropertyName: "width", publicName: "width", isSignal: true, isRequired: false, transformFunction: null }, loading: { classPropertyName: "loading", publicName: "loading", isSignal: true, isRequired: false, transformFunction: null }, showLegend: { classPropertyName: "showLegend", publicName: "showLegend", isSignal: true, isRequired: false, transformFunction: null }, showTableToggle: { classPropertyName: "showTableToggle", publicName: "showTableToggle", isSignal: true, isRequired: false, transformFunction: null }, hidden: { classPropertyName: "hidden", publicName: "hidden", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { hidden: "hiddenChange" }, host: { classAttribute: "nxe-chart nxe-line-chart" }, ngImport: i0, template: "<figure class=\"nxe-chart__figure\" [attr.aria-busy]=\"loading() ? 'true' : null\">\r\n @if (heading()) {\r\n <figcaption class=\"nxe-chart__heading\">{{ heading() }}</figcaption>\r\n }\r\n @if (loading()) {\r\n <nxe-skeleton variant=\"rect\" width=\"100%\" [height]=\"svgHeight()\" />\r\n } @else if (empty()) {\r\n <nxe-empty-state class=\"nxe-chart__empty\" [heading]=\"messages.t('charts.empty')\" [headingLevel]=\"4\" />\r\n } @else {\r\n @if (showTableToggle()) {\r\n <div class=\"nxe-chart__toolbar\">\r\n <button\r\n type=\"button\"\r\n class=\"nxe-chart__toggle\"\r\n [attr.aria-controls]=\"uid + '-table'\"\r\n (click)=\"toggleTable()\">\r\n {{ messages.t(tableShown() ? 'charts.viewAsChart' : 'charts.viewAsTable') }}\r\n </button>\r\n </div>\r\n }\r\n <div class=\"nxe-chart__stage\" [hidden]=\"tableShown()\">\r\n <svg\r\n class=\"nxe-chart__svg\"\r\n role=\"img\"\r\n [attr.aria-label]=\"summary()\"\r\n [attr.width]=\"svgWidth()\"\r\n [attr.height]=\"svgHeight()\"\r\n [attr.viewBox]=\"'0 0 ' + svgWidth() + ' ' + svgHeight()\">\r\n @for (g of yGrid(); track $index) {\r\n <line\r\n class=\"nxe-chart__grid\"\r\n [class.nxe-chart__grid--zero]=\"g.zero\"\r\n [attr.x1]=\"left()\"\r\n [attr.x2]=\"plotRight()\"\r\n [attr.y1]=\"g.y\"\r\n [attr.y2]=\"g.y\" />\r\n <text class=\"nxe-chart__tick\" text-anchor=\"end\" [attr.x]=\"left() - 8\" [attr.y]=\"g.y\" dy=\"0.32em\">{{ g.label }}</text>\r\n }\r\n @for (t of xTicks(); track $index) {\r\n <text class=\"nxe-chart__tick\" text-anchor=\"middle\" [attr.x]=\"t.x\" [attr.y]=\"plotBottom() + 18\">{{ t.text }}</text>\r\n }\r\n @for (l of lines(); track l.key) {\r\n @if (l.area) {\r\n <path\r\n class=\"nxe-chart__area\"\r\n [class.nxe-chart__area--stacked]=\"isStacked()\"\r\n [attr.d]=\"l.area\"\r\n [style.fill]=\"l.color\" />\r\n }\r\n }\r\n @for (l of lines(); track l.key) {\r\n <path class=\"nxe-chart__line\" [attr.d]=\"l.line\" [style.stroke]=\"l.color\" />\r\n @for (m of l.markers; track m.index) {\r\n @if (showAllMarkers() || l.markers.length === 1 || m.index === activeIndex()) {\r\n <circle class=\"nxe-chart__marker\" [attr.cx]=\"m.x\" [attr.cy]=\"m.y\" r=\"4\" [style.fill]=\"l.color\" />\r\n }\r\n }\r\n }\r\n @if (tooltip(); as tip) {\r\n <line class=\"nxe-chart__crosshair\" [attr.x1]=\"tip.x\" [attr.x2]=\"tip.x\" [attr.y1]=\"marginTop\" [attr.y2]=\"plotBottom()\" />\r\n @for (l of lines(); track l.key) {\r\n @for (m of l.markers; track m.index) {\r\n @if (m.index === tip.index) {\r\n <circle class=\"nxe-chart__marker nxe-chart__marker--active\" [attr.cx]=\"m.x\" [attr.cy]=\"m.y\" r=\"5\" [style.fill]=\"l.color\" />\r\n }\r\n }\r\n }\r\n }\r\n </svg>\r\n <div\r\n class=\"nxe-chart__focus\"\r\n role=\"group\"\r\n tabindex=\"0\"\r\n [attr.aria-label]=\"messages.t('charts.navigate')\"\r\n (pointermove)=\"onPointerMove($event)\"\r\n (pointerleave)=\"onPointerLeave()\"\r\n (focus)=\"onFocus()\"\r\n (blur)=\"onBlur()\"\r\n (keydown)=\"onKeydown($event)\"></div>\r\n @if (tooltip(); as tip) {\r\n <div\r\n class=\"nxe-chart__tooltip\"\r\n role=\"tooltip\"\r\n [class.nxe-chart__tooltip--start]=\"tip.align === 'start'\"\r\n [class.nxe-chart__tooltip--end]=\"tip.align === 'end'\"\r\n [style.left.px]=\"tip.x\"\r\n [style.top.px]=\"marginTop\">\r\n <div class=\"nxe-chart__tooltip-title\">{{ tip.title }}</div>\r\n @for (r of tip.rows; track r.key) {\r\n <div class=\"nxe-chart__tooltip-row\">\r\n <span class=\"nxe-chart__swatch\" aria-hidden=\"true\" [style.background]=\"r.color\"></span>\r\n <span class=\"nxe-chart__tooltip-name\">{{ r.name }}</span>\r\n <span class=\"nxe-chart__tooltip-value\">{{ r.value }}</span>\r\n </div>\r\n }\r\n </div>\r\n }\r\n <div class=\"nxe-chart__sr\" aria-live=\"polite\" aria-atomic=\"true\">{{ announcement() }}</div>\r\n </div>\r\n <div [id]=\"uid + '-table'\" [class.nxe-chart__sr]=\"!tableShown()\">\r\n <nxe-chart-data-table\r\n [caption]=\"heading()\"\r\n [corner]=\"messages.t('charts.table.category')\"\r\n [columns]=\"tableColumns()\"\r\n [rows]=\"tableRows()\" />\r\n </div>\r\n @if (legendShown()) {\r\n <nxe-chart-legend [items]=\"legendItems()\" [label]=\"messages.t('charts.legend')\" (toggled)=\"toggleSeries($event)\" />\r\n }\r\n }\r\n</figure>\r\n", styles: [".nxe-chart{display:block;width:100%;min-width:0;color:var(--nxe-ui-text);font-family:var(--nxe-ui-font-family)}.nxe-chart__figure{display:flex;flex-direction:column;gap:var(--nxe-ui-space-2, 8px);margin:0}.nxe-chart__heading{color:var(--nxe-ui-text);font-size:var(--nxe-ui-font-size-lg, 16px);font-weight:var(--nxe-ui-font-weight-semibold, 600);line-height:var(--nxe-ui-line-height-lg, 24px)}.nxe-chart__toolbar{display:flex;justify-content:flex-end}.nxe-chart__toggle{margin:0;padding:2px 8px;border:1px solid var(--nxe-ui-border);border-radius:var(--nxe-ui-radius, 4px);background:var(--nxe-ui-surface);color:var(--nxe-ui-text);font:inherit;font-size:var(--nxe-ui-font-size-xs, 12px);cursor:pointer}.nxe-chart__toggle:hover{background:var(--nxe-ui-hover-bg)}.nxe-chart__toggle:focus-visible{outline:2px solid var(--nxe-ui-focus-ring-color);outline-offset:1px}.nxe-chart__stage{position:relative;max-width:100%;overflow:visible}.nxe-chart__stage[hidden]{display:none}.nxe-chart__svg{display:block;max-width:100%;overflow:visible}.nxe-chart__focus{position:absolute;inset:0;border-radius:var(--nxe-ui-radius, 4px);outline:none;touch-action:pan-y}.nxe-chart__focus:focus-visible{outline:2px solid var(--nxe-ui-focus-ring-color);outline-offset:2px}.nxe-chart__grid{stroke:var(--nxe-ui-neutral-150);stroke-width:1;shape-rendering:crispEdges}.nxe-chart__grid--zero{stroke:var(--nxe-ui-neutral-240)}.nxe-chart__tick{fill:var(--nxe-ui-neutral-600);font-size:var(--nxe-ui-font-size-xs, 12px);font-variant-numeric:tabular-nums}.nxe-chart__line{fill:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round}.nxe-chart__area{fill-opacity:.16;stroke:none}.nxe-chart__area--stacked{fill-opacity:.9;stroke:var(--nxe-ui-surface);stroke-width:2;stroke-linejoin:round}.nxe-chart__marker{stroke:var(--nxe-ui-surface);stroke-width:2}.nxe-chart__crosshair{stroke:var(--nxe-ui-neutral-450);stroke-width:1;stroke-dasharray:3 3;pointer-events:none}.nxe-chart__tooltip{position:absolute;z-index:2;min-width:120px;max-width:260px;padding:6px 10px;border:1px solid var(--nxe-ui-border);border-radius:var(--nxe-ui-radius-md, 6px);background:var(--nxe-ui-surface);box-shadow:var(--nxe-ui-shadow-sm);color:var(--nxe-ui-text);font-size:var(--nxe-ui-font-size-xs, 12px);line-height:var(--nxe-ui-line-height-xs, 16px);pointer-events:none;transform:translate(-50%)}.nxe-chart__tooltip.nxe-chart__tooltip--start{transform:translate(12px)}.nxe-chart__tooltip.nxe-chart__tooltip--end{transform:translate(calc(-100% - 12px))}.nxe-chart__tooltip-title{margin-bottom:2px;font-weight:var(--nxe-ui-font-weight-semibold, 600)}.nxe-chart__tooltip-row{display:flex;align-items:center;gap:var(--nxe-ui-space-2, 8px)}.nxe-chart__tooltip-name{flex:1;min-width:0;overflow:hidden;color:var(--nxe-ui-neutral-700);text-overflow:ellipsis;white-space:nowrap}.nxe-chart__tooltip-value{font-variant-numeric:tabular-nums;font-weight:var(--nxe-ui-font-weight-medium, 500)}.nxe-chart__swatch{flex:none;width:10px;height:10px;border-radius:2px}.nxe-chart__sr{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;border:0;clip-path:inset(50%);white-space:nowrap}\n"], dependencies: [{ kind: "component", type: NxeChartLegendComponent, selector: "nxe-chart-legend", inputs: ["items", "label", "toggleable"], outputs: ["toggled"] }, { kind: "component", type: NxeChartDataTableComponent, selector: "nxe-chart-data-table", inputs: ["caption", "corner", "columns", "rows"] }, { kind: "component", type: NxeSkeletonComponent, selector: "nxe-skeleton", inputs: ["variant", "width", "height", "lines", "animated"] }, { kind: "component", type: NxeEmptyStateComponent, selector: "nxe-empty-state", inputs: ["heading", "description", "headingLevel"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
|
|
805
|
+
}
|
|
806
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: NxeLineChartComponent, decorators: [{
|
|
807
|
+
type: Component,
|
|
808
|
+
args: [{ selector: 'nxe-line-chart', imports: [NxeChartLegendComponent, NxeChartDataTableComponent, NxeSkeletonComponent, NxeEmptyStateComponent], host: { class: 'nxe-chart nxe-line-chart' }, changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, template: "<figure class=\"nxe-chart__figure\" [attr.aria-busy]=\"loading() ? 'true' : null\">\r\n @if (heading()) {\r\n <figcaption class=\"nxe-chart__heading\">{{ heading() }}</figcaption>\r\n }\r\n @if (loading()) {\r\n <nxe-skeleton variant=\"rect\" width=\"100%\" [height]=\"svgHeight()\" />\r\n } @else if (empty()) {\r\n <nxe-empty-state class=\"nxe-chart__empty\" [heading]=\"messages.t('charts.empty')\" [headingLevel]=\"4\" />\r\n } @else {\r\n @if (showTableToggle()) {\r\n <div class=\"nxe-chart__toolbar\">\r\n <button\r\n type=\"button\"\r\n class=\"nxe-chart__toggle\"\r\n [attr.aria-controls]=\"uid + '-table'\"\r\n (click)=\"toggleTable()\">\r\n {{ messages.t(tableShown() ? 'charts.viewAsChart' : 'charts.viewAsTable') }}\r\n </button>\r\n </div>\r\n }\r\n <div class=\"nxe-chart__stage\" [hidden]=\"tableShown()\">\r\n <svg\r\n class=\"nxe-chart__svg\"\r\n role=\"img\"\r\n [attr.aria-label]=\"summary()\"\r\n [attr.width]=\"svgWidth()\"\r\n [attr.height]=\"svgHeight()\"\r\n [attr.viewBox]=\"'0 0 ' + svgWidth() + ' ' + svgHeight()\">\r\n @for (g of yGrid(); track $index) {\r\n <line\r\n class=\"nxe-chart__grid\"\r\n [class.nxe-chart__grid--zero]=\"g.zero\"\r\n [attr.x1]=\"left()\"\r\n [attr.x2]=\"plotRight()\"\r\n [attr.y1]=\"g.y\"\r\n [attr.y2]=\"g.y\" />\r\n <text class=\"nxe-chart__tick\" text-anchor=\"end\" [attr.x]=\"left() - 8\" [attr.y]=\"g.y\" dy=\"0.32em\">{{ g.label }}</text>\r\n }\r\n @for (t of xTicks(); track $index) {\r\n <text class=\"nxe-chart__tick\" text-anchor=\"middle\" [attr.x]=\"t.x\" [attr.y]=\"plotBottom() + 18\">{{ t.text }}</text>\r\n }\r\n @for (l of lines(); track l.key) {\r\n @if (l.area) {\r\n <path\r\n class=\"nxe-chart__area\"\r\n [class.nxe-chart__area--stacked]=\"isStacked()\"\r\n [attr.d]=\"l.area\"\r\n [style.fill]=\"l.color\" />\r\n }\r\n }\r\n @for (l of lines(); track l.key) {\r\n <path class=\"nxe-chart__line\" [attr.d]=\"l.line\" [style.stroke]=\"l.color\" />\r\n @for (m of l.markers; track m.index) {\r\n @if (showAllMarkers() || l.markers.length === 1 || m.index === activeIndex()) {\r\n <circle class=\"nxe-chart__marker\" [attr.cx]=\"m.x\" [attr.cy]=\"m.y\" r=\"4\" [style.fill]=\"l.color\" />\r\n }\r\n }\r\n }\r\n @if (tooltip(); as tip) {\r\n <line class=\"nxe-chart__crosshair\" [attr.x1]=\"tip.x\" [attr.x2]=\"tip.x\" [attr.y1]=\"marginTop\" [attr.y2]=\"plotBottom()\" />\r\n @for (l of lines(); track l.key) {\r\n @for (m of l.markers; track m.index) {\r\n @if (m.index === tip.index) {\r\n <circle class=\"nxe-chart__marker nxe-chart__marker--active\" [attr.cx]=\"m.x\" [attr.cy]=\"m.y\" r=\"5\" [style.fill]=\"l.color\" />\r\n }\r\n }\r\n }\r\n }\r\n </svg>\r\n <div\r\n class=\"nxe-chart__focus\"\r\n role=\"group\"\r\n tabindex=\"0\"\r\n [attr.aria-label]=\"messages.t('charts.navigate')\"\r\n (pointermove)=\"onPointerMove($event)\"\r\n (pointerleave)=\"onPointerLeave()\"\r\n (focus)=\"onFocus()\"\r\n (blur)=\"onBlur()\"\r\n (keydown)=\"onKeydown($event)\"></div>\r\n @if (tooltip(); as tip) {\r\n <div\r\n class=\"nxe-chart__tooltip\"\r\n role=\"tooltip\"\r\n [class.nxe-chart__tooltip--start]=\"tip.align === 'start'\"\r\n [class.nxe-chart__tooltip--end]=\"tip.align === 'end'\"\r\n [style.left.px]=\"tip.x\"\r\n [style.top.px]=\"marginTop\">\r\n <div class=\"nxe-chart__tooltip-title\">{{ tip.title }}</div>\r\n @for (r of tip.rows; track r.key) {\r\n <div class=\"nxe-chart__tooltip-row\">\r\n <span class=\"nxe-chart__swatch\" aria-hidden=\"true\" [style.background]=\"r.color\"></span>\r\n <span class=\"nxe-chart__tooltip-name\">{{ r.name }}</span>\r\n <span class=\"nxe-chart__tooltip-value\">{{ r.value }}</span>\r\n </div>\r\n }\r\n </div>\r\n }\r\n <div class=\"nxe-chart__sr\" aria-live=\"polite\" aria-atomic=\"true\">{{ announcement() }}</div>\r\n </div>\r\n <div [id]=\"uid + '-table'\" [class.nxe-chart__sr]=\"!tableShown()\">\r\n <nxe-chart-data-table\r\n [caption]=\"heading()\"\r\n [corner]=\"messages.t('charts.table.category')\"\r\n [columns]=\"tableColumns()\"\r\n [rows]=\"tableRows()\" />\r\n </div>\r\n @if (legendShown()) {\r\n <nxe-chart-legend [items]=\"legendItems()\" [label]=\"messages.t('charts.legend')\" (toggled)=\"toggleSeries($event)\" />\r\n }\r\n }\r\n</figure>\r\n", styles: [".nxe-chart{display:block;width:100%;min-width:0;color:var(--nxe-ui-text);font-family:var(--nxe-ui-font-family)}.nxe-chart__figure{display:flex;flex-direction:column;gap:var(--nxe-ui-space-2, 8px);margin:0}.nxe-chart__heading{color:var(--nxe-ui-text);font-size:var(--nxe-ui-font-size-lg, 16px);font-weight:var(--nxe-ui-font-weight-semibold, 600);line-height:var(--nxe-ui-line-height-lg, 24px)}.nxe-chart__toolbar{display:flex;justify-content:flex-end}.nxe-chart__toggle{margin:0;padding:2px 8px;border:1px solid var(--nxe-ui-border);border-radius:var(--nxe-ui-radius, 4px);background:var(--nxe-ui-surface);color:var(--nxe-ui-text);font:inherit;font-size:var(--nxe-ui-font-size-xs, 12px);cursor:pointer}.nxe-chart__toggle:hover{background:var(--nxe-ui-hover-bg)}.nxe-chart__toggle:focus-visible{outline:2px solid var(--nxe-ui-focus-ring-color);outline-offset:1px}.nxe-chart__stage{position:relative;max-width:100%;overflow:visible}.nxe-chart__stage[hidden]{display:none}.nxe-chart__svg{display:block;max-width:100%;overflow:visible}.nxe-chart__focus{position:absolute;inset:0;border-radius:var(--nxe-ui-radius, 4px);outline:none;touch-action:pan-y}.nxe-chart__focus:focus-visible{outline:2px solid var(--nxe-ui-focus-ring-color);outline-offset:2px}.nxe-chart__grid{stroke:var(--nxe-ui-neutral-150);stroke-width:1;shape-rendering:crispEdges}.nxe-chart__grid--zero{stroke:var(--nxe-ui-neutral-240)}.nxe-chart__tick{fill:var(--nxe-ui-neutral-600);font-size:var(--nxe-ui-font-size-xs, 12px);font-variant-numeric:tabular-nums}.nxe-chart__line{fill:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round}.nxe-chart__area{fill-opacity:.16;stroke:none}.nxe-chart__area--stacked{fill-opacity:.9;stroke:var(--nxe-ui-surface);stroke-width:2;stroke-linejoin:round}.nxe-chart__marker{stroke:var(--nxe-ui-surface);stroke-width:2}.nxe-chart__crosshair{stroke:var(--nxe-ui-neutral-450);stroke-width:1;stroke-dasharray:3 3;pointer-events:none}.nxe-chart__tooltip{position:absolute;z-index:2;min-width:120px;max-width:260px;padding:6px 10px;border:1px solid var(--nxe-ui-border);border-radius:var(--nxe-ui-radius-md, 6px);background:var(--nxe-ui-surface);box-shadow:var(--nxe-ui-shadow-sm);color:var(--nxe-ui-text);font-size:var(--nxe-ui-font-size-xs, 12px);line-height:var(--nxe-ui-line-height-xs, 16px);pointer-events:none;transform:translate(-50%)}.nxe-chart__tooltip.nxe-chart__tooltip--start{transform:translate(12px)}.nxe-chart__tooltip.nxe-chart__tooltip--end{transform:translate(calc(-100% - 12px))}.nxe-chart__tooltip-title{margin-bottom:2px;font-weight:var(--nxe-ui-font-weight-semibold, 600)}.nxe-chart__tooltip-row{display:flex;align-items:center;gap:var(--nxe-ui-space-2, 8px)}.nxe-chart__tooltip-name{flex:1;min-width:0;overflow:hidden;color:var(--nxe-ui-neutral-700);text-overflow:ellipsis;white-space:nowrap}.nxe-chart__tooltip-value{font-variant-numeric:tabular-nums;font-weight:var(--nxe-ui-font-weight-medium, 500)}.nxe-chart__swatch{flex:none;width:10px;height:10px;border-radius:2px}.nxe-chart__sr{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;border:0;clip-path:inset(50%);white-space:nowrap}\n"] }]
|
|
809
|
+
}], ctorParameters: () => [], propDecorators: { series: [{ type: i0.Input, args: [{ isSignal: true, alias: "series", required: false }] }], categories: [{ type: i0.Input, args: [{ isSignal: true, alias: "categories", required: false }] }], xType: [{ type: i0.Input, args: [{ isSignal: true, alias: "xType", required: false }] }], xFormat: [{ type: i0.Input, args: [{ isSignal: true, alias: "xFormat", required: false }] }], valueFormat: [{ type: i0.Input, args: [{ isSignal: true, alias: "valueFormat", required: false }] }], axisFormat: [{ type: i0.Input, args: [{ isSignal: true, alias: "axisFormat", required: false }] }], yTicks: [{ type: i0.Input, args: [{ isSignal: true, alias: "yTicks", required: false }] }], includeZero: [{ type: i0.Input, args: [{ isSignal: true, alias: "includeZero", required: false }] }], area: [{ type: i0.Input, args: [{ isSignal: true, alias: "area", required: false }] }], stacked: [{ type: i0.Input, args: [{ isSignal: true, alias: "stacked", required: false }] }], heading: [{ type: i0.Input, args: [{ isSignal: true, alias: "heading", required: false }] }], description: [{ type: i0.Input, args: [{ isSignal: true, alias: "description", required: false }] }], height: [{ type: i0.Input, args: [{ isSignal: true, alias: "height", required: false }] }], width: [{ type: i0.Input, args: [{ isSignal: true, alias: "width", required: false }] }], loading: [{ type: i0.Input, args: [{ isSignal: true, alias: "loading", required: false }] }], showLegend: [{ type: i0.Input, args: [{ isSignal: true, alias: "showLegend", required: false }] }], showTableToggle: [{ type: i0.Input, args: [{ isSignal: true, alias: "showTableToggle", required: false }] }], hidden: [{ type: i0.Input, args: [{ isSignal: true, alias: "hidden", required: false }] }, { type: i0.Output, args: ["hiddenChange"] }] } });
|
|
810
|
+
|
|
811
|
+
/**
|
|
812
|
+
* `nxe-area-chart` is `nxe-line-chart` with the area under each line filled. It takes every input of
|
|
813
|
+
* the line chart; add `stacked` to stack the series (a missing value then counts as 0).
|
|
814
|
+
*
|
|
815
|
+
* ```html
|
|
816
|
+
* <nxe-area-chart heading="Spend by month" [categories]="months" [series]="series" stacked />
|
|
817
|
+
* ```
|
|
818
|
+
*/
|
|
819
|
+
class NxeAreaChartComponent extends NxeLineChartComponent {
|
|
820
|
+
areaForced = true;
|
|
821
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: NxeAreaChartComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
|
|
822
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.5", type: NxeAreaChartComponent, isStandalone: true, selector: "nxe-area-chart", host: { classAttribute: "nxe-chart nxe-area-chart" }, usesInheritance: true, ngImport: i0, template: "<figure class=\"nxe-chart__figure\" [attr.aria-busy]=\"loading() ? 'true' : null\">\r\n @if (heading()) {\r\n <figcaption class=\"nxe-chart__heading\">{{ heading() }}</figcaption>\r\n }\r\n @if (loading()) {\r\n <nxe-skeleton variant=\"rect\" width=\"100%\" [height]=\"svgHeight()\" />\r\n } @else if (empty()) {\r\n <nxe-empty-state class=\"nxe-chart__empty\" [heading]=\"messages.t('charts.empty')\" [headingLevel]=\"4\" />\r\n } @else {\r\n @if (showTableToggle()) {\r\n <div class=\"nxe-chart__toolbar\">\r\n <button\r\n type=\"button\"\r\n class=\"nxe-chart__toggle\"\r\n [attr.aria-controls]=\"uid + '-table'\"\r\n (click)=\"toggleTable()\">\r\n {{ messages.t(tableShown() ? 'charts.viewAsChart' : 'charts.viewAsTable') }}\r\n </button>\r\n </div>\r\n }\r\n <div class=\"nxe-chart__stage\" [hidden]=\"tableShown()\">\r\n <svg\r\n class=\"nxe-chart__svg\"\r\n role=\"img\"\r\n [attr.aria-label]=\"summary()\"\r\n [attr.width]=\"svgWidth()\"\r\n [attr.height]=\"svgHeight()\"\r\n [attr.viewBox]=\"'0 0 ' + svgWidth() + ' ' + svgHeight()\">\r\n @for (g of yGrid(); track $index) {\r\n <line\r\n class=\"nxe-chart__grid\"\r\n [class.nxe-chart__grid--zero]=\"g.zero\"\r\n [attr.x1]=\"left()\"\r\n [attr.x2]=\"plotRight()\"\r\n [attr.y1]=\"g.y\"\r\n [attr.y2]=\"g.y\" />\r\n <text class=\"nxe-chart__tick\" text-anchor=\"end\" [attr.x]=\"left() - 8\" [attr.y]=\"g.y\" dy=\"0.32em\">{{ g.label }}</text>\r\n }\r\n @for (t of xTicks(); track $index) {\r\n <text class=\"nxe-chart__tick\" text-anchor=\"middle\" [attr.x]=\"t.x\" [attr.y]=\"plotBottom() + 18\">{{ t.text }}</text>\r\n }\r\n @for (l of lines(); track l.key) {\r\n @if (l.area) {\r\n <path\r\n class=\"nxe-chart__area\"\r\n [class.nxe-chart__area--stacked]=\"isStacked()\"\r\n [attr.d]=\"l.area\"\r\n [style.fill]=\"l.color\" />\r\n }\r\n }\r\n @for (l of lines(); track l.key) {\r\n <path class=\"nxe-chart__line\" [attr.d]=\"l.line\" [style.stroke]=\"l.color\" />\r\n @for (m of l.markers; track m.index) {\r\n @if (showAllMarkers() || l.markers.length === 1 || m.index === activeIndex()) {\r\n <circle class=\"nxe-chart__marker\" [attr.cx]=\"m.x\" [attr.cy]=\"m.y\" r=\"4\" [style.fill]=\"l.color\" />\r\n }\r\n }\r\n }\r\n @if (tooltip(); as tip) {\r\n <line class=\"nxe-chart__crosshair\" [attr.x1]=\"tip.x\" [attr.x2]=\"tip.x\" [attr.y1]=\"marginTop\" [attr.y2]=\"plotBottom()\" />\r\n @for (l of lines(); track l.key) {\r\n @for (m of l.markers; track m.index) {\r\n @if (m.index === tip.index) {\r\n <circle class=\"nxe-chart__marker nxe-chart__marker--active\" [attr.cx]=\"m.x\" [attr.cy]=\"m.y\" r=\"5\" [style.fill]=\"l.color\" />\r\n }\r\n }\r\n }\r\n }\r\n </svg>\r\n <div\r\n class=\"nxe-chart__focus\"\r\n role=\"group\"\r\n tabindex=\"0\"\r\n [attr.aria-label]=\"messages.t('charts.navigate')\"\r\n (pointermove)=\"onPointerMove($event)\"\r\n (pointerleave)=\"onPointerLeave()\"\r\n (focus)=\"onFocus()\"\r\n (blur)=\"onBlur()\"\r\n (keydown)=\"onKeydown($event)\"></div>\r\n @if (tooltip(); as tip) {\r\n <div\r\n class=\"nxe-chart__tooltip\"\r\n role=\"tooltip\"\r\n [class.nxe-chart__tooltip--start]=\"tip.align === 'start'\"\r\n [class.nxe-chart__tooltip--end]=\"tip.align === 'end'\"\r\n [style.left.px]=\"tip.x\"\r\n [style.top.px]=\"marginTop\">\r\n <div class=\"nxe-chart__tooltip-title\">{{ tip.title }}</div>\r\n @for (r of tip.rows; track r.key) {\r\n <div class=\"nxe-chart__tooltip-row\">\r\n <span class=\"nxe-chart__swatch\" aria-hidden=\"true\" [style.background]=\"r.color\"></span>\r\n <span class=\"nxe-chart__tooltip-name\">{{ r.name }}</span>\r\n <span class=\"nxe-chart__tooltip-value\">{{ r.value }}</span>\r\n </div>\r\n }\r\n </div>\r\n }\r\n <div class=\"nxe-chart__sr\" aria-live=\"polite\" aria-atomic=\"true\">{{ announcement() }}</div>\r\n </div>\r\n <div [id]=\"uid + '-table'\" [class.nxe-chart__sr]=\"!tableShown()\">\r\n <nxe-chart-data-table\r\n [caption]=\"heading()\"\r\n [corner]=\"messages.t('charts.table.category')\"\r\n [columns]=\"tableColumns()\"\r\n [rows]=\"tableRows()\" />\r\n </div>\r\n @if (legendShown()) {\r\n <nxe-chart-legend [items]=\"legendItems()\" [label]=\"messages.t('charts.legend')\" (toggled)=\"toggleSeries($event)\" />\r\n }\r\n }\r\n</figure>\r\n", styles: [".nxe-chart{display:block;width:100%;min-width:0;color:var(--nxe-ui-text);font-family:var(--nxe-ui-font-family)}.nxe-chart__figure{display:flex;flex-direction:column;gap:var(--nxe-ui-space-2, 8px);margin:0}.nxe-chart__heading{color:var(--nxe-ui-text);font-size:var(--nxe-ui-font-size-lg, 16px);font-weight:var(--nxe-ui-font-weight-semibold, 600);line-height:var(--nxe-ui-line-height-lg, 24px)}.nxe-chart__toolbar{display:flex;justify-content:flex-end}.nxe-chart__toggle{margin:0;padding:2px 8px;border:1px solid var(--nxe-ui-border);border-radius:var(--nxe-ui-radius, 4px);background:var(--nxe-ui-surface);color:var(--nxe-ui-text);font:inherit;font-size:var(--nxe-ui-font-size-xs, 12px);cursor:pointer}.nxe-chart__toggle:hover{background:var(--nxe-ui-hover-bg)}.nxe-chart__toggle:focus-visible{outline:2px solid var(--nxe-ui-focus-ring-color);outline-offset:1px}.nxe-chart__stage{position:relative;max-width:100%;overflow:visible}.nxe-chart__stage[hidden]{display:none}.nxe-chart__svg{display:block;max-width:100%;overflow:visible}.nxe-chart__focus{position:absolute;inset:0;border-radius:var(--nxe-ui-radius, 4px);outline:none;touch-action:pan-y}.nxe-chart__focus:focus-visible{outline:2px solid var(--nxe-ui-focus-ring-color);outline-offset:2px}.nxe-chart__grid{stroke:var(--nxe-ui-neutral-150);stroke-width:1;shape-rendering:crispEdges}.nxe-chart__grid--zero{stroke:var(--nxe-ui-neutral-240)}.nxe-chart__tick{fill:var(--nxe-ui-neutral-600);font-size:var(--nxe-ui-font-size-xs, 12px);font-variant-numeric:tabular-nums}.nxe-chart__line{fill:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round}.nxe-chart__area{fill-opacity:.16;stroke:none}.nxe-chart__area--stacked{fill-opacity:.9;stroke:var(--nxe-ui-surface);stroke-width:2;stroke-linejoin:round}.nxe-chart__marker{stroke:var(--nxe-ui-surface);stroke-width:2}.nxe-chart__crosshair{stroke:var(--nxe-ui-neutral-450);stroke-width:1;stroke-dasharray:3 3;pointer-events:none}.nxe-chart__tooltip{position:absolute;z-index:2;min-width:120px;max-width:260px;padding:6px 10px;border:1px solid var(--nxe-ui-border);border-radius:var(--nxe-ui-radius-md, 6px);background:var(--nxe-ui-surface);box-shadow:var(--nxe-ui-shadow-sm);color:var(--nxe-ui-text);font-size:var(--nxe-ui-font-size-xs, 12px);line-height:var(--nxe-ui-line-height-xs, 16px);pointer-events:none;transform:translate(-50%)}.nxe-chart__tooltip.nxe-chart__tooltip--start{transform:translate(12px)}.nxe-chart__tooltip.nxe-chart__tooltip--end{transform:translate(calc(-100% - 12px))}.nxe-chart__tooltip-title{margin-bottom:2px;font-weight:var(--nxe-ui-font-weight-semibold, 600)}.nxe-chart__tooltip-row{display:flex;align-items:center;gap:var(--nxe-ui-space-2, 8px)}.nxe-chart__tooltip-name{flex:1;min-width:0;overflow:hidden;color:var(--nxe-ui-neutral-700);text-overflow:ellipsis;white-space:nowrap}.nxe-chart__tooltip-value{font-variant-numeric:tabular-nums;font-weight:var(--nxe-ui-font-weight-medium, 500)}.nxe-chart__swatch{flex:none;width:10px;height:10px;border-radius:2px}.nxe-chart__sr{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;border:0;clip-path:inset(50%);white-space:nowrap}\n"], dependencies: [{ kind: "component", type: NxeChartLegendComponent, selector: "nxe-chart-legend", inputs: ["items", "label", "toggleable"], outputs: ["toggled"] }, { kind: "component", type: NxeChartDataTableComponent, selector: "nxe-chart-data-table", inputs: ["caption", "corner", "columns", "rows"] }, { kind: "component", type: NxeSkeletonComponent, selector: "nxe-skeleton", inputs: ["variant", "width", "height", "lines", "animated"] }, { kind: "component", type: NxeEmptyStateComponent, selector: "nxe-empty-state", inputs: ["heading", "description", "headingLevel"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
|
|
823
|
+
}
|
|
824
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: NxeAreaChartComponent, decorators: [{
|
|
825
|
+
type: Component,
|
|
826
|
+
args: [{ selector: 'nxe-area-chart', imports: [NxeChartLegendComponent, NxeChartDataTableComponent, NxeSkeletonComponent, NxeEmptyStateComponent], host: { class: 'nxe-chart nxe-area-chart' }, changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, template: "<figure class=\"nxe-chart__figure\" [attr.aria-busy]=\"loading() ? 'true' : null\">\r\n @if (heading()) {\r\n <figcaption class=\"nxe-chart__heading\">{{ heading() }}</figcaption>\r\n }\r\n @if (loading()) {\r\n <nxe-skeleton variant=\"rect\" width=\"100%\" [height]=\"svgHeight()\" />\r\n } @else if (empty()) {\r\n <nxe-empty-state class=\"nxe-chart__empty\" [heading]=\"messages.t('charts.empty')\" [headingLevel]=\"4\" />\r\n } @else {\r\n @if (showTableToggle()) {\r\n <div class=\"nxe-chart__toolbar\">\r\n <button\r\n type=\"button\"\r\n class=\"nxe-chart__toggle\"\r\n [attr.aria-controls]=\"uid + '-table'\"\r\n (click)=\"toggleTable()\">\r\n {{ messages.t(tableShown() ? 'charts.viewAsChart' : 'charts.viewAsTable') }}\r\n </button>\r\n </div>\r\n }\r\n <div class=\"nxe-chart__stage\" [hidden]=\"tableShown()\">\r\n <svg\r\n class=\"nxe-chart__svg\"\r\n role=\"img\"\r\n [attr.aria-label]=\"summary()\"\r\n [attr.width]=\"svgWidth()\"\r\n [attr.height]=\"svgHeight()\"\r\n [attr.viewBox]=\"'0 0 ' + svgWidth() + ' ' + svgHeight()\">\r\n @for (g of yGrid(); track $index) {\r\n <line\r\n class=\"nxe-chart__grid\"\r\n [class.nxe-chart__grid--zero]=\"g.zero\"\r\n [attr.x1]=\"left()\"\r\n [attr.x2]=\"plotRight()\"\r\n [attr.y1]=\"g.y\"\r\n [attr.y2]=\"g.y\" />\r\n <text class=\"nxe-chart__tick\" text-anchor=\"end\" [attr.x]=\"left() - 8\" [attr.y]=\"g.y\" dy=\"0.32em\">{{ g.label }}</text>\r\n }\r\n @for (t of xTicks(); track $index) {\r\n <text class=\"nxe-chart__tick\" text-anchor=\"middle\" [attr.x]=\"t.x\" [attr.y]=\"plotBottom() + 18\">{{ t.text }}</text>\r\n }\r\n @for (l of lines(); track l.key) {\r\n @if (l.area) {\r\n <path\r\n class=\"nxe-chart__area\"\r\n [class.nxe-chart__area--stacked]=\"isStacked()\"\r\n [attr.d]=\"l.area\"\r\n [style.fill]=\"l.color\" />\r\n }\r\n }\r\n @for (l of lines(); track l.key) {\r\n <path class=\"nxe-chart__line\" [attr.d]=\"l.line\" [style.stroke]=\"l.color\" />\r\n @for (m of l.markers; track m.index) {\r\n @if (showAllMarkers() || l.markers.length === 1 || m.index === activeIndex()) {\r\n <circle class=\"nxe-chart__marker\" [attr.cx]=\"m.x\" [attr.cy]=\"m.y\" r=\"4\" [style.fill]=\"l.color\" />\r\n }\r\n }\r\n }\r\n @if (tooltip(); as tip) {\r\n <line class=\"nxe-chart__crosshair\" [attr.x1]=\"tip.x\" [attr.x2]=\"tip.x\" [attr.y1]=\"marginTop\" [attr.y2]=\"plotBottom()\" />\r\n @for (l of lines(); track l.key) {\r\n @for (m of l.markers; track m.index) {\r\n @if (m.index === tip.index) {\r\n <circle class=\"nxe-chart__marker nxe-chart__marker--active\" [attr.cx]=\"m.x\" [attr.cy]=\"m.y\" r=\"5\" [style.fill]=\"l.color\" />\r\n }\r\n }\r\n }\r\n }\r\n </svg>\r\n <div\r\n class=\"nxe-chart__focus\"\r\n role=\"group\"\r\n tabindex=\"0\"\r\n [attr.aria-label]=\"messages.t('charts.navigate')\"\r\n (pointermove)=\"onPointerMove($event)\"\r\n (pointerleave)=\"onPointerLeave()\"\r\n (focus)=\"onFocus()\"\r\n (blur)=\"onBlur()\"\r\n (keydown)=\"onKeydown($event)\"></div>\r\n @if (tooltip(); as tip) {\r\n <div\r\n class=\"nxe-chart__tooltip\"\r\n role=\"tooltip\"\r\n [class.nxe-chart__tooltip--start]=\"tip.align === 'start'\"\r\n [class.nxe-chart__tooltip--end]=\"tip.align === 'end'\"\r\n [style.left.px]=\"tip.x\"\r\n [style.top.px]=\"marginTop\">\r\n <div class=\"nxe-chart__tooltip-title\">{{ tip.title }}</div>\r\n @for (r of tip.rows; track r.key) {\r\n <div class=\"nxe-chart__tooltip-row\">\r\n <span class=\"nxe-chart__swatch\" aria-hidden=\"true\" [style.background]=\"r.color\"></span>\r\n <span class=\"nxe-chart__tooltip-name\">{{ r.name }}</span>\r\n <span class=\"nxe-chart__tooltip-value\">{{ r.value }}</span>\r\n </div>\r\n }\r\n </div>\r\n }\r\n <div class=\"nxe-chart__sr\" aria-live=\"polite\" aria-atomic=\"true\">{{ announcement() }}</div>\r\n </div>\r\n <div [id]=\"uid + '-table'\" [class.nxe-chart__sr]=\"!tableShown()\">\r\n <nxe-chart-data-table\r\n [caption]=\"heading()\"\r\n [corner]=\"messages.t('charts.table.category')\"\r\n [columns]=\"tableColumns()\"\r\n [rows]=\"tableRows()\" />\r\n </div>\r\n @if (legendShown()) {\r\n <nxe-chart-legend [items]=\"legendItems()\" [label]=\"messages.t('charts.legend')\" (toggled)=\"toggleSeries($event)\" />\r\n }\r\n }\r\n</figure>\r\n", styles: [".nxe-chart{display:block;width:100%;min-width:0;color:var(--nxe-ui-text);font-family:var(--nxe-ui-font-family)}.nxe-chart__figure{display:flex;flex-direction:column;gap:var(--nxe-ui-space-2, 8px);margin:0}.nxe-chart__heading{color:var(--nxe-ui-text);font-size:var(--nxe-ui-font-size-lg, 16px);font-weight:var(--nxe-ui-font-weight-semibold, 600);line-height:var(--nxe-ui-line-height-lg, 24px)}.nxe-chart__toolbar{display:flex;justify-content:flex-end}.nxe-chart__toggle{margin:0;padding:2px 8px;border:1px solid var(--nxe-ui-border);border-radius:var(--nxe-ui-radius, 4px);background:var(--nxe-ui-surface);color:var(--nxe-ui-text);font:inherit;font-size:var(--nxe-ui-font-size-xs, 12px);cursor:pointer}.nxe-chart__toggle:hover{background:var(--nxe-ui-hover-bg)}.nxe-chart__toggle:focus-visible{outline:2px solid var(--nxe-ui-focus-ring-color);outline-offset:1px}.nxe-chart__stage{position:relative;max-width:100%;overflow:visible}.nxe-chart__stage[hidden]{display:none}.nxe-chart__svg{display:block;max-width:100%;overflow:visible}.nxe-chart__focus{position:absolute;inset:0;border-radius:var(--nxe-ui-radius, 4px);outline:none;touch-action:pan-y}.nxe-chart__focus:focus-visible{outline:2px solid var(--nxe-ui-focus-ring-color);outline-offset:2px}.nxe-chart__grid{stroke:var(--nxe-ui-neutral-150);stroke-width:1;shape-rendering:crispEdges}.nxe-chart__grid--zero{stroke:var(--nxe-ui-neutral-240)}.nxe-chart__tick{fill:var(--nxe-ui-neutral-600);font-size:var(--nxe-ui-font-size-xs, 12px);font-variant-numeric:tabular-nums}.nxe-chart__line{fill:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round}.nxe-chart__area{fill-opacity:.16;stroke:none}.nxe-chart__area--stacked{fill-opacity:.9;stroke:var(--nxe-ui-surface);stroke-width:2;stroke-linejoin:round}.nxe-chart__marker{stroke:var(--nxe-ui-surface);stroke-width:2}.nxe-chart__crosshair{stroke:var(--nxe-ui-neutral-450);stroke-width:1;stroke-dasharray:3 3;pointer-events:none}.nxe-chart__tooltip{position:absolute;z-index:2;min-width:120px;max-width:260px;padding:6px 10px;border:1px solid var(--nxe-ui-border);border-radius:var(--nxe-ui-radius-md, 6px);background:var(--nxe-ui-surface);box-shadow:var(--nxe-ui-shadow-sm);color:var(--nxe-ui-text);font-size:var(--nxe-ui-font-size-xs, 12px);line-height:var(--nxe-ui-line-height-xs, 16px);pointer-events:none;transform:translate(-50%)}.nxe-chart__tooltip.nxe-chart__tooltip--start{transform:translate(12px)}.nxe-chart__tooltip.nxe-chart__tooltip--end{transform:translate(calc(-100% - 12px))}.nxe-chart__tooltip-title{margin-bottom:2px;font-weight:var(--nxe-ui-font-weight-semibold, 600)}.nxe-chart__tooltip-row{display:flex;align-items:center;gap:var(--nxe-ui-space-2, 8px)}.nxe-chart__tooltip-name{flex:1;min-width:0;overflow:hidden;color:var(--nxe-ui-neutral-700);text-overflow:ellipsis;white-space:nowrap}.nxe-chart__tooltip-value{font-variant-numeric:tabular-nums;font-weight:var(--nxe-ui-font-weight-medium, 500)}.nxe-chart__swatch{flex:none;width:10px;height:10px;border-radius:2px}.nxe-chart__sr{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;border:0;clip-path:inset(50%);white-space:nowrap}\n"] }]
|
|
827
|
+
}] });
|
|
828
|
+
|
|
829
|
+
const INNER_RATIO = 0.64;
|
|
830
|
+
const GAP = 2;
|
|
831
|
+
/**
|
|
832
|
+
* `nxe-donut-chart` shows how a total divides into parts.
|
|
833
|
+
*
|
|
834
|
+
* ```html
|
|
835
|
+
* <nxe-donut-chart
|
|
836
|
+
* heading="Spend by supplier"
|
|
837
|
+
* [data]="[{ label: 'PT Maju', value: 420 }, { label: 'CV Sinar', value: 180 }]"
|
|
838
|
+
* [maxSegments]="6" />
|
|
839
|
+
* ```
|
|
840
|
+
*
|
|
841
|
+
* Segments take the `--nxe-ui-chart-1..8` colours in order (or a `color` of their own, e.g. a status
|
|
842
|
+
* family `var(--nxe-ui-status-success-fg)`), separated by a 2px surface gap. When there are more than
|
|
843
|
+
* `maxSegments`, the smallest fold into one "Other" segment in a neutral colour. The centre shows
|
|
844
|
+
* the total of the visible segments. The legend lists each segment with its value and share and
|
|
845
|
+
* toggles it (keyboard accessible); hover or arrow-key focus on the ring shows a tooltip. It
|
|
846
|
+
* announces itself as an image with a summary, keeps a visually hidden data table (shown by "View
|
|
847
|
+
* as table"), and uses `nxe-skeleton` and `nxe-empty-state` for loading and no data.
|
|
848
|
+
*
|
|
849
|
+
* A donut is for a handful of parts of one whole: past six or so, use a bar chart.
|
|
850
|
+
*/
|
|
851
|
+
class NxeDonutChartComponent {
|
|
852
|
+
host = inject(ElementRef).nativeElement;
|
|
853
|
+
messages = nxeChartMessages();
|
|
854
|
+
formatter = nxeChartNumberFormatter();
|
|
855
|
+
/** The parts. Zero, negative and non-finite values are left out. */
|
|
856
|
+
data = input([], /* @ts-ignore */
|
|
857
|
+
...(ngDevMode ? [{ debugName: "data" }] : /* istanbul ignore next */ []));
|
|
858
|
+
/** Most segments shown; the rest fold into "Other". At most 9 (eight colours and Other). */
|
|
859
|
+
maxSegments = input(6, { ...(ngDevMode ? { debugName: "maxSegments" } : /* istanbul ignore next */ {}), transform: (v) => Math.min(9, Math.max(2, Math.floor(numberAttribute(v, 6)))) });
|
|
860
|
+
/** Number pattern for values. Default `#,##0.##`. */
|
|
861
|
+
valueFormat = input('#,##0.##', /* @ts-ignore */
|
|
862
|
+
...(ngDevMode ? [{ debugName: "valueFormat" }] : /* istanbul ignore next */ []));
|
|
863
|
+
/** Decimal places of the percentages. */
|
|
864
|
+
percentDecimals = input(1, { ...(ngDevMode ? { debugName: "percentDecimals" } : /* istanbul ignore next */ {}), transform: (v) => Math.min(4, Math.max(0, Math.floor(numberAttribute(v, 1)))) });
|
|
865
|
+
/** Visible title above the chart; also the start of its accessible summary. */
|
|
866
|
+
heading = input('', /* @ts-ignore */
|
|
867
|
+
...(ngDevMode ? [{ debugName: "heading" }] : /* istanbul ignore next */ []));
|
|
868
|
+
/** Extra sentence for the accessible summary. */
|
|
869
|
+
description = input('', /* @ts-ignore */
|
|
870
|
+
...(ngDevMode ? [{ debugName: "description" }] : /* istanbul ignore next */ []));
|
|
871
|
+
/** Diameter of the ring in px; it shrinks to fit a narrower container. */
|
|
872
|
+
size = input(220, { ...(ngDevMode ? { debugName: "size" } : /* istanbul ignore next */ {}), transform: (v) => Math.max(96, numberAttribute(v, 220)) });
|
|
873
|
+
/** Text under the total in the centre. Default: the message "Total". */
|
|
874
|
+
totalLabel = input(undefined, /* @ts-ignore */
|
|
875
|
+
...(ngDevMode ? [{ debugName: "totalLabel" }] : /* istanbul ignore next */ []));
|
|
876
|
+
/** Show the total in the centre. */
|
|
877
|
+
showTotal = input(true, { ...(ngDevMode ? { debugName: "showTotal" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
878
|
+
/** Show the legend (value and share per segment, and the toggles). */
|
|
879
|
+
showLegend = input(true, { ...(ngDevMode ? { debugName: "showLegend" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
880
|
+
/** Show the skeleton instead of the ring. */
|
|
881
|
+
loading = input(false, { ...(ngDevMode ? { debugName: "loading" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
882
|
+
/** Offer the "View as table" button. */
|
|
883
|
+
showTableToggle = input(true, { ...(ngDevMode ? { debugName: "showTableToggle" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
884
|
+
/** Keys of the segments the legend has hidden; two-way. */
|
|
885
|
+
hidden = model([], /* @ts-ignore */
|
|
886
|
+
...(ngDevMode ? [{ debugName: "hidden" }] : /* istanbul ignore next */ []));
|
|
887
|
+
uid = nxeChartId('nxe-chart');
|
|
888
|
+
measured = signal(0, /* @ts-ignore */
|
|
889
|
+
...(ngDevMode ? [{ debugName: "measured" }] : /* istanbul ignore next */ []));
|
|
890
|
+
hoverIndex = signal(null, /* @ts-ignore */
|
|
891
|
+
...(ngDevMode ? [{ debugName: "hoverIndex" }] : /* istanbul ignore next */ []));
|
|
892
|
+
keyboardIndex = signal(null, /* @ts-ignore */
|
|
893
|
+
...(ngDevMode ? [{ debugName: "keyboardIndex" }] : /* istanbul ignore next */ []));
|
|
894
|
+
pointer = signal(null, /* @ts-ignore */
|
|
895
|
+
...(ngDevMode ? [{ debugName: "pointer" }] : /* istanbul ignore next */ []));
|
|
896
|
+
tableShown = signal(false, /* @ts-ignore */
|
|
897
|
+
...(ngDevMode ? [{ debugName: "tableShown" }] : /* istanbul ignore next */ []));
|
|
898
|
+
constructor() {
|
|
899
|
+
nxeObserveWidth(this.host, (w) => this.measured.set(w));
|
|
900
|
+
}
|
|
901
|
+
// ---- data ------------------------------------------------------------------------------
|
|
902
|
+
/** Kept segments plus the "Other" remainder; colours follow position, so hiding repaints nothing. */
|
|
903
|
+
all = computed(() => {
|
|
904
|
+
const grouped = groupOther(this.data(), this.maxSegments(), this.messages.t('charts.other'));
|
|
905
|
+
const hidden = this.hidden();
|
|
906
|
+
return grouped.map((d, i) => {
|
|
907
|
+
const key = d.key ?? d.label;
|
|
908
|
+
return {
|
|
909
|
+
key,
|
|
910
|
+
label: d.label,
|
|
911
|
+
value: d.value,
|
|
912
|
+
color: d.color ?? (d.other ? NXE_CHART_OTHER_COLOR : nxeChartColor(i)),
|
|
913
|
+
hidden: hidden.includes(key),
|
|
914
|
+
};
|
|
915
|
+
});
|
|
916
|
+
}, /* @ts-ignore */
|
|
917
|
+
...(ngDevMode ? [{ debugName: "all" }] : /* istanbul ignore next */ []));
|
|
918
|
+
shown = computed(() => this.all().filter((s) => !s.hidden), /* @ts-ignore */
|
|
919
|
+
...(ngDevMode ? [{ debugName: "shown" }] : /* istanbul ignore next */ []));
|
|
920
|
+
total = computed(() => this.shown().reduce((s, x) => s + x.value, 0), /* @ts-ignore */
|
|
921
|
+
...(ngDevMode ? [{ debugName: "total" }] : /* istanbul ignore next */ []));
|
|
922
|
+
grandTotal = computed(() => this.all().reduce((s, x) => s + x.value, 0), /* @ts-ignore */
|
|
923
|
+
...(ngDevMode ? [{ debugName: "grandTotal" }] : /* istanbul ignore next */ []));
|
|
924
|
+
empty = computed(() => this.all().length === 0, /* @ts-ignore */
|
|
925
|
+
...(ngDevMode ? [{ debugName: "empty" }] : /* istanbul ignore next */ []));
|
|
926
|
+
pct(value, total) {
|
|
927
|
+
const decimals = this.percentDecimals();
|
|
928
|
+
const pattern = decimals > 0 ? `#,##0.${'0'.repeat(decimals)}` : '#,##0';
|
|
929
|
+
return `${this.formatter(total > 0 ? (value / total) * 100 : 0, pattern)}%`;
|
|
930
|
+
}
|
|
931
|
+
// ---- geometry --------------------------------------------------------------------------
|
|
932
|
+
diameter = computed(() => {
|
|
933
|
+
const available = this.measured();
|
|
934
|
+
return Math.round(available > 0 ? Math.min(this.size(), available) : this.size());
|
|
935
|
+
}, /* @ts-ignore */
|
|
936
|
+
...(ngDevMode ? [{ debugName: "diameter" }] : /* istanbul ignore next */ []));
|
|
937
|
+
outer = computed(() => this.diameter() / 2 - 2, /* @ts-ignore */
|
|
938
|
+
...(ngDevMode ? [{ debugName: "outer" }] : /* istanbul ignore next */ []));
|
|
939
|
+
inner = computed(() => this.outer() * INNER_RATIO, /* @ts-ignore */
|
|
940
|
+
...(ngDevMode ? [{ debugName: "inner" }] : /* istanbul ignore next */ []));
|
|
941
|
+
arcs = computed(() => {
|
|
942
|
+
const d = this.diameter() / 2;
|
|
943
|
+
const arcs = donutArcs(this.shown().map((s) => s.value), d, d, this.outer(), this.inner(), GAP);
|
|
944
|
+
return this.shown().map((s, i) => ({ ...s, arc: arcs[i] }));
|
|
945
|
+
}, /* @ts-ignore */
|
|
946
|
+
...(ngDevMode ? [{ debugName: "arcs" }] : /* istanbul ignore next */ []));
|
|
947
|
+
totalText = computed(() => this.formatter(this.total(), this.valueFormat()), /* @ts-ignore */
|
|
948
|
+
...(ngDevMode ? [{ debugName: "totalText" }] : /* istanbul ignore next */ []));
|
|
949
|
+
totalCaption = computed(() => this.totalLabel() ?? this.messages.t('charts.total'), /* @ts-ignore */
|
|
950
|
+
...(ngDevMode ? [{ debugName: "totalCaption" }] : /* istanbul ignore next */ []));
|
|
951
|
+
// ---- interaction -----------------------------------------------------------------------
|
|
952
|
+
activeIndex = computed(() => this.hoverIndex() ?? this.keyboardIndex(), /* @ts-ignore */
|
|
953
|
+
...(ngDevMode ? [{ debugName: "activeIndex" }] : /* istanbul ignore next */ []));
|
|
954
|
+
tooltip = computed(() => {
|
|
955
|
+
const i = this.activeIndex();
|
|
956
|
+
const s = i === null ? undefined : this.arcs()[i];
|
|
957
|
+
if (!s || !s.arc) {
|
|
958
|
+
return null;
|
|
959
|
+
}
|
|
960
|
+
const point = this.hoverIndex() !== null ? (this.pointer() ?? s.arc.centroid) : s.arc.centroid;
|
|
961
|
+
return {
|
|
962
|
+
label: s.label,
|
|
963
|
+
color: s.color,
|
|
964
|
+
value: this.formatter(s.value, this.valueFormat()),
|
|
965
|
+
share: this.pct(s.value, this.total()),
|
|
966
|
+
x: point.x,
|
|
967
|
+
y: point.y,
|
|
968
|
+
};
|
|
969
|
+
}, /* @ts-ignore */
|
|
970
|
+
...(ngDevMode ? [{ debugName: "tooltip" }] : /* istanbul ignore next */ []));
|
|
971
|
+
announcement = computed(() => {
|
|
972
|
+
const t = this.keyboardIndex() === null ? null : this.tooltip();
|
|
973
|
+
return t ? this.messages.t('charts.summary.segment', t.label, t.value, t.share) : '';
|
|
974
|
+
}, /* @ts-ignore */
|
|
975
|
+
...(ngDevMode ? [{ debugName: "announcement" }] : /* istanbul ignore next */ []));
|
|
976
|
+
onPointerMove(event) {
|
|
977
|
+
const rect = event.currentTarget.getBoundingClientRect();
|
|
978
|
+
const x = event.clientX - rect.left;
|
|
979
|
+
const y = event.clientY - rect.top;
|
|
980
|
+
const d = this.diameter() / 2;
|
|
981
|
+
const i = donutHit(this.shown().map((s) => s.value), d, d, this.outer(), this.inner(), x, y);
|
|
982
|
+
this.pointer.set({ x, y });
|
|
983
|
+
this.hoverIndex.set(i < 0 ? null : i);
|
|
984
|
+
}
|
|
985
|
+
onPointerLeave() {
|
|
986
|
+
this.hoverIndex.set(null);
|
|
987
|
+
this.pointer.set(null);
|
|
988
|
+
}
|
|
989
|
+
onFocus() {
|
|
990
|
+
if (this.keyboardIndex() === null && this.hoverIndex() === null && this.shown().length) {
|
|
991
|
+
this.keyboardIndex.set(0);
|
|
992
|
+
}
|
|
993
|
+
}
|
|
994
|
+
onBlur() {
|
|
995
|
+
this.keyboardIndex.set(null);
|
|
996
|
+
}
|
|
997
|
+
onKeydown(event) {
|
|
998
|
+
const n = this.shown().length;
|
|
999
|
+
if (n === 0) {
|
|
1000
|
+
return;
|
|
1001
|
+
}
|
|
1002
|
+
const current = this.keyboardIndex() ?? 0;
|
|
1003
|
+
let next;
|
|
1004
|
+
switch (event.key) {
|
|
1005
|
+
case 'ArrowRight':
|
|
1006
|
+
case 'ArrowDown':
|
|
1007
|
+
next = (current + 1) % n;
|
|
1008
|
+
break;
|
|
1009
|
+
case 'ArrowLeft':
|
|
1010
|
+
case 'ArrowUp':
|
|
1011
|
+
next = (current - 1 + n) % n;
|
|
1012
|
+
break;
|
|
1013
|
+
case 'Home':
|
|
1014
|
+
next = 0;
|
|
1015
|
+
break;
|
|
1016
|
+
case 'End':
|
|
1017
|
+
next = n - 1;
|
|
1018
|
+
break;
|
|
1019
|
+
case 'Escape':
|
|
1020
|
+
next = null;
|
|
1021
|
+
break;
|
|
1022
|
+
default:
|
|
1023
|
+
return;
|
|
1024
|
+
}
|
|
1025
|
+
event.preventDefault();
|
|
1026
|
+
this.hoverIndex.set(null);
|
|
1027
|
+
this.keyboardIndex.set(next);
|
|
1028
|
+
}
|
|
1029
|
+
// ---- legend, table, summary ------------------------------------------------------------
|
|
1030
|
+
legendItems = computed(() => {
|
|
1031
|
+
const total = this.total();
|
|
1032
|
+
const pattern = this.valueFormat();
|
|
1033
|
+
return this.all().map((s) => ({
|
|
1034
|
+
key: s.key,
|
|
1035
|
+
label: s.label,
|
|
1036
|
+
color: s.color,
|
|
1037
|
+
hidden: s.hidden,
|
|
1038
|
+
value: s.hidden ? undefined : `${this.formatter(s.value, pattern)} · ${this.pct(s.value, total)}`,
|
|
1039
|
+
}));
|
|
1040
|
+
}, /* @ts-ignore */
|
|
1041
|
+
...(ngDevMode ? [{ debugName: "legendItems" }] : /* istanbul ignore next */ []));
|
|
1042
|
+
toggleSegment(key) {
|
|
1043
|
+
const hidden = this.hidden();
|
|
1044
|
+
this.hidden.set(hidden.includes(key) ? hidden.filter((k) => k !== key) : [...hidden, key]);
|
|
1045
|
+
}
|
|
1046
|
+
tableRows = computed(() => {
|
|
1047
|
+
const grand = this.grandTotal();
|
|
1048
|
+
const pattern = this.valueFormat();
|
|
1049
|
+
return this.all().map((s) => ({
|
|
1050
|
+
header: s.label,
|
|
1051
|
+
cells: [this.formatter(s.value, pattern), this.pct(s.value, grand)],
|
|
1052
|
+
}));
|
|
1053
|
+
}, /* @ts-ignore */
|
|
1054
|
+
...(ngDevMode ? [{ debugName: "tableRows" }] : /* istanbul ignore next */ []));
|
|
1055
|
+
tableColumns = computed(() => [
|
|
1056
|
+
this.messages.t('charts.table.value'),
|
|
1057
|
+
this.messages.t('charts.table.share'),
|
|
1058
|
+
], /* @ts-ignore */
|
|
1059
|
+
...(ngDevMode ? [{ debugName: "tableColumns" }] : /* istanbul ignore next */ []));
|
|
1060
|
+
summary = computed(() => {
|
|
1061
|
+
const m = this.messages;
|
|
1062
|
+
const total = this.total();
|
|
1063
|
+
const pattern = this.valueFormat();
|
|
1064
|
+
const parts = [];
|
|
1065
|
+
if (this.heading()) {
|
|
1066
|
+
parts.push(`${this.heading()}.`);
|
|
1067
|
+
}
|
|
1068
|
+
parts.push(m.t('charts.summary.donut', this.shown().length, this.formatter(total, pattern)));
|
|
1069
|
+
for (const s of this.shown()) {
|
|
1070
|
+
parts.push(m.t('charts.summary.segment', s.label, this.formatter(s.value, pattern), this.pct(s.value, total)));
|
|
1071
|
+
}
|
|
1072
|
+
if (this.description()) {
|
|
1073
|
+
parts.push(this.description());
|
|
1074
|
+
}
|
|
1075
|
+
return parts.join(' ');
|
|
1076
|
+
}, /* @ts-ignore */
|
|
1077
|
+
...(ngDevMode ? [{ debugName: "summary" }] : /* istanbul ignore next */ []));
|
|
1078
|
+
toggleTable() {
|
|
1079
|
+
this.tableShown.update((v) => !v);
|
|
1080
|
+
}
|
|
1081
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: NxeDonutChartComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1082
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.5", type: NxeDonutChartComponent, isStandalone: true, selector: "nxe-donut-chart", inputs: { data: { classPropertyName: "data", publicName: "data", isSignal: true, isRequired: false, transformFunction: null }, maxSegments: { classPropertyName: "maxSegments", publicName: "maxSegments", isSignal: true, isRequired: false, transformFunction: null }, valueFormat: { classPropertyName: "valueFormat", publicName: "valueFormat", isSignal: true, isRequired: false, transformFunction: null }, percentDecimals: { classPropertyName: "percentDecimals", publicName: "percentDecimals", isSignal: true, isRequired: false, transformFunction: null }, heading: { classPropertyName: "heading", publicName: "heading", isSignal: true, isRequired: false, transformFunction: null }, description: { classPropertyName: "description", publicName: "description", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, totalLabel: { classPropertyName: "totalLabel", publicName: "totalLabel", isSignal: true, isRequired: false, transformFunction: null }, showTotal: { classPropertyName: "showTotal", publicName: "showTotal", isSignal: true, isRequired: false, transformFunction: null }, showLegend: { classPropertyName: "showLegend", publicName: "showLegend", isSignal: true, isRequired: false, transformFunction: null }, loading: { classPropertyName: "loading", publicName: "loading", isSignal: true, isRequired: false, transformFunction: null }, showTableToggle: { classPropertyName: "showTableToggle", publicName: "showTableToggle", isSignal: true, isRequired: false, transformFunction: null }, hidden: { classPropertyName: "hidden", publicName: "hidden", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { hidden: "hiddenChange" }, host: { classAttribute: "nxe-chart nxe-donut-chart" }, ngImport: i0, template: "<figure class=\"nxe-chart__figure\" [attr.aria-busy]=\"loading() ? 'true' : null\">\r\n @if (heading()) {\r\n <figcaption class=\"nxe-chart__heading\">{{ heading() }}</figcaption>\r\n }\r\n @if (loading()) {\r\n <nxe-skeleton variant=\"circle\" [width]=\"diameter()\" />\r\n } @else if (empty()) {\r\n <nxe-empty-state class=\"nxe-chart__empty\" [heading]=\"messages.t('charts.empty')\" [headingLevel]=\"4\" />\r\n } @else {\r\n @if (showTableToggle()) {\r\n <div class=\"nxe-chart__toolbar\">\r\n <button\r\n type=\"button\"\r\n class=\"nxe-chart__toggle\"\r\n [attr.aria-controls]=\"uid + '-table'\"\r\n (click)=\"toggleTable()\">\r\n {{ messages.t(tableShown() ? 'charts.viewAsChart' : 'charts.viewAsTable') }}\r\n </button>\r\n </div>\r\n }\r\n <div class=\"nxe-donut-chart__layout\" [hidden]=\"tableShown()\">\r\n <div class=\"nxe-donut-chart__plot nxe-chart__stage\" [style.width.px]=\"diameter()\" [style.height.px]=\"diameter()\">\r\n <svg\r\n class=\"nxe-chart__svg\"\r\n role=\"img\"\r\n [attr.aria-label]=\"summary()\"\r\n [attr.width]=\"diameter()\"\r\n [attr.height]=\"diameter()\"\r\n [attr.viewBox]=\"'0 0 ' + diameter() + ' ' + diameter()\">\r\n @for (s of arcs(); track s.key; let i = $index) {\r\n @if (s.arc) {\r\n <path\r\n class=\"nxe-donut-chart__segment\"\r\n [class.nxe-donut-chart__segment--dim]=\"activeIndex() !== null && activeIndex() !== i\"\r\n fill-rule=\"evenodd\"\r\n [attr.d]=\"s.arc.path\"\r\n [style.fill]=\"s.color\" />\r\n }\r\n }\r\n </svg>\r\n @if (showTotal()) {\r\n <div class=\"nxe-donut-chart__centre\" aria-hidden=\"true\">\r\n <span class=\"nxe-donut-chart__total-value\">{{ totalText() }}</span>\r\n <span class=\"nxe-donut-chart__total-label\">{{ totalCaption() }}</span>\r\n </div>\r\n }\r\n <div\r\n class=\"nxe-chart__focus\"\r\n role=\"group\"\r\n tabindex=\"0\"\r\n [attr.aria-label]=\"messages.t('charts.navigateSegments')\"\r\n (pointermove)=\"onPointerMove($event)\"\r\n (pointerleave)=\"onPointerLeave()\"\r\n (focus)=\"onFocus()\"\r\n (blur)=\"onBlur()\"\r\n (keydown)=\"onKeydown($event)\"></div>\r\n @if (tooltip(); as tip) {\r\n <div\r\n class=\"nxe-chart__tooltip nxe-donut-chart__tooltip\"\r\n role=\"tooltip\"\r\n [style.left.px]=\"tip.x\"\r\n [style.top.px]=\"tip.y - 12\">\r\n <div class=\"nxe-chart__tooltip-row\">\r\n <span class=\"nxe-chart__swatch\" aria-hidden=\"true\" [style.background]=\"tip.color\"></span>\r\n <span class=\"nxe-chart__tooltip-name\">{{ tip.label }}</span>\r\n </div>\r\n <div class=\"nxe-chart__tooltip-row\">\r\n <span class=\"nxe-chart__tooltip-value\">{{ tip.value }}</span>\r\n <span class=\"nxe-chart__tooltip-name\">{{ tip.share }}</span>\r\n </div>\r\n </div>\r\n }\r\n <div class=\"nxe-chart__sr\" aria-live=\"polite\" aria-atomic=\"true\">{{ announcement() }}</div>\r\n </div>\r\n @if (showLegend()) {\r\n <nxe-chart-legend\r\n class=\"nxe-donut-chart__legend\"\r\n [items]=\"legendItems()\"\r\n [label]=\"messages.t('charts.legend')\"\r\n (toggled)=\"toggleSegment($event)\" />\r\n }\r\n </div>\r\n <div [id]=\"uid + '-table'\" [class.nxe-chart__sr]=\"!tableShown()\">\r\n <nxe-chart-data-table\r\n [caption]=\"heading()\"\r\n [corner]=\"messages.t('charts.table.segment')\"\r\n [columns]=\"tableColumns()\"\r\n [rows]=\"tableRows()\" />\r\n </div>\r\n }\r\n</figure>\r\n", styles: [".nxe-chart{display:block;width:100%;min-width:0;color:var(--nxe-ui-text);font-family:var(--nxe-ui-font-family)}.nxe-chart__figure{display:flex;flex-direction:column;gap:var(--nxe-ui-space-2, 8px);margin:0}.nxe-chart__heading{color:var(--nxe-ui-text);font-size:var(--nxe-ui-font-size-lg, 16px);font-weight:var(--nxe-ui-font-weight-semibold, 600);line-height:var(--nxe-ui-line-height-lg, 24px)}.nxe-chart__toolbar{display:flex;justify-content:flex-end}.nxe-chart__toggle{margin:0;padding:2px 8px;border:1px solid var(--nxe-ui-border);border-radius:var(--nxe-ui-radius, 4px);background:var(--nxe-ui-surface);color:var(--nxe-ui-text);font:inherit;font-size:var(--nxe-ui-font-size-xs, 12px);cursor:pointer}.nxe-chart__toggle:hover{background:var(--nxe-ui-hover-bg)}.nxe-chart__toggle:focus-visible{outline:2px solid var(--nxe-ui-focus-ring-color);outline-offset:1px}.nxe-chart__stage{position:relative;max-width:100%;overflow:visible}.nxe-chart__stage[hidden]{display:none}.nxe-chart__svg{display:block;max-width:100%;overflow:visible}.nxe-chart__focus{position:absolute;inset:0;border-radius:var(--nxe-ui-radius, 4px);outline:none;touch-action:pan-y}.nxe-chart__focus:focus-visible{outline:2px solid var(--nxe-ui-focus-ring-color);outline-offset:2px}.nxe-chart__grid{stroke:var(--nxe-ui-neutral-150);stroke-width:1;shape-rendering:crispEdges}.nxe-chart__grid--zero{stroke:var(--nxe-ui-neutral-240)}.nxe-chart__tick{fill:var(--nxe-ui-neutral-600);font-size:var(--nxe-ui-font-size-xs, 12px);font-variant-numeric:tabular-nums}.nxe-chart__line{fill:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round}.nxe-chart__area{fill-opacity:.16;stroke:none}.nxe-chart__area--stacked{fill-opacity:.9;stroke:var(--nxe-ui-surface);stroke-width:2;stroke-linejoin:round}.nxe-chart__marker{stroke:var(--nxe-ui-surface);stroke-width:2}.nxe-chart__crosshair{stroke:var(--nxe-ui-neutral-450);stroke-width:1;stroke-dasharray:3 3;pointer-events:none}.nxe-chart__tooltip{position:absolute;z-index:2;min-width:120px;max-width:260px;padding:6px 10px;border:1px solid var(--nxe-ui-border);border-radius:var(--nxe-ui-radius-md, 6px);background:var(--nxe-ui-surface);box-shadow:var(--nxe-ui-shadow-sm);color:var(--nxe-ui-text);font-size:var(--nxe-ui-font-size-xs, 12px);line-height:var(--nxe-ui-line-height-xs, 16px);pointer-events:none;transform:translate(-50%)}.nxe-chart__tooltip.nxe-chart__tooltip--start{transform:translate(12px)}.nxe-chart__tooltip.nxe-chart__tooltip--end{transform:translate(calc(-100% - 12px))}.nxe-chart__tooltip-title{margin-bottom:2px;font-weight:var(--nxe-ui-font-weight-semibold, 600)}.nxe-chart__tooltip-row{display:flex;align-items:center;gap:var(--nxe-ui-space-2, 8px)}.nxe-chart__tooltip-name{flex:1;min-width:0;overflow:hidden;color:var(--nxe-ui-neutral-700);text-overflow:ellipsis;white-space:nowrap}.nxe-chart__tooltip-value{font-variant-numeric:tabular-nums;font-weight:var(--nxe-ui-font-weight-medium, 500)}.nxe-chart__swatch{flex:none;width:10px;height:10px;border-radius:2px}.nxe-chart__sr{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;border:0;clip-path:inset(50%);white-space:nowrap}\n", ".nxe-donut-chart__layout{display:flex;flex-wrap:wrap;align-items:center;gap:var(--nxe-ui-space-4, 16px) var(--nxe-ui-space-6, 24px)}.nxe-donut-chart__plot{position:relative;flex:none}.nxe-donut-chart__segment{stroke:none;transition:opacity .12s}.nxe-donut-chart__segment.nxe-donut-chart__segment--dim{opacity:.45}.nxe-donut-chart__centre{position:absolute;inset:0;display:flex;flex-direction:column;align-items:center;justify-content:center;pointer-events:none;text-align:center}.nxe-donut-chart__total-value{color:var(--nxe-ui-text);font-size:var(--nxe-ui-font-size-2xl, 24px);font-weight:var(--nxe-ui-font-weight-semibold, 600);line-height:var(--nxe-ui-line-height-2xl, 30px)}.nxe-donut-chart__total-label{color:var(--nxe-ui-neutral-600);font-size:var(--nxe-ui-font-size-xs, 12px)}.nxe-donut-chart__legend .nxe-chart-legend__list{flex-direction:column;gap:2px}.nxe-donut-chart__tooltip{min-width:0;transform:translate(-50%,-100%)}@media(prefers-reduced-motion:reduce){.nxe-donut-chart__segment{transition:none}}\n"], dependencies: [{ kind: "component", type: NxeChartLegendComponent, selector: "nxe-chart-legend", inputs: ["items", "label", "toggleable"], outputs: ["toggled"] }, { kind: "component", type: NxeChartDataTableComponent, selector: "nxe-chart-data-table", inputs: ["caption", "corner", "columns", "rows"] }, { kind: "component", type: NxeSkeletonComponent, selector: "nxe-skeleton", inputs: ["variant", "width", "height", "lines", "animated"] }, { kind: "component", type: NxeEmptyStateComponent, selector: "nxe-empty-state", inputs: ["heading", "description", "headingLevel"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
|
|
1083
|
+
}
|
|
1084
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: NxeDonutChartComponent, decorators: [{
|
|
1085
|
+
type: Component,
|
|
1086
|
+
args: [{ selector: 'nxe-donut-chart', imports: [NxeChartLegendComponent, NxeChartDataTableComponent, NxeSkeletonComponent, NxeEmptyStateComponent], host: { class: 'nxe-chart nxe-donut-chart' }, changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, template: "<figure class=\"nxe-chart__figure\" [attr.aria-busy]=\"loading() ? 'true' : null\">\r\n @if (heading()) {\r\n <figcaption class=\"nxe-chart__heading\">{{ heading() }}</figcaption>\r\n }\r\n @if (loading()) {\r\n <nxe-skeleton variant=\"circle\" [width]=\"diameter()\" />\r\n } @else if (empty()) {\r\n <nxe-empty-state class=\"nxe-chart__empty\" [heading]=\"messages.t('charts.empty')\" [headingLevel]=\"4\" />\r\n } @else {\r\n @if (showTableToggle()) {\r\n <div class=\"nxe-chart__toolbar\">\r\n <button\r\n type=\"button\"\r\n class=\"nxe-chart__toggle\"\r\n [attr.aria-controls]=\"uid + '-table'\"\r\n (click)=\"toggleTable()\">\r\n {{ messages.t(tableShown() ? 'charts.viewAsChart' : 'charts.viewAsTable') }}\r\n </button>\r\n </div>\r\n }\r\n <div class=\"nxe-donut-chart__layout\" [hidden]=\"tableShown()\">\r\n <div class=\"nxe-donut-chart__plot nxe-chart__stage\" [style.width.px]=\"diameter()\" [style.height.px]=\"diameter()\">\r\n <svg\r\n class=\"nxe-chart__svg\"\r\n role=\"img\"\r\n [attr.aria-label]=\"summary()\"\r\n [attr.width]=\"diameter()\"\r\n [attr.height]=\"diameter()\"\r\n [attr.viewBox]=\"'0 0 ' + diameter() + ' ' + diameter()\">\r\n @for (s of arcs(); track s.key; let i = $index) {\r\n @if (s.arc) {\r\n <path\r\n class=\"nxe-donut-chart__segment\"\r\n [class.nxe-donut-chart__segment--dim]=\"activeIndex() !== null && activeIndex() !== i\"\r\n fill-rule=\"evenodd\"\r\n [attr.d]=\"s.arc.path\"\r\n [style.fill]=\"s.color\" />\r\n }\r\n }\r\n </svg>\r\n @if (showTotal()) {\r\n <div class=\"nxe-donut-chart__centre\" aria-hidden=\"true\">\r\n <span class=\"nxe-donut-chart__total-value\">{{ totalText() }}</span>\r\n <span class=\"nxe-donut-chart__total-label\">{{ totalCaption() }}</span>\r\n </div>\r\n }\r\n <div\r\n class=\"nxe-chart__focus\"\r\n role=\"group\"\r\n tabindex=\"0\"\r\n [attr.aria-label]=\"messages.t('charts.navigateSegments')\"\r\n (pointermove)=\"onPointerMove($event)\"\r\n (pointerleave)=\"onPointerLeave()\"\r\n (focus)=\"onFocus()\"\r\n (blur)=\"onBlur()\"\r\n (keydown)=\"onKeydown($event)\"></div>\r\n @if (tooltip(); as tip) {\r\n <div\r\n class=\"nxe-chart__tooltip nxe-donut-chart__tooltip\"\r\n role=\"tooltip\"\r\n [style.left.px]=\"tip.x\"\r\n [style.top.px]=\"tip.y - 12\">\r\n <div class=\"nxe-chart__tooltip-row\">\r\n <span class=\"nxe-chart__swatch\" aria-hidden=\"true\" [style.background]=\"tip.color\"></span>\r\n <span class=\"nxe-chart__tooltip-name\">{{ tip.label }}</span>\r\n </div>\r\n <div class=\"nxe-chart__tooltip-row\">\r\n <span class=\"nxe-chart__tooltip-value\">{{ tip.value }}</span>\r\n <span class=\"nxe-chart__tooltip-name\">{{ tip.share }}</span>\r\n </div>\r\n </div>\r\n }\r\n <div class=\"nxe-chart__sr\" aria-live=\"polite\" aria-atomic=\"true\">{{ announcement() }}</div>\r\n </div>\r\n @if (showLegend()) {\r\n <nxe-chart-legend\r\n class=\"nxe-donut-chart__legend\"\r\n [items]=\"legendItems()\"\r\n [label]=\"messages.t('charts.legend')\"\r\n (toggled)=\"toggleSegment($event)\" />\r\n }\r\n </div>\r\n <div [id]=\"uid + '-table'\" [class.nxe-chart__sr]=\"!tableShown()\">\r\n <nxe-chart-data-table\r\n [caption]=\"heading()\"\r\n [corner]=\"messages.t('charts.table.segment')\"\r\n [columns]=\"tableColumns()\"\r\n [rows]=\"tableRows()\" />\r\n </div>\r\n }\r\n</figure>\r\n", styles: [".nxe-chart{display:block;width:100%;min-width:0;color:var(--nxe-ui-text);font-family:var(--nxe-ui-font-family)}.nxe-chart__figure{display:flex;flex-direction:column;gap:var(--nxe-ui-space-2, 8px);margin:0}.nxe-chart__heading{color:var(--nxe-ui-text);font-size:var(--nxe-ui-font-size-lg, 16px);font-weight:var(--nxe-ui-font-weight-semibold, 600);line-height:var(--nxe-ui-line-height-lg, 24px)}.nxe-chart__toolbar{display:flex;justify-content:flex-end}.nxe-chart__toggle{margin:0;padding:2px 8px;border:1px solid var(--nxe-ui-border);border-radius:var(--nxe-ui-radius, 4px);background:var(--nxe-ui-surface);color:var(--nxe-ui-text);font:inherit;font-size:var(--nxe-ui-font-size-xs, 12px);cursor:pointer}.nxe-chart__toggle:hover{background:var(--nxe-ui-hover-bg)}.nxe-chart__toggle:focus-visible{outline:2px solid var(--nxe-ui-focus-ring-color);outline-offset:1px}.nxe-chart__stage{position:relative;max-width:100%;overflow:visible}.nxe-chart__stage[hidden]{display:none}.nxe-chart__svg{display:block;max-width:100%;overflow:visible}.nxe-chart__focus{position:absolute;inset:0;border-radius:var(--nxe-ui-radius, 4px);outline:none;touch-action:pan-y}.nxe-chart__focus:focus-visible{outline:2px solid var(--nxe-ui-focus-ring-color);outline-offset:2px}.nxe-chart__grid{stroke:var(--nxe-ui-neutral-150);stroke-width:1;shape-rendering:crispEdges}.nxe-chart__grid--zero{stroke:var(--nxe-ui-neutral-240)}.nxe-chart__tick{fill:var(--nxe-ui-neutral-600);font-size:var(--nxe-ui-font-size-xs, 12px);font-variant-numeric:tabular-nums}.nxe-chart__line{fill:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round}.nxe-chart__area{fill-opacity:.16;stroke:none}.nxe-chart__area--stacked{fill-opacity:.9;stroke:var(--nxe-ui-surface);stroke-width:2;stroke-linejoin:round}.nxe-chart__marker{stroke:var(--nxe-ui-surface);stroke-width:2}.nxe-chart__crosshair{stroke:var(--nxe-ui-neutral-450);stroke-width:1;stroke-dasharray:3 3;pointer-events:none}.nxe-chart__tooltip{position:absolute;z-index:2;min-width:120px;max-width:260px;padding:6px 10px;border:1px solid var(--nxe-ui-border);border-radius:var(--nxe-ui-radius-md, 6px);background:var(--nxe-ui-surface);box-shadow:var(--nxe-ui-shadow-sm);color:var(--nxe-ui-text);font-size:var(--nxe-ui-font-size-xs, 12px);line-height:var(--nxe-ui-line-height-xs, 16px);pointer-events:none;transform:translate(-50%)}.nxe-chart__tooltip.nxe-chart__tooltip--start{transform:translate(12px)}.nxe-chart__tooltip.nxe-chart__tooltip--end{transform:translate(calc(-100% - 12px))}.nxe-chart__tooltip-title{margin-bottom:2px;font-weight:var(--nxe-ui-font-weight-semibold, 600)}.nxe-chart__tooltip-row{display:flex;align-items:center;gap:var(--nxe-ui-space-2, 8px)}.nxe-chart__tooltip-name{flex:1;min-width:0;overflow:hidden;color:var(--nxe-ui-neutral-700);text-overflow:ellipsis;white-space:nowrap}.nxe-chart__tooltip-value{font-variant-numeric:tabular-nums;font-weight:var(--nxe-ui-font-weight-medium, 500)}.nxe-chart__swatch{flex:none;width:10px;height:10px;border-radius:2px}.nxe-chart__sr{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;border:0;clip-path:inset(50%);white-space:nowrap}\n", ".nxe-donut-chart__layout{display:flex;flex-wrap:wrap;align-items:center;gap:var(--nxe-ui-space-4, 16px) var(--nxe-ui-space-6, 24px)}.nxe-donut-chart__plot{position:relative;flex:none}.nxe-donut-chart__segment{stroke:none;transition:opacity .12s}.nxe-donut-chart__segment.nxe-donut-chart__segment--dim{opacity:.45}.nxe-donut-chart__centre{position:absolute;inset:0;display:flex;flex-direction:column;align-items:center;justify-content:center;pointer-events:none;text-align:center}.nxe-donut-chart__total-value{color:var(--nxe-ui-text);font-size:var(--nxe-ui-font-size-2xl, 24px);font-weight:var(--nxe-ui-font-weight-semibold, 600);line-height:var(--nxe-ui-line-height-2xl, 30px)}.nxe-donut-chart__total-label{color:var(--nxe-ui-neutral-600);font-size:var(--nxe-ui-font-size-xs, 12px)}.nxe-donut-chart__legend .nxe-chart-legend__list{flex-direction:column;gap:2px}.nxe-donut-chart__tooltip{min-width:0;transform:translate(-50%,-100%)}@media(prefers-reduced-motion:reduce){.nxe-donut-chart__segment{transition:none}}\n"] }]
|
|
1087
|
+
}], ctorParameters: () => [], propDecorators: { data: [{ type: i0.Input, args: [{ isSignal: true, alias: "data", required: false }] }], maxSegments: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxSegments", required: false }] }], valueFormat: [{ type: i0.Input, args: [{ isSignal: true, alias: "valueFormat", required: false }] }], percentDecimals: [{ type: i0.Input, args: [{ isSignal: true, alias: "percentDecimals", required: false }] }], heading: [{ type: i0.Input, args: [{ isSignal: true, alias: "heading", required: false }] }], description: [{ type: i0.Input, args: [{ isSignal: true, alias: "description", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], totalLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "totalLabel", required: false }] }], showTotal: [{ type: i0.Input, args: [{ isSignal: true, alias: "showTotal", required: false }] }], showLegend: [{ type: i0.Input, args: [{ isSignal: true, alias: "showLegend", required: false }] }], loading: [{ type: i0.Input, args: [{ isSignal: true, alias: "loading", required: false }] }], showTableToggle: [{ type: i0.Input, args: [{ isSignal: true, alias: "showTableToggle", required: false }] }], hidden: [{ type: i0.Input, args: [{ isSignal: true, alias: "hidden", required: false }] }, { type: i0.Output, args: ["hiddenChange"] }] } });
|
|
1088
|
+
|
|
1089
|
+
// @nexterajs/ui/charts — public API.
|
|
1090
|
+
|
|
1091
|
+
/**
|
|
1092
|
+
* Generated bundle index. Do not edit.
|
|
1093
|
+
*/
|
|
1094
|
+
|
|
1095
|
+
export { NXE_CHART_OTHER_COLOR, NXE_CHART_SLOTS, NxeAreaChartComponent, NxeChartDataTableComponent, NxeChartLegendComponent, NxeDonutChartComponent, NxeLineChartComponent, areaPath, decimalsOf, donutArcs, donutHit, groupOther, labelIndexes, linePath, linearScale, nearestIndex, niceNumber, niceScale, nxeChartColor, patternForDecimals, segments, stackValues };
|
|
1096
|
+
//# sourceMappingURL=nexterajs-ui-charts.mjs.map
|