Browse Source

Merge pull request #10473 from thingsboard/feature/echarts-refactor

Update bar chart with labels and range chart widgets according to current time series widgets family
pull/10386/head
Igor Kulikov 2 years ago
committed by GitHub
parent
commit
f31d4bb831
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 85
      ui-ngx/src/app/modules/home/components/widget/config/basic/chart/bar-chart-with-labels-basic-config.component.html
  2. 43
      ui-ngx/src/app/modules/home/components/widget/config/basic/chart/bar-chart-with-labels-basic-config.component.ts
  3. 175
      ui-ngx/src/app/modules/home/components/widget/config/basic/chart/range-chart-basic-config.component.html
  4. 177
      ui-ngx/src/app/modules/home/components/widget/config/basic/chart/range-chart-basic-config.component.ts
  5. 15
      ui-ngx/src/app/modules/home/components/widget/lib/chart/bar-chart-with-labels-widget.component.html
  6. 11
      ui-ngx/src/app/modules/home/components/widget/lib/chart/bar-chart-with-labels-widget.component.scss
  7. 397
      ui-ngx/src/app/modules/home/components/widget/lib/chart/bar-chart-with-labels-widget.component.ts
  8. 149
      ui-ngx/src/app/modules/home/components/widget/lib/chart/bar-chart-with-labels-widget.models.ts
  9. 144
      ui-ngx/src/app/modules/home/components/widget/lib/chart/echarts-widget.models.ts
  10. 3
      ui-ngx/src/app/modules/home/components/widget/lib/chart/range-chart-widget.component.html
  11. 11
      ui-ngx/src/app/modules/home/components/widget/lib/chart/range-chart-widget.component.scss
  12. 340
      ui-ngx/src/app/modules/home/components/widget/lib/chart/range-chart-widget.component.ts
  13. 249
      ui-ngx/src/app/modules/home/components/widget/lib/chart/range-chart-widget.models.ts
  14. 30
      ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart-bar.models.ts
  15. 344
      ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart.models.ts
  16. 67
      ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart.ts
  17. 138
      ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/bar-chart-with-labels-widget-settings.component.html
  18. 36
      ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/bar-chart-with-labels-widget-settings.component.ts
  19. 196
      ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/range-chart-widget-settings.component.html
  20. 130
      ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/range-chart-widget-settings.component.ts
  21. 9
      ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/time-series-chart-bar-settings.component.html
  22. 14
      ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/time-series-chart-bar-settings.component.ts
  23. 13
      ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/time-series-chart-line-settings.component.html
  24. 22
      ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/time-series-chart-line-settings.component.ts
  25. 4
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-axis-settings.component.html
  26. 8
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-axis-settings.component.ts
  27. 4
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-fill-settings.component.html
  28. 4
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-fill-settings.component.ts
  29. 17
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-threshold-row.component.html
  30. 78
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-threshold-row.component.ts
  31. 21
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-threshold-settings-panel.component.html
  32. 45
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-threshold-settings-panel.component.ts
  33. 39
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-threshold-settings.component.html
  34. 124
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-threshold-settings.component.ts
  35. 3
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-thresholds-panel.component.html
  36. 5
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-thresholds-panel.component.ts
  37. 2
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-no-aggregation-bar-width-settings.component.html
  38. 5
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-no-aggregation-bar-width-settings.component.ts
  39. 10
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/widget-settings-common.module.ts
  40. 5
      ui-ngx/src/app/modules/home/components/widget/lib/settings/widget-settings.module.ts
  41. 15
      ui-ngx/src/assets/locale/locale.constant-en_US.json
  42. 4
      ui-ngx/src/assets/locale/locale.constant-pl_PL.json
  43. 2
      ui-ngx/src/assets/locale/locale.constant-zh_CN.json

85
ui-ngx/src/app/modules/home/components/widget/config/basic/chart/bar-chart-with-labels-basic-config.component.html

@ -84,8 +84,28 @@
</div>
</div>
</div>
<div class="tb-form-panel">
<div class="tb-form-panel-title" translate>widgets.time-series-chart.chart</div>
<div class="tb-form-row">
<mat-slide-toggle class="mat-slide" formControlName="dataZoom">
{{ 'widgets.time-series-chart.data-zoom' | translate }}
</mat-slide-toggle>
</div>
</div>
<div class="tb-form-panel">
<div class="tb-form-panel-title" translate>widgets.bar-chart.bar-appearance</div>
<div class="tb-form-row space-between">
<div translate>widget-config.units-short</div>
<tb-unit-input
formControlName="units">
</tb-unit-input>
</div>
<div class="tb-form-row space-between">
<div translate>widget-config.decimals-short</div>
<mat-form-field appearance="outline" class="number" subscriptSizing="dynamic">
<input matInput formControlName="decimals" type="number" min="0" max="15" step="1" placeholder="{{ 'widget-config.set' | translate }}">
</mat-form-field>
</div>
<div class="tb-form-row space-between">
<mat-slide-toggle class="mat-slide fixed-title-width" formControlName="showBarLabel">
{{ 'widgets.bar-chart.label-on-bar' | translate }}
@ -118,19 +138,57 @@
</tb-color-input>
</div>
</div>
<div class="tb-form-row space-between">
<div translate>widget-config.units-short</div>
<tb-unit-input
formControlName="units">
</tb-unit-input>
<div class="tb-form-row">
<mat-slide-toggle class="mat-slide" formControlName="showBarBorder">
{{ 'widgets.time-series-chart.series.bar.show-border' | translate }}
</mat-slide-toggle>
</div>
<div class="tb-form-row space-between">
<div translate>widget-config.decimals-short</div>
<mat-form-field appearance="outline" class="number" subscriptSizing="dynamic">
<input matInput formControlName="decimals" type="number" min="0" max="15" step="1" placeholder="{{ 'widget-config.set' | translate }}">
<div class="tb-form-row space-between column-xs">
<div translate>widgets.time-series-chart.series.bar.border-width</div>
<mat-form-field class="medium-width number" appearance="outline" subscriptSizing="dynamic">
<input matInput type="number" formControlName="barBorderWidth" min="0" step="1" placeholder="{{ 'widget-config.set' | translate }}"/>
</mat-form-field>
</div>
<div class="tb-form-row space-between column-xs">
<div translate>widgets.time-series-chart.series.bar.border-radius</div>
<mat-form-field class="medium-width number" appearance="outline" subscriptSizing="dynamic">
<input matInput type="number" formControlName="barBorderRadius" min="0" step="1" placeholder="{{ 'widget-config.set' | translate }}"/>
</mat-form-field>
</div>
<tb-time-series-chart-fill-settings
formControlName="barBackgroundSettings"
title="widgets.time-series-chart.series.background"
fillNoneTitle="widgets.time-series-chart.series.fill-type-solid">
</tb-time-series-chart-fill-settings>
<tb-time-series-no-aggregation-bar-width-settings
stroked
formControlName="noAggregationBarWidthSettings">
</tb-time-series-no-aggregation-bar-width-settings>
</div>
<div class="tb-form-panel">
<div class="tb-form-panel-title" translate>widgets.time-series-chart.axis.y-axis</div>
<tb-time-series-chart-axis-settings
formControlName="yAxis"
axisType="yAxis"
hideUnits
hideDecimals>
</tb-time-series-chart-axis-settings>
</div>
<div class="tb-form-panel">
<div class="tb-form-panel-title" translate>widgets.time-series-chart.axis.x-axis</div>
<tb-time-series-chart-axis-settings
formControlName="xAxis"
axisType="xAxis">
</tb-time-series-chart-axis-settings>
</div>
<tb-time-series-chart-thresholds-panel
formControlName="thresholds"
[aliasController]="aliasController"
[dataKeyCallbacks]="callbacks"
[datasource]="datasource"
[widgetConfig]="widgetConfig?.config"
hideYAxis>
</tb-time-series-chart-thresholds-panel>
<div class="tb-form-panel tb-slide-toggle">
<mat-expansion-panel class="tb-settings" [expanded]="barChartWidgetConfigForm.get('showLegend').value" [disabled]="!barChartWidgetConfigForm.get('showLegend').value">
<mat-expansion-panel-header fxLayout="row wrap">
@ -230,6 +288,9 @@
</ng-template>
</mat-expansion-panel>
</div>
<tb-time-series-chart-animation-settings
formControlName="animation">
</tb-time-series-chart-animation-settings>
<div class="tb-form-panel">
<div class="tb-form-panel-title" translate>widget-config.card-appearance</div>
<div class="tb-form-row space-between">
@ -249,6 +310,12 @@
<input matInput formControlName="borderRadius" placeholder="{{ 'widget-config.set' | translate }}">
</mat-form-field>
</div>
<div class="tb-form-row space-between">
<div>{{ 'widget-config.card-padding' | translate }}</div>
<mat-form-field appearance="outline" subscriptSizing="dynamic">
<input matInput formControlName="padding" placeholder="{{ 'widget-config.set' | translate }}">
</mat-form-field>
</div>
</div>
<tb-widget-actions-panel
formControlName="actions">

