@aquera/nile-visualization 1.8.0 → 1.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/src/index.d.ts +1 -1
- package/dist/src/internal/highcharts-provider.d.ts +6 -0
- package/dist/src/internal/highcharts-provider.js +32 -0
- package/dist/src/internal/types/aq-config.type.d.ts +5 -0
- package/dist/src/nile-chart/index.d.ts +1 -1
- package/dist/src/nile-chart/nile-chart-config.d.ts +69 -1
- package/dist/src/nile-chart/nile-chart.css.d.ts +1 -0
- package/dist/src/nile-chart/nile-chart.css.js +175 -85
- package/dist/src/nile-chart/nile-chart.d.ts +25 -6
- package/dist/src/nile-chart/nile-chart.js +196 -55
- package/dist/src/nile-kpi-chart/nile-kpi-chart.css.js +47 -3
- package/dist/src/nile-kpi-chart/nile-kpi-chart.d.ts +29 -1
- package/dist/src/nile-kpi-chart/nile-kpi-chart.js +164 -9
- package/package.json +1 -1
|
@@ -2,9 +2,17 @@ import { __decorate } from "tslib";
|
|
|
2
2
|
import { customElement, property, query, state } from 'lit/decorators.js';
|
|
3
3
|
import { html, nothing } from 'lit';
|
|
4
4
|
import NileElement from '../internal/nile-element.js';
|
|
5
|
-
import { styles } from './nile-chart.css.js';
|
|
5
|
+
import { styles, tooltipCss } from './nile-chart.css.js';
|
|
6
6
|
import { nileChartConfig } from './nile-chart-config-builder.js';
|
|
7
7
|
import { convertConfig } from '../internal/chart-adapters.js';
|
|
8
|
+
import { deepMerge } from '../internal/utils.js';
|
|
9
|
+
import { initNileChartExporting, getHighcharts } from '../internal/highcharts-provider.js';
|
|
10
|
+
// Start loading exporting/offline-exporting/export-data at module import time.
|
|
11
|
+
// `chart.fullscreen` is attached via a `beforeRender` hook installed by the exporting module's
|
|
12
|
+
// compose step, so if a chart is constructed before the module applies, that instance will have
|
|
13
|
+
// no `fullscreen`. Kicking the load off here gives it a head start; handlers still await it
|
|
14
|
+
// before invoking methods, and viewFullscreen falls back to the DOM Fullscreen API.
|
|
15
|
+
initNileChartExporting();
|
|
8
16
|
import '../nile-bar-chart/index.js';
|
|
9
17
|
import '../nile-pie-chart/index.js';
|
|
10
18
|
import '../nile-trendline-chart/index.js';
|
|
@@ -100,9 +108,20 @@ function chartTypeLabel(type) {
|
|
|
100
108
|
.replace(/-/g, ' ')
|
|
101
109
|
.replace(/\b\w/g, ch => ch.toUpperCase());
|
|
102
110
|
}
|
|
111
|
+
let _headerTooltipStylesInjected = false;
|
|
112
|
+
function ensureHeaderTooltipStyles() {
|
|
113
|
+
if (_headerTooltipStylesInjected || typeof document === 'undefined')
|
|
114
|
+
return;
|
|
115
|
+
const style = document.createElement('style');
|
|
116
|
+
style.dataset['nilechart'] = '';
|
|
117
|
+
style.textContent = tooltipCss;
|
|
118
|
+
document.head.appendChild(style);
|
|
119
|
+
_headerTooltipStylesInjected = true;
|
|
120
|
+
}
|
|
103
121
|
let NileChart = class NileChart extends NileElement {
|
|
104
122
|
constructor() {
|
|
105
123
|
super(...arguments);
|
|
124
|
+
this._headerTipEl = null;
|
|
106
125
|
/** Full chart configuration. Accepts flat NileChartConfig or separated { chart, aq } input. */
|
|
107
126
|
this.config = null;
|
|
108
127
|
/**
|
|
@@ -122,16 +141,22 @@ let NileChart = class NileChart extends NileElement {
|
|
|
122
141
|
*/
|
|
123
142
|
this.chartTypeAttr = '';
|
|
124
143
|
/** Summary/insight text — shown as the AI panel's opening message when the chat is opened. */
|
|
144
|
+
this.summary = '';
|
|
125
145
|
/**
|
|
126
|
-
*
|
|
127
|
-
*
|
|
146
|
+
* Controls which items appear in the actions menu. All items are opt-in —
|
|
147
|
+
* only items explicitly set to `true` are shown. Merged with (and takes
|
|
148
|
+
* priority over) `config.menu`.
|
|
149
|
+
*
|
|
150
|
+
* @example
|
|
151
|
+
* // PNG + CSV only
|
|
152
|
+
* chart.menu = { enabled: true, downloadPng: true, downloadCsv: true }
|
|
128
153
|
*/
|
|
129
|
-
|
|
130
|
-
this.summary = '';
|
|
154
|
+
this.menu = null;
|
|
131
155
|
this.activeType = null;
|
|
132
156
|
this.activeConfig = null;
|
|
133
157
|
this.menuOpen = false;
|
|
134
158
|
this.chatOpen = false;
|
|
159
|
+
this._hcChart = null;
|
|
135
160
|
/** True when elements are projected into the `header` slot (default title/subtitle hidden). */
|
|
136
161
|
this.hasHeaderSlotContent = false;
|
|
137
162
|
/** True when elements are projected into `header-actions` (used to show the header row). */
|
|
@@ -142,7 +167,24 @@ let NileChart = class NileChart extends NileElement {
|
|
|
142
167
|
this.chatOpen = false;
|
|
143
168
|
}
|
|
144
169
|
};
|
|
170
|
+
this.handleChartReady = (e) => {
|
|
171
|
+
this._hcChart = e.detail?.chart ?? null;
|
|
172
|
+
};
|
|
145
173
|
this.resolvedConfig = null;
|
|
174
|
+
this._onTitleEnter = (e) => {
|
|
175
|
+
const el = e.currentTarget;
|
|
176
|
+
if (el.scrollWidth <= el.clientWidth)
|
|
177
|
+
return;
|
|
178
|
+
const rect = el.getBoundingClientRect();
|
|
179
|
+
this._showHeaderTip(el.textContent ?? '', rect.left + rect.width / 2, rect.top);
|
|
180
|
+
};
|
|
181
|
+
this._onSubtitleEnter = (e) => {
|
|
182
|
+
const el = e.currentTarget;
|
|
183
|
+
if (el.scrollWidth <= el.clientWidth)
|
|
184
|
+
return;
|
|
185
|
+
const rect = el.getBoundingClientRect();
|
|
186
|
+
this._showHeaderTip(el.textContent ?? '', rect.left + rect.width / 2, rect.top);
|
|
187
|
+
};
|
|
146
188
|
}
|
|
147
189
|
get effectiveSummary() {
|
|
148
190
|
return this.resolvedConfig?.summary ?? this.summary;
|
|
@@ -153,7 +195,14 @@ let NileChart = class NileChart extends NileElement {
|
|
|
153
195
|
}
|
|
154
196
|
connectedCallback() {
|
|
155
197
|
super.connectedCallback();
|
|
198
|
+
ensureHeaderTooltipStyles();
|
|
199
|
+
const tip = document.createElement('div');
|
|
200
|
+
tip.className = 'nile-chart-header-tooltip';
|
|
201
|
+
document.body.appendChild(tip);
|
|
202
|
+
this._headerTipEl = tip;
|
|
156
203
|
document.addEventListener('click', this.handleOutsideClick);
|
|
204
|
+
this.addEventListener('nile-chart-ready', this.handleChartReady);
|
|
205
|
+
initNileChartExporting();
|
|
157
206
|
// Pick up config set before element upgrade (e.g. Angular ngAfterViewInit)
|
|
158
207
|
if (this.config && !this.resolvedConfig) {
|
|
159
208
|
this.resolvedConfig = this.resolveConfig(this.config);
|
|
@@ -164,6 +213,9 @@ let NileChart = class NileChart extends NileElement {
|
|
|
164
213
|
disconnectedCallback() {
|
|
165
214
|
super.disconnectedCallback();
|
|
166
215
|
document.removeEventListener('click', this.handleOutsideClick);
|
|
216
|
+
this.removeEventListener('nile-chart-ready', this.handleChartReady);
|
|
217
|
+
this._headerTipEl?.remove();
|
|
218
|
+
this._headerTipEl = null;
|
|
167
219
|
}
|
|
168
220
|
mergeChartTypeFromAttr(chart) {
|
|
169
221
|
const t = this.chartTypeAttr?.trim();
|
|
@@ -278,7 +330,8 @@ let NileChart = class NileChart extends NileElement {
|
|
|
278
330
|
}
|
|
279
331
|
shouldShowHeader() {
|
|
280
332
|
const hasTitles = !!(this.headerTitle || this.headerSubtitle);
|
|
281
|
-
const
|
|
333
|
+
const menuEnabled = this.resolvedConfig?.menu?.enabled === true || this.menu?.enabled === true;
|
|
334
|
+
const hasBuiltinActions = this.aiEnabled || (this.resolvedConfig?.switchableTypes?.length ?? 0) > 0 || menuEnabled;
|
|
282
335
|
return (hasTitles ||
|
|
283
336
|
this.hasHeaderSlotContent ||
|
|
284
337
|
this.hasHeaderActionsSlot ||
|
|
@@ -286,36 +339,102 @@ let NileChart = class NileChart extends NileElement {
|
|
|
286
339
|
this.lightDomHasSlot('header-actions') ||
|
|
287
340
|
hasBuiltinActions);
|
|
288
341
|
}
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
342
|
+
buildExportingOptions() {
|
|
343
|
+
return { exporting: { enabled: true, buttons: { contextButton: { enabled: false } } } };
|
|
344
|
+
}
|
|
345
|
+
/** Ensures exporting modules are loaded and the chart's exporting/fullscreen instances exist. */
|
|
346
|
+
async ensureExporting() {
|
|
347
|
+
await initNileChartExporting();
|
|
348
|
+
const chart = this._hcChart;
|
|
349
|
+
if (chart && !chart.exporting) {
|
|
350
|
+
const HC = await getHighcharts();
|
|
351
|
+
HC.fireEvent(chart, 'afterInit');
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
async viewFullscreen() {
|
|
355
|
+
this.menuOpen = false;
|
|
356
|
+
await this.ensureExporting();
|
|
357
|
+
if (this._hcChart?.fullscreen?.open) {
|
|
358
|
+
this._hcChart.fullscreen.open();
|
|
359
|
+
return;
|
|
360
|
+
}
|
|
361
|
+
const target = this.shadowRoot?.querySelector('.nile-chart-card') ?? this;
|
|
362
|
+
target.requestFullscreen?.();
|
|
363
|
+
}
|
|
364
|
+
async printChart() {
|
|
365
|
+
this.menuOpen = false;
|
|
366
|
+
await this.ensureExporting();
|
|
367
|
+
this._hcChart?.print?.();
|
|
368
|
+
}
|
|
369
|
+
async exportChart(type) {
|
|
370
|
+
this.menuOpen = false;
|
|
371
|
+
await this.ensureExporting();
|
|
372
|
+
const filename = (this.headerTitle || 'chart').replace(/[^a-z0-9_-]+/gi, '_');
|
|
373
|
+
this._hcChart?.exportChartLocal?.({ type, filename, local: true });
|
|
374
|
+
}
|
|
375
|
+
async downloadCsv() {
|
|
376
|
+
this.menuOpen = false;
|
|
377
|
+
await this.ensureExporting();
|
|
378
|
+
this._hcChart?.downloadCSV?.();
|
|
379
|
+
}
|
|
380
|
+
renderActionsMenu() {
|
|
381
|
+
if (!this.resolvedConfig)
|
|
292
382
|
return nothing;
|
|
383
|
+
const menuCfg = { ...(this.resolvedConfig.menu ?? {}), ...(this.menu ?? {}) };
|
|
384
|
+
if (menuCfg.enabled !== true)
|
|
385
|
+
return nothing;
|
|
386
|
+
const types = this.resolvedConfig.switchableTypes;
|
|
387
|
+
const allItems = [
|
|
388
|
+
...(this.resolvedConfig.menuItems ?? []),
|
|
389
|
+
...(menuCfg.items ?? []),
|
|
390
|
+
];
|
|
391
|
+
const hasChart = !!this._hcChart;
|
|
392
|
+
const hasAnyItems = !!(types?.length || allItems.length);
|
|
293
393
|
return html `
|
|
294
|
-
<div class="chart-menu-anchor">
|
|
394
|
+
<div class="nile-chart-menu-anchor">
|
|
295
395
|
<button
|
|
296
|
-
|
|
396
|
+
type="button"
|
|
397
|
+
class="nile-chart-menu-trigger"
|
|
297
398
|
aria-haspopup="true"
|
|
298
399
|
aria-expanded=${this.menuOpen ? 'true' : 'false'}
|
|
299
|
-
aria-label="
|
|
300
|
-
@click=${this.toggleMenu}
|
|
400
|
+
aria-label="Chart actions"
|
|
401
|
+
@click=${(e) => this.toggleMenu(e)}
|
|
301
402
|
>
|
|
302
403
|
<nile-glyph name="options" size="16"></nile-glyph>
|
|
303
404
|
</button>
|
|
304
|
-
${this.menuOpen
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
405
|
+
${this.menuOpen && hasAnyItems ? html `
|
|
406
|
+
<div class="nile-chart-menu-dropdown" role="menu">
|
|
407
|
+
${types?.length ? html `
|
|
408
|
+
${types.map(type => html `
|
|
409
|
+
<button type="button" class="nile-chart-menu-item ${type === this.activeType ? 'active' : ''}" role="menuitem" @click=${() => this.switchType(type)}>
|
|
410
|
+
${chartTypeLabel(type)}
|
|
411
|
+
</button>
|
|
412
|
+
`)}
|
|
413
|
+
${allItems.length ? html `<div class="nile-chart-menu-separator"></div>` : nothing}
|
|
414
|
+
` : nothing}
|
|
415
|
+
${allItems.map(item => {
|
|
416
|
+
if (item.type === 'custom') {
|
|
417
|
+
return html `
|
|
418
|
+
${item.divider ? html `<div class="nile-chart-menu-separator"></div>` : nothing}
|
|
419
|
+
<button type="button" class="nile-chart-menu-item" role="menuitem"
|
|
420
|
+
@click=${() => { this.menuOpen = false; this.emit('nile-menu-change', { id: item.id }); }}>
|
|
421
|
+
${item.label}
|
|
422
|
+
</button>
|
|
423
|
+
`;
|
|
424
|
+
}
|
|
425
|
+
if (!hasChart)
|
|
426
|
+
return nothing;
|
|
427
|
+
return html `
|
|
428
|
+
${item.divider ? html `<div class="nile-chart-menu-separator"></div>` : nothing}
|
|
429
|
+
${item.fullscreen ? html `<button type="button" class="nile-chart-menu-item" role="menuitem" @click=${() => this.viewFullscreen()}>${item.label ?? 'Fullscreen'}</button>` : nothing}
|
|
430
|
+
${item.print ? html `<button type="button" class="nile-chart-menu-item" role="menuitem" @click=${() => this.printChart()}>${item.label ?? 'Print'}</button>` : nothing}
|
|
431
|
+
${item.downloadPng ? html `<button type="button" class="nile-chart-menu-item" role="menuitem" @click=${() => this.exportChart('image/png')}>${item.label ?? 'Download PNG'}</button>` : nothing}
|
|
432
|
+
${item.downloadSvg ? html `<button type="button" class="nile-chart-menu-item" role="menuitem" @click=${() => this.exportChart('image/svg+xml')}>${item.label ?? 'Download SVG'}</button>` : nothing}
|
|
433
|
+
${item.downloadCsv ? html `<button type="button" class="nile-chart-menu-item" role="menuitem" @click=${() => this.downloadCsv()}>${item.label ?? 'Download CSV'}</button>` : nothing}
|
|
434
|
+
`;
|
|
435
|
+
})}
|
|
436
|
+
</div>
|
|
437
|
+
` : nothing}
|
|
319
438
|
</div>
|
|
320
439
|
`;
|
|
321
440
|
}
|
|
@@ -324,16 +443,28 @@ let NileChart = class NileChart extends NileElement {
|
|
|
324
443
|
return nothing;
|
|
325
444
|
return html `
|
|
326
445
|
<button
|
|
327
|
-
class="ai-trigger ${this.chatOpen ? 'active' : ''}"
|
|
446
|
+
class="nile-ai-trigger ${this.chatOpen ? 'active' : ''}"
|
|
328
447
|
aria-label="Ask AI about this chart"
|
|
329
448
|
aria-expanded=${this.chatOpen ? 'true' : 'false'}
|
|
330
449
|
@click=${this.toggleChat}
|
|
331
450
|
>
|
|
332
451
|
<nile-glyph name="smart-code" size="16"></nile-glyph>
|
|
333
452
|
</button>
|
|
334
|
-
<slot name="ai-trigger-after"></slot>
|
|
453
|
+
<slot name="nile-ai-trigger-after"></slot>
|
|
335
454
|
`;
|
|
336
455
|
}
|
|
456
|
+
_showHeaderTip(text, x, y) {
|
|
457
|
+
if (!this._headerTipEl)
|
|
458
|
+
return;
|
|
459
|
+
this._headerTipEl.textContent = text;
|
|
460
|
+
this._headerTipEl.style.left = `${x}px`;
|
|
461
|
+
this._headerTipEl.style.top = `${y}px`;
|
|
462
|
+
this._headerTipEl.style.display = 'block';
|
|
463
|
+
}
|
|
464
|
+
_hideHeaderTip() {
|
|
465
|
+
if (this._headerTipEl)
|
|
466
|
+
this._headerTipEl.style.display = 'none';
|
|
467
|
+
}
|
|
337
468
|
renderHeader() {
|
|
338
469
|
if (!this.shouldShowHeader())
|
|
339
470
|
return nothing;
|
|
@@ -342,20 +473,20 @@ let NileChart = class NileChart extends NileElement {
|
|
|
342
473
|
const showDefaultTitles = !this.hasHeaderSlotContent && !!(title || subtitle);
|
|
343
474
|
const headerCompact = !subtitle?.trim();
|
|
344
475
|
return html `
|
|
345
|
-
<div class="chart-header ${headerCompact ? 'chart-header--compact' : ''}">
|
|
346
|
-
<div class="chart-header-titles">
|
|
476
|
+
<div class="nile-chart-header ${headerCompact ? 'nile-chart-header--compact' : ''}">
|
|
477
|
+
<div class="nile-chart-header-titles">
|
|
347
478
|
<slot name="header" @slotchange=${this.onHeaderSlotChange}></slot>
|
|
348
479
|
${showDefaultTitles
|
|
349
480
|
? html `
|
|
350
|
-
${title ? html `<p class="chart-header-title">${title}</p>` : nothing}
|
|
351
|
-
${subtitle ? html `<p class="chart-header-subtitle">${subtitle}</p>` : nothing}
|
|
481
|
+
${title ? html `<p class="nile-chart-header-title" @mouseenter=${this._onTitleEnter} @mouseleave=${() => this._hideHeaderTip()}>${title}</p>` : nothing}
|
|
482
|
+
${subtitle ? html `<p class="nile-chart-header-subtitle" @mouseenter=${this._onSubtitleEnter} @mouseleave=${() => this._hideHeaderTip()}>${subtitle}</p>` : nothing}
|
|
352
483
|
`
|
|
353
484
|
: nothing}
|
|
354
485
|
</div>
|
|
355
|
-
<div class="chart-header-actions">
|
|
486
|
+
<div class="nile-chart-header-actions">
|
|
356
487
|
<slot name="header-actions" @slotchange=${this.onHeaderActionsSlotChange}></slot>
|
|
357
488
|
${this.renderAiTrigger()}
|
|
358
|
-
${this.
|
|
489
|
+
${this.renderActionsMenu()}
|
|
359
490
|
</div>
|
|
360
491
|
</div>
|
|
361
492
|
`;
|
|
@@ -371,7 +502,7 @@ let NileChart = class NileChart extends NileElement {
|
|
|
371
502
|
const summaryMessage = summary;
|
|
372
503
|
const welcomeMessage = summary ? '' : (aiConfig?.welcomeMessage ?? '');
|
|
373
504
|
return html `
|
|
374
|
-
<div class="ai-panel-overlay" ?data-open=${this.chatOpen}>
|
|
505
|
+
<div class="nile-ai-panel-overlay" ?data-open=${this.chatOpen}>
|
|
375
506
|
<nile-ai-panel
|
|
376
507
|
.placeholder=${aiConfig?.placeholder ?? 'Ask about this chart...'}
|
|
377
508
|
.welcomeMessage=${welcomeMessage}
|
|
@@ -385,7 +516,7 @@ let NileChart = class NileChart extends NileElement {
|
|
|
385
516
|
const config = this.activeConfig;
|
|
386
517
|
// Suppress inner Highcharts title/subtitle — the card header shows them
|
|
387
518
|
const noTitle = { title: { text: undefined }, subtitle: { text: undefined } };
|
|
388
|
-
const mergedOptions =
|
|
519
|
+
const mergedOptions = deepMerge(deepMerge(config.options ?? {}, noTitle), this.buildExportingOptions());
|
|
389
520
|
switch (config.type) {
|
|
390
521
|
case 'bar':
|
|
391
522
|
return html `<nile-bar-chart
|
|
@@ -542,7 +673,6 @@ let NileChart = class NileChart extends NileElement {
|
|
|
542
673
|
.height=${this.hasAttribute('fit') ? null : (config.height ?? '100%')}
|
|
543
674
|
.innerSize=${config.innerSize ?? '50%'}
|
|
544
675
|
.semiCircle=${config.semiCircle ?? false}
|
|
545
|
-
.semiCircle=${config.semiCircle ?? false}
|
|
546
676
|
.showDataLabels=${config.showDataLabels ?? true}
|
|
547
677
|
.showLegend=${config.showLegend ?? true}
|
|
548
678
|
.options=${mergedOptions}
|
|
@@ -1370,6 +1500,7 @@ let NileChart = class NileChart extends NileElement {
|
|
|
1370
1500
|
></nile-xrange-chart>`;
|
|
1371
1501
|
case 'kpi': {
|
|
1372
1502
|
const k = config;
|
|
1503
|
+
const kpiOptions = deepMerge(k.options ?? {}, this.buildExportingOptions());
|
|
1373
1504
|
return html `<nile-kpi-chart
|
|
1374
1505
|
embed-in-nile-chart
|
|
1375
1506
|
.config=${{
|
|
@@ -1429,8 +1560,11 @@ let NileChart = class NileChart extends NileElement {
|
|
|
1429
1560
|
contentGap: k.contentGap,
|
|
1430
1561
|
tooltipEnabled: k.tooltipEnabled,
|
|
1431
1562
|
loading: k.loading,
|
|
1432
|
-
options:
|
|
1563
|
+
options: kpiOptions,
|
|
1433
1564
|
height: k.height,
|
|
1565
|
+
valueFormat: k.valueFormat,
|
|
1566
|
+
precision: k.precision,
|
|
1567
|
+
unit: k.unit,
|
|
1434
1568
|
},
|
|
1435
1569
|
aq: {
|
|
1436
1570
|
chartSubtitle: k.chartSubtitle,
|
|
@@ -1442,9 +1576,6 @@ let NileChart = class NileChart extends NileElement {
|
|
|
1442
1576
|
const gridChrome = '--nile-data-grid-radius:0;' +
|
|
1443
1577
|
'--nile-data-grid-border-color:transparent;' +
|
|
1444
1578
|
'--nile-data-grid-shadow:none;';
|
|
1445
|
-
const gridStyle = config.height
|
|
1446
|
-
? `${gridChrome}height:${config.height};`
|
|
1447
|
-
: gridChrome;
|
|
1448
1579
|
return html `<nile-data-grid
|
|
1449
1580
|
class="nile-chart-grid"
|
|
1450
1581
|
.data=${config.data}
|
|
@@ -1454,9 +1585,9 @@ let NileChart = class NileChart extends NileElement {
|
|
|
1454
1585
|
.hoverable=${config.hoverable ?? false}
|
|
1455
1586
|
.stickyHeader=${config.stickyHeader ?? false}
|
|
1456
1587
|
.emptyMessage=${config.emptyMessage ?? 'No data'}
|
|
1457
|
-
.loadingMessage=${config.loadingMessage ?? 'Loading
|
|
1588
|
+
.loadingMessage=${config.loadingMessage ?? 'Loading…'}
|
|
1458
1589
|
.noMatchMessage=${config.noMatchMessage ?? 'No matching rows'}
|
|
1459
|
-
style=${
|
|
1590
|
+
style=${gridChrome}
|
|
1460
1591
|
></nile-data-grid>`;
|
|
1461
1592
|
}
|
|
1462
1593
|
default: {
|
|
@@ -1467,28 +1598,32 @@ let NileChart = class NileChart extends NileElement {
|
|
|
1467
1598
|
}
|
|
1468
1599
|
renderSkeleton() {
|
|
1469
1600
|
return html `
|
|
1470
|
-
<div class="chart-skeleton" aria-busy="true" aria-label="Loading chart">
|
|
1471
|
-
<div class="chart-skeleton-body">
|
|
1601
|
+
<div class="nile-chart-skeleton" aria-busy="true" aria-label="Loading chart">
|
|
1602
|
+
<div class="nile-chart-skeleton-body">
|
|
1472
1603
|
${[78, 55, 91, 42, 68].map(w => html `
|
|
1473
|
-
<div class="chart-skeleton-row">
|
|
1474
|
-
<div class="chart-skeleton-ylabel"></div>
|
|
1475
|
-
<div class="chart-skeleton-bar" style="--w: ${w}%"></div>
|
|
1604
|
+
<div class="nile-chart-skeleton-row">
|
|
1605
|
+
<div class="nile-chart-skeleton-ylabel"></div>
|
|
1606
|
+
<div class="nile-chart-skeleton-bar" style="--w: ${w}%"></div>
|
|
1476
1607
|
</div>
|
|
1477
1608
|
`)}
|
|
1478
1609
|
</div>
|
|
1479
|
-
<div class="chart-skeleton-xaxis-row">
|
|
1480
|
-
${[0, 1, 2, 3, 4].map(i => html `<div class="chart-skeleton-xlabel" style="--d: ${i * 80}ms"></div>`)}
|
|
1610
|
+
<div class="nile-chart-skeleton-xaxis-row">
|
|
1611
|
+
${[0, 1, 2, 3, 4].map(i => html `<div class="nile-chart-skeleton-xlabel" style="--d: ${i * 80}ms"></div>`)}
|
|
1481
1612
|
</div>
|
|
1482
1613
|
</div>
|
|
1483
1614
|
`;
|
|
1484
1615
|
}
|
|
1485
1616
|
render() {
|
|
1486
1617
|
const isLoading = this.loading || (this.activeConfig?.loading ?? false);
|
|
1618
|
+
const isGrid = this.activeConfig?.type === 'grid';
|
|
1619
|
+
const cardStyle = isGrid && this.activeConfig?.height
|
|
1620
|
+
? `height:${this.activeConfig.height}`
|
|
1621
|
+
: '';
|
|
1487
1622
|
return html `
|
|
1488
|
-
<div class="chart-card">
|
|
1623
|
+
<div class="nile-chart-card ${isGrid ? 'nile-chart-card--grid' : ''}" style=${cardStyle}>
|
|
1489
1624
|
${this.renderHeader()}
|
|
1490
|
-
<div class="chart-wrapper">
|
|
1491
|
-
<div class="chart-inner ${this.activeConfig?.type === 'kpi' ? 'chart-inner--kpi' : ''}">
|
|
1625
|
+
<div class="nile-chart-wrapper">
|
|
1626
|
+
<div class="nile-chart-inner ${this.activeConfig?.type === 'kpi' ? 'nile-chart-inner--kpi' : ''}">
|
|
1492
1627
|
${isLoading
|
|
1493
1628
|
? this.renderSkeleton()
|
|
1494
1629
|
: this.activeConfig
|
|
@@ -1515,6 +1650,9 @@ __decorate([
|
|
|
1515
1650
|
__decorate([
|
|
1516
1651
|
property({ type: String })
|
|
1517
1652
|
], NileChart.prototype, "summary", void 0);
|
|
1653
|
+
__decorate([
|
|
1654
|
+
property({ type: Object })
|
|
1655
|
+
], NileChart.prototype, "menu", void 0);
|
|
1518
1656
|
__decorate([
|
|
1519
1657
|
state()
|
|
1520
1658
|
], NileChart.prototype, "activeType", void 0);
|
|
@@ -1527,6 +1665,9 @@ __decorate([
|
|
|
1527
1665
|
__decorate([
|
|
1528
1666
|
state()
|
|
1529
1667
|
], NileChart.prototype, "chatOpen", void 0);
|
|
1668
|
+
__decorate([
|
|
1669
|
+
state()
|
|
1670
|
+
], NileChart.prototype, "_hcChart", void 0);
|
|
1530
1671
|
__decorate([
|
|
1531
1672
|
state()
|
|
1532
1673
|
], NileChart.prototype, "hasHeaderSlotContent", void 0);
|
|
@@ -47,7 +47,7 @@ export const styles = css `
|
|
|
47
47
|
--nile-kpi-label-color: var(--nile-colors-neutral-700, var(--ng-colors-text-secondary-700));
|
|
48
48
|
--nile-kpi-label-font-size: var(--nile-type-scale-3, var(--ng-font-size-text-sm));
|
|
49
49
|
--nile-kpi-label-font-weight: var(--nile-font-weight-medium, var(--ng-font-weight-medium));
|
|
50
|
-
--nile-kpi-value-font-size: clamp(1.25rem,
|
|
50
|
+
--nile-kpi-value-font-size: clamp(1.25rem, 5cqi + 0.5rem, 36px);
|
|
51
51
|
--nile-kpi-value-color: var(--nile-colors-dark-900, var(--ng-colors-text-primary-900));
|
|
52
52
|
--nile-kpi-prefix-suffix-font-size: var(--nile-type-scale-6, var(--ng-font-size-text-xl));
|
|
53
53
|
--nile-kpi-prefix-suffix-color: var(--nile-colors-neutral-700, var(--ng-colors-text-secondary-700));
|
|
@@ -59,8 +59,11 @@ export const styles = css `
|
|
|
59
59
|
display: flex;
|
|
60
60
|
flex-direction: column;
|
|
61
61
|
width: 100%;
|
|
62
|
+
min-width: 160px;
|
|
62
63
|
position: relative;
|
|
63
64
|
box-sizing: border-box;
|
|
65
|
+
overflow: hidden;
|
|
66
|
+
container-type: inline-size;
|
|
64
67
|
}
|
|
65
68
|
|
|
66
69
|
:host([hidden]) {
|
|
@@ -89,12 +92,29 @@ export const styles = css `
|
|
|
89
92
|
box-shadow: var(--nile-kpi-card-shadow-hover);
|
|
90
93
|
}
|
|
91
94
|
|
|
95
|
+
/* Accent variant — card chrome with a prominent left colour bar. */
|
|
96
|
+
:host([variant='accent']:not([embed-in-nile-chart])) {
|
|
97
|
+
--nile-kpi-accent-color: #005EA6;
|
|
98
|
+
background: var(--nile-kpi-card-bg);
|
|
99
|
+
border: var(--nile-kpi-card-border-width) solid var(--nile-kpi-card-border-color);
|
|
100
|
+
border-left: 4px solid var(--nile-kpi-accent-color);
|
|
101
|
+
border-radius: var(--nile-kpi-card-border-radius);
|
|
102
|
+
box-shadow: var(--nile-kpi-card-shadow);
|
|
103
|
+
transition: box-shadow var(--nile-transition-duration-default, var(--ng-transition-duration-default)) ease;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
:host([variant='accent']:not([embed-in-nile-chart]):hover) {
|
|
107
|
+
box-shadow: var(--nile-kpi-card-shadow-hover);
|
|
108
|
+
}
|
|
109
|
+
|
|
92
110
|
.kpi {
|
|
93
111
|
flex: 1 1 auto;
|
|
94
112
|
display: flex;
|
|
95
113
|
flex-direction: column;
|
|
96
114
|
gap: var(--nile-kpi-content-gap);
|
|
97
115
|
padding: var(--nile-kpi-padding-v) var(--nile-kpi-padding-h);
|
|
116
|
+
min-width: 0;
|
|
117
|
+
overflow: hidden;
|
|
98
118
|
}
|
|
99
119
|
|
|
100
120
|
.kpi-label {
|
|
@@ -104,13 +124,20 @@ export const styles = css `
|
|
|
104
124
|
font-weight: var(--nile-kpi-label-font-weight);
|
|
105
125
|
color: var(--nile-kpi-label-color);
|
|
106
126
|
line-height: 1.4;
|
|
127
|
+
white-space: nowrap;
|
|
128
|
+
overflow: hidden;
|
|
129
|
+
text-overflow: ellipsis;
|
|
130
|
+
min-width: 0;
|
|
131
|
+
width: 100%;
|
|
107
132
|
}
|
|
108
133
|
|
|
109
134
|
.kpi-value-row {
|
|
110
135
|
display: flex;
|
|
111
|
-
align-items:
|
|
136
|
+
align-items: center;
|
|
112
137
|
gap: var(--nile-spacing-md, var(--ng-spacing-md));
|
|
113
|
-
flex-wrap:
|
|
138
|
+
flex-wrap: nowrap;
|
|
139
|
+
min-width: 0;
|
|
140
|
+
overflow: hidden;
|
|
114
141
|
}
|
|
115
142
|
|
|
116
143
|
.kpi-value {
|
|
@@ -121,6 +148,8 @@ export const styles = css `
|
|
|
121
148
|
color: var(--nile-kpi-value-color);
|
|
122
149
|
line-height: 1.2;
|
|
123
150
|
cursor: default;
|
|
151
|
+
white-space: nowrap;
|
|
152
|
+
flex-shrink: 0;
|
|
124
153
|
}
|
|
125
154
|
|
|
126
155
|
.kpi-prefix,
|
|
@@ -141,6 +170,16 @@ export const styles = css `
|
|
|
141
170
|
font-size: var(--nile-type-scale-2, var(--ng-font-size-text-xs));
|
|
142
171
|
font-weight: var(--nile-font-weight-medium, var(--ng-font-weight-medium));
|
|
143
172
|
line-height: 1;
|
|
173
|
+
flex-shrink: 1;
|
|
174
|
+
min-width: 0;
|
|
175
|
+
overflow: hidden;
|
|
176
|
+
white-space: nowrap;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
@container (max-width: 240px) {
|
|
180
|
+
.kpi-trend {
|
|
181
|
+
display: none;
|
|
182
|
+
}
|
|
144
183
|
}
|
|
145
184
|
|
|
146
185
|
.kpi-trend--up {
|
|
@@ -176,6 +215,11 @@ export const styles = css `
|
|
|
176
215
|
font-size: var(--nile-kpi-description-font-size);
|
|
177
216
|
color: var(--nile-kpi-description-color);
|
|
178
217
|
line-height: 1.5;
|
|
218
|
+
white-space: nowrap;
|
|
219
|
+
overflow: hidden;
|
|
220
|
+
text-overflow: ellipsis;
|
|
221
|
+
min-width: 0;
|
|
222
|
+
width: 100%;
|
|
179
223
|
}
|
|
180
224
|
|
|
181
225
|
.kpi-sparkline {
|
|
@@ -3,8 +3,9 @@ import type Highcharts from 'highcharts';
|
|
|
3
3
|
import NileElement from '../internal/nile-element.js';
|
|
4
4
|
import type { AqConfigType } from '../internal/types/aq-config.type.js';
|
|
5
5
|
export type TrendDirection = 'up' | 'down' | 'neutral';
|
|
6
|
-
export type KpiVariant = 'default' | 'card' | 'gauge';
|
|
6
|
+
export type KpiVariant = 'default' | 'card' | 'gauge' | 'accent';
|
|
7
7
|
export type SparklineType = 'area' | 'line';
|
|
8
|
+
export type KpiValueFormat = 'auto' | 'K' | 'M' | 'B' | 'T' | 'none';
|
|
8
9
|
/** `chart` slice for `<nile-kpi-chart>.config` (discriminated by `type: 'kpi'`). */
|
|
9
10
|
export interface ChartKpiSeparatedPayload {
|
|
10
11
|
type: 'kpi';
|
|
@@ -63,6 +64,8 @@ export interface ChartKpiSeparatedPayload {
|
|
|
63
64
|
loadingText?: string;
|
|
64
65
|
/** Whether the hover tooltip is shown on value / sparkline / gauge (default: true). */
|
|
65
66
|
tooltipEnabled?: boolean;
|
|
67
|
+
/** Accent bar colour for the 'accent' variant (default: brand blue). */
|
|
68
|
+
accentColor?: string;
|
|
66
69
|
cardBackground?: string;
|
|
67
70
|
cardBorderColor?: string;
|
|
68
71
|
cardBorderWidth?: string | number;
|
|
@@ -86,6 +89,18 @@ export interface ChartKpiSeparatedPayload {
|
|
|
86
89
|
options?: Highcharts.Options;
|
|
87
90
|
/** Box size when a height is set (host min-height and height). */
|
|
88
91
|
height?: string | number;
|
|
92
|
+
/** How to abbreviate the display value. 'auto' picks K/M/B/T by magnitude. Default: 'auto'. */
|
|
93
|
+
valueFormat?: KpiValueFormat;
|
|
94
|
+
/** Decimal places for the abbreviated value. Auto-selects 0–2 when omitted. */
|
|
95
|
+
precision?: number;
|
|
96
|
+
/** BCP 47 locale for number formatting, e.g. 'en-IN'. Defaults to browser locale. */
|
|
97
|
+
locale?: string;
|
|
98
|
+
/**
|
|
99
|
+
* Base unit appended after the magnitude prefix — e.g. `'L'` + `valueFormat: 'auto'` on 1500
|
|
100
|
+
* renders `"1.5KL"` on the card and `"1,500L"` in the tooltip. Use for physical units
|
|
101
|
+
* (L, g, m, Pa, Hz, B, etc.). For non-unit suffix text (%, /day), use `suffix` instead.
|
|
102
|
+
*/
|
|
103
|
+
unit?: string;
|
|
89
104
|
}
|
|
90
105
|
/** Separated `{ chart, aq }` input for `<nile-kpi-chart>`. */
|
|
91
106
|
export interface NileKpiConfigInputType {
|
|
@@ -196,6 +211,8 @@ export declare class NileKpiChart extends NileElement {
|
|
|
196
211
|
cardPaddingVertical: string | number;
|
|
197
212
|
cardPaddingHorizontal: string | number;
|
|
198
213
|
contentGap: string | number;
|
|
214
|
+
/** Accent bar colour for the 'accent' variant. */
|
|
215
|
+
accentColor: string;
|
|
199
216
|
labelColor: string;
|
|
200
217
|
labelFontSize: string | number;
|
|
201
218
|
labelFontWeight: string | number;
|
|
@@ -210,6 +227,14 @@ export declare class NileKpiChart extends NileElement {
|
|
|
210
227
|
loading: boolean;
|
|
211
228
|
/** Highcharts options override for the sparkline or gauge. */
|
|
212
229
|
options: Highcharts.Options;
|
|
230
|
+
/** How to abbreviate the numeric value. 'auto' picks K/M/B/T by magnitude. Default: 'auto'. */
|
|
231
|
+
valueFormat: KpiValueFormat;
|
|
232
|
+
/** Base unit combined with the magnitude prefix (e.g. 'L' → "1.5KL"). */
|
|
233
|
+
unit: string;
|
|
234
|
+
/** Decimal places for the abbreviated value. null = auto (0–2 by magnitude). */
|
|
235
|
+
precision: number | null;
|
|
236
|
+
/** BCP 47 locale for number formatting, e.g. 'en-IN'. Defaults to browser locale. */
|
|
237
|
+
locale: string;
|
|
213
238
|
/**
|
|
214
239
|
* Set by nile-chart: skip host border/shadow (variant card/gauge) so the parent chart-card is the only frame.
|
|
215
240
|
*/
|
|
@@ -223,6 +248,7 @@ export declare class NileKpiChart extends NileElement {
|
|
|
223
248
|
private formatTooltipNumber;
|
|
224
249
|
private inferSparklineTooltipScale;
|
|
225
250
|
private getTooltipContent;
|
|
251
|
+
private formatValue;
|
|
226
252
|
private applyConfig;
|
|
227
253
|
connectedCallback(): void;
|
|
228
254
|
disconnectedCallback(): void;
|
|
@@ -240,6 +266,8 @@ export declare class NileKpiChart extends NileElement {
|
|
|
240
266
|
private destroyGauge;
|
|
241
267
|
private destroyCharts;
|
|
242
268
|
private _onValueEnter;
|
|
269
|
+
private _onDescEnter;
|
|
270
|
+
private _onLabelEnter;
|
|
243
271
|
private _onGaugeEnter;
|
|
244
272
|
private _onTipLeave;
|
|
245
273
|
private renderTrend;
|