@c8y/ngx-components
Version:
Angular modules for Cumulocity IoT applications
896 lines (891 loc) • 82.8 kB
JavaScript
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-