43
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;
}

175
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 }}
</mat-slide-toggle>
</div>
</div>
<div class="tb-form-panel">
<div class="tb-form-panel-title" translate>widgets.range-chart.range-chart-appearance</div>
<div class="tb-form-row space-between">
<div translate>widget-config.units-short</div>
<tb-unit-input
formControlName="units">
</tb-unit-input>
</div>
<div class="tb-form-row space-between">
<div translate>widget-config.decimals-short</div>
<mat-form-field appearance="outline" class="number" subscriptSizing="dynamic">
<input matInput formControlName="decimals" type="number" min="0" max="15" step="1" placeholder="{{ 'widget-config.set' | translate }}">
</mat-form-field>
</div>
<div class="tb-form-row space-between">
<div>{{ 'widgets.range-chart.range-colors' | translate }}</div>
<tb-color-range-settings formControlName="rangeColors">
@ -87,24 +102,155 @@
formControlName="outOfRangeColor">
</tb-color-input>
</div>
<div class="tb-form-row space-between">
<mat-slide-toggle class="mat-slide" formControlName="showRangeThresholds">
{{ 'widgets.range-chart.show-range-thresholds' | translate }}
</mat-slide-toggle>
<tb-time-series-chart-threshold-settings
boxButton
hideYAxis
[widgetConfig]="widgetConfig?.config"
title="widgets.range-chart.range-thresholds-settings"
formControlName="rangeThreshold">
</tb-time-series-chart-threshold-settings>
</div>
<div class="tb-form-row">
<mat-slide-toggle class="mat-slide" formControlName="fillArea">
{{ 'widgets.range-chart.fill-area' | translate }}
</mat-slide-toggle>
</div>
<div class="tb-form-row space-between">
<div translate>widget-config.units-short</div>
<tb-unit-input
formControlName="units">
</tb-unit-input>
</div>
<div class="tb-form-row space-between">
<div translate>widget-config.decimals-short</div>
<mat-form-field appearance="outline" class="number" subscriptSizing="dynamic">
<input matInput formControlName="decimals" type="number" min="0" max="15" step="1" placeholder="{{ 'widget-config.set' | translate }}">
<div class="tb-form-row space-between column-xs">
<div translate>widgets.range-chart.fill-area-opacity</div>
<mat-form-field class="medium-width number" appearance="outline" subscriptSizing="dynamic">
<input matInput type="number" formControlName="fillAreaOpacity" min="0" max="1"
step="0.1" placeholder="{{ 'widget-config.set' | translate }}"/>
</mat-form-field>
</div>
<div class="tb-form-panel stroked">
<div class="tb-form-panel-title" translate>widgets.time-series-chart.series.line.line</div>
<div class="tb-form-row">
<mat-slide-toggle class="mat-slide" formControlName="showLine">
{{ 'widgets.time-series-chart.series.line.show-line' | translate }}
</mat-slide-toggle>
</div>
<div class="tb-form-row space-between column-xs">
<mat-slide-toggle class="mat-slide" formControlName="step">
{{ 'widgets.time-series-chart.series.line.step-line' | translate }}
</mat-slide-toggle>
<mat-form-field class="medium-width" appearance="outline" subscriptSizing="dynamic">
<mat-select formControlName="stepType">
<mat-option *ngFor="let stepType of lineSeriesStepTypes" [value]="stepType">
{{ lineSeriesStepTypeTranslations.get(stepType) | translate }}
</mat-option>
</mat-select>
</mat-form-field>
</div>
<div class="tb-form-row">
<mat-slide-toggle class="mat-slide" formControlName="smooth">
{{ 'widgets.time-series-chart.series.line.smooth-line' | translate }}
</mat-slide-toggle>
</div>
<div class="tb-form-row space-between column-xs">
<div translate>widgets.time-series-chart.line-type</div>
<mat-form-field class="medium-width" appearance="outline" subscriptSizing="dynamic">
<mat-select formControlName="lineType">
<mat-option *ngFor="let lineType of timeSeriesLineTypes" [value]="lineType">
{{ timeSeriesLineTypeTranslations.get(lineType) | translate }}
</mat-option>
</mat-select>
</mat-form-field>
</div>
<div class="tb-form-row space-between column-xs">
<div translate>widgets.time-series-chart.line-width</div>
<mat-form-field class="medium-width number" appearance="outline" subscriptSizing="dynamic">
<input matInput type="number" formControlName="lineWidth" min="0" step="1" placeholder="{{ 'widget-config.set' | translate }}"/>
</mat-form-field>
</div>
</div>
<div class="tb-form-panel stroked">
<div class="tb-form-panel-title" translate>widgets.time-series-chart.series.point.points</div>
<div class="tb-form-row">
<mat-slide-toggle class="mat-slide" formControlName="showPoints">
{{ 'widgets.time-series-chart.series.point.show-points' | translate }}
</mat-slide-toggle>
</div>
<div class="tb-form-row space-between column-lt-md">
<mat-slide-toggle class="mat-slide" formControlName="showPointLabel">
<div tb-hint-tooltip-icon="{{'widgets.time-series-chart.series.point.point-label-hint' | translate}}">
{{ 'widgets.time-series-chart.series.point.point-label' | translate }}
</div>
</mat-slide-toggle>
<div fxLayout="row" fxFlex.lt-md fxLayoutAlign="start center" fxLayoutGap="8px">
<mat-form-field class="medium-width" fxFlex.lt-md appearance="outline" subscriptSizing="dynamic">
<mat-select formControlName="pointLabelPosition">
<mat-option *ngFor="let position of seriesLabelPositions" [value]="position">
{{ seriesLabelPositionTranslations.get(position) | translate }}
</mat-option>
</mat-select>
</mat-form-field>
<tb-font-settings formControlName="pointLabelFont"
clearButton
disabledLineHeight
forceSizeUnit="px"
[previewText]="pointLabelPreviewFn">
</tb-font-settings>
<tb-color-input asBoxInput
colorClearButton
formControlName="pointLabelColor">
</tb-color-input>
</div>
</div>
<div class="tb-form-row space-between">
<mat-slide-toggle class="mat-slide" formControlName="enablePointLabelBackground">
{{ 'widgets.time-series-chart.series.point.point-label-background' | translate }}
</mat-slide-toggle>
<tb-color-input asBoxInput
colorClearButton
formControlName="pointLabelBackground">
</tb-color-input>
</div>
<div class="tb-form-row space-between column-xs">
<div translate>widgets.time-series-chart.series.point.point-shape</div>
<mat-form-field class="medium-width" appearance="outline" subscriptSizing="dynamic">
<mat-select formControlName="pointShape">
<mat-option *ngFor="let shape of echartsShapes" [value]="shape">
{{ echartsShapeTranslations.get(shape) | translate }}
</mat-option>
</mat-select>
</mat-form-field>
</div>
<div class="tb-form-row space-between column-xs">
<div translate>widgets.time-series-chart.series.point.point-size</div>
<mat-form-field class="medium-width number" appearance="outline" subscriptSizing="dynamic">
<input matInput type="number" formControlName="pointSize" min="0" step="1" placeholder="{{ 'widget-config.set' | translate }}"/>
</mat-form-field>
</div>
</div>
</div>
<div class="tb-form-panel">
<div class="tb-form-panel-title" translate>widgets.time-series-chart.axis.y-axis</div>
<tb-time-series-chart-axis-settings
formControlName="yAxis"
axisType="yAxis"
hideUnits
hideDecimals>
</tb-time-series-chart-axis-settings>
</div>
<div class="tb-form-panel">
<div class="tb-form-panel-title" translate>widgets.time-series-chart.axis.x-axis</div>
<tb-time-series-chart-axis-settings
formControlName="xAxis"
axisType="xAxis">
</tb-time-series-chart-axis-settings>
</div>
<tb-time-series-chart-thresholds-panel
formControlName="thresholds"
[aliasController]="aliasController"
[dataKeyCallbacks]="callbacks"
[datasource]="datasource"
[widgetConfig]="widgetConfig?.config"
hideYAxis>
</tb-time-series-chart-thresholds-panel>
<div class="tb-form-panel tb-slide-toggle">
<mat-expansion-panel class="tb-settings" [expanded]="rangeChartWidgetConfigForm.get('showLegend').value" [disabled]="!rangeChartWidgetConfigForm.get('showLegend').value">
<mat-expansion-panel-header fxLayout="row wrap">
@ -204,6 +350,9 @@
</ng-template>
</mat-expansion-panel>
</div>
<tb-time-series-chart-animation-settings
formControlName="animation">
</tb-time-series-chart-animation-settings>
<div class="tb-form-panel">
<div class="tb-form-panel-title" translate>widget-config.card-appearance</div>
<div class="tb-form-row space-between">
@ -223,6 +372,12 @@
<input matInput formControlName="borderRadius" placeholder="{{ 'widget-config.set' | translate }}">
</mat-form-field>
</div>
<div class="tb-form-row space-between">
<div>{{ 'widget-config.card-padding' | translate }}</div>
<mat-form-field appearance="outline" subscriptSizing="dynamic">
<input matInput formControlName="padding" placeholder="{{ 'widget-config.set' | translate }}">
</mat-form-field>
</div>
</div>
<tb-widget-actions-panel
formControlName="actions">

