@aquera/nile-visualization 1.8.0 → 2.0.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 +208 -56
- 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 +2 -2
|
@@ -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,27 +141,60 @@ 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). */
|
|
138
163
|
this.hasHeaderActionsSlot = false;
|
|
139
164
|
this.handleOutsideClick = (e) => {
|
|
140
|
-
|
|
165
|
+
const path = e.composedPath();
|
|
166
|
+
if (!path.includes(this)) {
|
|
141
167
|
this.menuOpen = false;
|
|
142
168
|
this.chatOpen = false;
|
|
169
|
+
return;
|
|
143
170
|
}
|
|
171
|
+
if (this.chatOpen) {
|
|
172
|
+
const root = this.renderRoot;
|
|
173
|
+
const panel = root.querySelector('.nile-ai-panel-overlay');
|
|
174
|
+
const trigger = root.querySelector('.nile-ai-trigger');
|
|
175
|
+
if (!path.some((n) => n === panel || n === trigger)) {
|
|
176
|
+
this.chatOpen = false;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
};
|
|
180
|
+
this.handleChartReady = (e) => {
|
|
181
|
+
this._hcChart = e.detail?.chart ?? null;
|
|
144
182
|
};
|
|
145
183
|
this.resolvedConfig = null;
|
|
184
|
+
this._onTitleEnter = (e) => {
|
|
185
|
+
const el = e.currentTarget;
|
|
186
|
+
if (el.scrollWidth <= el.clientWidth)
|
|
187
|
+
return;
|
|
188
|
+
const rect = el.getBoundingClientRect();
|
|
189
|
+
this._showHeaderTip(el.textContent ?? '', rect.left + rect.width / 2, rect.top);
|
|
190
|
+
};
|
|
191
|
+
this._onSubtitleEnter = (e) => {
|
|
192
|
+
const el = e.currentTarget;
|
|
193
|
+
if (el.scrollWidth <= el.clientWidth)
|
|
194
|
+
return;
|
|
195
|
+
const rect = el.getBoundingClientRect();
|
|
196
|
+
this._showHeaderTip(el.textContent ?? '', rect.left + rect.width / 2, rect.top);
|
|
197
|
+
};
|
|
146
198
|
}
|
|
147
199
|
get effectiveSummary() {
|
|
148
200
|
return this.resolvedConfig?.summary ?? this.summary;
|
|
@@ -153,7 +205,14 @@ let NileChart = class NileChart extends NileElement {
|
|
|
153
205
|
}
|
|
154
206
|
connectedCallback() {
|
|
155
207
|
super.connectedCallback();
|
|
208
|
+
ensureHeaderTooltipStyles();
|
|
209
|
+
const tip = document.createElement('div');
|
|
210
|
+
tip.className = 'nile-chart-header-tooltip';
|
|
211
|
+
document.body.appendChild(tip);
|
|
212
|
+
this._headerTipEl = tip;
|
|
156
213
|
document.addEventListener('click', this.handleOutsideClick);
|
|
214
|
+
this.addEventListener('nile-chart-ready', this.handleChartReady);
|
|
215
|
+
initNileChartExporting();
|
|
157
216
|
// Pick up config set before element upgrade (e.g. Angular ngAfterViewInit)
|
|
158
217
|
if (this.config && !this.resolvedConfig) {
|
|
159
218
|
this.resolvedConfig = this.resolveConfig(this.config);
|
|
@@ -164,6 +223,9 @@ let NileChart = class NileChart extends NileElement {
|
|
|
164
223
|
disconnectedCallback() {
|
|
165
224
|
super.disconnectedCallback();
|
|
166
225
|
document.removeEventListener('click', this.handleOutsideClick);
|
|
226
|
+
this.removeEventListener('nile-chart-ready', this.handleChartReady);
|
|
227
|
+
this._headerTipEl?.remove();
|
|
228
|
+
this._headerTipEl = null;
|
|
167
229
|
}
|
|
168
230
|
mergeChartTypeFromAttr(chart) {
|
|
169
231
|
const t = this.chartTypeAttr?.trim();
|
|
@@ -278,7 +340,8 @@ let NileChart = class NileChart extends NileElement {
|
|
|
278
340
|
}
|
|
279
341
|
shouldShowHeader() {
|
|
280
342
|
const hasTitles = !!(this.headerTitle || this.headerSubtitle);
|
|
281
|
-
const
|
|
343
|
+
const menuEnabled = this.resolvedConfig?.menu?.enabled === true || this.menu?.enabled === true;
|
|
344
|
+
const hasBuiltinActions = this.aiEnabled || (this.resolvedConfig?.switchableTypes?.length ?? 0) > 0 || menuEnabled;
|
|
282
345
|
return (hasTitles ||
|
|
283
346
|
this.hasHeaderSlotContent ||
|
|
284
347
|
this.hasHeaderActionsSlot ||
|
|
@@ -286,36 +349,102 @@ let NileChart = class NileChart extends NileElement {
|
|
|
286
349
|
this.lightDomHasSlot('header-actions') ||
|
|
287
350
|
hasBuiltinActions);
|
|
288
351
|
}
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
352
|
+
buildExportingOptions() {
|
|
353
|
+
return { exporting: { enabled: true, buttons: { contextButton: { enabled: false } } } };
|
|
354
|
+
}
|
|
355
|
+
/** Ensures exporting modules are loaded and the chart's exporting/fullscreen instances exist. */
|
|
356
|
+
async ensureExporting() {
|
|
357
|
+
await initNileChartExporting();
|
|
358
|
+
const chart = this._hcChart;
|
|
359
|
+
if (chart && !chart.exporting) {
|
|
360
|
+
const HC = await getHighcharts();
|
|
361
|
+
HC.fireEvent(chart, 'afterInit');
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
async viewFullscreen() {
|
|
365
|
+
this.menuOpen = false;
|
|
366
|
+
await this.ensureExporting();
|
|
367
|
+
if (this._hcChart?.fullscreen?.open) {
|
|
368
|
+
this._hcChart.fullscreen.open();
|
|
369
|
+
return;
|
|
370
|
+
}
|
|
371
|
+
const target = this.shadowRoot?.querySelector('.nile-chart-card') ?? this;
|
|
372
|
+
target.requestFullscreen?.();
|
|
373
|
+
}
|
|
374
|
+
async printChart() {
|
|
375
|
+
this.menuOpen = false;
|
|
376
|
+
await this.ensureExporting();
|
|
377
|
+
this._hcChart?.print?.();
|
|
378
|
+
}
|
|
379
|
+
async exportChart(type) {
|
|
380
|
+
this.menuOpen = false;
|
|
381
|
+
await this.ensureExporting();
|
|
382
|
+
const filename = (this.headerTitle || 'chart').replace(/[^a-z0-9_-]+/gi, '_');
|
|
383
|
+
this._hcChart?.exportChartLocal?.({ type, filename, local: true });
|
|
384
|
+
}
|
|
385
|
+
async downloadCsv() {
|
|
386
|
+
this.menuOpen = false;
|
|
387
|
+
await this.ensureExporting();
|
|
388
|
+
this._hcChart?.downloadCSV?.();
|
|
389
|
+
}
|
|
390
|
+
renderActionsMenu() {
|
|
391
|
+
if (!this.resolvedConfig)
|
|
292
392
|
return nothing;
|
|
393
|
+
const menuCfg = { ...(this.resolvedConfig.menu ?? {}), ...(this.menu ?? {}) };
|
|
394
|
+
if (menuCfg.enabled !== true)
|
|
395
|
+
return nothing;
|
|
396
|
+
const types = this.resolvedConfig.switchableTypes;
|
|
397
|
+
const allItems = [
|
|
398
|
+
...(this.resolvedConfig.menuItems ?? []),
|
|
399
|
+
...(menuCfg.items ?? []),
|
|
400
|
+
];
|
|
401
|
+
const hasChart = !!this._hcChart;
|
|
402
|
+
const hasAnyItems = !!(types?.length || allItems.length);
|
|
293
403
|
return html `
|
|
294
|
-
<div class="chart-menu-anchor">
|
|
404
|
+
<div class="nile-chart-menu-anchor">
|
|
295
405
|
<button
|
|
296
|
-
|
|
406
|
+
type="button"
|
|
407
|
+
class="nile-chart-menu-trigger"
|
|
297
408
|
aria-haspopup="true"
|
|
298
409
|
aria-expanded=${this.menuOpen ? 'true' : 'false'}
|
|
299
|
-
aria-label="
|
|
300
|
-
@click=${this.toggleMenu}
|
|
410
|
+
aria-label="Chart actions"
|
|
411
|
+
@click=${(e) => this.toggleMenu(e)}
|
|
301
412
|
>
|
|
302
413
|
<nile-glyph name="options" size="16"></nile-glyph>
|
|
303
414
|
</button>
|
|
304
|
-
${this.menuOpen
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
415
|
+
${this.menuOpen && hasAnyItems ? html `
|
|
416
|
+
<div class="nile-chart-menu-dropdown" role="menu">
|
|
417
|
+
${types?.length ? html `
|
|
418
|
+
${types.map(type => html `
|
|
419
|
+
<button type="button" class="nile-chart-menu-item ${type === this.activeType ? 'active' : ''}" role="menuitem" @click=${() => this.switchType(type)}>
|
|
420
|
+
${chartTypeLabel(type)}
|
|
421
|
+
</button>
|
|
422
|
+
`)}
|
|
423
|
+
${allItems.length ? html `<div class="nile-chart-menu-separator"></div>` : nothing}
|
|
424
|
+
` : nothing}
|
|
425
|
+
${allItems.map(item => {
|
|
426
|
+
if (item.type === 'custom') {
|
|
427
|
+
return html `
|
|
428
|
+
${item.divider ? html `<div class="nile-chart-menu-separator"></div>` : nothing}
|
|
429
|
+
<button type="button" class="nile-chart-menu-item" role="menuitem"
|
|
430
|
+
@click=${() => { this.menuOpen = false; this.emit('nile-menu-change', { id: item.id }); }}>
|
|
431
|
+
${item.label}
|
|
432
|
+
</button>
|
|
433
|
+
`;
|
|
434
|
+
}
|
|
435
|
+
if (!hasChart)
|
|
436
|
+
return nothing;
|
|
437
|
+
return html `
|
|
438
|
+
${item.divider ? html `<div class="nile-chart-menu-separator"></div>` : nothing}
|
|
439
|
+
${item.fullscreen ? html `<button type="button" class="nile-chart-menu-item" role="menuitem" @click=${() => this.viewFullscreen()}>${item.label ?? 'Fullscreen'}</button>` : nothing}
|
|
440
|
+
${item.print ? html `<button type="button" class="nile-chart-menu-item" role="menuitem" @click=${() => this.printChart()}>${item.label ?? 'Print'}</button>` : nothing}
|
|
441
|
+
${item.downloadPng ? html `<button type="button" class="nile-chart-menu-item" role="menuitem" @click=${() => this.exportChart('image/png')}>${item.label ?? 'Download PNG'}</button>` : nothing}
|
|
442
|
+
${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}
|
|
443
|
+
${item.downloadCsv ? html `<button type="button" class="nile-chart-menu-item" role="menuitem" @click=${() => this.downloadCsv()}>${item.label ?? 'Download CSV'}</button>` : nothing}
|
|
444
|
+
`;
|
|
445
|
+
})}
|
|
446
|
+
</div>
|
|
447
|
+
` : nothing}
|
|
319
448
|
</div>
|
|
320
449
|
`;
|
|
321
450
|
}
|
|
@@ -324,16 +453,29 @@ let NileChart = class NileChart extends NileElement {
|
|
|
324
453
|
return nothing;
|
|
325
454
|
return html `
|
|
326
455
|
<button
|
|
327
|
-
class="ai-trigger ${this.chatOpen ? 'active' : ''}"
|
|
456
|
+
class="nile-ai-trigger ${this.chatOpen ? 'active' : ''}"
|
|
328
457
|
aria-label="Ask AI about this chart"
|
|
329
458
|
aria-expanded=${this.chatOpen ? 'true' : 'false'}
|
|
330
459
|
@click=${this.toggleChat}
|
|
460
|
+
part="ai-trigger"
|
|
331
461
|
>
|
|
332
462
|
<nile-glyph name="smart-code" size="16"></nile-glyph>
|
|
333
463
|
</button>
|
|
334
|
-
<slot name="ai-trigger-after"></slot>
|
|
464
|
+
<slot name="nile-ai-trigger-after"></slot>
|
|
335
465
|
`;
|
|
336
466
|
}
|
|
467
|
+
_showHeaderTip(text, x, y) {
|
|
468
|
+
if (!this._headerTipEl)
|
|
469
|
+
return;
|
|
470
|
+
this._headerTipEl.textContent = text;
|
|
471
|
+
this._headerTipEl.style.left = `${x}px`;
|
|
472
|
+
this._headerTipEl.style.top = `${y}px`;
|
|
473
|
+
this._headerTipEl.style.display = 'block';
|
|
474
|
+
}
|
|
475
|
+
_hideHeaderTip() {
|
|
476
|
+
if (this._headerTipEl)
|
|
477
|
+
this._headerTipEl.style.display = 'none';
|
|
478
|
+
}
|
|
337
479
|
renderHeader() {
|
|
338
480
|
if (!this.shouldShowHeader())
|
|
339
481
|
return nothing;
|
|
@@ -342,20 +484,20 @@ let NileChart = class NileChart extends NileElement {
|
|
|
342
484
|
const showDefaultTitles = !this.hasHeaderSlotContent && !!(title || subtitle);
|
|
343
485
|
const headerCompact = !subtitle?.trim();
|
|
344
486
|
return html `
|
|
345
|
-
<div class="chart-header ${headerCompact ? 'chart-header--compact' : ''}">
|
|
346
|
-
<div class="chart-header-titles">
|
|
487
|
+
<div class="nile-chart-header ${headerCompact ? 'nile-chart-header--compact' : ''}">
|
|
488
|
+
<div class="nile-chart-header-titles">
|
|
347
489
|
<slot name="header" @slotchange=${this.onHeaderSlotChange}></slot>
|
|
348
490
|
${showDefaultTitles
|
|
349
491
|
? html `
|
|
350
|
-
${title ? html `<p class="chart-header-title">${title}</p>` : nothing}
|
|
351
|
-
${subtitle ? html `<p class="chart-header-subtitle">${subtitle}</p>` : nothing}
|
|
492
|
+
${title ? html `<p class="nile-chart-header-title" @mouseenter=${this._onTitleEnter} @mouseleave=${() => this._hideHeaderTip()}>${title}</p>` : nothing}
|
|
493
|
+
${subtitle ? html `<p class="nile-chart-header-subtitle" @mouseenter=${this._onSubtitleEnter} @mouseleave=${() => this._hideHeaderTip()}>${subtitle}</p>` : nothing}
|
|
352
494
|
`
|
|
353
495
|
: nothing}
|
|
354
496
|
</div>
|
|
355
|
-
<div class="chart-header-actions">
|
|
497
|
+
<div class="nile-chart-header-actions">
|
|
356
498
|
<slot name="header-actions" @slotchange=${this.onHeaderActionsSlotChange}></slot>
|
|
357
499
|
${this.renderAiTrigger()}
|
|
358
|
-
${this.
|
|
500
|
+
${this.renderActionsMenu()}
|
|
359
501
|
</div>
|
|
360
502
|
</div>
|
|
361
503
|
`;
|
|
@@ -371,7 +513,7 @@ let NileChart = class NileChart extends NileElement {
|
|
|
371
513
|
const summaryMessage = summary;
|
|
372
514
|
const welcomeMessage = summary ? '' : (aiConfig?.welcomeMessage ?? '');
|
|
373
515
|
return html `
|
|
374
|
-
<div class="ai-panel-overlay" ?data-open=${this.chatOpen}>
|
|
516
|
+
<div class="nile-ai-panel-overlay" ?data-open=${this.chatOpen}>
|
|
375
517
|
<nile-ai-panel
|
|
376
518
|
.placeholder=${aiConfig?.placeholder ?? 'Ask about this chart...'}
|
|
377
519
|
.welcomeMessage=${welcomeMessage}
|
|
@@ -385,7 +527,7 @@ let NileChart = class NileChart extends NileElement {
|
|
|
385
527
|
const config = this.activeConfig;
|
|
386
528
|
// Suppress inner Highcharts title/subtitle — the card header shows them
|
|
387
529
|
const noTitle = { title: { text: undefined }, subtitle: { text: undefined } };
|
|
388
|
-
const mergedOptions =
|
|
530
|
+
const mergedOptions = deepMerge(deepMerge(config.options ?? {}, noTitle), this.buildExportingOptions());
|
|
389
531
|
switch (config.type) {
|
|
390
532
|
case 'bar':
|
|
391
533
|
return html `<nile-bar-chart
|
|
@@ -542,7 +684,6 @@ let NileChart = class NileChart extends NileElement {
|
|
|
542
684
|
.height=${this.hasAttribute('fit') ? null : (config.height ?? '100%')}
|
|
543
685
|
.innerSize=${config.innerSize ?? '50%'}
|
|
544
686
|
.semiCircle=${config.semiCircle ?? false}
|
|
545
|
-
.semiCircle=${config.semiCircle ?? false}
|
|
546
687
|
.showDataLabels=${config.showDataLabels ?? true}
|
|
547
688
|
.showLegend=${config.showLegend ?? true}
|
|
548
689
|
.options=${mergedOptions}
|
|
@@ -1370,6 +1511,7 @@ let NileChart = class NileChart extends NileElement {
|
|
|
1370
1511
|
></nile-xrange-chart>`;
|
|
1371
1512
|
case 'kpi': {
|
|
1372
1513
|
const k = config;
|
|
1514
|
+
const kpiOptions = deepMerge(k.options ?? {}, this.buildExportingOptions());
|
|
1373
1515
|
return html `<nile-kpi-chart
|
|
1374
1516
|
embed-in-nile-chart
|
|
1375
1517
|
.config=${{
|
|
@@ -1429,8 +1571,11 @@ let NileChart = class NileChart extends NileElement {
|
|
|
1429
1571
|
contentGap: k.contentGap,
|
|
1430
1572
|
tooltipEnabled: k.tooltipEnabled,
|
|
1431
1573
|
loading: k.loading,
|
|
1432
|
-
options:
|
|
1574
|
+
options: kpiOptions,
|
|
1433
1575
|
height: k.height,
|
|
1576
|
+
valueFormat: k.valueFormat,
|
|
1577
|
+
precision: k.precision,
|
|
1578
|
+
unit: k.unit,
|
|
1434
1579
|
},
|
|
1435
1580
|
aq: {
|
|
1436
1581
|
chartSubtitle: k.chartSubtitle,
|
|
@@ -1442,9 +1587,6 @@ let NileChart = class NileChart extends NileElement {
|
|
|
1442
1587
|
const gridChrome = '--nile-data-grid-radius:0;' +
|
|
1443
1588
|
'--nile-data-grid-border-color:transparent;' +
|
|
1444
1589
|
'--nile-data-grid-shadow:none;';
|
|
1445
|
-
const gridStyle = config.height
|
|
1446
|
-
? `${gridChrome}height:${config.height};`
|
|
1447
|
-
: gridChrome;
|
|
1448
1590
|
return html `<nile-data-grid
|
|
1449
1591
|
class="nile-chart-grid"
|
|
1450
1592
|
.data=${config.data}
|
|
@@ -1454,9 +1596,9 @@ let NileChart = class NileChart extends NileElement {
|
|
|
1454
1596
|
.hoverable=${config.hoverable ?? false}
|
|
1455
1597
|
.stickyHeader=${config.stickyHeader ?? false}
|
|
1456
1598
|
.emptyMessage=${config.emptyMessage ?? 'No data'}
|
|
1457
|
-
.loadingMessage=${config.loadingMessage ?? 'Loading
|
|
1599
|
+
.loadingMessage=${config.loadingMessage ?? 'Loading…'}
|
|
1458
1600
|
.noMatchMessage=${config.noMatchMessage ?? 'No matching rows'}
|
|
1459
|
-
style=${
|
|
1601
|
+
style=${gridChrome}
|
|
1460
1602
|
></nile-data-grid>`;
|
|
1461
1603
|
}
|
|
1462
1604
|
default: {
|
|
@@ -1467,28 +1609,32 @@ let NileChart = class NileChart extends NileElement {
|
|
|
1467
1609
|
}
|
|
1468
1610
|
renderSkeleton() {
|
|
1469
1611
|
return html `
|
|
1470
|
-
<div class="chart-skeleton" aria-busy="true" aria-label="Loading chart">
|
|
1471
|
-
<div class="chart-skeleton-body">
|
|
1612
|
+
<div class="nile-chart-skeleton" aria-busy="true" aria-label="Loading chart">
|
|
1613
|
+
<div class="nile-chart-skeleton-body">
|
|
1472
1614
|
${[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>
|
|
1615
|
+
<div class="nile-chart-skeleton-row">
|
|
1616
|
+
<div class="nile-chart-skeleton-ylabel"></div>
|
|
1617
|
+
<div class="nile-chart-skeleton-bar" style="--w: ${w}%"></div>
|
|
1476
1618
|
</div>
|
|
1477
1619
|
`)}
|
|
1478
1620
|
</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>`)}
|
|
1621
|
+
<div class="nile-chart-skeleton-xaxis-row">
|
|
1622
|
+
${[0, 1, 2, 3, 4].map(i => html `<div class="nile-chart-skeleton-xlabel" style="--d: ${i * 80}ms"></div>`)}
|
|
1481
1623
|
</div>
|
|
1482
1624
|
</div>
|
|
1483
1625
|
`;
|
|
1484
1626
|
}
|
|
1485
1627
|
render() {
|
|
1486
1628
|
const isLoading = this.loading || (this.activeConfig?.loading ?? false);
|
|
1629
|
+
const isGrid = this.activeConfig?.type === 'grid';
|
|
1630
|
+
const cardStyle = isGrid && this.activeConfig?.height
|
|
1631
|
+
? `height:${this.activeConfig.height}`
|
|
1632
|
+
: '';
|
|
1487
1633
|
return html `
|
|
1488
|
-
<div class="chart-card">
|
|
1634
|
+
<div class="nile-chart-card ${isGrid ? 'nile-chart-card--grid' : ''}" style=${cardStyle}>
|
|
1489
1635
|
${this.renderHeader()}
|
|
1490
|
-
<div class="chart-wrapper">
|
|
1491
|
-
<div class="chart-inner ${this.activeConfig?.type === 'kpi' ? 'chart-inner--kpi' : ''}">
|
|
1636
|
+
<div class="nile-chart-wrapper">
|
|
1637
|
+
<div class="nile-chart-inner ${this.activeConfig?.type === 'kpi' ? 'nile-chart-inner--kpi' : ''}">
|
|
1492
1638
|
${isLoading
|
|
1493
1639
|
? this.renderSkeleton()
|
|
1494
1640
|
: this.activeConfig
|
|
@@ -1515,6 +1661,9 @@ __decorate([
|
|
|
1515
1661
|
__decorate([
|
|
1516
1662
|
property({ type: String })
|
|
1517
1663
|
], NileChart.prototype, "summary", void 0);
|
|
1664
|
+
__decorate([
|
|
1665
|
+
property({ type: Object })
|
|
1666
|
+
], NileChart.prototype, "menu", void 0);
|
|
1518
1667
|
__decorate([
|
|
1519
1668
|
state()
|
|
1520
1669
|
], NileChart.prototype, "activeType", void 0);
|
|
@@ -1527,6 +1676,9 @@ __decorate([
|
|
|
1527
1676
|
__decorate([
|
|
1528
1677
|
state()
|
|
1529
1678
|
], NileChart.prototype, "chatOpen", void 0);
|
|
1679
|
+
__decorate([
|
|
1680
|
+
state()
|
|
1681
|
+
], NileChart.prototype, "_hcChart", void 0);
|
|
1530
1682
|
__decorate([
|
|
1531
1683
|
state()
|
|
1532
1684
|
], 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;
|