@skyux/charts 14.6.2 → 14.8.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/fesm2022/skyux-charts-testing.mjs +214 -0
- package/fesm2022/skyux-charts-testing.mjs.map +1 -0
- package/fesm2022/skyux-charts.mjs +1203 -30
- package/fesm2022/skyux-charts.mjs.map +1 -1
- package/package.json +14 -5
- package/types/skyux-charts-testing.d.ts +143 -0
- package/types/skyux-charts.d.ts +408 -10
|
@@ -1,14 +1,1135 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import {
|
|
2
|
+
import { input, booleanAttribute, ChangeDetectionStrategy, Component, numberAttribute, inject, computed, viewChild, ElementRef, DestroyRef, afterRenderEffect, signal, Injectable, Directive, contentChild, contentChildren, NgModule } from '@angular/core';
|
|
3
|
+
import { toSignal, toObservable } from '@angular/core/rxjs-interop';
|
|
4
|
+
import * as i2 from '@skyux/i18n';
|
|
5
|
+
import { SkyIntlNumberFormatStyle, SkyIntlNumberFormatter, SkyAppLocaleProvider, SkyLibResourcesService, SkyI18nModule } from '@skyux/i18n';
|
|
6
|
+
import { map } from 'rxjs/operators';
|
|
7
|
+
import { SkyLogService } from '@skyux/core';
|
|
8
|
+
import { SkyThemeService } from '@skyux/theme';
|
|
9
|
+
import { map as map$1, EMPTY, switchMap, of } from 'rxjs';
|
|
10
|
+
import { Chart, BarController, BarElement, CategoryScale, LinearScale, LogarithmicScale, Legend, Tooltip } from 'chart.js';
|
|
11
|
+
import * as i1$3 from '@skyux/indicators';
|
|
12
|
+
import { SkyWaitModule } from '@skyux/indicators';
|
|
3
13
|
import * as i1 from '@skyux/modals';
|
|
4
14
|
import { SkyModalInstance, SkyModalModule, SkyModalService } from '@skyux/modals';
|
|
5
15
|
import * as i1$1 from '@skyux/popovers';
|
|
6
16
|
import { SkyDropdownModule } from '@skyux/popovers';
|
|
7
|
-
import * as i2 from '@skyux/i18n';
|
|
8
|
-
import { SkyLibResourcesService, SkyI18nModule } from '@skyux/i18n';
|
|
9
17
|
import * as i1$2 from '@skyux/help-inline';
|
|
10
18
|
import { SkyHelpInlineModule } from '@skyux/help-inline';
|
|
11
19
|
|
|
20
|
+
/**
|
|
21
|
+
* Defines the category axis of a chart. Its categories are shared by every
|
|
22
|
+
* series plotted against it, and each series' values align to them by index.
|
|
23
|
+
*
|
|
24
|
+
* @preview
|
|
25
|
+
*/
|
|
26
|
+
class SkyChartAxisCategory {
|
|
27
|
+
constructor() {
|
|
28
|
+
/**
|
|
29
|
+
* The categories shared by every series plotted against this axis. Each
|
|
30
|
+
* series' values are aligned to these categories by index.
|
|
31
|
+
*/
|
|
32
|
+
this.categories = input.required(...(ngDevMode ? [{ debugName: "categories" }] : []));
|
|
33
|
+
/**
|
|
34
|
+
* Whether to hide the axis label.
|
|
35
|
+
*/
|
|
36
|
+
this.labelHidden = input(false, { ...(ngDevMode ? { debugName: "labelHidden" } : {}), transform: booleanAttribute });
|
|
37
|
+
/**
|
|
38
|
+
* The text of the axis label.
|
|
39
|
+
*/
|
|
40
|
+
this.labelText = input.required(...(ngDevMode ? [{ debugName: "labelText" }] : []));
|
|
41
|
+
}
|
|
42
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: SkyChartAxisCategory, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
43
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.2.0", type: SkyChartAxisCategory, isStandalone: true, selector: "sky-chart-axis-category", inputs: { categories: { classPropertyName: "categories", publicName: "categories", isSignal: true, isRequired: true, transformFunction: null }, labelHidden: { classPropertyName: "labelHidden", publicName: "labelHidden", isSignal: true, isRequired: false, transformFunction: null }, labelText: { classPropertyName: "labelText", publicName: "labelText", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0, template: '', isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
44
|
+
}
|
|
45
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: SkyChartAxisCategory, decorators: [{
|
|
46
|
+
type: Component,
|
|
47
|
+
args: [{
|
|
48
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
49
|
+
selector: 'sky-chart-axis-category',
|
|
50
|
+
template: '',
|
|
51
|
+
}]
|
|
52
|
+
}], propDecorators: { categories: [{ type: i0.Input, args: [{ isSignal: true, alias: "categories", required: true }] }], labelHidden: [{ type: i0.Input, args: [{ isSignal: true, alias: "labelHidden", required: false }] }], labelText: [{ type: i0.Input, args: [{ isSignal: true, alias: "labelText", required: true }] }] } });
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Creates a function that formats a numeric value according to the given
|
|
56
|
+
* `format`, `currencyCode`, `digits`, and `locale`. Shared by every chart type
|
|
57
|
+
* so value formatting is not tied to an axis.
|
|
58
|
+
* @internal
|
|
59
|
+
*/
|
|
60
|
+
function createSkyChartValueFormatter(options) {
|
|
61
|
+
const { format, currencyCode = 'USD', digits, locale } = options;
|
|
62
|
+
let style;
|
|
63
|
+
switch (format) {
|
|
64
|
+
case 'currency':
|
|
65
|
+
style = SkyIntlNumberFormatStyle.Currency;
|
|
66
|
+
break;
|
|
67
|
+
case 'percent':
|
|
68
|
+
style = SkyIntlNumberFormatStyle.Percent;
|
|
69
|
+
break;
|
|
70
|
+
default:
|
|
71
|
+
style = SkyIntlNumberFormatStyle.Decimal;
|
|
72
|
+
break;
|
|
73
|
+
}
|
|
74
|
+
return (value) => SkyIntlNumberFormatter.format(value, locale, style, {
|
|
75
|
+
currency: format === 'currency' ? currencyCode : undefined,
|
|
76
|
+
currencyDisplay: 'symbol',
|
|
77
|
+
minimumFractionDigits: digits,
|
|
78
|
+
maximumFractionDigits: digits,
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Coerces an attribute to a number, treating unset and invalid values as
|
|
84
|
+
* `undefined`.
|
|
85
|
+
*/
|
|
86
|
+
function optionalNumberAttribute(value) {
|
|
87
|
+
if (value === undefined || value === null) {
|
|
88
|
+
return undefined;
|
|
89
|
+
}
|
|
90
|
+
const num = numberAttribute(value);
|
|
91
|
+
return Number.isNaN(num) ? undefined : num;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Defines the value axis of a chart, which scales the plotted series and
|
|
96
|
+
* formats their values in axis labels, tooltips, and the data table.
|
|
97
|
+
*
|
|
98
|
+
* @preview
|
|
99
|
+
*/
|
|
100
|
+
class SkyChartAxisValue {
|
|
101
|
+
constructor() {
|
|
102
|
+
this.#localeProvider = inject(SkyAppLocaleProvider);
|
|
103
|
+
this.#locale = toSignal(this.#localeProvider.getLocaleInfo().pipe(map((info) => info.locale)), { initialValue: this.#localeProvider.defaultLocale });
|
|
104
|
+
/**
|
|
105
|
+
* The ISO 4217 currency code used when `format` is `currency`. When unset,
|
|
106
|
+
* currency values format as `USD`.
|
|
107
|
+
*/
|
|
108
|
+
this.currencyCode = input(...(ngDevMode ? [undefined, { debugName: "currencyCode" }] : []));
|
|
109
|
+
/**
|
|
110
|
+
* The number of decimal places to display. When unset, the format's
|
|
111
|
+
* locale-aware default is used (for example, two places for most
|
|
112
|
+
* currencies).
|
|
113
|
+
*/
|
|
114
|
+
this.digits = input(undefined, { ...(ngDevMode ? { debugName: "digits" } : {}), transform: optionalNumberAttribute });
|
|
115
|
+
/**
|
|
116
|
+
* How to format the axis values in axis labels, tooltips, and the data table.
|
|
117
|
+
* The `percent` format expects fractional values, so `0.25` displays as
|
|
118
|
+
* `25%`.
|
|
119
|
+
* @default 'number'
|
|
120
|
+
*/
|
|
121
|
+
this.format = input('number', ...(ngDevMode ? [{ debugName: "format" }] : []));
|
|
122
|
+
/**
|
|
123
|
+
* Whether to hide the axis label.
|
|
124
|
+
*/
|
|
125
|
+
this.labelHidden = input(false, { ...(ngDevMode ? { debugName: "labelHidden" } : {}), transform: booleanAttribute });
|
|
126
|
+
/**
|
|
127
|
+
* The text of the axis label.
|
|
128
|
+
*/
|
|
129
|
+
this.labelText = input.required(...(ngDevMode ? [{ debugName: "labelText" }] : []));
|
|
130
|
+
/**
|
|
131
|
+
* The highest value to display on the axis. When unset, the axis scales to
|
|
132
|
+
* fit the plotted values.
|
|
133
|
+
*/
|
|
134
|
+
this.max = input(undefined, { ...(ngDevMode ? { debugName: "max" } : {}), transform: optionalNumberAttribute });
|
|
135
|
+
/**
|
|
136
|
+
* The lowest value to display on the axis. When unset, the axis scales to
|
|
137
|
+
* fit the plotted values.
|
|
138
|
+
*/
|
|
139
|
+
this.min = input(undefined, { ...(ngDevMode ? { debugName: "min" } : {}), transform: optionalNumberAttribute });
|
|
140
|
+
/**
|
|
141
|
+
* The scale type for the value axis.
|
|
142
|
+
* @default 'linear'
|
|
143
|
+
*/
|
|
144
|
+
this.scaleType = input('linear', ...(ngDevMode ? [{ debugName: "scaleType" }] : []));
|
|
145
|
+
/**
|
|
146
|
+
* Formats a numeric value according to this axis's `format`, `currencyCode`,
|
|
147
|
+
* and the current locale.
|
|
148
|
+
* @internal
|
|
149
|
+
*/
|
|
150
|
+
this.formatValue = computed(() => createSkyChartValueFormatter({
|
|
151
|
+
format: this.format(),
|
|
152
|
+
currencyCode: this.currencyCode(),
|
|
153
|
+
digits: this.digits(),
|
|
154
|
+
locale: this.#locale(),
|
|
155
|
+
}), ...(ngDevMode ? [{ debugName: "formatValue" }] : []));
|
|
156
|
+
}
|
|
157
|
+
#localeProvider;
|
|
158
|
+
#locale;
|
|
159
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: SkyChartAxisValue, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
160
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.2.0", type: SkyChartAxisValue, isStandalone: true, selector: "sky-chart-axis-value", inputs: { currencyCode: { classPropertyName: "currencyCode", publicName: "currencyCode", isSignal: true, isRequired: false, transformFunction: null }, digits: { classPropertyName: "digits", publicName: "digits", isSignal: true, isRequired: false, transformFunction: null }, format: { classPropertyName: "format", publicName: "format", isSignal: true, isRequired: false, transformFunction: null }, labelHidden: { classPropertyName: "labelHidden", publicName: "labelHidden", isSignal: true, isRequired: false, transformFunction: null }, labelText: { classPropertyName: "labelText", publicName: "labelText", isSignal: true, isRequired: true, transformFunction: null }, max: { classPropertyName: "max", publicName: "max", isSignal: true, isRequired: false, transformFunction: null }, min: { classPropertyName: "min", publicName: "min", isSignal: true, isRequired: false, transformFunction: null }, scaleType: { classPropertyName: "scaleType", publicName: "scaleType", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: '', isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
161
|
+
}
|
|
162
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: SkyChartAxisValue, decorators: [{
|
|
163
|
+
type: Component,
|
|
164
|
+
args: [{
|
|
165
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
166
|
+
selector: 'sky-chart-axis-value',
|
|
167
|
+
template: '',
|
|
168
|
+
}]
|
|
169
|
+
}], propDecorators: { currencyCode: [{ type: i0.Input, args: [{ isSignal: true, alias: "currencyCode", required: false }] }], digits: [{ type: i0.Input, args: [{ isSignal: true, alias: "digits", required: false }] }], format: [{ type: i0.Input, args: [{ isSignal: true, alias: "format", required: false }] }], labelHidden: [{ type: i0.Input, args: [{ isSignal: true, alias: "labelHidden", required: false }] }], labelText: [{ type: i0.Input, args: [{ isSignal: true, alias: "labelText", required: true }] }], max: [{ type: i0.Input, args: [{ isSignal: true, alias: "max", required: false }] }], min: [{ type: i0.Input, args: [{ isSignal: true, alias: "min", required: false }] }], scaleType: [{ type: i0.Input, args: [{ isSignal: true, alias: "scaleType", required: false }] }] } });
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* The scale key shared by every series' category axis. Cartesian charts always
|
|
173
|
+
* have exactly one category axis, so a single, stable key is sufficient.
|
|
174
|
+
*/
|
|
175
|
+
const CATEGORY_AXIS_ID = 'category';
|
|
176
|
+
/**
|
|
177
|
+
* The scale key of the value axis. Cartesian charts have exactly one value
|
|
178
|
+
* axis, so a single, stable key is sufficient.
|
|
179
|
+
*/
|
|
180
|
+
const VALUE_AXIS_ID = 'value';
|
|
181
|
+
/**
|
|
182
|
+
* Whether a series value is a floating `[start, end]` range rather than a
|
|
183
|
+
* single number. `Array.isArray` alone does not narrow readonly tuples, so
|
|
184
|
+
* the check is wrapped in an explicit type predicate.
|
|
185
|
+
*/
|
|
186
|
+
function isValueRange(value) {
|
|
187
|
+
return Array.isArray(value);
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Resolves the axes and series into the data a chart needs to render, or
|
|
191
|
+
* `undefined` when a required axis or series is missing.
|
|
192
|
+
*/
|
|
193
|
+
function resolveCartesianData(categoryAxis, valueAxis, series) {
|
|
194
|
+
if (categoryAxis === undefined ||
|
|
195
|
+
valueAxis === undefined ||
|
|
196
|
+
series.length === 0) {
|
|
197
|
+
return undefined;
|
|
198
|
+
}
|
|
199
|
+
return { categoryAxis, valueAxis, series };
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* Builds the tabular representation of a cartesian chart for the accessible
|
|
203
|
+
* data table, formatting each series' values with the value axis's format.
|
|
204
|
+
* Floating `[start, end]` ranges render as "start – end", and null values
|
|
205
|
+
* render as empty cells, mirroring the gaps in the plot.
|
|
206
|
+
*/
|
|
207
|
+
function buildCartesianTable(categoryAxis, series, formatValue) {
|
|
208
|
+
return {
|
|
209
|
+
categoryLabel: categoryAxis.labelText(),
|
|
210
|
+
categories: categoryAxis.categories(),
|
|
211
|
+
series: series.map((chartSeries) => ({
|
|
212
|
+
label: chartSeries.labelText(),
|
|
213
|
+
values: chartSeries
|
|
214
|
+
.values()
|
|
215
|
+
.map((value) => formatCartesianValue(value, formatValue)),
|
|
216
|
+
})),
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* Formats a single plotted value: numbers use the value axis's format, a
|
|
221
|
+
* floating `[start, end]` range formats as "start – end", and `null` (a gap)
|
|
222
|
+
* formats as an empty string.
|
|
223
|
+
*/
|
|
224
|
+
function formatCartesianValue(value, formatValue) {
|
|
225
|
+
if (value === null) {
|
|
226
|
+
return '';
|
|
227
|
+
}
|
|
228
|
+
if (isValueRange(value)) {
|
|
229
|
+
return `${formatValue(value[0])} – ${formatValue(value[1])}`;
|
|
230
|
+
}
|
|
231
|
+
return formatValue(value);
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* Builds the themed styling shared by every cartesian scale from the resolved
|
|
235
|
+
* theme styles. Building it once keeps every axis visually consistent.
|
|
236
|
+
*/
|
|
237
|
+
function buildThemedScaleStyle(themeStyles) {
|
|
238
|
+
const { font, text, axis } = themeStyles;
|
|
239
|
+
return {
|
|
240
|
+
grid: {
|
|
241
|
+
color: axis.gridlineColor,
|
|
242
|
+
tickColor: axis.gridlineColor,
|
|
243
|
+
tickLength: axis.tickLength,
|
|
244
|
+
},
|
|
245
|
+
border: {
|
|
246
|
+
color: axis.lineColor,
|
|
247
|
+
},
|
|
248
|
+
ticks: {
|
|
249
|
+
color: text.color,
|
|
250
|
+
font: {
|
|
251
|
+
size: font.size,
|
|
252
|
+
family: font.family,
|
|
253
|
+
weight: font.weight,
|
|
254
|
+
},
|
|
255
|
+
},
|
|
256
|
+
title: {
|
|
257
|
+
color: text.deemphasizedColor,
|
|
258
|
+
font: {
|
|
259
|
+
size: font.size,
|
|
260
|
+
family: font.family,
|
|
261
|
+
},
|
|
262
|
+
padding: {
|
|
263
|
+
top: axis.titleGap,
|
|
264
|
+
bottom: axis.titleGap,
|
|
265
|
+
},
|
|
266
|
+
},
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
/**
|
|
270
|
+
* Restricts a logarithmic value axis's ticks to powers of ten. Chart.js's
|
|
271
|
+
* logarithmic tick generator inserts intermediate ticks within each decade,
|
|
272
|
+
* whose gridlines render at visually uneven intervals; decade-only gridlines
|
|
273
|
+
* read evenly. When the generated ticks span fewer than two decades, they are
|
|
274
|
+
* kept as-is so a narrow range is not left nearly unlabeled.
|
|
275
|
+
*/
|
|
276
|
+
function keepDecadeLogTicks(axis) {
|
|
277
|
+
const decadeTicks = axis.ticks.filter((tick) => {
|
|
278
|
+
const magnitude = Math.log10(tick.value);
|
|
279
|
+
return Math.abs(magnitude - Math.round(magnitude)) < 1e-9;
|
|
280
|
+
});
|
|
281
|
+
if (decadeTicks.length >= 2) {
|
|
282
|
+
axis.ticks = decadeTicks;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
/**
|
|
286
|
+
* Builds the category and value axis scales for a cartesian chart. The category
|
|
287
|
+
* axis draws no grid lines across the chart area, and the value axis draws them
|
|
288
|
+
* to aid value comparison. When `stacked` is set, both the category and value
|
|
289
|
+
* scales stack so that series accumulate into a single bar per category.
|
|
290
|
+
*/
|
|
291
|
+
function buildCartesianScales(options) {
|
|
292
|
+
const { categoryAxis, valueAxis, isHorizontal, isStacked = false, themeStyles, } = options;
|
|
293
|
+
const indexAxis = isHorizontal ? 'y' : 'x';
|
|
294
|
+
const valueDirection = isHorizontal ? 'x' : 'y';
|
|
295
|
+
const base = buildThemedScaleStyle(themeStyles);
|
|
296
|
+
const formatValue = valueAxis.formatValue();
|
|
297
|
+
const scaleType = valueAxis.scaleType();
|
|
298
|
+
return {
|
|
299
|
+
[CATEGORY_AXIS_ID]: {
|
|
300
|
+
type: 'category',
|
|
301
|
+
axis: indexAxis,
|
|
302
|
+
position: isHorizontal ? 'left' : 'bottom',
|
|
303
|
+
stacked: isStacked,
|
|
304
|
+
grid: {
|
|
305
|
+
display: true,
|
|
306
|
+
drawTicks: true,
|
|
307
|
+
// The category axis marks discrete groups, so grid lines running
|
|
308
|
+
// between the bars add clutter without aiding value comparison.
|
|
309
|
+
drawOnChartArea: false,
|
|
310
|
+
...base.grid,
|
|
311
|
+
},
|
|
312
|
+
border: {
|
|
313
|
+
display: true,
|
|
314
|
+
...base.border,
|
|
315
|
+
},
|
|
316
|
+
ticks: {
|
|
317
|
+
...base.ticks,
|
|
318
|
+
major: { enabled: true },
|
|
319
|
+
},
|
|
320
|
+
title: {
|
|
321
|
+
display: !categoryAxis.labelHidden(),
|
|
322
|
+
text: categoryAxis.labelText(),
|
|
323
|
+
...base.title,
|
|
324
|
+
},
|
|
325
|
+
},
|
|
326
|
+
[VALUE_AXIS_ID]: {
|
|
327
|
+
type: scaleType,
|
|
328
|
+
axis: valueDirection,
|
|
329
|
+
position: isHorizontal ? 'bottom' : 'left',
|
|
330
|
+
stacked: isStacked,
|
|
331
|
+
min: valueAxis.min(),
|
|
332
|
+
max: valueAxis.max(),
|
|
333
|
+
...(scaleType === 'logarithmic' && {
|
|
334
|
+
afterBuildTicks: keepDecadeLogTicks,
|
|
335
|
+
}),
|
|
336
|
+
grid: {
|
|
337
|
+
...base.grid,
|
|
338
|
+
drawOnChartArea: true,
|
|
339
|
+
},
|
|
340
|
+
border: {
|
|
341
|
+
...base.border,
|
|
342
|
+
},
|
|
343
|
+
ticks: {
|
|
344
|
+
...base.ticks,
|
|
345
|
+
padding: 0,
|
|
346
|
+
callback: (tickValue) => formatValue(Number(tickValue)),
|
|
347
|
+
},
|
|
348
|
+
title: {
|
|
349
|
+
display: !valueAxis.labelHidden(),
|
|
350
|
+
text: valueAxis.labelText(),
|
|
351
|
+
...base.title,
|
|
352
|
+
},
|
|
353
|
+
},
|
|
354
|
+
};
|
|
355
|
+
}
|
|
356
|
+
/**
|
|
357
|
+
* Builds a tooltip `label` callback that formats each point's value using the
|
|
358
|
+
* value axis's format.
|
|
359
|
+
*/
|
|
360
|
+
function buildValueTooltipLabel(formatValue, valueDirection) {
|
|
361
|
+
return (context) => {
|
|
362
|
+
const item = context;
|
|
363
|
+
// A floating bar carries its [start, end] range in the raw data element;
|
|
364
|
+
// `parsed` holds only the end value.
|
|
365
|
+
const formatted = isValueRange(item.raw)
|
|
366
|
+
? formatCartesianValue(item.raw, formatValue)
|
|
367
|
+
: formatValue(item.parsed[valueDirection] ?? 0);
|
|
368
|
+
const label = item.dataset.label;
|
|
369
|
+
return label ? `${label}: ${formatted}` : formatted;
|
|
370
|
+
};
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
Chart.register(BarController, BarElement, CategoryScale, LinearScale, LogarithmicScale, Legend, Tooltip);
|
|
374
|
+
/**
|
|
375
|
+
* Renders a Chart.js chart onto a canvas and manages its lifecycle (create,
|
|
376
|
+
* update, and destroy) from a reactive configuration. Plot components such as
|
|
377
|
+
* `sky-chart-bar` build the configuration and delegate rendering here.
|
|
378
|
+
* @internal
|
|
379
|
+
*/
|
|
380
|
+
class SkyChartJs {
|
|
381
|
+
#chart;
|
|
382
|
+
constructor() {
|
|
383
|
+
/**
|
|
384
|
+
* The Chart.js configuration to render.
|
|
385
|
+
*/
|
|
386
|
+
this.config = input.required(...(ngDevMode ? [{ debugName: "config" }] : []));
|
|
387
|
+
this.chartRef = viewChild.required('chartRef', {
|
|
388
|
+
read: ElementRef,
|
|
389
|
+
});
|
|
390
|
+
inject(DestroyRef).onDestroy(() => {
|
|
391
|
+
this.#chart?.destroy();
|
|
392
|
+
this.#chart = undefined;
|
|
393
|
+
});
|
|
394
|
+
afterRenderEffect(() => {
|
|
395
|
+
const config = this.config();
|
|
396
|
+
if (this.#chart) {
|
|
397
|
+
this.#chart.data = config.data;
|
|
398
|
+
this.#chart.options = config.options;
|
|
399
|
+
this.#chart.update();
|
|
400
|
+
}
|
|
401
|
+
else {
|
|
402
|
+
this.#chart = new Chart(this.chartRef().nativeElement, config);
|
|
403
|
+
}
|
|
404
|
+
});
|
|
405
|
+
}
|
|
406
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: SkyChartJs, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
407
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "21.2.0", type: SkyChartJs, isStandalone: true, selector: "sky-chart-js", inputs: { config: { classPropertyName: "config", publicName: "config", isSignal: true, isRequired: true, transformFunction: null } }, viewQueries: [{ propertyName: "chartRef", first: true, predicate: ["chartRef"], descendants: true, read: ElementRef, isSignal: true }], ngImport: i0, template: `<canvas #chartRef aria-hidden="true"></canvas>`, isInline: true, styles: [":host{display:block;position:relative}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
408
|
+
}
|
|
409
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: SkyChartJs, decorators: [{
|
|
410
|
+
type: Component,
|
|
411
|
+
args: [{ changeDetection: ChangeDetectionStrategy.OnPush, selector: 'sky-chart-js', template: `<canvas #chartRef aria-hidden="true"></canvas>`, styles: [":host{display:block;position:relative}\n"] }]
|
|
412
|
+
}], ctorParameters: () => [], propDecorators: { config: [{ type: i0.Input, args: [{ isSignal: true, alias: "config", required: true }] }], chartRef: [{ type: i0.ViewChild, args: ['chartRef', { ...{
|
|
413
|
+
read: ElementRef,
|
|
414
|
+
}, isSignal: true }] }] } });
|
|
415
|
+
|
|
416
|
+
/**
|
|
417
|
+
* Builds the Chart.js `options` shared by every SKY chart type: responsiveness,
|
|
418
|
+
* layout, interaction, hover, animation, and the themed legend and tooltip.
|
|
419
|
+
*/
|
|
420
|
+
function buildBaseChartJsOptions(themeStyles) {
|
|
421
|
+
const { font, text, tooltip } = themeStyles;
|
|
422
|
+
const bodyFont = {
|
|
423
|
+
family: font.family,
|
|
424
|
+
size: font.size,
|
|
425
|
+
weight: font.weight,
|
|
426
|
+
lineHeight: text.lineHeight,
|
|
427
|
+
};
|
|
428
|
+
const options = {
|
|
429
|
+
// Responsiveness: fill the container and re-layout on resize.
|
|
430
|
+
responsive: true,
|
|
431
|
+
maintainAspectRatio: false,
|
|
432
|
+
layout: { padding: 0 },
|
|
433
|
+
// Interaction: hovering and tooltips target the nearest point precisely.
|
|
434
|
+
interaction: { mode: 'nearest', intersect: true },
|
|
435
|
+
hover: { mode: 'nearest', intersect: true },
|
|
436
|
+
animation: { duration: 400, easing: 'easeInOutQuart' },
|
|
437
|
+
plugins: {
|
|
438
|
+
legend: {
|
|
439
|
+
position: 'bottom',
|
|
440
|
+
labels: {
|
|
441
|
+
usePointStyle: true,
|
|
442
|
+
pointStyle: 'circle',
|
|
443
|
+
color: text.color,
|
|
444
|
+
font: bodyFont,
|
|
445
|
+
},
|
|
446
|
+
},
|
|
447
|
+
tooltip: {
|
|
448
|
+
enabled: true,
|
|
449
|
+
position: 'average',
|
|
450
|
+
displayColors: true,
|
|
451
|
+
usePointStyle: true,
|
|
452
|
+
// Interaction
|
|
453
|
+
mode: 'index',
|
|
454
|
+
intersect: false,
|
|
455
|
+
// Typography
|
|
456
|
+
titleColor: text.color,
|
|
457
|
+
titleFont: {
|
|
458
|
+
...bodyFont,
|
|
459
|
+
weight: font.emphasizedWeight,
|
|
460
|
+
},
|
|
461
|
+
bodyColor: text.color,
|
|
462
|
+
bodyFont,
|
|
463
|
+
footerColor: text.color,
|
|
464
|
+
footerFont: bodyFont,
|
|
465
|
+
// Container
|
|
466
|
+
padding: { ...tooltip.inset },
|
|
467
|
+
cornerRadius: tooltip.cornerRadius,
|
|
468
|
+
borderWidth: tooltip.borderWidth,
|
|
469
|
+
caretSize: 8,
|
|
470
|
+
caretPadding: 4,
|
|
471
|
+
// Color-swatch icon
|
|
472
|
+
boxHeight: tooltip.iconSize,
|
|
473
|
+
boxWidth: tooltip.iconSize,
|
|
474
|
+
boxPadding: tooltip.iconGap,
|
|
475
|
+
multiKeyBackground: 'transparent',
|
|
476
|
+
// Text spacing.
|
|
477
|
+
titleMarginBottom: tooltip.titleGap,
|
|
478
|
+
bodySpacing: tooltip.bodyGap,
|
|
479
|
+
footerMarginTop: tooltip.titleGap,
|
|
480
|
+
// Colors.
|
|
481
|
+
backgroundColor: tooltip.backgroundColor,
|
|
482
|
+
borderColor: tooltip.borderColor,
|
|
483
|
+
},
|
|
484
|
+
},
|
|
485
|
+
};
|
|
486
|
+
return options;
|
|
487
|
+
}
|
|
488
|
+
/**
|
|
489
|
+
* Extends the shared, themed base options with a chart-type-specific configuration.
|
|
490
|
+
* @internal
|
|
491
|
+
*/
|
|
492
|
+
function extendBaseChartJsConfig(themeStyles, overrides) {
|
|
493
|
+
const base = buildBaseChartJsOptions(themeStyles);
|
|
494
|
+
const options = {
|
|
495
|
+
...base,
|
|
496
|
+
...overrides.options,
|
|
497
|
+
plugins: {
|
|
498
|
+
...base.plugins,
|
|
499
|
+
...overrides.options.plugins,
|
|
500
|
+
legend: {
|
|
501
|
+
...base.plugins?.legend,
|
|
502
|
+
...overrides.options.plugins?.legend,
|
|
503
|
+
},
|
|
504
|
+
tooltip: {
|
|
505
|
+
...base.plugins?.tooltip,
|
|
506
|
+
...overrides.options.plugins?.tooltip,
|
|
507
|
+
},
|
|
508
|
+
},
|
|
509
|
+
};
|
|
510
|
+
return {
|
|
511
|
+
...overrides,
|
|
512
|
+
options,
|
|
513
|
+
};
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
/**
|
|
517
|
+
* Bridges a chart's tabular representation from the plotted chart component to
|
|
518
|
+
* the data table modal. Provided at the `sky-chart` level so each chart has its
|
|
519
|
+
* own instance.
|
|
520
|
+
* @internal
|
|
521
|
+
*/
|
|
522
|
+
class SkyChartTableService {
|
|
523
|
+
constructor() {
|
|
524
|
+
/**
|
|
525
|
+
* The current tabular representation of the chart, or `undefined` when the
|
|
526
|
+
* chart has no data to represent.
|
|
527
|
+
*/
|
|
528
|
+
this.table = signal(undefined, ...(ngDevMode ? [{ debugName: "table" }] : []));
|
|
529
|
+
/**
|
|
530
|
+
* The current accessible summary of the chart, or `undefined` when the chart
|
|
531
|
+
* has no data to represent.
|
|
532
|
+
*/
|
|
533
|
+
this.summary = signal(undefined, ...(ngDevMode ? [{ debugName: "summary" }] : []));
|
|
534
|
+
}
|
|
535
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: SkyChartTableService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
536
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: SkyChartTableService }); }
|
|
537
|
+
}
|
|
538
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: SkyChartTableService, decorators: [{
|
|
539
|
+
type: Injectable
|
|
540
|
+
}] });
|
|
541
|
+
|
|
542
|
+
/**
|
|
543
|
+
* Resolves the active SKY theme's chart styling for the given host element.
|
|
544
|
+
* When the SKY theme styles are not loaded, the chart renders un-themed with
|
|
545
|
+
* Chart.js defaults.
|
|
546
|
+
* @internal
|
|
547
|
+
*/
|
|
548
|
+
function resolveChartThemeStyles(host) {
|
|
549
|
+
const styles = getComputedStyle(host);
|
|
550
|
+
// Custom properties keep `calc()` expressions unevaluated (for example, the
|
|
551
|
+
// SKY line-height tokens are authored as `calc(20/15)`). The probe assigns
|
|
552
|
+
// those raw values to a standard property, which the browser evaluates, and
|
|
553
|
+
// reads the resolved value back. It is created lazily — only for values the
|
|
554
|
+
// fast path cannot parse — and torn down once resolution finishes.
|
|
555
|
+
const probe = createTokenProbe(host);
|
|
556
|
+
// `rem` conversions share one root font-size read; see `remToPx`.
|
|
557
|
+
const rootFontSize = Number.parseFloat(getComputedStyle(host.ownerDocument.documentElement).fontSize);
|
|
558
|
+
try {
|
|
559
|
+
// The chart's height bounds are authored once, as custom properties on
|
|
560
|
+
// the `sky-chart` host (see `chart.scss`), so the plot heights resolved
|
|
561
|
+
// here always match the loading state's reserved height. Plots must
|
|
562
|
+
// render inside `sky-chart` (enforced by `SkyChartPlot`), so the
|
|
563
|
+
// properties are always in scope; like the theme tokens, they only fail
|
|
564
|
+
// to resolve in a genuinely broken setup.
|
|
565
|
+
const minHeight = readNumber(styles, probe, '--sky-chart-height-min', rootFontSize);
|
|
566
|
+
const maxHeight = readNumber(styles, probe, '--sky-chart-height-max', rootFontSize);
|
|
567
|
+
const viewportHeight = readString(styles, '--sky-chart-height-viewport');
|
|
568
|
+
return {
|
|
569
|
+
font: {
|
|
570
|
+
family: readString(styles, '--sky-font-family-primary'),
|
|
571
|
+
size: readNumber(styles, probe, '--sky-font-size-body-s', rootFontSize),
|
|
572
|
+
weight: readNumber(styles, probe, '--sky-font-style-body-s', rootFontSize),
|
|
573
|
+
emphasizedWeight: readNumber(styles, probe, '--sky-font-style-emphasized', rootFontSize),
|
|
574
|
+
},
|
|
575
|
+
text: {
|
|
576
|
+
color: readString(styles, '--sky-color-text-default'),
|
|
577
|
+
deemphasizedColor: readString(styles, '--sky-color-text-deemphasized'),
|
|
578
|
+
lineHeight: readLineHeight(styles, probe),
|
|
579
|
+
},
|
|
580
|
+
height: {
|
|
581
|
+
min: minHeight,
|
|
582
|
+
max: maxHeight,
|
|
583
|
+
default: `clamp(${minHeight}px, ${viewportHeight}, ${maxHeight}px)`,
|
|
584
|
+
},
|
|
585
|
+
axis: {
|
|
586
|
+
lineColor: readString(styles, '--sky-color-viz-axis'),
|
|
587
|
+
gridlineColor: readString(styles, '--sky-color-viz-gridline'),
|
|
588
|
+
tickLength: readNumber(styles, probe, '--sky-size-chart-tick_length-measure', rootFontSize),
|
|
589
|
+
titleGap: readNumber(styles, probe, '--sky-space-stacked-xs', rootFontSize),
|
|
590
|
+
},
|
|
591
|
+
series: {
|
|
592
|
+
categoricalPalette: Array.from({ length: 8 }, (_, index) => readString(styles, `--sky-color-viz-category-${index + 1}`)),
|
|
593
|
+
},
|
|
594
|
+
tooltip: {
|
|
595
|
+
backgroundColor: readString(styles, '--sky-color-background-container-base'),
|
|
596
|
+
borderColor: readString(styles, '--sky-color-border-container-base'),
|
|
597
|
+
borderWidth: readNumber(styles, probe, '--sky-border-width-container-base', rootFontSize),
|
|
598
|
+
cornerRadius: readNumber(styles, probe, '--sky-border-radius-s', rootFontSize),
|
|
599
|
+
inset: {
|
|
600
|
+
top: readNumber(styles, probe, '--sky-comp-chart-tooltip-space-inset-top', rootFontSize),
|
|
601
|
+
right: readNumber(styles, probe, '--sky-comp-chart-tooltip-space-inset-right', rootFontSize),
|
|
602
|
+
bottom: readNumber(styles, probe, '--sky-comp-chart-tooltip-space-inset-bottom', rootFontSize),
|
|
603
|
+
left: readNumber(styles, probe, '--sky-comp-chart-tooltip-space-inset-left', rootFontSize),
|
|
604
|
+
},
|
|
605
|
+
iconSize: readNumber(styles, probe, '--sky-size-icon-xs', rootFontSize),
|
|
606
|
+
iconGap: readNumber(styles, probe, '--sky-space-gap-icon-s', rootFontSize),
|
|
607
|
+
titleGap: readNumber(styles, probe, '--sky-space-stacked-s', rootFontSize),
|
|
608
|
+
bodyGap: readNumber(styles, probe, '--sky-space-stacked-0', rootFontSize),
|
|
609
|
+
},
|
|
610
|
+
bar: {
|
|
611
|
+
borderColor: readString(styles, '--sky-color-background-container-base'),
|
|
612
|
+
borderRadius: readNumber(styles, probe, '--sky-border-radius-xs', rootFontSize),
|
|
613
|
+
vertical: {
|
|
614
|
+
baseBarThickness: remToPx('2rem', rootFontSize),
|
|
615
|
+
minBarThickness: remToPx('0.75rem', rootFontSize),
|
|
616
|
+
maxBarThickness: remToPx('7.5rem', rootFontSize),
|
|
617
|
+
},
|
|
618
|
+
horizontal: {
|
|
619
|
+
minBarThickness: remToPx('0.75rem', rootFontSize),
|
|
620
|
+
maxBarThickness: remToPx('1rem', rootFontSize),
|
|
621
|
+
minCategoryGap: remToPx('0.5rem', rootFontSize),
|
|
622
|
+
},
|
|
623
|
+
},
|
|
624
|
+
};
|
|
625
|
+
}
|
|
626
|
+
finally {
|
|
627
|
+
probe.destroy();
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
/**
|
|
631
|
+
* Derives the default-theme override property for a SKY theme token. The
|
|
632
|
+
* modern theme's `--sky-*` tokens are not defined in the SKY UX default
|
|
633
|
+
* theme, so `sky-chart` provides `--sky-override-chart-*` values scoped to
|
|
634
|
+
* the default theme (see `chart.scss`). The override wins when present,
|
|
635
|
+
* matching the `var(--sky-override-x, var(--sky-x))` convention used in
|
|
636
|
+
* component CSS.
|
|
637
|
+
*/
|
|
638
|
+
function overrideProperty(property) {
|
|
639
|
+
return `--sky-override-chart-${property.slice('--sky-'.length)}`;
|
|
640
|
+
}
|
|
641
|
+
/**
|
|
642
|
+
* Reads a CSS custom property — preferring its default-theme override —
|
|
643
|
+
* returning an empty string when neither is set. An empty string reaching
|
|
644
|
+
* Chart.js renders un-themed rather than broken.
|
|
645
|
+
*/
|
|
646
|
+
function readString(styles, property) {
|
|
647
|
+
return (styles.getPropertyValue(overrideProperty(property)).trim() ||
|
|
648
|
+
styles.getPropertyValue(property).trim());
|
|
649
|
+
}
|
|
650
|
+
/**
|
|
651
|
+
* Reads a numeric CSS custom property — preferring its default-theme
|
|
652
|
+
* override — as a number, converting `rem` values to pixels using the root
|
|
653
|
+
* font size. Values the fast path cannot parse (such as `calc()` lengths) are
|
|
654
|
+
* resolved through the probe. Every token has a default defined in the
|
|
655
|
+
* `sky-default-overrides` mixin in `chart.scss`, so a value only fails to
|
|
656
|
+
* resolve — returning `NaN` — in a genuinely broken theme.
|
|
657
|
+
*/
|
|
658
|
+
function readNumber(styles, probe, property, rootFontSize) {
|
|
659
|
+
const raw = readString(styles, property);
|
|
660
|
+
if (raw === '') {
|
|
661
|
+
return Number.NaN;
|
|
662
|
+
}
|
|
663
|
+
const value = Number.parseFloat(raw);
|
|
664
|
+
if (!Number.isNaN(value)) {
|
|
665
|
+
return raw.endsWith('rem') ? remToPx(raw, rootFontSize) : value;
|
|
666
|
+
}
|
|
667
|
+
// A non-numeric literal such as a `calc()` length; resolve it via the probe.
|
|
668
|
+
return probe.resolveLength(raw) ?? Number.NaN;
|
|
669
|
+
}
|
|
670
|
+
/**
|
|
671
|
+
* Converts a `rem` length to pixels using the document root font size. `rem` is
|
|
672
|
+
* defined relative to the root element (never the host), so the root font size
|
|
673
|
+
* is the correct reference — resolved once per `resolveChartThemeStyles` call
|
|
674
|
+
* and shared by every conversion. Chart.js reasons in pixels when it lays out
|
|
675
|
+
* a canvas, so `rem`-based sizing has to be resolved before it reaches the
|
|
676
|
+
* chart.
|
|
677
|
+
*/
|
|
678
|
+
function remToPx(rem, rootFontSize) {
|
|
679
|
+
return Number.parseFloat(rem) * rootFontSize;
|
|
680
|
+
}
|
|
681
|
+
/**
|
|
682
|
+
* Reads the body-s line height as a multiple of the font size. A plain
|
|
683
|
+
* numeric value (such as the default theme's override) parses directly; the
|
|
684
|
+
* modern theme's token is a `calc()` expression that `getComputedStyle`
|
|
685
|
+
* leaves unevaluated on custom properties, so it is resolved through the
|
|
686
|
+
* probe. Its default is defined in the `sky-default-overrides` mixin in
|
|
687
|
+
* `chart.scss`, so it only fails to resolve — returning `NaN` — in a
|
|
688
|
+
* genuinely broken theme.
|
|
689
|
+
*/
|
|
690
|
+
function readLineHeight(styles, probe) {
|
|
691
|
+
const raw = readString(styles, '--sky-font-line_height-body-s');
|
|
692
|
+
if (raw === '') {
|
|
693
|
+
return Number.NaN;
|
|
694
|
+
}
|
|
695
|
+
const value = Number.parseFloat(raw);
|
|
696
|
+
if (!Number.isNaN(value)) {
|
|
697
|
+
return value;
|
|
698
|
+
}
|
|
699
|
+
// A non-numeric expression such as `calc(20/15)`; resolve it via the probe.
|
|
700
|
+
return probe.resolveNumber(raw) ?? Number.NaN;
|
|
701
|
+
}
|
|
702
|
+
/**
|
|
703
|
+
* Creates a {@link SkyChartTokenProbe} bound to `host`. The probe element is
|
|
704
|
+
* created on first use — so resolving only fast-path values costs nothing —
|
|
705
|
+
* and inherits `host`'s theme context.
|
|
706
|
+
*/
|
|
707
|
+
function createTokenProbe(host) {
|
|
708
|
+
let element;
|
|
709
|
+
function probeElement() {
|
|
710
|
+
if (!element) {
|
|
711
|
+
element = document.createElement('span');
|
|
712
|
+
element.style.position = 'absolute';
|
|
713
|
+
element.style.width = '0';
|
|
714
|
+
element.style.height = '0';
|
|
715
|
+
element.style.overflow = 'hidden';
|
|
716
|
+
element.style.visibility = 'hidden';
|
|
717
|
+
host.appendChild(element);
|
|
718
|
+
}
|
|
719
|
+
return element;
|
|
720
|
+
}
|
|
721
|
+
// Assigns the raw value to a carrier property the browser evaluates, then
|
|
722
|
+
// reads the resolved value back. An invalid value is rejected by the CSSOM,
|
|
723
|
+
// leaving the carrier empty, which signals it could not be resolved.
|
|
724
|
+
function resolveWith(carrier, value) {
|
|
725
|
+
const el = probeElement();
|
|
726
|
+
el.style[carrier] = '';
|
|
727
|
+
el.style[carrier] = value;
|
|
728
|
+
return el.style[carrier] === ''
|
|
729
|
+
? undefined
|
|
730
|
+
: Number.parseFloat(getComputedStyle(el)[carrier]);
|
|
731
|
+
}
|
|
732
|
+
return {
|
|
733
|
+
resolveLength: (value) => resolveWith('paddingLeft', value),
|
|
734
|
+
resolveNumber: (value) => resolveWith('flexGrow', value),
|
|
735
|
+
destroy: () => element?.remove(),
|
|
736
|
+
};
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
/**
|
|
740
|
+
* Base class for chart plot components (for example, `sky-chart-bar`). Owns the
|
|
741
|
+
* bridge that publishes each plot's tabular representation to the accessible
|
|
742
|
+
* data table, so every plot type shares the same lifecycle. Subclasses
|
|
743
|
+
* implement `getChartTable`, `getAccessibleSummary`, and their own rendering.
|
|
744
|
+
*
|
|
745
|
+
* Plots must be rendered inside `sky-chart`: the wrapper provides the data
|
|
746
|
+
* table bridge and the default-theme styling the plot resolves its themed
|
|
747
|
+
* values from.
|
|
748
|
+
* @internal
|
|
749
|
+
*/
|
|
750
|
+
class SkyChartPlot {
|
|
751
|
+
#elementRef = inject(ElementRef);
|
|
752
|
+
#tableSvc;
|
|
753
|
+
constructor() {
|
|
754
|
+
const tableSvc = inject(SkyChartTableService, { optional: true });
|
|
755
|
+
if (!tableSvc) {
|
|
756
|
+
const tagName = this.#elementRef.nativeElement.tagName.toLowerCase();
|
|
757
|
+
throw new Error(`The <${tagName}> component must be rendered inside a <sky-chart> ` +
|
|
758
|
+
'component.');
|
|
759
|
+
}
|
|
760
|
+
this.#tableSvc = tableSvc;
|
|
761
|
+
inject(DestroyRef).onDestroy(() => {
|
|
762
|
+
this.#tableSvc.table.set(undefined);
|
|
763
|
+
this.#tableSvc.summary.set(undefined);
|
|
764
|
+
});
|
|
765
|
+
afterRenderEffect(() => {
|
|
766
|
+
this.#tableSvc.table.set(this.getChartTable());
|
|
767
|
+
this.#tableSvc.summary.set(this.getAccessibleSummary());
|
|
768
|
+
});
|
|
769
|
+
}
|
|
770
|
+
/**
|
|
771
|
+
* Resolves the active theme's chart styling against this plot's element.
|
|
772
|
+
* Chart.js renders to a canvas that cannot read CSS variables, so themed
|
|
773
|
+
* tokens must be resolved to concrete values against the DOM.
|
|
774
|
+
*/
|
|
775
|
+
getThemeStyles() {
|
|
776
|
+
return resolveChartThemeStyles(this.#elementRef.nativeElement);
|
|
777
|
+
}
|
|
778
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: SkyChartPlot, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
779
|
+
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.0", type: SkyChartPlot, isStandalone: true, ngImport: i0 }); }
|
|
780
|
+
}
|
|
781
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: SkyChartPlot, decorators: [{
|
|
782
|
+
type: Directive
|
|
783
|
+
}], ctorParameters: () => [] });
|
|
784
|
+
|
|
785
|
+
/**
|
|
786
|
+
* Defines a single series of values to plot on a bar chart, aligned to the
|
|
787
|
+
* category axis by index.
|
|
788
|
+
*
|
|
789
|
+
* @preview
|
|
790
|
+
*/
|
|
791
|
+
class SkyChartBarSeries {
|
|
792
|
+
constructor() {
|
|
793
|
+
/**
|
|
794
|
+
* The text that identifies this series in the legend and tooltips.
|
|
795
|
+
*/
|
|
796
|
+
this.labelText = input.required(...(ngDevMode ? [{ debugName: "labelText" }] : []));
|
|
797
|
+
/**
|
|
798
|
+
* The stack this series belongs to. When a bar chart's `seriesLayout`
|
|
799
|
+
* is `stacked`, series that share the same `stackId` value accumulate into
|
|
800
|
+
* a single bar per category, and series with different `stackId` values
|
|
801
|
+
* are placed side by side. Omit to stack every series into one bar per
|
|
802
|
+
* category. Has no effect when `seriesLayout` is `grouped`.
|
|
803
|
+
*/
|
|
804
|
+
this.stackId = input(...(ngDevMode ? [undefined, { debugName: "stackId" }] : []));
|
|
805
|
+
/**
|
|
806
|
+
* The values for this series, aligned to the category axis categories by
|
|
807
|
+
* index. A number renders a standard bar measured from the value axis's
|
|
808
|
+
* baseline, a `[start, end]` tuple renders a floating bar spanning the two
|
|
809
|
+
* values, and a `null` value renders a gap in the chart and an empty cell
|
|
810
|
+
* in the data table.
|
|
811
|
+
*/
|
|
812
|
+
this.values = input.required(...(ngDevMode ? [{ debugName: "values" }] : []));
|
|
813
|
+
}
|
|
814
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: SkyChartBarSeries, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
815
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.2.0", type: SkyChartBarSeries, isStandalone: true, selector: "sky-chart-bar-series", inputs: { labelText: { classPropertyName: "labelText", publicName: "labelText", isSignal: true, isRequired: true, transformFunction: null }, stackId: { classPropertyName: "stackId", publicName: "stackId", isSignal: true, isRequired: false, transformFunction: null }, values: { classPropertyName: "values", publicName: "values", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0, template: '', isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
816
|
+
}
|
|
817
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: SkyChartBarSeries, decorators: [{
|
|
818
|
+
type: Component,
|
|
819
|
+
args: [{
|
|
820
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
821
|
+
selector: 'sky-chart-bar-series',
|
|
822
|
+
template: '',
|
|
823
|
+
}]
|
|
824
|
+
}], propDecorators: { labelText: [{ type: i0.Input, args: [{ isSignal: true, alias: "labelText", required: true }] }], stackId: [{ type: i0.Input, args: [{ isSignal: true, alias: "stackId", required: false }] }], values: [{ type: i0.Input, args: [{ isSignal: true, alias: "values", required: true }] }] } });
|
|
825
|
+
|
|
826
|
+
/**
|
|
827
|
+
* Renders a bar chart from a category axis, a value axis, and one or more
|
|
828
|
+
* series.
|
|
829
|
+
*
|
|
830
|
+
* @preview
|
|
831
|
+
*/
|
|
832
|
+
class SkyChartBar extends SkyChartPlot {
|
|
833
|
+
#themeSettings;
|
|
834
|
+
constructor() {
|
|
835
|
+
super();
|
|
836
|
+
this.#themeSettings = toSignal(inject(SkyThemeService, { optional: true })?.settingsChange.pipe(map$1((change) => change.currentSettings)) ?? EMPTY, { initialValue: undefined });
|
|
837
|
+
/**
|
|
838
|
+
* The orientation of the bars.
|
|
839
|
+
* @default 'vertical'
|
|
840
|
+
*/
|
|
841
|
+
this.orientation = input('vertical', ...(ngDevMode ? [{ debugName: "orientation" }] : []));
|
|
842
|
+
/**
|
|
843
|
+
* How the bars of multiple series are arranged within each category.
|
|
844
|
+
* `grouped` places the series' bars side by side; `stacked` accumulates the
|
|
845
|
+
* bars into a single bar per category. When `stacked`, assign each series a
|
|
846
|
+
* `stackId` value to subdivide the bar into side-by-side stacks (grouped,
|
|
847
|
+
* stacked bars). This has no visible effect when the chart has a single
|
|
848
|
+
* series.
|
|
849
|
+
* @default 'grouped'
|
|
850
|
+
*/
|
|
851
|
+
this.seriesLayout = input('grouped', ...(ngDevMode ? [{ debugName: "seriesLayout" }] : []));
|
|
852
|
+
this.categoryAxis = contentChild(SkyChartAxisCategory, ...(ngDevMode ? [{ debugName: "categoryAxis" }] : []));
|
|
853
|
+
this.valueAxis = contentChild(SkyChartAxisValue, ...(ngDevMode ? [{ debugName: "valueAxis" }] : []));
|
|
854
|
+
this.series = contentChildren(SkyChartBarSeries, ...(ngDevMode ? [{ debugName: "series" }] : []));
|
|
855
|
+
this.chartJsConfig = computed(() => this.#getChartJsConfig(), ...(ngDevMode ? [{ debugName: "chartJsConfig" }] : []));
|
|
856
|
+
/**
|
|
857
|
+
* The height to apply to the rendered chart. Chart.js runs with
|
|
858
|
+
* `maintainAspectRatio: false`, so its container must be given an explicit
|
|
859
|
+
* height. Vertical charts use the themed default; horizontal charts grow
|
|
860
|
+
* with their content so every bar stays legible.
|
|
861
|
+
*/
|
|
862
|
+
this.chartHeight = computed(() => this.#getChartHeight(), ...(ngDevMode ? [{ debugName: "chartHeight" }] : []));
|
|
863
|
+
const logger = inject(SkyLogService);
|
|
864
|
+
// Values align to categories by index, so a length mismatch silently
|
|
865
|
+
// misaligns or drops data. Warn about the one alignment mistake that is
|
|
866
|
+
// mechanically detectable.
|
|
867
|
+
afterRenderEffect(() => {
|
|
868
|
+
const categoryCount = this.categoryAxis()?.categories().length;
|
|
869
|
+
if (categoryCount === undefined) {
|
|
870
|
+
return;
|
|
871
|
+
}
|
|
872
|
+
for (const chartSeries of this.series()) {
|
|
873
|
+
const valueCount = chartSeries.values().length;
|
|
874
|
+
if (valueCount !== categoryCount) {
|
|
875
|
+
logger.warn(`The <sky-chart-bar-series> labeled "${chartSeries.labelText()}" ` +
|
|
876
|
+
`has ${valueCount} values, but the category axis has ` +
|
|
877
|
+
`${categoryCount} categories. Values align to categories by ` +
|
|
878
|
+
'index, so each series must provide one value per category.');
|
|
879
|
+
}
|
|
880
|
+
}
|
|
881
|
+
});
|
|
882
|
+
}
|
|
883
|
+
getChartTable() {
|
|
884
|
+
const data = resolveCartesianData(this.categoryAxis(), this.valueAxis(), this.series());
|
|
885
|
+
if (!data) {
|
|
886
|
+
return undefined;
|
|
887
|
+
}
|
|
888
|
+
return buildCartesianTable(data.categoryAxis, data.series, data.valueAxis.formatValue());
|
|
889
|
+
}
|
|
890
|
+
getAccessibleSummary() {
|
|
891
|
+
const data = resolveCartesianData(this.categoryAxis(), this.valueAxis(), this.series());
|
|
892
|
+
if (!data) {
|
|
893
|
+
return undefined;
|
|
894
|
+
}
|
|
895
|
+
return {
|
|
896
|
+
resourceKey: 'skyux_charts.chart.bar.accessible_summary',
|
|
897
|
+
args: [data.series.length, data.categoryAxis.categories().length],
|
|
898
|
+
};
|
|
899
|
+
}
|
|
900
|
+
#getChartJsConfig() {
|
|
901
|
+
const data = resolveCartesianData(this.categoryAxis(), this.valueAxis(), this.series());
|
|
902
|
+
if (!data) {
|
|
903
|
+
return undefined;
|
|
904
|
+
}
|
|
905
|
+
const { categoryAxis, valueAxis, series } = data;
|
|
906
|
+
// Read the theme signal so the config rebuilds when the theme changes,
|
|
907
|
+
// then resolve the themed CSS custom properties to concrete values.
|
|
908
|
+
this.#themeSettings();
|
|
909
|
+
const themeStyles = this.getThemeStyles();
|
|
910
|
+
const categorical = themeStyles.series.categoricalPalette;
|
|
911
|
+
const isHorizontal = this.orientation() === 'horizontal';
|
|
912
|
+
const isStacked = this.seriesLayout() === 'stacked';
|
|
913
|
+
const indexAxis = isHorizontal ? 'y' : 'x';
|
|
914
|
+
const valueDirection = isHorizontal ? 'x' : 'y';
|
|
915
|
+
const formatValue = valueAxis.formatValue();
|
|
916
|
+
// Horizontal bars are rendered at an explicit thickness (see below), which
|
|
917
|
+
// the container height is also derived from; resolve it once so the two
|
|
918
|
+
// always agree.
|
|
919
|
+
const horizontalBarThickness = isHorizontal
|
|
920
|
+
? this.#getHorizontalBarSpacing(themeStyles).barThickness
|
|
921
|
+
: undefined;
|
|
922
|
+
// Vertical bars fill their responsive width; the fill percentages are
|
|
923
|
+
// tuned by category count (see below).
|
|
924
|
+
const verticalSpacing = isHorizontal
|
|
925
|
+
? undefined
|
|
926
|
+
: this.#getVerticalBarElementSpacing(categoryAxis.categories().length);
|
|
927
|
+
const datasets = series.map((chartSeries, index) => {
|
|
928
|
+
const dataset = {
|
|
929
|
+
label: chartSeries.labelText(),
|
|
930
|
+
// Chart.js mutates the arrays it is given, so deep-copy the readonly
|
|
931
|
+
// input, including floating [start, end] ranges.
|
|
932
|
+
data: chartSeries
|
|
933
|
+
.values()
|
|
934
|
+
.map((value) => (isValueRange(value) ? [...value] : value)),
|
|
935
|
+
backgroundColor: categorical[index % categorical.length],
|
|
936
|
+
};
|
|
937
|
+
if (isHorizontal) {
|
|
938
|
+
// Render bars at the exact thickness the container height was computed
|
|
939
|
+
// for. Without this, Chart.js shrinks each bar to a fraction of its
|
|
940
|
+
// category slot, ignoring the minimum thickness.
|
|
941
|
+
dataset.barThickness = horizontalBarThickness;
|
|
942
|
+
}
|
|
943
|
+
else {
|
|
944
|
+
// Shape the whitespace around vertical bars and cap their width so
|
|
945
|
+
// sparse charts do not render unusably wide bars.
|
|
946
|
+
dataset.categoryPercentage = verticalSpacing?.categoryPercentage;
|
|
947
|
+
dataset.barPercentage = verticalSpacing?.barPercentage;
|
|
948
|
+
dataset.maxBarThickness = themeStyles.bar.vertical.maxBarThickness;
|
|
949
|
+
}
|
|
950
|
+
// Stack groups only apply to a stacked layout; on a grouped layout the
|
|
951
|
+
// scales are not stacked, so a shared stack id would overlap bars instead
|
|
952
|
+
// of accumulating them.
|
|
953
|
+
const stackId = chartSeries.stackId();
|
|
954
|
+
if (isStacked && stackId !== undefined) {
|
|
955
|
+
dataset.stack = stackId;
|
|
956
|
+
}
|
|
957
|
+
if (isHorizontal) {
|
|
958
|
+
dataset.xAxisID = VALUE_AXIS_ID;
|
|
959
|
+
dataset.yAxisID = CATEGORY_AXIS_ID;
|
|
960
|
+
}
|
|
961
|
+
else {
|
|
962
|
+
dataset.yAxisID = VALUE_AXIS_ID;
|
|
963
|
+
dataset.xAxisID = CATEGORY_AXIS_ID;
|
|
964
|
+
}
|
|
965
|
+
return dataset;
|
|
966
|
+
});
|
|
967
|
+
return extendBaseChartJsConfig(themeStyles, {
|
|
968
|
+
type: 'bar',
|
|
969
|
+
data: {
|
|
970
|
+
// Chart.js mutates the arrays it is given, so copy the readonly input.
|
|
971
|
+
labels: [...categoryAxis.categories()],
|
|
972
|
+
datasets,
|
|
973
|
+
},
|
|
974
|
+
options: {
|
|
975
|
+
interaction: {
|
|
976
|
+
// Index hits along the category axis's direction (see the category scale's
|
|
977
|
+
// `axis` in buildCartesianScales); this is a cartesian direction, not a scale ID.
|
|
978
|
+
axis: isHorizontal ? 'y' : 'x',
|
|
979
|
+
},
|
|
980
|
+
elements: {
|
|
981
|
+
bar: {
|
|
982
|
+
borderWidth: 1,
|
|
983
|
+
borderColor: themeStyles.bar.borderColor,
|
|
984
|
+
borderRadius: themeStyles.bar.borderRadius,
|
|
985
|
+
},
|
|
986
|
+
},
|
|
987
|
+
indexAxis,
|
|
988
|
+
scales: buildCartesianScales({
|
|
989
|
+
categoryAxis,
|
|
990
|
+
valueAxis,
|
|
991
|
+
isHorizontal,
|
|
992
|
+
isStacked,
|
|
993
|
+
themeStyles,
|
|
994
|
+
}),
|
|
995
|
+
plugins: {
|
|
996
|
+
legend: {
|
|
997
|
+
// Show the legend only when there are multiple series to
|
|
998
|
+
// distinguish; a single-series chart's legend is redundant.
|
|
999
|
+
display: datasets.length > 1,
|
|
1000
|
+
},
|
|
1001
|
+
tooltip: {
|
|
1002
|
+
callbacks: {
|
|
1003
|
+
label: buildValueTooltipLabel(formatValue, valueDirection),
|
|
1004
|
+
},
|
|
1005
|
+
},
|
|
1006
|
+
},
|
|
1007
|
+
},
|
|
1008
|
+
});
|
|
1009
|
+
}
|
|
1010
|
+
/**
|
|
1011
|
+
* Resolves the height to apply to the chart container. A vertical chart uses
|
|
1012
|
+
* the themed default height. A horizontal chart grows with its content:
|
|
1013
|
+
* every category needs enough room for its bars, so the height is derived
|
|
1014
|
+
* from the bar count and clamped to the minimum so small charts stay legible.
|
|
1015
|
+
*/
|
|
1016
|
+
#getChartHeight() {
|
|
1017
|
+
// Read the theme signal so the height recomputes when the theme changes.
|
|
1018
|
+
this.#themeSettings();
|
|
1019
|
+
const themeStyles = this.getThemeStyles();
|
|
1020
|
+
if (this.orientation() !== 'horizontal') {
|
|
1021
|
+
return themeStyles.height.default;
|
|
1022
|
+
}
|
|
1023
|
+
const { barThickness, categoryGap, categoryCount, barsPerCategory } = this.#getHorizontalBarSpacing(themeStyles);
|
|
1024
|
+
const rowHeight = barThickness * barsPerCategory + categoryGap;
|
|
1025
|
+
const totalRowsHeight = categoryCount * rowHeight;
|
|
1026
|
+
const computedHeight = this.#computeChartOverhead(themeStyles) + totalRowsHeight;
|
|
1027
|
+
// Horizontal charts may grow without bound, but never shrink below the
|
|
1028
|
+
// themed minimum.
|
|
1029
|
+
const clampedHeight = Math.max(themeStyles.height.min, computedHeight);
|
|
1030
|
+
return `${clampedHeight}px`;
|
|
1031
|
+
}
|
|
1032
|
+
/**
|
|
1033
|
+
* Resolves the horizontal bar layout — the per-bar thickness, the gap between
|
|
1034
|
+
* categories, and the counts they derive from — shared by the container
|
|
1035
|
+
* height and the datasets so the two always agree.
|
|
1036
|
+
*/
|
|
1037
|
+
#getHorizontalBarSpacing(themeStyles) {
|
|
1038
|
+
const seriesCount = this.series().length;
|
|
1039
|
+
// Chart.js renders one row per category-axis label, so the row count must
|
|
1040
|
+
// come from the category axis — a sparse series with fewer values than
|
|
1041
|
+
// categories must not shrink the height. The axis is always present when
|
|
1042
|
+
// the chart (and therefore this height) renders, so the fallback is
|
|
1043
|
+
// defensive only.
|
|
1044
|
+
/* istanbul ignore next */
|
|
1045
|
+
const categoryCount = this.categoryAxis()?.categories().length ?? 0;
|
|
1046
|
+
// A stacked layout renders one bar per distinct stack group — series that
|
|
1047
|
+
// share a stack group (or all lack one) accumulate into a single bar — so
|
|
1048
|
+
// the number of bars per category is the count of distinct stacks, not one.
|
|
1049
|
+
const barsPerCategory = this.seriesLayout() === 'stacked'
|
|
1050
|
+
? new Set(this.series().map((chartSeries) => chartSeries.stackId()))
|
|
1051
|
+
.size
|
|
1052
|
+
: seriesCount;
|
|
1053
|
+
const totalBars = categoryCount * barsPerCategory;
|
|
1054
|
+
return {
|
|
1055
|
+
...this.#computeHorizontalBarElementSpacing(totalBars, themeStyles),
|
|
1056
|
+
categoryCount,
|
|
1057
|
+
barsPerCategory,
|
|
1058
|
+
};
|
|
1059
|
+
}
|
|
1060
|
+
/**
|
|
1061
|
+
* Derives the per-bar thickness and the gap between categories for a
|
|
1062
|
+
* horizontal chart. Charts with few bars use the full bar thickness and a
|
|
1063
|
+
* tight gap; as the bar count grows the bars taper toward the minimum
|
|
1064
|
+
* thickness while the gap widens so grouped bars stay visually separated.
|
|
1065
|
+
*/
|
|
1066
|
+
#computeHorizontalBarElementSpacing(totalBars, themeStyles) {
|
|
1067
|
+
const { minBarThickness, maxBarThickness, minCategoryGap } = themeStyles.bar.horizontal;
|
|
1068
|
+
const taperingStart = 12;
|
|
1069
|
+
const taperingStop = 36;
|
|
1070
|
+
const lowCategoryGapPercentage = 0.375;
|
|
1071
|
+
const highCategoryGapPercentage = 0.75;
|
|
1072
|
+
if (totalBars < taperingStart) {
|
|
1073
|
+
// Few bars: use the full thickness and target a fraction of the bar
|
|
1074
|
+
// width for the gap. No minimum is applied because the bars are already
|
|
1075
|
+
// at their full thickness.
|
|
1076
|
+
return {
|
|
1077
|
+
barThickness: maxBarThickness,
|
|
1078
|
+
categoryGap: maxBarThickness * lowCategoryGapPercentage,
|
|
1079
|
+
};
|
|
1080
|
+
}
|
|
1081
|
+
// Many bars: taper the thickness toward the minimum and widen the gap so
|
|
1082
|
+
// grouped bars stay separated, but never below the minimum category gap.
|
|
1083
|
+
const taperRange = taperingStop - taperingStart;
|
|
1084
|
+
const taperFraction = Math.min(1, (totalBars - taperingStart) / taperRange);
|
|
1085
|
+
const thicknessRange = maxBarThickness - minBarThickness;
|
|
1086
|
+
const taperedThickness = Math.round(maxBarThickness - taperFraction * thicknessRange);
|
|
1087
|
+
const barThickness = Math.max(minBarThickness, taperedThickness);
|
|
1088
|
+
return {
|
|
1089
|
+
barThickness,
|
|
1090
|
+
categoryGap: Math.max(minCategoryGap, barThickness * highCategoryGapPercentage),
|
|
1091
|
+
};
|
|
1092
|
+
}
|
|
1093
|
+
/**
|
|
1094
|
+
* Tunes the category and bar fill percentages for a vertical chart. Vertical
|
|
1095
|
+
* bars fill their responsive width up to `bar.vertical.maxBarThickness`, so
|
|
1096
|
+
* these percentages shape the surrounding whitespace: sparse charts keep the
|
|
1097
|
+
* bars near the base width with room around them, while dense charts widen
|
|
1098
|
+
* the fill so bars use the available width (approaching the minimum). Chart.js
|
|
1099
|
+
* has no minimum-thickness option, so the base and minimum widths are soft
|
|
1100
|
+
* targets rather than hard pixel constraints.
|
|
1101
|
+
*/
|
|
1102
|
+
#getVerticalBarElementSpacing(categoryCount) {
|
|
1103
|
+
const barPercentage = 0.85;
|
|
1104
|
+
const categoryPercentage = categoryCount <= 3 ? 0.4 : categoryCount >= 12 ? 0.95 : 0.7;
|
|
1105
|
+
return { categoryPercentage, barPercentage };
|
|
1106
|
+
}
|
|
1107
|
+
/**
|
|
1108
|
+
* Estimates the fixed vertical space a horizontal chart needs outside its
|
|
1109
|
+
* plotted rows: the bottom value axis (its tick marks, a row of tick labels,
|
|
1110
|
+
* and its title) plus the legend row when multiple series are shown. Added
|
|
1111
|
+
* to the rows' height, this keeps the plotted area sized to its bars rather
|
|
1112
|
+
* than absorbing the chrome.
|
|
1113
|
+
*/
|
|
1114
|
+
#computeChartOverhead(themeStyles) {
|
|
1115
|
+
const { axis, font, text, tooltip } = themeStyles;
|
|
1116
|
+
const lineHeight = font.size * text.lineHeight;
|
|
1117
|
+
// Bottom value axis: tick marks, a row of tick labels, and the axis title.
|
|
1118
|
+
const valueAxisHeight = axis.tickLength + lineHeight + axis.titleGap + lineHeight;
|
|
1119
|
+
// The legend only renders with multiple series.
|
|
1120
|
+
const legendHeight = this.series().length > 1
|
|
1121
|
+
? Math.max(tooltip.iconSize, lineHeight) + axis.titleGap
|
|
1122
|
+
: 0;
|
|
1123
|
+
return valueAxisHeight + legendHeight;
|
|
1124
|
+
}
|
|
1125
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: SkyChartBar, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
1126
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.0", type: SkyChartBar, isStandalone: true, selector: "sky-chart-bar", inputs: { orientation: { classPropertyName: "orientation", publicName: "orientation", isSignal: true, isRequired: false, transformFunction: null }, seriesLayout: { classPropertyName: "seriesLayout", publicName: "seriesLayout", isSignal: true, isRequired: false, transformFunction: null } }, queries: [{ propertyName: "categoryAxis", first: true, predicate: SkyChartAxisCategory, descendants: true, isSignal: true }, { propertyName: "valueAxis", first: true, predicate: SkyChartAxisValue, descendants: true, isSignal: true }, { propertyName: "series", predicate: SkyChartBarSeries, isSignal: true }], usesInheritance: true, ngImport: i0, template: "@if (chartJsConfig(); as config) {\n <sky-chart-js [config]=\"config\" [style.height]=\"chartHeight()\" />\n}\n", dependencies: [{ kind: "component", type: SkyChartJs, selector: "sky-chart-js", inputs: ["config"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
1127
|
+
}
|
|
1128
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: SkyChartBar, decorators: [{
|
|
1129
|
+
type: Component,
|
|
1130
|
+
args: [{ changeDetection: ChangeDetectionStrategy.OnPush, imports: [SkyChartJs], selector: 'sky-chart-bar', template: "@if (chartJsConfig(); as config) {\n <sky-chart-js [config]=\"config\" [style.height]=\"chartHeight()\" />\n}\n" }]
|
|
1131
|
+
}], ctorParameters: () => [], propDecorators: { orientation: [{ type: i0.Input, args: [{ isSignal: true, alias: "orientation", required: false }] }], seriesLayout: [{ type: i0.Input, args: [{ isSignal: true, alias: "seriesLayout", required: false }] }], categoryAxis: [{ type: i0.ContentChild, args: [i0.forwardRef(() => SkyChartAxisCategory), { isSignal: true }] }], valueAxis: [{ type: i0.ContentChild, args: [i0.forwardRef(() => SkyChartAxisValue), { isSignal: true }] }], series: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => SkyChartBarSeries), { isSignal: true }] }] } });
|
|
1132
|
+
|
|
12
1133
|
/* istanbul ignore file */
|
|
13
1134
|
/**
|
|
14
1135
|
* NOTICE: DO NOT MODIFY THIS FILE!
|
|
@@ -18,6 +1139,9 @@ import { SkyHelpInlineModule } from '@skyux/help-inline';
|
|
|
18
1139
|
*/
|
|
19
1140
|
const RESOURCES = {
|
|
20
1141
|
'EN-US': {
|
|
1142
|
+
'skyux_charts.chart.bar.accessible_summary': {
|
|
1143
|
+
message: "Bar chart. Number of series: {0}. Number of categories: {1}. A data table is available from the chart's context menu.",
|
|
1144
|
+
},
|
|
21
1145
|
'skyux_charts.chart.controls.context_menu.accessible_name': {
|
|
22
1146
|
message: 'Context menu for {0}',
|
|
23
1147
|
},
|
|
@@ -25,6 +1149,9 @@ const RESOURCES = {
|
|
|
25
1149
|
message: 'View data table',
|
|
26
1150
|
},
|
|
27
1151
|
'skyux_charts.data_table_modal.close_button': { message: 'Close' },
|
|
1152
|
+
'skyux_charts.data_table_modal.table_region_label': {
|
|
1153
|
+
message: 'Data table for {0}',
|
|
1154
|
+
},
|
|
28
1155
|
},
|
|
29
1156
|
};
|
|
30
1157
|
SkyLibResourcesService.addResources(RESOURCES);
|
|
@@ -43,37 +1170,47 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImpor
|
|
|
43
1170
|
}]
|
|
44
1171
|
}] });
|
|
45
1172
|
|
|
46
|
-
class
|
|
1173
|
+
class SkyChartTableModalContext {
|
|
47
1174
|
}
|
|
48
|
-
class
|
|
1175
|
+
class SkyChartTableModal {
|
|
49
1176
|
constructor() {
|
|
50
|
-
this.context = inject(
|
|
1177
|
+
this.context = inject(SkyChartTableModalContext);
|
|
51
1178
|
this.modal = inject(SkyModalInstance);
|
|
52
1179
|
}
|
|
53
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type:
|
|
54
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "
|
|
1180
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: SkyChartTableModal, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
1181
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.0", type: SkyChartTableModal, isStandalone: true, selector: "sky-chart-table-modal", ngImport: i0, template: "<sky-modal [headingText]=\"context.headingText\">\n <sky-modal-content>\n @if (context.table; as table) {\n <div\n class=\"sky-chart-data-table-wrapper\"\n role=\"region\"\n tabindex=\"0\"\n [attr.aria-label]=\"\n 'skyux_charts.data_table_modal.table_region_label'\n | skyLibResources: context.headingText\n \"\n >\n <table class=\"sky-chart-data-table\">\n <thead>\n <tr>\n <th scope=\"col\">\n {{ table.categoryLabel }}\n </th>\n @for (series of table.series; track $index) {\n <th scope=\"col\">\n {{ series.label }}\n </th>\n }\n </tr>\n </thead>\n <tbody>\n @for (\n category of table.categories;\n track $index;\n let rowIndex = $index\n ) {\n <tr>\n <th scope=\"row\">\n {{ category }}\n </th>\n @for (series of table.series; track $index) {\n <td>\n {{ series.values[rowIndex] }}\n </td>\n }\n </tr>\n }\n </tbody>\n </table>\n </div>\n }\n </sky-modal-content>\n <sky-modal-footer>\n <button\n class=\"sky-btn sky-btn-link sky-chart-table-modal-close\"\n type=\"button\"\n (click)=\"modal.close()\"\n >\n {{ 'skyux_charts.data_table_modal.close_button' | skyLibResources }}\n </button>\n </sky-modal-footer>\n</sky-modal>\n", styles: [".sky-chart-data-table:not(.sky-theme-modern *){--sky-override-chart-table-color-text-default: #212327;--sky-override-chart-table-color-text-heading: #212327;--sky-override-chart-table-header-background-color: #eeeeef;--sky-override-chart-table-row-border: 1px solid #cdcfd2;--sky-override-chart-table-cell-padding: 8px 12px}.sky-chart-data-table-wrapper{overflow-x:auto}.sky-chart-data-table{border-collapse:collapse;width:100%;color:var(--sky-override-chart-table-color-text-default, var(--sky-color-text-default))}.sky-chart-data-table th{text-align:left}.sky-chart-data-table td,.sky-chart-data-table th{padding:var(--sky-override-chart-table-cell-padding, var(--sky-comp-grid-cell-space-inset-top) var(--sky-comp-grid-cell-space-inset-right) var(--sky-comp-grid-cell-space-inset-bottom) var(--sky-comp-grid-cell-space-inset-left))}.sky-chart-data-table td,.sky-chart-data-table thead th:not(:first-child){text-align:right}.sky-chart-data-table thead th{background-color:var(--sky-override-chart-table-header-background-color, var(--sky-background-color-page-default));color:var(--sky-override-chart-table-color-text-heading, var(--sky-color-text-heading))}.sky-chart-data-table thead tr,.sky-chart-data-table tbody tr:not(:last-child){border-bottom:var(--sky-override-chart-table-row-border, var(--sky-border-width-separator-row) var(--sky-border-style-separator-row) var(--sky-color-border-separator-row))}\n"], dependencies: [{ kind: "ngmodule", type: SkyChartsResourcesModule }, { kind: "ngmodule", type: SkyModalModule }, { kind: "component", type: i1.λ5, selector: "sky-modal", inputs: ["formErrors", "headingText", "helpKey", "helpPopoverContent", "helpPopoverTitle", "ariaRole", "tiledBody", "ariaDescribedBy", "ariaLabelledBy", "layout", "headingHidden"] }, { kind: "component", type: i1.λ2, selector: "sky-modal-content" }, { kind: "component", type: i1.λ3, selector: "sky-modal-footer" }, { kind: "pipe", type: i2.SkyLibResourcesPipe, name: "skyLibResources" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
55
1182
|
}
|
|
56
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type:
|
|
1183
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: SkyChartTableModal, decorators: [{
|
|
57
1184
|
type: Component,
|
|
58
|
-
args: [{ changeDetection: ChangeDetectionStrategy.OnPush, imports: [SkyChartsResourcesModule, SkyModalModule], template: "<sky-modal [headingText]=\"context.headingText\">\n <sky-modal-content
|
|
1185
|
+
args: [{ changeDetection: ChangeDetectionStrategy.OnPush, imports: [SkyChartsResourcesModule, SkyModalModule], selector: 'sky-chart-table-modal', template: "<sky-modal [headingText]=\"context.headingText\">\n <sky-modal-content>\n @if (context.table; as table) {\n <div\n class=\"sky-chart-data-table-wrapper\"\n role=\"region\"\n tabindex=\"0\"\n [attr.aria-label]=\"\n 'skyux_charts.data_table_modal.table_region_label'\n | skyLibResources: context.headingText\n \"\n >\n <table class=\"sky-chart-data-table\">\n <thead>\n <tr>\n <th scope=\"col\">\n {{ table.categoryLabel }}\n </th>\n @for (series of table.series; track $index) {\n <th scope=\"col\">\n {{ series.label }}\n </th>\n }\n </tr>\n </thead>\n <tbody>\n @for (\n category of table.categories;\n track $index;\n let rowIndex = $index\n ) {\n <tr>\n <th scope=\"row\">\n {{ category }}\n </th>\n @for (series of table.series; track $index) {\n <td>\n {{ series.values[rowIndex] }}\n </td>\n }\n </tr>\n }\n </tbody>\n </table>\n </div>\n }\n </sky-modal-content>\n <sky-modal-footer>\n <button\n class=\"sky-btn sky-btn-link sky-chart-table-modal-close\"\n type=\"button\"\n (click)=\"modal.close()\"\n >\n {{ 'skyux_charts.data_table_modal.close_button' | skyLibResources }}\n </button>\n </sky-modal-footer>\n</sky-modal>\n", styles: [".sky-chart-data-table:not(.sky-theme-modern *){--sky-override-chart-table-color-text-default: #212327;--sky-override-chart-table-color-text-heading: #212327;--sky-override-chart-table-header-background-color: #eeeeef;--sky-override-chart-table-row-border: 1px solid #cdcfd2;--sky-override-chart-table-cell-padding: 8px 12px}.sky-chart-data-table-wrapper{overflow-x:auto}.sky-chart-data-table{border-collapse:collapse;width:100%;color:var(--sky-override-chart-table-color-text-default, var(--sky-color-text-default))}.sky-chart-data-table th{text-align:left}.sky-chart-data-table td,.sky-chart-data-table th{padding:var(--sky-override-chart-table-cell-padding, var(--sky-comp-grid-cell-space-inset-top) var(--sky-comp-grid-cell-space-inset-right) var(--sky-comp-grid-cell-space-inset-bottom) var(--sky-comp-grid-cell-space-inset-left))}.sky-chart-data-table td,.sky-chart-data-table thead th:not(:first-child){text-align:right}.sky-chart-data-table thead th{background-color:var(--sky-override-chart-table-header-background-color, var(--sky-background-color-page-default));color:var(--sky-override-chart-table-color-text-heading, var(--sky-color-text-heading))}.sky-chart-data-table thead tr,.sky-chart-data-table tbody tr:not(:last-child){border-bottom:var(--sky-override-chart-table-row-border, var(--sky-border-width-separator-row) var(--sky-border-style-separator-row) var(--sky-color-border-separator-row))}\n"] }]
|
|
59
1186
|
}] });
|
|
60
1187
|
|
|
61
1188
|
class SkyChartControls {
|
|
62
1189
|
constructor() {
|
|
63
1190
|
this.#destroyRef = inject(DestroyRef);
|
|
64
1191
|
this.#modalSvc = inject(SkyModalService);
|
|
1192
|
+
this.#tableSvc = inject(SkyChartTableService);
|
|
65
1193
|
this.headingText = input.required(...(ngDevMode ? [{ debugName: "headingText" }] : []));
|
|
1194
|
+
/**
|
|
1195
|
+
* The plot's data table. The context menu's only action is viewing the
|
|
1196
|
+
* data table, so the menu renders only while a table is available — a
|
|
1197
|
+
* plot that has not published one (no data yet, or still loading for the
|
|
1198
|
+
* first time) has no actions to offer.
|
|
1199
|
+
*/
|
|
1200
|
+
this.table = this.#tableSvc.table;
|
|
66
1201
|
}
|
|
67
1202
|
#destroyRef;
|
|
68
1203
|
#modalSvc;
|
|
1204
|
+
#tableSvc;
|
|
69
1205
|
openDataTableModal() {
|
|
70
|
-
const instance = this.#modalSvc.open(
|
|
1206
|
+
const instance = this.#modalSvc.open(SkyChartTableModal, {
|
|
71
1207
|
size: 'large',
|
|
72
1208
|
providers: [
|
|
73
1209
|
{
|
|
74
|
-
provide:
|
|
1210
|
+
provide: SkyChartTableModalContext,
|
|
75
1211
|
useValue: {
|
|
76
1212
|
headingText: this.headingText(),
|
|
1213
|
+
table: this.#tableSvc.table(),
|
|
77
1214
|
},
|
|
78
1215
|
},
|
|
79
1216
|
],
|
|
@@ -81,11 +1218,11 @@ class SkyChartControls {
|
|
|
81
1218
|
this.#destroyRef.onDestroy(() => instance.close());
|
|
82
1219
|
}
|
|
83
1220
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: SkyChartControls, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
84
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.
|
|
1221
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.0", type: SkyChartControls, isStandalone: true, selector: "sky-chart-controls", inputs: { headingText: { classPropertyName: "headingText", publicName: "headingText", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0, template: "@if (table()) {\n <sky-dropdown\n buttonType=\"context-menu\"\n [label]=\"\n 'skyux_charts.chart.controls.context_menu.accessible_name'\n | skyLibResources: headingText()\n \"\n >\n <sky-dropdown-menu>\n <sky-dropdown-item>\n <button type=\"button\" (click)=\"openDataTableModal()\">\n {{\n 'skyux_charts.chart.controls.context_menu.view_data_table'\n | skyLibResources\n }}\n </button>\n </sky-dropdown-item>\n </sky-dropdown-menu>\n </sky-dropdown>\n}\n", dependencies: [{ kind: "ngmodule", type: SkyChartsResourcesModule }, { kind: "ngmodule", type: SkyDropdownModule }, { kind: "component", type: i1$1.λ3, selector: "sky-dropdown", inputs: ["buttonStyle", "buttonType", "disabled", "label", "horizontalAlignment", "messageStream", "title", "trigger"] }, { kind: "component", type: i1$1.λ1, selector: "sky-dropdown-item", inputs: ["ariaRole"] }, { kind: "component", type: i1$1.λ4, selector: "sky-dropdown-menu", inputs: ["ariaLabelledBy", "ariaRole", "useNativeFocus"], outputs: ["menuChanges"] }, { kind: "pipe", type: i2.SkyLibResourcesPipe, name: "skyLibResources" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
85
1222
|
}
|
|
86
1223
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: SkyChartControls, decorators: [{
|
|
87
1224
|
type: Component,
|
|
88
|
-
args: [{ changeDetection: ChangeDetectionStrategy.OnPush, imports: [SkyChartsResourcesModule, SkyDropdownModule], selector: 'sky-chart-controls', template: "<sky-dropdown\n
|
|
1225
|
+
args: [{ changeDetection: ChangeDetectionStrategy.OnPush, imports: [SkyChartsResourcesModule, SkyDropdownModule], selector: 'sky-chart-controls', template: "@if (table()) {\n <sky-dropdown\n buttonType=\"context-menu\"\n [label]=\"\n 'skyux_charts.chart.controls.context_menu.accessible_name'\n | skyLibResources: headingText()\n \"\n >\n <sky-dropdown-menu>\n <sky-dropdown-item>\n <button type=\"button\" (click)=\"openDataTableModal()\">\n {{\n 'skyux_charts.chart.controls.context_menu.view_data_table'\n | skyLibResources\n }}\n </button>\n </sky-dropdown-item>\n </sky-dropdown-menu>\n </sky-dropdown>\n}\n" }]
|
|
89
1226
|
}], propDecorators: { headingText: [{ type: i0.Input, args: [{ isSignal: true, alias: "headingText", required: true }] }] } });
|
|
90
1227
|
|
|
91
1228
|
class SkyChartHeading {
|
|
@@ -137,17 +1274,17 @@ class SkyChartSubheading {
|
|
|
137
1274
|
this.subheadingText = input.required(...(ngDevMode ? [{ debugName: "subheadingText" }] : []));
|
|
138
1275
|
}
|
|
139
1276
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: SkyChartSubheading, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
140
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.2.0", type: SkyChartSubheading, isStandalone: true, selector: "sky-chart-subheading", inputs: { subheadingText: { classPropertyName: "subheadingText", publicName: "subheadingText", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0, template: `{{ subheadingText() }}`, isInline: true, styles: [":host{display:block;font-family:var(--sky-font-family-primary);font-size:var(--sky-font-size-body-m);font-weight:var(--sky-font-style-body-m);line-height:var(--sky-font-line_height-body-m);color:var(--sky-color-text-deemphasized)}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
1277
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.2.0", type: SkyChartSubheading, isStandalone: true, selector: "sky-chart-subheading", inputs: { subheadingText: { classPropertyName: "subheadingText", publicName: "subheadingText", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0, template: `{{ subheadingText() }}`, isInline: true, styles: [":host:not(.sky-theme-modern *){--sky-override-chart-font-size-body-m: 15px;--sky-override-chart-font-style-body-m: 400;--sky-override-chart-font-line_height-body-m: 1.3333}:host{display:block;font-family:var(--sky-override-chart-font-family-primary, var(--sky-font-family-primary));font-size:var(--sky-override-chart-font-size-body-m, var(--sky-font-size-body-m));font-weight:var(--sky-override-chart-font-style-body-m, var(--sky-font-style-body-m));line-height:var(--sky-override-chart-font-line_height-body-m, var(--sky-font-line_height-body-m));color:var(--sky-override-chart-color-text-deemphasized, var(--sky-color-text-deemphasized))}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
141
1278
|
}
|
|
142
1279
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: SkyChartSubheading, decorators: [{
|
|
143
1280
|
type: Component,
|
|
144
|
-
args: [{ changeDetection: ChangeDetectionStrategy.OnPush, selector: 'sky-chart-subheading', template: `{{ subheadingText() }}`, styles: [":host{display:block;font-family:var(--sky-font-family-primary);font-size:var(--sky-font-size-body-m);font-weight:var(--sky-font-style-body-m);line-height:var(--sky-font-line_height-body-m);color:var(--sky-color-text-deemphasized)}\n"] }]
|
|
1281
|
+
args: [{ changeDetection: ChangeDetectionStrategy.OnPush, selector: 'sky-chart-subheading', template: `{{ subheadingText() }}`, styles: [":host:not(.sky-theme-modern *){--sky-override-chart-font-size-body-m: 15px;--sky-override-chart-font-style-body-m: 400;--sky-override-chart-font-line_height-body-m: 1.3333}:host{display:block;font-family:var(--sky-override-chart-font-family-primary, var(--sky-font-family-primary));font-size:var(--sky-override-chart-font-size-body-m, var(--sky-font-size-body-m));font-weight:var(--sky-override-chart-font-style-body-m, var(--sky-font-style-body-m));line-height:var(--sky-override-chart-font-line_height-body-m, var(--sky-font-line_height-body-m));color:var(--sky-override-chart-color-text-deemphasized, var(--sky-color-text-deemphasized))}\n"] }]
|
|
145
1282
|
}], propDecorators: { subheadingText: [{ type: i0.Input, args: [{ isSignal: true, alias: "subheadingText", required: true }] }] } });
|
|
146
1283
|
|
|
147
1284
|
/**
|
|
148
|
-
* @preview
|
|
149
|
-
*
|
|
150
1285
|
* Provides a consistent heading, subheading, and layout wrapper for a chart.
|
|
1286
|
+
*
|
|
1287
|
+
* @preview
|
|
151
1288
|
*/
|
|
152
1289
|
class SkyChart {
|
|
153
1290
|
constructor() {
|
|
@@ -186,33 +1323,69 @@ class SkyChart {
|
|
|
186
1323
|
*/
|
|
187
1324
|
this.helpPopoverTitle = input(...(ngDevMode ? [undefined, { debugName: "helpPopoverTitle" }] : []));
|
|
188
1325
|
/**
|
|
189
|
-
* Whether
|
|
1326
|
+
* Whether the chart's data is being loaded. When `true`, a wait overlay
|
|
1327
|
+
* covers the chart's content area, which reserves the default chart height
|
|
1328
|
+
* while no plot is rendered. The heading and help button stay interactive.
|
|
1329
|
+
* @default false
|
|
190
1330
|
*/
|
|
191
|
-
this.
|
|
1331
|
+
this.loading = input(false, { ...(ngDevMode ? { debugName: "loading" } : {}), transform: booleanAttribute });
|
|
192
1332
|
/**
|
|
193
1333
|
* The text to display as the chart's subheading.
|
|
194
1334
|
*/
|
|
195
1335
|
this.subheadingText = input(...(ngDevMode ? [undefined, { debugName: "subheadingText" }] : []));
|
|
1336
|
+
this.#resourcesSvc = inject(SkyLibResourcesService);
|
|
1337
|
+
this.#tableSvc = inject(SkyChartTableService);
|
|
1338
|
+
// Resolve the plot's descriptive summary into localized text. Each plot type
|
|
1339
|
+
// publishes its own resource key and arguments, so the wording can describe
|
|
1340
|
+
// that type's shape.
|
|
1341
|
+
this.#summaryText = toSignal(toObservable(this.#tableSvc.summary).pipe(switchMap((summary) => summary
|
|
1342
|
+
? this.#resourcesSvc.getString(summary.resourceKey, ...summary.args)
|
|
1343
|
+
: of(undefined))), { initialValue: undefined });
|
|
196
1344
|
this.figureLabel = computed(() => {
|
|
197
|
-
const
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
1345
|
+
const parts = [];
|
|
1346
|
+
// When the heading is hidden, name the figure with the title (and
|
|
1347
|
+
// subheading) so it is not lost. When the heading is visible, it already
|
|
1348
|
+
// provides that context, so the title is omitted here to avoid announcing
|
|
1349
|
+
// it twice.
|
|
1350
|
+
if (this.headingHidden()) {
|
|
1351
|
+
const subheadingText = this.subheadingText();
|
|
1352
|
+
parts.push(subheadingText
|
|
1353
|
+
? `${this.headingText()}, ${subheadingText}`
|
|
1354
|
+
: this.headingText());
|
|
1355
|
+
}
|
|
1356
|
+
// The descriptive summary (chart type, shape, and that a data table is
|
|
1357
|
+
// available) adds information rather than echoing the title, so it is safe
|
|
1358
|
+
// to include whether or not the heading is visible.
|
|
1359
|
+
const summaryText = this.#summaryText();
|
|
1360
|
+
if (summaryText) {
|
|
1361
|
+
parts.push(summaryText);
|
|
1362
|
+
}
|
|
1363
|
+
return parts.length > 0 ? parts.join('. ') : null;
|
|
201
1364
|
}, ...(ngDevMode ? [{ debugName: "figureLabel" }] : []));
|
|
202
1365
|
}
|
|
1366
|
+
#resourcesSvc;
|
|
1367
|
+
#tableSvc;
|
|
1368
|
+
// Resolve the plot's descriptive summary into localized text. Each plot type
|
|
1369
|
+
// publishes its own resource key and arguments, so the wording can describe
|
|
1370
|
+
// that type's shape.
|
|
1371
|
+
#summaryText;
|
|
203
1372
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: SkyChart, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
204
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.0", type: SkyChart, isStandalone: true, selector: "sky-chart", inputs: { headingHidden: { classPropertyName: "headingHidden", publicName: "headingHidden", isSignal: true, isRequired: false, transformFunction: null }, headingLevel: { classPropertyName: "headingLevel", publicName: "headingLevel", isSignal: true, isRequired: false, transformFunction: null }, headingStyle: { classPropertyName: "headingStyle", publicName: "headingStyle", isSignal: true, isRequired: false, transformFunction: null }, headingText: { classPropertyName: "headingText", publicName: "headingText", isSignal: true, isRequired: true, transformFunction: null }, helpKey: { classPropertyName: "helpKey", publicName: "helpKey", isSignal: true, isRequired: false, transformFunction: null }, helpPopoverContent: { classPropertyName: "helpPopoverContent", publicName: "helpPopoverContent", isSignal: true, isRequired: false, transformFunction: null }, helpPopoverTitle: { classPropertyName: "helpPopoverTitle", publicName: "helpPopoverTitle", isSignal: true, isRequired: false, transformFunction: null },
|
|
1373
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.0", type: SkyChart, isStandalone: true, selector: "sky-chart", inputs: { headingHidden: { classPropertyName: "headingHidden", publicName: "headingHidden", isSignal: true, isRequired: false, transformFunction: null }, headingLevel: { classPropertyName: "headingLevel", publicName: "headingLevel", isSignal: true, isRequired: false, transformFunction: null }, headingStyle: { classPropertyName: "headingStyle", publicName: "headingStyle", isSignal: true, isRequired: false, transformFunction: null }, headingText: { classPropertyName: "headingText", publicName: "headingText", isSignal: true, isRequired: true, transformFunction: null }, helpKey: { classPropertyName: "helpKey", publicName: "helpKey", isSignal: true, isRequired: false, transformFunction: null }, helpPopoverContent: { classPropertyName: "helpPopoverContent", publicName: "helpPopoverContent", isSignal: true, isRequired: false, transformFunction: null }, helpPopoverTitle: { classPropertyName: "helpPopoverTitle", publicName: "helpPopoverTitle", isSignal: true, isRequired: false, transformFunction: null }, loading: { classPropertyName: "loading", publicName: "loading", isSignal: true, isRequired: false, transformFunction: null }, subheadingText: { classPropertyName: "subheadingText", publicName: "subheadingText", isSignal: true, isRequired: false, transformFunction: null } }, providers: [SkyChartTableService], ngImport: i0, template: "<div class=\"sky-chart-header\">\n <div>\n @if (!headingHidden()) {\n <sky-chart-heading\n [headingLevel]=\"headingLevel()\"\n [headingStyle]=\"headingStyle()\"\n [headingText]=\"headingText()\"\n [helpKey]=\"helpKey()\"\n [helpPopoverContent]=\"helpPopoverContent()\"\n [helpPopoverTitle]=\"helpPopoverTitle()\"\n />\n @if (subheadingText(); as subheadingText) {\n <sky-chart-subheading [subheadingText]=\"subheadingText\" />\n }\n }\n </div>\n @if (!loading()) {\n <sky-chart-controls [headingText]=\"headingText()\" />\n }\n</div>\n<figure\n class=\"sky-chart-content\"\n [attr.aria-busy]=\"loading() ? 'true' : null\"\n [attr.aria-label]=\"figureLabel()\"\n [attr.role]=\"figureLabel() ? 'img' : null\"\n [class.sky-chart-content-loading]=\"loading()\"\n>\n <sky-wait [isWaiting]=\"loading()\" />\n <ng-content />\n</figure>\n", styles: [":host:not(.sky-theme-modern *){--sky-override-chart-font-family-primary: BLKB Sans, Helvetica Neue, Arial, sans-serif;--sky-override-chart-font-size-body-s: 13px;--sky-override-chart-font-style-body-s: 400;--sky-override-chart-font-style-emphasized: 700;--sky-override-chart-font-line_height-body-s: 1.3846;--sky-override-chart-color-text-default: #212327;--sky-override-chart-color-text-deemphasized: #686c73;--sky-override-chart-color-viz-axis: #cdcfd2;--sky-override-chart-color-viz-gridline: #eeeeef;--sky-override-chart-color-viz-category-1: #06a39e;--sky-override-chart-color-viz-category-2: #6d3c96;--sky-override-chart-color-viz-category-3: #5589dd;--sky-override-chart-color-viz-category-4: #004252;--sky-override-chart-color-viz-category-5: #ce5600;--sky-override-chart-color-viz-category-6: #822325;--sky-override-chart-color-viz-category-7: #c650c1;--sky-override-chart-color-viz-category-8: #077e43;--sky-override-chart-color-background-container-base: #ffffff;--sky-override-chart-color-border-container-base: #cdcfd2;--sky-override-chart-size-chart-tick_length-measure: 12px;--sky-override-chart-space-stacked-xs: 4px;--sky-override-chart-space-stacked-s: 8px;--sky-override-chart-space-stacked-0: 0px;--sky-override-chart-space-gap-icon-s: 4px;--sky-override-chart-space-gap-text_action-m: 12px;--sky-override-chart-size-icon-xs: 16px;--sky-override-chart-border-width-container-base: 1px;--sky-override-chart-border-radius-s: 4px;--sky-override-chart-border-radius-xs: 2px;--sky-override-chart-comp-chart-tooltip-space-inset-top: 8px;--sky-override-chart-comp-chart-tooltip-space-inset-right: 12px;--sky-override-chart-comp-chart-tooltip-space-inset-bottom: 8px;--sky-override-chart-comp-chart-tooltip-space-inset-left: 12px}:host{display:block;--sky-chart-height-min: 11.25rem;--sky-chart-height-viewport: 28vh;--sky-chart-height-max: 25rem}.sky-chart-header{display:flex;align-items:baseline;justify-content:space-between;column-gap:var(--sky-override-chart-space-gap-text_action-m, var(--sky-space-gap-text_action-m));margin-bottom:var(--sky-override-chart-space-stacked-s, var(--sky-space-stacked-s))}figure.sky-chart-content{margin:0}figure.sky-chart-content-loading{position:relative;min-height:clamp(var(--sky-chart-height-min),var(--sky-chart-height-viewport),var(--sky-chart-height-max))}\n"], dependencies: [{ kind: "component", type: SkyChartControls, selector: "sky-chart-controls", inputs: ["headingText"] }, { kind: "component", type: SkyChartHeading, selector: "sky-chart-heading", inputs: ["headingLevel", "headingStyle", "headingText", "helpKey", "helpPopoverContent", "helpPopoverTitle"] }, { kind: "component", type: SkyChartSubheading, selector: "sky-chart-subheading", inputs: ["subheadingText"] }, { kind: "ngmodule", type: SkyChartsResourcesModule }, { kind: "ngmodule", type: SkyWaitModule }, { kind: "component", type: i1$3.λ14, selector: "sky-wait", inputs: ["ariaLabel", "isWaiting", "isFullPage", "isNonBlocking", "screenReaderCompletedText"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
205
1374
|
}
|
|
206
1375
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: SkyChart, decorators: [{
|
|
207
1376
|
type: Component,
|
|
208
|
-
args: [{ changeDetection: ChangeDetectionStrategy.OnPush,
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
1377
|
+
args: [{ changeDetection: ChangeDetectionStrategy.OnPush, imports: [
|
|
1378
|
+
SkyChartControls,
|
|
1379
|
+
SkyChartHeading,
|
|
1380
|
+
SkyChartSubheading,
|
|
1381
|
+
SkyChartsResourcesModule,
|
|
1382
|
+
SkyWaitModule,
|
|
1383
|
+
], providers: [SkyChartTableService], selector: 'sky-chart', template: "<div class=\"sky-chart-header\">\n <div>\n @if (!headingHidden()) {\n <sky-chart-heading\n [headingLevel]=\"headingLevel()\"\n [headingStyle]=\"headingStyle()\"\n [headingText]=\"headingText()\"\n [helpKey]=\"helpKey()\"\n [helpPopoverContent]=\"helpPopoverContent()\"\n [helpPopoverTitle]=\"helpPopoverTitle()\"\n />\n @if (subheadingText(); as subheadingText) {\n <sky-chart-subheading [subheadingText]=\"subheadingText\" />\n }\n }\n </div>\n @if (!loading()) {\n <sky-chart-controls [headingText]=\"headingText()\" />\n }\n</div>\n<figure\n class=\"sky-chart-content\"\n [attr.aria-busy]=\"loading() ? 'true' : null\"\n [attr.aria-label]=\"figureLabel()\"\n [attr.role]=\"figureLabel() ? 'img' : null\"\n [class.sky-chart-content-loading]=\"loading()\"\n>\n <sky-wait [isWaiting]=\"loading()\" />\n <ng-content />\n</figure>\n", styles: [":host:not(.sky-theme-modern *){--sky-override-chart-font-family-primary: BLKB Sans, Helvetica Neue, Arial, sans-serif;--sky-override-chart-font-size-body-s: 13px;--sky-override-chart-font-style-body-s: 400;--sky-override-chart-font-style-emphasized: 700;--sky-override-chart-font-line_height-body-s: 1.3846;--sky-override-chart-color-text-default: #212327;--sky-override-chart-color-text-deemphasized: #686c73;--sky-override-chart-color-viz-axis: #cdcfd2;--sky-override-chart-color-viz-gridline: #eeeeef;--sky-override-chart-color-viz-category-1: #06a39e;--sky-override-chart-color-viz-category-2: #6d3c96;--sky-override-chart-color-viz-category-3: #5589dd;--sky-override-chart-color-viz-category-4: #004252;--sky-override-chart-color-viz-category-5: #ce5600;--sky-override-chart-color-viz-category-6: #822325;--sky-override-chart-color-viz-category-7: #c650c1;--sky-override-chart-color-viz-category-8: #077e43;--sky-override-chart-color-background-container-base: #ffffff;--sky-override-chart-color-border-container-base: #cdcfd2;--sky-override-chart-size-chart-tick_length-measure: 12px;--sky-override-chart-space-stacked-xs: 4px;--sky-override-chart-space-stacked-s: 8px;--sky-override-chart-space-stacked-0: 0px;--sky-override-chart-space-gap-icon-s: 4px;--sky-override-chart-space-gap-text_action-m: 12px;--sky-override-chart-size-icon-xs: 16px;--sky-override-chart-border-width-container-base: 1px;--sky-override-chart-border-radius-s: 4px;--sky-override-chart-border-radius-xs: 2px;--sky-override-chart-comp-chart-tooltip-space-inset-top: 8px;--sky-override-chart-comp-chart-tooltip-space-inset-right: 12px;--sky-override-chart-comp-chart-tooltip-space-inset-bottom: 8px;--sky-override-chart-comp-chart-tooltip-space-inset-left: 12px}:host{display:block;--sky-chart-height-min: 11.25rem;--sky-chart-height-viewport: 28vh;--sky-chart-height-max: 25rem}.sky-chart-header{display:flex;align-items:baseline;justify-content:space-between;column-gap:var(--sky-override-chart-space-gap-text_action-m, var(--sky-space-gap-text_action-m));margin-bottom:var(--sky-override-chart-space-stacked-s, var(--sky-space-stacked-s))}figure.sky-chart-content{margin:0}figure.sky-chart-content-loading{position:relative;min-height:clamp(var(--sky-chart-height-min),var(--sky-chart-height-viewport),var(--sky-chart-height-max))}\n"] }]
|
|
1384
|
+
}], propDecorators: { headingHidden: [{ type: i0.Input, args: [{ isSignal: true, alias: "headingHidden", required: false }] }], headingLevel: [{ type: i0.Input, args: [{ isSignal: true, alias: "headingLevel", required: false }] }], headingStyle: [{ type: i0.Input, args: [{ isSignal: true, alias: "headingStyle", required: false }] }], headingText: [{ type: i0.Input, args: [{ isSignal: true, alias: "headingText", required: true }] }], helpKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "helpKey", required: false }] }], helpPopoverContent: [{ type: i0.Input, args: [{ isSignal: true, alias: "helpPopoverContent", required: false }] }], helpPopoverTitle: [{ type: i0.Input, args: [{ isSignal: true, alias: "helpPopoverTitle", required: false }] }], loading: [{ type: i0.Input, args: [{ isSignal: true, alias: "loading", required: false }] }], subheadingText: [{ type: i0.Input, args: [{ isSignal: true, alias: "subheadingText", required: false }] }] } });
|
|
212
1385
|
|
|
213
1386
|
/**
|
|
214
1387
|
* Generated bundle index. Do not edit.
|
|
215
1388
|
*/
|
|
216
1389
|
|
|
217
|
-
export { SkyChart };
|
|
1390
|
+
export { SkyChart, SkyChartAxisCategory, SkyChartAxisValue, SkyChartBar, SkyChartBarSeries };
|
|
218
1391
|
//# sourceMappingURL=skyux-charts.mjs.map
|