177
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;

15
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.
-->
<div class="tb-bar-chart-panel" [style]="backgroundStyle$ | async">
<div class="tb-bar-chart-panel" [style.padding]="padding"
[class.overlay]="overlayEnabled" [style]="backgroundStyle$ | async">
<div class="tb-bar-chart-overlay" [style]="overlayStyle"></div>
<ng-container *ngTemplateOutlet="widgetTitlePanel"></ng-container>
<div class="tb-bar-chart-content" [class]="legendClass">
<div #chartShape class="tb-bar-chart-shape">
</div>
<div *ngIf="showLegend" class="tb-bar-chart-legend">
<div class="tb-bar-chart-legend-item" *ngFor="let legendItem of legendItems"
(mouseenter)="onLegendItemEnter(legendItem)"
(mouseleave)="onLegendItemLeave(legendItem)"
(click)="toggleLegendItem(legendItem)">
<div class="tb-bar-chart-legend-item" *ngFor="let legendKey of legendKeys"
(mouseenter)="onLegendKeyEnter(legendKey)"
(mouseleave)="onLegendKeyLeave(legendKey)"
(click)="toggleLegendKey(legendKey)">
<div class="tb-bar-chart-legend-item-label">
<div class="tb-bar-chart-legend-item-label-circle" [style]="{background: legendItem.enabled ? legendItem.color : null}"></div>
<div [style]="legendItem.enabled ? legendLabelStyle : disabledLegendLabelStyle">{{ legendItem.label }}</div>
<div class="tb-bar-chart-legend-item-label-circle" [style]="{background: !legendKey.hidden ? legendKey.color : null}"></div>
<div [style]="!legendKey.hidden ? legendLabelStyle : disabledLegendLabelStyle">{{ legendKey.label }}</div>
</div>
</div>
</div>

11
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;

397
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<ComponentStyle>;
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<CustomSeriesOption> {
const series: Array<CustomSeriesOption> = [];
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);
}
}

149
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<TimeSeriesChartSettings> => ({
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<TimeSeriesChartKeySettings> => {
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
}
}
}
};
};

