UNPKG

@c8y/ngx-components

Version:

Angular modules for Cumulocity IoT applications

896 lines (891 loc) 82.8 kB
import * as i0 from '@angular/core'; import { Pipe, Injectable, inject, ViewChild, Input, Component, Optional, EventEmitter, forwardRef, Output } from '@angular/core'; import * as i1 from '@c8y/ngx-components'; import { gettext, ThemeSwitcherService, MeasurementRealtimeService, DatePipe, CommonModule, FormGroupComponent, MessagesComponent, FormsModule } from '@c8y/ngx-components'; import * as i2$1 from '@angular/common'; import { AsyncPipe, NgIf, NgFor, NgStyle, DecimalPipe } from '@angular/common'; import { of, BehaviorSubject, switchMap, tap, Subject, takeUntil } from 'rxjs'; import { map, catchError } from 'rxjs/operators'; import * as i2 from '@c8y/ngx-components/context-dashboard'; import { defaultWidgetIds } from '@c8y/ngx-components/widgets/definitions'; import * as i1$1 from 'ngx-echarts'; import { NgxEchartsModule, NGX_ECHARTS_CONFIG } from 'ngx-echarts'; import * as i1$2 from '@angular/forms'; import { NG_VALUE_ACCESSOR, Validators, NgForm, ControlContainer, ReactiveFormsModule } from '@angular/forms'; import * as i6 from '@c8y/ngx-components/datapoint-selector'; import { DatapointSelectorModule } from '@c8y/ngx-components/datapoint-selector'; import { EditorComponent, MonacoEditorMarkerValidatorDirective } from '@c8y/ngx-components/editor'; import * as i5 from 'ngx-bootstrap/collapse'; import { CollapseModule } from 'ngx-bootstrap/collapse'; const INFO_GAUGE_COLORS = { GREEN: 'var(--c8y-brand-50)', YELLOW: 'var(--c8y-palette-status-warning)', RED: 'var(--c8y-palette-status-danger)' }; class InfoGaugeCurrentMeasurementPipe { constructor(measurementRealtime, alert) { this.measurementRealtime = measurementRealtime; this.alert = alert; } transform(datapoint, calculateGauge) { return this.measurementRealtime .latestValueOfSpecificMeasurement$(datapoint.fragment, datapoint.series, datapoint.__target, 1, true) .pipe(map(m => { // in case measurement is not stored in DB when initially requested. if (!m) { return { value: Number.NaN, date: '', unit: datapoint.unit || '', notFound: true }; } const measurementValue = m[datapoint.fragment][datapoint.series]; const data = { value: measurementValue.value, unit: measurementValue.unit || datapoint.unit, date: m.time }; if (!calculateGauge) { return data; } const gauge = this.calculateGauge(datapoint, measurementValue.value); return { ...data, ...gauge }; }), catchError(e => { this.alert.addServerFailure(e); return of({ value: Number.NaN, date: '', unit: '' }); })); } calculateGauge(datapoint, value) { const val = value; const min = Number(datapoint.min || 0); const max = Number(datapoint.max || 0); const yMin = Number(datapoint.yellowRangeMin); const yMax = Number(datapoint.yellowRangeMax); const rMin = Number(datapoint.redRangeMin); const rMax = Number(datapoint.redRangeMax); // Previously d3 was used for linear scale: d3.scale.linear().domain([min, max]).range([0, 100]); // wanted to avoid importing d3 just for this. const scale = (value1) => (value1 - min) / ((max - min) / 100); const strokeDashOffset = 125.75 + (377.25 - (scale(val) / 100) * 377.25); let color = INFO_GAUGE_COLORS.GREEN; if (Number.isFinite(yMin) && Number.isFinite(yMax)) { if (val >= yMin && val <= yMax) { color = INFO_GAUGE_COLORS.YELLOW; } } if (Number.isFinite(rMin) && Number.isFinite(rMax)) { if (val >= rMin && val <= rMax) { color = INFO_GAUGE_COLORS.RED; } } return { color, strokeDashOffset }; } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: InfoGaugeCurrentMeasurementPipe, deps: [{ token: i1.MeasurementRealtimeService }, { token: i1.AlertService }], target: i0.ɵɵFactoryTarget.Pipe }); } static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "19.2.14", ngImport: i0, type: InfoGaugeCurrentMeasurementPipe, isStandalone: true, name: "infoGaugeCurrentMeasurement" }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: InfoGaugeCurrentMeasurementPipe, decorators: [{ type: Pipe, args: [{ name: 'infoGaugeCurrentMeasurement', pure: true, standalone: true }] }], ctorParameters: () => [{ type: i1.MeasurementRealtimeService }, { type: i1.AlertService }] }); const GAUGE_PRESET_NAMES = { DEFAULT: gettext('Default'), CUSTOM: gettext('Custom preset'), POINTER: gettext('Pointer'), PROGRESS_BAR: gettext('Progress bar'), PROGRESS_INDICATOR: gettext('Progress indicator'), GRADE_RATING: gettext('Grade rating') }; const GAUGE_PRESETS = [ { // General properties name: GAUGE_PRESET_NAMES.DEFAULT, id: 'default', radius: '90%', startAngle: 240, endAngle: 300, // Split properties splitNumber: 10, splitLineLengthRatio: 0.3, splitLineColor: '#fff', splitLineWidth: 4, splitLineDistanceRatio: -0.3, // Tick properties tickShow: true, tickDistanceRatio: -0.16, tickWidth: 2, tickColor: '#fff', tickLengthRatio: 0.16, // Axis properties axisLabelDistanceRatio: 0.25, axisLabelColor: 'black', axisLabelFontSizeMin: 8, axisLabelFontSizeRatio: 0.04, axisLabelFontSizeMax: 32, axisLineWidthRatio: 0.125, // Pointer properties showPointer: true, pointerColor: '#222', pointerStyle: 'triangle', pointerWidthRatio: 2, pointerLength: '55%', pointerOffset: 0, // Progress bar properties progressBar: false, progressBarColor: '#119d11', // Detail properties showDetail: false, showMarkPoint: true, // Typography & Anchor measurementValueFontRatio: 0.08, measurementValueFontMin: 16, measurementValueFontMax: 56, measurementValueColor: 'white', unitFontSize: 18, unitFontRatio: 0.045, unitFontMin: 14, unitFontMax: 40, unitColor: 'white', dateFontSize: 0.001, dateFontRatio: 0.03, dateFontMin: 10, dateFontMax: 16, dateColor: 'gray', anchor: { show: true, sizeRatio: 0.5, itemStyle: { color: '#222' // Color of the anchor } } }, { // General properties name: GAUGE_PRESET_NAMES.POINTER, id: 'pointer', radius: '90%', startAngle: 240, endAngle: 300, // Split properties splitNumber: 10, splitLineLengthRatio: 1, splitLineColor: '#fff', splitLineWidth: 4, splitLineDistanceRatio: -1, // Tick properties tickShow: true, tickDistanceRatio: -1, tickWidth: 2, tickColor: '#fff', tickLengthRatio: 0.2666666667, // Axis properties axisLabelColor: '#212121', axisLabelFontSizeMin: 8, axisLabelFontSizeRatio: 0.04, axisLabelFontSizeMax: 32, axisLineWidthRatio: 0.075, axisLabelDistanceRatio: 1.1666666667, // Pointer properties showPointer: true, pointerStyle: 'default', pointerColor: 'black', pointerWidthRatio: 0.2666666667, pointerLength: '70%', pointerOffset: 5, // Progress bar properties progressBar: false, progressBarColor: '#119d11', // Detail properties showDetail: true, detailOffsetCenter: ['0%', '35%'], showMarkPoint: false, // Typography specs measurementValueFontRatio: 0.08, measurementValueFontMin: 16, measurementValueFontMax: 56, measurementValueColor: 'black', unitFontSize: 18, unitFontRatio: 0.045, unitFontMin: 8, unitFontMax: 40, unitColor: 'black', dateFontRatio: 0.03, dateFontMin: 10, dateFontMax: 16, dateColor: 'black' }, { // General properties name: GAUGE_PRESET_NAMES.PROGRESS_BAR, id: 'progress-bar', radius: '85%', center: ['50%', '57%'], startAngle: 200, endAngle: -20, // Split properties splitNumber: 10, splitLineLengthRatio: 1, splitLineColor: '#fff', splitLineWidth: 3, splitLineDistanceRatio: -1, // Tick properties tickShow: false, // Axis properties axisLabelColor: '#212121', axisLabelFontSizeMin: 8, axisLabelFontSizeRatio: 0.04, axisLabelFontSizeMax: 32, axisLineWidthRatio: 0.075, axisLabelDistanceRatio: -0.8, // Pointer properties showPointer: false, // Progress bar properties progressBar: true, progressBarWidthRatio: 0.3333, progressBarRoundCap: false, progressBarColor: '#119d11', additionalGaugeColors: ['#FFAB91', '#FD7347'], // Detail properties showDetail: true, detailOffsetCenter: ['0%', '0%'], showMarkPoint: false, // Typography specs measurementValueFontRatio: 0.12, measurementValueFontMin: 16, measurementValueFontMax: 72, measurementValueColor: 'black', unitFontSize: 18, unitFontRatio: 0.085, unitFontMin: 10, unitFontMax: 40, unitColor: 'black', dateFontRatio: 0.03, dateFontMin: 10, dateFontMax: 16, dateColor: 'black' }, { // General properties name: GAUGE_PRESET_NAMES.PROGRESS_INDICATOR, id: 'progress-indicator', radius: '85%', startAngle: 240, endAngle: 300, // Split line properties splitNumber: 4, splitLineLengthRatio: 1, splitLineColor: '#fff', splitLineWidth: 2, splitLineDistanceRatio: -1, // Tick properties tickShow: false, // Axis properties axisLabelColor: '#212121', axisLabelFontSizeMin: 7, axisLabelFontSizeRatio: 0.04, axisLabelFontSizeMax: 32, axisLineWidthRatio: 0.075, axisLabelDistanceRatio: -1, // Pointer properties showPointer: true, pointerStyle: 'default', pointerColor: 'black', pointerWidthRatio: 0.2, pointerLength: '100%', pointerOffset: 5, // Progress bar properties progressBar: true, progressBarWidthRatio: 0.3333, progressBarRoundCap: false, progressBarColor: '#119d11', // Detail properties showDetail: true, detailOffsetCenter: ['0%', '35%'], showMarkPoint: false, // Typography & Anchor measurementValueFontRatio: 0.08, measurementValueFontMin: 12, measurementValueFontMax: 56, measurementValueColor: 'black', unitFontSize: 18, unitFontRatio: 0.045, unitFontMin: 10, unitFontMax: 40, unitColor: 'black', dateFontRatio: 0.03, dateFontMin: 10, dateFontMax: 16, dateColor: 'black', anchor: { show: true, showAbove: true, sizeRatio: 0.1, itemStyle: { borderWidth: 10 } } }, { // General properties name: GAUGE_PRESET_NAMES.GRADE_RATING, id: 'grade-rating', radius: '75%', startAngle: 180, endAngle: 360, center: ['50%', '65%'], // Split line properties splitNumber: 4, splitLineLengthRatio: 3, splitLineColor: 'auto', splitLineWidth: 6, splitLineDistanceRatio: -3, // Tick properties tickShow: true, tickDistanceRatio: -2, tickWidth: 2, tickColor: 'auto', tickLengthRatio: 2, // Axis properties axisLabelDistanceRatio: -6, axisLabelColor: 'auto', axisLabelFontSizeMin: 6, axisLabelFontSizeRatio: 0.04, axisLabelFontSizeMax: 32, axisLineWidthRatio: 0.02, // Pointer properties showPointer: true, pointerColor: 'black', pointerStyle: 'triangle', pointerWidthRatio: 2, pointerLength: '12%', pointerOffset: '-50%', // Progress bar properties progressBar: false, progressBarColor: '#119d11', // Detail properties showDetail: true, detailOffsetCenter: ['0%', '0%'], showMarkPoint: false, // Typography measurementValueFontRatio: 0.08, measurementValueFontMin: 16, measurementValueFontMax: 56, measurementValueColor: 'black', unitFontRatio: 0.045, unitFontMin: 8, unitFontMax: 40, unitColor: 'black', dateFontRatio: 0.03, dateFontMin: 10, dateFontMax: 16, dateColor: 'black' } ]; class RadialGaugeService { constructor(c8yDatePipe) { this.c8yDatePipe = c8yDatePipe; this.containerSize = 400; } setChart(chart) { this.chart = chart; } getChartOptions(gaugeOptions, activeDatapointGauge, selectedPresetId, gaugeOptionsColors, rangeColors, measurement, fractionSize) { const colorArray = this.getColorArray(activeDatapointGauge, rangeColors); return { series: [ { type: 'gauge', radius: gaugeOptions?.radius || '90%', center: gaugeOptions?.center || ['50%', '50%'], startAngle: gaugeOptions?.startAngle || 240, endAngle: gaugeOptions?.endAngle || 300, min: activeDatapointGauge?.min || 0, max: activeDatapointGauge?.max !== undefined && activeDatapointGauge?.max !== null ? activeDatapointGauge.max : 100, // Split properties splitNumber: gaugeOptions?.splitNumber || 10, splitLine: { distance: gaugeOptions?.splitLineDistanceRatio ? this.containerSize * gaugeOptions?.axisLineWidthRatio * gaugeOptions?.splitLineDistanceRatio : gaugeOptions?.splitLineDistance || -30, length: gaugeOptions?.splitLineLengthRatio ? this.containerSize * gaugeOptions?.axisLineWidthRatio * gaugeOptions?.splitLineLengthRatio : gaugeOptions?.splitLineLength || 30, lineStyle: { color: selectedPresetId === 'custom' || selectedPresetId === 'grade-rating' ? gaugeOptions?.splitLineColor : gaugeOptionsColors?.splitLineColor || '#fff', width: gaugeOptions?.splitLineWidth || 4 } }, // Tick properties axisTick: { show: gaugeOptions?.tickShow || false, distance: gaugeOptions?.tickDistanceRatio ? this.containerSize * gaugeOptions?.axisLineWidthRatio * gaugeOptions?.tickDistanceRatio : gaugeOptions?.tickDistance || -30, length: gaugeOptions?.tickLengthRatio ? this.containerSize * gaugeOptions?.axisLineWidthRatio * gaugeOptions?.tickLengthRatio : gaugeOptions?.tickLength || 8, lineStyle: { color: selectedPresetId === 'custom' || selectedPresetId === 'grade-rating' ? gaugeOptions.tickColor : gaugeOptionsColors?.tickColor || '#fff', width: gaugeOptions?.tickWidth || 2 } }, // Axis properties axisLine: { lineStyle: { width: gaugeOptions?.axisLineWidthRatio ? this.containerSize * gaugeOptions?.axisLineWidthRatio : gaugeOptions?.axisLineWidth || 30, color: colorArray } }, axisLabel: { color: selectedPresetId === 'custom' || selectedPresetId === 'grade-rating' ? gaugeOptions.axisLabelColor : gaugeOptionsColors?.axisLabelColor || 'inherit', distance: gaugeOptions?.axisLabelDistanceRatio ? this.containerSize * gaugeOptions?.axisLineWidthRatio * gaugeOptions?.axisLabelDistanceRatio : gaugeOptions?.axisLabelDistance || 40, fontSize: gaugeOptions?.axisLabelFontSizeRatio ? this.clampRelative(gaugeOptions?.axisLabelFontSizeRatio, gaugeOptions?.axisLabelFontSizeMin, gaugeOptions?.axisLabelFontSizeMax) : gaugeOptions?.axisLabelFontSize || 16, fontWeight: 'bold', fontFamily: gaugeOptionsColors?.fontFamily }, // pointer properties pointer: { ...(gaugeOptions?.pointerStyle !== 'default' && { icon: gaugeOptions?.pointerStyle }), show: gaugeOptions?.showPointer || false, length: gaugeOptions?.pointerLengthRatio ? gaugeOptions?.pointerLengthRatio * (this.containerSize * gaugeOptions?.axisLineWidthRatio) : gaugeOptions?.pointerLength || '65%', width: gaugeOptions?.pointerWidthRatio ? this.containerSize * gaugeOptions?.axisLineWidthRatio * gaugeOptions?.pointerWidthRatio : gaugeOptions?.pointerWidth || 40, offsetCenter: selectedPresetId === 'default' ? [0, this.containerSize * gaugeOptions?.axisLineWidthRatio * -1] : [0, gaugeOptions?.pointerOffset || -30], itemStyle: { color: selectedPresetId === 'custom' ? gaugeOptions.pointerColor : gaugeOptionsColors?.pointerColor || 'inherit' } }, // Progress bar properties progress: { show: gaugeOptions?.progressBar || false, roundCap: gaugeOptions?.progressBarRoundCap || false, width: gaugeOptions?.progressBarWidthRatio ? this.containerSize * gaugeOptions?.axisLineWidthRatio * gaugeOptions?.progressBarWidthRatio : gaugeOptions?.progressBarWidth || 18, itemStyle: { color: selectedPresetId === 'custom' ? gaugeOptions.progressBarColor : gaugeOptionsColors?.progressBarColor || 'inherit' } }, // Anchor properties anchor: gaugeOptions?.anchor ? { ...gaugeOptions.anchor, size: gaugeOptions?.anchor?.sizeRatio ? this.containerSize * gaugeOptions?.anchor?.sizeRatio : gaugeOptions?.anchor?.size || 45, itemStyle: { color: selectedPresetId === 'custom' ? gaugeOptions.pointerColor : gaugeOptionsColors?.pointerColor } } : undefined, // Detail properties detail: { show: gaugeOptions?.showDetail || false, valueAnimation: true, offsetCenter: gaugeOptions?.detailOffsetCenterYRatio ? [0, gaugeOptions?.detailOffsetCenterYRatio * this.containerSize] : gaugeOptions?.detailOffsetCenter || [0, 0], formatter: () => { const value = measurement?.value.toFixed(fractionSize) || 0; const unit = activeDatapointGauge?.unit || measurement?.unit || ''; const date = new Date(measurement?.date); const formattedDate = this.c8yDatePipe.transform(date); return `{value|${value}}{unit|${unit}} \n {date|${formattedDate}} `; }, color: 'inherit', lineHeight: gaugeOptions?.measurementValueFontRatio ? this.clampRelative(gaugeOptions?.measurementValueFontRatio * 0.9, gaugeOptions?.measurementValueFontSizeMin, gaugeOptions?.measurementValueFontSizeMax) : gaugeOptions?.measurementValueFontSize || 32, rich: { value: { fontSize: gaugeOptions?.measurementValueFontRatio ? this.clampRelative(fractionSize > 5 || measurement?.value > 9999 ? gaugeOptions?.measurementValueFontRatio * 0.5 : gaugeOptions?.measurementValueFontRatio, gaugeOptions?.measurementValueFontSizeMin, gaugeOptions?.measurementValueFontSizeMax) : gaugeOptions?.measurementValueFontSize || 32, fontWeight: 'bolder', fontFamily: gaugeOptionsColors?.fontFamily, color: selectedPresetId === 'custom' ? gaugeOptions.measurementValueColor : gaugeOptionsColors?.measurementValueColor || '#777' }, unit: { fontSize: gaugeOptions?.unitFontRatio ? this.clampRelative(gaugeOptions?.unitFontRatio, gaugeOptions?.unitFontMin, gaugeOptions?.unitFontMax) : gaugeOptions?.unitFontSize || 20, fontFamily: gaugeOptionsColors?.fontFamily, color: selectedPresetId === 'custom' ? gaugeOptions?.unitColor : gaugeOptionsColors?.unitColor || '#999' }, date: { fontSize: gaugeOptions?.dateFontRatio ? this.clampRelative(gaugeOptions?.dateFontRatio, gaugeOptions?.dateFontSizeMin, gaugeOptions?.dateFontSizeMax) : gaugeOptions?.dateFontSize || 12, fontFamily: gaugeOptionsColors?.fontFamily, color: selectedPresetId === 'custom' ? gaugeOptions?.dateColor : gaugeOptionsColors?.dateColor || '#555' } } }, markPoint: { symbolSize: 0, offsetCenter: gaugeOptions?.detailOffsetCenter || [0, 0], data: [ { x: '50%', y: '50%', label: { show: gaugeOptions?.showMarkPoint, formatter: () => { const value = measurement?.value.toFixed(fractionSize) || 0; const unit = activeDatapointGauge?.unit || measurement?.unit || ''; const date = new Date(measurement?.date); const formattedDate = this.c8yDatePipe.transform(date); return `{value|${value}}{unit|${unit}} \n {date|${formattedDate}} `; }, color: '#fff', align: 'center', rich: { value: { fontSize: gaugeOptions?.measurementValueFontRatio ? this.clampRelative(fractionSize > 5 ? gaugeOptions?.measurementValueFontRatio * 0.5 : gaugeOptions?.measurementValueFontRatio, fractionSize > 5 ? gaugeOptions.measurementValueFontMin * 0.5 : gaugeOptions?.measurementValueFontSizeMin, gaugeOptions?.measurementValueFontSizeMax) : gaugeOptions?.measurementValueFontSize || 32, fontFamily: gaugeOptionsColors?.fontFamily, fontWeight: 'bolder', color: gaugeOptionsColors?.knobFontColor || '#777' }, unit: { fontSize: gaugeOptions?.unitFontRatio ? this.clampRelative(gaugeOptions?.unitFontRatio, gaugeOptions?.unitFontSizeMin, gaugeOptions?.unitFontSizeMax) : gaugeOptions?.unitFontSize || 20, fontFamily: gaugeOptionsColors?.fontFamily, color: gaugeOptionsColors?.knobFontColor || '#999' }, date: { fontSize: gaugeOptions?.dateFontRatio ? this.clampRelative(gaugeOptions?.dateFontRatio, gaugeOptions?.dateFontSizeMin, gaugeOptions?.dateFontSizeMax) : gaugeOptions?.dateFontSize || 12, fontFamily: gaugeOptionsColors?.fontFamily, color: gaugeOptionsColors?.knobFontColor || '#555' } } } } ] }, data: [ { value: 0 || measurement?.value } ] } ] }; } updateRangeColors() { if (!this.chart) { return; } return { default: getComputedStyle(this.chart.nativeElement).getPropertyValue('--c8y-form-control-border-color-default'), yellow: getComputedStyle(this.chart.nativeElement).getPropertyValue('--c8y-palette-status-warning'), red: getComputedStyle(this.chart.nativeElement).getPropertyValue('--c8y-palette-status-danger') }; } updateGaugeOptionsColors() { if (!this.chart) { return; } return { splitLineColor: getComputedStyle(this.chart.nativeElement).getPropertyValue('--c8y-root-component-background-default'), tickColor: getComputedStyle(this.chart.nativeElement).getPropertyValue('--c8y-root-component-background-default'), axisLabelColor: getComputedStyle(this.chart.nativeElement).getPropertyValue('--c8y-root-component-color-default'), pointerColor: getComputedStyle(this.chart.nativeElement).getPropertyValue('--c8y-root-component-color-default'), knobColor: getComputedStyle(this.chart.nativeElement).getPropertyValue('--c8y-root-component-color-default'), knobFontColor: getComputedStyle(this.chart.nativeElement).getPropertyValue('--c8y-root-component-background-default'), measurementValueColor: getComputedStyle(this.chart.nativeElement).getPropertyValue('--c8y-root-component-color-default'), unitColor: getComputedStyle(this.chart.nativeElement).getPropertyValue('--c8y-root-component-color-default'), dateColor: getComputedStyle(this.chart.nativeElement).getPropertyValue('--c8y-root-component-text-muted'), progressBarColor: getComputedStyle(this.chart.nativeElement).getPropertyValue('--c8y-root-component-brand-primary'), fontFamily: getComputedStyle(document.body).getPropertyValue('font-family') }; } getColorArray(activeDatapointGauge, rangeColors) { const min = activeDatapointGauge?.min ?? 0; const max = activeDatapointGauge?.max ?? 100; const range = max - min; const yellowMin = activeDatapointGauge?.yellowRangeMin; const yellowMax = activeDatapointGauge?.yellowRangeMax; const redMin = activeDatapointGauge?.redRangeMin; const redMax = activeDatapointGauge?.redRangeMax; const ranges = []; // First add red range if (redMin != null && redMax != null && redMin < redMax) { ranges.push({ start: redMin, end: redMax, color: rangeColors.red }); } // Then add yellow, but clip out any overlap with red if (yellowMin != null && yellowMax != null && yellowMin < yellowMax) { const redRange = ranges.find(r => r.color === rangeColors.red); if (!redRange || yellowMax <= redRange.start || // yellow ends before red yellowMin >= redRange.end // yellow starts after red ) { // no overlap ranges.push({ start: yellowMin, end: yellowMax, color: rangeColors.yellow }); } else { // partial overlap – split yellow into non-overlapping parts if (yellowMin < redRange.start) { ranges.push({ start: yellowMin, end: Math.min(yellowMax, redRange.start), color: rangeColors.yellow }); } if (yellowMax > redRange.end) { ranges.push({ start: Math.max(yellowMin, redRange.end), end: yellowMax, color: rangeColors.yellow }); } } } // Sort by start position ranges.sort((a, b) => a.start - b.start); // Build segments const segments = []; let current = min; for (const { start, end, color } of ranges) { if (start > current) { // Default fill before this range segments.push([(start - min) / range, rangeColors.default]); } // Start color segments.push([(start - min) / range, color]); // End color segments.push([(end - min) / range, color]); current = Math.max(current, end); } // Fill remaining with default if (current < max) { segments.push([(current - min) / range, rangeColors.default]); } if (segments.length === 0 || segments[segments.length - 1][0] < 1) { segments.push([1, rangeColors.default]); } return segments; } clampRelative(ratio, min, max) { const clampedValue = this.containerSize * ratio; if (clampedValue < min) { return min; } else if (clampedValue > max) { return max; } else { return clampedValue; } } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: RadialGaugeService, deps: [{ token: i1.DatePipe }], target: i0.ɵɵFactoryTarget.Injectable }); } static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: RadialGaugeService, providedIn: 'root' }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: RadialGaugeService, decorators: [{ type: Injectable, args: [{ providedIn: 'root' }] }], ctorParameters: () => [{ type: i1.DatePipe }] }); class RadialGaugeViewComponent { constructor() { this.fractionSize = 2; this.selectedPresetId = ''; this.rangeColors = { default: '#E8EBED', yellow: '#ff8800', red: '#d70f0f' }; this.configChangedSubject = new BehaviorSubject(null); this.themeSwitcherService = inject(ThemeSwitcherService); this.radialGaugeService = inject(RadialGaugeService); this.chartOptions$ = this.configChangedSubject.pipe(switchMap(() => of(this.radialGaugeService.getChartOptions(this.gaugeOptions, this.activeDatapointGauge, this.selectedPresetId, this.gaugeOptionsColors, this.rangeColors, this.measurement, this.fractionSize))), tap(options => { if (this.echartsInstance) { this.echartsInstance.setOption(options, false, true); } })); } ngOnInit() { if (!this.gaugeOptions) { this.gaugeOptions = GAUGE_PRESETS[0]; } this.themeSubscription = this.themeSwitcherService.currentlyAppliedTheme$.subscribe(() => { queueMicrotask(() => { this.radialGaugeService.setChart(this.chart); this.rangeColors = this.radialGaugeService.updateRangeColors(); this.gaugeOptionsColors = this.radialGaugeService.updateGaugeOptionsColors(); requestAnimationFrame(() => { this.configChangedSubject.next(); }); }); }); } ngAfterViewInit() { this.resizeObserver = new ResizeObserver(entries => { for (const entry of entries) { if (entry.contentRect.width > 0 && entry.contentRect.height > 0) { this.radialGaugeService.containerSize = entry.contentRect.width >= entry.contentRect.height ? entry.contentRect.height : entry.contentRect.width; this.configChangedSubject.next(); } } }); this.resizeObserver.observe(this.chart.nativeElement); } ngOnDestroy() { if (this.themeSubscription) { this.themeSubscription.unsubscribe(); } if (this.resizeObserver) { this.resizeObserver.disconnect(); } } ngOnChanges(changes) { if (changes.gaugeOptions || changes.measurement || changes.activeDatapointGauge) { this.configChangedSubject.next(); } } onChartInit(ec) { this.echartsInstance = ec; } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: RadialGaugeViewComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); } static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.14", type: RadialGaugeViewComponent, isStandalone: true, selector: "c8y-radial-gauge", inputs: { activeDatapointGauge: "activeDatapointGauge", measurement: "measurement", fractionSize: "fractionSize", gaugeOptions: "gaugeOptions", selectedPresetId: "selectedPresetId" }, host: { classAttribute: "c8y-radial-gauge" }, providers: [ MeasurementRealtimeService, RadialGaugeService, { provide: NGX_ECHARTS_CONFIG, useFactory: () => ({ echarts: () => import('echarts') }) } ], viewQueries: [{ propertyName: "chart", first: true, predicate: ["chart"], descendants: true }], usesOnChanges: true, ngImport: i0, template: "<div\n class=\"p-absolute fit-w fit-h\"\n data-cy=\"c8y-radial-gauge--chart\"\n #chart\n echarts\n [options]=\"chartOptions$ | async\"\n (chartInit)=\"onChartInit($event)\"\n></div>\n", dependencies: [{ kind: "pipe", type: AsyncPipe, name: "async" }, { kind: "ngmodule", type: NgxEchartsModule }, { kind: "directive", type: i1$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"] }] }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: RadialGaugeViewComponent, decorators: [{ type: Component, args: [{ selector: 'c8y-radial-gauge', host: { class: 'c8y-radial-gauge' }, providers: [ MeasurementRealtimeService, RadialGaugeService, { provide: NGX_ECHARTS_CONFIG, useFactory: () => ({ echarts: () => import('echarts') }) } ], standalone: true, imports: [AsyncPipe, NgxEchartsModule], template: "<div\n class=\"p-absolute fit-w fit-h\"\n data-cy=\"c8y-radial-gauge--chart\"\n #chart\n echarts\n [options]=\"chartOptions$ | async\"\n (chartInit)=\"onChartInit($event)\"\n></div>\n" }] }], ctorParameters: () => [], propDecorators: { activeDatapointGauge: [{ type: Input }], measurement: [{ type: Input }], fractionSize: [{ type: Input }], gaugeOptions: [{ type: Input }], selectedPresetId: [{ type: Input }], chart: [{ type: ViewChild, args: ['chart', { static: false }] }] } }); class InfoGaugeWidgetViewComponent { constructor(dashboard, dynamicComponent) { this.dashboard = dashboard; this.dynamicComponent = dynamicComponent; this.activeDatapointLabels = []; this.fractionSize = '1.1-1'; } ngOnInit() { if (this.config?.datapoints && !this.config.datapointsGauge) { this.config.datapointsGauge = this.config.datapoints; } } ngOnChanges() { if (this.config?.datapointsLabels && Array.isArray(this.config?.datapointsLabels)) { this.config.datapointsLabels.forEach(dp => this.assignContextFromContextDashboard(dp)); this.activeDatapointLabels = this.config?.datapointsLabels.filter(dp => dp.__active); } if (this.config?.datapointsGauge && Array.isArray(this.config?.datapointsGauge)) { this.config.datapointsGauge.forEach(dp => this.assignContextFromContextDashboard(dp)); this.activeDatapointGauge = this.config?.datapointsGauge.find(dp => dp.__active); } if (this.config?.datapoints && Array.isArray(this.config?.datapoints) && this.config?.datapoints.length > 0 && !this.config?.datapointsGauge) { this.config.datapoints.forEach(dp => this.assignContextFromContextDashboard(dp)); this.activeDatapointGauge = this.config?.datapoints.find(dp => dp.__active); } if (typeof this.config.fractionSize === 'number' && !Number.isNaN(this.config.fractionSize)) { this.fractionSize = `1.${this.config.fractionSize}-${this.config.fractionSize}`; } this.isInfoGauge = this.dynamicComponent?.componentId === defaultWidgetIds.INFO_GAUGE; } assignContextFromContextDashboard(datapoint) { if (!this.dashboard?.isDeviceTypeDashboard) { return; } const context = this.dashboard?.context; if (context?.id) { const { name, id } = context; datapoint.__target = { name, id }; } } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: InfoGaugeWidgetViewComponent, deps: [{ token: i2.ContextDashboardComponent, optional: true }, { token: i1.DynamicComponentComponent, optional: true }], target: i0.ɵɵFactoryTarget.Component }); } static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.14", type: InfoGaugeWidgetViewComponent, isStandalone: true, selector: "c8y-info-gauge-widget-view", inputs: { config: "config" }, host: { classAttribute: "d-contents" }, providers: [MeasurementRealtimeService], usesOnChanges: true, ngImport: i0, template: "<div\n class=\"label-value-unit-gauge new-radial\"\n *ngIf=\"isInfoGauge\"\n>\n <div\n class=\"gauge-legend\"\n *ngIf=\"activeDatapointLabels?.length && isInfoGauge\"\n >\n <ng-container *ngFor=\"let dp of activeDatapointLabels\">\n <ng-container *ngIf=\"dp | infoGaugeCurrentMeasurement | async as measurement\">\n <label\n class=\"text-truncate\"\n title=\"{{ dp.label }}\"\n >\n {{ dp.label }}\n </label>\n <h3\n class=\"text-truncate\"\n title=\"{{ measurement.value | number: fractionSize }} {{ dp.unit || measurement.unit }}\"\n *ngIf=\"!measurement.notFound; else notFound\"\n >\n {{ measurement.value | number: fractionSize }} {{ dp.unit || measurement.unit }}\n </h3>\n <ng-template #notFound>\n <h3>--</h3>\n </ng-template>\n <p class=\"text-muted m-b-8\">\n <small>\n <em>{{ measurement.date | c8yDate: 'short' }}</em>\n </small>\n </p>\n </ng-container>\n </ng-container>\n </div>\n\n <ng-container *ngIf=\"activeDatapointGauge && isInfoGauge\">\n <div\n class=\"gauge-svg\"\n *ngIf=\"activeDatapointGauge | infoGaugeCurrentMeasurement: true | async as measurement\"\n >\n <svg\n height=\"214px\"\n width=\"214px\"\n viewBox=\"0 0 214 214\"\n version=\"1.1\"\n xmlns=\"http://www.w3.org/2000/svg\"\n xmlns:xlink=\"http://www.w3.org/1999/xlink\"\n >\n <desc>radial gauge</desc>\n <g\n id=\"scale\"\n stroke=\"none\"\n stroke-width=\"1\"\n fill=\"none\"\n fill-rule=\"evenodd\"\n stroke-dasharray=\"1,5\"\n >\n <circle\n id=\"Oval\"\n stroke=\"#CACECE\"\n stroke-width=\"7\"\n cx=\"107\"\n cy=\"107\"\n r=\"103\"\n ></circle>\n <rect\n id=\"mask\"\n height=\"214\"\n stroke=\"none\"\n fill-rule=\"evenodd\"\n x=\"0\"\n y=\"0\"\n width=\"214\"\n transform=\"rotate(-45 290 182)\"\n ></rect>\n </g>\n <path\n class=\"track\"\n d=\"M 107 27 a 80 80 0 1 0.1 0 Z\"\n transform=\"rotate(-135 107 107)\"\n ></path>\n <path\n class=\"track-value\"\n [ngStyle]=\"{\n stroke: measurement.color,\n 'stroke-dashoffset': -measurement.strokeDashOffset\n }\"\n d=\"M 107 27 a 80 80 0 1 0.1 0 Z\"\n transform=\"rotate(-135 107 107)\"\n ></path>\n <foreignObject\n class=\"d-flex a-i-center j-c-center\"\n height=\"100%\"\n width=\"100%\"\n requiredFeatures=\"http://www.w3.org/TR/SVG11/feature#Extensibility\"\n >\n <div\n class=\"d-flex d-col fit-h a-i-center j-c-center\"\n style=\"padding: 3rem\"\n xmlns=\"http://www.w3.org/1999/xhtml\"\n >\n <p\n class=\"text-truncate text-center\"\n title=\"{{ activeDatapointGauge.label }}\"\n >\n {{ activeDatapointGauge.label }}\n </p>\n <p\n class=\"center-value text-truncate\"\n title=\"{{ measurement.value | number: fractionSize }}\"\n *ngIf=\"!measurement.notFound; else notFoundSVG\"\n >\n {{ measurement.value | number: fractionSize }}\n </p>\n <ng-template #notFoundSVG>\n <p class=\"center-value\">--</p>\n </ng-template>\n <p class=\"center-unit strong\">{{ activeDatapointGauge.unit || measurement.unit }}</p>\n <p class=\"center-date-time\">{{ measurement.date | c8yDate: 'short' }}</p>\n </div>\n </foreignObject>\n </svg>\n </div>\n </ng-container>\n\n <div class=\"clearfix\"></div>\n</div>\n<ng-container *ngIf=\"!isInfoGauge\">\n <c8y-radial-gauge\n *ngIf=\"activeDatapointGauge | infoGaugeCurrentMeasurement | async as measurement\"\n [activeDatapointGauge]=\"activeDatapointGauge\"\n [measurement]=\"measurement\"\n [fractionSize]=\"config.fractionSize\"\n [gaugeOptions]=\"config.gaugeOptions\"\n [selectedPresetId]=\"config.selectedPresetId\"\n ></c8y-radial-gauge>\n</ng-container>\n", dependencies: [{ kind: "directive", type: NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: NgFor, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "pipe", type: InfoGaugeCurrentMeasurementPipe, name: "infoGaugeCurrentMeasurement" }, { kind: "pipe", type: AsyncPipe, name: "async" }, { kind: "pipe", type: DecimalPipe, name: "number" }, { kind: "pipe", type: DatePipe, name: "c8yDate" }, { kind: "component", type: RadialGaugeViewComponent, selector: "c8y-radial-gauge", inputs: ["activeDatapointGauge", "measurement", "fractionSize", "gaugeOptions", "selectedPresetId"] }] }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: InfoGaugeWidgetViewComponent, decorators: [{ type: Component, args: [{ selector: 'c8y-info-gauge-widget-view', host: { class: 'd-contents' }, providers: [MeasurementRealtimeService], standalone: true, imports: [ NgIf, NgFor, NgStyle, InfoGaugeCurrentMeasurementPipe, AsyncPipe, DecimalPipe, DatePipe, RadialGaugeViewComponent ], template: "<div\n class=\"label-value-unit-gauge new-radial\"\n *ngIf=\"isInfoGauge\"\n>\n <div\n class=\"gauge-legend\"\n *ngIf=\"activeDatapointLabels?.length && isInfoGauge\"\n >\n <ng-container *ngFor=\"let dp of activeDatapointLabels\">\n <ng-container *ngIf=\"dp | infoGaugeCurrentMeasurement | async as measurement\">\n <label\n class=\"text-truncate\"\n title=\"{{ dp.label }}\"\n >\n {{ dp.label }}\n </label>\n <h3\n class=\"text-truncate\"\n title=\"{{ measurement.value | number: fractionSize }} {{ dp.unit || measurement.unit }}\"\n *ngIf=\"!measurement.notFound; else notFound\"\n >\n {{ measurement.value | number: fractionSize }} {{ dp.unit || measurement.unit }}\n </h3>\n <ng-template #notFound>\n <h3>--</h3>\n </ng-template>\n <p class=\"text-