@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.
@@ -1,14 +1,1135 @@
1
1
  import * as i0 from '@angular/core';
2
- import { NgModule, inject, ChangeDetectionStrategy, Component, DestroyRef, input, computed, numberAttribute, booleanAttribute } from '@angular/core';
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 SkyChartDataTableModalContext {
1173
+ class SkyChartTableModalContext {
47
1174
  }
48
- class SkyChartDataTableModal {
1175
+ class SkyChartTableModal {
49
1176
  constructor() {
50
- this.context = inject(SkyChartDataTableModalContext);
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: SkyChartDataTableModal, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
54
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.0", type: SkyChartDataTableModal, isStandalone: true, selector: "ng-component", ngImport: i0, template: "<sky-modal [headingText]=\"context.headingText\">\n <sky-modal-content> Chart data table, here. </sky-modal-content>\n <sky-modal-footer>\n <button class=\"sky-btn sky-btn-link\" type=\"button\" (click)=\"modal.close()\">\n {{ 'skyux_charts.data_table_modal.close_button' | skyLibResources }}\n </button>\n </sky-modal-footer>\n</sky-modal>\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 }); }
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: SkyChartDataTableModal, decorators: [{
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> Chart data table, here. </sky-modal-content>\n <sky-modal-footer>\n <button class=\"sky-btn sky-btn-link\" type=\"button\" (click)=\"modal.close()\">\n {{ 'skyux_charts.data_table_modal.close_button' | skyLibResources }}\n </button>\n </sky-modal-footer>\n</sky-modal>\n" }]
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(SkyChartDataTableModal, {
1206
+ const instance = this.#modalSvc.open(SkyChartTableModal, {
71
1207
  size: 'large',
72
1208
  providers: [
73
1209
  {
74
- provide: SkyChartDataTableModalContext,
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.1.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: "<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", 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 }); }
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 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" }]
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 to hide the chart's subheading.
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.subheadingHidden = input(false, { ...(ngDevMode ? { debugName: "subheadingHidden" } : {}), transform: booleanAttribute });
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 subheadingText = this.subheadingText();
198
- return subheadingText
199
- ? `${this.headingText()}, ${subheadingText}`
200
- : this.headingText();
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 }, subheadingHidden: { classPropertyName: "subheadingHidden", publicName: "subheadingHidden", isSignal: true, isRequired: false, transformFunction: null }, subheadingText: { classPropertyName: "subheadingText", publicName: "subheadingText", isSignal: true, isRequired: false, transformFunction: null } }, host: { classAttribute: "sky-chart" }, 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 }\n @if (!subheadingHidden() && subheadingText(); as subheadingText) {\n <sky-chart-subheading [subheadingText]=\"subheadingText\" />\n }\n </div>\n <sky-chart-controls [headingText]=\"headingText()\" />\n</div>\n<figure class=\"sky-chart-content\" [attr.aria-label]=\"figureLabel()\">\n <ng-content />\n</figure>\n", styles: [".sky-chart{display:block}.sky-chart-header{display:flex;align-items:baseline;justify-content:space-between;column-gap:var(--sky-space-gap-text_action-m, 12px);margin-bottom:var(--sky-space-stacked-xs, 5px)}figure.sky-chart-content{margin:0}\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"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
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, host: {
209
- class: 'sky-chart',
210
- }, imports: [SkyChartControls, SkyChartHeading, SkyChartSubheading], 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 }\n @if (!subheadingHidden() && subheadingText(); as subheadingText) {\n <sky-chart-subheading [subheadingText]=\"subheadingText\" />\n }\n </div>\n <sky-chart-controls [headingText]=\"headingText()\" />\n</div>\n<figure class=\"sky-chart-content\" [attr.aria-label]=\"figureLabel()\">\n <ng-content />\n</figure>\n", styles: [".sky-chart{display:block}.sky-chart-header{display:flex;align-items:baseline;justify-content:space-between;column-gap:var(--sky-space-gap-text_action-m, 12px);margin-bottom:var(--sky-space-stacked-xs, 5px)}figure.sky-chart-content{margin:0}\n"] }]
211
- }], 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 }] }], subheadingHidden: [{ type: i0.Input, args: [{ isSignal: true, alias: "subheadingHidden", required: false }] }], subheadingText: [{ type: i0.Input, args: [{ isSignal: true, alias: "subheadingText", required: false }] }] } });
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