144
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, string>(
[
[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, EChartsShapeOffsetFunction>(
[
[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<LineSeriesOption>;
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<EChartsTooltipTrigger, strin
export interface EChartsTooltipWidgetSettings {
showTooltip: boolean;
tooltipTrigger?: EChartsTooltipTrigger;
tooltipShowFocusedSeries?: boolean;
tooltipValueFont: Font;
tooltipValueColor: string;
tooltipShowDate: boolean;

3
ui-ngx/src/app/modules/home/components/widget/lib/chart/range-chart-widget.component.html

@ -15,7 +15,8 @@
limitations under the License.
-->
<div class="tb-range-chart-panel" [style]="backgroundStyle$ | async">
<div class="tb-range-chart-panel" [style.padding]="padding"
[class.overlay]="overlayEnabled" [style]="backgroundStyle$ | async">
<div class="tb-range-chart-overlay" [style]="overlayStyle"></div>
<ng-container *ngTemplateOutlet="widgetTitlePanel"></ng-container>
<div class="tb-range-chart-content" [class]="legendClass">

11
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;

340
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<ColorRange>): 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<RangeItem>): number[] => {
const points = new Set<number>();
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<ComponentStyle>;
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);
}
}

249
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<ColorRange>;
outOfRangeColor: string;
showRangeThresholds: boolean;
rangeThreshold: Partial<TimeSeriesChartThreshold>;
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<TimeSeriesChartThreshold>,
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<TimeSeriesChartSettings> => {
let thresholds: DeepPartial<TimeSeriesChartThreshold>[] = settings.showRangeThresholds ? getMarkPoints(rangeItems).map(item => ({
...{type: TimeSeriesChartThresholdType.constant,
yAxisId: 'default',
units,
decimals,
value: item},
...settings.rangeThreshold
} as DeepPartial<TimeSeriesChartThreshold>)) : [];
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<TimeSeriesChartKeySettings> => ({
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<ColorRange>): 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<RangeItem>): number[] => {
const points = new Set<number>();
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();
};

30
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[];

344
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<TimeSeriesChartType, stri
]
);
const timeSeriesChartColorScheme: TbColorScheme = {
export const timeSeriesChartColorScheme: TbColorScheme = {
'threshold.line': {
light: 'rgba(0, 0, 0, 0.76)',
dark: '#eee'
@ -127,34 +134,6 @@ export const timeSeriesAxisPositionTranslations = new Map<AxisPosition, string>(
]
);
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, string>(
[
[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<LineSeriesOption> => {
const generateChartThresholds = (thresholdItems: TimeSeriesChartThresholdItem[]): Array<LineSeriesOption> => {
const series: Array<LineSeriesOption> = [];
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;
};

67
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<TimeSeriesChartThreshold>({} 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);
}

138
ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/bar-chart-with-labels-widget-settings.component.html

@ -17,39 +17,99 @@
-->
<ng-container [formGroup]="barChartWidgetSettingsForm">
<div class="tb-form-panel">
<div class="tb-form-panel-title" translate>widgets.bar-chart.bar-chart-card-style</div>
<div class="tb-form-row space-between">
<mat-slide-toggle class="mat-slide fixed-title-width" formControlName="showBarLabel">
{{ 'widgets.bar-chart.label-on-bar' | translate }}
<div class="tb-form-panel-title" translate>widgets.bar-chart.bar-chart-style</div>
<div class="tb-form-row">
<mat-slide-toggle class="mat-slide" formControlName="dataZoom">
{{ 'widgets.time-series-chart.data-zoom' | translate }}
</mat-slide-toggle>
<div fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px">
<tb-font-settings formControlName="barLabelFont"
disabledLineHeight
forceSizeUnit="px"
previewText="Humidity">
</tb-font-settings>
<tb-color-input asBoxInput
colorClearButton
formControlName="barLabelColor">
</tb-color-input>
</div>
</div>
<div class="tb-form-row space-between">
<mat-slide-toggle class="mat-slide fixed-title-width" formControlName="showBarValue">
{{ 'widgets.bar-chart.value-on-bar' | translate }}
</mat-slide-toggle>
<div fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px">
<tb-font-settings formControlName="barValueFont"
disabledLineHeight
forceSizeUnit="px"
previewText="45">
</tb-font-settings>
<tb-color-input asBoxInput
colorClearButton
formControlName="barValueColor">
</tb-color-input>
<div class="tb-form-panel">
<div class="tb-form-panel-title" translate>widgets.bar-chart.bar-appearance</div>
<div class="tb-form-row space-between">
<mat-slide-toggle class="mat-slide fixed-title-width" formControlName="showBarLabel">
{{ 'widgets.bar-chart.label-on-bar' | translate }}
</mat-slide-toggle>
<div fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px">
<tb-font-settings formControlName="barLabelFont"
disabledLineHeight
forceSizeUnit="px"
previewText="Humidity">
</tb-font-settings>
<tb-color-input asBoxInput
colorClearButton
formControlName="barLabelColor">
</tb-color-input>
</div>
</div>
<div class="tb-form-row space-between">
<mat-slide-toggle class="mat-slide fixed-title-width" formControlName="showBarValue">
{{ 'widgets.bar-chart.value-on-bar' | translate }}
</mat-slide-toggle>
<div fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px">
<tb-font-settings formControlName="barValueFont"
disabledLineHeight
forceSizeUnit="px"
previewText="45">
</tb-font-settings>
<tb-color-input asBoxInput
colorClearButton
formControlName="barValueColor">
</tb-color-input>
</div>
</div>
<div class="tb-form-row">
<mat-slide-toggle class="mat-slide" formControlName="showBarBorder">
{{ 'widgets.time-series-chart.series.bar.show-border' | translate }}
</mat-slide-toggle>
</div>
<div class="tb-form-row space-between column-xs">
<div translate>widgets.time-series-chart.series.bar.border-width</div>
<mat-form-field class="medium-width number" appearance="outline" subscriptSizing="dynamic">
<input matInput type="number" formControlName="barBorderWidth" min="0" step="1" placeholder="{{ 'widget-config.set' | translate }}"/>
</mat-form-field>
</div>
<div class="tb-form-row space-between column-xs">
<div translate>widgets.time-series-chart.series.bar.border-radius</div>
<mat-form-field class="medium-width number" appearance="outline" subscriptSizing="dynamic">
<input matInput type="number" formControlName="barBorderRadius" min="0" step="1" placeholder="{{ 'widget-config.set' | translate }}"/>
</mat-form-field>
</div>
<tb-time-series-chart-fill-settings
formControlName="barBackgroundSettings"
title="widgets.time-series-chart.series.background"
fillNoneTitle="widgets.time-series-chart.series.fill-type-solid">
</tb-time-series-chart-fill-settings>
<tb-time-series-no-aggregation-bar-width-settings
stroked
formControlName="noAggregationBarWidthSettings">
</tb-time-series-no-aggregation-bar-width-settings>
</div>
<div class="tb-form-panel">
<div class="tb-form-panel-title" translate>widgets.time-series-chart.axis.y-axis</div>
<tb-time-series-chart-axis-settings
formControlName="yAxis"
axisType="yAxis"
advanced
hideUnits
hideDecimals>
</tb-time-series-chart-axis-settings>
</div>
<div class="tb-form-panel">
<div class="tb-form-panel-title" translate>widgets.time-series-chart.axis.x-axis</div>
<tb-time-series-chart-axis-settings
formControlName="xAxis"
axisType="xAxis"
advanced>
</tb-time-series-chart-axis-settings>
</div>
<tb-time-series-chart-thresholds-panel
formControlName="thresholds"
[aliasController]="aliasController"
[dataKeyCallbacks]="dataKeyCallbacks"
[datasource]="datasource"
[widgetConfig]="widgetConfig?.config"
hideYAxis>
</tb-time-series-chart-thresholds-panel>
<div class="tb-form-panel tb-slide-toggle">
<mat-expansion-panel class="tb-settings" [expanded]="barChartWidgetSettingsForm.get('showLegend').value" [disabled]="!barChartWidgetSettingsForm.get('showLegend').value">
<mat-expansion-panel-header fxLayout="row wrap">
@ -149,10 +209,22 @@
</ng-template>
</mat-expansion-panel>
</div>
<div class="tb-form-row space-between">
<div>{{ 'widgets.background.background' | translate }}</div>
<tb-background-settings formControlName="background">
</tb-background-settings>
<tb-time-series-chart-animation-settings
formControlName="animation">
</tb-time-series-chart-animation-settings>
<div class="tb-form-panel">
<div class="tb-form-panel-title" translate>widget-config.card-appearance</div>
<div class="tb-form-row space-between">
<div>{{ 'widgets.background.background' | translate }}</div>
<tb-background-settings formControlName="background">
</tb-background-settings>
</div>
<div class="tb-form-row space-between">
<div>{{ 'widget-config.card-padding' | translate }}</div>
<mat-form-field appearance="outline" subscriptSizing="dynamic">
<input matInput formControlName="padding" placeholder="{{ 'widget-config.set' | translate }}">
</mat-form-field>
</div>
</div>
</div>
</ng-container>

36
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();

196
ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/range-chart-widget-settings.component.html

@ -17,29 +17,175 @@
-->
<ng-container [formGroup]="rangeChartWidgetSettingsForm">
<div class="tb-form-panel">
<div class="tb-form-panel-title" translate>widgets.range-chart.range-chart-card-style</div>
<div class="tb-form-panel-title" translate>widgets.range-chart.range-chart-style</div>
<div class="tb-form-row">
<mat-slide-toggle class="mat-slide" formControlName="dataZoom">
{{ 'widgets.range-chart.data-zoom' | translate }}
</mat-slide-toggle>
</div>
<div class="tb-form-row space-between">
<div>{{ 'widgets.range-chart.range-colors' | translate }}</div>
<tb-color-range-settings formControlName="rangeColors">
</tb-color-range-settings>
<div class="tb-form-panel">
<div class="tb-form-panel-title" translate>widgets.range-chart.range-chart-appearance</div>
<div class="tb-form-row space-between">
<div>{{ 'widgets.range-chart.range-colors' | translate }}</div>
<tb-color-range-settings formControlName="rangeColors">
</tb-color-range-settings>
</div>
<div class="tb-form-row space-between">
<div>{{ 'widgets.range-chart.out-of-range-color' | translate }}</div>
<tb-color-input asBoxInput
colorClearButton
formControlName="outOfRangeColor">
</tb-color-input>
</div>
<div class="tb-form-row space-between">
<mat-slide-toggle class="mat-slide" formControlName="showRangeThresholds">
{{ 'widgets.range-chart.show-range-thresholds' | translate }}
</mat-slide-toggle>
<tb-time-series-chart-threshold-settings
boxButton
hideYAxis
[widgetConfig]="widgetConfig?.config"
title="widgets.range-chart.range-thresholds-settings"
formControlName="rangeThreshold">
</tb-time-series-chart-threshold-settings>
</div>
<div class="tb-form-row">
<mat-slide-toggle class="mat-slide" formControlName="fillArea">
{{ 'widgets.range-chart.fill-area' | translate }}
</mat-slide-toggle>
</div>
<div class="tb-form-row space-between column-xs">
<div translate>widgets.range-chart.fill-area-opacity</div>
<mat-form-field class="medium-width number" appearance="outline" subscriptSizing="dynamic">
<input matInput type="number" formControlName="fillAreaOpacity" min="0" max="1"
step="0.1" placeholder="{{ 'widget-config.set' | translate }}"/>
</mat-form-field>
</div>
<div class="tb-form-panel stroked">
<div class="tb-form-panel-title" translate>widgets.time-series-chart.series.line.line</div>
<div class="tb-form-row">
<mat-slide-toggle class="mat-slide" formControlName="showLine">
{{ 'widgets.time-series-chart.series.line.show-line' | translate }}
</mat-slide-toggle>
</div>
<div class="tb-form-row space-between column-xs">
<mat-slide-toggle class="mat-slide" formControlName="step">
{{ 'widgets.time-series-chart.series.line.step-line' | translate }}
</mat-slide-toggle>
<mat-form-field class="medium-width" appearance="outline" subscriptSizing="dynamic">
<mat-select formControlName="stepType">
<mat-option *ngFor="let stepType of lineSeriesStepTypes" [value]="stepType">
{{ lineSeriesStepTypeTranslations.get(stepType) | translate }}
</mat-option>
</mat-select>
</mat-form-field>
</div>
<div class="tb-form-row">
<mat-slide-toggle class="mat-slide" formControlName="smooth">
{{ 'widgets.time-series-chart.series.line.smooth-line' | translate }}
</mat-slide-toggle>
</div>
<div class="tb-form-row space-between column-xs">
<div translate>widgets.time-series-chart.line-type</div>
<mat-form-field class="medium-width" appearance="outline" subscriptSizing="dynamic">
<mat-select formControlName="lineType">
<mat-option *ngFor="let lineType of timeSeriesLineTypes" [value]="lineType">
{{ timeSeriesLineTypeTranslations.get(lineType) | translate }}
</mat-option>
</mat-select>
</mat-form-field>
</div>
<div class="tb-form-row space-between column-xs">
<div translate>widgets.time-series-chart.line-width</div>
<mat-form-field class="medium-width number" appearance="outline" subscriptSizing="dynamic">
<input matInput type="number" formControlName="lineWidth" min="0" step="1" placeholder="{{ 'widget-config.set' | translate }}"/>
</mat-form-field>
</div>
</div>
<div class="tb-form-panel stroked">
<div class="tb-form-panel-title" translate>widgets.time-series-chart.series.point.points</div>
<div class="tb-form-row">
<mat-slide-toggle class="mat-slide" formControlName="showPoints">
{{ 'widgets.time-series-chart.series.point.show-points' | translate }}
</mat-slide-toggle>
</div>
<div class="tb-form-row space-between column-lt-md">
<mat-slide-toggle class="mat-slide" formControlName="showPointLabel">
<div tb-hint-tooltip-icon="{{'widgets.time-series-chart.series.point.point-label-hint' | translate}}">
{{ 'widgets.time-series-chart.series.point.point-label' | translate }}
</div>
</mat-slide-toggle>
<div fxLayout="row" fxFlex.lt-md fxLayoutAlign="start center" fxLayoutGap="8px">
<mat-form-field class="medium-width" fxFlex.lt-md appearance="outline" subscriptSizing="dynamic">
<mat-select formControlName="pointLabelPosition">
<mat-option *ngFor="let position of seriesLabelPositions" [value]="position">
{{ seriesLabelPositionTranslations.get(position) | translate }}
</mat-option>
</mat-select>
</mat-form-field>
<tb-font-settings formControlName="pointLabelFont"
clearButton
disabledLineHeight
forceSizeUnit="px"
[previewText]="pointLabelPreviewFn">
</tb-font-settings>
<tb-color-input asBoxInput
colorClearButton
formControlName="pointLabelColor">
</tb-color-input>
</div>
</div>
<div class="tb-form-row space-between">
<mat-slide-toggle class="mat-slide" formControlName="enablePointLabelBackground">
{{ 'widgets.time-series-chart.series.point.point-label-background' | translate }}
</mat-slide-toggle>
<tb-color-input asBoxInput
colorClearButton
formControlName="pointLabelBackground">
</tb-color-input>
</div>
<div class="tb-form-row space-between column-xs">
<div translate>widgets.time-series-chart.series.point.point-shape</div>
<mat-form-field class="medium-width" appearance="outline" subscriptSizing="dynamic">
<mat-select formControlName="pointShape">
<mat-option *ngFor="let shape of echartsShapes" [value]="shape">
{{ echartsShapeTranslations.get(shape) | translate }}
</mat-option>
</mat-select>
</mat-form-field>
</div>
<div class="tb-form-row space-between column-xs">
<div translate>widgets.time-series-chart.series.point.point-size</div>
<mat-form-field class="medium-width number" appearance="outline" subscriptSizing="dynamic">
<input matInput type="number" formControlName="pointSize" min="0" step="1" placeholder="{{ 'widget-config.set' | translate }}"/>
</mat-form-field>
</div>
</div>
</div>
<div class="tb-form-row space-between">
<div>{{ 'widgets.range-chart.out-of-range-color' | translate }}</div>
<tb-color-input asBoxInput
colorClearButton
formControlName="outOfRangeColor">
</tb-color-input>
<div class="tb-form-panel">
<div class="tb-form-panel-title" translate>widgets.time-series-chart.axis.y-axis</div>
<tb-time-series-chart-axis-settings
formControlName="yAxis"
axisType="yAxis"
hideUnits
hideDecimals>
</tb-time-series-chart-axis-settings>
</div>
<div class="tb-form-row">
<mat-slide-toggle class="mat-slide" formControlName="fillArea">
{{ 'widgets.range-chart.fill-area' | translate }}
</mat-slide-toggle>
<div class="tb-form-panel">
<div class="tb-form-panel-title" translate>widgets.time-series-chart.axis.x-axis</div>
<tb-time-series-chart-axis-settings
formControlName="xAxis"
axisType="xAxis">
</tb-time-series-chart-axis-settings>
</div>
<tb-time-series-chart-thresholds-panel
formControlName="thresholds"
[aliasController]="aliasController"
[dataKeyCallbacks]="dataKeyCallbacks"
[datasource]="datasource"
[widgetConfig]="widgetConfig?.config"
hideYAxis>
</tb-time-series-chart-thresholds-panel>
<div class="tb-form-panel tb-slide-toggle">
<mat-expansion-panel class="tb-settings" [expanded]="rangeChartWidgetSettingsForm.get('showLegend').value" [disabled]="!rangeChartWidgetSettingsForm.get('showLegend').value">
<mat-expansion-panel-header fxLayout="row wrap">
@ -139,10 +285,22 @@
</ng-template>
</mat-expansion-panel>
</div>
<div class="tb-form-row space-between">
<div>{{ 'widgets.background.background' | translate }}</div>
<tb-background-settings formControlName="background">
</tb-background-settings>
<tb-time-series-chart-animation-settings
formControlName="animation">
</tb-time-series-chart-animation-settings>
<div class="tb-form-panel">
<div class="tb-form-panel-title" translate>widget-config.card-appearance</div>
<div class="tb-form-row space-between">
<div>{{ 'widgets.background.background' | translate }}</div>
<tb-background-settings formControlName="background">
</tb-background-settings>
</div>
<div class="tb-form-row space-between">
<div>{{ 'widget-config.card-padding' | translate }}</div>
<mat-form-field appearance="outline" subscriptSizing="dynamic">
<input matInput formControlName="padding" placeholder="{{ 'widget-config.set' | translate }}">
</mat-form-field>
</div>
</div>
</div>
</ng-container>

130
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;
}
}

9
ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/time-series-chart-bar-settings.component.html

@ -59,6 +59,15 @@
</tb-color-input>
</div>
</div>
<div class="tb-form-row space-between">
<mat-slide-toggle class="mat-slide" formControlName="enableLabelBackground">
{{ 'widgets.time-series-chart.series.bar.label-background' | translate }}
</mat-slide-toggle>
<tb-color-input asBoxInput
colorClearButton
formControlName="labelBackground">
</tb-color-input>
</div>
<tb-time-series-chart-fill-settings
formControlName="backgroundSettings"
title="widgets.time-series-chart.series.background"

14
ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/time-series-chart-bar-settings.component.ts

@ -76,13 +76,16 @@ export class TimeSeriesChartBarSettingsComponent implements OnInit, ControlValue
labelPosition: [null, []],
labelFont: [null, []],
labelColor: [null, []],
enableLabelBackground: [null, []],
labelBackground: [null, []],
backgroundSettings: [null, []]
});
this.barSettingsFormGroup.valueChanges.subscribe(() => {
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});
}
}

13
ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/time-series-chart-line-settings.component.html

@ -97,12 +97,21 @@
</tb-color-input>
</div>
</div>
<div class="tb-form-row space-between">
<mat-slide-toggle class="mat-slide" formControlName="enablePointLabelBackground">
{{ 'widgets.time-series-chart.series.point.point-label-background' | translate }}
</mat-slide-toggle>
<tb-color-input asBoxInput
colorClearButton
formControlName="pointLabelBackground">
</tb-color-input>
</div>
<div class="tb-form-row space-between column-xs">
<div translate>widgets.time-series-chart.series.point.point-shape</div>
<mat-form-field class="medium-width" appearance="outline" subscriptSizing="dynamic">
<mat-select formControlName="pointShape">
<mat-option *ngFor="let shape of timeSeriesChartShapes" [value]="shape">
{{ timeSeriesChartShapeTranslations.get(shape) | translate }}
<mat-option *ngFor="let shape of echartsShapes" [value]="shape">
{{ echartsShapeTranslations.get(shape) | translate }}
</mat-option>
</mat-select>
</mat-form-field>

22
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});
}
}

