diff --git a/ui-ngx/src/app/modules/home/components/widget/config/basic/chart/bar-chart-with-labels-basic-config.component.html b/ui-ngx/src/app/modules/home/components/widget/config/basic/chart/bar-chart-with-labels-basic-config.component.html index c98c9274cc..0b629e4cae 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/basic/chart/bar-chart-with-labels-basic-config.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/config/basic/chart/bar-chart-with-labels-basic-config.component.html @@ -84,8 +84,28 @@ +
+
widgets.time-series-chart.chart
+
+ + {{ 'widgets.time-series-chart.data-zoom' | translate }} + +
+
widgets.bar-chart.bar-appearance
+
+
widget-config.units-short
+ + +
+
+
widget-config.decimals-short
+ + + +
{{ 'widgets.bar-chart.label-on-bar' | translate }} @@ -118,19 +138,57 @@
-
-
widget-config.units-short
- - +
+ + {{ 'widgets.time-series-chart.series.bar.show-border' | translate }} +
-
-
widget-config.decimals-short
- - +
+
widgets.time-series-chart.series.bar.border-width
+ + + +
+
+
widgets.time-series-chart.series.bar.border-radius
+ +
+ + + +
+
+
widgets.time-series-chart.axis.y-axis
+ + +
+
+
widgets.time-series-chart.axis.x-axis
+ + +
+ +
@@ -230,6 +288,9 @@
+ +
widget-config.card-appearance
@@ -249,6 +310,12 @@
+
+
{{ 'widget-config.card-padding' | translate }}
+ + + +
diff --git a/ui-ngx/src/app/modules/home/components/widget/config/basic/chart/bar-chart-with-labels-basic-config.component.ts b/ui-ngx/src/app/modules/home/components/widget/config/basic/chart/bar-chart-with-labels-basic-config.component.ts index 78b0b6073b..ab5fc0a867 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/basic/chart/bar-chart-with-labels-basic-config.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/config/basic/chart/bar-chart-with-labels-basic-config.component.ts @@ -44,6 +44,7 @@ import { barChartWithLabelsDefaultSettings, BarChartWithLabelsWidgetSettings } from '@home/components/widget/lib/chart/bar-chart-with-labels-widget.models'; +import { TimeSeriesChartType } from '@home/components/widget/lib/chart/time-series-chart.models'; @Component({ selector: 'tb-bar-chart-with-labels-basic-config', @@ -105,16 +106,30 @@ export class BarChartWithLabelsBasicConfigComponent extends BasicWidgetConfigCom icon: [configData.config.titleIcon, []], iconColor: [configData.config.iconColor, []], + dataZoom: [settings.dataZoom, []], + showBarLabel: [settings.showBarLabel, []], barLabelFont: [settings.barLabelFont, []], barLabelColor: [settings.barLabelColor, []], showBarValue: [settings.showBarValue, []], barValueFont: [settings.barValueFont, []], barValueColor: [settings.barValueColor, []], + showBarBorder: [settings.showBarBorder, []], + barBorderWidth: [settings.barBorderWidth, []], + barBorderRadius: [settings.barBorderRadius, []], + barBackgroundSettings: [settings.barBackgroundSettings, []], + noAggregationBarWidthSettings: [settings.noAggregationBarWidthSettings, []], units: [configData.config.units, []], decimals: [configData.config.decimals, []], + yAxis: [settings.yAxis, []], + xAxis: [settings.xAxis, []], + + thresholds: [settings.thresholds, []], + + animation: [settings.animation, []], + showLegend: [settings.showLegend, []], legendPosition: [settings.legendPosition, []], legendLabelFont: [settings.legendLabelFont, []], @@ -136,6 +151,7 @@ export class BarChartWithLabelsBasicConfigComponent extends BasicWidgetConfigCom cardButtons: [this.getCardButtons(configData.config), []], borderRadius: [configData.config.borderRadius, []], + padding: [settings.padding, []], actions: [configData.config.actions || {}, []] }); @@ -158,6 +174,8 @@ export class BarChartWithLabelsBasicConfigComponent extends BasicWidgetConfigCom this.widgetConfig.config.settings = this.widgetConfig.config.settings || {}; + this.widgetConfig.config.settings.dataZoom = config.dataZoom; + this.widgetConfig.config.settings.showBarLabel = config.showBarLabel; this.widgetConfig.config.settings.barLabelFont = config.barLabelFont; this.widgetConfig.config.settings.barLabelColor = config.barLabelColor; @@ -165,9 +183,22 @@ export class BarChartWithLabelsBasicConfigComponent extends BasicWidgetConfigCom this.widgetConfig.config.settings.barValueFont = config.barValueFont; this.widgetConfig.config.settings.barValueColor = config.barValueColor; + this.widgetConfig.config.settings.showBarBorder = config.showBarBorder; + this.widgetConfig.config.settings.barBorderWidth = config.barBorderWidth; + this.widgetConfig.config.settings.barBorderRadius = config.barBorderRadius; + this.widgetConfig.config.settings.barBackgroundSettings = config.barBackgroundSettings; + this.widgetConfig.config.settings.noAggregationBarWidthSettings = config.noAggregationBarWidthSettings; + this.widgetConfig.config.units = config.units; this.widgetConfig.config.decimals = config.decimals; + this.widgetConfig.config.settings.yAxis = config.yAxis; + this.widgetConfig.config.settings.xAxis = config.xAxis; + + this.widgetConfig.config.settings.thresholds = config.thresholds; + + this.widgetConfig.config.settings.animation = config.animation; + this.widgetConfig.config.settings.showLegend = config.showLegend; this.widgetConfig.config.settings.legendPosition = config.legendPosition; this.widgetConfig.config.settings.legendLabelFont = config.legendLabelFont; @@ -188,13 +219,14 @@ export class BarChartWithLabelsBasicConfigComponent extends BasicWidgetConfigCom this.setCardButtons(config.cardButtons, this.widgetConfig.config); this.widgetConfig.config.borderRadius = config.borderRadius; + this.widgetConfig.config.settings.padding = config.padding; this.widgetConfig.config.actions = config.actions; return this.widgetConfig; } protected validatorTriggers(): string[] { - return ['showTitle', 'showIcon', 'showBarLabel', 'showBarValue', 'showLegend', 'showTooltip', 'tooltipShowDate']; + return ['showTitle', 'showIcon', 'showBarLabel', 'showBarValue', 'showBarBorder', 'showLegend', 'showTooltip', 'tooltipShowDate']; } protected updateValidators(emitEvent: boolean, trigger?: string) { @@ -202,6 +234,7 @@ export class BarChartWithLabelsBasicConfigComponent extends BasicWidgetConfigCom const showIcon: boolean = this.barChartWidgetConfigForm.get('showIcon').value; const showBarLabel: boolean = this.barChartWidgetConfigForm.get('showBarLabel').value; const showBarValue: boolean = this.barChartWidgetConfigForm.get('showBarValue').value; + const showBarBorder: boolean = this.barChartWidgetConfigForm.get('showBarBorder').value; const showLegend: boolean = this.barChartWidgetConfigForm.get('showLegend').value; const showTooltip: boolean = this.barChartWidgetConfigForm.get('showTooltip').value; const tooltipShowDate: boolean = this.barChartWidgetConfigForm.get('tooltipShowDate').value; @@ -248,7 +281,11 @@ export class BarChartWithLabelsBasicConfigComponent extends BasicWidgetConfigCom this.barChartWidgetConfigForm.get('barValueFont').disable(); this.barChartWidgetConfigForm.get('barValueColor').disable(); } - + if (showBarBorder) { + this.barChartWidgetConfigForm.get('barBorderWidth').enable(); + } else { + this.barChartWidgetConfigForm.get('barBorderWidth').disable(); + } if (showLegend) { this.barChartWidgetConfigForm.get('legendPosition').enable(); this.barChartWidgetConfigForm.get('legendLabelFont').enable(); @@ -326,4 +363,6 @@ export class BarChartWithLabelsBasicConfigComponent extends BasicWidgetConfigCom processor.update(Date.now()); return processor.formatted; } + + protected readonly TimeSeriesChartType = TimeSeriesChartType; } diff --git a/ui-ngx/src/app/modules/home/components/widget/config/basic/chart/range-chart-basic-config.component.html b/ui-ngx/src/app/modules/home/components/widget/config/basic/chart/range-chart-basic-config.component.html index 5be56204fa..eb94f9c977 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/basic/chart/range-chart-basic-config.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/config/basic/chart/range-chart-basic-config.component.html @@ -75,6 +75,21 @@ {{ 'widgets.range-chart.data-zoom' | translate }}
+ +
+
widgets.range-chart.range-chart-appearance
+
+
widget-config.units-short
+ + +
+
+
widget-config.decimals-short
+ + + +
{{ 'widgets.range-chart.range-colors' | translate }}
@@ -87,24 +102,155 @@ formControlName="outOfRangeColor">
+
+ + {{ 'widgets.range-chart.show-range-thresholds' | translate }} + + + +
{{ 'widgets.range-chart.fill-area' | translate }}
-
-
widget-config.units-short
- - -
-
-
widget-config.decimals-short
- - +
+
widgets.range-chart.fill-area-opacity
+ + +
+
+
widgets.time-series-chart.series.line.line
+
+ + {{ 'widgets.time-series-chart.series.line.show-line' | translate }} + +
+
+ + {{ 'widgets.time-series-chart.series.line.step-line' | translate }} + + + + + {{ lineSeriesStepTypeTranslations.get(stepType) | translate }} + + + +
+
+ + {{ 'widgets.time-series-chart.series.line.smooth-line' | translate }} + +
+
+
widgets.time-series-chart.line-type
+ + + + {{ timeSeriesLineTypeTranslations.get(lineType) | translate }} + + +
+
+
widgets.time-series-chart.line-width
+ + + +
+
+
+
widgets.time-series-chart.series.point.points
+
+ + {{ 'widgets.time-series-chart.series.point.show-points' | translate }} + +
+
+ +
+ {{ 'widgets.time-series-chart.series.point.point-label' | translate }} +
+
+
+ + + + {{ seriesLabelPositionTranslations.get(position) | translate }} + + + + + + + +
+
+
+ + {{ 'widgets.time-series-chart.series.point.point-label-background' | translate }} + + + +
+
+
widgets.time-series-chart.series.point.point-shape
+ + + + {{ echartsShapeTranslations.get(shape) | translate }} + + + +
+
+
widgets.time-series-chart.series.point.point-size
+ + + +
+
+
+
widgets.time-series-chart.axis.y-axis
+ + +
+
+
widgets.time-series-chart.axis.x-axis
+ + +
+ +
@@ -204,6 +350,9 @@
+ +
widget-config.card-appearance
@@ -223,6 +372,12 @@
+
+
{{ 'widget-config.card-padding' | translate }}
+ + + +
diff --git a/ui-ngx/src/app/modules/home/components/widget/config/basic/chart/range-chart-basic-config.component.ts b/ui-ngx/src/app/modules/home/components/widget/config/basic/chart/range-chart-basic-config.component.ts index 0add69096b..40c9bb9d12 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/basic/chart/range-chart-basic-config.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/config/basic/chart/range-chart-basic-config.component.ts @@ -14,13 +14,19 @@ /// limitations under the License. /// -import { ChangeDetectorRef, Component, Injector } from '@angular/core'; +import { Component, Injector } from '@angular/core'; import { UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms'; import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; import { BasicWidgetConfigComponent } from '@home/components/widget/config/widget-config.component.models'; import { WidgetConfigComponentData } from '@home/models/widget-component.models'; -import { DataKey, legendPositions, legendPositionTranslationMap, WidgetConfig, } from '@shared/models/widget.models'; +import { + DataKey, + Datasource, + legendPositions, + legendPositionTranslationMap, + WidgetConfig, +} from '@shared/models/widget.models'; import { WidgetConfigComponent } from '@home/components/widget/widget-config.component'; import { DataKeyType } from '@shared/models/telemetry/telemetry.models'; import { @@ -38,6 +44,15 @@ import { rangeChartDefaultSettings, RangeChartWidgetSettings } from '@home/components/widget/lib/chart/range-chart-widget.models'; +import { + lineSeriesStepTypes, + lineSeriesStepTypeTranslations, + seriesLabelPositions, + seriesLabelPositionTranslations, + timeSeriesLineTypes, + timeSeriesLineTypeTranslations +} from '@home/components/widget/lib/chart/time-series-chart.models'; +import { echartsShapes, echartsShapeTranslations } from '@home/components/widget/lib/chart/echarts-widget.models'; @Component({ selector: 'tb-range-chart-basic-config', @@ -46,12 +61,39 @@ import { }) export class RangeChartBasicConfigComponent extends BasicWidgetConfigComponent { + public get datasource(): Datasource { + const datasources: Datasource[] = this.rangeChartWidgetConfigForm.get('datasources').value; + if (datasources && datasources.length) { + return datasources[0]; + } else { + return null; + } + } + + lineSeriesStepTypes = lineSeriesStepTypes; + + lineSeriesStepTypeTranslations = lineSeriesStepTypeTranslations; + + timeSeriesLineTypes = timeSeriesLineTypes; + + timeSeriesLineTypeTranslations = timeSeriesLineTypeTranslations; + + seriesLabelPositions = seriesLabelPositions; + + seriesLabelPositionTranslations = seriesLabelPositionTranslations; + + echartsShapes = echartsShapes; + + echartsShapeTranslations = echartsShapeTranslations; + legendPositions = legendPositions; legendPositionTranslationMap = legendPositionTranslationMap; rangeChartWidgetConfigForm: UntypedFormGroup; + pointLabelPreviewFn = this._pointLabelPreviewFn.bind(this); + tooltipValuePreviewFn = this._tooltipValuePreviewFn.bind(this); tooltipDatePreviewFn = this._tooltipDatePreviewFn.bind(this); @@ -90,11 +132,39 @@ export class RangeChartBasicConfigComponent extends BasicWidgetConfigComponent { iconColor: [configData.config.iconColor, []], dataZoom: [settings.dataZoom, []], + + units: [configData.config.units, []], + decimals: [configData.config.decimals, []], rangeColors: [settings.rangeColors, []], outOfRangeColor: [settings.outOfRangeColor, []], + showRangeThresholds: [settings.showRangeThresholds, []], + rangeThreshold: [settings.rangeThreshold, []], fillArea: [settings.fillArea, []], - units: [configData.config.units, []], - decimals: [configData.config.decimals, []], + fillAreaOpacity: [settings.fillAreaOpacity, [Validators.min(0), Validators.max(1)]], + + showLine: [settings.showLine, []], + step: [settings.step, []], + stepType: [settings.stepType, []], + smooth: [settings.smooth, []], + lineType: [settings.lineType, []], + lineWidth: [settings.lineWidth, [Validators.min(0)]], + + showPoints: [settings.showPoints, []], + showPointLabel: [settings.showPointLabel, []], + pointLabelPosition: [settings.pointLabelPosition, []], + pointLabelFont: [settings.pointLabelFont, []], + pointLabelColor: [settings.pointLabelColor, []], + enablePointLabelBackground: [settings.enablePointLabelBackground, []], + pointLabelBackground: [settings.pointLabelBackground, []], + pointShape: [settings.pointShape, []], + pointSize: [settings.pointSize, [Validators.min(0)]], + + yAxis: [settings.yAxis, []], + xAxis: [settings.xAxis, []], + + thresholds: [settings.thresholds, []], + + animation: [settings.animation, []], showLegend: [settings.showLegend, []], legendPosition: [settings.legendPosition, []], @@ -117,6 +187,7 @@ export class RangeChartBasicConfigComponent extends BasicWidgetConfigComponent { cardButtons: [this.getCardButtons(configData.config), []], borderRadius: [configData.config.borderRadius, []], + padding: [settings.padding, []], actions: [configData.config.actions || {}, []] }); @@ -138,12 +209,42 @@ export class RangeChartBasicConfigComponent extends BasicWidgetConfigComponent { this.widgetConfig.config.settings = this.widgetConfig.config.settings || {}; + this.widgetConfig.config.settings.dataZoom = config.dataZoom; + + this.widgetConfig.config.units = config.units; + this.widgetConfig.config.decimals = config.decimals; + this.widgetConfig.config.settings.rangeColors = config.rangeColors; this.widgetConfig.config.settings.outOfRangeColor = config.outOfRangeColor; + this.widgetConfig.config.settings.showRangeThresholds = config.showRangeThresholds; + this.widgetConfig.config.settings.rangeThreshold = config.rangeThreshold; this.widgetConfig.config.settings.fillArea = config.fillArea; - this.widgetConfig.config.units = config.units; - this.widgetConfig.config.decimals = config.decimals; + this.widgetConfig.config.settings.fillAreaOpacity = config.fillAreaOpacity; + + this.widgetConfig.config.settings.showLine = config.showLine; + this.widgetConfig.config.settings.step = config.step; + this.widgetConfig.config.settings.stepType = config.stepType; + this.widgetConfig.config.settings.smooth = config.smooth; + this.widgetConfig.config.settings.lineType = config.lineType; + this.widgetConfig.config.settings.lineWidth = config.lineWidth; + + this.widgetConfig.config.settings.showPoints = config.showPoints; + this.widgetConfig.config.settings.showPointLabel = config.showPointLabel; + this.widgetConfig.config.settings.pointLabelPosition = config.pointLabelPosition; + this.widgetConfig.config.settings.pointLabelFont = config.pointLabelFont; + this.widgetConfig.config.settings.pointLabelColor = config.pointLabelColor; + this.widgetConfig.config.settings.enablePointLabelBackground = config.enablePointLabelBackground; + this.widgetConfig.config.settings.pointLabelBackground = config.pointLabelBackground; + this.widgetConfig.config.settings.pointShape = config.pointShape; + this.widgetConfig.config.settings.pointSize = config.pointSize; + + this.widgetConfig.config.settings.yAxis = config.yAxis; + this.widgetConfig.config.settings.xAxis = config.xAxis; + + this.widgetConfig.config.settings.thresholds = config.thresholds; + + this.widgetConfig.config.settings.animation = config.animation; this.widgetConfig.config.settings.showLegend = config.showLegend; this.widgetConfig.config.settings.legendPosition = config.legendPosition; @@ -165,18 +266,26 @@ export class RangeChartBasicConfigComponent extends BasicWidgetConfigComponent { this.setCardButtons(config.cardButtons, this.widgetConfig.config); this.widgetConfig.config.borderRadius = config.borderRadius; + this.widgetConfig.config.settings.padding = config.padding; this.widgetConfig.config.actions = config.actions; return this.widgetConfig; } protected validatorTriggers(): string[] { - return ['showTitle', 'showIcon', 'showLegend', 'showTooltip', 'tooltipShowDate']; + return ['showTitle', 'showIcon', 'showRangeThresholds', 'fillArea', 'showLine', + 'step', 'showPointLabel', 'enablePointLabelBackground', 'showLegend', 'showTooltip', 'tooltipShowDate']; } protected updateValidators(emitEvent: boolean, trigger?: string) { const showTitle: boolean = this.rangeChartWidgetConfigForm.get('showTitle').value; const showIcon: boolean = this.rangeChartWidgetConfigForm.get('showIcon').value; + const showRangeThresholds: boolean = this.rangeChartWidgetConfigForm.get('showRangeThresholds').value; + const fillArea: boolean = this.rangeChartWidgetConfigForm.get('fillArea').value; + const showLine: boolean = this.rangeChartWidgetConfigForm.get('showLine').value; + const step: boolean = this.rangeChartWidgetConfigForm.get('step').value; + const showPointLabel: boolean = this.rangeChartWidgetConfigForm.get('showPointLabel').value; + const enablePointLabelBackground: boolean = this.rangeChartWidgetConfigForm.get('enablePointLabelBackground').value; const showLegend: boolean = this.rangeChartWidgetConfigForm.get('showLegend').value; const showTooltip: boolean = this.rangeChartWidgetConfigForm.get('showTooltip').value; const tooltipShowDate: boolean = this.rangeChartWidgetConfigForm.get('tooltipShowDate').value; @@ -208,6 +317,54 @@ export class RangeChartBasicConfigComponent extends BasicWidgetConfigComponent { this.rangeChartWidgetConfigForm.get('iconColor').disable(); } + if (showRangeThresholds) { + this.rangeChartWidgetConfigForm.get('rangeThreshold').enable(); + } else { + this.rangeChartWidgetConfigForm.get('rangeThreshold').disable(); + } + + if (fillArea) { + this.rangeChartWidgetConfigForm.get('fillAreaOpacity').enable(); + } else { + this.rangeChartWidgetConfigForm.get('fillAreaOpacity').disable(); + } + + if (showLine) { + this.rangeChartWidgetConfigForm.get('step').enable({emitEvent: false}); + if (step) { + this.rangeChartWidgetConfigForm.get('stepType').enable(); + this.rangeChartWidgetConfigForm.get('smooth').disable(); + } else { + this.rangeChartWidgetConfigForm.get('stepType').disable(); + this.rangeChartWidgetConfigForm.get('smooth').enable(); + } + this.rangeChartWidgetConfigForm.get('lineType').enable(); + this.rangeChartWidgetConfigForm.get('lineWidth').enable(); + } else { + this.rangeChartWidgetConfigForm.get('step').disable({emitEvent: false}); + this.rangeChartWidgetConfigForm.get('stepType').disable(); + this.rangeChartWidgetConfigForm.get('smooth').disable(); + this.rangeChartWidgetConfigForm.get('lineType').disable(); + this.rangeChartWidgetConfigForm.get('lineWidth').disable(); + } + if (showPointLabel) { + this.rangeChartWidgetConfigForm.get('pointLabelPosition').enable(); + this.rangeChartWidgetConfigForm.get('pointLabelFont').enable(); + this.rangeChartWidgetConfigForm.get('pointLabelColor').enable(); + this.rangeChartWidgetConfigForm.get('enablePointLabelBackground').enable({emitEvent: false}); + if (enablePointLabelBackground) { + this.rangeChartWidgetConfigForm.get('pointLabelBackground').enable(); + } else { + this.rangeChartWidgetConfigForm.get('pointLabelBackground').disable(); + } + } else { + this.rangeChartWidgetConfigForm.get('pointLabelPosition').disable(); + this.rangeChartWidgetConfigForm.get('pointLabelFont').disable(); + this.rangeChartWidgetConfigForm.get('pointLabelColor').disable(); + this.rangeChartWidgetConfigForm.get('enablePointLabelBackground').disable({emitEvent: false}); + this.rangeChartWidgetConfigForm.get('pointLabelBackground').disable(); + } + if (showLegend) { this.rangeChartWidgetConfigForm.get('legendPosition').enable(); this.rangeChartWidgetConfigForm.get('legendLabelFont').enable(); @@ -260,6 +417,12 @@ export class RangeChartBasicConfigComponent extends BasicWidgetConfigComponent { config.enableFullscreen = buttons.includes('fullscreen'); } + private _pointLabelPreviewFn(): string { + const units: string = this.rangeChartWidgetConfigForm.get('units').value; + const decimals: number = this.rangeChartWidgetConfigForm.get('decimals').value; + return formatValue(22, decimals, units, false); + } + private _tooltipValuePreviewFn(): string { const units: string = this.rangeChartWidgetConfigForm.get('units').value; const decimals: number = this.rangeChartWidgetConfigForm.get('decimals').value; diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/chart/bar-chart-with-labels-widget.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/chart/bar-chart-with-labels-widget.component.html index 8d660a7884..f48b8def30 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/chart/bar-chart-with-labels-widget.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/chart/bar-chart-with-labels-widget.component.html @@ -15,20 +15,21 @@ limitations under the License. --> -
+
-
+
-
-
{{ legendItem.label }}
+
+
{{ legendKey.label }}
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/chart/bar-chart-with-labels-widget.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/chart/bar-chart-with-labels-widget.component.scss index feb65353f6..ac7229672e 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/chart/bar-chart-with-labels-widget.component.scss +++ b/ui-ngx/src/app/modules/home/components/widget/lib/chart/bar-chart-with-labels-widget.component.scss @@ -19,8 +19,10 @@ position: relative; display: flex; flex-direction: column; - gap: 16px; - padding: 20px 24px 24px 24px; + gap: 8px; + &.overlay { + padding: 20px 24px 24px 24px; + } > div:not(.tb-bar-chart-overlay) { z-index: 1; } @@ -40,7 +42,7 @@ min-height: 0; display: flex; flex-direction: column; - gap: 16px; + gap: 8px; &.legend-top { flex-direction: column-reverse; } @@ -88,8 +90,9 @@ } } &.legend-right, &.legend-left { - gap: 24px; + gap: 16px; .tb-bar-chart-legend { + padding-top: 8px; flex-direction: column-reverse; justify-content: flex-end; align-items: stretch; diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/chart/bar-chart-with-labels-widget.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/chart/bar-chart-with-labels-widget.component.ts index e2779f2821..b840f49e3b 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/chart/bar-chart-with-labels-widget.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/chart/bar-chart-with-labels-widget.component.ts @@ -28,45 +28,18 @@ import { ViewEncapsulation } from '@angular/core'; import { WidgetContext } from '@home/models/widget-component.models'; -import { - backgroundStyle, - ComponentStyle, - DateFormatProcessor, - overlayStyle, - textStyle -} from '@shared/models/widget-settings.models'; -import { ResizeObserver } from '@juggle/resize-observer'; -import { formatValue } from '@core/utils'; +import { backgroundStyle, ComponentStyle, overlayStyle, textStyle } from '@shared/models/widget-settings.models'; import { Observable } from 'rxjs'; import { ImagePipe } from '@shared/pipe/image.pipe'; import { DomSanitizer } from '@angular/platform-browser'; import { barChartWithLabelsDefaultSettings, + barChartWithLabelsTimeSeriesKeySettings, + barChartWithLabelsTimeSeriesSettings, BarChartWithLabelsWidgetSettings } from '@home/components/widget/lib/chart/bar-chart-with-labels-widget.models'; - -import * as echarts from 'echarts/core'; -import { CustomSeriesOption } from 'echarts/charts'; -import { CallbackDataParams, CustomSeriesRenderItem, LabelLayoutOptionCallback } from 'echarts/types/dist/shared'; - -import { - ECharts, - echartsModule, - EChartsOption, - EChartsSeriesItem, - echartsTooltipFormatter, timeAxisBandWidthCalculator, - toNamedData -} from '@home/components/widget/lib/chart/echarts-widget.models'; -import { AggregationType, IntervalMath } from '@shared/models/time/time.models'; - -type BarChartDataItem = EChartsSeriesItem; - -interface BarChartLegendItem { - id: string; - color: string; - label: string; - enabled: boolean; -} +import { TbTimeSeriesChart } from '@home/components/widget/lib/chart/time-series-chart'; +import { DataKey } from '@shared/models/widget.models'; @Component({ selector: 'tb-bar-chart-with-labels-widget', @@ -92,30 +65,14 @@ export class BarChartWithLabelsWidgetComponent implements OnInit, OnDestroy, Aft backgroundStyle$: Observable; overlayStyle: ComponentStyle = {}; + overlayEnabled: boolean; + padding: string; - legendItems: BarChartLegendItem[]; + legendKeys: DataKey[]; legendLabelStyle: ComponentStyle; disabledLegendLabelStyle: ComponentStyle; - private get noAggregation(): boolean { - return this.ctx.defaultSubscription.timeWindowConfig?.aggregation?.type === AggregationType.NONE; - } - - private shapeResize$: ResizeObserver; - - private decimals = 0; - private units = ''; - - private dataItems: BarChartDataItem[] = []; - - private drawChartPending = false; - private barChart: ECharts; - private barChartOptions: EChartsOption; - - private tooltipDateFormat: DateFormatProcessor; - - private barRenderItem: CustomSeriesRenderItem; - private barLabelLayoutCallback: LabelLayoutOptionCallback; + private timeSeriesChart: TbTimeSeriesChart; constructor(private imagePipe: ImagePipe, private sanitizer: DomSanitizer, @@ -127,356 +84,64 @@ export class BarChartWithLabelsWidgetComponent implements OnInit, OnDestroy, Aft this.ctx.$scope.barChartWidget = this; this.settings = {...barChartWithLabelsDefaultSettings, ...this.ctx.settings}; - this.decimals = this.ctx.decimals; - this.units = this.ctx.units; - this.backgroundStyle$ = backgroundStyle(this.settings.background, this.imagePipe, this.sanitizer); this.overlayStyle = overlayStyle(this.settings.background.overlay); + this.overlayEnabled = this.settings.background.overlay.enabled; + this.padding = this.overlayEnabled ? undefined : this.settings.padding; this.showLegend = this.settings.showLegend; if (this.showLegend) { - this.legendItems = []; + this.legendKeys = []; this.legendClass = `legend-${this.settings.legendPosition}`; this.legendLabelStyle = textStyle(this.settings.legendLabelFont); this.disabledLegendLabelStyle = textStyle(this.settings.legendLabelFont); this.legendLabelStyle.color = this.settings.legendLabelColor; } - let counter = 0; - if (this.ctx.datasources.length) { - for (const datasource of this.ctx.datasources) { - const dataKeys = datasource.dataKeys; - for (const dataKey of dataKeys) { - const id = counter++; - const datasourceData = this.ctx.data ? this.ctx.data.find(d => d.dataKey === dataKey) : null; - const namedData = datasourceData?.data ? toNamedData(datasourceData.data) : []; - this.dataItems.push({ - id: id+'', - dataKey, - data: namedData, - enabled: true - }); - if (this.showLegend) { - this.legendItems.push( - { - id: id+'', - label: dataKey.label, - color: dataKey.color, - enabled: true - } - ); - } + const barSettings = barChartWithLabelsTimeSeriesKeySettings(this.settings, this.ctx.decimals); + for (const datasource of this.ctx.datasources) { + const dataKeys = datasource.dataKeys; + for (const dataKey of dataKeys) { + dataKey.settings = barSettings; + if (this.showLegend) { + this.legendKeys.push(dataKey); } } } - - if (this.settings.showTooltip && this.settings.tooltipShowDate) { - this.tooltipDateFormat = DateFormatProcessor.fromSettings(this.ctx.$injector, this.settings.tooltipDateFormat); - } - - const barValueStyle: ComponentStyle = textStyle(this.settings.barValueFont); - delete barValueStyle.lineHeight; - barValueStyle.fontSize = this.settings.barValueFont.size; - barValueStyle.fill = this.settings.barValueColor; - - const barLabelStyle: ComponentStyle = textStyle(this.settings.barLabelFont); - delete barLabelStyle.lineHeight; - barLabelStyle.fontSize = this.settings.barLabelFont.size; - barLabelStyle.fill = this.settings.barLabelColor; - - this.barRenderItem = (params, api) => { - - const time = api.value(0) as number; - let start = api.value(2) as number; - const end = api.value(3) as number; - let interval = end - start; - if (!start || !end || !interval) { - interval = IntervalMath.numberValue(this.ctx.timeWindow.interval); - start = time - interval / 2; - } - const enabledDataItems = this.dataItems.filter(d => d.enabled); - const barInterval = interval / (enabledDataItems.length + 1); - const intervalGap = barInterval / 2; - - const index = enabledDataItems.findIndex(d => d.id === params.seriesId); - const value = api.value(1); - const startTime = start + intervalGap + barInterval * index; - const delta = barInterval; - const lowerLeft = api.coord([startTime, value >= 0 ? value : 0]); - const height = api.size([delta, value])[1]; - const width = api.size([delta, 10])[0]; - - const coordSys: {x: number; y: number; width: number; height: number} = params.coordSys as any; - - const rectShape = echarts.graphic.clipRectByRect({ - x: lowerLeft[0], - y: lowerLeft[1], - width, - height - }, { - x: coordSys.x, - y: coordSys.y, - width: coordSys.width, - height: coordSys.height - }); - - const zeroPos = api.coord([0, 0]); - const labelParts: string[] = []; - if (this.settings.showBarValue) { - const labelValue = formatValue(value, this.decimals, '', false); - labelParts.push(`{value|${labelValue}}`); - } - if (this.settings.showBarLabel) { - labelParts.push(`{label|${params.seriesName}}`); - } - const barLabel = labelParts.join(' '); - return rectShape && { - type: 'rect', - id: time + '', - shape: rectShape, - style: { - fill: api.visual('color'), - text: barLabel, - textPosition: 'insideBottom', - textRotation: Math.PI / 2, - textDistance: 15, - textStrokeWidth: 0, - textAlign: 'left', - textVerticalAlign: 'middle', - rich: { - value: barValueStyle, - label: barLabelStyle - } - }, - focus: 'series', - transition: 'all', - enterFrom: { - style: { opacity: 0 }, - shape: { height: 0, y: zeroPos[1] } - } - }; - }; - - this.barLabelLayoutCallback = (params) => { - if (params.rect.width - params.labelRect.width < 2) { - return { - y: '100000%', - }; - } else { - return { - hideOverlap: true - }; - } - }; } ngAfterViewInit() { - if (this.drawChartPending) { - this.drawChart(); - } + const settings = barChartWithLabelsTimeSeriesSettings(this.settings); + this.timeSeriesChart = new TbTimeSeriesChart(this.ctx, settings, this.chartShape.nativeElement, this.renderer); } ngOnDestroy() { - if (this.shapeResize$) { - this.shapeResize$.disconnect(); - } - if (this.barChart) { - this.barChart.dispose(); + if (this.timeSeriesChart) { + this.timeSeriesChart.destroy(); } } public onInit() { const borderRadius = this.ctx.$widgetElement.css('borderRadius'); this.overlayStyle = {...this.overlayStyle, ...{borderRadius}}; - if (this.chartShape) { - this.drawChart(); - } else { - this.drawChartPending = true; - } this.cd.detectChanges(); } public onDataUpdated() { - for (const item of this.dataItems) { - const datasourceData = this.ctx.data ? this.ctx.data.find(d => d.dataKey === item.dataKey) : null; - item.data = datasourceData?.data ? toNamedData(datasourceData.data) : []; - } - if (this.barChart) { - (this.barChartOptions.xAxis as any).min = this.ctx.defaultSubscription.timeWindow.minTime; - (this.barChartOptions.xAxis as any).max = this.ctx.defaultSubscription.timeWindow.maxTime; - (this.barChartOptions.xAxis as any).tbTimeWindow = this.ctx.defaultSubscription.timeWindow; - this.barChartOptions.series = this.updateSeries(); - this.barChart.setOption(this.barChartOptions); + if (this.timeSeriesChart) { + this.timeSeriesChart.update(); } } - private updateSeries(): Array { - const series: Array = []; - for (const item of this.dataItems) { - if (item.enabled) { - const seriesOption: CustomSeriesOption = { - type: 'custom', - id: item.id, - name: item.dataKey.label, - color: item.dataKey.color, - data: item.data, - renderItem: this.barRenderItem, - labelLayout: this.barLabelLayoutCallback, - dimensions: [ - {name: 'intervalStart', type: 'number'}, - {name: 'intervalEnd', type: 'number'} - ], - encode: { - intervalStart: 2, - intervalEnd: 3 - } - }; - series.push(seriesOption); - } - } - return series; + public onLegendKeyEnter(key: DataKey) { + this.timeSeriesChart.keyEnter(key); } - - public onLegendItemEnter(item: BarChartLegendItem) { - this.barChart.dispatchAction({ - type: 'highlight', - seriesId: item.id - }); - } - - public onLegendItemLeave(item: BarChartLegendItem) { - this.barChart.dispatchAction({ - type: 'downplay', - seriesId: item.id - }); - } - - public toggleLegendItem(item: BarChartLegendItem) { - const enable = !item.enabled; - const dataItem = this.dataItems.find(d => d.id === item.id); - if (dataItem) { - dataItem.enabled = enable; - if (!enable) { - this.barChart.dispatchAction({ - type: 'downplay', - seriesId: item.id - }); - } - this.barChartOptions.series = this.updateSeries(); - this.barChart.setOption(this.barChartOptions, {replaceMerge: ['series']}); - item.enabled = enable; - if (enable) { - this.barChart.dispatchAction({ - type: 'highlight', - seriesId: item.id - }); - } - } + public onLegendKeyLeave(key: DataKey) { + this.timeSeriesChart.keyLeave(key); } - private drawChart() { - echartsModule.init(); - this.barChart = echarts.init(this.chartShape.nativeElement, null, { - renderer: 'canvas', - }); - this.barChartOptions = { - tooltip: { - trigger: 'axis', - confine: true, - appendTo: 'body', - axisPointer: { - type: 'shadow' - }, - formatter: (params: CallbackDataParams[]) => { - if (this.settings.showTooltip) { - const focusedSeriesIndex = this.focusedSeriesIndex(); - return echartsTooltipFormatter(this.renderer, this.tooltipDateFormat, - this.settings, params, this.decimals, this.units, focusedSeriesIndex, null, - this.noAggregation ? null : this.ctx.timeWindow.interval); - } else { - return undefined; - } - }, - padding: [8, 12], - backgroundColor: this.settings.tooltipBackgroundColor, - borderWidth: 0, - extraCssText: `line-height: 1; backdrop-filter: blur(${this.settings.tooltipBackgroundBlur}px);` - }, - grid: { - containLabel: true, - top: '30', - left: 0, - right: 0, - bottom: 0 - }, - xAxis: { - type: 'time', - scale: true, - axisTick: { - show: false - }, - axisLabel: { - hideOverlap: true, - fontSize: 10 - }, - axisLine: { - onZero: false - }, - min: this.ctx.defaultSubscription.timeWindow.minTime, - max: this.ctx.defaultSubscription.timeWindow.maxTime, - bandWidthCalculator: timeAxisBandWidthCalculator - }, - yAxis: { - type: 'value', - axisLabel: { - formatter: (value: any) => formatValue(value, this.decimals, this.units, false) - } - } - }; - - (this.barChartOptions.xAxis as any).tbTimeWindow = this.ctx.defaultSubscription.timeWindow; - - this.barChartOptions.series = this.updateSeries(); - - this.barChart.setOption(this.barChartOptions); - - this.shapeResize$ = new ResizeObserver(() => { - this.onResize(); - }); - this.shapeResize$.observe(this.chartShape.nativeElement); - this.onResize(); - } - - private focusedSeriesIndex(): number { - let index = - 1; - const views: any[] = (this.barChart as any)._chartsViews; - if (views) { - const hasBlurredView = !!views.find(view => { - const graphicEls: any[] = view._data._graphicEls; - return !!graphicEls.find(el => el?.currentStates.includes('blur')); - }); - if (hasBlurredView) { - const focusedView = views.find(view => { - const graphicEls: any[] = view._data._graphicEls; - return !!graphicEls.find(el => !el?.currentStates.includes('blur')); - }); - if (focusedView) { - index = !!focusedView._model ? - focusedView._model.seriesIndex : (!!focusedView.__model ? focusedView.__model.seriesIndex : -1); - } - } - } - return index; - } - - private onResize() { - const width = this.barChart.getWidth(); - const height = this.barChart.getHeight(); - const shapeWidth = this.chartShape.nativeElement.offsetWidth; - const shapeHeight = this.chartShape.nativeElement.offsetHeight; - if (width !== shapeWidth || height !== shapeHeight) { - this.barChart.resize(); - } + public toggleLegendKey(key: DataKey) { + this.timeSeriesChart.toggleKey(key); } } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/chart/bar-chart-with-labels-widget.models.ts b/ui-ngx/src/app/modules/home/components/widget/lib/chart/bar-chart-with-labels-widget.models.ts index 99ea088c92..73ecad5264 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/chart/bar-chart-with-labels-widget.models.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/chart/bar-chart-with-labels-widget.models.ts @@ -14,25 +14,64 @@ /// limitations under the License. /// -import { BackgroundSettings, BackgroundType, customDateFormat, Font } from '@shared/models/widget-settings.models'; +import { + BackgroundSettings, + BackgroundType, + ComponentStyle, + customDateFormat, + Font, + textStyle +} from '@shared/models/widget-settings.models'; import { LegendPosition } from '@shared/models/widget.models'; import { EChartsTooltipWidgetSettings } from '@home/components/widget/lib/chart/echarts-widget.models'; +import { DeepPartial } from '@shared/models/common'; +import { + defaultTimeSeriesChartXAxisSettings, + defaultTimeSeriesChartYAxisSettings, + SeriesFillSettings, + SeriesFillType, + timeSeriesChartAnimationDefaultSettings, + TimeSeriesChartAnimationSettings, + TimeSeriesChartKeySettings, + timeSeriesChartNoAggregationBarWidthDefaultSettings, + TimeSeriesChartNoAggregationBarWidthSettings, + TimeSeriesChartSeriesType, + TimeSeriesChartSettings, + TimeSeriesChartThreshold, + TimeSeriesChartXAxisSettings, + TimeSeriesChartYAxisSettings +} from '@home/components/widget/lib/chart/time-series-chart.models'; +import { CallbackDataParams, LabelLayoutOptionCallbackParams } from 'echarts/types/dist/shared'; +import { formatValue, mergeDeep } from '@core/utils'; +import { LabelLayoutOption } from 'echarts/types/src/util/types'; export interface BarChartWithLabelsWidgetSettings extends EChartsTooltipWidgetSettings { + dataZoom: boolean; showBarLabel: boolean; barLabelFont: Font; barLabelColor: string; showBarValue: boolean; barValueFont: Font; barValueColor: string; + showBarBorder: boolean; + barBorderWidth: number; + barBorderRadius: number; + barBackgroundSettings: SeriesFillSettings; + noAggregationBarWidthSettings: TimeSeriesChartNoAggregationBarWidthSettings; + yAxis: TimeSeriesChartYAxisSettings; + xAxis: TimeSeriesChartXAxisSettings; + animation: TimeSeriesChartAnimationSettings; + thresholds: TimeSeriesChartThreshold[]; showLegend: boolean; legendPosition: LegendPosition; legendLabelFont: Font; legendLabelColor: string; background: BackgroundSettings; + padding: string; } export const barChartWithLabelsDefaultSettings: BarChartWithLabelsWidgetSettings = { + dataZoom: false, showBarLabel: true, barLabelFont: { family: 'Roboto', @@ -53,6 +92,28 @@ export const barChartWithLabelsDefaultSettings: BarChartWithLabelsWidgetSettings lineHeight: '12px' }, barValueColor: 'rgba(0, 0, 0, 0.76)', + showBarBorder: false, + barBorderWidth: 2, + barBorderRadius: 0, + barBackgroundSettings: { + type: SeriesFillType.none, + opacity: 0.4, + gradient: { + start: 100, + end: 0 + } + }, + noAggregationBarWidthSettings: mergeDeep({} as TimeSeriesChartNoAggregationBarWidthSettings, + timeSeriesChartNoAggregationBarWidthDefaultSettings), + yAxis: mergeDeep({} as TimeSeriesChartYAxisSettings, + defaultTimeSeriesChartYAxisSettings, + { id: 'default', order: 0, showLine: false, showTicks: false } as TimeSeriesChartYAxisSettings), + xAxis: mergeDeep({} as TimeSeriesChartXAxisSettings, + defaultTimeSeriesChartXAxisSettings, + {showTicks: false, showSplitLines: false} as TimeSeriesChartXAxisSettings), + animation: mergeDeep({} as TimeSeriesChartAnimationSettings, + timeSeriesChartAnimationDefaultSettings), + thresholds: [], showLegend: true, legendPosition: LegendPosition.top, legendLabelFont: { @@ -96,5 +157,89 @@ export const barChartWithLabelsDefaultSettings: BarChartWithLabelsWidgetSettings color: 'rgba(255,255,255,0.72)', blur: 3 } - } + }, + padding: '12px' +}; + +export const barChartWithLabelsTimeSeriesSettings = (settings: BarChartWithLabelsWidgetSettings): DeepPartial => ({ + dataZoom: settings.dataZoom, + yAxes: { + default: settings.yAxis + }, + xAxis: settings.xAxis, + barWidthSettings: { + barGap: 0, + intervalGap: 0.5 + }, + noAggregationBarWidthSettings: settings.noAggregationBarWidthSettings, + animation: settings.animation, + thresholds: settings.thresholds, + showTooltip: settings.showTooltip, + tooltipValueFont: settings.tooltipValueFont, + tooltipValueColor: settings.tooltipValueColor, + tooltipShowDate: settings.tooltipShowDate, + tooltipDateInterval: settings.tooltipDateInterval, + tooltipDateFormat: settings.tooltipDateFormat, + tooltipDateFont: settings.tooltipDateFont, + tooltipDateColor: settings.tooltipDateColor, + tooltipBackgroundColor: settings.tooltipBackgroundColor, + tooltipBackgroundBlur: settings.tooltipBackgroundBlur, + tooltipShowFocusedSeries: true +}); + +export const barChartWithLabelsTimeSeriesKeySettings = (settings: BarChartWithLabelsWidgetSettings, + decimals: number): DeepPartial => { + const barValueStyle: ComponentStyle = textStyle(settings.barValueFont); + delete barValueStyle.lineHeight; + barValueStyle.fontSize = settings.barValueFont.size; + barValueStyle.fill = settings.barValueColor; + + const barLabelStyle: ComponentStyle = textStyle(settings.barLabelFont); + delete barLabelStyle.lineHeight; + barLabelStyle.fontSize = settings.barLabelFont.size; + barLabelStyle.fill = settings.barLabelColor; + return { + type: TimeSeriesChartSeriesType.bar, + barSettings: { + showBorder: settings.showBarBorder, + borderWidth: settings.barBorderWidth, + borderRadius: settings.barBorderRadius, + backgroundSettings: settings.barBackgroundSettings, + showLabel: settings.showBarLabel || settings.showBarValue, + labelPosition: 'insideBottom', + labelFormatter: (params: CallbackDataParams): string => { + const labelParts: string[] = []; + if (settings.showBarValue) { + const labelValue = formatValue(params.value[1], decimals, '', false); + labelParts.push(`{value|${labelValue}}`); + } + if (settings.showBarLabel) { + labelParts.push(`{label|${params.seriesName}}`); + } + return labelParts.join(' '); + }, + labelLayout: (params: LabelLayoutOptionCallbackParams): LabelLayoutOption => { + if (params.rect.width - params.labelRect.width < 2) { + return { + y: '100000%', + }; + } else { + return { + hideOverlap: true + }; + } + }, + additionalLabelOption: { + textRotation: Math.PI / 2, + textDistance: 15, + textStrokeWidth: 0, + textAlign: 'left', + textVerticalAlign: 'middle', + rich: { + value: barValueStyle, + label: barLabelStyle + } + } + } + }; }; diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/chart/echarts-widget.models.ts b/ui-ngx/src/app/modules/home/components/widget/lib/chart/echarts-widget.models.ts index 816f45a7e8..81ffb457b9 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/chart/echarts-widget.models.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/chart/echarts-widget.models.ts @@ -17,7 +17,7 @@ import * as echarts from 'echarts/core'; import AxisModel from 'echarts/types/src/coord/cartesian/AxisModel'; import { estimateLabelUnionRect } from 'echarts/lib/coord/axisHelper'; -import { formatValue, isDefinedAndNotNull } from '@core/utils'; +import { formatValue, isDefinedAndNotNull, isNumber } from '@core/utils'; import { DataZoomComponent, DataZoomComponentOption, @@ -55,6 +55,7 @@ import { DateFormatProcessor, DateFormatSettings, Font } from '@shared/models/wi import GlobalModel from 'echarts/types/src/model/Global'; import Axis2D from 'echarts/types/src/coord/cartesian/Axis2D'; import SeriesModel from 'echarts/types/src/model/Series'; +import { MarkLine2DDataItemOption } from 'echarts/types/src/component/marker/MarkLineModel'; class EChartsModule { private initialized = false; @@ -110,6 +111,51 @@ export type EChartsSeriesItem = { decimals?: number; }; +export enum EChartsShape { + emptyCircle = 'emptyCircle', + circle = 'circle', + rect = 'rect', + roundRect = 'roundRect', + triangle = 'triangle', + diamond = 'diamond', + pin = 'pin', + arrow = 'arrow', + none = 'none' +} + +export const echartsShapes = Object.keys(EChartsShape) as EChartsShape[]; + +export const echartsShapeTranslations = new Map( + [ + [EChartsShape.emptyCircle, 'widgets.time-series-chart.shape-empty-circle'], + [EChartsShape.circle, 'widgets.time-series-chart.shape-circle'], + [EChartsShape.rect, 'widgets.time-series-chart.shape-rect'], + [EChartsShape.roundRect, 'widgets.time-series-chart.shape-round-rect'], + [EChartsShape.triangle, 'widgets.time-series-chart.shape-triangle'], + [EChartsShape.diamond, 'widgets.time-series-chart.shape-diamond'], + [EChartsShape.pin, 'widgets.time-series-chart.shape-pin'], + [EChartsShape.arrow, 'widgets.time-series-chart.shape-arrow'], + [EChartsShape.none, 'widgets.time-series-chart.shape-none'] + ] +); + +type EChartsShapeOffsetFunction = (size: number) => number; + +export const timeSeriesChartShapeOffsetFunctions = new Map( + [ + [EChartsShape.emptyCircle, size => size / 2 + 1], + [EChartsShape.circle, size => size / 2], + [EChartsShape.rect, size => size / 2], + [EChartsShape.roundRect, size => size / 2], + [EChartsShape.triangle, size => size / 2], + [EChartsShape.diamond, size => size / 2], + [EChartsShape.pin, size => size], + [EChartsShape.arrow, () => 0], + [EChartsShape.none, () => 0], + ] +); + + export const timeAxisBandWidthCalculator: TimeAxisBandWidthCalculator = (model) => { let interval: number; const axisOption = model.option; @@ -211,7 +257,23 @@ export const measureXAxisNameHeight = (chart: ECharts, name: string): number => return 0; }; -export const measureThresholdLabelOffset = (chart: ECharts, axisId: string, thresholdId: string, value: any): [number, number] => { +const measureSymbolOffset = (symbol: string, symbolSize: any): number => { + if (isNumber(symbolSize)) { + if (symbol) { + const offsetFunction = timeSeriesChartShapeOffsetFunctions.get(symbol as EChartsShape); + if (offsetFunction) { + return offsetFunction(symbolSize); + } else { + return symbolSize / 2; + } + } + } else { + return 0; + } +} + +export const measureThresholdOffset = (chart: ECharts, axisId: string, thresholdId: string, value: any): [number, number] => { + const offset: [number, number] = [0,0]; const axis = getYAxis(chart, axisId); if (axis && !axis.scale.isBlank()) { const extent = axis.scale.getExtent(); @@ -220,6 +282,16 @@ export const measureThresholdLabelOffset = (chart: ECharts, axisId: string, thre if (models?.length) { const lineSeriesModel = models[0] as SeriesModel; const markLineModel = lineSeriesModel.getModel('markLine'); + const dataOption = markLineModel.get('data'); + for (const dataItemOption of dataOption) { + const dataItem = dataItemOption as MarkLine2DDataItemOption; + const start = dataItem[0]; + const startOffset = measureSymbolOffset(start.symbol, start.symbolSize); + offset[0] = Math.max(offset[0], startOffset); + const end = dataItem[1]; + const endOffset = measureSymbolOffset(end.symbol, end.symbolSize); + offset[1] = Math.max(offset[1], endOffset); + } const labelPosition = markLineModel.get(['label', 'position']); if (labelPosition === 'start' || labelPosition === 'end') { const labelModel = markLineModel.getModel('label'); @@ -239,23 +311,38 @@ export const measureThresholdLabelOffset = (chart: ECharts, axisId: string, thre } } if (!textWidth) { - return [0,0]; + return offset; } const distanceOpt = markLineModel.get(['label', 'distance']); let distance = 5; if (distanceOpt) { distance = typeof distanceOpt === 'number' ? distanceOpt : distanceOpt[0]; } - const offset = distance + textWidth; + const paddingOpt = markLineModel.get(['label', 'padding']); + let leftPadding = 0; + let rightPadding = 0; + if (paddingOpt) { + if (Array.isArray(paddingOpt)) { + if (paddingOpt.length === 4) { + leftPadding = paddingOpt[3]; + rightPadding = paddingOpt[1]; + } else if (paddingOpt.length === 2) { + leftPadding = rightPadding = paddingOpt[1]; + } + } else { + leftPadding = rightPadding = paddingOpt; + } + } + const textOffset = distance + textWidth + leftPadding + rightPadding; if (labelPosition === 'start') { - return [offset, 0]; + offset[0] = Math.max(offset[0], textOffset); } else { - return [0, offset]; + offset[1] = Math.max(offset[1], textOffset); } } } } - return [0,0]; + return offset; }; export const getAxisExtent = (chart: ECharts, axisId: string): [number, number] => { @@ -266,6 +353,48 @@ export const getAxisExtent = (chart: ECharts, axisId: string): [number, number] return [0,0]; }; +let componentBlurredKey: string; + +const isBlurred = (model: SeriesModel): boolean => { + if (!componentBlurredKey) { + const innerKeys = Object.keys(model).filter(k => k.startsWith('__ec_inner_')); + for (const k of innerKeys) { + const obj = model[k]; + if (obj.hasOwnProperty('isBlured')) { + componentBlurredKey = k; + break; + } + } + } + if (componentBlurredKey) { + const obj = model[componentBlurredKey]; + return !!obj?.isBlured; + } else { + return false; + } +}; + +export const getFocusedSeriesIndex = (chart: ECharts): number => { + const model: GlobalModel = (chart as any).getModel(); + const models = model.queryComponents({mainType: 'series'}); + if (models) { + let hasBlurred = false; + let notBlurredIndex = -1; + for (const _model of models) { + const seriesModel = _model as SeriesModel; + const blurred = isBlurred(seriesModel); + if (!blurred) { + notBlurredIndex = seriesModel.seriesIndex; + } + hasBlurred = blurred || hasBlurred; + } + if (hasBlurred) { + return notBlurredIndex; + } + } + return -1; +}; + export const toNamedData = (data: DataSet): NamedDataSet => { if (!data?.length) { return []; @@ -304,6 +433,7 @@ export const tooltipTriggerTranslationMap = new Map -
+
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/chart/range-chart-widget.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/chart/range-chart-widget.component.scss index de2ca546fc..d956173281 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/chart/range-chart-widget.component.scss +++ b/ui-ngx/src/app/modules/home/components/widget/lib/chart/range-chart-widget.component.scss @@ -19,8 +19,10 @@ position: relative; display: flex; flex-direction: column; - gap: 16px; - padding: 20px 24px 24px 24px; + gap: 8px; + &.overlay { + padding: 20px 24px 24px 24px; + } > div:not(.tb-range-chart-overlay) { z-index: 1; } @@ -40,7 +42,7 @@ min-height: 0; display: flex; flex-direction: column; - gap: 16px; + gap: 8px; &.legend-top { flex-direction: column-reverse; } @@ -88,8 +90,9 @@ } } &.legend-right, &.legend-left { - gap: 24px; + gap: 16px; .tb-range-chart-legend { + padding-top: 8px; flex-direction: column-reverse; justify-content: flex-end; align-items: stretch; diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/chart/range-chart-widget.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/chart/range-chart-widget.component.ts index 1ef756f1f7..9d89154111 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/chart/range-chart-widget.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/chart/range-chart-widget.component.ts @@ -30,144 +30,24 @@ import { import { WidgetContext } from '@home/models/widget-component.models'; import { backgroundStyle, - ColorRange, ComponentStyle, - DateFormatProcessor, - filterIncludingColorRanges, getDataKey, overlayStyle, - sortedColorRange, textStyle } from '@shared/models/widget-settings.models'; -import { ResizeObserver } from '@juggle/resize-observer'; -import * as echarts from 'echarts/core'; -import { formatValue, isDefinedAndNotNull, isNumber } from '@core/utils'; -import { rangeChartDefaultSettings, RangeChartWidgetSettings } from './range-chart-widget.models'; +import { isDefinedAndNotNull } from '@core/utils'; +import { + rangeChartDefaultSettings, + rangeChartTimeSeriesKeySettings, + rangeChartTimeSeriesSettings, + RangeChartWidgetSettings, + RangeItem, + toRangeItems +} from './range-chart-widget.models'; import { Observable } from 'rxjs'; import { ImagePipe } from '@shared/pipe/image.pipe'; import { DomSanitizer } from '@angular/platform-browser'; -import { - ECharts, - echartsModule, - EChartsOption, - echartsTooltipFormatter, timeAxisBandWidthCalculator, - toNamedData -} from '@home/components/widget/lib/chart/echarts-widget.models'; -import { CallbackDataParams } from 'echarts/types/dist/shared'; -import { AggregationType } from '@shared/models/time/time.models'; - -interface VisualPiece { - lt?: number; - gt?: number; - lte?: number; - gte?: number; - value?: number; - color?: string; -} - -interface RangeItem { - index: number; - from?: number; - to?: number; - piece: VisualPiece; - color: string; - label: string; - visible: boolean; - enabled: boolean; -} - -const rangeItemLabel = (from?: number, to?: number): string => { - if (isNumber(from) && isNumber(to)) { - if (from === to) { - return `${from}`; - } else { - return `${from} - ${to}`; - } - } else if (isNumber(from)) { - return `≥ ${from}`; - } else if (isNumber(to)) { - return `< ${to}`; - } else { - return null; - } -}; - -const toVisualPiece = (color: string, from?: number, to?: number): VisualPiece => { - const piece: VisualPiece = { - color - }; - if (isNumber(from) && isNumber(to)) { - if (from === to) { - piece.value = from; - } else { - piece.gte = from; - piece.lt = to; - } - } else if (isNumber(from)) { - piece.gte = from; - } else if (isNumber(to)) { - piece.lt = to; - } - return piece; -}; - -const toRangeItems = (colorRanges: Array): RangeItem[] => { - const rangeItems: RangeItem[] = []; - let counter = 0; - const ranges = sortedColorRange(filterIncludingColorRanges(colorRanges)).filter(r => isNumber(r.from) || isNumber(r.to)); - for (let i = 0; i < ranges.length; i++) { - const range = ranges[i]; - let from = range.from; - const to = range.to; - if (i > 0) { - const prevRange = ranges[i - 1]; - if (isNumber(prevRange.to) && isNumber(from) && from < prevRange.to) { - from = prevRange.to; - } - } - rangeItems.push( - { - index: counter++, - color: range.color, - enabled: true, - visible: true, - from, - to, - label: rangeItemLabel(from, to), - piece: toVisualPiece(range.color, from, to) - } - ); - if (!isNumber(from) || !isNumber(to)) { - const value = !isNumber(from) ? to : from; - rangeItems.push( - { - index: counter++, - color: 'transparent', - enabled: true, - visible: false, - label: '', - piece: { gt: value - 0.000000001, lt: value + 0.000000001, color: 'transparent'} - } - ); - } - } - return rangeItems; -}; - -const getMarkPoints = (ranges: Array): number[] => { - const points = new Set(); - for (const range of ranges) { - if (range.visible) { - if (isNumber(range.from)) { - points.add(range.from); - } - if (isNumber(range.to)) { - points.add(range.to); - } - } - } - return Array.from(points).sort(); -}; +import { TbTimeSeriesChart } from '@home/components/widget/lib/chart/time-series-chart'; @Component({ selector: 'tb-range-chart-widget', @@ -193,28 +73,19 @@ export class RangeChartWidgetComponent implements OnInit, OnDestroy, AfterViewIn backgroundStyle$: Observable; overlayStyle: ComponentStyle = {}; + overlayEnabled: boolean; + padding: string; legendLabelStyle: ComponentStyle; disabledLegendLabelStyle: ComponentStyle; visibleRangeItems: RangeItem[]; - private get noAggregation(): boolean { - return this.ctx.defaultSubscription.timeWindowConfig?.aggregation?.type === AggregationType.NONE; - } - - private rangeItems: RangeItem[]; - - private shapeResize$: ResizeObserver; - private decimals = 0; private units = ''; - private drawChartPending = false; - private rangeChart: ECharts; - private rangeChartOptions: EChartsOption; - private selectedRanges: {[key: number]: boolean} = {}; + private rangeItems: RangeItem[]; - private tooltipDateFormat: DateFormatProcessor; + private timeSeriesChart: TbTimeSeriesChart; constructor(private imagePipe: ImagePipe, private sanitizer: DomSanitizer, @@ -235,16 +106,17 @@ export class RangeChartWidgetComponent implements OnInit, OnDestroy, AfterViewIn if (dataKey?.units) { this.units = dataKey.units; } - + if (dataKey) { + dataKey.settings = rangeChartTimeSeriesKeySettings(this.settings); + } this.backgroundStyle$ = backgroundStyle(this.settings.background, this.imagePipe, this.sanitizer); this.overlayStyle = overlayStyle(this.settings.background.overlay); + this.overlayEnabled = this.settings.background.overlay.enabled; + this.padding = this.overlayEnabled ? undefined : this.settings.padding; this.rangeItems = toRangeItems(this.settings.rangeColors); this.visibleRangeItems = this.rangeItems.filter(item => item.visible); - for (const range of this.rangeItems) { - this.selectedRanges[range.index] = true; - } this.showLegend = this.settings.showLegend && !!this.rangeItems.length; @@ -254,193 +126,33 @@ export class RangeChartWidgetComponent implements OnInit, OnDestroy, AfterViewIn this.disabledLegendLabelStyle = textStyle(this.settings.legendLabelFont); this.legendLabelStyle.color = this.settings.legendLabelColor; } - - if (this.settings.showTooltip && this.settings.tooltipShowDate) { - this.tooltipDateFormat = DateFormatProcessor.fromSettings(this.ctx.$injector, this.settings.tooltipDateFormat); - } } ngAfterViewInit() { - if (this.drawChartPending) { - this.drawChart(); - } + const settings = rangeChartTimeSeriesSettings(this.settings, this.rangeItems, this.decimals, this.units); + this.timeSeriesChart = new TbTimeSeriesChart(this.ctx, settings, this.chartShape.nativeElement, this.renderer); } ngOnDestroy() { - if (this.shapeResize$) { - this.shapeResize$.disconnect(); - } - if (this.rangeChart) { - this.rangeChart.dispose(); + if (this.timeSeriesChart) { + this.timeSeriesChart.destroy(); } } public onInit() { const borderRadius = this.ctx.$widgetElement.css('borderRadius'); this.overlayStyle = {...this.overlayStyle, ...{borderRadius}}; - if (this.chartShape) { - this.drawChart(); - } else { - this.drawChartPending = true; - } this.cd.detectChanges(); } public onDataUpdated() { - if (this.rangeChart) { - this.rangeChart.setOption({ - xAxis: { - min: this.ctx.defaultSubscription.timeWindow.minTime, - max: this.ctx.defaultSubscription.timeWindow.maxTime, - tbTimeWindow: this.ctx.defaultSubscription.timeWindow - }, - series: [ - {data: this.ctx.data?.length ? toNamedData(this.ctx.data[0].data) : []} - ], - visualMap: { - selected: this.selectedRanges - } - }); + if (this.timeSeriesChart) { + this.timeSeriesChart.update(); } } public toggleRangeItem(item: RangeItem) { item.enabled = !item.enabled; - this.selectedRanges[item.index] = item.enabled; - this.rangeChart.dispatchAction({ - type: 'selectDataRange', - selected: this.selectedRanges - }); - } - - private drawChart() { - echartsModule.init(); - const dataKey = getDataKey(this.ctx.datasources); - this.rangeChart = echarts.init(this.chartShape.nativeElement, null, { - renderer: 'canvas', - }); - this.rangeChartOptions = { - tooltip: { - trigger: 'axis', - confine: true, - appendTo: 'body', - axisPointer: { - type: 'shadow' - }, - formatter: (params: CallbackDataParams[]) => - this.settings.showTooltip ? echartsTooltipFormatter(this.renderer, this.tooltipDateFormat, - this.settings, params, this.decimals, this.units, 0, null, - this.noAggregation ? null : this.ctx.timeWindow.interval) : undefined, - padding: [8, 12], - backgroundColor: this.settings.tooltipBackgroundColor, - borderWidth: 0, - extraCssText: `line-height: 1; backdrop-filter: blur(${this.settings.tooltipBackgroundBlur}px);` - }, - grid: { - containLabel: true, - top: '30', - left: 0, - right: 0, - bottom: this.settings.dataZoom ? 60 : 0 - }, - xAxis: { - type: 'time', - axisTick: { - show: true - }, - axisLabel: { - hideOverlap: true, - fontSize: 10 - }, - axisLine: { - onZero: false - }, - min: this.ctx.defaultSubscription.timeWindow.minTime, - max: this.ctx.defaultSubscription.timeWindow.maxTime, - bandWidthCalculator: timeAxisBandWidthCalculator - }, - yAxis: { - type: 'value', - axisLabel: { - formatter: (value: any) => formatValue(value, this.decimals, this.units, false) - } - }, - series: [{ - type: 'line', - name: dataKey?.label, - smooth: false, - showSymbol: false, - animation: true, - areaStyle: this.settings.fillArea ? {} : undefined, - data: this.ctx.data?.length ? toNamedData(this.ctx.data[0].data) : [], - markLine: this.rangeItems.length ? { - animation: true, - symbol: ['circle', 'arrow'], - symbolSize: [5, 7], - lineStyle: { - width: 1, - type: [3, 3], - color: '#37383b' - }, - label: { - position: 'insideEndTop', - color: '#37383b', - backgroundColor: 'rgba(255,255,255,0.56)', - padding: [4, 5], - borderRadius: 4, - formatter: params => formatValue(params.value, this.decimals, this.units, false) - }, - emphasis: { - disabled: true - }, - data: getMarkPoints(this.rangeItems).map(point => ({ yAxis: point })) - } : undefined - }], - dataZoom: [ - { - type: 'inside', - disabled: !this.settings.dataZoom - }, - { - type: 'slider', - show: this.settings.dataZoom, - showDetail: false, - right: 10 - } - ], - visualMap: { - show: false, - type: 'piecewise', - selected: this.selectedRanges, - dimension: 1, - pieces: this.rangeItems.map(item => item.piece), - outOfRange: { - color: this.settings.outOfRangeColor - }, - inRange: !this.rangeItems.length ? { - color: this.settings.outOfRangeColor - } : undefined - } - }; - - (this.rangeChartOptions.xAxis as any).tbTimeWindow = this.ctx.defaultSubscription.timeWindow; - - this.rangeChart.setOption(this.rangeChartOptions); - - this.shapeResize$ = new ResizeObserver(() => { - this.onResize(); - }); - this.shapeResize$.observe(this.chartShape.nativeElement); - this.onResize(); - } - - private onResize() { - const width = this.rangeChart.getWidth(); - const height = this.rangeChart.getHeight(); - const shapeWidth = this.chartShape.nativeElement.offsetWidth; - const shapeHeight = this.chartShape.nativeElement.offsetHeight; - if (width !== shapeWidth || height !== shapeHeight) { - this.rangeChart.resize(); - } + this.timeSeriesChart.toggleVisualMapRange(item.index); } } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/chart/range-chart-widget.models.ts b/ui-ngx/src/app/modules/home/components/widget/lib/chart/range-chart-widget.models.ts index 31905e70ba..e04dccf74d 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/chart/range-chart-widget.models.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/chart/range-chart-widget.models.ts @@ -18,22 +18,79 @@ import { BackgroundSettings, BackgroundType, ColorRange, + filterIncludingColorRanges, Font, - simpleDateFormat + simpleDateFormat, + sortedColorRange } from '@shared/models/widget-settings.models'; import { LegendPosition } from '@shared/models/widget.models'; -import { EChartsTooltipWidgetSettings } from '@home/components/widget/lib/chart/echarts-widget.models'; +import { EChartsShape, EChartsTooltipWidgetSettings } from '@home/components/widget/lib/chart/echarts-widget.models'; +import { + createTimeSeriesChartVisualMapPiece, + defaultTimeSeriesChartXAxisSettings, + defaultTimeSeriesChartYAxisSettings, + LineSeriesStepType, + SeriesFillType, + SeriesLabelPosition, ThresholdLabelPosition, timeSeriesChartAnimationDefaultSettings, + TimeSeriesChartAnimationSettings, + timeSeriesChartColorScheme, + TimeSeriesChartKeySettings, + TimeSeriesChartLineType, + TimeSeriesChartSeriesType, + TimeSeriesChartSettings, + TimeSeriesChartThreshold, timeSeriesChartThresholdDefaultSettings, + TimeSeriesChartThresholdType, + TimeSeriesChartVisualMapPiece, + TimeSeriesChartXAxisSettings, + TimeSeriesChartYAxisSettings +} from '@home/components/widget/lib/chart/time-series-chart.models'; +import { isNumber, mergeDeep } from '@core/utils'; +import { DeepPartial } from '@shared/models/common'; + +export interface RangeItem { + index: number; + from?: number; + to?: number; + color: string; + label: string; + visible: boolean; + enabled: boolean; + piece: TimeSeriesChartVisualMapPiece; +} export interface RangeChartWidgetSettings extends EChartsTooltipWidgetSettings { dataZoom: boolean; rangeColors: Array; outOfRangeColor: string; + showRangeThresholds: boolean; + rangeThreshold: Partial; fillArea: boolean; + fillAreaOpacity: number; + showLine: boolean; + step: boolean; + stepType: LineSeriesStepType; + smooth: boolean; + lineType: TimeSeriesChartLineType; + lineWidth: number; + showPoints: boolean; + showPointLabel: boolean; + pointLabelPosition: SeriesLabelPosition; + pointLabelFont: Font; + pointLabelColor: string; + enablePointLabelBackground: boolean; + pointLabelBackground: string; + pointShape: EChartsShape; + pointSize: number; + yAxis: TimeSeriesChartYAxisSettings; + xAxis: TimeSeriesChartXAxisSettings; + animation: TimeSeriesChartAnimationSettings; + thresholds: TimeSeriesChartThreshold[]; showLegend: boolean; legendPosition: LegendPosition; legendLabelFont: Font; legendLabelColor: string; background: BackgroundSettings; + padding: string; } export const rangeChartDefaultSettings: RangeChartWidgetSettings = { @@ -48,7 +105,51 @@ export const rangeChartDefaultSettings: RangeChartWidgetSettings = { {from: 40, color: '#D81838'} ], outOfRangeColor: '#ccc', + showRangeThresholds: true, + rangeThreshold: mergeDeep({} as Partial, + timeSeriesChartThresholdDefaultSettings, + { lineColor: '#37383b', + lineType: TimeSeriesChartLineType.dashed, + startSymbol: EChartsShape.circle, + startSymbolSize: 5, + endSymbol: EChartsShape.arrow, + endSymbolSize: 7, + labelPosition: ThresholdLabelPosition.insideEndTop, + labelColor: '#37383b', + enableLabelBackground: true}), fillArea: true, + fillAreaOpacity: 0.7, + showLine: true, + step: false, + stepType: LineSeriesStepType.start, + smooth: false, + lineType: TimeSeriesChartLineType.solid, + lineWidth: 2, + showPoints: false, + showPointLabel: false, + pointLabelPosition: SeriesLabelPosition.top, + pointLabelFont: { + family: 'Roboto', + size: 11, + sizeUnit: 'px', + style: 'normal', + weight: '400', + lineHeight: '1' + }, + pointLabelColor: timeSeriesChartColorScheme['series.label'].light, + enablePointLabelBackground: false, + pointLabelBackground: 'rgba(255,255,255,0.56)', + pointShape: EChartsShape.emptyCircle, + pointSize: 4, + yAxis: mergeDeep({} as TimeSeriesChartYAxisSettings, + defaultTimeSeriesChartYAxisSettings, + { id: 'default', order: 0, showLine: false, showTicks: false } as TimeSeriesChartYAxisSettings), + xAxis: mergeDeep({} as TimeSeriesChartXAxisSettings, + defaultTimeSeriesChartXAxisSettings, + {showSplitLines: false} as TimeSeriesChartXAxisSettings), + animation: mergeDeep({} as TimeSeriesChartAnimationSettings, + timeSeriesChartAnimationDefaultSettings), + thresholds: [], showLegend: true, legendPosition: LegendPosition.top, legendLabelFont: { @@ -92,5 +193,149 @@ export const rangeChartDefaultSettings: RangeChartWidgetSettings = { color: 'rgba(255,255,255,0.72)', blur: 3 } + }, + padding: '12px' +}; + +export const rangeChartTimeSeriesSettings = (settings: RangeChartWidgetSettings, rangeItems: RangeItem[], + decimals: number, units: string): DeepPartial => { + let thresholds: DeepPartial[] = settings.showRangeThresholds ? getMarkPoints(rangeItems).map(item => ({ + ...{type: TimeSeriesChartThresholdType.constant, + yAxisId: 'default', + units, + decimals, + value: item}, + ...settings.rangeThreshold + } as DeepPartial)) : []; + if (settings.thresholds?.length) { + thresholds = thresholds.concat(settings.thresholds); + } + return { + dataZoom: settings.dataZoom, + thresholds, + yAxes: { + default: { + ...settings.yAxis, + ...{ + decimals, + units + } + } + }, + xAxis: settings.xAxis, + animation: settings.animation, + visualMapSettings: { + outOfRangeColor: settings.outOfRangeColor, + pieces: rangeItems.map(item => item.piece) + }, + showTooltip: settings.showTooltip, + tooltipValueFont: settings.tooltipValueFont, + tooltipValueColor: settings.tooltipValueColor, + tooltipShowDate: settings.tooltipShowDate, + tooltipDateInterval: settings.tooltipDateInterval, + tooltipDateFormat: settings.tooltipDateFormat, + tooltipDateFont: settings.tooltipDateFont, + tooltipDateColor: settings.tooltipDateColor, + tooltipBackgroundColor: settings.tooltipBackgroundColor, + tooltipBackgroundBlur: settings.tooltipBackgroundBlur, + }; +}; + +export const rangeChartTimeSeriesKeySettings = (settings: RangeChartWidgetSettings): DeepPartial => ({ + type: TimeSeriesChartSeriesType.line, + lineSettings: { + showLine: settings.showLine, + step: settings.step, + stepType: settings.stepType, + smooth: settings.smooth, + lineType: settings.lineType, + lineWidth: settings.lineWidth, + showPoints: settings.showPoints, + showPointLabel: settings.showPointLabel, + pointLabelPosition: settings.pointLabelPosition, + pointLabelFont: settings.pointLabelFont, + pointLabelColor: settings.pointLabelColor, + enablePointLabelBackground: settings.enablePointLabelBackground, + pointLabelBackground: settings.pointLabelBackground, + pointShape: settings.pointShape, + pointSize: settings.pointSize, + fillAreaSettings: { + type: settings.fillArea ? SeriesFillType.opacity : SeriesFillType.none, + opacity: settings.fillAreaOpacity + } + } + }); + +export const toRangeItems = (colorRanges: Array): RangeItem[] => { + const rangeItems: RangeItem[] = []; + let counter = 0; + const ranges = sortedColorRange(filterIncludingColorRanges(colorRanges)).filter(r => isNumber(r.from) || isNumber(r.to)); + for (let i = 0; i < ranges.length; i++) { + const range = ranges[i]; + let from = range.from; + const to = range.to; + if (i > 0) { + const prevRange = ranges[i - 1]; + if (isNumber(prevRange.to) && isNumber(from) && from < prevRange.to) { + from = prevRange.to; + } + } + rangeItems.push( + { + index: counter++, + color: range.color, + enabled: true, + visible: true, + from, + to, + label: rangeItemLabel(from, to), + piece: createTimeSeriesChartVisualMapPiece(range.color, from, to) + } + ); + if (!isNumber(from) || !isNumber(to)) { + const value = !isNumber(from) ? to : from; + rangeItems.push( + { + index: counter++, + color: 'transparent', + enabled: true, + visible: false, + label: '', + piece: { gt: value - 0.000000001, lt: value + 0.000000001, color: 'transparent'} + } + ); + } + } + return rangeItems; +}; + +const rangeItemLabel = (from?: number, to?: number): string => { + if (isNumber(from) && isNumber(to)) { + if (from === to) { + return `${from}`; + } else { + return `${from} - ${to}`; + } + } else if (isNumber(from)) { + return `≥ ${from}`; + } else if (isNumber(to)) { + return `< ${to}`; + } else { + return null; + } +}; + +const getMarkPoints = (ranges: Array): number[] => { + const points = new Set(); + for (const range of ranges) { + if (range.visible) { + if (isNumber(range.from)) { + points.add(range.from); + } + if (isNumber(range.to)) { + points.add(range.to); + } + } } + return Array.from(points).sort(); }; diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart-bar.models.ts b/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart-bar.models.ts index c00a078b2f..ee3c703a17 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart-bar.models.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart-bar.models.ts @@ -22,7 +22,7 @@ import { TimeSeriesChartNoAggregationBarWidthStrategy } from '@home/components/widget/lib/chart/time-series-chart.models'; import { CustomSeriesRenderItemParams } from 'echarts'; -import { CustomSeriesRenderItemAPI, CustomSeriesRenderItemReturn } from 'echarts/types/dist/shared'; +import { CallbackDataParams, CustomSeriesRenderItemAPI, CustomSeriesRenderItemReturn } from 'echarts/types/dist/shared'; import { isNumeric } from '@core/utils'; import * as echarts from 'echarts/core'; @@ -34,6 +34,8 @@ export interface BarVisualSettings { } export interface BarRenderSharedContext { + barGap: number; + intervalGap: number; timeInterval: Interval; noAggregationBarWidthStrategy: TimeSeriesChartNoAggregationBarWidthStrategy; noAggregationWidthRelative: boolean; @@ -47,6 +49,7 @@ export interface BarRenderContext { noAggregation?: boolean; visualSettings?: BarVisualSettings; labelOption?: SeriesLabelOption; + additionalLabelOption?: {[key: string]: any}; barStackIndex?: number; currentStackItems?: TimeSeriesChartDataItem[]; } @@ -77,10 +80,13 @@ export const renderTimeSeriesBar = (params: CustomSeriesRenderItemParams, api: C start = time - interval / 2; } - const gap = 0.3; - const barInterval = separateBar ? interval : interval / (renderCtx.barsCount + gap * (renderCtx.barsCount + 3)); - const intervalGap = barInterval * gap * 2; - const barGap = barInterval * gap; + const barGapRatio = renderCtx.shared.barGap; + const intervalGapRatio = renderCtx.shared.intervalGap; + const barInterval = separateBar + ? interval + : interval / (renderCtx.barsCount + barGapRatio * (renderCtx.barsCount - 1) + intervalGapRatio * 2); + const intervalGap = barInterval * intervalGapRatio; + const barGap = barInterval * barGapRatio; const value = api.value(1); const startTime = separateBar ? start : start + intervalGap + (barInterval + barGap) * renderCtx.barIndex; const delta = barInterval; @@ -124,7 +130,7 @@ export const renderTimeSeriesBar = (params: CustomSeriesRenderItemParams, api: C const zeroPos = api.coord([0, offset]); - const style: any = { + let style: any = { fill: renderCtx.visualSettings.color, stroke: renderCtx.visualSettings.borderColor, lineWidth: renderCtx.visualSettings.borderWidth @@ -139,10 +145,20 @@ export const renderTimeSeriesBar = (params: CustomSeriesRenderItemParams, api: C position = 'top'; } } - style.text = (renderCtx.labelOption.formatter as LabelFormatterCallback)({value: [null, value]} as any); + style.text = (renderCtx.labelOption.formatter as LabelFormatterCallback)( + { + seriesName: params.seriesName, + value: [null, value] + } as CallbackDataParams); style.textDistance = 5; style.textPosition = position; + style.textBackgroundColor = renderCtx.labelOption.backgroundColor; + style.textPadding = renderCtx.labelOption.padding; + style.textBorderRadius = renderCtx.labelOption.borderRadius; style.rich = renderCtx.labelOption.rich; + if (renderCtx.additionalLabelOption) { + style = {...style, ...renderCtx.additionalLabelOption}; + } } let borderRadius: number[]; diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart.models.ts b/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart.models.ts index e931349bca..3589d69d4f 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart.models.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart.models.ts @@ -17,10 +17,11 @@ import { ECharts, EChartsOption, - EChartsSeriesItem, + EChartsSeriesItem, EChartsShape, EChartsTooltipTrigger, EChartsTooltipWidgetSettings, - measureThresholdLabelOffset, timeAxisBandWidthCalculator + measureThresholdOffset, + timeAxisBandWidthCalculator } from '@home/components/widget/lib/chart/echarts-widget.models'; import { autoDateFormat, @@ -30,12 +31,17 @@ import { textStyle, tsToFormatTimeUnit } from '@shared/models/widget-settings.models'; -import { XAXisOption, YAXisOption } from 'echarts/types/dist/shared'; +import { + LabelLayoutOptionCallback, + VisualMapComponentOption, + XAXisOption, + YAXisOption +} from 'echarts/types/dist/shared'; import { CustomSeriesOption, LineSeriesOption } from 'echarts/charts'; import { formatValue, isDefinedAndNotNull, - isFunction, + isFunction, isNumber, isNumeric, isUndefined, isUndefinedOrNull, @@ -45,7 +51,7 @@ import { import { LinearGradientObject } from 'zrender/lib/graphic/LinearGradient'; import tinycolor from 'tinycolor2'; import { ValueAxisBaseOption } from 'echarts/types/src/coord/axisCommonTypes'; -import { SeriesLabelOption } from 'echarts/types/src/util/types'; +import { LabelFormatterCallback, LabelLayoutOption, SeriesLabelOption } from 'echarts/types/src/util/types'; import { BarRenderContext, BarRenderSharedContext, @@ -58,6 +64,7 @@ import { TbColorScheme } from '@shared/models/color.models'; import { AbstractControl, ValidationErrors } from '@angular/forms'; import { MarkLine2DDataItemOption } from 'echarts/types/src/component/marker/MarkLineModel'; import { DatePipe } from '@angular/common'; +import { BuiltinTextPosition } from 'zrender/src/core/types'; export enum TimeSeriesChartType { default = 'default', @@ -74,7 +81,7 @@ export const timeSeriesChartTypeTranslations = new Map( ] ); -export enum TimeSeriesChartShape { - emptyCircle = 'emptyCircle', - circle = 'circle', - rect = 'rect', - roundRect = 'roundRect', - triangle = 'triangle', - diamond = 'diamond', - pin = 'pin', - arrow = 'arrow', - none = 'none' -} - -export const timeSeriesChartShapes = Object.keys(TimeSeriesChartShape) as TimeSeriesChartShape[]; - -export const timeSeriesChartShapeTranslations = new Map( - [ - [TimeSeriesChartShape.emptyCircle, 'widgets.time-series-chart.shape-empty-circle'], - [TimeSeriesChartShape.circle, 'widgets.time-series-chart.shape-circle'], - [TimeSeriesChartShape.rect, 'widgets.time-series-chart.shape-rect'], - [TimeSeriesChartShape.roundRect, 'widgets.time-series-chart.shape-round-rect'], - [TimeSeriesChartShape.triangle, 'widgets.time-series-chart.shape-triangle'], - [TimeSeriesChartShape.diamond, 'widgets.time-series-chart.shape-diamond'], - [TimeSeriesChartShape.pin, 'widgets.time-series-chart.shape-pin'], - [TimeSeriesChartShape.arrow, 'widgets.time-series-chart.shape-arrow'], - [TimeSeriesChartShape.none, 'widgets.time-series-chart.shape-none'] - ] -); - export enum TimeSeriesChartLineType { solid = 'solid', dashed = 'dashed', @@ -401,6 +380,38 @@ export const defaultTimeSeriesChartYAxisSettings: TimeSeriesChartYAxisSettings = splitLinesColor: timeSeriesChartColorScheme['axis.splitLine'].light }; +export const defaultTimeSeriesChartXAxisSettings: TimeSeriesChartXAxisSettings = { + show: true, + label: '', + labelFont: { + family: 'Roboto', + size: 12, + sizeUnit: 'px', + style: 'normal', + weight: '600', + lineHeight: '1' + }, + labelColor: timeSeriesChartColorScheme['axis.label'].light, + position: AxisPosition.bottom, + showTickLabels: true, + tickLabelFont: { + family: 'Roboto', + size: 10, + sizeUnit: 'px', + style: 'normal', + weight: '400', + lineHeight: '1' + }, + tickLabelColor: timeSeriesChartColorScheme['axis.tickLabel'].light, + ticksFormat: {}, + showTicks: true, + ticksColor: timeSeriesChartColorScheme['axis.ticks'].light, + showLine: true, + lineColor: timeSeriesChartColorScheme['axis.line'].light, + showSplitLines: true, + splitLinesColor: timeSeriesChartColorScheme['axis.splitLine'].light +}; + export type TimeSeriesChartYAxes = {[id: TimeSeriesChartYAxisId]: TimeSeriesChartYAxisSettings}; export interface TimeSeriesChartThreshold { @@ -415,16 +426,19 @@ export interface TimeSeriesChartThreshold { units?: string; decimals?: number; lineColor: string; - lineType: TimeSeriesChartLineType; + lineType: TimeSeriesChartLineType | number | number[]; lineWidth: number; - startSymbol: TimeSeriesChartShape; + startSymbol: EChartsShape; startSymbolSize: number; - endSymbol: TimeSeriesChartShape; + endSymbol: EChartsShape; endSymbolSize: number; showLabel: boolean; labelPosition: ThresholdLabelPosition; labelFont: Font; labelColor: string; + enableLabelBackground: boolean; + labelBackground: string; + additionalLabelOption?: {[key: string]: any}; } export const timeSeriesChartThresholdValid = (threshold: TimeSeriesChartThreshold): boolean => { @@ -469,9 +483,9 @@ export const timeSeriesChartThresholdDefaultSettings: TimeSeriesChartThreshold = lineColor: timeSeriesChartColorScheme['threshold.line'].light, lineType: TimeSeriesChartLineType.solid, lineWidth: 1, - startSymbol: TimeSeriesChartShape.none, + startSymbol: EChartsShape.none, startSymbolSize: 5, - endSymbol: TimeSeriesChartShape.arrow, + endSymbol: EChartsShape.arrow, endSymbolSize: 5, showLabel: true, labelPosition: ThresholdLabelPosition.end, @@ -483,7 +497,9 @@ export const timeSeriesChartThresholdDefaultSettings: TimeSeriesChartThreshold = weight: '400', lineHeight: '1' }, - labelColor: timeSeriesChartColorScheme['threshold.label'].light + labelColor: timeSeriesChartColorScheme['threshold.label'].light, + enableLabelBackground: false, + labelBackground: 'rgba(255,255,255,0.56)' }; export enum TimeSeriesChartNoAggregationBarWidthStrategy { @@ -513,6 +529,25 @@ export interface TimeSeriesChartNoAggregationBarWidthSettings { barWidth?: TimeSeriesChartBarWidth; } +export const timeSeriesChartNoAggregationBarWidthDefaultSettings: TimeSeriesChartNoAggregationBarWidthSettings = { + strategy: TimeSeriesChartNoAggregationBarWidthStrategy.group, + groupWidth: { + relative: true, + relativeWidth: 2, + absoluteWidth: 1000 + }, + barWidth: { + relative: true, + relativeWidth: 2, + absoluteWidth: 1000 + } +}; + +export interface TimeSeriesChartBarWidthSettings { + barGap: number; + intervalGap: number; +} + export enum TimeSeriesChartAnimationEasing { linear = 'linear', quadraticIn = 'quadraticIn', @@ -560,6 +595,50 @@ export interface TimeSeriesChartAnimationSettings { animationDelayUpdate: number; } +export const timeSeriesChartAnimationDefaultSettings: TimeSeriesChartAnimationSettings = { + animation: true, + animationThreshold: 2000, + animationDuration: 500, + animationEasing: TimeSeriesChartAnimationEasing.cubicOut, + animationDelay: 0, + animationDurationUpdate: 300, + animationEasingUpdate: TimeSeriesChartAnimationEasing.cubicOut, + animationDelayUpdate: 0 +}; + +export interface TimeSeriesChartVisualMapPiece { + lt?: number; + gt?: number; + lte?: number; + gte?: number; + value?: number; + color?: string; +} + +export const createTimeSeriesChartVisualMapPiece = (color: string, from?: number, to?: number): TimeSeriesChartVisualMapPiece => { + const piece: TimeSeriesChartVisualMapPiece = { + color + }; + if (isNumber(from) && isNumber(to)) { + if (from === to) { + piece.value = from; + } else { + piece.gte = from; + piece.lt = to; + } + } else if (isNumber(from)) { + piece.gte = from; + } else if (isNumber(to)) { + piece.lt = to; + } + return piece; +}; + +export interface TimeSeriesChartVisualMapSettings { + outOfRangeColor: string; + pieces: TimeSeriesChartVisualMapPiece[]; +} + export interface TimeSeriesChartSettings extends EChartsTooltipWidgetSettings { thresholds: TimeSeriesChartThreshold[]; darkMode: boolean; @@ -568,7 +647,9 @@ export interface TimeSeriesChartSettings extends EChartsTooltipWidgetSettings { yAxes: TimeSeriesChartYAxes; xAxis: TimeSeriesChartXAxisSettings; animation: TimeSeriesChartAnimationSettings; + barWidthSettings: TimeSeriesChartBarWidthSettings; noAggregationBarWidthSettings: TimeSeriesChartNoAggregationBarWidthSettings; + visualMapSettings?: TimeSeriesChartVisualMapSettings; } export const timeSeriesChartDefaultSettings: TimeSeriesChartSettings = { @@ -581,60 +662,16 @@ export const timeSeriesChartDefaultSettings: TimeSeriesChartSettings = { defaultTimeSeriesChartYAxisSettings, { id: 'default', order: 0 } as TimeSeriesChartYAxisSettings) }, - xAxis: { - show: true, - label: '', - labelFont: { - family: 'Roboto', - size: 12, - sizeUnit: 'px', - style: 'normal', - weight: '600', - lineHeight: '1' - }, - labelColor: timeSeriesChartColorScheme['axis.label'].light, - position: AxisPosition.bottom, - showTickLabels: true, - tickLabelFont: { - family: 'Roboto', - size: 10, - sizeUnit: 'px', - style: 'normal', - weight: '400', - lineHeight: '1' - }, - tickLabelColor: timeSeriesChartColorScheme['axis.tickLabel'].light, - ticksFormat: {}, - showTicks: true, - ticksColor: timeSeriesChartColorScheme['axis.ticks'].light, - showLine: true, - lineColor: timeSeriesChartColorScheme['axis.line'].light, - showSplitLines: true, - splitLinesColor: timeSeriesChartColorScheme['axis.splitLine'].light - }, - animation: { - animation: true, - animationThreshold: 2000, - animationDuration: 500, - animationEasing: TimeSeriesChartAnimationEasing.cubicOut, - animationDelay: 0, - animationDurationUpdate: 300, - animationEasingUpdate: TimeSeriesChartAnimationEasing.cubicOut, - animationDelayUpdate: 0 - }, - noAggregationBarWidthSettings: { - strategy: TimeSeriesChartNoAggregationBarWidthStrategy.group, - groupWidth: { - relative: true, - relativeWidth: 2, - absoluteWidth: 1000 - }, - barWidth: { - relative: true, - relativeWidth: 2, - absoluteWidth: 1000 - } + xAxis: mergeDeep({} as TimeSeriesChartXAxisSettings, + defaultTimeSeriesChartXAxisSettings), + animation: mergeDeep({} as TimeSeriesChartAnimationSettings, + timeSeriesChartAnimationDefaultSettings), + barWidthSettings: { + barGap: 0.3, + intervalGap: 0.6 }, + noAggregationBarWidthSettings: mergeDeep({} as TimeSeriesChartNoAggregationBarWidthSettings, + timeSeriesChartNoAggregationBarWidthDefaultSettings), showTooltip: true, tooltipTrigger: EChartsTooltipTrigger.axis, tooltipValueFont: { @@ -683,7 +720,10 @@ export interface LineSeriesSettings { pointLabelPosition: SeriesLabelPosition; pointLabelFont: Font; pointLabelColor: string; - pointShape: TimeSeriesChartShape; + enablePointLabelBackground: boolean; + pointLabelBackground: string; + pointLabelFormatter?: string | LabelFormatterCallback; + pointShape: EChartsShape; pointSize: number; fillAreaSettings: SeriesFillSettings; } @@ -693,9 +733,14 @@ export interface BarSeriesSettings { borderWidth: number; borderRadius: number; showLabel: boolean; - labelPosition: SeriesLabelPosition; + labelPosition: SeriesLabelPosition | BuiltinTextPosition; labelFont: Font; labelColor: string; + enableLabelBackground: boolean; + labelBackground: string; + labelFormatter?: string | LabelFormatterCallback; + labelLayout?: LabelLayoutOption | LabelLayoutOptionCallback; + additionalLabelOption?: {[key: string]: any}; backgroundSettings: SeriesFillSettings; } @@ -732,7 +777,9 @@ export const timeSeriesChartKeyDefaultSettings: TimeSeriesChartKeySettings = { lineHeight: '1' }, pointLabelColor: timeSeriesChartColorScheme['series.label'].light, - pointShape: TimeSeriesChartShape.emptyCircle, + enablePointLabelBackground: false, + pointLabelBackground: 'rgba(255,255,255,0.56)', + pointShape: EChartsShape.emptyCircle, pointSize: 4, fillAreaSettings: { type: SeriesFillType.none, @@ -758,6 +805,8 @@ export const timeSeriesChartKeyDefaultSettings: TimeSeriesChartKeySettings = { lineHeight: '1' }, labelColor: timeSeriesChartColorScheme['series.label'].light, + enableLabelBackground: false, + labelBackground: 'rgba(255,255,255,0.56)', backgroundSettings: { type: SeriesFillType.none, opacity: 0.4, @@ -944,7 +993,12 @@ export const createTimeSeriesXAxisOption = (settings: TimeSeriesChartXAxisSettin fontFamily: xAxisTickLabelStyle.fontFamily, fontSize: xAxisTickLabelStyle.fontSize, hideOverlap: true, - formatter: (value: number, index: number, extra: {level: number}) => { + /** Min/Max time label always visible **/ + /* alignMinLabel: 'left', + alignMaxLabel: 'right', + showMinLabel: true, + showMaxLabel: true, */ + formatter: (value: number, _index: number, extra: {level: number}) => { const unit = tsToFormatTimeUnit(value); const format = ticksFormat[unit]; const formatted = datePipe.transform(value, format); @@ -974,6 +1028,21 @@ export const createTimeSeriesXAxisOption = (settings: TimeSeriesChartXAxisSettin }; }; +export const createTimeSeriesVisualMapOption = (settings: TimeSeriesChartVisualMapSettings, + selectedRanges: {[key: number]: boolean}): VisualMapComponentOption => ({ + show: false, + type: 'piecewise', + selected: selectedRanges, + dimension: 1, + pieces: settings.pieces, + outOfRange: { + color: settings.outOfRangeColor +}, + inRange: !settings.pieces.length ? { + color: settings.outOfRangeColor + } : undefined +}); + export const generateChartData = (dataItems: TimeSeriesChartDataItem[], thresholdItems: TimeSeriesChartThresholdItem[], stack: boolean, @@ -983,7 +1052,7 @@ export const generateChartData = (dataItems: TimeSeriesChartDataItem[], let series = generateChartSeries(dataItems, stack, noAggregation, barRenderSharedContext, darkMode); if (thresholdItems.length) { - const thresholds = generateChartThresholds(thresholdItems, darkMode); + const thresholds = generateChartThresholds(thresholdItems); series = series.concat(thresholds); } return series; @@ -995,7 +1064,7 @@ export const calculateThresholdsOffset = (chart: ECharts, const result: [number, number] = [0, 0]; for (const item of thresholdItems) { const yAxis = yAxisList[item.yAxisIndex]; - const offset = measureThresholdLabelOffset(chart, yAxis.id, item.id, item.value); + const offset = measureThresholdOffset(chart, yAxis.id, item.id, item.value); result[0] = Math.max(result[0], offset[0]); result[1] = Math.max(result[1], offset[1]); } @@ -1017,7 +1086,7 @@ export const parseThresholdData = (value: any): TimeSeriesChartThresholdValue => return thresholdValue; }; -const generateChartThresholds = (thresholdItems: TimeSeriesChartThresholdItem[], darkMode: boolean): Array => { +const generateChartThresholds = (thresholdItems: TimeSeriesChartThresholdItem[]): Array => { const series: Array = []; for (const item of thresholdItems) { if (isDefinedAndNotNull(item.value)) { @@ -1056,6 +1125,14 @@ const generateChartThresholds = (thresholdItems: TimeSeriesChartThresholdItem[], } } }; + if (item.settings.enableLabelBackground) { + seriesOption.markLine.label.backgroundColor = item.settings.labelBackground; + seriesOption.markLine.label.padding = [4, 5]; + seriesOption.markLine.label.borderRadius = 4; + } + if (item.settings.additionalLabelOption) { + seriesOption.markLine.label = {...seriesOption.markLine.label, ...item.settings.additionalLabelOption}; + } item.option = seriesOption; } seriesOption.markLine.data = []; @@ -1131,7 +1208,7 @@ const generateChartSeries = (dataItems: TimeSeriesChartDataItem[], export const updateDarkMode = (options: EChartsOption, settings: TimeSeriesChartSettings, yAxisList: TimeSeriesChartYAxis[], - dataItems: TimeSeriesChartDataItem[], thresholdDataItems: TimeSeriesChartThresholdItem[], + dataItems: TimeSeriesChartDataItem[], darkMode: boolean): EChartsOption => { options.darkMode = darkMode; if (Array.isArray(options.yAxis)) { @@ -1171,7 +1248,7 @@ export const updateDarkMode = (options: EChartsOption, settings: TimeSeriesChart } else { if (item.barRenderContext?.labelOption?.show) { const barSettings = item.dataKey.settings as BarSeriesSettings; - item.barRenderContext.labelOption.rich.value.color = prepareChartThemeColor(barSettings.labelColor, + (item.barRenderContext.labelOption.rich.value as any).fill = prepareChartThemeColor(barSettings.labelColor, darkMode, 'series.label'); } } @@ -1215,7 +1292,10 @@ const createTimeSeriesChartSeries = (item: TimeSeriesChartDataItem, const lineSeriesOption = seriesOption as LineSeriesOption; lineSeriesOption.type = 'line'; lineSeriesOption.label = createSeriesLabelOption(item, lineSettings.showPointLabel, - lineSettings.pointLabelFont, lineSettings.pointLabelColor, lineSettings.pointLabelPosition, darkMode); + lineSettings.pointLabelFont, lineSettings.pointLabelColor, + lineSettings.enablePointLabelBackground, lineSettings.pointLabelBackground, + lineSettings.pointLabelPosition, + lineSettings.pointLabelFormatter, false, darkMode); lineSeriesOption.step = lineSettings.step ? lineSettings.stepType : false; lineSeriesOption.smooth = lineSettings.smooth; if (lineSettings.smooth) { @@ -1229,7 +1309,7 @@ const createTimeSeriesChartSeries = (item: TimeSeriesChartDataItem, lineSeriesOption.areaStyle = {}; if (lineSettings.fillAreaSettings.type === SeriesFillType.opacity) { lineSeriesOption.areaStyle.opacity = lineSettings.fillAreaSettings.opacity; - } else { + } else if (lineSettings.fillAreaSettings.type === SeriesFillType.gradient) { lineSeriesOption.areaStyle.opacity = 1; lineSeriesOption.areaStyle.color = createLinearOpacityGradient(seriesColor, lineSettings.fillAreaSettings.gradient); } @@ -1256,9 +1336,12 @@ const createTimeSeriesChartSeries = (item: TimeSeriesChartDataItem, } item.barRenderContext.visualSettings = barVisualSettings; item.barRenderContext.labelOption = createSeriesLabelOption(item, barSettings.showLabel, - barSettings.labelFont, barSettings.labelColor, barSettings.labelPosition, darkMode); + barSettings.labelFont, barSettings.labelColor, barSettings.enableLabelBackground, barSettings.labelBackground, + barSettings.labelPosition, barSettings.labelFormatter, true, darkMode); + item.barRenderContext.additionalLabelOption = barSettings.additionalLabelOption; barSeriesOption.renderItem = (params, api) => renderTimeSeriesBar(params, api, item.barRenderContext); + barSeriesOption.labelLayout = barSettings.labelLayout; } } seriesOption.data = item.data; @@ -1266,30 +1349,63 @@ const createTimeSeriesChartSeries = (item: TimeSeriesChartDataItem, }; const createSeriesLabelOption = (item: TimeSeriesChartDataItem, show: boolean, - labelFont: Font, labelColor: string, position: SeriesLabelPosition, + labelFont: Font, labelColor: string, + enableBackground: boolean, labelBackground: string, + position: SeriesLabelPosition | BuiltinTextPosition, + labelFormatter: string | LabelFormatterCallback, + labelColorFill: boolean, darkMode: boolean): SeriesLabelOption => { let labelStyle: ComponentStyle = {}; if (show) { - labelStyle = createChartTextStyle(labelFont, labelColor, darkMode, 'series.label'); + labelStyle = createChartTextStyle(labelFont, labelColor, darkMode, 'series.label', labelColorFill); } - return { - show, - position, - formatter: (params): string => { + let formatter: LabelFormatterCallback; + if (isFunction(labelFormatter)) { + formatter = labelFormatter as LabelFormatterCallback; + } else if (labelFormatter?.length) { + const formatFunction = parseFunction(labelFormatter, ['value']); + formatter = (params): string => { + let result: string; + try { + result = formatFunction(params.value[1]); + } catch (_e) { + } + if (isUndefined(result)) { + result = formatValue(params.value[1], item.decimals, item.units, false); + } + return `{value|${result}}`; + }; + } else { + formatter = (params): string => { const value = formatValue(params.value[1], item.decimals, item.units, false); return `{value|${value}}`; - }, + }; + } + const labelOption: SeriesLabelOption = { + show, + position, + formatter, rich: { value: labelStyle } }; + if (enableBackground) { + labelOption.backgroundColor = labelBackground; + labelOption.padding = [4, 5]; + labelOption.borderRadius = 4; + } + return labelOption; }; -const createChartTextStyle = (font: Font, color: string, darkMode: boolean, colorKey?: string): ComponentStyle => { +const createChartTextStyle = (font: Font, color: string, darkMode: boolean, colorKey?: string, fill = false): ComponentStyle => { const style = textStyle(font); delete style.lineHeight; style.fontSize = font.size; - style.color = prepareChartThemeColor(color, darkMode, colorKey); + if (fill) { + style.fill = prepareChartThemeColor(color, darkMode, colorKey); + } else { + style.color = prepareChartThemeColor(color, darkMode, colorKey); + } return style; }; diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart.ts b/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart.ts index bd9005c497..00f2c55d3f 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart.ts @@ -18,6 +18,7 @@ import { WidgetContext } from '@home/models/widget-component.models'; import { AxisPosition, calculateThresholdsOffset, + createTimeSeriesVisualMapOption, createTimeSeriesXAxisOption, createTimeSeriesYAxis, defaultTimeSeriesChartYAxisSettings, @@ -31,7 +32,6 @@ import { TimeSeriesChartNoAggregationBarWidthStrategy, TimeSeriesChartSeriesType, TimeSeriesChartSettings, - TimeSeriesChartShape, TimeSeriesChartThreshold, timeSeriesChartThresholdDefaultSettings, TimeSeriesChartThresholdItem, @@ -48,10 +48,11 @@ import { calculateYAxisWidth, ECharts, echartsModule, - EChartsOption, + EChartsOption, EChartsShape, echartsTooltipFormatter, EChartsTooltipTrigger, getAxisExtent, + getFocusedSeriesIndex, measureXAxisNameHeight, measureYAxisNameWidth, toNamedData @@ -60,7 +61,7 @@ import { DateFormatProcessor } from '@shared/models/widget-settings.models'; import { isDefinedAndNotNull, isEqual, mergeDeep } from '@core/utils'; import { DataKey, Datasource, DatasourceType, widgetType } from '@shared/models/widget.models'; import * as echarts from 'echarts/core'; -import { CallbackDataParams } from 'echarts/types/dist/shared'; +import { CallbackDataParams, PiecewiseVisualMapOption } from 'echarts/types/dist/shared'; import { Renderer2 } from '@angular/core'; import { CustomSeriesOption, LineSeriesOption } from 'echarts/charts'; import { BehaviorSubject } from 'rxjs'; @@ -86,7 +87,7 @@ export class TbTimeSeriesChart { settings.type = TimeSeriesChartSeriesType.line; settings.lineSettings.showLine = false; settings.lineSettings.showPoints = true; - settings.lineSettings.pointShape = TimeSeriesChartShape.circle; + settings.lineSettings.pointShape = EChartsShape.circle; settings.lineSettings.pointSize = 8; } return settings; @@ -107,6 +108,9 @@ export class TbTimeSeriesChart { private dataItems: TimeSeriesChartDataItem[] = []; private thresholdItems: TimeSeriesChartThresholdItem[] = []; + private hasVisualMap = false; + private visualMapSelectedRanges: {[key: number]: boolean}; + private timeSeriesChart: ECharts; private timeSeriesChartOptions: EChartsOption; @@ -145,6 +149,7 @@ export class TbTimeSeriesChart { this.setupYAxes(); this.setupData(); this.setupThresholds(); + this.setupVisualMap(); if (this.settings.showTooltip && this.settings.tooltipShowDate) { this.tooltipDateFormat = DateFormatProcessor.fromSettings(this.ctx.$injector, this.settings.tooltipDateFormat); } @@ -186,6 +191,9 @@ export class TbTimeSeriesChart { } else { this.timeSeriesChartOptions.tooltip[0].axisPointer.type = 'shadow'; } + if (this.hasVisualMap) { + (this.timeSeriesChartOptions.visualMap as PiecewiseVisualMapOption).selected = this.visualMapSelectedRanges; + } this.barRenderSharedContext.timeInterval = this.ctx.timeWindow.interval; this.updateSeriesData(true); if (this.highlightedDataKey) { @@ -263,6 +271,16 @@ export class TbTimeSeriesChart { } } + public toggleVisualMapRange(index: number): void { + if (this.hasVisualMap) { + this.visualMapSelectedRanges[index] = !this.visualMapSelectedRanges[index]; + this.timeSeriesChart.dispatchAction({ + type: 'selectDataRange', + selected: this.visualMapSelectedRanges + }); + } + } + public destroy(): void { if (this.shapeResize$) { this.shapeResize$.disconnect(); @@ -284,8 +302,7 @@ export class TbTimeSeriesChart { this.darkMode = darkMode; if (this.timeSeriesChart) { this.timeSeriesChartOptions = updateDarkMode(this.timeSeriesChartOptions, - this.settings, this.yAxisList, this.dataItems, - this.thresholdItems, darkMode); + this.settings, this.yAxisList, this.dataItems, darkMode); this.timeSeriesChart.setOption(this.timeSeriesChartOptions); } } @@ -300,6 +317,8 @@ export class TbTimeSeriesChart { const targetBarWidth = noAggregationBarWidthSettings.strategy === TimeSeriesChartNoAggregationBarWidthStrategy.group ? noAggregationBarWidthSettings.groupWidth : noAggregationBarWidthSettings.barWidth; this.barRenderSharedContext = { + barGap: this.settings.barWidthSettings.barGap, + intervalGap: this.settings.barWidthSettings.intervalGap, timeInterval: this.ctx.timeWindow?.interval, noAggregationBarWidthStrategy: noAggregationBarWidthSettings.strategy, noAggregationWidthRelative: targetBarWidth.relative, @@ -313,7 +332,10 @@ export class TbTimeSeriesChart { timeSeriesChartKeyDefaultSettings, dataKey.settings); if ((keySettings.type === TimeSeriesChartSeriesType.line && keySettings.lineSettings.showPointLabel && keySettings.lineSettings.pointLabelPosition === SeriesLabelPosition.top) || - (keySettings.type === TimeSeriesChartSeriesType.bar && keySettings.barSettings.showLabel)) { + (keySettings.type === TimeSeriesChartSeriesType.bar && + keySettings.barSettings.showLabel && + [SeriesLabelPosition.top, SeriesLabelPosition.bottom] + .includes(keySettings.barSettings.labelPosition as SeriesLabelPosition))) { this.topPointLabels = true; } dataKey.settings = keySettings; @@ -346,6 +368,11 @@ export class TbTimeSeriesChart { for (const thresholdSettings of this.settings.thresholds) { const threshold = mergeDeep({} as TimeSeriesChartThreshold, timeSeriesChartThresholdDefaultSettings, thresholdSettings); + if (!this.topPointLabels) { + if (threshold.showLabel && !threshold.labelPosition.endsWith('Bottom')) { + this.topPointLabels = true; + } + } let latestDataKey: DataKey = null; let entityDataKey: DataKey = null; let value = null; @@ -429,6 +456,15 @@ export class TbTimeSeriesChart { } } + private setupVisualMap(): void { + if (this.settings.visualMapSettings?.pieces && this.settings.visualMapSettings?.pieces.length) { + this.hasVisualMap = true; + this.visualMapSelectedRanges = {}; + this.settings.visualMapSettings.pieces.forEach((_val, index) => { + this.visualMapSelectedRanges[index] = true; + }); + } + } private nextComponentId(): string { return (this.componentIndexCounter++) + ''; @@ -492,8 +528,9 @@ export class TbTimeSeriesChart { }, formatter: (params: CallbackDataParams[]) => this.settings.showTooltip ? echartsTooltipFormatter(this.renderer, this.tooltipDateFormat, - this.settings, params, 0, '', -1, this.dataItems, - this.noAggregation ? null : this.ctx.timeWindow.interval) : undefined, + this.settings, params, 0, '', + this.settings.tooltipShowFocusedSeries ? getFocusedSeriesIndex(this.timeSeriesChart) : -1, + this.dataItems, this.noAggregation ? null : this.ctx.timeWindow.interval) : undefined, padding: [8, 12], backgroundColor: this.settings.tooltipBackgroundColor, borderWidth: 0, @@ -533,6 +570,10 @@ export class TbTimeSeriesChart { animationEasingUpdate: this.settings.animation.animationEasingUpdate, animationDelayUpdate: this.settings.animation.animationDelayUpdate }; + if (this.hasVisualMap) { + this.timeSeriesChartOptions.visualMap = + createTimeSeriesVisualMapOption(this.settings.visualMapSettings, this.visualMapSelectedRanges); + } this.timeSeriesChartOptions.xAxis[0].tbTimeWindow = this.ctx.defaultSubscription.timeWindow; @@ -542,7 +583,7 @@ export class TbTimeSeriesChart { } this.timeSeriesChart.setOption(this.timeSeriesChartOptions); - this.updateAxes(); + this.updateAxes(false); if (this.settings.dataZoom) { this.timeSeriesChart.on('datazoom', () => { @@ -567,7 +608,7 @@ export class TbTimeSeriesChart { this.barRenderSharedContext, this.darkMode); } - private updateAxes() { + private updateAxes(lazy = true) { const leftAxisList = this.yAxisList.filter(axis => axis.option.position === 'left'); let res = this.updateYAxisOffset(leftAxisList); let leftOffset = res.offset + (!res.offset && this.settings.dataZoom ? 5 : 0); @@ -617,7 +658,7 @@ export class TbTimeSeriesChart { } if (changed) { this.timeSeriesChartOptions.yAxis = this.yAxisList.map(axis => axis.option); - this.timeSeriesChart.setOption(this.timeSeriesChartOptions, {replaceMerge: ['yAxis', 'xAxis', 'grid'], lazyUpdate: true}); + this.timeSeriesChart.setOption(this.timeSeriesChartOptions, {replaceMerge: ['yAxis', 'xAxis', 'grid'], lazyUpdate: lazy}); } if (this.yAxisList.length) { const extent = getAxisExtent(this.timeSeriesChart, this.yAxisList[0].id); @@ -696,7 +737,7 @@ export class TbTimeSeriesChart { private minTopOffset(): number { const showTickLabels = !!this.yAxisList.find(yAxis => yAxis.settings.show && yAxis.settings.showTickLabels); - return (this.topPointLabels) ? 20 : + return (this.topPointLabels) ? 25 : (showTickLabels ? 10 : 5); } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/bar-chart-with-labels-widget-settings.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/bar-chart-with-labels-widget-settings.component.html index b20b941bd1..7db5a92746 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/bar-chart-with-labels-widget-settings.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/bar-chart-with-labels-widget-settings.component.html @@ -17,39 +17,99 @@ -->
-
widgets.bar-chart.bar-chart-card-style
-
- - {{ 'widgets.bar-chart.label-on-bar' | translate }} +
widgets.bar-chart.bar-chart-style
+
+ + {{ 'widgets.time-series-chart.data-zoom' | translate }} -
- - - - -
-
- - {{ 'widgets.bar-chart.value-on-bar' | translate }} - -
- - - - +
+
widgets.bar-chart.bar-appearance
+
+ + {{ 'widgets.bar-chart.label-on-bar' | translate }} + +
+ + + + +
+
+
+ + {{ 'widgets.bar-chart.value-on-bar' | translate }} + +
+ + + + +
+
+
+ + {{ 'widgets.time-series-chart.series.bar.show-border' | translate }} + +
+
+
widgets.time-series-chart.series.bar.border-width
+ + +
+
+
widgets.time-series-chart.series.bar.border-radius
+ + + +
+ + + + +
+
+
widgets.time-series-chart.axis.y-axis
+ + +
+
+
widgets.time-series-chart.axis.x-axis
+ +
+ +
@@ -149,10 +209,22 @@
-
-
{{ 'widgets.background.background' | translate }}
- - + + +
+
widget-config.card-appearance
+
+
{{ 'widgets.background.background' | translate }}
+ + +
+
+
{{ 'widget-config.card-padding' | translate }}
+ + + +
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/bar-chart-with-labels-widget-settings.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/bar-chart-with-labels-widget-settings.component.ts index b69b023d34..b221d09cf9 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/bar-chart-with-labels-widget-settings.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/bar-chart-with-labels-widget-settings.component.ts @@ -16,6 +16,7 @@ import { Component, Injector } from '@angular/core'; import { + Datasource, legendPositions, legendPositionTranslationMap, WidgetSettings, @@ -37,6 +38,15 @@ import { }) export class BarChartWithLabelsWidgetSettingsComponent extends WidgetSettingsComponent { + public get datasource(): Datasource { + const datasources: Datasource[] = this.widgetConfig.config.datasources; + if (datasources && datasources.length) { + return datasources[0]; + } else { + return null; + } + } + legendPositions = legendPositions; legendPositionTranslationMap = legendPositionTranslationMap; @@ -64,12 +74,26 @@ export class BarChartWithLabelsWidgetSettingsComponent extends WidgetSettingsCom protected onSettingsSet(settings: WidgetSettings) { this.barChartWidgetSettingsForm = this.fb.group({ + dataZoom: [settings.dataZoom, []], + showBarLabel: [settings.showBarLabel, []], barLabelFont: [settings.barLabelFont, []], barLabelColor: [settings.barLabelColor, []], showBarValue: [settings.showBarValue, []], barValueFont: [settings.barValueFont, []], barValueColor: [settings.barValueColor, []], + showBarBorder: [settings.showBarBorder, []], + barBorderWidth: [settings.barBorderWidth, []], + barBorderRadius: [settings.barBorderRadius, []], + barBackgroundSettings: [settings.barBackgroundSettings, []], + noAggregationBarWidthSettings: [settings.noAggregationBarWidthSettings, []], + + yAxis: [settings.yAxis, []], + xAxis: [settings.xAxis, []], + + thresholds: [settings.thresholds, []], + + animation: [settings.animation, []], showLegend: [settings.showLegend, []], legendPosition: [settings.legendPosition, []], @@ -88,17 +112,19 @@ export class BarChartWithLabelsWidgetSettingsComponent extends WidgetSettingsCom tooltipBackgroundColor: [settings.tooltipBackgroundColor, []], tooltipBackgroundBlur: [settings.tooltipBackgroundBlur, []], - background: [settings.background, []] + background: [settings.background, []], + padding: [settings.padding, []] }); } protected validatorTriggers(): string[] { - return ['showBarLabel', 'showBarValue', 'showLegend', 'showTooltip', 'tooltipShowDate']; + return ['showBarLabel', 'showBarValue', 'showBarBorder', 'showLegend', 'showTooltip', 'tooltipShowDate']; } protected updateValidators(emitEvent: boolean) { const showBarLabel: boolean = this.barChartWidgetSettingsForm.get('showBarLabel').value; const showBarValue: boolean = this.barChartWidgetSettingsForm.get('showBarValue').value; + const showBarBorder: boolean = this.barChartWidgetSettingsForm.get('showBarBorder').value; const showLegend: boolean = this.barChartWidgetSettingsForm.get('showLegend').value; const showTooltip: boolean = this.barChartWidgetSettingsForm.get('showTooltip').value; const tooltipShowDate: boolean = this.barChartWidgetSettingsForm.get('tooltipShowDate').value; @@ -119,6 +145,12 @@ export class BarChartWithLabelsWidgetSettingsComponent extends WidgetSettingsCom this.barChartWidgetSettingsForm.get('barValueColor').disable(); } + if (showBarBorder) { + this.barChartWidgetSettingsForm.get('barBorderWidth').enable(); + } else { + this.barChartWidgetSettingsForm.get('barBorderWidth').disable(); + } + if (showLegend) { this.barChartWidgetSettingsForm.get('legendPosition').enable(); this.barChartWidgetSettingsForm.get('legendLabelFont').enable(); diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/range-chart-widget-settings.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/range-chart-widget-settings.component.html index cf5bbcfdd5..a19b8eb845 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/range-chart-widget-settings.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/range-chart-widget-settings.component.html @@ -17,29 +17,175 @@ -->
-
widgets.range-chart.range-chart-card-style
+
widgets.range-chart.range-chart-style
{{ 'widgets.range-chart.data-zoom' | translate }}
-
-
{{ 'widgets.range-chart.range-colors' | translate }}
- - +
+
widgets.range-chart.range-chart-appearance
+
+
{{ 'widgets.range-chart.range-colors' | translate }}
+ + +
+
+
{{ 'widgets.range-chart.out-of-range-color' | translate }}
+ + +
+
+ + {{ 'widgets.range-chart.show-range-thresholds' | translate }} + + + +
+
+ + {{ 'widgets.range-chart.fill-area' | translate }} + +
+
+
widgets.range-chart.fill-area-opacity
+ + + +
+
+
widgets.time-series-chart.series.line.line
+
+ + {{ 'widgets.time-series-chart.series.line.show-line' | translate }} + +
+
+ + {{ 'widgets.time-series-chart.series.line.step-line' | translate }} + + + + + {{ lineSeriesStepTypeTranslations.get(stepType) | translate }} + + + +
+
+ + {{ 'widgets.time-series-chart.series.line.smooth-line' | translate }} + +
+
+
widgets.time-series-chart.line-type
+ + + + {{ timeSeriesLineTypeTranslations.get(lineType) | translate }} + + + +
+
+
widgets.time-series-chart.line-width
+ + + +
+
+
+
widgets.time-series-chart.series.point.points
+
+ + {{ 'widgets.time-series-chart.series.point.show-points' | translate }} + +
+
+ +
+ {{ 'widgets.time-series-chart.series.point.point-label' | translate }} +
+
+
+ + + + {{ seriesLabelPositionTranslations.get(position) | translate }} + + + + + + + +
+
+
+ + {{ 'widgets.time-series-chart.series.point.point-label-background' | translate }} + + + +
+
+
widgets.time-series-chart.series.point.point-shape
+ + + + {{ echartsShapeTranslations.get(shape) | translate }} + + + +
+
+
widgets.time-series-chart.series.point.point-size
+ + + +
+
-
-
{{ 'widgets.range-chart.out-of-range-color' | translate }}
- - +
+
widgets.time-series-chart.axis.y-axis
+ +
-
- - {{ 'widgets.range-chart.fill-area' | translate }} - +
+
widgets.time-series-chart.axis.x-axis
+ +
+ +
@@ -139,10 +285,22 @@
-
-
{{ 'widgets.background.background' | translate }}
- - + + +
+
widget-config.card-appearance
+
+
{{ 'widgets.background.background' | translate }}
+ + +
+
+
{{ 'widget-config.card-padding' | translate }}
+ + + +
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/range-chart-widget-settings.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/range-chart-widget-settings.component.ts index f0870dbd80..b9a058af24 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/range-chart-widget-settings.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/range-chart-widget-settings.component.ts @@ -16,17 +16,24 @@ import { Component, Injector } from '@angular/core'; import { + Datasource, legendPositions, legendPositionTranslationMap, WidgetSettings, WidgetSettingsComponent } from '@shared/models/widget.models'; -import { UntypedFormBuilder, UntypedFormGroup } from '@angular/forms'; +import { UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms'; import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; import { formatValue } from '@core/utils'; import { rangeChartDefaultSettings } from '@home/components/widget/lib/chart/range-chart-widget.models'; import { DateFormatProcessor, DateFormatSettings } from '@shared/models/widget-settings.models'; +import { + lineSeriesStepTypes, lineSeriesStepTypeTranslations, + seriesLabelPositions, seriesLabelPositionTranslations, + timeSeriesLineTypes, timeSeriesLineTypeTranslations +} from '@home/components/widget/lib/chart/time-series-chart.models'; +import { echartsShapes, echartsShapeTranslations } from '@home/components/widget/lib/chart/echarts-widget.models'; @Component({ selector: 'tb-range-chart-widget-settings', @@ -35,12 +42,39 @@ import { DateFormatProcessor, DateFormatSettings } from '@shared/models/widget-s }) export class RangeChartWidgetSettingsComponent extends WidgetSettingsComponent { + public get datasource(): Datasource { + const datasources: Datasource[] = this.widgetConfig.config.datasources; + if (datasources && datasources.length) { + return datasources[0]; + } else { + return null; + } + } + + lineSeriesStepTypes = lineSeriesStepTypes; + + lineSeriesStepTypeTranslations = lineSeriesStepTypeTranslations; + + timeSeriesLineTypes = timeSeriesLineTypes; + + timeSeriesLineTypeTranslations = timeSeriesLineTypeTranslations; + + seriesLabelPositions = seriesLabelPositions; + + seriesLabelPositionTranslations = seriesLabelPositionTranslations; + + echartsShapes = echartsShapes; + + echartsShapeTranslations = echartsShapeTranslations; + legendPositions = legendPositions; legendPositionTranslationMap = legendPositionTranslationMap; rangeChartWidgetSettingsForm: UntypedFormGroup; + pointLabelPreviewFn = this._pointLabelPreviewFn.bind(this); + tooltipValuePreviewFn = this._tooltipValuePreviewFn.bind(this); tooltipDatePreviewFn = this._tooltipDatePreviewFn.bind(this); @@ -64,7 +98,34 @@ export class RangeChartWidgetSettingsComponent extends WidgetSettingsComponent { dataZoom: [settings.dataZoom, []], rangeColors: [settings.rangeColors, []], outOfRangeColor: [settings.outOfRangeColor, []], + showRangeThresholds: [settings.showRangeThresholds, []], + rangeThreshold: [settings.rangeThreshold, []], fillArea: [settings.fillArea, []], + fillAreaOpacity: [settings.fillAreaOpacity, [Validators.min(0), Validators.max(1)]], + + showLine: [settings.showLine, []], + step: [settings.step, []], + stepType: [settings.stepType, []], + smooth: [settings.smooth, []], + lineType: [settings.lineType, []], + lineWidth: [settings.lineWidth, [Validators.min(0)]], + + showPoints: [settings.showPoints, []], + showPointLabel: [settings.showPointLabel, []], + pointLabelPosition: [settings.pointLabelPosition, []], + pointLabelFont: [settings.pointLabelFont, []], + pointLabelColor: [settings.pointLabelColor, []], + enablePointLabelBackground: [settings.enablePointLabelBackground, []], + pointLabelBackground: [settings.pointLabelBackground, []], + pointShape: [settings.pointShape, []], + pointSize: [settings.pointSize, [Validators.min(0)]], + + yAxis: [settings.yAxis, []], + xAxis: [settings.xAxis, []], + + thresholds: [settings.thresholds, []], + + animation: [settings.animation, []], showLegend: [settings.showLegend, []], legendPosition: [settings.legendPosition, []], @@ -83,19 +144,75 @@ export class RangeChartWidgetSettingsComponent extends WidgetSettingsComponent { tooltipBackgroundColor: [settings.tooltipBackgroundColor, []], tooltipBackgroundBlur: [settings.tooltipBackgroundBlur, []], - background: [settings.background, []] + background: [settings.background, []], + padding: [settings.padding, []] }); } protected validatorTriggers(): string[] { - return ['showLegend', 'showTooltip', 'tooltipShowDate']; + return ['showRangeThresholds', 'fillArea', 'showLine', 'step', 'showPointLabel', 'enablePointLabelBackground', + 'showLegend', 'showTooltip', 'tooltipShowDate']; } protected updateValidators(emitEvent: boolean) { + const showRangeThresholds: boolean = this.rangeChartWidgetSettingsForm.get('showRangeThresholds').value; + const fillArea: boolean = this.rangeChartWidgetSettingsForm.get('fillArea').value; + const showLine: boolean = this.rangeChartWidgetSettingsForm.get('showLine').value; + const step: boolean = this.rangeChartWidgetSettingsForm.get('step').value; + const showPointLabel: boolean = this.rangeChartWidgetSettingsForm.get('showPointLabel').value; + const enablePointLabelBackground: boolean = this.rangeChartWidgetSettingsForm.get('enablePointLabelBackground').value; const showLegend: boolean = this.rangeChartWidgetSettingsForm.get('showLegend').value; const showTooltip: boolean = this.rangeChartWidgetSettingsForm.get('showTooltip').value; const tooltipShowDate: boolean = this.rangeChartWidgetSettingsForm.get('tooltipShowDate').value; + if (showRangeThresholds) { + this.rangeChartWidgetSettingsForm.get('rangeThreshold').enable(); + } else { + this.rangeChartWidgetSettingsForm.get('rangeThreshold').disable(); + } + + if (fillArea) { + this.rangeChartWidgetSettingsForm.get('fillAreaOpacity').enable(); + } else { + this.rangeChartWidgetSettingsForm.get('fillAreaOpacity').disable(); + } + + if (showLine) { + this.rangeChartWidgetSettingsForm.get('step').enable({emitEvent: false}); + if (step) { + this.rangeChartWidgetSettingsForm.get('stepType').enable(); + this.rangeChartWidgetSettingsForm.get('smooth').disable(); + } else { + this.rangeChartWidgetSettingsForm.get('stepType').disable(); + this.rangeChartWidgetSettingsForm.get('smooth').enable(); + } + this.rangeChartWidgetSettingsForm.get('lineType').enable(); + this.rangeChartWidgetSettingsForm.get('lineWidth').enable(); + } else { + this.rangeChartWidgetSettingsForm.get('step').disable({emitEvent: false}); + this.rangeChartWidgetSettingsForm.get('stepType').disable(); + this.rangeChartWidgetSettingsForm.get('smooth').disable(); + this.rangeChartWidgetSettingsForm.get('lineType').disable(); + this.rangeChartWidgetSettingsForm.get('lineWidth').disable(); + } + if (showPointLabel) { + this.rangeChartWidgetSettingsForm.get('pointLabelPosition').enable(); + this.rangeChartWidgetSettingsForm.get('pointLabelFont').enable(); + this.rangeChartWidgetSettingsForm.get('pointLabelColor').enable(); + this.rangeChartWidgetSettingsForm.get('enablePointLabelBackground').enable({emitEvent: false}); + if (enablePointLabelBackground) { + this.rangeChartWidgetSettingsForm.get('pointLabelBackground').enable(); + } else { + this.rangeChartWidgetSettingsForm.get('pointLabelBackground').disable(); + } + } else { + this.rangeChartWidgetSettingsForm.get('pointLabelPosition').disable(); + this.rangeChartWidgetSettingsForm.get('pointLabelFont').disable(); + this.rangeChartWidgetSettingsForm.get('pointLabelColor').disable(); + this.rangeChartWidgetSettingsForm.get('enablePointLabelBackground').disable({emitEvent: false}); + this.rangeChartWidgetSettingsForm.get('pointLabelBackground').disable(); + } + if (showLegend) { this.rangeChartWidgetSettingsForm.get('legendPosition').enable(); this.rangeChartWidgetSettingsForm.get('legendLabelFont').enable(); @@ -136,6 +253,12 @@ export class RangeChartWidgetSettingsComponent extends WidgetSettingsComponent { } } + private _pointLabelPreviewFn(): string { + const units: string = this.widgetConfig.config.units; + const decimals: number = this.widgetConfig.config.decimals; + return formatValue(22, decimals, units, false); + } + private _tooltipValuePreviewFn(): string { const units: string = this.widgetConfig.config.units; const decimals: number = this.widgetConfig.config.decimals; @@ -148,5 +271,4 @@ export class RangeChartWidgetSettingsComponent extends WidgetSettingsComponent { processor.update(Date.now()); return processor.formatted; } - } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/time-series-chart-bar-settings.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/time-series-chart-bar-settings.component.html index 447784091e..ddbeaf2480 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/time-series-chart-bar-settings.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/time-series-chart-bar-settings.component.html @@ -59,6 +59,15 @@
+
+ + {{ 'widgets.time-series-chart.series.bar.label-background' | translate }} + + + +
{ this.updateModel(); }); merge(this.barSettingsFormGroup.get('showBorder').valueChanges, - this.barSettingsFormGroup.get('showLabel').valueChanges) + this.barSettingsFormGroup.get('showLabel').valueChanges, + this.barSettingsFormGroup.get('enableLabelBackground').valueChanges) .subscribe(() => { this.updateValidators(); }); @@ -116,6 +119,7 @@ export class TimeSeriesChartBarSettingsComponent implements OnInit, ControlValue private updateValidators() { const showBorder: boolean = this.barSettingsFormGroup.get('showBorder').value; const showLabel: boolean = this.barSettingsFormGroup.get('showLabel').value; + const enableLabelBackground: boolean = this.barSettingsFormGroup.get('enableLabelBackground').value; if (showBorder) { this.barSettingsFormGroup.get('borderWidth').enable({emitEvent: false}); } else { @@ -125,10 +129,18 @@ export class TimeSeriesChartBarSettingsComponent implements OnInit, ControlValue this.barSettingsFormGroup.get('labelPosition').enable({emitEvent: false}); this.barSettingsFormGroup.get('labelFont').enable({emitEvent: false}); this.barSettingsFormGroup.get('labelColor').enable({emitEvent: false}); + this.barSettingsFormGroup.get('enableLabelBackground').enable({emitEvent: false}); + if (enableLabelBackground) { + this.barSettingsFormGroup.get('labelBackground').enable({emitEvent: false}); + } else { + this.barSettingsFormGroup.get('labelBackground').disable({emitEvent: false}); + } } else { this.barSettingsFormGroup.get('labelPosition').disable({emitEvent: false}); this.barSettingsFormGroup.get('labelFont').disable({emitEvent: false}); this.barSettingsFormGroup.get('labelColor').disable({emitEvent: false}); + this.barSettingsFormGroup.get('enableLabelBackground').disable({emitEvent: false}); + this.barSettingsFormGroup.get('labelBackground').disable({emitEvent: false}); } } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/time-series-chart-line-settings.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/time-series-chart-line-settings.component.html index 78f1129727..cc3102c475 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/time-series-chart-line-settings.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/time-series-chart-line-settings.component.html @@ -97,12 +97,21 @@
+
+ + {{ 'widgets.time-series-chart.series.point.point-label-background' | translate }} + + + +
widgets.time-series-chart.series.point.point-shape
- - {{ timeSeriesChartShapeTranslations.get(shape) | translate }} + + {{ echartsShapeTranslations.get(shape) | translate }} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/time-series-chart-line-settings.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/time-series-chart-line-settings.component.ts index 84533b26e4..5b4701bf07 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/time-series-chart-line-settings.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/time-series-chart-line-settings.component.ts @@ -28,11 +28,11 @@ import { lineSeriesStepTypeTranslations, seriesLabelPositions, seriesLabelPositionTranslations, - timeSeriesChartShapes, - timeSeriesChartShapeTranslations, TimeSeriesChartType, + TimeSeriesChartType, timeSeriesLineTypes, timeSeriesLineTypeTranslations } from '@home/components/widget/lib/chart/time-series-chart.models'; +import { echartsShapes, echartsShapeTranslations } from '@home/components/widget/lib/chart/echarts-widget.models'; import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; import { merge } from 'rxjs'; @@ -67,9 +67,9 @@ export class TimeSeriesChartLineSettingsComponent implements OnInit, ControlValu seriesLabelPositionTranslations = seriesLabelPositionTranslations; - timeSeriesChartShapes = timeSeriesChartShapes; + echartsShapes = echartsShapes; - timeSeriesChartShapeTranslations = timeSeriesChartShapeTranslations; + echartsShapeTranslations = echartsShapeTranslations; pointLabelPreviewFn = this._pointLabelPreviewFn.bind(this); @@ -103,6 +103,8 @@ export class TimeSeriesChartLineSettingsComponent implements OnInit, ControlValu pointLabelPosition: [null, []], pointLabelFont: [null, []], pointLabelColor: [null, []], + enablePointLabelBackground: [null, []], + pointLabelBackground: [null, []], pointShape: [null, []], pointSize: [null, [Validators.min(0)]], fillAreaSettings: [null, []] @@ -112,7 +114,8 @@ export class TimeSeriesChartLineSettingsComponent implements OnInit, ControlValu }); merge(this.lineSettingsFormGroup.get('showLine').valueChanges, this.lineSettingsFormGroup.get('step').valueChanges, - this.lineSettingsFormGroup.get('showPointLabel').valueChanges) + this.lineSettingsFormGroup.get('showPointLabel').valueChanges, + this.lineSettingsFormGroup.get('enablePointLabelBackground').valueChanges) .subscribe(() => { this.updateValidators(); }); @@ -147,6 +150,7 @@ export class TimeSeriesChartLineSettingsComponent implements OnInit, ControlValu const showLine: boolean = this.lineSettingsFormGroup.get('showLine').value; const step: boolean = this.lineSettingsFormGroup.get('step').value; const showPointLabel: boolean = this.lineSettingsFormGroup.get('showPointLabel').value; + const enablePointLabelBackground: boolean = this.lineSettingsFormGroup.get('enablePointLabelBackground').value; if (showLine) { this.lineSettingsFormGroup.get('step').enable({emitEvent: false}); if (step) { @@ -169,10 +173,18 @@ export class TimeSeriesChartLineSettingsComponent implements OnInit, ControlValu this.lineSettingsFormGroup.get('pointLabelPosition').enable({emitEvent: false}); this.lineSettingsFormGroup.get('pointLabelFont').enable({emitEvent: false}); this.lineSettingsFormGroup.get('pointLabelColor').enable({emitEvent: false}); + this.lineSettingsFormGroup.get('enablePointLabelBackground').enable({emitEvent: false}); + if (enablePointLabelBackground) { + this.lineSettingsFormGroup.get('pointLabelBackground').enable({emitEvent: false}); + } else { + this.lineSettingsFormGroup.get('pointLabelBackground').disable({emitEvent: false}); + } } else { this.lineSettingsFormGroup.get('pointLabelPosition').disable({emitEvent: false}); this.lineSettingsFormGroup.get('pointLabelFont').disable({emitEvent: false}); this.lineSettingsFormGroup.get('pointLabelColor').disable({emitEvent: false}); + this.lineSettingsFormGroup.get('enablePointLabelBackground').disable({emitEvent: false}); + this.lineSettingsFormGroup.get('pointLabelBackground').disable({emitEvent: false}); } } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-axis-settings.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-axis-settings.component.html index 1d4bb95f5c..66b6c9896d 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-axis-settings.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-axis-settings.component.html @@ -56,13 +56,13 @@
-
+
widget-config.units-short
-
+
widget-config.decimals-short
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-axis-settings.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-axis-settings.component.ts index 8dafc7050f..ac49b8137d 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-axis-settings.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-axis-settings.component.ts @@ -70,6 +70,14 @@ export class TimeSeriesChartAxisSettingsComponent implements OnInit, ControlValu @coerceBoolean() advanced = false; + @Input() + @coerceBoolean() + hideUnits = false; + + @Input() + @coerceBoolean() + hideDecimals = false; + private modelValue: TimeSeriesChartXAxisSettings | TimeSeriesChartYAxisSettings; private propagateChange = null; diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/time-series-chart-fill-settings.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-fill-settings.component.html similarity index 96% rename from ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/time-series-chart-fill-settings.component.html rename to ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-fill-settings.component.html index 934f985483..99e3c1be8f 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/time-series-chart-fill-settings.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-fill-settings.component.html @@ -27,8 +27,8 @@
widgets.time-series-chart.series.opacity
- +
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/time-series-chart-fill-settings.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-fill-settings.component.ts similarity index 97% rename from ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/time-series-chart-fill-settings.component.ts rename to ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-fill-settings.component.ts index 44cc870f5b..dd7f4dd7f6 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/time-series-chart-fill-settings.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-fill-settings.component.ts @@ -34,7 +34,7 @@ import { AppState } from '@core/core.state'; @Component({ selector: 'tb-time-series-chart-fill-settings', templateUrl: './time-series-chart-fill-settings.component.html', - styleUrls: ['./../widget-settings.scss'], + styleUrls: ['./../../widget-settings.scss'], providers: [ { provide: NG_VALUE_ACCESSOR, @@ -73,7 +73,7 @@ export class TimeSeriesChartFillSettingsComponent implements OnInit, ControlValu ngOnInit(): void { this.fillSettingsFormGroup = this.fb.group({ type: [null, []], - opacity: [null, [Validators.min(0), Validators.max(100)]], + opacity: [null, [Validators.min(0), Validators.max(1)]], gradient: this.fb.group({ start: [null, [Validators.min(0), Validators.max(100)]], end: [null, [Validators.min(0), Validators.max(100)]] diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-threshold-row.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-threshold-row.component.html index 7e2c9f74c2..d0ea8f0ef3 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-threshold-row.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-threshold-row.component.html @@ -73,7 +73,7 @@ [formControl]="entityKeyFormControl">
-
+
@@ -98,14 +98,13 @@
- + + + + + diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-threshold-settings.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-threshold-settings.component.ts new file mode 100644 index 0000000000..6908b086af --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-threshold-settings.component.ts @@ -0,0 +1,124 @@ +/// +/// Copyright © 2016-2024 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component, forwardRef, Input, OnInit, Renderer2, ViewContainerRef } from '@angular/core'; +import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; +import { MatButton } from '@angular/material/button'; +import { TbPopoverService } from '@shared/components/popover.service'; +import { deepClone } from '@core/utils'; +import { coerceBoolean } from '@shared/decorators/coercion'; +import { WidgetConfig } from '@shared/models/widget.models'; +import { + TimeSeriesChartThreshold, + TimeSeriesChartYAxisId +} from '@home/components/widget/lib/chart/time-series-chart.models'; +import { + TimeSeriesChartThresholdSettingsPanelComponent +} from '@home/components/widget/lib/settings/common/chart/time-series-chart-threshold-settings-panel.component'; + +@Component({ + selector: 'tb-time-series-chart-threshold-settings', + templateUrl: './time-series-chart-threshold-settings.component.html', + styleUrls: [], + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => TimeSeriesChartThresholdSettingsComponent), + multi: true + } + ] +}) +export class TimeSeriesChartThresholdSettingsComponent implements OnInit, ControlValueAccessor { + + @Input() + disabled: boolean; + + @Input() + widgetConfig: WidgetConfig; + + @Input() + yAxisIds: TimeSeriesChartYAxisId[]; + + @Input() + @coerceBoolean() + hideYAxis = false; + + @Input() + @coerceBoolean() + boxButton = false; + + @Input() + icon = 'settings'; + + @Input() + title = 'widgets.time-series-chart.threshold.threshold-settings'; + + private modelValue: Partial; + + private propagateChange = null; + + constructor(private popoverService: TbPopoverService, + private renderer: Renderer2, + private viewContainerRef: ViewContainerRef) {} + + ngOnInit(): void { + } + + registerOnChange(fn: any): void { + this.propagateChange = fn; + } + + registerOnTouched(fn: any): void { + } + + setDisabledState(isDisabled: boolean): void { + this.disabled = isDisabled; + } + + writeValue(value: Partial): void { + this.modelValue = value; + } + + openThresholdSettingsPopup($event: Event, matButton: MatButton) { + if ($event) { + $event.stopPropagation(); + } + const trigger = matButton._elementRef.nativeElement; + if (this.popoverService.hasPopover(trigger)) { + this.popoverService.hidePopover(trigger); + } else { + const ctx: any = { + thresholdSettings: deepClone(this.modelValue), + panelTitle: this.title, + widgetConfig: this.widgetConfig, + hideYAxis: this.hideYAxis, + yAxisIds: this.yAxisIds + }; + const thresholdSettingsPanelPopover = this.popoverService.displayPopover(trigger, this.renderer, + this.viewContainerRef, TimeSeriesChartThresholdSettingsPanelComponent, ['leftOnly', 'leftTopOnly', 'leftBottomOnly'], true, null, + ctx, + {}, + {}, {}, true); + thresholdSettingsPanelPopover.tbComponentRef.instance.popover = thresholdSettingsPanelPopover; + thresholdSettingsPanelPopover.tbComponentRef.instance.thresholdSettingsApplied.subscribe((thresholdSettings) => { + thresholdSettingsPanelPopover.hide(); + this.modelValue = thresholdSettings; + this.propagateChange(this.modelValue); + }); + } + } + +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-thresholds-panel.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-thresholds-panel.component.html index e975eb4653..ba0325da93 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-thresholds-panel.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-thresholds-panel.component.html @@ -21,7 +21,7 @@
widgets.time-series-chart.threshold.source
widgets.time-series-chart.threshold.key-value
-
widgets.time-series-chart.axis.y-axis
+
widgets.time-series-chart.axis.y-axis
widgets.color.color
widget-config.units-short
widget-config.decimals-short
@@ -31,6 +31,7 @@
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-thresholds-panel.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-thresholds-panel.component.ts index 9cb5e5ced2..95e3b242c3 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-thresholds-panel.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-thresholds-panel.component.ts @@ -38,6 +38,7 @@ import { IAliasController } from '@core/api/widget-api.models'; import { DataKeysCallbacks } from '@home/components/widget/config/data-keys.component.models'; import { DataKey, Datasource, WidgetConfig } from '@shared/models/widget.models'; import { DataKeyType } from '@shared/models/telemetry/telemetry.models'; +import { coerceBoolean } from '@shared/decorators/coercion'; @Component({ selector: 'tb-time-series-chart-thresholds-panel', @@ -77,6 +78,10 @@ export class TimeSeriesChartThresholdsPanelComponent implements ControlValueAcce @Input() yAxisIds: TimeSeriesChartYAxisId[]; + @Input() + @coerceBoolean() + hideYAxis = false; + thresholdsFormGroup: UntypedFormGroup; private propagateChange = (_val: any) => {}; diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-no-aggregation-bar-width-settings.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-no-aggregation-bar-width-settings.component.html index 239c6ea18c..28c79a1310 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-no-aggregation-bar-width-settings.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-no-aggregation-bar-width-settings.component.html @@ -15,7 +15,7 @@ limitations under the License. --> -
+
widgets.time-series-chart.no-aggregation-bar-width-strategy
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-no-aggregation-bar-width-settings.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-no-aggregation-bar-width-settings.component.ts index 36d8cb917a..8601c48159 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-no-aggregation-bar-width-settings.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-no-aggregation-bar-width-settings.component.ts @@ -29,6 +29,7 @@ import { timeSeriesChartNoAggregationBarWidthStrategyTranslations } from '@home/components/widget/lib/chart/time-series-chart.models'; import { merge } from 'rxjs'; +import { coerceBoolean } from '@shared/decorators/coercion'; @Component({ selector: 'tb-time-series-no-aggregation-bar-width-settings', @@ -53,6 +54,10 @@ export class TimeSeriesNoAggregationBarWidthSettingsComponent implements OnInit, @Input() disabled: boolean; + @Input() + @coerceBoolean() + stroked = false; + private modelValue: TimeSeriesChartNoAggregationBarWidthSettings; private propagateChange = null; diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/widget-settings-common.module.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/widget-settings-common.module.ts index 5ed50a226f..86e460ff9d 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/widget-settings-common.module.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/widget-settings-common.module.ts @@ -127,6 +127,12 @@ import { import { AutoDateFormatSettingsComponent } from '@home/components/widget/lib/settings/common/auto-date-format-settings.component'; +import { + TimeSeriesChartFillSettingsComponent +} from '@home/components/widget/lib/settings/common/chart/time-series-chart-fill-settings.component'; +import { + TimeSeriesChartThresholdSettingsComponent +} from '@home/components/widget/lib/settings/common/chart/time-series-chart-threshold-settings.component'; @NgModule({ declarations: [ @@ -174,6 +180,8 @@ import { TimeSeriesChartYAxisRowComponent, TimeSeriesChartYAxisSettingsPanelComponent, TimeSeriesChartAnimationSettingsComponent, + TimeSeriesChartFillSettingsComponent, + TimeSeriesChartThresholdSettingsComponent, DataKeyInputComponent, EntityAliasInputComponent ], @@ -227,6 +235,8 @@ import { TimeSeriesChartYAxisRowComponent, TimeSeriesChartYAxisSettingsPanelComponent, TimeSeriesChartAnimationSettingsComponent, + TimeSeriesChartFillSettingsComponent, + TimeSeriesChartThresholdSettingsComponent, DataKeyInputComponent, EntityAliasInputComponent ], diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/widget-settings.module.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/widget-settings.module.ts index 8522734eda..5b83f859e1 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/widget-settings.module.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/widget-settings.module.ts @@ -336,9 +336,6 @@ import { import { TimeSeriesChartLineSettingsComponent } from '@home/components/widget/lib/settings/chart/time-series-chart-line-settings.component'; -import { - TimeSeriesChartFillSettingsComponent -} from '@home/components/widget/lib/settings/chart/time-series-chart-fill-settings.component'; import { TimeSeriesChartBarSettingsComponent } from '@home/components/widget/lib/settings/chart/time-series-chart-bar-settings.component'; @@ -467,7 +464,6 @@ import { TimeSeriesChartKeySettingsComponent, TimeSeriesChartLineSettingsComponent, TimeSeriesChartBarSettingsComponent, - TimeSeriesChartFillSettingsComponent, TimeSeriesChartWidgetSettingsComponent ], imports: [ @@ -596,7 +592,6 @@ import { TimeSeriesChartKeySettingsComponent, TimeSeriesChartLineSettingsComponent, TimeSeriesChartBarSettingsComponent, - TimeSeriesChartFillSettingsComponent, TimeSeriesChartWidgetSettingsComponent ] }) diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index a317b9b89d..be0757fa13 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -5423,7 +5423,7 @@ "bar-appearance": "Bar appearance", "label-on-bar": "Label on bar", "value-on-bar": "Value on bar", - "bar-chart-card-style": "Bar chart card style" + "bar-chart-style": "Bar chart style" }, "battery-level": { "layout": "Layout", @@ -6173,10 +6173,14 @@ "range-chart": { "chart": "Chart", "data-zoom": "Data zoom", + "range-chart-appearance": "Range chart appearance", "range-colors": "Range colors", "out-of-range-color": "Out of range color", + "show-range-thresholds": "Show range thresholds", + "range-thresholds-settings": "Range thresholds settings", "fill-area": "Fill area", - "range-chart-card-style": "Range chart card style" + "fill-area-opacity": "Fill area opacity", + "range-chart-style": "Range chart style" }, "rpc": { "value-settings": "Value settings", @@ -6770,7 +6774,8 @@ "label-position-inside-middle-bottom": "Inside middle bottom", "label-position-inside-end": "Inside end", "label-position-inside-end-top": "Inside end top", - "label-position-inside-end-bottom": "Inside end bottom" + "label-position-inside-end-bottom": "Inside end bottom", + "label-background": "Label background" }, "axis": { "axes": "Axes", @@ -6851,6 +6856,7 @@ "show-points": "Show points", "point-label": "Point label", "point-label-hint": "Display label with value over the series point.", + "point-label-background": "Point label background", "point-shape": "Point shape", "point-size": "Point size" }, @@ -6859,7 +6865,8 @@ "border-width": "Border width", "border-radius": "Border radius", "label": "Label", - "label-hint": "Display label with value over the bar." + "label-hint": "Display label with value over the bar.", + "label-background": "Label background" } } }, diff --git a/ui-ngx/src/assets/locale/locale.constant-pl_PL.json b/ui-ngx/src/assets/locale/locale.constant-pl_PL.json index 014666017c..58d4a0168f 100644 --- a/ui-ngx/src/assets/locale/locale.constant-pl_PL.json +++ b/ui-ngx/src/assets/locale/locale.constant-pl_PL.json @@ -5172,7 +5172,7 @@ "bar-appearance":"Wygląd słupka", "label-on-bar":"Etykieta na słupku", "value-on-bar":"Wartość na słupku", - "bar-chart-card-style":"Styl karty wykresu słupkowego" + "bar-chart-style":"Styl wykresu słupkowego" }, "battery-level":{ "layout":"Układ", @@ -5924,7 +5924,7 @@ "range-colors":"Kolory zakresu", "out-of-range-color":"Kolor poza zakresem", "fill-area":"Wypełnij obszar", - "range-chart-card-style":"Styl karty wykresu zakresu" + "range-chart-style":"Styl wykresu zakresu" }, "rpc":{ "value-settings":"Ustawienia wartości", diff --git a/ui-ngx/src/assets/locale/locale.constant-zh_CN.json b/ui-ngx/src/assets/locale/locale.constant-zh_CN.json index fdc0274cdf..886151fdf2 100644 --- a/ui-ngx/src/assets/locale/locale.constant-zh_CN.json +++ b/ui-ngx/src/assets/locale/locale.constant-zh_CN.json @@ -5491,7 +5491,7 @@ "range-colors": "范围颜色", "out-of-range-color": "超出范围颜色", "fill-area": "填充区域", - "range-chart-card-style": "范围图表卡片样式" + "range-chart-style": "范围图样式" }, "rpc": { "value-settings": "值设置",