UNPKG

@c8y/ngx-components

Version:

Angular modules for Cumulocity IoT applications

1 lines 111 kB
{"version":3,"file":"c8y-ngx-components-widgets-implementations-info-gauge.mjs","sources":["../../widgets/implementations/info-gauge/current-measurement.pipe.ts","../../widgets/implementations/info-gauge/info-gauge-widget-config/gauge.model.ts","../../widgets/implementations/info-gauge/radial-gauge/radial-gauge.service.ts","../../widgets/implementations/info-gauge/radial-gauge/radial-gauge.component.ts","../../widgets/implementations/info-gauge/radial-gauge/radial-gauge.component.html","../../widgets/implementations/info-gauge/info-gauge-widget-view/info-gauge-widget-view.component.ts","../../widgets/implementations/info-gauge/info-gauge-widget-view/info-gauge-widget-view.component.html","../../widgets/implementations/info-gauge/info-gauge-widget-config/preset-preview/preset-preview.component.ts","../../widgets/implementations/info-gauge/info-gauge-widget-config/preset-preview/preset-preview.component.html","../../widgets/implementations/info-gauge/info-gauge-widget-config/info-gauge-widget-config.component.ts","../../widgets/implementations/info-gauge/info-gauge-widget-config/info-gauge-widget-config.component.html","../../widgets/implementations/info-gauge/c8y-ngx-components-widgets-implementations-info-gauge.ts"],"sourcesContent":["import { Pipe, PipeTransform } from '@angular/core';\nimport type { KPIDetails } from '@c8y/ngx-components/datapoint-selector';\nimport { AlertService, MeasurementRealtimeService } from '@c8y/ngx-components';\nimport { IMeasurementValue } from '@c8y/client';\nimport { Observable, of } from 'rxjs';\nimport { map, catchError } from 'rxjs/operators';\n\nconst INFO_GAUGE_COLORS = {\n GREEN: 'var(--c8y-brand-50)',\n YELLOW: 'var(--c8y-palette-status-warning)',\n RED: 'var(--c8y-palette-status-danger)'\n} as const;\n\n@Pipe({\n name: 'infoGaugeCurrentMeasurement',\n pure: true,\n standalone: true\n})\nexport class InfoGaugeCurrentMeasurementPipe implements PipeTransform {\n constructor(\n private measurementRealtime: MeasurementRealtimeService,\n private alert: AlertService\n ) {}\n transform(\n datapoint: KPIDetails,\n calculateGauge?: false\n ): Observable<{ date: string; value: number; unit: string; notFound?: boolean }>;\n transform(\n datapoint: KPIDetails,\n calculateGauge?: true\n ): Observable<{\n date: string;\n value: number;\n unit: string;\n color: (typeof INFO_GAUGE_COLORS)[keyof typeof INFO_GAUGE_COLORS];\n strokeDashOffset: number;\n notFound?: boolean;\n }>;\n transform(\n datapoint: KPIDetails,\n calculateGauge?: boolean\n ): Observable<{\n date: string;\n value: number;\n unit: string;\n color?: (typeof INFO_GAUGE_COLORS)[keyof typeof INFO_GAUGE_COLORS];\n strokeDashOffset?: number;\n notFound?: boolean;\n }> {\n return this.measurementRealtime\n .latestValueOfSpecificMeasurement$(\n datapoint.fragment,\n datapoint.series,\n datapoint.__target,\n 1,\n true\n )\n .pipe(\n map(m => {\n // in case measurement is not stored in DB when initially requested.\n if (!m) {\n return { value: Number.NaN, date: '', unit: datapoint.unit || '', notFound: true };\n }\n const measurementValue: IMeasurementValue = m[datapoint.fragment][datapoint.series];\n const data = {\n value: measurementValue.value,\n unit: measurementValue.unit || datapoint.unit,\n date: m.time\n };\n if (!calculateGauge) {\n return data;\n }\n const gauge = this.calculateGauge(datapoint, measurementValue.value);\n return { ...data, ...gauge };\n }),\n catchError(e => {\n this.alert.addServerFailure(e);\n\n return of({ value: Number.NaN, date: '', unit: '' });\n })\n );\n }\n\n private calculateGauge(datapoint: KPIDetails, value: number) {\n const val = value;\n const min = Number(datapoint.min || 0);\n const max = Number(datapoint.max || 0);\n const yMin = Number(datapoint.yellowRangeMin);\n const yMax = Number(datapoint.yellowRangeMax);\n const rMin = Number(datapoint.redRangeMin);\n const rMax = Number(datapoint.redRangeMax);\n\n // Previously d3 was used for linear scale: d3.scale.linear().domain([min, max]).range([0, 100]);\n // wanted to avoid importing d3 just for this.\n const scale = (value1: number) => (value1 - min) / ((max - min) / 100);\n\n const strokeDashOffset = 125.75 + (377.25 - (scale(val) / 100) * 377.25);\n let color: (typeof INFO_GAUGE_COLORS)[keyof typeof INFO_GAUGE_COLORS] = INFO_GAUGE_COLORS.GREEN;\n\n if (Number.isFinite(yMin) && Number.isFinite(yMax)) {\n if (val >= yMin && val <= yMax) {\n color = INFO_GAUGE_COLORS.YELLOW;\n }\n }\n\n if (Number.isFinite(rMin) && Number.isFinite(rMax)) {\n if (val >= rMin && val <= rMax) {\n color = INFO_GAUGE_COLORS.RED;\n }\n }\n\n return { color, strokeDashOffset };\n }\n}\n","import { gettext } from '@c8y/ngx-components';\nimport { KPIDetails } from '@c8y/ngx-components/datapoint-selector';\n\n/* General properties for the gauge */\nexport interface GeneralGaugeOptions {\n /**\n * Name of the gauge preset\n */\n name?: string;\n\n /**\n * Radius of the gauge (e.g., '90%')\n */\n radius?: string;\n\n /**\n * Center of the gauge (e.g., ['50%', '50%'])\n */\n center?: string[];\n\n /**\n * Starting angle of the gauge\n */\n startAngle?: number;\n\n /**\n * Ending angle of the gauge\n */\n endAngle?: number;\n}\n\n/**\n * Properties related to the split lines of the gauge\n */\nexport interface SplitPropertiesGaugeOptions {\n /**\n * Number of segments in the gauge\n */\n splitNumber?: number;\n\n /**\n * Length of the split line, can be a percentage value relative to radius\n */\n splitLineLength?: number | string;\n\n /**\n * Length of the split line as a ratio relative to the axisLineWidth (overrides splitLineLength)\n */\n splitLineLengthRatio?: number;\n\n /**\n * Distance between the split line and axis line\n */\n splitLineDistance?: number;\n\n /**\n * Distance between the split line and axis line as a ratio relative to the axisLineWidth (overrides splitLineDistance)\n */\n splitLineDistanceRatio?: number;\n\n /**\n * Color of the split lines (used only in the custom preset)\n */\n splitLineColor?: string;\n\n /**\n * Width of the split lines\n */\n splitLineWidth?: number;\n}\n\n/**\n * Properties related to the ticks of the gauge\n */\nexport interface TickPropertiesGaugeOptions {\n /**\n * Whether to show ticks\n */\n tickShow?: boolean;\n\n /**\n * Width of the ticks\n */\n tickWidth?: number;\n\n /**\n * Color of the ticks (used only in the custom preset)\n */\n tickColor?: string;\n\n /**\n * Distance of the ticks from the center\n */\n tickDistance?: number;\n\n /**\n * Distance of the ticks from the center as a ratio relative to the axisLineWidth (overrides tickDistance)\n */\n tickDistanceRatio?: number;\n\n /**\n * Length of the ticks\n */\n tickLength?: number;\n\n /**\n * Length of the ticks as a ratio relative to the axisLineWidth (overrides tickLength)\n */\n tickLengthRatio?: number;\n}\n\n/**\n * Properties related to the axis of the gauge\n */\nexport interface AxisPropertiesGaugeOptions {\n /**\n * Distance of the axis labels from the center\n */\n axisLabelDistance?: number;\n\n /**\n * Distance of the axis labels from the center as a ratio relative to the axisLineWidth (overrides axisLabelDistance)\n */\n axisLabelDistanceRatio?: number;\n\n /**\n * Color of the axis labels (used only in the custom preset)\n */\n axisLabelColor?: string;\n\n /**\n * Font size of the axis labels\n */\n axisLabelFontSize?: number;\n\n /**\n * Font size of the axis labels as a ratio relative to the container size (overrides axisLabelFontSize)\n */\n axisLabelFontSizeRatio?: number;\n\n /**\n * Minimum font size of the axis labels (used for clamping)\n */\n axisLabelFontSizeMin?: number;\n\n /**\n * Maximum font size of the axis labels (used for clamping)\n */\n axisLabelFontSizeMax?: number;\n\n /**\n * Width of the axis line\n */\n axisLineWidth?: number;\n\n /**\n * Width of the axis line as a ratio relative to the container size (overrides axisLineWidth)\n */\n axisLineWidthRatio?: number;\n}\n\n/**\n * Properties related to the pointer of the gauge\n */\nexport interface PointerPropertiesGaugeOptions {\n /**\n * Whether to show the pointer\n */\n showPointer?: boolean;\n\n /**\n * Style of the pointer (e.g., custom path)\n */\n pointerStyle?: string;\n\n /**\n * Color of the pointer (used only in the custom preset)\n */\n pointerColor?: string;\n\n /**\n * Width of the pointer\n */\n pointerWidth?: string | number;\n\n /**\n * Width of the pointer as a ratio relative to the container size (overrides pointerWidth)\n */\n pointerWidthRatio?: number;\n\n /**\n * Length of the pointer\n */\n pointerLength?: string | number;\n\n /**\n * Length of the pointer as a ratio relative to the container size (overrides pointerLength)\n */\n pointerLenghtRatio?: number;\n\n /**\n * Offset of the pointer from the center\n */\n pointerOffset?: string | number;\n}\n\n/**\n * Properties related to the progress bar of the gauge\n */\nexport interface ProgressBarGaugeOptions {\n /**\n * Whether to show a progress bar\n */\n progressBar?: boolean;\n\n /**\n * Width of the progress bar\n */\n progressBarWidth?: number;\n\n /**\n * Whether the progress bar has rounded caps\n */\n progressBarRoundCap?: boolean;\n\n /**\n * Color of the progress bar\n */\n progressBarColor?: string;\n\n /**\n * Additional colors for the gauge\n */\n additionalGaugeColors?: string[];\n}\n\n/**\n * Typography-related properties for the gauge\n */\nexport interface TypographyGaugeOptions {\n /**\n * Font size of the measurement value\n */\n measurementValueFontRatio?: number;\n\n /**\n * Minimum font size of the measurement value\n */\n measurementValueFontMin?: number;\n\n /**\n * Maximum font size of the measurement value\n */\n measurementValueFontMax?: number;\n\n /**\n * Color of the measurement value\n */\n measurementValueColor?: string;\n\n /**\n * Font size of the unit\n */\n unitFontSize?: number;\n\n /**\n * Font size of the unit as a ratio relative to the container size (overrides unitFontSize)\n */\n unitFontRatio?: number;\n\n /**\n * Minimum font size of the unit\n */\n unitFontMin?: number;\n\n /**\n * Maximum font size of the unit\n */\n unitFontMax?: number;\n\n /**\n * Color of the unit\n */\n unitColor?: string;\n\n /**\n * Font size of the date\n */\n dateFontSize?: number;\n\n /**\n * Font size of the date as a ratio relative to the container size (overrides dateFontSize)\n */\n dateFontRatio?: number;\n\n /**\n * Minimum font size of the date\n */\n dateFontMin?: number;\n\n /**\n * Maximum font size of the date\n */\n dateFontMax?: number;\n\n /**\n * Color of the date\n */\n dateColor?: string;\n}\n\n/**\n * Properties related to detailed information display\n */\nexport interface DetailPropertiesGaugeOptions {\n /**\n * Whether to show detailed information\n */\n showDetail?: boolean;\n\n /**\n * Font size of the value displayed\n */\n valueFontSize?: number;\n\n /**\n * Offset of the detail from the center\n */\n detailOffsetCenter?: number[] | string[];\n\n /**\n * Whether to show mark points\n */\n showMarkPoint?: boolean;\n}\n\n/**\n * Main interface combining all the smaller interfaces\n */\nexport interface GaugeOptions\n extends GeneralGaugeOptions,\n SplitPropertiesGaugeOptions,\n TickPropertiesGaugeOptions,\n AxisPropertiesGaugeOptions,\n PointerPropertiesGaugeOptions,\n ProgressBarGaugeOptions,\n TypographyGaugeOptions,\n DetailPropertiesGaugeOptions {\n /**\n * Miscellaneous properties for flexibility\n */\n [key: string]: any;\n}\n\nexport interface GaugeOptionsColors {\n splitLineColor: string;\n tickColor: string;\n axisLabelColor: string;\n pointerColor: string;\n knobColor: string;\n knobFontColor: string;\n measurementValueColor: string;\n unitColor: string;\n dateColor: string;\n progressBarColor: string;\n fontFamily: string;\n}\n\nexport interface GaugeAnchor {\n /**\n * Indicates whether to show the anchor.\n */\n show?: boolean;\n\n /**\n * Indicates whether to show the anchor above the gauge.\n */\n showAbove?: boolean;\n\n /**\n * The size of the anchor.\n */\n size?: number;\n\n /**\n * The size of the anchor as a ratio relative to the container size.\n * Overrides the `size` property when set.\n */\n sizeRatio?: number;\n\n /**\n * The style of the anchor, represented as a key-value pair object.\n */\n itemStyle?: { [key: string]: any };\n}\n\nexport const GAUGE_PRESET_NAMES = {\n DEFAULT: gettext('Default'),\n CUSTOM: gettext('Custom preset'),\n POINTER: gettext('Pointer'),\n PROGRESS_BAR: gettext('Progress bar'),\n PROGRESS_INDICATOR: gettext('Progress indicator'),\n GRADE_RATING: gettext('Grade rating')\n} as const;\n\nexport interface InfoGaugeWidgetConfig {\n datapoints?: KPIDetails[];\n datapointsLabels?: KPIDetails[];\n datapointsGauge?: KPIDetails[];\n selectedPresetId?: string;\n gaugeOptions?: GaugeOptions;\n fractionSize: number;\n}\n\nexport const GAUGE_PRESETS: GaugeOptions[] = [\n {\n // General properties\n name: GAUGE_PRESET_NAMES.DEFAULT,\n id: 'default',\n radius: '90%',\n startAngle: 240,\n endAngle: 300,\n\n // Split properties\n splitNumber: 10,\n splitLineLengthRatio: 0.3,\n splitLineColor: '#fff',\n splitLineWidth: 4,\n splitLineDistanceRatio: -0.3,\n\n // Tick properties\n tickShow: true,\n tickDistanceRatio: -0.16,\n tickWidth: 2,\n tickColor: '#fff',\n tickLengthRatio: 0.16,\n\n // Axis properties\n axisLabelDistanceRatio: 0.25,\n axisLabelColor: 'black',\n axisLabelFontSizeMin: 8,\n axisLabelFontSizeRatio: 0.04,\n axisLabelFontSizeMax: 32,\n axisLineWidthRatio: 0.125,\n\n // Pointer properties\n showPointer: true,\n pointerColor: '#222',\n pointerStyle: 'triangle',\n pointerWidthRatio: 2,\n pointerLength: '55%',\n pointerOffset: 0,\n\n // Progress bar properties\n progressBar: false,\n progressBarColor: '#119d11',\n\n // Detail properties\n showDetail: false,\n showMarkPoint: true,\n\n // Typography & Anchor\n measurementValueFontRatio: 0.08,\n measurementValueFontMin: 16,\n measurementValueFontMax: 56,\n measurementValueColor: 'white',\n unitFontSize: 18,\n unitFontRatio: 0.045,\n unitFontMin: 14,\n unitFontMax: 40,\n unitColor: 'white',\n dateFontSize: 0.001,\n dateFontRatio: 0.03,\n dateFontMin: 10,\n dateFontMax: 16,\n dateColor: 'gray',\n anchor: {\n show: true,\n sizeRatio: 0.5,\n itemStyle: {\n color: '#222' // Color of the anchor\n }\n }\n },\n {\n // General properties\n name: GAUGE_PRESET_NAMES.POINTER,\n id: 'pointer',\n radius: '90%',\n startAngle: 240,\n endAngle: 300,\n\n // Split properties\n splitNumber: 10,\n splitLineLengthRatio: 1,\n splitLineColor: '#fff',\n splitLineWidth: 4,\n splitLineDistanceRatio: -1,\n\n // Tick properties\n tickShow: true,\n tickDistanceRatio: -1,\n tickWidth: 2,\n tickColor: '#fff',\n tickLengthRatio: 0.2666666667,\n\n // Axis properties\n axisLabelColor: '#212121',\n axisLabelFontSizeMin: 8,\n axisLabelFontSizeRatio: 0.04,\n axisLabelFontSizeMax: 32,\n axisLineWidthRatio: 0.075,\n axisLabelDistanceRatio: 1.1666666667,\n\n // Pointer properties\n showPointer: true,\n pointerStyle: 'default',\n pointerColor: 'black',\n pointerWidthRatio: 0.2666666667,\n pointerLength: '70%',\n pointerOffset: 5,\n\n // Progress bar properties\n progressBar: false,\n progressBarColor: '#119d11',\n\n // Detail properties\n showDetail: true,\n detailOffsetCenter: ['0%', '35%'],\n showMarkPoint: false,\n\n // Typography specs\n measurementValueFontRatio: 0.08,\n measurementValueFontMin: 16,\n measurementValueFontMax: 56,\n measurementValueColor: 'black',\n unitFontSize: 18,\n unitFontRatio: 0.045,\n unitFontMin: 8,\n unitFontMax: 40,\n unitColor: 'black',\n dateFontRatio: 0.03,\n dateFontMin: 10,\n dateFontMax: 16,\n dateColor: 'black'\n },\n {\n // General properties\n name: GAUGE_PRESET_NAMES.PROGRESS_BAR,\n id: 'progress-bar',\n radius: '85%',\n center: ['50%', '57%'],\n startAngle: 200,\n endAngle: -20,\n\n // Split properties\n splitNumber: 10,\n splitLineLengthRatio: 1,\n splitLineColor: '#fff',\n splitLineWidth: 3,\n splitLineDistanceRatio: -1,\n\n // Tick properties\n tickShow: false,\n\n // Axis properties\n axisLabelColor: '#212121',\n axisLabelFontSizeMin: 8,\n axisLabelFontSizeRatio: 0.04,\n axisLabelFontSizeMax: 32,\n axisLineWidthRatio: 0.075,\n axisLabelDistanceRatio: -0.8,\n\n // Pointer properties\n showPointer: false,\n\n // Progress bar properties\n progressBar: true,\n progressBarWidthRatio: 0.3333,\n progressBarRoundCap: false,\n progressBarColor: '#119d11',\n additionalGaugeColors: ['#FFAB91', '#FD7347'],\n\n // Detail properties\n showDetail: true,\n detailOffsetCenter: ['0%', '0%'],\n showMarkPoint: false,\n\n // Typography specs\n measurementValueFontRatio: 0.12,\n measurementValueFontMin: 16,\n measurementValueFontMax: 72,\n measurementValueColor: 'black',\n unitFontSize: 18,\n unitFontRatio: 0.085,\n unitFontMin: 10,\n unitFontMax: 40,\n unitColor: 'black',\n dateFontRatio: 0.03,\n dateFontMin: 10,\n dateFontMax: 16,\n dateColor: 'black'\n },\n {\n // General properties\n name: GAUGE_PRESET_NAMES.PROGRESS_INDICATOR,\n id: 'progress-indicator',\n radius: '85%',\n startAngle: 240,\n endAngle: 300,\n\n // Split line properties\n splitNumber: 4,\n splitLineLengthRatio: 1,\n splitLineColor: '#fff',\n splitLineWidth: 2,\n splitLineDistanceRatio: -1,\n\n // Tick properties\n tickShow: false,\n\n // Axis properties\n axisLabelColor: '#212121',\n axisLabelFontSizeMin: 7,\n axisLabelFontSizeRatio: 0.04,\n axisLabelFontSizeMax: 32,\n axisLineWidthRatio: 0.075,\n axisLabelDistanceRatio: -1,\n\n // Pointer properties\n showPointer: true,\n pointerStyle: 'default',\n pointerColor: 'black',\n pointerWidthRatio: 0.2,\n pointerLength: '100%',\n pointerOffset: 5,\n\n // Progress bar properties\n progressBar: true,\n progressBarWidthRatio: 0.3333,\n progressBarRoundCap: false,\n progressBarColor: '#119d11',\n\n // Detail properties\n showDetail: true,\n detailOffsetCenter: ['0%', '35%'],\n showMarkPoint: false,\n\n // Typography & Anchor\n measurementValueFontRatio: 0.08,\n measurementValueFontMin: 12,\n measurementValueFontMax: 56,\n measurementValueColor: 'black',\n unitFontSize: 18,\n unitFontRatio: 0.045,\n unitFontMin: 10,\n unitFontMax: 40,\n unitColor: 'black',\n dateFontRatio: 0.03,\n dateFontMin: 10,\n dateFontMax: 16,\n dateColor: 'black',\n\n anchor: {\n show: true,\n showAbove: true,\n sizeRatio: 0.1,\n itemStyle: {\n borderWidth: 10\n }\n }\n },\n {\n // General properties\n name: GAUGE_PRESET_NAMES.GRADE_RATING,\n id: 'grade-rating',\n radius: '75%',\n startAngle: 180,\n endAngle: 360,\n center: ['50%', '65%'],\n\n // Split line properties\n splitNumber: 4,\n splitLineLengthRatio: 3,\n splitLineColor: 'auto',\n splitLineWidth: 6,\n splitLineDistanceRatio: -3,\n\n // Tick properties\n tickShow: true,\n tickDistanceRatio: -2,\n tickWidth: 2,\n tickColor: 'auto',\n tickLengthRatio: 2,\n\n // Axis properties\n axisLabelDistanceRatio: -6,\n axisLabelColor: 'auto',\n axisLabelFontSizeMin: 6,\n axisLabelFontSizeRatio: 0.04,\n axisLabelFontSizeMax: 32,\n axisLineWidthRatio: 0.02,\n\n // Pointer properties\n showPointer: true,\n pointerColor: 'black',\n pointerStyle: 'triangle',\n pointerWidthRatio: 2,\n pointerLength: '12%',\n pointerOffset: '-50%',\n\n // Progress bar properties\n progressBar: false,\n progressBarColor: '#119d11',\n\n // Detail properties\n showDetail: true,\n detailOffsetCenter: ['0%', '0%'],\n showMarkPoint: false,\n\n // Typography\n measurementValueFontRatio: 0.08,\n measurementValueFontMin: 16,\n measurementValueFontMax: 56,\n measurementValueColor: 'black',\n unitFontRatio: 0.045,\n unitFontMin: 8,\n unitFontMax: 40,\n unitColor: 'black',\n dateFontRatio: 0.03,\n dateFontMin: 10,\n dateFontMax: 16,\n dateColor: 'black'\n }\n];\n","import { ElementRef, Injectable } from '@angular/core';\nimport { KPIDetails } from '@c8y/ngx-components/datapoint-selector';\nimport { GaugeOptions, GaugeOptionsColors } from '../info-gauge-widget-config/gauge.model';\nimport { IMeasurementValue } from '@c8y/client';\nimport { DatePipe } from '@c8y/ngx-components';\n\n@Injectable({\n providedIn: 'root'\n})\nexport class RadialGaugeService {\n private chart: ElementRef;\n containerSize = 400;\n\n constructor(private c8yDatePipe: DatePipe) {}\n\n setChart(chart: ElementRef): void {\n this.chart = chart;\n }\n\n getChartOptions(\n gaugeOptions: GaugeOptions,\n activeDatapointGauge: KPIDetails,\n selectedPresetId: string,\n gaugeOptionsColors: GaugeOptionsColors,\n rangeColors: { default: string; yellow: string; red: string },\n measurement: IMeasurementValue,\n fractionSize: number\n ) {\n const colorArray = this.getColorArray(activeDatapointGauge, rangeColors);\n\n return {\n series: [\n {\n type: 'gauge',\n radius: gaugeOptions?.radius || '90%',\n center: gaugeOptions?.center || ['50%', '50%'],\n startAngle: gaugeOptions?.startAngle || 240,\n endAngle: gaugeOptions?.endAngle || 300,\n min: activeDatapointGauge?.min || 0,\n max:\n activeDatapointGauge?.max !== undefined && activeDatapointGauge?.max !== null\n ? activeDatapointGauge.max\n : 100,\n\n // Split properties\n splitNumber: gaugeOptions?.splitNumber || 10,\n splitLine: {\n distance: gaugeOptions?.splitLineDistanceRatio\n ? this.containerSize *\n gaugeOptions?.axisLineWidthRatio *\n gaugeOptions?.splitLineDistanceRatio\n : gaugeOptions?.splitLineDistance || -30,\n\n length: gaugeOptions?.splitLineLengthRatio\n ? this.containerSize *\n gaugeOptions?.axisLineWidthRatio *\n gaugeOptions?.splitLineLengthRatio\n : gaugeOptions?.splitLineLength || 30,\n\n lineStyle: {\n color:\n selectedPresetId === 'custom' || selectedPresetId === 'grade-rating'\n ? gaugeOptions?.splitLineColor\n : gaugeOptionsColors?.splitLineColor || '#fff',\n width: gaugeOptions?.splitLineWidth || 4\n }\n },\n\n // Tick properties\n axisTick: {\n show: gaugeOptions?.tickShow || false,\n distance: gaugeOptions?.tickDistanceRatio\n ? this.containerSize *\n gaugeOptions?.axisLineWidthRatio *\n gaugeOptions?.tickDistanceRatio\n : gaugeOptions?.tickDistance || -30,\n length: gaugeOptions?.tickLengthRatio\n ? this.containerSize *\n gaugeOptions?.axisLineWidthRatio *\n gaugeOptions?.tickLengthRatio\n : gaugeOptions?.tickLength || 8,\n lineStyle: {\n color:\n selectedPresetId === 'custom' || selectedPresetId === 'grade-rating'\n ? gaugeOptions.tickColor\n : gaugeOptionsColors?.tickColor || '#fff',\n width: gaugeOptions?.tickWidth || 2\n }\n },\n\n // Axis properties\n axisLine: {\n lineStyle: {\n width: gaugeOptions?.axisLineWidthRatio\n ? this.containerSize * gaugeOptions?.axisLineWidthRatio\n : gaugeOptions?.axisLineWidth || 30,\n color: colorArray\n }\n },\n\n axisLabel: {\n color:\n selectedPresetId === 'custom' || selectedPresetId === 'grade-rating'\n ? gaugeOptions.axisLabelColor\n : gaugeOptionsColors?.axisLabelColor || 'inherit',\n distance: gaugeOptions?.axisLabelDistanceRatio\n ? this.containerSize *\n gaugeOptions?.axisLineWidthRatio *\n gaugeOptions?.axisLabelDistanceRatio\n : gaugeOptions?.axisLabelDistance || 40,\n fontSize: gaugeOptions?.axisLabelFontSizeRatio\n ? this.clampRelative(\n gaugeOptions?.axisLabelFontSizeRatio,\n gaugeOptions?.axisLabelFontSizeMin,\n gaugeOptions?.axisLabelFontSizeMax\n )\n : gaugeOptions?.axisLabelFontSize || 16,\n fontWeight: 'bold',\n fontFamily: gaugeOptionsColors?.fontFamily\n },\n\n // pointer properties\n pointer: {\n ...(gaugeOptions?.pointerStyle !== 'default' && {\n icon: gaugeOptions?.pointerStyle\n }),\n show: gaugeOptions?.showPointer || false,\n length: gaugeOptions?.pointerLengthRatio\n ? gaugeOptions?.pointerLengthRatio *\n (this.containerSize * gaugeOptions?.axisLineWidthRatio)\n : gaugeOptions?.pointerLength || '65%',\n width: gaugeOptions?.pointerWidthRatio\n ? this.containerSize *\n gaugeOptions?.axisLineWidthRatio *\n gaugeOptions?.pointerWidthRatio\n : gaugeOptions?.pointerWidth || 40,\n offsetCenter:\n selectedPresetId === 'default'\n ? [0, this.containerSize * gaugeOptions?.axisLineWidthRatio * -1]\n : [0, gaugeOptions?.pointerOffset || -30],\n itemStyle: {\n color:\n selectedPresetId === 'custom'\n ? gaugeOptions.pointerColor\n : gaugeOptionsColors?.pointerColor || 'inherit'\n }\n },\n\n // Progress bar properties\n progress: {\n show: gaugeOptions?.progressBar || false,\n roundCap: gaugeOptions?.progressBarRoundCap || false,\n width: gaugeOptions?.progressBarWidthRatio\n ? this.containerSize *\n gaugeOptions?.axisLineWidthRatio *\n gaugeOptions?.progressBarWidthRatio\n : gaugeOptions?.progressBarWidth || 18,\n itemStyle: {\n color:\n selectedPresetId === 'custom'\n ? gaugeOptions.progressBarColor\n : gaugeOptionsColors?.progressBarColor || 'inherit'\n }\n },\n\n // Anchor properties\n anchor: gaugeOptions?.anchor\n ? {\n ...gaugeOptions.anchor,\n size: gaugeOptions?.anchor?.sizeRatio\n ? this.containerSize * gaugeOptions?.anchor?.sizeRatio\n : gaugeOptions?.anchor?.size || 45,\n itemStyle: {\n color:\n selectedPresetId === 'custom'\n ? gaugeOptions.pointerColor\n : gaugeOptionsColors?.pointerColor\n }\n }\n : undefined,\n\n // Detail properties\n detail: {\n show: gaugeOptions?.showDetail || false,\n valueAnimation: true,\n offsetCenter: gaugeOptions?.detailOffsetCenterYRatio\n ? [0, gaugeOptions?.detailOffsetCenterYRatio * this.containerSize]\n : gaugeOptions?.detailOffsetCenter || [0, 0],\n\n formatter: () => {\n const value = measurement?.value.toFixed(fractionSize) || 0;\n const unit = activeDatapointGauge?.unit || measurement?.unit || '';\n const date = new Date(measurement?.date);\n const formattedDate = this.c8yDatePipe.transform(date);\n return `{value|${value}}{unit|${unit}} \\n {date|${formattedDate}} `;\n },\n color: 'inherit',\n lineHeight: gaugeOptions?.measurementValueFontRatio\n ? this.clampRelative(\n gaugeOptions?.measurementValueFontRatio * 0.9,\n gaugeOptions?.measurementValueFontSizeMin,\n gaugeOptions?.measurementValueFontSizeMax\n )\n : gaugeOptions?.measurementValueFontSize || 32,\n rich: {\n value: {\n fontSize: gaugeOptions?.measurementValueFontRatio\n ? this.clampRelative(\n fractionSize > 5 || measurement?.value > 9999\n ? gaugeOptions?.measurementValueFontRatio * 0.5\n : gaugeOptions?.measurementValueFontRatio,\n gaugeOptions?.measurementValueFontSizeMin,\n gaugeOptions?.measurementValueFontSizeMax\n )\n : gaugeOptions?.measurementValueFontSize || 32,\n fontWeight: 'bolder',\n fontFamily: gaugeOptionsColors?.fontFamily,\n color:\n selectedPresetId === 'custom'\n ? gaugeOptions.measurementValueColor\n : gaugeOptionsColors?.measurementValueColor || '#777'\n },\n unit: {\n fontSize: gaugeOptions?.unitFontRatio\n ? this.clampRelative(\n gaugeOptions?.unitFontRatio,\n gaugeOptions?.unitFontMin,\n gaugeOptions?.unitFontMax\n )\n : gaugeOptions?.unitFontSize || 20,\n fontFamily: gaugeOptionsColors?.fontFamily,\n color:\n selectedPresetId === 'custom'\n ? gaugeOptions?.unitColor\n : gaugeOptionsColors?.unitColor || '#999'\n },\n date: {\n fontSize: gaugeOptions?.dateFontRatio\n ? this.clampRelative(\n gaugeOptions?.dateFontRatio,\n gaugeOptions?.dateFontSizeMin,\n gaugeOptions?.dateFontSizeMax\n )\n : gaugeOptions?.dateFontSize || 12,\n fontFamily: gaugeOptionsColors?.fontFamily,\n color:\n selectedPresetId === 'custom'\n ? gaugeOptions?.dateColor\n : gaugeOptionsColors?.dateColor || '#555'\n }\n }\n },\n markPoint: {\n symbolSize: 0,\n offsetCenter: gaugeOptions?.detailOffsetCenter || [0, 0],\n data: [\n {\n x: '50%',\n y: '50%',\n label: {\n show: gaugeOptions?.showMarkPoint,\n formatter: () => {\n const value = measurement?.value.toFixed(fractionSize) || 0;\n const unit = activeDatapointGauge?.unit || measurement?.unit || '';\n const date = new Date(measurement?.date);\n const formattedDate = this.c8yDatePipe.transform(date);\n return `{value|${value}}{unit|${unit}} \\n {date|${formattedDate}} `;\n },\n color: '#fff',\n align: 'center',\n rich: {\n value: {\n fontSize: gaugeOptions?.measurementValueFontRatio\n ? this.clampRelative(\n fractionSize > 5\n ? gaugeOptions?.measurementValueFontRatio * 0.5\n : gaugeOptions?.measurementValueFontRatio,\n fractionSize > 5\n ? gaugeOptions.measurementValueFontMin * 0.5\n : gaugeOptions?.measurementValueFontSizeMin,\n gaugeOptions?.measurementValueFontSizeMax\n )\n : gaugeOptions?.measurementValueFontSize || 32,\n fontFamily: gaugeOptionsColors?.fontFamily,\n fontWeight: 'bolder',\n color: gaugeOptionsColors?.knobFontColor || '#777'\n },\n unit: {\n fontSize: gaugeOptions?.unitFontRatio\n ? this.clampRelative(\n gaugeOptions?.unitFontRatio,\n gaugeOptions?.unitFontSizeMin,\n gaugeOptions?.unitFontSizeMax\n )\n : gaugeOptions?.unitFontSize || 20,\n fontFamily: gaugeOptionsColors?.fontFamily,\n color: gaugeOptionsColors?.knobFontColor || '#999'\n },\n date: {\n fontSize: gaugeOptions?.dateFontRatio\n ? this.clampRelative(\n gaugeOptions?.dateFontRatio,\n gaugeOptions?.dateFontSizeMin,\n gaugeOptions?.dateFontSizeMax\n )\n : gaugeOptions?.dateFontSize || 12,\n fontFamily: gaugeOptionsColors?.fontFamily,\n color: gaugeOptionsColors?.knobFontColor || '#555'\n }\n }\n }\n }\n ]\n },\n data: [\n {\n value: 0 || measurement?.value\n }\n ]\n }\n ]\n };\n }\n\n updateRangeColors(): any {\n if (!this.chart) {\n return;\n }\n\n return {\n default: getComputedStyle(this.chart.nativeElement).getPropertyValue(\n '--c8y-form-control-border-color-default'\n ),\n yellow: getComputedStyle(this.chart.nativeElement).getPropertyValue(\n '--c8y-palette-status-warning'\n ),\n red: getComputedStyle(this.chart.nativeElement).getPropertyValue(\n '--c8y-palette-status-danger'\n )\n };\n }\n\n updateGaugeOptionsColors(): any {\n if (!this.chart) {\n return;\n }\n\n return {\n splitLineColor: getComputedStyle(this.chart.nativeElement).getPropertyValue(\n '--c8y-root-component-background-default'\n ),\n tickColor: getComputedStyle(this.chart.nativeElement).getPropertyValue(\n '--c8y-root-component-background-default'\n ),\n axisLabelColor: getComputedStyle(this.chart.nativeElement).getPropertyValue(\n '--c8y-root-component-color-default'\n ),\n pointerColor: getComputedStyle(this.chart.nativeElement).getPropertyValue(\n '--c8y-root-component-color-default'\n ),\n knobColor: getComputedStyle(this.chart.nativeElement).getPropertyValue(\n '--c8y-root-component-color-default'\n ),\n knobFontColor: getComputedStyle(this.chart.nativeElement).getPropertyValue(\n '--c8y-root-component-background-default'\n ),\n measurementValueColor: getComputedStyle(this.chart.nativeElement).getPropertyValue(\n '--c8y-root-component-color-default'\n ),\n unitColor: getComputedStyle(this.chart.nativeElement).getPropertyValue(\n '--c8y-root-component-color-default'\n ),\n dateColor: getComputedStyle(this.chart.nativeElement).getPropertyValue(\n '--c8y-root-component-text-muted'\n ),\n progressBarColor: getComputedStyle(this.chart.nativeElement).getPropertyValue(\n '--c8y-root-component-brand-primary'\n ),\n fontFamily: getComputedStyle(document.body).getPropertyValue('font-family')\n };\n }\n\n private getColorArray(\n activeDatapointGauge: KPIDetails,\n rangeColors: { default: string; yellow: string; red: string }\n ): [number, string][] {\n const min = activeDatapointGauge?.min ?? 0;\n const max = activeDatapointGauge?.max ?? 100;\n const range = max - min;\n\n const yellowMin = activeDatapointGauge?.yellowRangeMin;\n const yellowMax = activeDatapointGauge?.yellowRangeMax;\n const redMin = activeDatapointGauge?.redRangeMin;\n const redMax = activeDatapointGauge?.redRangeMax;\n\n type Range = { start: number; end: number; color: string };\n\n const ranges: Range[] = [];\n\n // First add red range\n if (redMin != null && redMax != null && redMin < redMax) {\n ranges.push({ start: redMin, end: redMax, color: rangeColors.red });\n }\n\n // Then add yellow, but clip out any overlap with red\n if (yellowMin != null && yellowMax != null && yellowMin < yellowMax) {\n const redRange = ranges.find(r => r.color === rangeColors.red);\n\n if (\n !redRange ||\n yellowMax <= redRange.start || // yellow ends before red\n yellowMin >= redRange.end // yellow starts after red\n ) {\n // no overlap\n ranges.push({ start: yellowMin, end: yellowMax, color: rangeColors.yellow });\n } else {\n // partial overlap – split yellow into non-overlapping parts\n if (yellowMin < redRange.start) {\n ranges.push({\n start: yellowMin,\n end: Math.min(yellowMax, redRange.start),\n color: rangeColors.yellow\n });\n }\n if (yellowMax > redRange.end) {\n ranges.push({\n start: Math.max(yellowMin, redRange.end),\n end: yellowMax,\n color: rangeColors.yellow\n });\n }\n }\n }\n\n // Sort by start position\n ranges.sort((a, b) => a.start - b.start);\n\n // Build segments\n const segments: [number, string][] = [];\n let current = min;\n\n for (const { start, end, color } of ranges) {\n if (start > current) {\n // Default fill before this range\n segments.push([(start - min) / range, rangeColors.default]);\n }\n // Start color\n segments.push([(start - min) / range, color]);\n // End color\n segments.push([(end - min) / range, color]);\n\n current = Math.max(current, end);\n }\n\n // Fill remaining with default\n if (current < max) {\n segments.push([(current - min) / range, rangeColors.default]);\n }\n\n if (segments.length === 0 || segments[segments.length - 1][0] < 1) {\n segments.push([1, rangeColors.default]);\n }\n\n return segments;\n }\n\n private clampRelative(ratio: number, min: number, max: number): number {\n const clampedValue = this.containerSize * ratio;\n if (clampedValue < min) {\n return min;\n } else if (clampedValue > max) {\n return max;\n } else {\n return clampedValue;\n }\n }\n}\n","import {\n Component,\n OnChanges,\n Input,\n SimpleChanges,\n ElementRef,\n ViewChild,\n AfterViewInit,\n OnDestroy,\n inject\n} from '@angular/core';\nimport type { ECharts } from 'echarts';\nimport type { KPIDetails } from '@c8y/ngx-components/datapoint-selector';\nimport { MeasurementRealtimeService, ThemeSwitcherService } from '@c8y/ngx-components';\nimport { AsyncPipe } from '@angular/common';\nimport { IMeasurementValue } from '@c8y/client';\nimport { BehaviorSubject, Observable, of, Subscription, switchMap, tap } from 'rxjs';\nimport { NGX_ECHARTS_CONFIG, NgxEchartsModule } from 'ngx-echarts';\nimport {\n GAUGE_PRESETS,\n GaugeOptions,\n GaugeOptionsColors\n} from '../info-gauge-widget-config/gauge.model';\nimport { RadialGaugeService } from './radial-gauge.service';\n\n@Component({\n selector: 'c8y-radial-gauge',\n templateUrl: './radial-gauge.component.html',\n host: { class: 'c8y-radial-gauge' },\n providers: [\n MeasurementRealtimeService,\n RadialGaugeService,\n { provide: NGX_ECHARTS_CONFIG, useFactory: () => ({ echarts: () => import('echarts') }) }\n ],\n standalone: true,\n imports: [AsyncPipe, NgxEchartsModule]\n})\nexport class RadialGaugeViewComponent implements OnChanges, OnDestroy, AfterViewInit {\n @Input() activeDatapointGauge: KPIDetails;\n @Input() measurement: IMeasurementValue;\n @Input() fractionSize = 2;\n @Input() gaugeOptions: GaugeOptions;\n @Input() selectedPresetId = '';\n @ViewChild('chart', { static: false }) chart!: ElementRef;\n private resizeObserver!: ResizeObserver;\n\n chartOptions$: Observable<GaugeOptions>;\n echartsInstance!: ECharts;\n rangeColors = { default: '#E8EBED', yellow: '#ff8800', red: '#d70f0f' };\n gaugeOptionsColors: GaugeOptionsColors;\n private configChangedSubject = new BehaviorSubject<void | null>(null);\n private themeSubscription: Subscription;\n\n private readonly themeSwitcherService = inject(ThemeSwitcherService);\n private readonly radialGaugeService = inject(RadialGaugeService);\n\n constructor() {\n this.chartOptions$ = this.configChangedSubject.pipe(\n switchMap(() =>\n of(\n this.radialGaugeService.getChartOptions(\n this.gaugeOptions,\n this.activeDatapointGauge,\n this.selectedPresetId,\n this.gaugeOptionsColors,\n this.rangeColors,\n this.measurement,\n this.fractionSize\n )\n )\n ),\n tap(options => {\n if (this.echartsInstance) {\n this.echartsInstance.setOption(options, false, true);\n }\n })\n );\n }\n\n ngOnInit() {\n if (!this.gaugeOptions) {\n this.gaugeOptions = GAUGE_PRESETS[0];\n }\n\n this.themeSubscription = this.themeSwitcherService.currentlyAppliedTheme$.subscribe(() => {\n queueMicrotask(() => {\n this.radialGaugeService.setChart(this.chart);\n this.rangeColors = this.radialGaugeService.updateRangeColors();\n this.gaugeOptionsColors = this.radialGaugeService.updateGaugeOptionsColors();\n requestAnimationFrame(() => {\n this.configChangedSubject.next();\n });\n });\n });\n }\n\n ngAfterViewInit() {\n this.resizeObserver = new ResizeObserver(entries => {\n for (const entry of entries) {\n if (entry.contentRect.width > 0 && entry.contentRect.height > 0) {\n this.radialGaugeService.containerSize =\n entry.contentRect.width >= entry.contentRect.height\n ? entry.contentRect.height\n : entry.contentRect.width;\n this.configChangedSubject.next();\n }\n }\n });\n\n this.resizeObserver.observe(this.chart.nativeElement);\n }\n\n ngOnDestroy() {\n if (this.themeSubscription) {\n this.themeSubscription.unsubscribe();\n }\n\n if (this.resizeObserver) {\n this.resizeObserver.disconnect();\n }\n }\n\n ngOnChanges(changes: SimpleChanges) {\n if (changes.gaugeOptions || changes.measurement || changes.activeDatapointGauge) {\n this.configChangedSubject.next();\n }\n }\n\n onChartInit(ec: ECharts) {\n this.echartsInstance = ec;\n }\n}\n","<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","import { Component, OnChanges, Input, Optional } from '@angular/core';\nimport type { KPIDetails } from '@c8y/ngx-components/datapoint-selector';\nimport {\n DatePipe,\n DynamicComponentComponent,\n MeasurementRealtimeService\n} from '@c8y/ngx-components';\nimport { AsyncPipe, DecimalPipe, NgFor, NgIf, NgStyle } from '@angular/common';\nimport { InfoGaugeCurrentMeasurementPipe } from '../current-measurement.pipe';\nimport { ContextDashboardComponent } from '@c8y/ngx-components/context-dashboard';\nimport { defaultWidgetIds } from '@c8y/ngx-components/widgets/definitions';\nimport { RadialGaugeViewComponent } from '../radial-gauge/radial-gauge.component';\nimport { InfoGaugeWidgetConfig } from '../info-gauge-widget-config/gauge.model';\n\n@Component({\n selector: 'c8y-info-gauge-widget-view',\n templateUrl: './info-gauge-widget-view.component.html',\n host: { class: 'd-contents' },\n providers: [MeasurementRealtimeService],\n standalone: true,\n imports: [\n NgIf,\n NgFor,\n NgStyle,\n InfoGaugeCurrentMeasurementPipe,\n AsyncPipe,\n DecimalPipe,\n DatePipe,\n RadialGaugeViewComponent\n ]\n})\nexport class InfoGaugeWidgetViewComponent implements OnChanges {\n @Input() config: InfoGaugeWidgetConfig;\n activeDatapointLabels = [];\n activeDatapointGauge: KPIDetails;\n fractionSize = '1.1-1';\n isInfoGauge: boolean;\n\n constructor(\n @Optional() private dashboard: ContextDashboardComponent,\n @Optional() private dynamicComponent?: DynamicComponentComponent\n ) {}\n\n ngOnInit() {\n if (this.config?.datapoints && !this.config.datapointsGauge) {\n this.config.datapointsGauge = this.config.datapoints;\n }\n }\n\n ngOnChanges(): void {\n if (this.config?.datapointsLabels && Array.isArray(this.config?.datapointsLabels)) {\n this.config.datapointsLabels.forEach(dp => this.assignContextFromContextDashboard(dp));\n this.activeDatapointLabels = this.config?.datapointsLabels.filter(dp => dp.__active);\n }\n\n if (this.config?.datapointsGauge && Array.isArray(this.config?.datapointsGauge)) {\n this.config.datapointsGauge.forEach(dp => this.assignContextFromContextDashboard(dp));\n this.activeDatapointGauge = this.config?.datapointsGauge.find(dp => dp.__active);\n }\n\n if (\n this.config?.datapoints &&\n Array.isArray(this.config?.datapoints) &&\n this.config?.datapoints.length > 0 &&\n !this.config?.datapointsGauge\n ) {\n this.config.datapoints.forEach(dp => this.assignContextFromContextDashboard(dp));\n this.activeDatapointGauge = this.config?.datapoints.find(dp => dp.__active);\n }\n\n if (typeof this.config.fractionSize === 'number' && !Number.isNaN(this.config.fractionSize)) {\n this.fractionSize = `1.${this.config.fractionSize}-${this.config.fractionSize}`;\n }\n this.isInfoGauge = this.dynamicComponent?.componentId === defaultWidgetIds.INFO_GAUGE;\n }\n\n private assignContextFromContextDashboard(datapoint: KPIDetails) {\n if (!this.dashboard?.isDeviceTypeDashboard) {\n return;\n }\n const context = this.dashboard?.context;\n if (context?.id) {\n const { name, id } = context;\n datapoint.__target = { name, id };\n }\n }\n}\n","<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-valu