4
ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-axis-settings.component.html

@ -56,13 +56,13 @@
</mat-select>
</mat-form-field>
</div>
<div *ngIf="axisType === 'yAxis'" class="tb-form-row space-between">
<div *ngIf="axisType === 'yAxis' && !hideUnits" class="tb-form-row space-between">
<div translate>widget-config.units-short</div>
<tb-unit-input
formControlName="units">
</tb-unit-input>
</div>
<div *ngIf="axisType === 'yAxis'" class="tb-form-row space-between">
<div *ngIf="axisType === 'yAxis' && !hideDecimals" class="tb-form-row space-between">
<div translate>widget-config.decimals-short</div>
<mat-form-field appearance="outline" class="number" subscriptSizing="dynamic">
<input matInput formControlName="decimals" type="number" min="0" max="15" step="1" placeholder="{{ 'widget-config.set' | translate }}">

8
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;

4
ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/time-series-chart-fill-settings.component.html → ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-fill-settings.component.html

@ -27,8 +27,8 @@
<div class="tb-form-row space-between column-xs">
<div translate>widgets.time-series-chart.series.opacity</div>
<mat-form-field class="medium-width number" appearance="outline" subscriptSizing="dynamic">
<input matInput type="number" formControlName="opacity" min="0" max="100"
step="1" placeholder="{{ 'widget-config.set' | translate }}"/>
<input matInput type="number" formControlName="opacity" min="0" max="1"
step="0.1" placeholder="{{ 'widget-config.set' | translate }}"/>
</mat-form-field>
</div>
</ng-container>

