@c8y/ngx-components 1024.1.6 → 1024.2.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/fesm2022/c8y-ngx-components-auth-configuration.mjs +18 -16
- package/fesm2022/c8y-ngx-components-auth-configuration.mjs.map +1 -1
- package/fesm2022/c8y-ngx-components-datapoint-explorer-view.mjs +64 -25
- package/fesm2022/c8y-ngx-components-datapoint-explorer-view.mjs.map +1 -1
- package/fesm2022/c8y-ngx-components-datapoint-selector.mjs +363 -38
- package/fesm2022/c8y-ngx-components-datapoint-selector.mjs.map +1 -1
- package/fesm2022/c8y-ngx-components-echart-models.mjs.map +1 -1
- package/fesm2022/c8y-ngx-components-echart.mjs +796 -123
- package/fesm2022/c8y-ngx-components-echart.mjs.map +1 -1
- package/fesm2022/c8y-ngx-components-global-context.mjs +391 -54
- package/fesm2022/c8y-ngx-components-global-context.mjs.map +1 -1
- package/fesm2022/c8y-ngx-components-time-context.mjs +146 -20
- package/fesm2022/c8y-ngx-components-time-context.mjs.map +1 -1
- package/fesm2022/c8y-ngx-components-widgets-implementations-datapoints-graph.mjs +198 -24
- package/fesm2022/c8y-ngx-components-widgets-implementations-datapoints-graph.mjs.map +1 -1
- package/fesm2022/c8y-ngx-components.mjs +100 -32
- package/fesm2022/c8y-ngx-components.mjs.map +1 -1
- package/locales/de.po +80 -8
- package/locales/es.po +80 -8
- package/locales/fr.po +80 -8
- package/locales/ja_JP.po +80 -8
- package/locales/ko.po +80 -8
- package/locales/locales.pot +79 -7
- package/locales/nl.po +80 -8
- package/locales/pl.po +80 -8
- package/locales/pt_BR.po +80 -8
- package/locales/zh_CN.po +80 -8
- package/locales/zh_TW.po +80 -8
- package/package.json +1 -1
- package/types/c8y-ngx-components-auth-configuration.d.ts +3 -3
- package/types/c8y-ngx-components-auth-configuration.d.ts.map +1 -1
- package/types/c8y-ngx-components-datapoint-explorer-view.d.ts +4 -1
- package/types/c8y-ngx-components-datapoint-explorer-view.d.ts.map +1 -1
- package/types/c8y-ngx-components-datapoint-selector.d.ts +93 -5
- package/types/c8y-ngx-components-datapoint-selector.d.ts.map +1 -1
- package/types/c8y-ngx-components-echart-models.d.ts +25 -3
- package/types/c8y-ngx-components-echart-models.d.ts.map +1 -1
- package/types/c8y-ngx-components-echart.d.ts +65 -5
- package/types/c8y-ngx-components-echart.d.ts.map +1 -1
- package/types/c8y-ngx-components-global-context.d.ts +187 -7
- package/types/c8y-ngx-components-global-context.d.ts.map +1 -1
- package/types/c8y-ngx-components-time-context.d.ts +50 -8
- package/types/c8y-ngx-components-time-context.d.ts.map +1 -1
- package/types/c8y-ngx-components-widgets-implementations-alarms.d.ts +1 -0
- package/types/c8y-ngx-components-widgets-implementations-alarms.d.ts.map +1 -1
- package/types/c8y-ngx-components-widgets-implementations-datapoints-graph.d.ts +25 -0
- package/types/c8y-ngx-components-widgets-implementations-datapoints-graph.d.ts.map +1 -1
- package/types/c8y-ngx-components-widgets-implementations-datapoints-list.d.ts +1 -0
- package/types/c8y-ngx-components-widgets-implementations-datapoints-list.d.ts.map +1 -1
- package/types/c8y-ngx-components-widgets-implementations-scada.d.ts +2 -0
- package/types/c8y-ngx-components-widgets-implementations-scada.d.ts.map +1 -1
- package/types/c8y-ngx-components.d.ts +50 -11
- package/types/c8y-ngx-components.d.ts.map +1 -1
|
@@ -3,22 +3,23 @@ import { Injectable, inject, SecurityContext, ViewChild, Input, Component, Event
|
|
|
3
3
|
import { gettext } from '@c8y/ngx-components/gettext';
|
|
4
4
|
import { from, merge, interval, BehaviorSubject, of, lastValueFrom, defer, forkJoin } from 'rxjs';
|
|
5
5
|
import { mergeMap, map, tap, throttleTime, buffer, switchMap } from 'rxjs/operators';
|
|
6
|
-
import * as i1 from '@c8y/ngx-components';
|
|
6
|
+
import * as i1$1 from '@c8y/ngx-components';
|
|
7
7
|
import { CommonModule, FormsModule, IconDirective, C8yTranslatePipe, WidgetTimeContextMediatorService, WidgetTimeContextDateRangeService, ThemeSwitcherService, DismissAlertStrategy, DynamicComponentAlert, CoreModule, MeasurementRealtimeService, AlarmRealtimeService, EventRealtimeService } from '@c8y/ngx-components';
|
|
8
|
+
import chroma from 'chroma-js';
|
|
8
9
|
import * as echarts from 'echarts';
|
|
9
|
-
import * as i1
|
|
10
|
+
import * as i1 from '@ngx-translate/core';
|
|
11
|
+
import * as i1$3 from '@c8y/client';
|
|
10
12
|
import { AlarmStatus, aggregationType, MeasurementService } from '@c8y/client';
|
|
11
13
|
import * as i6 from '@angular/router';
|
|
12
14
|
import * as i4 from '@c8y/ngx-components/alarms';
|
|
13
15
|
import { AlarmsModule, AlarmSeverityToIconPipe, AlarmSeverityToLabelPipe } from '@c8y/ngx-components/alarms';
|
|
14
16
|
import { INTERVALS } from '@c8y/ngx-components/interval-picker';
|
|
15
|
-
import * as
|
|
16
|
-
import * as i1$1 from '@angular/common';
|
|
17
|
+
import * as i1$2 from '@angular/common';
|
|
17
18
|
import { NgIf, NgForOf, CommonModule as CommonModule$1 } from '@angular/common';
|
|
18
19
|
import * as i2$1 from 'ngx-echarts';
|
|
19
20
|
import { NgxEchartsModule, NGX_ECHARTS_CONFIG } from 'ngx-echarts';
|
|
20
21
|
import { TooltipModule } from 'ngx-bootstrap/tooltip';
|
|
21
|
-
import * as i1$
|
|
22
|
+
import * as i1$4 from 'ngx-bootstrap/popover';
|
|
22
23
|
import { PopoverModule } from 'ngx-bootstrap/popover';
|
|
23
24
|
import { DomSanitizer } from '@angular/platform-browser';
|
|
24
25
|
import * as i2 from 'ngx-bootstrap/dropdown';
|
|
@@ -102,17 +103,18 @@ const ICONS_MAP = {
|
|
|
102
103
|
};
|
|
103
104
|
|
|
104
105
|
class YAxisService {
|
|
105
|
-
constructor(
|
|
106
|
-
this.
|
|
106
|
+
constructor(translate) {
|
|
107
|
+
this.translate = translate;
|
|
107
108
|
this.Y_AXIS_OFFSET = 48;
|
|
108
109
|
this.Y_AXIS_OFFSET_WITH_DP_VALUES = 22;
|
|
109
110
|
this.Y_AXIS_OFFSET_WITHOUT_DP_VALUES = 15;
|
|
110
|
-
this.intlNumberFormatCompliantLocale = this.appStateService.state.lang.replace('_', '-');
|
|
111
111
|
}
|
|
112
112
|
getYAxis(datapointsWithValues, YAxisOptions) {
|
|
113
113
|
const YAxisPlacement = this.getYAxisPlacement(datapointsWithValues, YAxisOptions);
|
|
114
114
|
const hasValues = dp => dp.values && Object.keys(dp.values).length > 0;
|
|
115
115
|
const anyDpHasValues = datapointsWithValues.some(dp => hasValues(dp));
|
|
116
|
+
const selectedLanguage = this.translate.currentLang;
|
|
117
|
+
const locale = selectedLanguage?.replace('_', '-');
|
|
116
118
|
if (YAxisOptions.forceMergeDatapoints) {
|
|
117
119
|
return {
|
|
118
120
|
name: Array.from(datapointsWithValues)
|
|
@@ -134,7 +136,12 @@ class YAxisService {
|
|
|
134
136
|
axisLabel: {
|
|
135
137
|
fontSize: 10,
|
|
136
138
|
show: true,
|
|
137
|
-
color: YAxisOptions.axisColor
|
|
139
|
+
color: YAxisOptions.axisColor,
|
|
140
|
+
formatter: (val) => new Intl.NumberFormat(locale, {
|
|
141
|
+
notation: 'compact',
|
|
142
|
+
compactDisplay: 'short',
|
|
143
|
+
maximumFractionDigits: 3
|
|
144
|
+
}).format(val)
|
|
138
145
|
},
|
|
139
146
|
axisLine: {
|
|
140
147
|
show: true,
|
|
@@ -214,9 +221,14 @@ class YAxisService {
|
|
|
214
221
|
if (this.isDefined(dp.min) && this.isDefined(dp.max)) {
|
|
215
222
|
const range = dp.max - dp.min;
|
|
216
223
|
const decimalPlaces = this.getDecimalPlaces(range);
|
|
217
|
-
return
|
|
224
|
+
return new Intl.NumberFormat(locale, {
|
|
225
|
+
notation: 'compact',
|
|
226
|
+
compactDisplay: 'short',
|
|
227
|
+
minimumFractionDigits: decimalPlaces,
|
|
228
|
+
maximumFractionDigits: decimalPlaces
|
|
229
|
+
}).format(val);
|
|
218
230
|
}
|
|
219
|
-
return new Intl.NumberFormat(
|
|
231
|
+
return new Intl.NumberFormat(locale, {
|
|
220
232
|
notation: 'compact',
|
|
221
233
|
compactDisplay: 'short',
|
|
222
234
|
maximumFractionDigits: 3
|
|
@@ -335,12 +347,12 @@ class YAxisService {
|
|
|
335
347
|
}
|
|
336
348
|
return !dps.some((dp2, index2) => this.isMatchingDp(dp, dp2) && index2 < index);
|
|
337
349
|
}
|
|
338
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: YAxisService, deps: [{ token: i1.
|
|
350
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: YAxisService, deps: [{ token: i1.TranslateService }], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
339
351
|
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: YAxisService }); }
|
|
340
352
|
}
|
|
341
353
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: YAxisService, decorators: [{
|
|
342
354
|
type: Injectable
|
|
343
|
-
}], ctorParameters: () => [{ type: i1.
|
|
355
|
+
}], ctorParameters: () => [{ type: i1.TranslateService }] });
|
|
344
356
|
|
|
345
357
|
function assertUnreachable(_) {
|
|
346
358
|
throw new Error('Case unreachable.');
|
|
@@ -367,6 +379,10 @@ class ChartTypesService {
|
|
|
367
379
|
return this.stepBeforeSeriesOption(color, isMinMaxChart, renderType);
|
|
368
380
|
case 'step-after':
|
|
369
381
|
return this.stepAfterSeriesOption(color, isMinMaxChart, renderType);
|
|
382
|
+
case 'dashed':
|
|
383
|
+
return this.dashedLineSeriesOption(color, isMinMaxChart, renderType);
|
|
384
|
+
case 'dotted':
|
|
385
|
+
return this.dottedLineSeriesOption(color, isMinMaxChart, renderType);
|
|
370
386
|
case 'line':
|
|
371
387
|
case undefined:
|
|
372
388
|
return this.lineSeriesOption(color, isMinMaxChart, renderType);
|
|
@@ -438,6 +454,20 @@ class ChartTypesService {
|
|
|
438
454
|
const baseOption = this.lineSeriesOption(color, isMinMaxChart, renderType);
|
|
439
455
|
return { ...baseOption, step: 'end' };
|
|
440
456
|
}
|
|
457
|
+
dashedLineSeriesOption(color, isMinMaxChart, renderType) {
|
|
458
|
+
const baseOption = this.lineSeriesOption(color, isMinMaxChart, renderType);
|
|
459
|
+
return {
|
|
460
|
+
...baseOption,
|
|
461
|
+
lineStyle: { ...baseOption.lineStyle, type: 'dashed' }
|
|
462
|
+
};
|
|
463
|
+
}
|
|
464
|
+
dottedLineSeriesOption(color, isMinMaxChart, renderType) {
|
|
465
|
+
const baseOption = this.lineSeriesOption(color, isMinMaxChart, renderType);
|
|
466
|
+
return {
|
|
467
|
+
...baseOption,
|
|
468
|
+
lineStyle: { ...baseOption.lineStyle, type: 'dotted' }
|
|
469
|
+
};
|
|
470
|
+
}
|
|
441
471
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: ChartTypesService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
442
472
|
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: ChartTypesService }); }
|
|
443
473
|
}
|
|
@@ -445,8 +475,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImpo
|
|
|
445
475
|
type: Injectable
|
|
446
476
|
}] });
|
|
447
477
|
|
|
448
|
-
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
449
478
|
const INDEX_HTML = '/index.html';
|
|
479
|
+
const DEFAULT_DATAPOINT_COLOR = '#1776bf';
|
|
450
480
|
class EchartsOptionsService {
|
|
451
481
|
constructor(datePipe, yAxisService, chartTypesService, severityIconPipe, severityLabelPipe, translate, router) {
|
|
452
482
|
this.datePipe = datePipe;
|
|
@@ -467,7 +497,11 @@ class EchartsOptionsService {
|
|
|
467
497
|
numberOfDecimalPlaces: CHART_DISPLAY_OPTION_DEFAULTS.numberOfDecimalPlaces,
|
|
468
498
|
...displayOptions
|
|
469
499
|
};
|
|
470
|
-
|
|
500
|
+
// Expand datapoints so each enabled render type config becomes its own virtual dp,
|
|
501
|
+
// but only when aggregation is active — without aggregation the API returns raw
|
|
502
|
+
// min/max data and we must render a single series in the datapoint's primary color.
|
|
503
|
+
const expandedDatapoints = this.expandDatapointsForRenderTypeConfigs(datapointsWithValues, displayOptions.aggregationActive);
|
|
504
|
+
const yAxis = this.yAxisService.getYAxis(expandedDatapoints, {
|
|
471
505
|
showSplitLines: showSplitLines.YAxis,
|
|
472
506
|
mergeMatchingDatapoints: displayOptions.mergeMatchingDatapoints,
|
|
473
507
|
forceMergeDatapoints: displayOptions.forceMergeDatapoints,
|
|
@@ -515,7 +549,7 @@ class EchartsOptionsService {
|
|
|
515
549
|
{
|
|
516
550
|
type: 'inside',
|
|
517
551
|
// TODO: use 'none' only when this bug is fixed https://github.com/apache/echarts/issues/17858
|
|
518
|
-
filterMode:
|
|
552
|
+
filterMode: expandedDatapoints.some(dp => dp.lineType === 'bars') ? 'filter' : 'none',
|
|
519
553
|
zoomOnMouseWheel: true,
|
|
520
554
|
startValue: selectedTimeRange
|
|
521
555
|
? new Date(selectedTimeRange.dateFrom).valueOf()
|
|
@@ -580,6 +614,7 @@ class EchartsOptionsService {
|
|
|
580
614
|
},
|
|
581
615
|
backgroundColor: 'var(--c8y-root-component-background-default)',
|
|
582
616
|
borderColor: 'var(--c8y-root-component-border-color)',
|
|
617
|
+
padding: 0,
|
|
583
618
|
formatter: this.getTooltipFormatter(displayOptions),
|
|
584
619
|
appendToBody: true,
|
|
585
620
|
position: this.tooltipPosition(),
|
|
@@ -606,7 +641,7 @@ class EchartsOptionsService {
|
|
|
606
641
|
lineStyle: {
|
|
607
642
|
color: displayOptions.axisColor
|
|
608
643
|
},
|
|
609
|
-
onZeroAxisIndex:
|
|
644
|
+
onZeroAxisIndex: expandedDatapoints.findIndex(dp => dp.lineType === 'bars') // align X axis to 0 of Y axis of datapoint with lineType 'bars'
|
|
610
645
|
},
|
|
611
646
|
axisLabel: {
|
|
612
647
|
hideOverlap: true,
|
|
@@ -651,7 +686,7 @@ class EchartsOptionsService {
|
|
|
651
686
|
yAxis,
|
|
652
687
|
series: [
|
|
653
688
|
this.getAggregatedSeries(aggregatedDatapoint || null, displayOptions),
|
|
654
|
-
...this.getChartSeries(
|
|
689
|
+
...this.getChartSeries(expandedDatapoints, events, alarms, displayOptions)
|
|
655
690
|
]
|
|
656
691
|
};
|
|
657
692
|
}
|
|
@@ -680,7 +715,9 @@ class EchartsOptionsService {
|
|
|
680
715
|
12;
|
|
681
716
|
break;
|
|
682
717
|
default:
|
|
683
|
-
intervalInMs = INTERVALS.find(
|
|
718
|
+
intervalInMs = INTERVALS.find(
|
|
719
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
720
|
+
i => i.id === interval).timespanInMs;
|
|
684
721
|
break;
|
|
685
722
|
}
|
|
686
723
|
return intervalInMs;
|
|
@@ -690,7 +727,7 @@ class EchartsOptionsService {
|
|
|
690
727
|
return { type: 'line', data: [], id: 'aggregated-empty' };
|
|
691
728
|
}
|
|
692
729
|
const series = [];
|
|
693
|
-
const renderType = aggregatedDatapoint?.renderType || '
|
|
730
|
+
const renderType = aggregatedDatapoint?.renderType || 'avg';
|
|
694
731
|
const idx = 0;
|
|
695
732
|
if (renderType === 'area') {
|
|
696
733
|
series.push(this.getSingleSeries(aggregatedDatapoint, 'min', idx, true, displayOptions, this.AGGREGATED_SERIES_TYPE));
|
|
@@ -838,6 +875,7 @@ class EchartsOptionsService {
|
|
|
838
875
|
const XAxisValue = tooltipParams[0].data[0];
|
|
839
876
|
const YAxisReadings = [];
|
|
840
877
|
const allSeries = this.echartsInstance?.getOption()['series'];
|
|
878
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
841
879
|
const legendSelected = this.echartsInstance?.getOption()['legend']?.[0]?.selected;
|
|
842
880
|
// filter out alarm, event, and hidden series
|
|
843
881
|
const allDataPointSeries = allSeries.filter(series => series['typeOfSeries'] !== 'alarm' &&
|
|
@@ -1059,8 +1097,8 @@ class EchartsOptionsService {
|
|
|
1059
1097
|
YAxisReadings.push(this.buildCompactTooltipRow(color, label, value));
|
|
1060
1098
|
}
|
|
1061
1099
|
else {
|
|
1062
|
-
YAxisReadings.push(`<div class="d-flex a-i-center p-b-
|
|
1063
|
-
`<
|
|
1100
|
+
YAxisReadings.push(`<div class="d-flex a-i-center p-8 p-b-0 text-default"><span class='dlt-c8y-icon-circle m-r-4' style='color: ${color};'></span>` + // color circle
|
|
1101
|
+
`<span class="text-truncate text-medium">${label}</span></div>` + // name
|
|
1064
1102
|
`${value}` // single value or min-max range
|
|
1065
1103
|
);
|
|
1066
1104
|
}
|
|
@@ -1089,7 +1127,7 @@ class EchartsOptionsService {
|
|
|
1089
1127
|
if (displayOptions.showCompactTooltip) {
|
|
1090
1128
|
return `${minStr} — ${maxStr}${unitStr}`;
|
|
1091
1129
|
}
|
|
1092
|
-
return (`<div class="d-flex a-i-center separator-
|
|
1130
|
+
return (`<div class="d-flex a-i-center separator-bottom p-8 p-b-0 text-default"><label class="text-12 m-r-8 m-b-0">${echarts.format.encodeHTML(this.datePipe.transform(minValue[0]))}</label>` +
|
|
1093
1131
|
`<span class="m-l-auto text-12">${minStr} — ${maxStr}${unitStr}</span>` +
|
|
1094
1132
|
`</div>`);
|
|
1095
1133
|
}
|
|
@@ -1111,7 +1149,7 @@ class EchartsOptionsService {
|
|
|
1111
1149
|
if (isCompact) {
|
|
1112
1150
|
return `${valStr}${unitStr}`;
|
|
1113
1151
|
}
|
|
1114
|
-
return (`<div class="d-flex a-i-center p-
|
|
1152
|
+
return (`<div class="d-flex a-i-center separator-bottom p-8 p-b-0 text-default">` +
|
|
1115
1153
|
`<label class="m-b-0 m-r-8 text-12">${echarts.format.encodeHTML(this.datePipe.transform(seriesValue[0]))}</label>` +
|
|
1116
1154
|
`<span class="m-l-auto text-12">${valStr}${unitStr}</span>` +
|
|
1117
1155
|
`</div>`);
|
|
@@ -1122,7 +1160,7 @@ class EchartsOptionsService {
|
|
|
1122
1160
|
* @returns The processed value.
|
|
1123
1161
|
*/
|
|
1124
1162
|
processEvent(event) {
|
|
1125
|
-
let value = `<ul class="list-unstyled small separator-top text-default">`;
|
|
1163
|
+
let value = `<ul class="list-unstyled small separator-top text-default p-l-8 p-r-8">`;
|
|
1126
1164
|
value += `<li class="p-t-4 p-b-4 d-flex separator-bottom text-truncate"><label class="text-label-small m-b-0 m-r-8">${echarts.format.encodeHTML(this.translate.instant(gettext('Event type')))}</label><span class="small m-l-auto"><code title="${echarts.format.encodeHTML(event.type)}">${echarts.format.encodeHTML(event.type)}</code></span></li>`;
|
|
1127
1165
|
value += `<li class="p-t-4 p-b-4 d-flex separator-bottom text-truncate"><label class="text-label-small m-b-0 m-r-8">${echarts.format.encodeHTML(this.translate.instant(gettext('Event text')))}</label><span class="small m-l-auto" style="display: -webkit-box; -webkit-box-orient: vertical; overflow: hidden; text-overflow: ellipsis; word-break: break-word; white-space: normal; max-width: 200px; -webkit-line-clamp: 5;" title="${echarts.format.encodeHTML(event.text)}">${echarts.format.encodeHTML(event.text)}</span></li>`;
|
|
1128
1166
|
value += `<li class="p-t-4 p-b-4 d-flex separator-bottom text-truncate"><label class="text-label-small m-b-0 m-r-8">${echarts.format.encodeHTML(this.translate.instant(gettext('Event time')))}</label><span class="small m-l-auto">${echarts.format.encodeHTML(this.datePipe.transform(event.time))}<span></li>`;
|
|
@@ -1135,7 +1173,7 @@ class EchartsOptionsService {
|
|
|
1135
1173
|
* @returns The processed value.
|
|
1136
1174
|
*/
|
|
1137
1175
|
async processAlarm(alarm) {
|
|
1138
|
-
let value = `<ul class="list-unstyled small separator-top text-default m-0">`;
|
|
1176
|
+
let value = `<ul class="list-unstyled small separator-top text-default m-0 p-l-8 p-r-8">`;
|
|
1139
1177
|
value += `<li class="p-t-4 p-b-4 d-flex a-i-center separator-bottom text-truncate"><label class="text-label-small m-b-0 m-r-8">${echarts.format.encodeHTML(this.translate.instant(gettext('Alarm severity')))}</label>`;
|
|
1140
1178
|
value += `<span class="small d-inline-flex a-i-center gap-4 m-l-auto"><i class="stroked-icon icon-14 status dlt-c8y-icon-${echarts.format.encodeHTML(this.severityIconPipe.transform(alarm.severity))} ${alarm.severity.toLowerCase()}" > </i> ${this.severityLabelPipe.transform(alarm.severity)} </span></li>`;
|
|
1141
1179
|
value += `<li class="p-t-4 p-b-4 d-flex separator-bottom text-truncate"><label class="text-label-small m-b-0 m-r-8">${echarts.format.encodeHTML(this.translate.instant(gettext('Alarm type')))}</label><span class="small m-l-auto"><code title="${echarts.format.encodeHTML(alarm.type)}">${echarts.format.encodeHTML(alarm.type)}</code></span></li>`;
|
|
@@ -1162,17 +1200,53 @@ class EchartsOptionsService {
|
|
|
1162
1200
|
const series = [];
|
|
1163
1201
|
let eventSeries = [];
|
|
1164
1202
|
let alarmSeries = [];
|
|
1203
|
+
// When expandDatapointsForRenderTypeConfigs produces multiple virtual DPs from
|
|
1204
|
+
// the same source (e.g. separate min + max), they each get their own Y-axis entry
|
|
1205
|
+
// but the duplicate is hidden by YAxisService. To keep all series on the *same*
|
|
1206
|
+
// visible axis, map each expanded DP to the first expanded index that shares its
|
|
1207
|
+
// source identity and range.
|
|
1208
|
+
const sourceYAxisMap = new Map();
|
|
1209
|
+
const resolveYAxisIdx = (dp, idx) => {
|
|
1210
|
+
if (displayOptions.forceMergeDatapoints)
|
|
1211
|
+
return idx;
|
|
1212
|
+
const key = `${dp.__target?.id}|${dp.fragment}|${dp.series}|${dp.min ?? ''}|${dp.max ?? ''}`;
|
|
1213
|
+
if (!sourceYAxisMap.has(key)) {
|
|
1214
|
+
sourceYAxisMap.set(key, idx);
|
|
1215
|
+
}
|
|
1216
|
+
return sourceYAxisMap.get(key);
|
|
1217
|
+
};
|
|
1165
1218
|
datapointsWithValues.forEach((dp, idx) => {
|
|
1166
|
-
const
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1219
|
+
const yIdx = resolveYAxisIdx(dp, idx);
|
|
1220
|
+
const renderType = dp.renderType || 'avg';
|
|
1221
|
+
const renderTypes = !dp.renderTypeConfigs && dp.renderTypes?.length > 1 && displayOptions.aggregationActive
|
|
1222
|
+
? dp.renderTypes
|
|
1223
|
+
: null;
|
|
1224
|
+
if (renderTypes) {
|
|
1225
|
+
// Legacy multi-render-type (no renderTypeConfigs): create one series per type
|
|
1226
|
+
for (const rt of renderTypes) {
|
|
1227
|
+
if (rt === 'area') {
|
|
1228
|
+
series.push(this.getSingleSeries(dp, 'min', yIdx, true, displayOptions));
|
|
1229
|
+
series.push(this.getSingleSeries(dp, 'max', yIdx, true, displayOptions));
|
|
1230
|
+
}
|
|
1231
|
+
else {
|
|
1232
|
+
series.push(this.getSingleSeries(dp, rt, yIdx, false, displayOptions, '', rt));
|
|
1233
|
+
}
|
|
1234
|
+
}
|
|
1235
|
+
}
|
|
1236
|
+
else if (renderType === 'area') {
|
|
1237
|
+
if (dp.renderAsBand) {
|
|
1238
|
+
series.push(...this.getBandSeries(dp, yIdx, displayOptions));
|
|
1239
|
+
}
|
|
1240
|
+
else {
|
|
1241
|
+
series.push(this.getSingleSeries(dp, 'min', yIdx, true, displayOptions));
|
|
1242
|
+
series.push(this.getSingleSeries(dp, 'max', yIdx, true, displayOptions));
|
|
1243
|
+
}
|
|
1170
1244
|
}
|
|
1171
1245
|
else {
|
|
1172
|
-
series.push(this.getSingleSeries(dp, renderType,
|
|
1246
|
+
series.push(this.getSingleSeries(dp, renderType, yIdx, false, displayOptions));
|
|
1173
1247
|
}
|
|
1174
|
-
const newEventSeries = this.getAlarmOrEventSeries(dp, renderType, false, events, 'event', displayOptions, null,
|
|
1175
|
-
const newAlarmSeries = this.getAlarmOrEventSeries(dp, renderType, false, alarms, 'alarm', displayOptions, null,
|
|
1248
|
+
const newEventSeries = this.getAlarmOrEventSeries(dp, renderType, false, events, 'event', displayOptions, null, yIdx);
|
|
1249
|
+
const newAlarmSeries = this.getAlarmOrEventSeries(dp, renderType, false, alarms, 'alarm', displayOptions, null, yIdx);
|
|
1176
1250
|
eventSeries = [...eventSeries, ...newEventSeries];
|
|
1177
1251
|
alarmSeries = [...alarmSeries, ...newAlarmSeries];
|
|
1178
1252
|
});
|
|
@@ -1222,6 +1296,7 @@ class EchartsOptionsService {
|
|
|
1222
1296
|
return items.reduce((grouped, item) => {
|
|
1223
1297
|
(grouped[item[typeField]] = grouped[item[typeField]] || []).push(item);
|
|
1224
1298
|
return grouped;
|
|
1299
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1225
1300
|
}, {});
|
|
1226
1301
|
}
|
|
1227
1302
|
calculateGridLeft(leftAxisCount, forceMergeDatapoints) {
|
|
@@ -1242,6 +1317,65 @@ class EchartsOptionsService {
|
|
|
1242
1317
|
}
|
|
1243
1318
|
return (rightAxisCount - 1) * this.yAxisService.Y_AXIS_OFFSET + RIGHT_AXIS_BASE_SPACE;
|
|
1244
1319
|
}
|
|
1320
|
+
/**
|
|
1321
|
+
* Expands datapoints so that each enabled render type config becomes its own
|
|
1322
|
+
* virtual DpWithValues entry with overridden lineType, color, min, max, yAxisType.
|
|
1323
|
+
* Datapoints without renderTypeConfigs are passed through unchanged.
|
|
1324
|
+
* When `aggregationActive` is false the expansion is skipped so non-aggregated
|
|
1325
|
+
* raw data renders as a single series using the primary renderType and datapoint color.
|
|
1326
|
+
*/
|
|
1327
|
+
expandDatapointsForRenderTypeConfigs(dps, aggregationActive = false) {
|
|
1328
|
+
const result = [];
|
|
1329
|
+
for (const dp of dps) {
|
|
1330
|
+
const configs = dp.renderTypeConfigs;
|
|
1331
|
+
if (!configs || !aggregationActive) {
|
|
1332
|
+
result.push(dp);
|
|
1333
|
+
continue;
|
|
1334
|
+
}
|
|
1335
|
+
const enabledKeys = Object.keys(configs).filter(k => configs[k]?.enabled);
|
|
1336
|
+
if (enabledKeys.length === 0) {
|
|
1337
|
+
result.push(dp);
|
|
1338
|
+
continue;
|
|
1339
|
+
}
|
|
1340
|
+
// When renderAsBand is true, replace separate min+max with a single 'area' render type
|
|
1341
|
+
const hasMinAndMax = enabledKeys.includes('min') && enabledKeys.includes('max');
|
|
1342
|
+
const renderAsBand = dp.renderAsBand && hasMinAndMax;
|
|
1343
|
+
for (const rt of enabledKeys) {
|
|
1344
|
+
if (renderAsBand && (rt === 'min' || rt === 'max')) {
|
|
1345
|
+
continue; // skip individual min/max; we'll add area below
|
|
1346
|
+
}
|
|
1347
|
+
const cfg = configs[rt];
|
|
1348
|
+
const expanded = {
|
|
1349
|
+
...dp,
|
|
1350
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1351
|
+
renderType: rt,
|
|
1352
|
+
renderTypes: null,
|
|
1353
|
+
lineType: cfg.lineType || dp.lineType,
|
|
1354
|
+
color: cfg.color || this.getRenderTypeColorFromBase(dp.color, rt) || dp.color,
|
|
1355
|
+
min: cfg.rangeMin ?? dp.min,
|
|
1356
|
+
max: cfg.rangeMax ?? dp.max,
|
|
1357
|
+
yAxisType: cfg.yAxisType || dp.yAxisType
|
|
1358
|
+
};
|
|
1359
|
+
result.push(expanded);
|
|
1360
|
+
}
|
|
1361
|
+
if (renderAsBand) {
|
|
1362
|
+
// Add a single 'area' entry using min config's properties as fallback
|
|
1363
|
+
const minCfg = configs['min'];
|
|
1364
|
+
result.push({
|
|
1365
|
+
...dp,
|
|
1366
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1367
|
+
renderType: 'area',
|
|
1368
|
+
renderTypes: null,
|
|
1369
|
+
lineType: minCfg?.lineType || dp.lineType,
|
|
1370
|
+
color: minCfg?.color || dp.color,
|
|
1371
|
+
min: minCfg?.rangeMin ?? dp.min,
|
|
1372
|
+
max: minCfg?.rangeMax ?? dp.max,
|
|
1373
|
+
yAxisType: minCfg?.yAxisType || dp.yAxisType
|
|
1374
|
+
});
|
|
1375
|
+
}
|
|
1376
|
+
}
|
|
1377
|
+
return result;
|
|
1378
|
+
}
|
|
1245
1379
|
/**
|
|
1246
1380
|
* This method interpolates between two data points. The goal is to place the markPoint on the chart in the right place.
|
|
1247
1381
|
* @param dpValuesArray array of data points
|
|
@@ -1491,9 +1625,12 @@ class EchartsOptionsService {
|
|
|
1491
1625
|
}
|
|
1492
1626
|
}, []);
|
|
1493
1627
|
}
|
|
1494
|
-
getSingleSeries(dp, renderType, idx, isMinMaxChart = false, displayOptions, seriesType = '') {
|
|
1628
|
+
getSingleSeries(dp, renderType, idx, isMinMaxChart = false, displayOptions, seriesType = '', multiRenderTypeLabel) {
|
|
1495
1629
|
const datapointId = dp.__target?.id + dp.fragment + dp.series;
|
|
1496
|
-
const isLine = dp.lineType === 'line' ||
|
|
1630
|
+
const isLine = dp.lineType === 'line' ||
|
|
1631
|
+
dp.lineType === 'linePoints' ||
|
|
1632
|
+
dp.lineType === 'dashed' ||
|
|
1633
|
+
dp.lineType === 'dotted';
|
|
1497
1634
|
const isAggr = seriesType === this.AGGREGATED_SERIES_TYPE;
|
|
1498
1635
|
const data = isAggr && Object.keys(dp.values).length <= 1
|
|
1499
1636
|
? []
|
|
@@ -1501,21 +1638,133 @@ class EchartsOptionsService {
|
|
|
1501
1638
|
dateString,
|
|
1502
1639
|
values[0][renderType] ?? values[0]['min']
|
|
1503
1640
|
]);
|
|
1641
|
+
const effectiveLabel = multiRenderTypeLabel ||
|
|
1642
|
+
(dp.renderTypeConfigs && displayOptions.aggregationActive ? renderType : undefined);
|
|
1643
|
+
const renderTypeSuffix = effectiveLabel ? `~${effectiveLabel}` : '';
|
|
1644
|
+
const colorOverride = multiRenderTypeLabel && dp.renderTypeColors?.[multiRenderTypeLabel]
|
|
1645
|
+
? dp.renderTypeColors[multiRenderTypeLabel]
|
|
1646
|
+
: multiRenderTypeLabel && dp.color
|
|
1647
|
+
? this.getRenderTypeColorFromBase(dp.color, multiRenderTypeLabel)
|
|
1648
|
+
: undefined;
|
|
1649
|
+
const dpForSeries = colorOverride ? { ...dp, color: colorOverride } : dp;
|
|
1650
|
+
const displayType = isMinMaxChart && renderType === 'min' ? 'min — max' : effectiveLabel;
|
|
1504
1651
|
return {
|
|
1505
1652
|
datapointId,
|
|
1506
1653
|
datapointUnit: renderType !== 'count' ? dp.unit : '',
|
|
1507
1654
|
// 'id' property is needed as 'seriesId' in tooltip formatter
|
|
1508
1655
|
id: isMinMaxChart
|
|
1509
1656
|
? `${datapointId}/${renderType}${seriesType}`
|
|
1510
|
-
: `${datapointId}${seriesType}`,
|
|
1511
|
-
name:
|
|
1512
|
-
|
|
1657
|
+
: `${datapointId}${seriesType}${renderTypeSuffix}`,
|
|
1658
|
+
name: displayType
|
|
1659
|
+
? `[${displayType}] ${dp.label} (${dp.__target?.['name']})`
|
|
1660
|
+
: `${dp.label} (${dp.__target?.['name']})`,
|
|
1661
|
+
datapointLabel: displayType ? `[${displayType}] ${dp.label || ''}` : dp.label || '',
|
|
1513
1662
|
data,
|
|
1514
1663
|
...(displayOptions.forceMergeDatapoints ? {} : { yAxisIndex: idx }),
|
|
1515
|
-
...this.chartTypesService.getSeriesOptions(
|
|
1664
|
+
...this.chartTypesService.getSeriesOptions(dpForSeries, isMinMaxChart, renderType),
|
|
1516
1665
|
...(isLine && { smooth: displayOptions.smoothLines ?? false, triggerLineEvent: true })
|
|
1517
1666
|
};
|
|
1518
1667
|
}
|
|
1668
|
+
/**
|
|
1669
|
+
* Creates a band (shaded area) between min and max values using the
|
|
1670
|
+
* stack + stackStrategy:'positive' technique.
|
|
1671
|
+
*
|
|
1672
|
+
* Three series are produced:
|
|
1673
|
+
* 1. max – visible line, stacked
|
|
1674
|
+
* 2. diff (min − max, negative) – invisible line stacked on max with
|
|
1675
|
+
* stackStrategy:'positive' so the area fills downward to min
|
|
1676
|
+
* 3. min – visible line, NOT stacked
|
|
1677
|
+
*/
|
|
1678
|
+
getBandSeries(dp, idx, displayOptions) {
|
|
1679
|
+
const datapointId = dp.__target?.id + dp.fragment + dp.series;
|
|
1680
|
+
const configs = dp.renderTypeConfigs;
|
|
1681
|
+
const minColor = configs?.['min']?.color || dp.color || DEFAULT_DATAPOINT_COLOR;
|
|
1682
|
+
const maxColor = configs?.['max']?.color || dp.color || DEFAULT_DATAPOINT_COLOR;
|
|
1683
|
+
const bandColor = dp.bandColor || dp.color || DEFAULT_DATAPOINT_COLOR;
|
|
1684
|
+
const stackId = `band-${datapointId}`;
|
|
1685
|
+
const maxData = Object.entries(dp.values).map(([dateString, values]) => [
|
|
1686
|
+
dateString,
|
|
1687
|
+
values[0]['max'] ?? null
|
|
1688
|
+
]);
|
|
1689
|
+
const minData = Object.entries(dp.values).map(([dateString, values]) => [
|
|
1690
|
+
dateString,
|
|
1691
|
+
values[0]['min'] ?? null
|
|
1692
|
+
]);
|
|
1693
|
+
const yAxisIndex = displayOptions.forceMergeDatapoints ? {} : { yAxisIndex: idx };
|
|
1694
|
+
// ECharts cumulative line-stack technique for confidence bands.
|
|
1695
|
+
// diff = minVal − maxVal is always negative (min < max).
|
|
1696
|
+
// Stacked cumulative: maxVal + (minVal − maxVal) = minVal.
|
|
1697
|
+
// areaStyle on the diff series fills from minVal (current cumulative) back to
|
|
1698
|
+
// maxVal (previous cumulative) — the correct band — for both positive and negative ranges.
|
|
1699
|
+
// A separate invisible bandMaxSeries is used so getSeriesOptions never touches the stack.
|
|
1700
|
+
const diffData = Object.entries(dp.values).map(([dateString, values]) => {
|
|
1701
|
+
const minVal = values[0]['min'];
|
|
1702
|
+
const maxVal = values[0]['max'];
|
|
1703
|
+
if (minVal == null || maxVal == null)
|
|
1704
|
+
return [dateString, null];
|
|
1705
|
+
return [dateString, minVal - maxVal];
|
|
1706
|
+
});
|
|
1707
|
+
// Invisible stacked upper boundary at maxVal.
|
|
1708
|
+
const bandMaxSeries = {
|
|
1709
|
+
datapointId,
|
|
1710
|
+
datapointUnit: '',
|
|
1711
|
+
id: `${datapointId}/band-max`,
|
|
1712
|
+
name: `[band-max] ${dp.label} (${dp.__target?.['name']})`,
|
|
1713
|
+
datapointLabel: '',
|
|
1714
|
+
typeOfSeries: 'fake',
|
|
1715
|
+
type: 'line',
|
|
1716
|
+
stack: stackId,
|
|
1717
|
+
symbol: 'none',
|
|
1718
|
+
lineStyle: { opacity: 0 },
|
|
1719
|
+
tooltip: { show: false },
|
|
1720
|
+
data: maxData,
|
|
1721
|
+
...yAxisIndex
|
|
1722
|
+
};
|
|
1723
|
+
// Invisible stacked diff series — cumulative lands at minVal, fills the band.
|
|
1724
|
+
// stackStrategy:'all' is required: the default 'samesign' would refuse to stack
|
|
1725
|
+
// a negative diff on a positive sum (or vice versa), leaving it at its raw value.
|
|
1726
|
+
const bandMinSeries = {
|
|
1727
|
+
datapointId,
|
|
1728
|
+
datapointUnit: dp.unit || '',
|
|
1729
|
+
id: `${datapointId}/band-area`,
|
|
1730
|
+
name: `[band] ${dp.label} (${dp.__target?.['name']})`,
|
|
1731
|
+
datapointLabel: `[band] ${dp.label || ''}`,
|
|
1732
|
+
typeOfSeries: 'fake',
|
|
1733
|
+
type: 'line',
|
|
1734
|
+
stack: stackId,
|
|
1735
|
+
stackStrategy: 'all',
|
|
1736
|
+
symbol: 'none',
|
|
1737
|
+
lineStyle: { opacity: 0 },
|
|
1738
|
+
areaStyle: { opacity: 0.2, color: bandColor },
|
|
1739
|
+
tooltip: { show: false },
|
|
1740
|
+
data: diffData,
|
|
1741
|
+
...yAxisIndex
|
|
1742
|
+
};
|
|
1743
|
+
// Visible max line — not stacked, drawn at actual maxVal.
|
|
1744
|
+
const maxSeries = {
|
|
1745
|
+
datapointId,
|
|
1746
|
+
datapointUnit: dp.unit || '',
|
|
1747
|
+
id: `${datapointId}/max`,
|
|
1748
|
+
name: `[max] ${dp.label} (${dp.__target?.['name']})`,
|
|
1749
|
+
datapointLabel: `[max] ${dp.label || ''}`,
|
|
1750
|
+
data: maxData,
|
|
1751
|
+
...this.chartTypesService.getSeriesOptions({ ...dp, color: maxColor }, true, 'max'),
|
|
1752
|
+
...yAxisIndex
|
|
1753
|
+
};
|
|
1754
|
+
// Visible min line — not stacked, drawn at actual minVal. Tooltip shows combined [min — max].
|
|
1755
|
+
const minSeries = {
|
|
1756
|
+
datapointId,
|
|
1757
|
+
datapointUnit: dp.unit || '',
|
|
1758
|
+
id: `${datapointId}/min`,
|
|
1759
|
+
name: `[min — max] ${dp.label} (${dp.__target?.['name']})`,
|
|
1760
|
+
datapointLabel: `[min — max] ${dp.label || ''}`,
|
|
1761
|
+
tooltipColor: bandColor,
|
|
1762
|
+
data: minData,
|
|
1763
|
+
...this.chartTypesService.getSeriesOptions({ ...dp, color: minColor }, true, 'min'),
|
|
1764
|
+
...yAxisIndex
|
|
1765
|
+
};
|
|
1766
|
+
return [bandMaxSeries, bandMinSeries, maxSeries, minSeries];
|
|
1767
|
+
}
|
|
1519
1768
|
/**
|
|
1520
1769
|
* This method creates a general tooltip formatter for the chart.
|
|
1521
1770
|
* @returns TooltipComponentFormatterCallback<TooltipComponentFormatterCallbackParams>
|
|
@@ -1529,11 +1778,36 @@ class EchartsOptionsService {
|
|
|
1529
1778
|
const XAxisValue = data[0];
|
|
1530
1779
|
const YAxisReadings = [];
|
|
1531
1780
|
const allSeries = this.echartsInstance?.getOption()['series'];
|
|
1781
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1532
1782
|
const legendSelected = this.echartsInstance?.getOption()['legend']?.[0]?.selected;
|
|
1533
1783
|
const allDataPointSeries = allSeries.filter(series => series['typeOfSeries'] !== 'alarm' &&
|
|
1534
1784
|
series['typeOfSeries'] !== 'event' &&
|
|
1535
1785
|
series['typeOfSeries'] !== 'fake' &&
|
|
1536
1786
|
legendSelected?.[series['name']] !== false);
|
|
1787
|
+
// Pre-compute which per-function series groups have identical values at this x-coordinate.
|
|
1788
|
+
// Per-function series are identified by having '~' in their id (e.g. datapointId~avg).
|
|
1789
|
+
const perFnGroupSeries = new Map();
|
|
1790
|
+
for (const s of allDataPointSeries) {
|
|
1791
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1792
|
+
const sId = s['id'];
|
|
1793
|
+
if (!sId?.includes('~'))
|
|
1794
|
+
continue;
|
|
1795
|
+
const sDpId = sId.split('~')[0];
|
|
1796
|
+
if (!perFnGroupSeries.has(sDpId))
|
|
1797
|
+
perFnGroupSeries.set(sDpId, []);
|
|
1798
|
+
perFnGroupSeries.get(sDpId).push(s);
|
|
1799
|
+
}
|
|
1800
|
+
const duplicateDpIds = new Set();
|
|
1801
|
+
perFnGroupSeries.forEach((group, sDpId) => {
|
|
1802
|
+
if (group.length < 2)
|
|
1803
|
+
return;
|
|
1804
|
+
const vals = group.map(s => this.findValueForExactOrEarlierTimestamp(s['data'], XAxisValue)?.[1] ??
|
|
1805
|
+
null);
|
|
1806
|
+
const first = vals[0];
|
|
1807
|
+
if (first !== null && vals.every(v => v === first)) {
|
|
1808
|
+
duplicateDpIds.add(sDpId);
|
|
1809
|
+
}
|
|
1810
|
+
});
|
|
1537
1811
|
// Count only series that will actually produce a row at this timestamp
|
|
1538
1812
|
const tooltipRowCount = allDataPointSeries.filter(series => {
|
|
1539
1813
|
const id = series['id'];
|
|
@@ -1546,42 +1820,68 @@ class EchartsOptionsService {
|
|
|
1546
1820
|
allDataPointSeries.forEach((series) => {
|
|
1547
1821
|
const id = series['id'];
|
|
1548
1822
|
const itemStyle = series['itemStyle'];
|
|
1549
|
-
const
|
|
1823
|
+
const seriesColor = series['tooltipColor'] ?? itemStyle?.color;
|
|
1824
|
+
const color = echarts.format.encodeHTML(seriesColor);
|
|
1550
1825
|
const label = echarts.format.encodeHTML(series['datapointLabel']);
|
|
1551
1826
|
const unit = series['datapointUnit']
|
|
1552
1827
|
? ` ${echarts.format.encodeHTML(series['datapointUnit'])}`
|
|
1553
1828
|
: '';
|
|
1829
|
+
const dpId = id?.includes('~') ? id.split('~')[0] : id?.split('/')[0];
|
|
1830
|
+
const isCollapsed = !!(dpId && displayOptions.collapsedDpIds?.has(dpId));
|
|
1831
|
+
const isDuplicate = !!(dpId && duplicateDpIds.has(dpId)) || isCollapsed;
|
|
1832
|
+
const isDimmed = this.highlightedSeriesName !== null && series['name'] !== this.highlightedSeriesName;
|
|
1554
1833
|
if (id.endsWith('/min')) {
|
|
1555
1834
|
const minValue = this.findValueForExactOrEarlierTimestamp(series['data'], XAxisValue);
|
|
1556
1835
|
if (!minValue)
|
|
1557
1836
|
return;
|
|
1558
1837
|
const maxSeries = allDataPointSeries.find(s => s['id'] === id.replace('/min', '/max'));
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1838
|
+
const maxValue = maxSeries
|
|
1839
|
+
? this.findValueForExactOrEarlierTimestamp(maxSeries['data'], XAxisValue)
|
|
1840
|
+
: null;
|
|
1841
|
+
let valueStr;
|
|
1842
|
+
if (maxSeries && maxValue !== null) {
|
|
1843
|
+
// Both min and max visible: show combined range.
|
|
1844
|
+
const minStr = echarts.format.encodeHTML(minValue[1].toFixed(displayOptions.numberOfDecimalPlaces ?? 2) ?? '--');
|
|
1845
|
+
const maxStr = echarts.format.encodeHTML(maxValue[1].toFixed(displayOptions.numberOfDecimalPlaces ?? 2) ?? '--');
|
|
1846
|
+
valueStr = `${minStr} — ${maxStr}`;
|
|
1847
|
+
}
|
|
1848
|
+
else {
|
|
1849
|
+
// Max is hidden: show min value only.
|
|
1850
|
+
const minStr = echarts.format.encodeHTML(minValue[1].toFixed(displayOptions.numberOfDecimalPlaces ?? 2) ?? '--');
|
|
1851
|
+
valueStr = minStr;
|
|
1852
|
+
}
|
|
1567
1853
|
if (isCompact) {
|
|
1568
|
-
YAxisReadings.push(this.buildCompactTooltipRow(color, label, valueStr));
|
|
1854
|
+
YAxisReadings.push(this.buildCompactTooltipRow(color, label, `${valueStr}${unit}`, isDimmed));
|
|
1569
1855
|
}
|
|
1570
1856
|
else {
|
|
1571
1857
|
const date = echarts.format.encodeHTML(this.datePipe.transform(minValue[0]));
|
|
1572
|
-
YAxisReadings.push(this.buildTooltipRow(color, label, date, valueStr));
|
|
1858
|
+
YAxisReadings.push(this.buildTooltipRow(color, label, date, valueStr, isDimmed, unit));
|
|
1573
1859
|
}
|
|
1574
1860
|
}
|
|
1575
1861
|
else if (id.endsWith('/max')) {
|
|
1576
|
-
|
|
1577
|
-
|
|
1862
|
+
const minSeries = allDataPointSeries.find(s => s['id'] === id.replace('/max', '/min'));
|
|
1863
|
+
if (minSeries) {
|
|
1864
|
+
// Min is visible and handles the tooltip for this pair: skip.
|
|
1865
|
+
return;
|
|
1866
|
+
}
|
|
1867
|
+
// Min is hidden: show max value only.
|
|
1868
|
+
const maxValue = this.findValueForExactOrEarlierTimestamp(series['data'], XAxisValue);
|
|
1869
|
+
if (!maxValue)
|
|
1870
|
+
return;
|
|
1871
|
+
const maxStr = echarts.format.encodeHTML(maxValue[1].toFixed(displayOptions.numberOfDecimalPlaces ?? 2) ?? '--');
|
|
1872
|
+
if (isCompact) {
|
|
1873
|
+
YAxisReadings.push(this.buildCompactTooltipRow(color, label, `${maxStr}${unit}`, isDimmed));
|
|
1874
|
+
}
|
|
1875
|
+
else {
|
|
1876
|
+
const date = echarts.format.encodeHTML(this.datePipe.transform(maxValue[0]));
|
|
1877
|
+
YAxisReadings.push(this.buildTooltipRow(color, label, date, maxStr, isDimmed, unit));
|
|
1878
|
+
}
|
|
1578
1879
|
}
|
|
1579
1880
|
else {
|
|
1580
|
-
const isDimmed = this.highlightedSeriesName !== null && series['name'] !== this.highlightedSeriesName;
|
|
1581
1881
|
const seriesValue = this.findValueForExactOrEarlierTimestamp(series['data'], XAxisValue);
|
|
1582
1882
|
if (!seriesValue)
|
|
1583
1883
|
return;
|
|
1584
|
-
const valStr = echarts.format.encodeHTML(seriesValue[1]?.toFixed(displayOptions.numberOfDecimalPlaces) ?? '--');
|
|
1884
|
+
const valStr = echarts.format.encodeHTML(seriesValue[1]?.toFixed(displayOptions.numberOfDecimalPlaces ?? 2) ?? '--');
|
|
1585
1885
|
const valueStr = `${valStr}${unit}`;
|
|
1586
1886
|
if (isCompact) {
|
|
1587
1887
|
YAxisReadings.push(this.buildCompactTooltipRow(color, label, valueStr, isDimmed));
|
|
@@ -1591,23 +1891,52 @@ class EchartsOptionsService {
|
|
|
1591
1891
|
YAxisReadings.push(this.buildTooltipRow(color, label, date, valueStr, isDimmed));
|
|
1592
1892
|
}
|
|
1593
1893
|
}
|
|
1894
|
+
if (isDuplicate) {
|
|
1895
|
+
const group = perFnGroupSeries.get(dpId);
|
|
1896
|
+
// For per-fn groups: show warning after last series in group.
|
|
1897
|
+
// For collapsed single-series: always show warning (no group exists).
|
|
1898
|
+
const isLastInGroup = group ? group[group.length - 1]?.['id'] === id : true;
|
|
1899
|
+
if (isLastInGroup) {
|
|
1900
|
+
const dpLabel = group
|
|
1901
|
+
? // eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1902
|
+
group[0]['datapointLabel']
|
|
1903
|
+
: series['datapointLabel'];
|
|
1904
|
+
const baseName = dpLabel?.replace(/\s*\[.*?\]\s*$/g, '').trim() || dpLabel;
|
|
1905
|
+
const msg = this.translate.instant(gettext('The selected aggregation functions for {{dp}} return the same value because each interval contains a single measurement. Choose a coarser aggregation interval.'), { dp: baseName });
|
|
1906
|
+
YAxisReadings.push(`<div class="separator-bottom p-b-8 text-muted small bg-component text-12 text-break-word" style="white-space: wrap; margin-top:-2px">` +
|
|
1907
|
+
`<div class="icon-flex"><i class="dlt-c8y-icon-warning m-r-4" style="color: var(--c8y-palette-status-warning)"></i>` +
|
|
1908
|
+
`<span>${echarts.format.encodeHTML(msg)}</span>` +
|
|
1909
|
+
`</div></div>`);
|
|
1910
|
+
}
|
|
1911
|
+
}
|
|
1594
1912
|
});
|
|
1595
1913
|
const tooltipWidth = isCompact ? this.COMPACT_TOOLTIP_WIDTH : this.TOOLTIP_WIDTH;
|
|
1596
1914
|
return `<div inner-e-cbg-component style="width: ${tooltipWidth}px">${YAxisReadings.join('')}</div>`;
|
|
1597
1915
|
};
|
|
1598
1916
|
}
|
|
1599
|
-
buildTooltipRow(color, label, date, value, isDimmed) {
|
|
1917
|
+
buildTooltipRow(color, label, date, value, isDimmed, unit) {
|
|
1918
|
+
const header = `<div class="d-flex a-i-center p-8 p-b-0 text-default"><span class='dlt-c8y-icon-circle m-r-4' style='color: ${color}'></span>` +
|
|
1919
|
+
`<strong class="text-truncate">${label}</strong></div>`;
|
|
1920
|
+
if (unit !== undefined) {
|
|
1921
|
+
// min/max area row: unit sits after the text-truncate div, row uses p-8 p-b-0
|
|
1922
|
+
// The extra </div> preserves the output shape from the original code pattern.
|
|
1923
|
+
return (`<div style="opacity: ${isDimmed ? 0.4 : 1}">` +
|
|
1924
|
+
header +
|
|
1925
|
+
`<div class="d-flex a-i-center separator-bottom text-default p-8 p-b-0">` +
|
|
1926
|
+
`<label class="text-12 text-muted m-r-8 m-b-0">${date}</label>` +
|
|
1927
|
+
`<div class="m-l-auto text-12 text-truncate">${value}</div>${unit}` +
|
|
1928
|
+
`</div></div></div>`);
|
|
1929
|
+
}
|
|
1600
1930
|
return (`<div style="opacity: ${isDimmed ? 0.4 : 1}">` +
|
|
1601
|
-
|
|
1602
|
-
`<
|
|
1603
|
-
`<
|
|
1604
|
-
`<label class="text-12 m-r-8 m-b-0">${date}</label>` +
|
|
1931
|
+
header +
|
|
1932
|
+
`<div class="d-flex a-i-center separator-bottom text-default p-b-8 p-l-24 p-r-8">` +
|
|
1933
|
+
`<label class="text-12 text-muted m-r-8 m-b-0">${date}</label>` +
|
|
1605
1934
|
`<div class="m-l-auto text-12 text-truncate">${value}</div>` +
|
|
1606
1935
|
`</div></div>`);
|
|
1607
1936
|
}
|
|
1608
1937
|
buildCompactTooltipRow(color, label, value, isDimmed) {
|
|
1609
1938
|
return (`<div style="opacity: ${isDimmed ? 0.4 : 1}">` +
|
|
1610
|
-
`<div class="d-flex a-i-center p-t-4 p-b-4 separator-bottom text-default">` +
|
|
1939
|
+
`<div class="d-flex a-i-center p-t-4 p-b-4 p-l-8 p-r-8 separator-bottom text-default">` +
|
|
1611
1940
|
`<span class="dlt-c8y-icon-circle m-r-4 flex-no-shrink" style="color:${color}"></span>` +
|
|
1612
1941
|
`<strong class="text-truncate m-r-4" style="max-width:120px">${label}</strong>` +
|
|
1613
1942
|
`<span class="m-l-auto">${value}</span>` +
|
|
@@ -1626,12 +1955,53 @@ class EchartsOptionsService {
|
|
|
1626
1955
|
return acc;
|
|
1627
1956
|
}, null);
|
|
1628
1957
|
}
|
|
1629
|
-
|
|
1958
|
+
/**
|
|
1959
|
+
* Generates a per-function color from the datapoint's base color using luminance-matched shades.
|
|
1960
|
+
* `avg` maps to the base color itself; `min`/`count` shift one/two steps darker;
|
|
1961
|
+
* `max`/`sum` shift one/two steps lighter.
|
|
1962
|
+
*/
|
|
1963
|
+
getRenderTypeColorFromBase(baseColor, renderType) {
|
|
1964
|
+
const SHADE_OFFSETS = {
|
|
1965
|
+
count: -2,
|
|
1966
|
+
min: -1,
|
|
1967
|
+
avg: 0,
|
|
1968
|
+
max: 1,
|
|
1969
|
+
sum: 2
|
|
1970
|
+
};
|
|
1971
|
+
const offset = SHADE_OFFSETS[renderType];
|
|
1972
|
+
if (offset === undefined) {
|
|
1973
|
+
return undefined;
|
|
1974
|
+
}
|
|
1975
|
+
if (offset === 0) {
|
|
1976
|
+
return baseColor;
|
|
1977
|
+
}
|
|
1978
|
+
try {
|
|
1979
|
+
const referenceLuminances = [
|
|
1980
|
+
chroma('#134158').luminance(),
|
|
1981
|
+
chroma('#1C5569').luminance(),
|
|
1982
|
+
chroma('#058192').luminance(), // primary / avg
|
|
1983
|
+
chroma('#22A6AA').luminance(),
|
|
1984
|
+
chroma('#3CC1B7').luminance()
|
|
1985
|
+
];
|
|
1986
|
+
const baseLuminance = chroma(baseColor).luminance();
|
|
1987
|
+
const targetIndex = 2 + offset;
|
|
1988
|
+
if (targetIndex < 0 || targetIndex >= referenceLuminances.length) {
|
|
1989
|
+
return baseColor;
|
|
1990
|
+
}
|
|
1991
|
+
const luminanceDelta = referenceLuminances[targetIndex] - referenceLuminances[2];
|
|
1992
|
+
const targetLuminance = Math.max(0, Math.min(1, baseLuminance + luminanceDelta));
|
|
1993
|
+
return chroma(baseColor).luminance(targetLuminance).hex();
|
|
1994
|
+
}
|
|
1995
|
+
catch {
|
|
1996
|
+
return baseColor;
|
|
1997
|
+
}
|
|
1998
|
+
}
|
|
1999
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: EchartsOptionsService, deps: [{ token: i1$1.DatePipe }, { token: YAxisService }, { token: ChartTypesService }, { token: i4.AlarmSeverityToIconPipe }, { token: i4.AlarmSeverityToLabelPipe }, { token: i1.TranslateService }, { token: i6.Router }], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
1630
2000
|
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: EchartsOptionsService }); }
|
|
1631
2001
|
}
|
|
1632
2002
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: EchartsOptionsService, decorators: [{
|
|
1633
2003
|
type: Injectable
|
|
1634
|
-
}], ctorParameters: () => [{ type: i1.DatePipe }, { type: YAxisService }, { type: ChartTypesService }, { type: i4.AlarmSeverityToIconPipe }, { type: i4.AlarmSeverityToLabelPipe }, { type:
|
|
2004
|
+
}], ctorParameters: () => [{ type: i1$1.DatePipe }, { type: YAxisService }, { type: ChartTypesService }, { type: i4.AlarmSeverityToIconPipe }, { type: i4.AlarmSeverityToLabelPipe }, { type: i1.TranslateService }, { type: i6.Router }] });
|
|
1635
2005
|
|
|
1636
2006
|
class ChartRealtimeService {
|
|
1637
2007
|
constructor(measurementRealtime, alarmRealtimeService, eventRealtimeService, echartsOptionsService) {
|
|
@@ -1757,9 +2127,23 @@ class ChartRealtimeService {
|
|
|
1757
2127
|
.subscribe();
|
|
1758
2128
|
}
|
|
1759
2129
|
stopRealtime() {
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
2130
|
+
// Tearing down a realtime subscription forwards to cometd's unsubscribe, which throws
|
|
2131
|
+
// 'Illegal state: disconnected' if the cometd client has already disconnected (e.g. when
|
|
2132
|
+
// switching config/history mode or saving the widget). Guard each teardown so a disconnected
|
|
2133
|
+
// channel cannot bubble up as an UnsubscriptionError.
|
|
2134
|
+
this.safeUnsubscribe(this.realtimeSubscriptionMeasurements);
|
|
2135
|
+
this.safeUnsubscribe(this.realtimeSubscriptionAlarmsEvents);
|
|
2136
|
+
this.safeUnsubscribe(this.realtimeIntervalSubscription);
|
|
2137
|
+
}
|
|
2138
|
+
safeUnsubscribe(subscription) {
|
|
2139
|
+
try {
|
|
2140
|
+
subscription?.unsubscribe();
|
|
2141
|
+
}
|
|
2142
|
+
catch (error) {
|
|
2143
|
+
// Expected when cometd has already disconnected; warn rather than error so this benign
|
|
2144
|
+
// race does not surface as a failure in error monitoring.
|
|
2145
|
+
console.warn('Failed to unsubscribe from realtime channel', error);
|
|
2146
|
+
}
|
|
1763
2147
|
}
|
|
1764
2148
|
/**
|
|
1765
2149
|
* Updates the chart with the current alarms and events data. Only used when no active datapoints are selected.
|
|
@@ -1964,12 +2348,12 @@ class ChartRealtimeService {
|
|
|
1964
2348
|
datapointBackInSyncCallback(datapoint);
|
|
1965
2349
|
}
|
|
1966
2350
|
}
|
|
1967
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: ChartRealtimeService, deps: [{ token: i1.MeasurementRealtimeService }, { token: i1.AlarmRealtimeService }, { token: i1.EventRealtimeService }, { token: EchartsOptionsService }], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
2351
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: ChartRealtimeService, deps: [{ token: i1$1.MeasurementRealtimeService }, { token: i1$1.AlarmRealtimeService }, { token: i1$1.EventRealtimeService }, { token: EchartsOptionsService }], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
1968
2352
|
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: ChartRealtimeService }); }
|
|
1969
2353
|
}
|
|
1970
2354
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: ChartRealtimeService, decorators: [{
|
|
1971
2355
|
type: Injectable
|
|
1972
|
-
}], ctorParameters: () => [{ type: i1.MeasurementRealtimeService }, { type: i1.AlarmRealtimeService }, { type: i1.EventRealtimeService }, { type: EchartsOptionsService }] });
|
|
2356
|
+
}], ctorParameters: () => [{ type: i1$1.MeasurementRealtimeService }, { type: i1$1.AlarmRealtimeService }, { type: i1$1.EventRealtimeService }, { type: EchartsOptionsService }] });
|
|
1973
2357
|
|
|
1974
2358
|
class ChartAlertsComponent {
|
|
1975
2359
|
constructor() {
|
|
@@ -1982,7 +2366,7 @@ class ChartAlertsComponent {
|
|
|
1982
2366
|
return text;
|
|
1983
2367
|
}
|
|
1984
2368
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: ChartAlertsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
1985
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.17", type: ChartAlertsComponent, isStandalone: true, selector: "c8y-chart-alerts", inputs: { alerts: "alerts" }, viewQueries: [{ propertyName: "dismissPermanently", first: true, predicate: ["dismissPermanently"], descendants: true }], ngImport: i0, template: "@if (alerts?.anyAlertExists$ | async) {\n <div class=\"overlay-center-vertically d-col p-16 p-r-24 p-l-24\">\n @for (alertGroup of alerts?.alertGroups; track alertGroup) {\n @if (alertGroup.value.alerts.length) {\n <div\n class=\"alert\"\n role=\"alert\"\n [ngClass]=\"'alert-' + alertGroup.type\"\n >\n @if (alertGroup.value.alertDismissal.dismissStrategy !== 'none') {\n <button\n class=\"close\"\n type=\"button\"\n (click)=\"\n alerts.dismissAlertGroup(alertGroup.type, dismissPermanently.nativeElement.checked)\n \"\n >\n <span aria-hidden=\"true\">\u00D7</span>\n <span class=\"sr-only\">{{ 'Close' | translate }}</span>\n </button>\n }\n\n @for (alertItem of alertGroup.value.alerts; track alertItem) {\n <p [innerHTML]=\"sanitizeAlertText(alertItem.text)\"></p>\n }\n\n @if (alertGroup.value.alertDismissal.dismissStrategy !== 'none') {\n <label\n class=\"c8y-checkbox m-t-16\"\n title=\"{{ 'Don\\'t show again' | translate }}\"\n >\n <input\n type=\"checkbox\"\n #dismissPermanently\n />\n <span></span>\n <span>{{ \"Don't show again\" | translate }}</span>\n </label>\n }\n </div>\n }\n }\n </div>\n}\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$
|
|
2369
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.17", type: ChartAlertsComponent, isStandalone: true, selector: "c8y-chart-alerts", inputs: { alerts: "alerts" }, viewQueries: [{ propertyName: "dismissPermanently", first: true, predicate: ["dismissPermanently"], descendants: true }], ngImport: i0, template: "@if (alerts?.anyAlertExists$ | async) {\n <div class=\"overlay-center-vertically d-col p-16 p-r-24 p-l-24\">\n @for (alertGroup of alerts?.alertGroups; track alertGroup) {\n @if (alertGroup.value.alerts.length) {\n <div\n class=\"alert\"\n role=\"alert\"\n [ngClass]=\"'alert-' + alertGroup.type\"\n >\n @if (alertGroup.value.alertDismissal.dismissStrategy !== 'none') {\n <button\n class=\"close\"\n type=\"button\"\n (click)=\"\n alerts.dismissAlertGroup(alertGroup.type, dismissPermanently.nativeElement.checked)\n \"\n >\n <span aria-hidden=\"true\">\u00D7</span>\n <span class=\"sr-only\">{{ 'Close' | translate }}</span>\n </button>\n }\n\n @for (alertItem of alertGroup.value.alerts; track alertItem) {\n <p [innerHTML]=\"sanitizeAlertText(alertItem.text)\"></p>\n }\n\n @if (alertGroup.value.alertDismissal.dismissStrategy !== 'none') {\n <label\n class=\"c8y-checkbox m-t-16\"\n title=\"{{ 'Don\\'t show again' | translate }}\"\n >\n <input\n type=\"checkbox\"\n #dismissPermanently\n />\n <span></span>\n <span>{{ \"Don't show again\" | translate }}</span>\n </label>\n }\n </div>\n }\n }\n </div>\n}\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$2.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "pipe", type: i1$1.C8yTranslatePipe, name: "translate" }, { kind: "pipe", type: i1$2.AsyncPipe, name: "async" }] }); }
|
|
1986
2370
|
}
|
|
1987
2371
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: ChartAlertsComponent, decorators: [{
|
|
1988
2372
|
type: Component,
|
|
@@ -2027,12 +2411,12 @@ class ChartEventsService {
|
|
|
2027
2411
|
const result = await Promise.all(promises);
|
|
2028
2412
|
return result.flat();
|
|
2029
2413
|
}
|
|
2030
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: ChartEventsService, deps: [{ token: i1$
|
|
2414
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: ChartEventsService, deps: [{ token: i1$3.EventService }], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
2031
2415
|
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: ChartEventsService }); }
|
|
2032
2416
|
}
|
|
2033
2417
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: ChartEventsService, decorators: [{
|
|
2034
2418
|
type: Injectable
|
|
2035
|
-
}], ctorParameters: () => [{ type: i1$
|
|
2419
|
+
}], ctorParameters: () => [{ type: i1$3.EventService }] });
|
|
2036
2420
|
|
|
2037
2421
|
class ChartAlarmsService {
|
|
2038
2422
|
constructor(alarmService) {
|
|
@@ -2081,12 +2465,12 @@ class ChartAlarmsService {
|
|
|
2081
2465
|
const result = await Promise.all(promises);
|
|
2082
2466
|
return result.flat();
|
|
2083
2467
|
}
|
|
2084
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: ChartAlarmsService, deps: [{ token: i1$
|
|
2468
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: ChartAlarmsService, deps: [{ token: i1$3.AlarmService }], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
2085
2469
|
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: ChartAlarmsService }); }
|
|
2086
2470
|
}
|
|
2087
2471
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: ChartAlarmsService, decorators: [{
|
|
2088
2472
|
type: Injectable
|
|
2089
|
-
}], ctorParameters: () => [{ type: i1$
|
|
2473
|
+
}], ctorParameters: () => [{ type: i1$3.AlarmService }] });
|
|
2090
2474
|
|
|
2091
2475
|
class ChartHelpersService {
|
|
2092
2476
|
/**
|
|
@@ -2113,6 +2497,107 @@ class ChartHelpersService {
|
|
|
2113
2497
|
dp.fragment === selected.fragment &&
|
|
2114
2498
|
dp.series === selected.series);
|
|
2115
2499
|
}
|
|
2500
|
+
/**
|
|
2501
|
+
* Maps a computed auto-aggregation interval string (e.g. `'5m'`, `'3h'`, `'7d'`) to
|
|
2502
|
+
* the nearest standard `aggregationType` for non-time-series tenants.
|
|
2503
|
+
* - seconds / minutes → MINUTELY (finest supported granularity)
|
|
2504
|
+
* - hours → HOURLY
|
|
2505
|
+
* - days → DAILY
|
|
2506
|
+
*/
|
|
2507
|
+
mapIntervalToAggregationType(interval) {
|
|
2508
|
+
if (interval.endsWith('d'))
|
|
2509
|
+
return aggregationType.DAILY;
|
|
2510
|
+
if (interval.endsWith('h'))
|
|
2511
|
+
return aggregationType.HOURLY;
|
|
2512
|
+
if (interval.endsWith('m') || interval.endsWith('s'))
|
|
2513
|
+
return aggregationType.MINUTELY;
|
|
2514
|
+
return null;
|
|
2515
|
+
}
|
|
2516
|
+
/**
|
|
2517
|
+
* Computes the aggregation interval string (e.g. `30s`, `5m`, `24h`, `7d`) that
|
|
2518
|
+
* targets approximately `desiredDatapoints` buckets within the given time range.
|
|
2519
|
+
* The integer part is capped at 999 to satisfy the API constraint.
|
|
2520
|
+
*/
|
|
2521
|
+
calculateAutoAggregationInterval(dateFrom, dateTo, desiredDatapoints) {
|
|
2522
|
+
const rangeMs = new Date(dateTo).valueOf() - new Date(dateFrom).valueOf();
|
|
2523
|
+
const intervalMs = rangeMs / Math.max(1, desiredDatapoints);
|
|
2524
|
+
// If each bucket would be sub-second, the zoom is fine enough to show raw data
|
|
2525
|
+
if (intervalMs <= 1_000)
|
|
2526
|
+
return null;
|
|
2527
|
+
// Use the most natural human-readable unit.
|
|
2528
|
+
// e.g. 24h / 300 dp → 288 s → 5 m (not 288s)
|
|
2529
|
+
const totalSeconds = Math.max(1, Math.round(intervalMs / 1_000));
|
|
2530
|
+
if (totalSeconds < 60)
|
|
2531
|
+
return `${Math.min(999, totalSeconds)}s`;
|
|
2532
|
+
const totalMinutes = Math.round(totalSeconds / 60);
|
|
2533
|
+
if (totalMinutes < 60)
|
|
2534
|
+
return `${Math.min(999, totalMinutes)}m`;
|
|
2535
|
+
const totalHours = Math.round(totalSeconds / 3_600);
|
|
2536
|
+
if (totalHours < 24)
|
|
2537
|
+
return `${Math.min(999, totalHours)}h`;
|
|
2538
|
+
const totalDays = Math.max(1, Math.round(totalSeconds / 86_400));
|
|
2539
|
+
return `${Math.min(999, totalDays)}d`;
|
|
2540
|
+
}
|
|
2541
|
+
/**
|
|
2542
|
+
* Scans raw DpWithValues to determine which datapoints should be collapsed to a single
|
|
2543
|
+
* series — either because all per-function values are identical at every timestamp (the
|
|
2544
|
+
* aggregation interval is too fine — each interval holds a single measurement — to
|
|
2545
|
+
* differentiate functions).
|
|
2546
|
+
*/
|
|
2547
|
+
detectCollapsedDatapoints(dps) {
|
|
2548
|
+
const collapsed = new Set();
|
|
2549
|
+
for (const dp of dps) {
|
|
2550
|
+
const dpId = `${dp.__target?.id}${dp.fragment}${dp.series}`;
|
|
2551
|
+
const valueEntries = Object.values(dp.values ?? {});
|
|
2552
|
+
if (!valueEntries.length)
|
|
2553
|
+
continue;
|
|
2554
|
+
const renderTypeConfigs = dp.renderTypeConfigs;
|
|
2555
|
+
const renderAsBand = !!dp.renderAsBand;
|
|
2556
|
+
let fnsToCompare = [];
|
|
2557
|
+
let isBand = false;
|
|
2558
|
+
if (renderTypeConfigs) {
|
|
2559
|
+
const enabled = Object.keys(renderTypeConfigs).filter(k => renderTypeConfigs[k]?.enabled);
|
|
2560
|
+
const hasMinMax = enabled.includes('min') && enabled.includes('max');
|
|
2561
|
+
if (renderAsBand && hasMinMax) {
|
|
2562
|
+
isBand = true;
|
|
2563
|
+
}
|
|
2564
|
+
else if (enabled.length >= 2) {
|
|
2565
|
+
fnsToCompare = enabled;
|
|
2566
|
+
}
|
|
2567
|
+
else {
|
|
2568
|
+
continue;
|
|
2569
|
+
}
|
|
2570
|
+
}
|
|
2571
|
+
else if (dp.renderTypes && dp.renderTypes.length > 1) {
|
|
2572
|
+
const fns = dp.renderTypes.filter(rt => rt !== 'area');
|
|
2573
|
+
if (fns.length >= 2) {
|
|
2574
|
+
fnsToCompare = fns;
|
|
2575
|
+
}
|
|
2576
|
+
else {
|
|
2577
|
+
continue;
|
|
2578
|
+
}
|
|
2579
|
+
}
|
|
2580
|
+
else {
|
|
2581
|
+
continue;
|
|
2582
|
+
}
|
|
2583
|
+
if (isBand) {
|
|
2584
|
+
const allSame = valueEntries.every(vals => vals[0]?.min === vals[0]?.max);
|
|
2585
|
+
if (allSame)
|
|
2586
|
+
collapsed.add(dpId);
|
|
2587
|
+
}
|
|
2588
|
+
else {
|
|
2589
|
+
const allSame = valueEntries.every(vals => {
|
|
2590
|
+
const first = vals[0]?.[fnsToCompare[0]];
|
|
2591
|
+
if (first === undefined || first === null)
|
|
2592
|
+
return false;
|
|
2593
|
+
return fnsToCompare.slice(1).every(fn => vals[0]?.[fn] === first);
|
|
2594
|
+
});
|
|
2595
|
+
if (allSame)
|
|
2596
|
+
collapsed.add(dpId);
|
|
2597
|
+
}
|
|
2598
|
+
}
|
|
2599
|
+
return collapsed;
|
|
2600
|
+
}
|
|
2116
2601
|
getTargetId(obj) {
|
|
2117
2602
|
return obj?.__target?.id ?? obj?.target;
|
|
2118
2603
|
}
|
|
@@ -2159,7 +2644,7 @@ class SelectAggregatedDatapointComponent {
|
|
|
2159
2644
|
this.selectedDatapointColor = found?.color || '';
|
|
2160
2645
|
}
|
|
2161
2646
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: SelectAggregatedDatapointComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
2162
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.17", type: SelectAggregatedDatapointComponent, isStandalone: true, selector: "c8y-select-aggregated-datapoint", inputs: { activeDatapoints: "activeDatapoints", value: "value" }, outputs: { valueChange: "valueChange" }, host: { classAttribute: "d-contents" }, usesOnChanges: true, ngImport: i0, template: "<div\n class=\"dropdown dropup\"\n dropdown\n #datapointDropdown=\"bs-dropdown\"\n [cdkTrapFocus]=\"datapointDropdown.isOpen\"\n *ngIf=\"activeDatapoints?.length > 0\"\n>\n <ng-template #selectedDatapoint>\n <div class=\"text-left\">\n <strong>{{ selectedDatapointLabel }}</strong>\n <br />\n {{ 'Click to change' | translate }}\n </div>\n </ng-template>\n <button\n class=\"btn btn-default d-flex a-i-center dropdown-toggle c8y-dropdown\"\n title=\"{{'Select a data point to view its aggregated values in the time slider.' | translate }}\"\n type=\"button\"\n dropdownToggle\n >\n <span\n [style.background-color]=\"selectedDatapointColor\"\n style=\"display: inline-block; width: 16px; height: 16px; border-radius: 50%\"\n ></span>\n <span class=\"caret\"></span>\n </button>\n <ul\n class=\"dropdown-menu dropdown-menu-right\"\n *dropdownMenu\n >\n <li *ngFor=\"let datapoint of activeDatapoints\">\n <button\n class=\"p-r-8\"\n type=\"button\"\n (click)=\"changeDatapointSelection(datapoint)\"\n >\n <span\n class=\"d-inline-block m-r-4\"\n [style.background-color]=\"datapoint?.color\"\n style=\"width: 16px; height: 16px; border-radius: 50%\"\n ></span>\n <span class=\"text-truncate\" style=\"max-width:220px!important\" title=\"{{ datapoint?.label || `${datapoint?.fragment}-${datapoint?.series}` }}\">\n {{ datapoint?.label || `${datapoint?.fragment}-${datapoint?.series}` }}\n </span>\n <span\n class=\"m-l-auto flex-no-shrink\"\n *ngIf=\"\n datapoint.__target.id === value?.__target.id &&\n datapoint.fragment === value?.fragment &&\n datapoint.series === value?.series\n \"\n >\n <i\n class=\"text-success h4\"\n [c8yIcon]=\"'check'\"\n ></i>\n </span>\n </button>\n </li>\n </ul>\n</div>\n<button\n class=\"btn-help\"\n [attr.aria-label]=\"'Help' | translate\"\n popover=\"{{\n 'Select a data point to view its aggregated values in the time slider.' | translate\n }}\"\n placement=\"left\"\n triggers=\"focus\"\n container=\"body\"\n></button>\n", dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "ngmodule", type: PopoverModule }, { kind: "directive", type: i1$
|
|
2647
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.17", type: SelectAggregatedDatapointComponent, isStandalone: true, selector: "c8y-select-aggregated-datapoint", inputs: { activeDatapoints: "activeDatapoints", value: "value" }, outputs: { valueChange: "valueChange" }, host: { classAttribute: "d-contents" }, usesOnChanges: true, ngImport: i0, template: "<div\n class=\"dropdown dropup\"\n dropdown\n #datapointDropdown=\"bs-dropdown\"\n [cdkTrapFocus]=\"datapointDropdown.isOpen\"\n *ngIf=\"activeDatapoints?.length > 0\"\n>\n <ng-template #selectedDatapoint>\n <div class=\"text-left\">\n <strong>{{ selectedDatapointLabel }}</strong>\n <br />\n {{ 'Click to change' | translate }}\n </div>\n </ng-template>\n <button\n class=\"btn btn-default d-flex a-i-center dropdown-toggle c8y-dropdown\"\n title=\"{{'Select a data point to view its aggregated values in the time slider.' | translate }}\"\n type=\"button\"\n dropdownToggle\n >\n <span\n [style.background-color]=\"selectedDatapointColor\"\n style=\"display: inline-block; width: 16px; height: 16px; border-radius: 50%\"\n ></span>\n <span class=\"caret\"></span>\n </button>\n <ul\n class=\"dropdown-menu dropdown-menu-right\"\n *dropdownMenu\n >\n <li *ngFor=\"let datapoint of activeDatapoints\">\n <button\n class=\"p-r-8\"\n type=\"button\"\n (click)=\"changeDatapointSelection(datapoint)\"\n >\n <span\n class=\"d-inline-block m-r-4\"\n [style.background-color]=\"datapoint?.color\"\n style=\"width: 16px; height: 16px; border-radius: 50%\"\n ></span>\n <span class=\"text-truncate\" style=\"max-width:220px!important\" title=\"{{ datapoint?.label || `${datapoint?.fragment}-${datapoint?.series}` }}\">\n {{ datapoint?.label || `${datapoint?.fragment}-${datapoint?.series}` }}\n </span>\n <span\n class=\"m-l-auto flex-no-shrink\"\n *ngIf=\"\n datapoint.__target.id === value?.__target.id &&\n datapoint.fragment === value?.fragment &&\n datapoint.series === value?.series\n \"\n >\n <i\n class=\"text-success h4\"\n [c8yIcon]=\"'check'\"\n ></i>\n </span>\n </button>\n </li>\n </ul>\n</div>\n<button\n class=\"btn-help\"\n [attr.aria-label]=\"'Help' | translate\"\n popover=\"{{\n 'Select a data point to view its aggregated values in the time slider.' | translate\n }}\"\n placement=\"left\"\n triggers=\"focus\"\n container=\"body\"\n></button>\n", dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "ngmodule", type: PopoverModule }, { kind: "directive", type: i1$4.PopoverDirective, selector: "[popover]", inputs: ["adaptivePosition", "boundariesElement", "popover", "popoverContext", "popoverTitle", "placement", "outsideClick", "triggers", "container", "containerClass", "isOpen", "delay"], outputs: ["onShown", "onHidden"], exportAs: ["bs-popover"] }, { kind: "ngmodule", type: TooltipModule }, { kind: "ngmodule", type: BsDropdownModule }, { kind: "directive", type: i2.BsDropdownMenuDirective, selector: "[bsDropdownMenu],[dropdownMenu]", exportAs: ["bs-dropdown-menu"] }, { kind: "directive", type: i2.BsDropdownToggleDirective, selector: "[bsDropdownToggle],[dropdownToggle]", exportAs: ["bs-dropdown-toggle"] }, { kind: "directive", type: i2.BsDropdownDirective, selector: "[bsDropdown], [dropdown]", inputs: ["placement", "triggers", "container", "dropup", "autoClose", "isAnimated", "insideClick", "isDisabled", "isOpen"], outputs: ["isOpenChange", "onShown", "onHidden"], exportAs: ["bs-dropdown"] }, { kind: "ngmodule", type: A11yModule }, { kind: "directive", type: i3.CdkTrapFocus, selector: "[cdkTrapFocus]", inputs: ["cdkTrapFocus", "cdkTrapFocusAutoCapture"], exportAs: ["cdkTrapFocus"] }, { kind: "directive", type: IconDirective, selector: "[c8yIcon]", inputs: ["c8yIcon"] }, { kind: "directive", type: NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "pipe", type: C8yTranslatePipe, name: "translate" }] }); }
|
|
2163
2648
|
}
|
|
2164
2649
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: SelectAggregatedDatapointComponent, decorators: [{
|
|
2165
2650
|
type: Component,
|
|
@@ -2184,7 +2669,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImpo
|
|
|
2184
2669
|
type: Output
|
|
2185
2670
|
}] } });
|
|
2186
2671
|
|
|
2187
|
-
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2188
2672
|
class ChartsComponent {
|
|
2189
2673
|
get axisColor() {
|
|
2190
2674
|
return (getComputedStyle(this.chart.nativeElement).getPropertyValue('--text-muted')?.trim() ||
|
|
@@ -2206,6 +2690,13 @@ class ChartsComponent {
|
|
|
2206
2690
|
this.finishLoading = new EventEmitter(false);
|
|
2207
2691
|
this.updateActiveDatapoints = new EventEmitter();
|
|
2208
2692
|
this.updateAggregatedSliderDatapoint = new EventEmitter();
|
|
2693
|
+
/**
|
|
2694
|
+
* Emitted after each data load with the set of datapointIds (target+fragment+series)
|
|
2695
|
+
* whose per-function aggregation series all return identical values — indicating that
|
|
2696
|
+
* the current zoom/aggregation interval is too fine (each interval holds a single
|
|
2697
|
+
* measurement) to differentiate functions.
|
|
2698
|
+
*/
|
|
2699
|
+
this.duplicateAggregationsDetected = new EventEmitter();
|
|
2209
2700
|
this.CHART_VIEW_CONTEXT = CHART_VIEW_CONTEXT;
|
|
2210
2701
|
this.AGGREGATION_INTERVAL_MAP = {
|
|
2211
2702
|
[aggregationType.MINUTELY]: '1m',
|
|
@@ -2213,18 +2704,26 @@ class ChartsComponent {
|
|
|
2213
2704
|
[aggregationType.DAILY]: '1d'
|
|
2214
2705
|
};
|
|
2215
2706
|
this.configChangedSubject = new BehaviorSubject(null);
|
|
2707
|
+
/** Series names hidden by the user via per-function legend toggles; re-applied after each setOption. */
|
|
2708
|
+
this.userHiddenSeriesNames = new Set();
|
|
2709
|
+
/** DpIds whose per-fn series were stripped to a single series on the last render. */
|
|
2710
|
+
this.lastCollapsedDpIds = new Set();
|
|
2216
2711
|
this.destroyRef = inject(DestroyRef);
|
|
2217
2712
|
this.measurementService = inject(MeasurementService);
|
|
2218
2713
|
this.echartsOptionsService = inject(EchartsOptionsService);
|
|
2219
2714
|
this.chartRealtimeService = inject(ChartRealtimeService);
|
|
2220
2715
|
this.chartEventsService = inject(ChartEventsService);
|
|
2221
2716
|
this.chartAlarmsService = inject(ChartAlarmsService);
|
|
2717
|
+
this.chartHelpersService = inject(ChartHelpersService);
|
|
2222
2718
|
this.mediator = inject(WidgetTimeContextMediatorService);
|
|
2223
2719
|
this.widgetTimeContextDateRangeService = inject(WidgetTimeContextDateRangeService);
|
|
2224
2720
|
this.themeSwitcherService = inject(ThemeSwitcherService);
|
|
2225
2721
|
this.loadedTimeRange = null;
|
|
2226
2722
|
/** Tracks if the last data fetch returned truncated data */
|
|
2227
2723
|
this.dataWasTruncated = false;
|
|
2724
|
+
/** Tracks the aggregation interval used in the last fetch so we can detect
|
|
2725
|
+
* whether a zoom-in would produce a finer interval (and thus warrants a refetch). */
|
|
2726
|
+
this.lastAutoAggregationInterval = null;
|
|
2228
2727
|
/** Tracks if current change originated from zoom interaction */
|
|
2229
2728
|
this.isChangeFromZoom = false;
|
|
2230
2729
|
/** Tracks if the slider is being dragged — suppresses config updates mid-drag */
|
|
@@ -2239,7 +2738,27 @@ class ChartsComponent {
|
|
|
2239
2738
|
}
|
|
2240
2739
|
// Use padded range since data is fetched with 60s padding
|
|
2241
2740
|
this.loadedTimeRange = { ...this.getTimeRange(60_000) };
|
|
2242
|
-
|
|
2741
|
+
// Pre-render collapse detection from raw API values.
|
|
2742
|
+
// Emit early so the view legend/dropdown rebuilds BEFORE the chart renders.
|
|
2743
|
+
const collapsed = this.chartHelpersService.detectCollapsedDatapoints(datapointsWithValues);
|
|
2744
|
+
this.duplicateAggregationsDetected.emit(collapsed);
|
|
2745
|
+
this.lastCollapsedDpIds = collapsed;
|
|
2746
|
+
// Strip multi-fn rendering config from collapsed DPs so getChartOptions
|
|
2747
|
+
// produces a single series in dp.color instead of multiple per-fn series.
|
|
2748
|
+
const processedDps = datapointsWithValues.map(dp => {
|
|
2749
|
+
const dpId = `${dp.__target?.id}${dp.fragment}${dp.series}`;
|
|
2750
|
+
if (!collapsed.has(dpId))
|
|
2751
|
+
return dp;
|
|
2752
|
+
const stripped = {
|
|
2753
|
+
...dp,
|
|
2754
|
+
renderTypes: null,
|
|
2755
|
+
renderTypeColors: null,
|
|
2756
|
+
renderTypeConfigs: null,
|
|
2757
|
+
renderAsBand: false
|
|
2758
|
+
};
|
|
2759
|
+
return stripped;
|
|
2760
|
+
});
|
|
2761
|
+
return this.getChartOptions(processedDps);
|
|
2243
2762
|
}), tap(options => {
|
|
2244
2763
|
this.isFetching = false;
|
|
2245
2764
|
this.finishLoading.emit(false);
|
|
@@ -2249,6 +2768,14 @@ class ChartsComponent {
|
|
|
2249
2768
|
this.chartRealtimeService.stopRealtime();
|
|
2250
2769
|
this.startRealtimeIfPossible();
|
|
2251
2770
|
if (this.echartsInstance) {
|
|
2771
|
+
// Apply user-hidden series into legend.selected so setOption initializes with the
|
|
2772
|
+
// correct state. Dispatching legendUnSelect after setOption races with lazyUpdate=true,
|
|
2773
|
+
// because ECharts defers the actual render and overwrites the legend state afterward.
|
|
2774
|
+
if (this.userHiddenSeriesNames.size > 0) {
|
|
2775
|
+
const selectedMap = {};
|
|
2776
|
+
this.userHiddenSeriesNames.forEach(name => (selectedMap[name] = false));
|
|
2777
|
+
options.legend = { ...(options.legend ?? {}), selected: selectedMap };
|
|
2778
|
+
}
|
|
2252
2779
|
// If a series is highlighted, set the opacity into the chart options before
|
|
2253
2780
|
// rendering so the lazy setOption already carries the correct visual state.
|
|
2254
2781
|
// This avoids any async race — the render itself is the source of truth.
|
|
@@ -2290,7 +2817,7 @@ class ChartsComponent {
|
|
|
2290
2817
|
const isWithinLoadedRange = this.loadedTimeRange &&
|
|
2291
2818
|
new Date(currentRange.dateFrom) >= new Date(this.loadedTimeRange.dateFrom) &&
|
|
2292
2819
|
new Date(currentRange.dateTo) <= new Date(this.loadedTimeRange.dateTo);
|
|
2293
|
-
// Sync zoom level across multiple
|
|
2820
|
+
// Sync zoom level across multiple Data graph widgets on the same dashboard
|
|
2294
2821
|
if (this.echartsInstance) {
|
|
2295
2822
|
this.echartsInstance.dispatchAction({
|
|
2296
2823
|
type: 'dataZoom',
|
|
@@ -2298,7 +2825,23 @@ class ChartsComponent {
|
|
|
2298
2825
|
endValue: new Date(currentRange.dateTo).getTime()
|
|
2299
2826
|
}, { silent: true });
|
|
2300
2827
|
}
|
|
2301
|
-
|
|
2828
|
+
// Auto mode with "Keep loading data when zooming in" disabled: a zoom that stays
|
|
2829
|
+
// within the already loaded range must not trigger any request the chart just
|
|
2830
|
+
// zooms into the data it has. When enabled (the default), zooming in refetches at
|
|
2831
|
+
// a finer interval to maintain the desired number of aggregation points. Zooming
|
|
2832
|
+
// out beyond the loaded range or zooming into truncated data always refetches.
|
|
2833
|
+
if (isWithinLoadedRange &&
|
|
2834
|
+
!this.dataWasTruncated &&
|
|
2835
|
+
this.config.autoAggregation &&
|
|
2836
|
+
!(this.config.autoAggregation.keepDataOnZoomIn ?? true)) {
|
|
2837
|
+
return;
|
|
2838
|
+
}
|
|
2839
|
+
let needsAutoAggregationRefetch = false;
|
|
2840
|
+
if (this.config.autoAggregation) {
|
|
2841
|
+
const zoomedInterval = this.chartHelpersService.calculateAutoAggregationInterval(currentRange.dateFrom, currentRange.dateTo, this.config.autoAggregation.datapoints);
|
|
2842
|
+
needsAutoAggregationRefetch = zoomedInterval !== this.lastAutoAggregationInterval;
|
|
2843
|
+
}
|
|
2844
|
+
if (!isWithinLoadedRange || this.dataWasTruncated || needsAutoAggregationRefetch) {
|
|
2302
2845
|
this.configChangedSubject.next();
|
|
2303
2846
|
}
|
|
2304
2847
|
}
|
|
@@ -2474,7 +3017,9 @@ class ChartsComponent {
|
|
|
2474
3017
|
}
|
|
2475
3018
|
loadMoreData() {
|
|
2476
3019
|
const currentRange = this.widgetTimeContextDateRangeService.initialTimeRange();
|
|
2477
|
-
const currentTimeRangeInMs = this.echartsOptionsService.calculateExtendedIntervalInMs(this.config.dateTimeContext?.interval ||
|
|
3020
|
+
const currentTimeRangeInMs = this.echartsOptionsService.calculateExtendedIntervalInMs(this.config.dateTimeContext?.interval ||
|
|
3021
|
+
this.config.interval || // eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
3022
|
+
'custom', {
|
|
2478
3023
|
dateTo: currentRange.dateTo,
|
|
2479
3024
|
dateFrom: currentRange.dateFrom
|
|
2480
3025
|
});
|
|
@@ -2508,13 +3053,15 @@ class ChartsComponent {
|
|
|
2508
3053
|
*/
|
|
2509
3054
|
toggleDatapointSeriesVisibility(datapoint) {
|
|
2510
3055
|
const allSeries = this.echartsInstance.getOption().series;
|
|
2511
|
-
const
|
|
2512
|
-
|
|
3056
|
+
const dpId = `${datapoint.__target?.id}${datapoint.fragment}${datapoint.series}`;
|
|
3057
|
+
// Use filter (not find) so all matching series (e.g. multiple per-fn series) are toggled.
|
|
3058
|
+
const seriesToUpdate = allSeries.filter(s => s.datapointId === dpId);
|
|
3059
|
+
seriesToUpdate.forEach(s => {
|
|
2513
3060
|
this.echartsInstance.dispatchAction({
|
|
2514
3061
|
type: datapoint.__active ? 'legendSelect' : 'legendUnSelect',
|
|
2515
|
-
name:
|
|
3062
|
+
name: s.name
|
|
2516
3063
|
});
|
|
2517
|
-
}
|
|
3064
|
+
});
|
|
2518
3065
|
const deviceId = datapoint.__target?.id;
|
|
2519
3066
|
const isHiding = !datapoint.__active;
|
|
2520
3067
|
const visibleTypes = new Set(this.config.alarmsEventsConfigs
|
|
@@ -2536,6 +3083,22 @@ class ChartsComponent {
|
|
|
2536
3083
|
}
|
|
2537
3084
|
});
|
|
2538
3085
|
}
|
|
3086
|
+
/** Toggles a single chart series by its exact name (used for per-function legend entries). */
|
|
3087
|
+
toggleSeriesByName(seriesName, visible) {
|
|
3088
|
+
if (!this.echartsInstance) {
|
|
3089
|
+
return;
|
|
3090
|
+
}
|
|
3091
|
+
if (visible) {
|
|
3092
|
+
this.userHiddenSeriesNames.delete(seriesName);
|
|
3093
|
+
}
|
|
3094
|
+
else {
|
|
3095
|
+
this.userHiddenSeriesNames.add(seriesName);
|
|
3096
|
+
}
|
|
3097
|
+
this.echartsInstance.dispatchAction({
|
|
3098
|
+
type: visible ? 'legendSelect' : 'legendUnSelect',
|
|
3099
|
+
name: seriesName
|
|
3100
|
+
});
|
|
3101
|
+
}
|
|
2539
3102
|
/**
|
|
2540
3103
|
* Toggles alarm/event series visibility via legendSelect/legendUnSelect.
|
|
2541
3104
|
* Matches all mark-line and mark-point series for the alarm type across every axis,
|
|
@@ -2650,7 +3213,8 @@ class ChartsComponent {
|
|
|
2650
3213
|
showSlider: this.config.showSlider ?? chartDefaults.showSlider,
|
|
2651
3214
|
smoothLines: this.config.smoothLines ?? chartDefaults.smoothLines,
|
|
2652
3215
|
numberOfDecimalPlaces: this.config.numberOfDecimalPlaces ?? chartDefaults.numberOfDecimalPlaces,
|
|
2653
|
-
axisColor: this.axisColor
|
|
3216
|
+
axisColor: this.axisColor,
|
|
3217
|
+
aggregationActive: !!(this.config.aggregation || this.config.autoAggregation)
|
|
2654
3218
|
};
|
|
2655
3219
|
}
|
|
2656
3220
|
getDefaultChartOptions() {
|
|
@@ -2818,6 +3382,8 @@ class ChartsComponent {
|
|
|
2818
3382
|
smoothLines: false,
|
|
2819
3383
|
showSlider: false,
|
|
2820
3384
|
showOnlyAlarmsOrEvents: true,
|
|
3385
|
+
aggregationActive: !!(this.config.aggregation || this.config.autoAggregation),
|
|
3386
|
+
collapsedDpIds: this.lastCollapsedDpIds,
|
|
2821
3387
|
axisColor: this.axisColor
|
|
2822
3388
|
});
|
|
2823
3389
|
}
|
|
@@ -2836,11 +3402,14 @@ class ChartsComponent {
|
|
|
2836
3402
|
dateTo: timeRange.dateTo
|
|
2837
3403
|
}, this.config.aggregatedDatapoint))
|
|
2838
3404
|
: undefined;
|
|
2839
|
-
return this.echartsOptionsService.getChartOptions(datapointsWithValues,
|
|
3405
|
+
return this.echartsOptionsService.getChartOptions(datapointsWithValues,
|
|
3406
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
3407
|
+
timeRange, {
|
|
2840
3408
|
YAxis: this.config.yAxisSplitLines || false,
|
|
2841
3409
|
XAxis: this.config.xAxisSplitLines || false
|
|
2842
3410
|
}, this.events, this.alarms, {
|
|
2843
3411
|
sliderPositionMode,
|
|
3412
|
+
collapsedDpIds: this.lastCollapsedDpIds,
|
|
2844
3413
|
...this.displayOptions
|
|
2845
3414
|
}, undefined, aggregatedDatapoint);
|
|
2846
3415
|
}
|
|
@@ -2875,8 +3444,11 @@ class ChartsComponent {
|
|
|
2875
3444
|
XAxis: this.config.xAxisSplitLines || false
|
|
2876
3445
|
}, this.events, this.alarms, {
|
|
2877
3446
|
sliderPositionMode,
|
|
3447
|
+
collapsedDpIds: this.lastCollapsedDpIds,
|
|
2878
3448
|
...this.displayOptions
|
|
2879
|
-
},
|
|
3449
|
+
},
|
|
3450
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
3451
|
+
timeRange, aggregatedDatapoint, true);
|
|
2880
3452
|
}
|
|
2881
3453
|
}
|
|
2882
3454
|
fetchSeriesForDatapoints$() {
|
|
@@ -2898,47 +3470,122 @@ class ChartsComponent {
|
|
|
2898
3470
|
const datapointsWithValuesRequests = [];
|
|
2899
3471
|
const timeRange = this.getTimeRange();
|
|
2900
3472
|
for (const dp of this.activeDatapoints) {
|
|
2901
|
-
const
|
|
2902
|
-
|
|
2903
|
-
|
|
2904
|
-
|
|
2905
|
-
|
|
2906
|
-
|
|
2907
|
-
|
|
2908
|
-
|
|
2909
|
-
|
|
2910
|
-
|
|
2911
|
-
this.config.
|
|
2912
|
-
|
|
2913
|
-
|
|
2914
|
-
|
|
2915
|
-
|
|
2916
|
-
|
|
2917
|
-
|
|
2918
|
-
|
|
2919
|
-
|
|
2920
|
-
|
|
2921
|
-
|
|
2922
|
-
|
|
3473
|
+
const renderTypes = dp.renderTypes?.length > 1 ? dp.renderTypes : null;
|
|
3474
|
+
if (renderTypes &&
|
|
3475
|
+
(this.config.autoAggregation ||
|
|
3476
|
+
(this.config.showAdvancedChartOptions && this.config.aggregation))) {
|
|
3477
|
+
const nonAreaRenderTypes = renderTypes.filter(rt => rt !== 'area');
|
|
3478
|
+
const singleRequest = defer(() => this.measurementService.listSeries({
|
|
3479
|
+
revert: true,
|
|
3480
|
+
...timeRange,
|
|
3481
|
+
source: dp.__target?.id || '',
|
|
3482
|
+
series: [`${dp.fragment}.${dp.series}`],
|
|
3483
|
+
...(this.config.autoAggregation &&
|
|
3484
|
+
(() => {
|
|
3485
|
+
const interval = this.chartHelpersService.calculateAutoAggregationInterval(timeRange.dateFrom, timeRange.dateTo, this.config.autoAggregation.datapoints);
|
|
3486
|
+
if (!interval)
|
|
3487
|
+
return {};
|
|
3488
|
+
if (this.config.showAdvancedChartOptions) {
|
|
3489
|
+
return { aggregationFunction: nonAreaRenderTypes, aggregationInterval: interval };
|
|
3490
|
+
}
|
|
3491
|
+
const aggType = this.chartHelpersService.mapIntervalToAggregationType(interval);
|
|
3492
|
+
return aggType ? { aggregationType: aggType } : {};
|
|
3493
|
+
})()),
|
|
3494
|
+
...(!this.config.autoAggregation &&
|
|
3495
|
+
this.config.aggregation && {
|
|
3496
|
+
aggregationFunction: nonAreaRenderTypes,
|
|
3497
|
+
aggregationInterval: this.AGGREGATION_INTERVAL_MAP[this.config.aggregation]
|
|
3498
|
+
})
|
|
3499
|
+
})).pipe(map(res => {
|
|
3500
|
+
const values = res.data.values;
|
|
3501
|
+
if (res.data.truncated && this.config.dateFrom) {
|
|
3502
|
+
values[new Date(this.config.dateFrom).toISOString()] = [{ min: null, max: null }];
|
|
3503
|
+
}
|
|
3504
|
+
else if (this.alerts) {
|
|
2923
3505
|
this.alerts.clear();
|
|
2924
3506
|
}
|
|
2925
|
-
|
|
2926
|
-
|
|
2927
|
-
|
|
2928
|
-
|
|
2929
|
-
|
|
2930
|
-
|
|
3507
|
+
return {
|
|
3508
|
+
...dp,
|
|
3509
|
+
renderType: dp.renderType,
|
|
3510
|
+
values,
|
|
3511
|
+
truncated: res.data.truncated
|
|
3512
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
3513
|
+
};
|
|
3514
|
+
}));
|
|
3515
|
+
datapointsWithValuesRequests.push(singleRequest);
|
|
3516
|
+
}
|
|
3517
|
+
else {
|
|
3518
|
+
const request = defer(() => this.measurementService.listSeries({
|
|
3519
|
+
revert: true,
|
|
3520
|
+
...timeRange,
|
|
3521
|
+
source: dp.__target?.id || '',
|
|
3522
|
+
series: [`${dp.fragment}.${dp.series}`],
|
|
3523
|
+
// Auto aggregation: compute interval from time range + desired datapoints.
|
|
3524
|
+
// Falls back to raw data when zoomed in fine enough (returns null).
|
|
3525
|
+
...(this.config.autoAggregation &&
|
|
3526
|
+
(() => {
|
|
3527
|
+
const interval = this.chartHelpersService.calculateAutoAggregationInterval(timeRange.dateFrom, timeRange.dateTo, this.config.autoAggregation.datapoints);
|
|
3528
|
+
if (!interval)
|
|
3529
|
+
return {};
|
|
3530
|
+
if (this.config.showAdvancedChartOptions) {
|
|
3531
|
+
return {
|
|
3532
|
+
aggregationFunction: dp.renderType ?? 'avg',
|
|
3533
|
+
aggregationInterval: interval
|
|
3534
|
+
};
|
|
3535
|
+
}
|
|
3536
|
+
const aggType = this.chartHelpersService.mapIntervalToAggregationType(interval);
|
|
3537
|
+
return aggType ? { aggregationType: aggType } : {};
|
|
3538
|
+
})()),
|
|
3539
|
+
// Standard aggregation (only when auto aggregation is not active)
|
|
3540
|
+
...(!this.config.autoAggregation &&
|
|
3541
|
+
this.config.aggregation &&
|
|
3542
|
+
(!this.config.showAdvancedChartOptions || dp.renderType === 'area') && {
|
|
3543
|
+
aggregationType: this.config.aggregation
|
|
3544
|
+
}),
|
|
3545
|
+
...(!this.config.autoAggregation &&
|
|
3546
|
+
this.config.aggregation &&
|
|
3547
|
+
this.config.showAdvancedChartOptions &&
|
|
3548
|
+
dp.renderType !== 'area' && {
|
|
3549
|
+
aggregationFunction: dp.renderType,
|
|
3550
|
+
aggregationInterval: this.AGGREGATION_INTERVAL_MAP[this.config.aggregation]
|
|
3551
|
+
})
|
|
3552
|
+
})).pipe(map(res => {
|
|
3553
|
+
const values = res.data.values;
|
|
3554
|
+
if (res.data.truncated && this.config.dateFrom) {
|
|
3555
|
+
values[new Date(this.config.dateFrom).toISOString()] = [{ min: null, max: null }];
|
|
2931
3556
|
}
|
|
2932
|
-
|
|
2933
|
-
|
|
2934
|
-
|
|
2935
|
-
|
|
3557
|
+
else {
|
|
3558
|
+
if (this.alerts) {
|
|
3559
|
+
this.alerts.clear();
|
|
3560
|
+
}
|
|
3561
|
+
}
|
|
3562
|
+
// If the datapoint already has a unit (e.g. from config), keep it. Otherwise, try to get it from the series response.
|
|
3563
|
+
if (!dp.unit) {
|
|
3564
|
+
const unitFromSeries = res.data.series[0]?.unit;
|
|
3565
|
+
if (unitFromSeries) {
|
|
3566
|
+
dp.unit = unitFromSeries;
|
|
3567
|
+
}
|
|
3568
|
+
}
|
|
3569
|
+
return {
|
|
3570
|
+
...dp,
|
|
3571
|
+
renderType: dp.renderType,
|
|
3572
|
+
values,
|
|
3573
|
+
truncated: res.data.truncated
|
|
3574
|
+
};
|
|
3575
|
+
}));
|
|
3576
|
+
datapointsWithValuesRequests.push(request);
|
|
3577
|
+
}
|
|
2936
3578
|
}
|
|
2937
3579
|
return forkJoin(datapointsWithValuesRequests).pipe(tap((dpsWithValues) => {
|
|
2938
3580
|
this.dataWasTruncated = dpsWithValues.some(dp => dp['truncated']);
|
|
2939
3581
|
if (this.dataWasTruncated) {
|
|
2940
3582
|
this.addTruncatedDataAlert();
|
|
2941
3583
|
}
|
|
3584
|
+
if (this.config.autoAggregation) {
|
|
3585
|
+
const timeRange = this.getTimeRange(60_000);
|
|
3586
|
+
this.lastAutoAggregationInterval =
|
|
3587
|
+
this.chartHelpersService.calculateAutoAggregationInterval(timeRange.dateFrom, timeRange.dateTo, this.config.autoAggregation.datapoints);
|
|
3588
|
+
}
|
|
2942
3589
|
}));
|
|
2943
3590
|
}
|
|
2944
3591
|
fetchAggregatedSeriesForSelectedDatapoint$(customTimeRange, aggregatedDatapoint) {
|
|
@@ -2952,13 +3599,35 @@ class ChartsComponent {
|
|
|
2952
3599
|
...(customTimeRange ? customTimeRange : timeRange),
|
|
2953
3600
|
source: aggregatedDatapoint.__target?.id || '',
|
|
2954
3601
|
series: [`${aggregatedDatapoint.fragment}.${aggregatedDatapoint.series}`],
|
|
2955
|
-
|
|
3602
|
+
// Auto aggregation: compute interval from time range + desired datapoints.
|
|
3603
|
+
// Falls back to raw data when the zoom level is fine enough that aggregation
|
|
3604
|
+
// would add no value (calculateAutoAggregationInterval returns null).
|
|
3605
|
+
// Non-migrated tenants don't support aggregationFunction/aggregationInterval,
|
|
3606
|
+
// so we map the computed interval to the nearest standard aggregationType.
|
|
3607
|
+
...(this.config.autoAggregation &&
|
|
3608
|
+
(() => {
|
|
3609
|
+
const interval = this.chartHelpersService.calculateAutoAggregationInterval((customTimeRange ?? timeRange).dateFrom, (customTimeRange ?? timeRange).dateTo, this.config.autoAggregation.datapoints);
|
|
3610
|
+
if (!interval)
|
|
3611
|
+
return {};
|
|
3612
|
+
if (this.config.showAdvancedChartOptions) {
|
|
3613
|
+
return {
|
|
3614
|
+
aggregationFunction: aggregatedDatapoint.renderType ?? 'avg',
|
|
3615
|
+
aggregationInterval: interval
|
|
3616
|
+
};
|
|
3617
|
+
}
|
|
3618
|
+
const aggType = this.chartHelpersService.mapIntervalToAggregationType(interval);
|
|
3619
|
+
return aggType ? { aggregationType: aggType } : {};
|
|
3620
|
+
})()),
|
|
3621
|
+
// Standard aggregation (only when auto aggregation is not active)
|
|
3622
|
+
...(!this.config.autoAggregation &&
|
|
3623
|
+
(this.config.aggregation || customTimeRange) &&
|
|
2956
3624
|
(!this.config.showAdvancedChartOptions || aggregatedDatapoint.renderType === 'area') && {
|
|
2957
3625
|
aggregationType: customTimeRange
|
|
2958
3626
|
? this.getAggregationTypeForTimeRange()
|
|
2959
3627
|
: this.config.aggregation
|
|
2960
3628
|
}),
|
|
2961
|
-
...(
|
|
3629
|
+
...(!this.config.autoAggregation &&
|
|
3630
|
+
(this.config.aggregation || customTimeRange) &&
|
|
2962
3631
|
this.config.showAdvancedChartOptions &&
|
|
2963
3632
|
aggregatedDatapoint.renderType !== 'area' && {
|
|
2964
3633
|
aggregationFunction: aggregatedDatapoint.renderType,
|
|
@@ -3042,7 +3711,7 @@ class ChartsComponent {
|
|
|
3042
3711
|
return { dateFrom: timeRange.dateFrom.toISOString(), dateTo: timeRange.dateTo.toISOString() };
|
|
3043
3712
|
}
|
|
3044
3713
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: ChartsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
3045
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.17", type: ChartsComponent, isStandalone: true, selector: "c8y-charts", inputs: { config: "config", alerts: "alerts", chartViewContext: "chartViewContext" }, outputs: { configChangeOnZoomOut: "configChangeOnZoomOut", timeRangeChangeOnRealtime: "timeRangeChangeOnRealtime", datapointOutOfSync: "datapointOutOfSync", datapointBackInSync: "datapointBackInSync", updateAlarmsAndEvents: "updateAlarmsAndEvents", isMarkedAreaEnabled: "isMarkedAreaEnabled", finishLoading: "finishLoading", updateActiveDatapoints: "updateActiveDatapoints", updateAggregatedSliderDatapoint: "updateAggregatedSliderDatapoint" }, providers: [
|
|
3714
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.17", type: ChartsComponent, isStandalone: true, selector: "c8y-charts", inputs: { config: "config", alerts: "alerts", chartViewContext: "chartViewContext" }, outputs: { configChangeOnZoomOut: "configChangeOnZoomOut", timeRangeChangeOnRealtime: "timeRangeChangeOnRealtime", datapointOutOfSync: "datapointOutOfSync", datapointBackInSync: "datapointBackInSync", updateAlarmsAndEvents: "updateAlarmsAndEvents", isMarkedAreaEnabled: "isMarkedAreaEnabled", finishLoading: "finishLoading", updateActiveDatapoints: "updateActiveDatapoints", updateAggregatedSliderDatapoint: "updateAggregatedSliderDatapoint", duplicateAggregationsDetected: "duplicateAggregationsDetected" }, providers: [
|
|
3046
3715
|
{ provide: NGX_ECHARTS_CONFIG, useFactory: () => ({ echarts: () => import('echarts') }) },
|
|
3047
3716
|
ChartRealtimeService,
|
|
3048
3717
|
MeasurementRealtimeService,
|
|
@@ -3052,8 +3721,9 @@ class ChartsComponent {
|
|
|
3052
3721
|
EchartsOptionsService,
|
|
3053
3722
|
YAxisService,
|
|
3054
3723
|
AlarmSeverityToIconPipe,
|
|
3055
|
-
AlarmSeverityToLabelPipe
|
|
3056
|
-
|
|
3724
|
+
AlarmSeverityToLabelPipe,
|
|
3725
|
+
ChartHelpersService
|
|
3726
|
+
], viewQueries: [{ propertyName: "chart", first: true, predicate: ["chart"], descendants: true }], usesOnChanges: true, ngImport: i0, template: "<div\n class=\"p-relative fit-h chart-container\"\n [attr.data-cy]=\"\n config?.realtime ? 'c8y-charts--realtime-active' : 'c8y-charts--realtime-inactive'\n \"\n>\n <div\n class=\"fit-w fit-h\"\n #chart\n echarts\n [options]=\"chartOption$ | async\"\n (chartInit)=\"onChartInit($event)\"\n ></div>\n\n <c8y-chart-alerts [alerts]=\"alerts\"></c8y-chart-alerts>\n\n @if (showLoadMore) {\n <div class=\"chart-load-more\">\n <button\n class=\"btn btn-primary btn-block\"\n [disabled]=\"isFetching\"\n (click)=\"loadMoreData()\"\n >\n <i c8yIcon=\"restore\"></i>\n {{ 'Load more' | translate }}\n </button>\n </div>\n }\n\n @if (\n activeDatapoints.length > 1 &&\n config?.showSlider &&\n chartViewContext !== CHART_VIEW_CONTEXT.WIDGET_VIEW\n ) {\n <div class=\"e-charts--aggregated-datapoint\">\n <c8y-select-aggregated-datapoint\n [value]=\"config?.aggregatedDatapoint || activeDatapoints[0]\"\n (valueChange)=\"updateAggregatedSliderDatapoint.emit($event)\"\n [activeDatapoints]=\"activeDatapoints\"\n ></c8y-select-aggregated-datapoint>\n </div>\n }\n</div>\n", dependencies: [{ kind: "ngmodule", type: CommonModule$1 }, { kind: "ngmodule", type: CoreModule }, { kind: "directive", type: i1$1.IconDirective, selector: "[c8yIcon]", inputs: ["c8yIcon"] }, { kind: "ngmodule", type: NgxEchartsModule }, { kind: "directive", type: i2$1.NgxEchartsDirective, selector: "echarts, [echarts]", inputs: ["options", "theme", "initOpts", "merge", "autoResize", "loading", "loadingType", "loadingOpts"], outputs: ["chartInit", "optionsError", "chartClick", "chartDblClick", "chartMouseDown", "chartMouseMove", "chartMouseUp", "chartMouseOver", "chartMouseOut", "chartGlobalOut", "chartContextMenu", "chartHighlight", "chartDownplay", "chartSelectChanged", "chartLegendSelectChanged", "chartLegendSelected", "chartLegendUnselected", "chartLegendLegendSelectAll", "chartLegendLegendInverseSelect", "chartLegendScroll", "chartDataZoom", "chartDataRangeSelected", "chartGraphRoam", "chartGeoRoam", "chartTreeRoam", "chartTimelineChanged", "chartTimelinePlayChanged", "chartRestore", "chartDataViewChanged", "chartMagicTypeChanged", "chartGeoSelectChanged", "chartGeoSelected", "chartGeoUnselected", "chartAxisAreaSelected", "chartBrush", "chartBrushEnd", "chartBrushSelected", "chartGlobalCursorTaken", "chartRendered", "chartFinished"], exportAs: ["echarts"] }, { kind: "ngmodule", type: TooltipModule }, { kind: "ngmodule", type: PopoverModule }, { kind: "component", type: ChartAlertsComponent, selector: "c8y-chart-alerts", inputs: ["alerts"] }, { kind: "ngmodule", type: AlarmsModule }, { kind: "component", type: SelectAggregatedDatapointComponent, selector: "c8y-select-aggregated-datapoint", inputs: ["activeDatapoints", "value"], outputs: ["valueChange"] }, { kind: "pipe", type: i1$2.AsyncPipe, name: "async" }, { kind: "pipe", type: i1$1.C8yTranslatePipe, name: "translate" }] }); }
|
|
3057
3727
|
}
|
|
3058
3728
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: ChartsComponent, decorators: [{
|
|
3059
3729
|
type: Component,
|
|
@@ -3067,7 +3737,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImpo
|
|
|
3067
3737
|
EchartsOptionsService,
|
|
3068
3738
|
YAxisService,
|
|
3069
3739
|
AlarmSeverityToIconPipe,
|
|
3070
|
-
AlarmSeverityToLabelPipe
|
|
3740
|
+
AlarmSeverityToLabelPipe,
|
|
3741
|
+
ChartHelpersService
|
|
3071
3742
|
], standalone: true, imports: [
|
|
3072
3743
|
CommonModule$1,
|
|
3073
3744
|
CoreModule,
|
|
@@ -3102,6 +3773,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImpo
|
|
|
3102
3773
|
type: Output
|
|
3103
3774
|
}], updateAggregatedSliderDatapoint: [{
|
|
3104
3775
|
type: Output
|
|
3776
|
+
}], duplicateAggregationsDetected: [{
|
|
3777
|
+
type: Output
|
|
3105
3778
|
}], chart: [{
|
|
3106
3779
|
type: ViewChild,
|
|
3107
3780
|
args: ['chart']
|