4
ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/time-series-chart-fill-settings.component.ts → 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)]]

17
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">
</tb-data-key-input>
</div>
<div class="tb-y-axis-field">
<div *ngIf="!hideYAxis" class="tb-y-axis-field">
<mat-form-field class="tb-inline-field fixed-height" appearance="outline" subscriptSizing="dynamic">
<mat-select formControlName="yAxisId">
<mat-option *ngFor="let yAxis of yAxisIds" [value]="yAxis">
@ -98,14 +98,13 @@
</mat-form-field>
</div>
<div class="tb-form-table-row-cell-buttons">
<button type="button"
mat-icon-button
#matButton
(click)="editThreshold($event, matButton)"
matTooltip="{{ 'widgets.time-series-chart.threshold.threshold-settings' | translate }}"
matTooltipPosition="above">
<mat-icon>settings</mat-icon>
</button>
<tb-time-series-chart-threshold-settings
[widgetConfig]="widgetConfig"
[yAxisIds]="yAxisIds"
[hideYAxis]="hideYAxis"
icon="settings"
[formControl]="thresholdSettingsFormControl">
</tb-time-series-chart-threshold-settings>
<button type="button"
mat-icon-button
(click)="thresholdRemoved.emit()"

78
ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-threshold-row.component.ts

@ -19,11 +19,11 @@ import {
Component,
EventEmitter,
forwardRef,
Input, OnChanges,
Input,
OnChanges,
OnInit,
Output,
Renderer2, SimpleChanges,
ViewContainerRef,
SimpleChanges,
ViewEncapsulation
} from '@angular/core';
import {
@ -36,7 +36,8 @@ import {
} from '@angular/forms';
import {
TimeSeriesChartThreshold,
TimeSeriesChartThresholdType, TimeSeriesChartYAxisId,
TimeSeriesChartThresholdType,
TimeSeriesChartYAxisId,
timeSeriesThresholdTypes,
timeSeriesThresholdTypeTranslations
} from '@home/components/widget/lib/chart/time-series-chart.models';
@ -47,12 +48,8 @@ import { IAliasController } from '@core/api/widget-api.models';
import { DataKey, Datasource, DatasourceType, WidgetConfig } from '@shared/models/widget.models';
import { DataKeysCallbacks } from '@home/components/widget/config/data-keys.component.models';
import { DataKeyType } from '@shared/models/telemetry/telemetry.models';
import { MatButton } from '@angular/material/button';
import { TbPopoverService } from '@shared/components/popover.service';
import { deepClone } from '@core/utils';
import {
TimeSeriesChartThresholdSettingsPanelComponent
} from '@home/components/widget/lib/settings/common/chart/time-series-chart-threshold-settings-panel.component';
import { coerceBoolean } from '@shared/decorators/coercion';
@Component({
selector: 'tb-time-series-chart-threshold-row',
@ -101,6 +98,10 @@ export class TimeSeriesChartThresholdRowComponent implements ControlValueAccesso
@Input()
yAxisIds: TimeSeriesChartYAxisId[];
@Input()
@coerceBoolean()
hideYAxis = false;
@Output()
thresholdRemoved = new EventEmitter();
@ -112,12 +113,11 @@ export class TimeSeriesChartThresholdRowComponent implements ControlValueAccesso
entityKeyFormControl: UntypedFormControl;
thresholdSettingsFormControl: UntypedFormControl;
private propagateChange = (_val: any) => {};
constructor(private fb: UntypedFormBuilder,
private popoverService: TbPopoverService,
private renderer: Renderer2,
private viewContainerRef: ViewContainerRef,
private thresholdsPanel: TimeSeriesChartThresholdsPanelComponent,
private cd: ChangeDetectorRef) {
}
@ -134,6 +134,7 @@ export class TimeSeriesChartThresholdRowComponent implements ControlValueAccesso
});
this.latestKeyFormControl = this.fb.control(null, [Validators.required]);
this.entityKeyFormControl = this.fb.control(null, [Validators.required]);
this.thresholdSettingsFormControl = this.fb.control(null);
this.thresholdFormGroup.valueChanges.subscribe(
() => this.updateModel()
);
@ -143,6 +144,18 @@ export class TimeSeriesChartThresholdRowComponent implements ControlValueAccesso
this.entityKeyFormControl.valueChanges.subscribe(
() => this.updateModel()
);
this.thresholdSettingsFormControl.valueChanges.subscribe((thresholdSettings: Partial<TimeSeriesChartThreshold>) => {
this.modelValue = {...this.modelValue, ...thresholdSettings};
this.thresholdFormGroup.patchValue(
{
yAxisId: this.modelValue.yAxisId,
units: this.modelValue.units,
decimals: this.modelValue.decimals,
lineColor: this.modelValue.lineColor
},
{emitEvent: false});
this.propagateChange(this.modelValue);
});
this.thresholdFormGroup.get('type').valueChanges.subscribe(() => {
this.updateValidators();
});
@ -175,8 +188,10 @@ export class TimeSeriesChartThresholdRowComponent implements ControlValueAccesso
this.thresholdFormGroup.disable({emitEvent: false});
this.latestKeyFormControl.disable({emitEvent: false});
this.entityKeyFormControl.disable({emitEvent: false});
this.thresholdSettingsFormControl.disable({emitEvent: false});
} else {
this.thresholdFormGroup.enable({emitEvent: false});
this.thresholdSettingsFormControl.enable({emitEvent: false});
this.updateValidators();
}
}
@ -205,45 +220,12 @@ export class TimeSeriesChartThresholdRowComponent implements ControlValueAccesso
name: value.entityKey
}, {emitEvent: false});
}
this.thresholdSettingsFormControl.patchValue(deepClone(this.modelValue),
{emitEvent: false});
this.updateValidators();
this.cd.markForCheck();
}
editThreshold($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),
widgetConfig: this.widgetConfig,
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 = {...this.modelValue, ...thresholdSettings};
this.thresholdFormGroup.patchValue(
{
yAxisId: this.modelValue.yAxisId,
units: this.modelValue.units,
decimals: this.modelValue.decimals,
lineColor: this.modelValue.lineColor
},
{emitEvent: false});
this.propagateChange(this.modelValue);
});
}
}
private updateValidators() {
const type: TimeSeriesChartThresholdType = this.thresholdFormGroup.get('type').value;
if (type === TimeSeriesChartThresholdType.constant) {
@ -282,6 +264,8 @@ export class TimeSeriesChartThresholdRowComponent implements ControlValueAccesso
this.modelValue.entityKey = entityKey?.name;
this.modelValue.entityKeyType = (entityKey?.type as any);
}
this.thresholdSettingsFormControl.patchValue(deepClone(this.modelValue),
{emitEvent: false});
this.propagateChange(this.modelValue);
}
}

21
ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-threshold-settings-panel.component.html

@ -16,9 +16,9 @@
-->
<div class="tb-threshold-settings-panel" [formGroup]="thresholdSettingsFormGroup">
<div class="tb-threshold-settings-title">{{ 'widgets.time-series-chart.threshold.threshold-settings' | translate }}</div>
<div class="tb-threshold-settings-title">{{ panelTitle | translate }}</div>
<div class="tb-threshold-settings-panel-content">
<div class="tb-form-row space-between column-xs">
<div *ngIf="!hideYAxis" class="tb-form-row space-between column-xs">
<div translate>widgets.time-series-chart.axis.y-axis</div>
<mat-form-field class="medium-width" appearance="outline" subscriptSizing="dynamic">
<mat-select formControlName="yAxisId">
@ -64,6 +64,15 @@
</tb-color-input>
</div>
</div>
<div class="tb-form-row space-between">
<mat-slide-toggle class="mat-slide" formControlName="enableLabelBackground">
{{ 'widgets.time-series-chart.threshold.label-background' | translate }}
</mat-slide-toggle>
<tb-color-input asBoxInput
colorClearButton
formControlName="labelBackground">
</tb-color-input>
</div>
<div class="tb-form-panel">
<div class="tb-form-panel-title" translate>widgets.time-series-chart.threshold.line-appearance</div>
<div class="tb-form-row space-between">
@ -94,8 +103,8 @@
<div fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px">
<mat-form-field class="flex" appearance="outline" subscriptSizing="dynamic">
<mat-select formControlName="startSymbol">
<mat-option *ngFor="let shape of timeSeriesChartShapes" [value]="shape">
{{ timeSeriesChartShapeTranslations.get(shape) | translate }}
<mat-option *ngFor="let shape of echartsShapes" [value]="shape">
{{ echartsShapeTranslations.get(shape) | translate }}
</mat-option>
</mat-select>
</mat-form-field>
@ -111,8 +120,8 @@
<div fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px">
<mat-form-field class="flex" appearance="outline" subscriptSizing="dynamic">
<mat-select formControlName="endSymbol">
<mat-option *ngFor="let shape of timeSeriesChartShapes" [value]="shape">
{{ timeSeriesChartShapeTranslations.get(shape) | translate }}
<mat-option *ngFor="let shape of echartsShapes" [value]="shape">
{{ echartsShapeTranslations.get(shape) | translate }}
</mat-option>
</mat-select>
</mat-form-field>

45
ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-threshold-settings-panel.component.ts

@ -18,18 +18,22 @@ import { Component, EventEmitter, Input, OnInit, Output, ViewEncapsulation } fro
import { TbPopoverComponent } from '@shared/components/popover.component';
import { UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms';
import {
TimeSeriesChartShape,
timeSeriesChartShapes,
timeSeriesChartShapeTranslations,
TimeSeriesChartThreshold, TimeSeriesChartYAxisId,
TimeSeriesChartThreshold,
TimeSeriesChartYAxisId,
timeSeriesLineTypes,
timeSeriesLineTypeTranslations,
timeSeriesThresholdLabelPositions,
timeSeriesThresholdLabelPositionTranslations
} from '@home/components/widget/lib/chart/time-series-chart.models';
import {
EChartsShape,
echartsShapes,
echartsShapeTranslations
} from '@home/components/widget/lib/chart/echarts-widget.models';
import { merge } from 'rxjs';
import { WidgetConfig } from '@shared/models/widget.models';
import { formatValue, isDefinedAndNotNull } from '@core/utils';
import { coerceBoolean } from '@shared/decorators/coercion';
@Component({
selector: 'tb-time-series-chart-threshold-settings-panel',
@ -44,9 +48,9 @@ export class TimeSeriesChartThresholdSettingsPanelComponent implements OnInit {
timeSeriesLineTypeTranslations = timeSeriesLineTypeTranslations;
timeSeriesChartShapes = timeSeriesChartShapes;
echartsShapes = echartsShapes;
timeSeriesChartShapeTranslations = timeSeriesChartShapeTranslations;
echartsShapeTranslations = echartsShapeTranslations;
timeSeriesThresholdLabelPositions = timeSeriesThresholdLabelPositions;
@ -66,6 +70,13 @@ export class TimeSeriesChartThresholdSettingsPanelComponent implements OnInit {
@Input()
popover: TbPopoverComponent<TimeSeriesChartThresholdSettingsPanelComponent>;
@Input()
@coerceBoolean()
hideYAxis = false;
@Input()
panelTitle = 'widgets.time-series-chart.threshold.threshold-settings';
@Output()
thresholdSettingsApplied = new EventEmitter<Partial<TimeSeriesChartThreshold>>();
@ -90,10 +101,13 @@ export class TimeSeriesChartThresholdSettingsPanelComponent implements OnInit {
showLabel: [this.thresholdSettings.showLabel, []],
labelPosition: [this.thresholdSettings.labelPosition, []],
labelFont: [this.thresholdSettings.labelFont, []],
labelColor: [this.thresholdSettings.labelColor, []]
labelColor: [this.thresholdSettings.labelColor, []],
enableLabelBackground: [this.thresholdSettings.enableLabelBackground, []],
labelBackground: [this.thresholdSettings.labelBackground, []]
}
);
merge(this.thresholdSettingsFormGroup.get('showLabel').valueChanges,
this.thresholdSettingsFormGroup.get('enableLabelBackground').valueChanges,
this.thresholdSettingsFormGroup.get('startSymbol').valueChanges,
this.thresholdSettingsFormGroup.get('endSymbol').valueChanges).subscribe(() => {
this.updateValidators();
@ -112,23 +126,32 @@ export class TimeSeriesChartThresholdSettingsPanelComponent implements OnInit {
private updateValidators() {
const showLabel: boolean = this.thresholdSettingsFormGroup.get('showLabel').value;
const startSymbol: TimeSeriesChartShape = this.thresholdSettingsFormGroup.get('startSymbol').value;
const endSymbol: TimeSeriesChartShape = this.thresholdSettingsFormGroup.get('endSymbol').value;
const enableLabelBackground: boolean = this.thresholdSettingsFormGroup.get('enableLabelBackground').value;
const startSymbol: EChartsShape = this.thresholdSettingsFormGroup.get('startSymbol').value;
const endSymbol: EChartsShape = this.thresholdSettingsFormGroup.get('endSymbol').value;
if (showLabel) {
this.thresholdSettingsFormGroup.get('labelPosition').enable({emitEvent: false});
this.thresholdSettingsFormGroup.get('labelFont').enable({emitEvent: false});
this.thresholdSettingsFormGroup.get('labelColor').enable({emitEvent: false});
this.thresholdSettingsFormGroup.get('enableLabelBackground').enable({emitEvent: false});
if (enableLabelBackground) {
this.thresholdSettingsFormGroup.get('labelBackground').enable({emitEvent: false});
} else {
this.thresholdSettingsFormGroup.get('labelBackground').disable({emitEvent: false});
}
} else {
this.thresholdSettingsFormGroup.get('labelPosition').disable({emitEvent: false});
this.thresholdSettingsFormGroup.get('labelFont').disable({emitEvent: false});
this.thresholdSettingsFormGroup.get('labelColor').disable({emitEvent: false});
this.thresholdSettingsFormGroup.get('enableLabelBackground').disable({emitEvent: false});
this.thresholdSettingsFormGroup.get('labelBackground').disable({emitEvent: false});
}
if (startSymbol === TimeSeriesChartShape.none) {
if (startSymbol === EChartsShape.none) {
this.thresholdSettingsFormGroup.get('startSymbolSize').disable({emitEvent: false});
} else {
this.thresholdSettingsFormGroup.get('startSymbolSize').enable({emitEvent: false});
}
if (endSymbol === TimeSeriesChartShape.none) {
if (endSymbol === EChartsShape.none) {
this.thresholdSettingsFormGroup.get('endSymbolSize').disable({emitEvent: false});
} else {
this.thresholdSettingsFormGroup.get('endSymbolSize').enable({emitEvent: false});

39
ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-threshold-settings.component.html

@ -0,0 +1,39 @@
<!--
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.
-->
<button *ngIf="boxButton; else iconButton"
type="button"
mat-stroked-button
class="tb-box-button"
[disabled]="disabled"
#matButton
(click)="openThresholdSettingsPopup($event, matButton)"
matTooltip="{{ title | translate }}"
matTooltipPosition="above">
<tb-icon matButtonIcon>{{icon}}</tb-icon>
</button>
<ng-template #iconButton>
<button type="button"
mat-icon-button
[disabled]="disabled"
#matButton
(click)="openThresholdSettingsPopup($event, matButton)"
matTooltip="{{ title | translate }}"
matTooltipPosition="above">
<tb-icon>{{icon}}</tb-icon>
</button>
</ng-template>

124
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<TimeSeriesChartThreshold>;
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<TimeSeriesChartThreshold>): 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);
});
}
}
}

3
ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-thresholds-panel.component.html

@ -21,7 +21,7 @@
<div class="tb-form-table-header">
<div class="tb-form-table-header-cell tb-threshold-source-header" translate>widgets.time-series-chart.threshold.source</div>
<div class="tb-form-table-header-cell tb-threshold-key-value-header" translate>widgets.time-series-chart.threshold.key-value</div>
<div class="tb-form-table-header-cell tb-y-axis-header" translate>widgets.time-series-chart.axis.y-axis</div>
<div *ngIf="!hideYAxis" class="tb-form-table-header-cell tb-y-axis-header" translate>widgets.time-series-chart.axis.y-axis</div>
<div class="tb-form-table-header-cell tb-color-header" translate>widgets.color.color</div>
<div class="tb-form-table-header-cell tb-units-header" translate>widget-config.units-short</div>
<div class="tb-form-table-header-cell tb-decimals-header" translate>widget-config.decimals-short</div>
@ -31,6 +31,7 @@
<div *ngFor="let thresholdControl of thresholdsFormArray().controls; trackBy: trackByThreshold; let $index = index; let $last = last">
<tb-time-series-chart-threshold-row fxFlex
[formControl]="thresholdControl"
[hideYAxis]="hideYAxis"
[yAxisIds]="yAxisIds"
(thresholdRemoved)="removeThreshold($index)">
</tb-time-series-chart-threshold-row>

5
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) => {};

2
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.
-->
<div class="tb-form-panel" [formGroup]="barWidthSettingsFormGroup">
<div class="tb-form-panel" [class.stroked]="stroked" [formGroup]="barWidthSettingsFormGroup">
<div fxLayout="row" fxLayoutAlign="space-between center">
<div fxFlex translate>widgets.time-series-chart.no-aggregation-bar-width-strategy</div>
<tb-toggle-select formControlName="strategy" selectMediaBreakpoint="xs">

5
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;

10
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
],

5
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
]
})

15
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"
}
}
},

4
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",

2
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": "值设置",

Loading…
Cancel
Save