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>
</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">
<div class="tb-form-panel-title" translate>widgets.bar-chart.bar-appearance</div> <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"> <div class="tb-form-row space-between">
<mat-slide-toggle class="mat-slide fixed-title-width" formControlName="showBarLabel"> <mat-slide-toggle class="mat-slide fixed-title-width" formControlName="showBarLabel">
{{ 'widgets.bar-chart.label-on-bar' | translate }} {{ 'widgets.bar-chart.label-on-bar' | translate }}
@ -118,19 +138,57 @@
</tb-color-input> </tb-color-input>
</div> </div>
</div> </div>
<div class="tb-form-row space-between"> <div class="tb-form-row">
<div translate>widget-config.units-short</div> <mat-slide-toggle class="mat-slide" formControlName="showBarBorder">
<tb-unit-input {{ 'widgets.time-series-chart.series.bar.show-border' | translate }}
formControlName="units"> </mat-slide-toggle>
</tb-unit-input>
</div> </div>
<div class="tb-form-row space-between"> <div class="tb-form-row space-between column-xs">
<div translate>widget-config.decimals-short</div> <div translate>widgets.time-series-chart.series.bar.border-width</div>
<mat-form-field appearance="outline" class="number" subscriptSizing="dynamic"> <mat-form-field class="medium-width number" appearance="outline" subscriptSizing="dynamic">
<input matInput formControlName="decimals" type="number" min="0" max="15" step="1" placeholder="{{ 'widget-config.set' | translate }}"> <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> </mat-form-field>
</div> </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>
<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"> <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 class="tb-settings" [expanded]="barChartWidgetConfigForm.get('showLegend').value" [disabled]="!barChartWidgetConfigForm.get('showLegend').value">
<mat-expansion-panel-header fxLayout="row wrap"> <mat-expansion-panel-header fxLayout="row wrap">
@ -230,6 +288,9 @@
</ng-template> </ng-template>
</mat-expansion-panel> </mat-expansion-panel>
</div> </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">
<div class="tb-form-panel-title" translate>widget-config.card-appearance</div> <div class="tb-form-panel-title" translate>widget-config.card-appearance</div>
<div class="tb-form-row space-between"> <div class="tb-form-row space-between">
@ -249,6 +310,12 @@
<input matInput formControlName="borderRadius" placeholder="{{ 'widget-config.set' | translate }}"> <input matInput formControlName="borderRadius" placeholder="{{ 'widget-config.set' | translate }}">
</mat-form-field> </mat-form-field>
</div> </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>
<tb-widget-actions-panel <tb-widget-actions-panel
formControlName="actions"> 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, barChartWithLabelsDefaultSettings,
BarChartWithLabelsWidgetSettings BarChartWithLabelsWidgetSettings
} from '@home/components/widget/lib/chart/bar-chart-with-labels-widget.models'; } 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({ @Component({
selector: 'tb-bar-chart-with-labels-basic-config', selector: 'tb-bar-chart-with-labels-basic-config',
@ -105,16 +106,30 @@ export class BarChartWithLabelsBasicConfigComponent extends BasicWidgetConfigCom
icon: [configData.config.titleIcon, []], icon: [configData.config.titleIcon, []],
iconColor: [configData.config.iconColor, []], iconColor: [configData.config.iconColor, []],
dataZoom: [settings.dataZoom, []],
showBarLabel: [settings.showBarLabel, []], showBarLabel: [settings.showBarLabel, []],
barLabelFont: [settings.barLabelFont, []], barLabelFont: [settings.barLabelFont, []],
barLabelColor: [settings.barLabelColor, []], barLabelColor: [settings.barLabelColor, []],
showBarValue: [settings.showBarValue, []], showBarValue: [settings.showBarValue, []],
barValueFont: [settings.barValueFont, []], barValueFont: [settings.barValueFont, []],
barValueColor: [settings.barValueColor, []], barValueColor: [settings.barValueColor, []],
showBarBorder: [settings.showBarBorder, []],
barBorderWidth: [settings.barBorderWidth, []],
barBorderRadius: [settings.barBorderRadius, []],
barBackgroundSettings: [settings.barBackgroundSettings, []],
noAggregationBarWidthSettings: [settings.noAggregationBarWidthSettings, []],
units: [configData.config.units, []], units: [configData.config.units, []],
decimals: [configData.config.decimals, []], decimals: [configData.config.decimals, []],
yAxis: [settings.yAxis, []],
xAxis: [settings.xAxis, []],
thresholds: [settings.thresholds, []],
animation: [settings.animation, []],
showLegend: [settings.showLegend, []], showLegend: [settings.showLegend, []],
legendPosition: [settings.legendPosition, []], legendPosition: [settings.legendPosition, []],
legendLabelFont: [settings.legendLabelFont, []], legendLabelFont: [settings.legendLabelFont, []],
@ -136,6 +151,7 @@ export class BarChartWithLabelsBasicConfigComponent extends BasicWidgetConfigCom
cardButtons: [this.getCardButtons(configData.config), []], cardButtons: [this.getCardButtons(configData.config), []],
borderRadius: [configData.config.borderRadius, []], borderRadius: [configData.config.borderRadius, []],
padding: [settings.padding, []],
actions: [configData.config.actions || {}, []] 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 = this.widgetConfig.config.settings || {};
this.widgetConfig.config.settings.dataZoom = config.dataZoom;
this.widgetConfig.config.settings.showBarLabel = config.showBarLabel; this.widgetConfig.config.settings.showBarLabel = config.showBarLabel;
this.widgetConfig.config.settings.barLabelFont = config.barLabelFont; this.widgetConfig.config.settings.barLabelFont = config.barLabelFont;
this.widgetConfig.config.settings.barLabelColor = config.barLabelColor; 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.barValueFont = config.barValueFont;
this.widgetConfig.config.settings.barValueColor = config.barValueColor; 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.units = config.units;
this.widgetConfig.config.decimals = config.decimals; 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.showLegend = config.showLegend;
this.widgetConfig.config.settings.legendPosition = config.legendPosition; this.widgetConfig.config.settings.legendPosition = config.legendPosition;
this.widgetConfig.config.settings.legendLabelFont = config.legendLabelFont; this.widgetConfig.config.settings.legendLabelFont = config.legendLabelFont;
@ -188,13 +219,14 @@ export class BarChartWithLabelsBasicConfigComponent extends BasicWidgetConfigCom
this.setCardButtons(config.cardButtons, this.widgetConfig.config); this.setCardButtons(config.cardButtons, this.widgetConfig.config);
this.widgetConfig.config.borderRadius = config.borderRadius; this.widgetConfig.config.borderRadius = config.borderRadius;
this.widgetConfig.config.settings.padding = config.padding;
this.widgetConfig.config.actions = config.actions; this.widgetConfig.config.actions = config.actions;
return this.widgetConfig; return this.widgetConfig;
} }
protected validatorTriggers(): string[] { 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) { protected updateValidators(emitEvent: boolean, trigger?: string) {
@ -202,6 +234,7 @@ export class BarChartWithLabelsBasicConfigComponent extends BasicWidgetConfigCom
const showIcon: boolean = this.barChartWidgetConfigForm.get('showIcon').value; const showIcon: boolean = this.barChartWidgetConfigForm.get('showIcon').value;
const showBarLabel: boolean = this.barChartWidgetConfigForm.get('showBarLabel').value; const showBarLabel: boolean = this.barChartWidgetConfigForm.get('showBarLabel').value;
const showBarValue: boolean = this.barChartWidgetConfigForm.get('showBarValue').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 showLegend: boolean = this.barChartWidgetConfigForm.get('showLegend').value;
const showTooltip: boolean = this.barChartWidgetConfigForm.get('showTooltip').value; const showTooltip: boolean = this.barChartWidgetConfigForm.get('showTooltip').value;
const tooltipShowDate: boolean = this.barChartWidgetConfigForm.get('tooltipShowDate').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('barValueFont').disable();
this.barChartWidgetConfigForm.get('barValueColor').disable(); this.barChartWidgetConfigForm.get('barValueColor').disable();
} }
if (showBarBorder) {
this.barChartWidgetConfigForm.get('barBorderWidth').enable();
} else {
this.barChartWidgetConfigForm.get('barBorderWidth').disable();
}
if (showLegend) { if (showLegend) {
this.barChartWidgetConfigForm.get('legendPosition').enable(); this.barChartWidgetConfigForm.get('legendPosition').enable();
this.barChartWidgetConfigForm.get('legendLabelFont').enable(); this.barChartWidgetConfigForm.get('legendLabelFont').enable();
@ -326,4 +363,6 @@ export class BarChartWithLabelsBasicConfigComponent extends BasicWidgetConfigCom
processor.update(Date.now()); processor.update(Date.now());
return processor.formatted; 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 }} {{ 'widgets.range-chart.data-zoom' | translate }}
</mat-slide-toggle> </mat-slide-toggle>
</div> </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 class="tb-form-row space-between">
<div>{{ 'widgets.range-chart.range-colors' | translate }}</div> <div>{{ 'widgets.range-chart.range-colors' | translate }}</div>
<tb-color-range-settings formControlName="rangeColors"> <tb-color-range-settings formControlName="rangeColors">
@ -87,24 +102,155 @@
formControlName="outOfRangeColor"> formControlName="outOfRangeColor">
</tb-color-input> </tb-color-input>
</div> </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"> <div class="tb-form-row">
<mat-slide-toggle class="mat-slide" formControlName="fillArea"> <mat-slide-toggle class="mat-slide" formControlName="fillArea">
{{ 'widgets.range-chart.fill-area' | translate }} {{ 'widgets.range-chart.fill-area' | translate }}
</mat-slide-toggle> </mat-slide-toggle>
</div> </div>
<div class="tb-form-row space-between"> <div class="tb-form-row space-between column-xs">
<div translate>widget-config.units-short</div> <div translate>widgets.range-chart.fill-area-opacity</div>
<tb-unit-input <mat-form-field class="medium-width number" appearance="outline" subscriptSizing="dynamic">
formControlName="units"> <input matInput type="number" formControlName="fillAreaOpacity" min="0" max="1"
</tb-unit-input> step="0.1" placeholder="{{ 'widget-config.set' | translate }}"/>
</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> </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>
<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>
<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"> <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 class="tb-settings" [expanded]="rangeChartWidgetConfigForm.get('showLegend').value" [disabled]="!rangeChartWidgetConfigForm.get('showLegend').value">
<mat-expansion-panel-header fxLayout="row wrap"> <mat-expansion-panel-header fxLayout="row wrap">
@ -204,6 +350,9 @@
</ng-template> </ng-template>
</mat-expansion-panel> </mat-expansion-panel>
</div> </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">
<div class="tb-form-panel-title" translate>widget-config.card-appearance</div> <div class="tb-form-panel-title" translate>widget-config.card-appearance</div>
<div class="tb-form-row space-between"> <div class="tb-form-row space-between">
@ -223,6 +372,12 @@
<input matInput formControlName="borderRadius" placeholder="{{ 'widget-config.set' | translate }}"> <input matInput formControlName="borderRadius" placeholder="{{ 'widget-config.set' | translate }}">
</mat-form-field> </mat-form-field>
</div> </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>
<tb-widget-actions-panel <tb-widget-actions-panel
formControlName="actions"> 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. /// 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 { UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms';
import { Store } from '@ngrx/store'; import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state'; import { AppState } from '@core/core.state';
import { BasicWidgetConfigComponent } from '@home/components/widget/config/widget-config.component.models'; import { BasicWidgetConfigComponent } from '@home/components/widget/config/widget-config.component.models';
import { WidgetConfigComponentData } from '@home/models/widget-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 { WidgetConfigComponent } from '@home/components/widget/widget-config.component';
import { DataKeyType } from '@shared/models/telemetry/telemetry.models'; import { DataKeyType } from '@shared/models/telemetry/telemetry.models';
import { import {
@ -38,6 +44,15 @@ import {
rangeChartDefaultSettings, rangeChartDefaultSettings,
RangeChartWidgetSettings RangeChartWidgetSettings
} from '@home/components/widget/lib/chart/range-chart-widget.models'; } 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({ @Component({
selector: 'tb-range-chart-basic-config', selector: 'tb-range-chart-basic-config',
@ -46,12 +61,39 @@ import {
}) })
export class RangeChartBasicConfigComponent extends BasicWidgetConfigComponent { 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; legendPositions = legendPositions;
legendPositionTranslationMap = legendPositionTranslationMap; legendPositionTranslationMap = legendPositionTranslationMap;
rangeChartWidgetConfigForm: UntypedFormGroup; rangeChartWidgetConfigForm: UntypedFormGroup;
pointLabelPreviewFn = this._pointLabelPreviewFn.bind(this);
tooltipValuePreviewFn = this._tooltipValuePreviewFn.bind(this); tooltipValuePreviewFn = this._tooltipValuePreviewFn.bind(this);
tooltipDatePreviewFn = this._tooltipDatePreviewFn.bind(this); tooltipDatePreviewFn = this._tooltipDatePreviewFn.bind(this);
@ -90,11 +132,39 @@ export class RangeChartBasicConfigComponent extends BasicWidgetConfigComponent {
iconColor: [configData.config.iconColor, []], iconColor: [configData.config.iconColor, []],
dataZoom: [settings.dataZoom, []], dataZoom: [settings.dataZoom, []],
units: [configData.config.units, []],
decimals: [configData.config.decimals, []],
rangeColors: [settings.rangeColors, []], rangeColors: [settings.rangeColors, []],
outOfRangeColor: [settings.outOfRangeColor, []], outOfRangeColor: [settings.outOfRangeColor, []],
showRangeThresholds: [settings.showRangeThresholds, []],
rangeThreshold: [settings.rangeThreshold, []],
fillArea: [settings.fillArea, []], fillArea: [settings.fillArea, []],
units: [configData.config.units, []], fillAreaOpacity: [settings.fillAreaOpacity, [Validators.min(0), Validators.max(1)]],
decimals: [configData.config.decimals, []],
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, []], showLegend: [settings.showLegend, []],
legendPosition: [settings.legendPosition, []], legendPosition: [settings.legendPosition, []],
@ -117,6 +187,7 @@ export class RangeChartBasicConfigComponent extends BasicWidgetConfigComponent {
cardButtons: [this.getCardButtons(configData.config), []], cardButtons: [this.getCardButtons(configData.config), []],
borderRadius: [configData.config.borderRadius, []], borderRadius: [configData.config.borderRadius, []],
padding: [settings.padding, []],
actions: [configData.config.actions || {}, []] 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 = this.widgetConfig.config.settings || {};
this.widgetConfig.config.settings.dataZoom = config.dataZoom; 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.rangeColors = config.rangeColors;
this.widgetConfig.config.settings.outOfRangeColor = config.outOfRangeColor; 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.settings.fillArea = config.fillArea;
this.widgetConfig.config.units = config.units; this.widgetConfig.config.settings.fillAreaOpacity = config.fillAreaOpacity;
this.widgetConfig.config.decimals = config.decimals;
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.showLegend = config.showLegend;
this.widgetConfig.config.settings.legendPosition = config.legendPosition; this.widgetConfig.config.settings.legendPosition = config.legendPosition;
@ -165,18 +266,26 @@ export class RangeChartBasicConfigComponent extends BasicWidgetConfigComponent {
this.setCardButtons(config.cardButtons, this.widgetConfig.config); this.setCardButtons(config.cardButtons, this.widgetConfig.config);
this.widgetConfig.config.borderRadius = config.borderRadius; this.widgetConfig.config.borderRadius = config.borderRadius;
this.widgetConfig.config.settings.padding = config.padding;
this.widgetConfig.config.actions = config.actions; this.widgetConfig.config.actions = config.actions;
return this.widgetConfig; return this.widgetConfig;
} }
protected validatorTriggers(): string[] { 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) { protected updateValidators(emitEvent: boolean, trigger?: string) {
const showTitle: boolean = this.rangeChartWidgetConfigForm.get('showTitle').value; const showTitle: boolean = this.rangeChartWidgetConfigForm.get('showTitle').value;
const showIcon: boolean = this.rangeChartWidgetConfigForm.get('showIcon').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 showLegend: boolean = this.rangeChartWidgetConfigForm.get('showLegend').value;
const showTooltip: boolean = this.rangeChartWidgetConfigForm.get('showTooltip').value; const showTooltip: boolean = this.rangeChartWidgetConfigForm.get('showTooltip').value;
const tooltipShowDate: boolean = this.rangeChartWidgetConfigForm.get('tooltipShowDate').value; const tooltipShowDate: boolean = this.rangeChartWidgetConfigForm.get('tooltipShowDate').value;
@ -208,6 +317,54 @@ export class RangeChartBasicConfigComponent extends BasicWidgetConfigComponent {
this.rangeChartWidgetConfigForm.get('iconColor').disable(); 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) { if (showLegend) {
this.rangeChartWidgetConfigForm.get('legendPosition').enable(); this.rangeChartWidgetConfigForm.get('legendPosition').enable();
this.rangeChartWidgetConfigForm.get('legendLabelFont').enable(); this.rangeChartWidgetConfigForm.get('legendLabelFont').enable();
@ -260,6 +417,12 @@ export class RangeChartBasicConfigComponent extends BasicWidgetConfigComponent {
config.enableFullscreen = buttons.includes('fullscreen'); 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 { private _tooltipValuePreviewFn(): string {
const units: string = this.rangeChartWidgetConfigForm.get('units').value; const units: string = this.rangeChartWidgetConfigForm.get('units').value;
const decimals: number = this.rangeChartWidgetConfigForm.get('decimals').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. 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> <div class="tb-bar-chart-overlay" [style]="overlayStyle"></div>
<ng-container *ngTemplateOutlet="widgetTitlePanel"></ng-container> <ng-container *ngTemplateOutlet="widgetTitlePanel"></ng-container>
<div class="tb-bar-chart-content" [class]="legendClass"> <div class="tb-bar-chart-content" [class]="legendClass">
<div #chartShape class="tb-bar-chart-shape"> <div #chartShape class="tb-bar-chart-shape">
</div> </div>
<div *ngIf="showLegend" class="tb-bar-chart-legend"> <div *ngIf="showLegend" class="tb-bar-chart-legend">
<div class="tb-bar-chart-legend-item" *ngFor="let legendItem of legendItems" <div class="tb-bar-chart-legend-item" *ngFor="let legendKey of legendKeys"
(mouseenter)="onLegendItemEnter(legendItem)" (mouseenter)="onLegendKeyEnter(legendKey)"
(mouseleave)="onLegendItemLeave(legendItem)" (mouseleave)="onLegendKeyLeave(legendKey)"
(click)="toggleLegendItem(legendItem)"> (click)="toggleLegendKey(legendKey)">
<div class="tb-bar-chart-legend-item-label"> <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 class="tb-bar-chart-legend-item-label-circle" [style]="{background: !legendKey.hidden ? legendKey.color : null}"></div>
<div [style]="legendItem.enabled ? legendLabelStyle : disabledLegendLabelStyle">{{ legendItem.label }}</div> <div [style]="!legendKey.hidden ? legendLabelStyle : disabledLegendLabelStyle">{{ legendKey.label }}</div>
</div> </div>
</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; position: relative;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 16px; gap: 8px;
padding: 20px 24px 24px 24px; &.overlay {
padding: 20px 24px 24px 24px;
}
> div:not(.tb-bar-chart-overlay) { > div:not(.tb-bar-chart-overlay) {
z-index: 1; z-index: 1;
} }
@ -40,7 +42,7 @@
min-height: 0; min-height: 0;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 16px; gap: 8px;
&.legend-top { &.legend-top {
flex-direction: column-reverse; flex-direction: column-reverse;
} }
@ -88,8 +90,9 @@
} }
} }
&.legend-right, &.legend-left { &.legend-right, &.legend-left {
gap: 24px; gap: 16px;
.tb-bar-chart-legend { .tb-bar-chart-legend {
padding-top: 8px;
flex-direction: column-reverse; flex-direction: column-reverse;
justify-content: flex-end; justify-content: flex-end;
align-items: stretch; 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 ViewEncapsulation
} from '@angular/core'; } from '@angular/core';
import { WidgetContext } from '@home/models/widget-component.models'; import { WidgetContext } from '@home/models/widget-component.models';
import { import { backgroundStyle, ComponentStyle, overlayStyle, textStyle } from '@shared/models/widget-settings.models';
backgroundStyle,
ComponentStyle,
DateFormatProcessor,
overlayStyle,
textStyle
} from '@shared/models/widget-settings.models';
import { ResizeObserver } from '@juggle/resize-observer';
import { formatValue } from '@core/utils';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
import { ImagePipe } from '@shared/pipe/image.pipe'; import { ImagePipe } from '@shared/pipe/image.pipe';
import { DomSanitizer } from '@angular/platform-browser'; import { DomSanitizer } from '@angular/platform-browser';
import { import {
barChartWithLabelsDefaultSettings, barChartWithLabelsDefaultSettings,
barChartWithLabelsTimeSeriesKeySettings,
barChartWithLabelsTimeSeriesSettings,
BarChartWithLabelsWidgetSettings BarChartWithLabelsWidgetSettings
} from '@home/components/widget/lib/chart/bar-chart-with-labels-widget.models'; } from '@home/components/widget/lib/chart/bar-chart-with-labels-widget.models';
import { TbTimeSeriesChart } from '@home/components/widget/lib/chart/time-series-chart';
import * as echarts from 'echarts/core'; import { DataKey } from '@shared/models/widget.models';
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;
}
@Component({ @Component({
selector: 'tb-bar-chart-with-labels-widget', selector: 'tb-bar-chart-with-labels-widget',
@ -92,30 +65,14 @@ export class BarChartWithLabelsWidgetComponent implements OnInit, OnDestroy, Aft
backgroundStyle$: Observable<ComponentStyle>; backgroundStyle$: Observable<ComponentStyle>;
overlayStyle: ComponentStyle = {}; overlayStyle: ComponentStyle = {};
overlayEnabled: boolean;
padding: string;
legendItems: BarChartLegendItem[]; legendKeys: DataKey[];
legendLabelStyle: ComponentStyle; legendLabelStyle: ComponentStyle;
disabledLegendLabelStyle: ComponentStyle; disabledLegendLabelStyle: ComponentStyle;
private get noAggregation(): boolean { private timeSeriesChart: TbTimeSeriesChart;
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;
constructor(private imagePipe: ImagePipe, constructor(private imagePipe: ImagePipe,
private sanitizer: DomSanitizer, private sanitizer: DomSanitizer,
@ -127,356 +84,64 @@ export class BarChartWithLabelsWidgetComponent implements OnInit, OnDestroy, Aft
this.ctx.$scope.barChartWidget = this; this.ctx.$scope.barChartWidget = this;
this.settings = {...barChartWithLabelsDefaultSettings, ...this.ctx.settings}; 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.backgroundStyle$ = backgroundStyle(this.settings.background, this.imagePipe, this.sanitizer);
this.overlayStyle = overlayStyle(this.settings.background.overlay); 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; this.showLegend = this.settings.showLegend;
if (this.showLegend) { if (this.showLegend) {
this.legendItems = []; this.legendKeys = [];
this.legendClass = `legend-${this.settings.legendPosition}`; this.legendClass = `legend-${this.settings.legendPosition}`;
this.legendLabelStyle = textStyle(this.settings.legendLabelFont); this.legendLabelStyle = textStyle(this.settings.legendLabelFont);
this.disabledLegendLabelStyle = textStyle(this.settings.legendLabelFont); this.disabledLegendLabelStyle = textStyle(this.settings.legendLabelFont);
this.legendLabelStyle.color = this.settings.legendLabelColor; this.legendLabelStyle.color = this.settings.legendLabelColor;
} }
let counter = 0; const barSettings = barChartWithLabelsTimeSeriesKeySettings(this.settings, this.ctx.decimals);
if (this.ctx.datasources.length) { for (const datasource of this.ctx.datasources) {
for (const datasource of this.ctx.datasources) { const dataKeys = datasource.dataKeys;
const dataKeys = datasource.dataKeys; for (const dataKey of dataKeys) {
for (const dataKey of dataKeys) { dataKey.settings = barSettings;
const id = counter++; if (this.showLegend) {
const datasourceData = this.ctx.data ? this.ctx.data.find(d => d.dataKey === dataKey) : null; this.legendKeys.push(dataKey);
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
}
);
}
} }
} }
} }
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() { ngAfterViewInit() {
if (this.drawChartPending) { const settings = barChartWithLabelsTimeSeriesSettings(this.settings);
this.drawChart(); this.timeSeriesChart = new TbTimeSeriesChart(this.ctx, settings, this.chartShape.nativeElement, this.renderer);
}
} }
ngOnDestroy() { ngOnDestroy() {
if (this.shapeResize$) { if (this.timeSeriesChart) {
this.shapeResize$.disconnect(); this.timeSeriesChart.destroy();
}
if (this.barChart) {
this.barChart.dispose();
} }
} }
public onInit() { public onInit() {
const borderRadius = this.ctx.$widgetElement.css('borderRadius'); const borderRadius = this.ctx.$widgetElement.css('borderRadius');
this.overlayStyle = {...this.overlayStyle, ...{borderRadius}}; this.overlayStyle = {...this.overlayStyle, ...{borderRadius}};
if (this.chartShape) {
this.drawChart();
} else {
this.drawChartPending = true;
}
this.cd.detectChanges(); this.cd.detectChanges();
} }
public onDataUpdated() { public onDataUpdated() {
for (const item of this.dataItems) { if (this.timeSeriesChart) {
const datasourceData = this.ctx.data ? this.ctx.data.find(d => d.dataKey === item.dataKey) : null; this.timeSeriesChart.update();
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);
} }
} }
private updateSeries(): Array<CustomSeriesOption> { public onLegendKeyEnter(key: DataKey) {
const series: Array<CustomSeriesOption> = []; this.timeSeriesChart.keyEnter(key);
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 onLegendKeyLeave(key: DataKey) {
public onLegendItemEnter(item: BarChartLegendItem) { this.timeSeriesChart.keyLeave(key);
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
});
}
}
} }
private drawChart() { public toggleLegendKey(key: DataKey) {
echartsModule.init(); this.timeSeriesChart.toggleKey(key);
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();
}
} }
} }

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. /// 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 { LegendPosition } from '@shared/models/widget.models';
import { EChartsTooltipWidgetSettings } from '@home/components/widget/lib/chart/echarts-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 { export interface BarChartWithLabelsWidgetSettings extends EChartsTooltipWidgetSettings {
dataZoom: boolean;
showBarLabel: boolean; showBarLabel: boolean;
barLabelFont: Font; barLabelFont: Font;
barLabelColor: string; barLabelColor: string;
showBarValue: boolean; showBarValue: boolean;
barValueFont: Font; barValueFont: Font;
barValueColor: string; barValueColor: string;
showBarBorder: boolean;
barBorderWidth: number;
barBorderRadius: number;
barBackgroundSettings: SeriesFillSettings;
noAggregationBarWidthSettings: TimeSeriesChartNoAggregationBarWidthSettings;
yAxis: TimeSeriesChartYAxisSettings;
xAxis: TimeSeriesChartXAxisSettings;
animation: TimeSeriesChartAnimationSettings;
thresholds: TimeSeriesChartThreshold[];
showLegend: boolean; showLegend: boolean;
legendPosition: LegendPosition; legendPosition: LegendPosition;
legendLabelFont: Font; legendLabelFont: Font;
legendLabelColor: string; legendLabelColor: string;
background: BackgroundSettings; background: BackgroundSettings;
padding: string;
} }
export const barChartWithLabelsDefaultSettings: BarChartWithLabelsWidgetSettings = { export const barChartWithLabelsDefaultSettings: BarChartWithLabelsWidgetSettings = {
dataZoom: false,
showBarLabel: true, showBarLabel: true,
barLabelFont: { barLabelFont: {
family: 'Roboto', family: 'Roboto',
@ -53,6 +92,28 @@ export const barChartWithLabelsDefaultSettings: BarChartWithLabelsWidgetSettings
lineHeight: '12px' lineHeight: '12px'
}, },
barValueColor: 'rgba(0, 0, 0, 0.76)', 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, showLegend: true,
legendPosition: LegendPosition.top, legendPosition: LegendPosition.top,
legendLabelFont: { legendLabelFont: {
@ -96,5 +157,89 @@ export const barChartWithLabelsDefaultSettings: BarChartWithLabelsWidgetSettings
color: 'rgba(255,255,255,0.72)', color: 'rgba(255,255,255,0.72)',
blur: 3 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 * as echarts from 'echarts/core';
import AxisModel from 'echarts/types/src/coord/cartesian/AxisModel'; import AxisModel from 'echarts/types/src/coord/cartesian/AxisModel';
import { estimateLabelUnionRect } from 'echarts/lib/coord/axisHelper'; import { estimateLabelUnionRect } from 'echarts/lib/coord/axisHelper';
import { formatValue, isDefinedAndNotNull } from '@core/utils'; import { formatValue, isDefinedAndNotNull, isNumber } from '@core/utils';
import { import {
DataZoomComponent, DataZoomComponent,
DataZoomComponentOption, DataZoomComponentOption,
@ -55,6 +55,7 @@ import { DateFormatProcessor, DateFormatSettings, Font } from '@shared/models/wi
import GlobalModel from 'echarts/types/src/model/Global'; import GlobalModel from 'echarts/types/src/model/Global';
import Axis2D from 'echarts/types/src/coord/cartesian/Axis2D'; import Axis2D from 'echarts/types/src/coord/cartesian/Axis2D';
import SeriesModel from 'echarts/types/src/model/Series'; import SeriesModel from 'echarts/types/src/model/Series';
import { MarkLine2DDataItemOption } from 'echarts/types/src/component/marker/MarkLineModel';
class EChartsModule { class EChartsModule {
private initialized = false; private initialized = false;
@ -110,6 +111,51 @@ export type EChartsSeriesItem = {
decimals?: number; 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) => { export const timeAxisBandWidthCalculator: TimeAxisBandWidthCalculator = (model) => {
let interval: number; let interval: number;
const axisOption = model.option; const axisOption = model.option;
@ -211,7 +257,23 @@ export const measureXAxisNameHeight = (chart: ECharts, name: string): number =>
return 0; 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); const axis = getYAxis(chart, axisId);
if (axis && !axis.scale.isBlank()) { if (axis && !axis.scale.isBlank()) {
const extent = axis.scale.getExtent(); const extent = axis.scale.getExtent();
@ -220,6 +282,16 @@ export const measureThresholdLabelOffset = (chart: ECharts, axisId: string, thre
if (models?.length) { if (models?.length) {
const lineSeriesModel = models[0] as SeriesModel<LineSeriesOption>; const lineSeriesModel = models[0] as SeriesModel<LineSeriesOption>;
const markLineModel = lineSeriesModel.getModel('markLine'); 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']); const labelPosition = markLineModel.get(['label', 'position']);
if (labelPosition === 'start' || labelPosition === 'end') { if (labelPosition === 'start' || labelPosition === 'end') {
const labelModel = markLineModel.getModel('label'); const labelModel = markLineModel.getModel('label');
@ -239,23 +311,38 @@ export const measureThresholdLabelOffset = (chart: ECharts, axisId: string, thre
} }
} }
if (!textWidth) { if (!textWidth) {
return [0,0]; return offset;
} }
const distanceOpt = markLineModel.get(['label', 'distance']); const distanceOpt = markLineModel.get(['label', 'distance']);
let distance = 5; let distance = 5;
if (distanceOpt) { if (distanceOpt) {
distance = typeof distanceOpt === 'number' ? distanceOpt : distanceOpt[0]; 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') { if (labelPosition === 'start') {
return [offset, 0]; offset[0] = Math.max(offset[0], textOffset);
} else { } 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] => { 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]; 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 => { export const toNamedData = (data: DataSet): NamedDataSet => {
if (!data?.length) { if (!data?.length) {
return []; return [];
@ -304,6 +433,7 @@ export const tooltipTriggerTranslationMap = new Map<EChartsTooltipTrigger, strin
export interface EChartsTooltipWidgetSettings { export interface EChartsTooltipWidgetSettings {
showTooltip: boolean; showTooltip: boolean;
tooltipTrigger?: EChartsTooltipTrigger; tooltipTrigger?: EChartsTooltipTrigger;
tooltipShowFocusedSeries?: boolean;
tooltipValueFont: Font; tooltipValueFont: Font;
tooltipValueColor: string; tooltipValueColor: string;
tooltipShowDate: boolean; 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. 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> <div class="tb-range-chart-overlay" [style]="overlayStyle"></div>
<ng-container *ngTemplateOutlet="widgetTitlePanel"></ng-container> <ng-container *ngTemplateOutlet="widgetTitlePanel"></ng-container>
<div class="tb-range-chart-content" [class]="legendClass"> <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; position: relative;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 16px; gap: 8px;
padding: 20px 24px 24px 24px; &.overlay {
padding: 20px 24px 24px 24px;
}
> div:not(.tb-range-chart-overlay) { > div:not(.tb-range-chart-overlay) {
z-index: 1; z-index: 1;
} }
@ -40,7 +42,7 @@
min-height: 0; min-height: 0;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 16px; gap: 8px;
&.legend-top { &.legend-top {
flex-direction: column-reverse; flex-direction: column-reverse;
} }
@ -88,8 +90,9 @@
} }
} }
&.legend-right, &.legend-left { &.legend-right, &.legend-left {
gap: 24px; gap: 16px;
.tb-range-chart-legend { .tb-range-chart-legend {
padding-top: 8px;
flex-direction: column-reverse; flex-direction: column-reverse;
justify-content: flex-end; justify-content: flex-end;
align-items: stretch; 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 { WidgetContext } from '@home/models/widget-component.models';
import { import {
backgroundStyle, backgroundStyle,
ColorRange,
ComponentStyle, ComponentStyle,
DateFormatProcessor,
filterIncludingColorRanges,
getDataKey, getDataKey,
overlayStyle, overlayStyle,
sortedColorRange,
textStyle textStyle
} from '@shared/models/widget-settings.models'; } from '@shared/models/widget-settings.models';
import { ResizeObserver } from '@juggle/resize-observer'; import { isDefinedAndNotNull } from '@core/utils';
import * as echarts from 'echarts/core'; import {
import { formatValue, isDefinedAndNotNull, isNumber } from '@core/utils'; rangeChartDefaultSettings,
import { rangeChartDefaultSettings, RangeChartWidgetSettings } from './range-chart-widget.models'; rangeChartTimeSeriesKeySettings,
rangeChartTimeSeriesSettings,
RangeChartWidgetSettings,
RangeItem,
toRangeItems
} from './range-chart-widget.models';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
import { ImagePipe } from '@shared/pipe/image.pipe'; import { ImagePipe } from '@shared/pipe/image.pipe';
import { DomSanitizer } from '@angular/platform-browser'; import { DomSanitizer } from '@angular/platform-browser';
import { import { TbTimeSeriesChart } from '@home/components/widget/lib/chart/time-series-chart';
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();
};
@Component({ @Component({
selector: 'tb-range-chart-widget', selector: 'tb-range-chart-widget',
@ -193,28 +73,19 @@ export class RangeChartWidgetComponent implements OnInit, OnDestroy, AfterViewIn
backgroundStyle$: Observable<ComponentStyle>; backgroundStyle$: Observable<ComponentStyle>;
overlayStyle: ComponentStyle = {}; overlayStyle: ComponentStyle = {};
overlayEnabled: boolean;
padding: string;
legendLabelStyle: ComponentStyle; legendLabelStyle: ComponentStyle;
disabledLegendLabelStyle: ComponentStyle; disabledLegendLabelStyle: ComponentStyle;
visibleRangeItems: RangeItem[]; 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 decimals = 0;
private units = ''; private units = '';
private drawChartPending = false; private rangeItems: RangeItem[];
private rangeChart: ECharts;
private rangeChartOptions: EChartsOption;
private selectedRanges: {[key: number]: boolean} = {};
private tooltipDateFormat: DateFormatProcessor; private timeSeriesChart: TbTimeSeriesChart;
constructor(private imagePipe: ImagePipe, constructor(private imagePipe: ImagePipe,
private sanitizer: DomSanitizer, private sanitizer: DomSanitizer,
@ -235,16 +106,17 @@ export class RangeChartWidgetComponent implements OnInit, OnDestroy, AfterViewIn
if (dataKey?.units) { if (dataKey?.units) {
this.units = dataKey.units; this.units = dataKey.units;
} }
if (dataKey) {
dataKey.settings = rangeChartTimeSeriesKeySettings(this.settings);
}
this.backgroundStyle$ = backgroundStyle(this.settings.background, this.imagePipe, this.sanitizer); this.backgroundStyle$ = backgroundStyle(this.settings.background, this.imagePipe, this.sanitizer);
this.overlayStyle = overlayStyle(this.settings.background.overlay); 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.rangeItems = toRangeItems(this.settings.rangeColors);
this.visibleRangeItems = this.rangeItems.filter(item => item.visible); 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; 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.disabledLegendLabelStyle = textStyle(this.settings.legendLabelFont);
this.legendLabelStyle.color = this.settings.legendLabelColor; this.legendLabelStyle.color = this.settings.legendLabelColor;
} }
if (this.settings.showTooltip && this.settings.tooltipShowDate) {
this.tooltipDateFormat = DateFormatProcessor.fromSettings(this.ctx.$injector, this.settings.tooltipDateFormat);
}
} }
ngAfterViewInit() { ngAfterViewInit() {
if (this.drawChartPending) { const settings = rangeChartTimeSeriesSettings(this.settings, this.rangeItems, this.decimals, this.units);
this.drawChart(); this.timeSeriesChart = new TbTimeSeriesChart(this.ctx, settings, this.chartShape.nativeElement, this.renderer);
}
} }
ngOnDestroy() { ngOnDestroy() {
if (this.shapeResize$) { if (this.timeSeriesChart) {
this.shapeResize$.disconnect(); this.timeSeriesChart.destroy();
}
if (this.rangeChart) {
this.rangeChart.dispose();
} }
} }
public onInit() { public onInit() {
const borderRadius = this.ctx.$widgetElement.css('borderRadius'); const borderRadius = this.ctx.$widgetElement.css('borderRadius');
this.overlayStyle = {...this.overlayStyle, ...{borderRadius}}; this.overlayStyle = {...this.overlayStyle, ...{borderRadius}};
if (this.chartShape) {
this.drawChart();
} else {
this.drawChartPending = true;
}
this.cd.detectChanges(); this.cd.detectChanges();
} }
public onDataUpdated() { public onDataUpdated() {
if (this.rangeChart) { if (this.timeSeriesChart) {
this.rangeChart.setOption({ this.timeSeriesChart.update();
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
}
});
} }
} }
public toggleRangeItem(item: RangeItem) { public toggleRangeItem(item: RangeItem) {
item.enabled = !item.enabled; item.enabled = !item.enabled;
this.selectedRanges[item.index] = item.enabled; this.timeSeriesChart.toggleVisualMapRange(item.index);
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();
}
} }
} }

249
ui-ngx/src/app/modules/home/components/widget/lib/chart/range-chart-widget.models.ts

@ -18,22 +18,79 @@ import {
BackgroundSettings, BackgroundSettings,
BackgroundType, BackgroundType,
ColorRange, ColorRange,
filterIncludingColorRanges,
Font, Font,
simpleDateFormat simpleDateFormat,
sortedColorRange
} from '@shared/models/widget-settings.models'; } from '@shared/models/widget-settings.models';
import { LegendPosition } from '@shared/models/widget.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 { export interface RangeChartWidgetSettings extends EChartsTooltipWidgetSettings {
dataZoom: boolean; dataZoom: boolean;
rangeColors: Array<ColorRange>; rangeColors: Array<ColorRange>;
outOfRangeColor: string; outOfRangeColor: string;
showRangeThresholds: boolean;
rangeThreshold: Partial<TimeSeriesChartThreshold>;
fillArea: boolean; 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; showLegend: boolean;
legendPosition: LegendPosition; legendPosition: LegendPosition;
legendLabelFont: Font; legendLabelFont: Font;
legendLabelColor: string; legendLabelColor: string;
background: BackgroundSettings; background: BackgroundSettings;
padding: string;
} }
export const rangeChartDefaultSettings: RangeChartWidgetSettings = { export const rangeChartDefaultSettings: RangeChartWidgetSettings = {
@ -48,7 +105,51 @@ export const rangeChartDefaultSettings: RangeChartWidgetSettings = {
{from: 40, color: '#D81838'} {from: 40, color: '#D81838'}
], ],
outOfRangeColor: '#ccc', 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, 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, showLegend: true,
legendPosition: LegendPosition.top, legendPosition: LegendPosition.top,
legendLabelFont: { legendLabelFont: {
@ -92,5 +193,149 @@ export const rangeChartDefaultSettings: RangeChartWidgetSettings = {
color: 'rgba(255,255,255,0.72)', color: 'rgba(255,255,255,0.72)',
blur: 3 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 TimeSeriesChartNoAggregationBarWidthStrategy
} from '@home/components/widget/lib/chart/time-series-chart.models'; } from '@home/components/widget/lib/chart/time-series-chart.models';
import { CustomSeriesRenderItemParams } from 'echarts'; 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 { isNumeric } from '@core/utils';
import * as echarts from 'echarts/core'; import * as echarts from 'echarts/core';
@ -34,6 +34,8 @@ export interface BarVisualSettings {
} }
export interface BarRenderSharedContext { export interface BarRenderSharedContext {
barGap: number;
intervalGap: number;
timeInterval: Interval; timeInterval: Interval;
noAggregationBarWidthStrategy: TimeSeriesChartNoAggregationBarWidthStrategy; noAggregationBarWidthStrategy: TimeSeriesChartNoAggregationBarWidthStrategy;
noAggregationWidthRelative: boolean; noAggregationWidthRelative: boolean;
@ -47,6 +49,7 @@ export interface BarRenderContext {
noAggregation?: boolean; noAggregation?: boolean;
visualSettings?: BarVisualSettings; visualSettings?: BarVisualSettings;
labelOption?: SeriesLabelOption; labelOption?: SeriesLabelOption;
additionalLabelOption?: {[key: string]: any};
barStackIndex?: number; barStackIndex?: number;
currentStackItems?: TimeSeriesChartDataItem[]; currentStackItems?: TimeSeriesChartDataItem[];
} }
@ -77,10 +80,13 @@ export const renderTimeSeriesBar = (params: CustomSeriesRenderItemParams, api: C
start = time - interval / 2; start = time - interval / 2;
} }
const gap = 0.3; const barGapRatio = renderCtx.shared.barGap;
const barInterval = separateBar ? interval : interval / (renderCtx.barsCount + gap * (renderCtx.barsCount + 3)); const intervalGapRatio = renderCtx.shared.intervalGap;
const intervalGap = barInterval * gap * 2; const barInterval = separateBar
const barGap = barInterval * gap; ? interval
: interval / (renderCtx.barsCount + barGapRatio * (renderCtx.barsCount - 1) + intervalGapRatio * 2);
const intervalGap = barInterval * intervalGapRatio;
const barGap = barInterval * barGapRatio;
const value = api.value(1); const value = api.value(1);
const startTime = separateBar ? start : start + intervalGap + (barInterval + barGap) * renderCtx.barIndex; const startTime = separateBar ? start : start + intervalGap + (barInterval + barGap) * renderCtx.barIndex;
const delta = barInterval; const delta = barInterval;
@ -124,7 +130,7 @@ export const renderTimeSeriesBar = (params: CustomSeriesRenderItemParams, api: C
const zeroPos = api.coord([0, offset]); const zeroPos = api.coord([0, offset]);
const style: any = { let style: any = {
fill: renderCtx.visualSettings.color, fill: renderCtx.visualSettings.color,
stroke: renderCtx.visualSettings.borderColor, stroke: renderCtx.visualSettings.borderColor,
lineWidth: renderCtx.visualSettings.borderWidth lineWidth: renderCtx.visualSettings.borderWidth
@ -139,10 +145,20 @@ export const renderTimeSeriesBar = (params: CustomSeriesRenderItemParams, api: C
position = 'top'; 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.textDistance = 5;
style.textPosition = position; style.textPosition = position;
style.textBackgroundColor = renderCtx.labelOption.backgroundColor;
style.textPadding = renderCtx.labelOption.padding;
style.textBorderRadius = renderCtx.labelOption.borderRadius;
style.rich = renderCtx.labelOption.rich; style.rich = renderCtx.labelOption.rich;
if (renderCtx.additionalLabelOption) {
style = {...style, ...renderCtx.additionalLabelOption};
}
} }
let borderRadius: number[]; let borderRadius: number[];

344
ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart.models.ts

@ -17,10 +17,11 @@
import { import {
ECharts, ECharts,
EChartsOption, EChartsOption,
EChartsSeriesItem, EChartsSeriesItem, EChartsShape,
EChartsTooltipTrigger, EChartsTooltipTrigger,
EChartsTooltipWidgetSettings, EChartsTooltipWidgetSettings,
measureThresholdLabelOffset, timeAxisBandWidthCalculator measureThresholdOffset,
timeAxisBandWidthCalculator
} from '@home/components/widget/lib/chart/echarts-widget.models'; } from '@home/components/widget/lib/chart/echarts-widget.models';
import { import {
autoDateFormat, autoDateFormat,
@ -30,12 +31,17 @@ import {
textStyle, textStyle,
tsToFormatTimeUnit tsToFormatTimeUnit
} from '@shared/models/widget-settings.models'; } 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 { CustomSeriesOption, LineSeriesOption } from 'echarts/charts';
import { import {
formatValue, formatValue,
isDefinedAndNotNull, isDefinedAndNotNull,
isFunction, isFunction, isNumber,
isNumeric, isNumeric,
isUndefined, isUndefined,
isUndefinedOrNull, isUndefinedOrNull,
@ -45,7 +51,7 @@ import {
import { LinearGradientObject } from 'zrender/lib/graphic/LinearGradient'; import { LinearGradientObject } from 'zrender/lib/graphic/LinearGradient';
import tinycolor from 'tinycolor2'; import tinycolor from 'tinycolor2';
import { ValueAxisBaseOption } from 'echarts/types/src/coord/axisCommonTypes'; 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 { import {
BarRenderContext, BarRenderContext,
BarRenderSharedContext, BarRenderSharedContext,
@ -58,6 +64,7 @@ import { TbColorScheme } from '@shared/models/color.models';
import { AbstractControl, ValidationErrors } from '@angular/forms'; import { AbstractControl, ValidationErrors } from '@angular/forms';
import { MarkLine2DDataItemOption } from 'echarts/types/src/component/marker/MarkLineModel'; import { MarkLine2DDataItemOption } from 'echarts/types/src/component/marker/MarkLineModel';
import { DatePipe } from '@angular/common'; import { DatePipe } from '@angular/common';
import { BuiltinTextPosition } from 'zrender/src/core/types';
export enum TimeSeriesChartType { export enum TimeSeriesChartType {
default = 'default', default = 'default',
@ -74,7 +81,7 @@ export const timeSeriesChartTypeTranslations = new Map<TimeSeriesChartType, stri
] ]
); );
const timeSeriesChartColorScheme: TbColorScheme = { export const timeSeriesChartColorScheme: TbColorScheme = {
'threshold.line': { 'threshold.line': {
light: 'rgba(0, 0, 0, 0.76)', light: 'rgba(0, 0, 0, 0.76)',
dark: '#eee' 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 { export enum TimeSeriesChartLineType {
solid = 'solid', solid = 'solid',
dashed = 'dashed', dashed = 'dashed',
@ -401,6 +380,38 @@ export const defaultTimeSeriesChartYAxisSettings: TimeSeriesChartYAxisSettings =
splitLinesColor: timeSeriesChartColorScheme['axis.splitLine'].light 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 type TimeSeriesChartYAxes = {[id: TimeSeriesChartYAxisId]: TimeSeriesChartYAxisSettings};
export interface TimeSeriesChartThreshold { export interface TimeSeriesChartThreshold {
@ -415,16 +426,19 @@ export interface TimeSeriesChartThreshold {
units?: string; units?: string;
decimals?: number; decimals?: number;
lineColor: string; lineColor: string;
lineType: TimeSeriesChartLineType; lineType: TimeSeriesChartLineType | number | number[];
lineWidth: number; lineWidth: number;
startSymbol: TimeSeriesChartShape; startSymbol: EChartsShape;
startSymbolSize: number; startSymbolSize: number;
endSymbol: TimeSeriesChartShape; endSymbol: EChartsShape;
endSymbolSize: number; endSymbolSize: number;
showLabel: boolean; showLabel: boolean;
labelPosition: ThresholdLabelPosition; labelPosition: ThresholdLabelPosition;
labelFont: Font; labelFont: Font;
labelColor: string; labelColor: string;
enableLabelBackground: boolean;
labelBackground: string;
additionalLabelOption?: {[key: string]: any};
} }
export const timeSeriesChartThresholdValid = (threshold: TimeSeriesChartThreshold): boolean => { export const timeSeriesChartThresholdValid = (threshold: TimeSeriesChartThreshold): boolean => {
@ -469,9 +483,9 @@ export const timeSeriesChartThresholdDefaultSettings: TimeSeriesChartThreshold =
lineColor: timeSeriesChartColorScheme['threshold.line'].light, lineColor: timeSeriesChartColorScheme['threshold.line'].light,
lineType: TimeSeriesChartLineType.solid, lineType: TimeSeriesChartLineType.solid,
lineWidth: 1, lineWidth: 1,
startSymbol: TimeSeriesChartShape.none, startSymbol: EChartsShape.none,
startSymbolSize: 5, startSymbolSize: 5,
endSymbol: TimeSeriesChartShape.arrow, endSymbol: EChartsShape.arrow,
endSymbolSize: 5, endSymbolSize: 5,
showLabel: true, showLabel: true,
labelPosition: ThresholdLabelPosition.end, labelPosition: ThresholdLabelPosition.end,
@ -483,7 +497,9 @@ export const timeSeriesChartThresholdDefaultSettings: TimeSeriesChartThreshold =
weight: '400', weight: '400',
lineHeight: '1' lineHeight: '1'
}, },
labelColor: timeSeriesChartColorScheme['threshold.label'].light labelColor: timeSeriesChartColorScheme['threshold.label'].light,
enableLabelBackground: false,
labelBackground: 'rgba(255,255,255,0.56)'
}; };
export enum TimeSeriesChartNoAggregationBarWidthStrategy { export enum TimeSeriesChartNoAggregationBarWidthStrategy {
@ -513,6 +529,25 @@ export interface TimeSeriesChartNoAggregationBarWidthSettings {
barWidth?: TimeSeriesChartBarWidth; 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 { export enum TimeSeriesChartAnimationEasing {
linear = 'linear', linear = 'linear',
quadraticIn = 'quadraticIn', quadraticIn = 'quadraticIn',
@ -560,6 +595,50 @@ export interface TimeSeriesChartAnimationSettings {
animationDelayUpdate: number; 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 { export interface TimeSeriesChartSettings extends EChartsTooltipWidgetSettings {
thresholds: TimeSeriesChartThreshold[]; thresholds: TimeSeriesChartThreshold[];
darkMode: boolean; darkMode: boolean;
@ -568,7 +647,9 @@ export interface TimeSeriesChartSettings extends EChartsTooltipWidgetSettings {
yAxes: TimeSeriesChartYAxes; yAxes: TimeSeriesChartYAxes;
xAxis: TimeSeriesChartXAxisSettings; xAxis: TimeSeriesChartXAxisSettings;
animation: TimeSeriesChartAnimationSettings; animation: TimeSeriesChartAnimationSettings;
barWidthSettings: TimeSeriesChartBarWidthSettings;
noAggregationBarWidthSettings: TimeSeriesChartNoAggregationBarWidthSettings; noAggregationBarWidthSettings: TimeSeriesChartNoAggregationBarWidthSettings;
visualMapSettings?: TimeSeriesChartVisualMapSettings;
} }
export const timeSeriesChartDefaultSettings: TimeSeriesChartSettings = { export const timeSeriesChartDefaultSettings: TimeSeriesChartSettings = {
@ -581,60 +662,16 @@ export const timeSeriesChartDefaultSettings: TimeSeriesChartSettings = {
defaultTimeSeriesChartYAxisSettings, defaultTimeSeriesChartYAxisSettings,
{ id: 'default', order: 0 } as TimeSeriesChartYAxisSettings) { id: 'default', order: 0 } as TimeSeriesChartYAxisSettings)
}, },
xAxis: { xAxis: mergeDeep({} as TimeSeriesChartXAxisSettings,
show: true, defaultTimeSeriesChartXAxisSettings),
label: '', animation: mergeDeep({} as TimeSeriesChartAnimationSettings,
labelFont: { timeSeriesChartAnimationDefaultSettings),
family: 'Roboto', barWidthSettings: {
size: 12, barGap: 0.3,
sizeUnit: 'px', intervalGap: 0.6
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
}
}, },
noAggregationBarWidthSettings: mergeDeep({} as TimeSeriesChartNoAggregationBarWidthSettings,
timeSeriesChartNoAggregationBarWidthDefaultSettings),
showTooltip: true, showTooltip: true,
tooltipTrigger: EChartsTooltipTrigger.axis, tooltipTrigger: EChartsTooltipTrigger.axis,
tooltipValueFont: { tooltipValueFont: {
@ -683,7 +720,10 @@ export interface LineSeriesSettings {
pointLabelPosition: SeriesLabelPosition; pointLabelPosition: SeriesLabelPosition;
pointLabelFont: Font; pointLabelFont: Font;
pointLabelColor: string; pointLabelColor: string;
pointShape: TimeSeriesChartShape; enablePointLabelBackground: boolean;
pointLabelBackground: string;
pointLabelFormatter?: string | LabelFormatterCallback;
pointShape: EChartsShape;
pointSize: number; pointSize: number;
fillAreaSettings: SeriesFillSettings; fillAreaSettings: SeriesFillSettings;
} }
@ -693,9 +733,14 @@ export interface BarSeriesSettings {
borderWidth: number; borderWidth: number;
borderRadius: number; borderRadius: number;
showLabel: boolean; showLabel: boolean;
labelPosition: SeriesLabelPosition; labelPosition: SeriesLabelPosition | BuiltinTextPosition;
labelFont: Font; labelFont: Font;
labelColor: string; labelColor: string;
enableLabelBackground: boolean;
labelBackground: string;
labelFormatter?: string | LabelFormatterCallback;
labelLayout?: LabelLayoutOption | LabelLayoutOptionCallback;
additionalLabelOption?: {[key: string]: any};
backgroundSettings: SeriesFillSettings; backgroundSettings: SeriesFillSettings;
} }
@ -732,7 +777,9 @@ export const timeSeriesChartKeyDefaultSettings: TimeSeriesChartKeySettings = {
lineHeight: '1' lineHeight: '1'
}, },
pointLabelColor: timeSeriesChartColorScheme['series.label'].light, pointLabelColor: timeSeriesChartColorScheme['series.label'].light,
pointShape: TimeSeriesChartShape.emptyCircle, enablePointLabelBackground: false,
pointLabelBackground: 'rgba(255,255,255,0.56)',
pointShape: EChartsShape.emptyCircle,
pointSize: 4, pointSize: 4,
fillAreaSettings: { fillAreaSettings: {
type: SeriesFillType.none, type: SeriesFillType.none,
@ -758,6 +805,8 @@ export const timeSeriesChartKeyDefaultSettings: TimeSeriesChartKeySettings = {
lineHeight: '1' lineHeight: '1'
}, },
labelColor: timeSeriesChartColorScheme['series.label'].light, labelColor: timeSeriesChartColorScheme['series.label'].light,
enableLabelBackground: false,
labelBackground: 'rgba(255,255,255,0.56)',
backgroundSettings: { backgroundSettings: {
type: SeriesFillType.none, type: SeriesFillType.none,
opacity: 0.4, opacity: 0.4,
@ -944,7 +993,12 @@ export const createTimeSeriesXAxisOption = (settings: TimeSeriesChartXAxisSettin
fontFamily: xAxisTickLabelStyle.fontFamily, fontFamily: xAxisTickLabelStyle.fontFamily,
fontSize: xAxisTickLabelStyle.fontSize, fontSize: xAxisTickLabelStyle.fontSize,
hideOverlap: true, 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 unit = tsToFormatTimeUnit(value);
const format = ticksFormat[unit]; const format = ticksFormat[unit];
const formatted = datePipe.transform(value, format); 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[], export const generateChartData = (dataItems: TimeSeriesChartDataItem[],
thresholdItems: TimeSeriesChartThresholdItem[], thresholdItems: TimeSeriesChartThresholdItem[],
stack: boolean, stack: boolean,
@ -983,7 +1052,7 @@ export const generateChartData = (dataItems: TimeSeriesChartDataItem[],
let series = generateChartSeries(dataItems, let series = generateChartSeries(dataItems,
stack, noAggregation, barRenderSharedContext, darkMode); stack, noAggregation, barRenderSharedContext, darkMode);
if (thresholdItems.length) { if (thresholdItems.length) {
const thresholds = generateChartThresholds(thresholdItems, darkMode); const thresholds = generateChartThresholds(thresholdItems);
series = series.concat(thresholds); series = series.concat(thresholds);
} }
return series; return series;
@ -995,7 +1064,7 @@ export const calculateThresholdsOffset = (chart: ECharts,
const result: [number, number] = [0, 0]; const result: [number, number] = [0, 0];
for (const item of thresholdItems) { for (const item of thresholdItems) {
const yAxis = yAxisList[item.yAxisIndex]; 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[0] = Math.max(result[0], offset[0]);
result[1] = Math.max(result[1], offset[1]); result[1] = Math.max(result[1], offset[1]);
} }
@ -1017,7 +1086,7 @@ export const parseThresholdData = (value: any): TimeSeriesChartThresholdValue =>
return thresholdValue; return thresholdValue;
}; };
const generateChartThresholds = (thresholdItems: TimeSeriesChartThresholdItem[], darkMode: boolean): Array<LineSeriesOption> => { const generateChartThresholds = (thresholdItems: TimeSeriesChartThresholdItem[]): Array<LineSeriesOption> => {
const series: Array<LineSeriesOption> = []; const series: Array<LineSeriesOption> = [];
for (const item of thresholdItems) { for (const item of thresholdItems) {
if (isDefinedAndNotNull(item.value)) { 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; item.option = seriesOption;
} }
seriesOption.markLine.data = []; seriesOption.markLine.data = [];
@ -1131,7 +1208,7 @@ const generateChartSeries = (dataItems: TimeSeriesChartDataItem[],
export const updateDarkMode = (options: EChartsOption, settings: TimeSeriesChartSettings, export const updateDarkMode = (options: EChartsOption, settings: TimeSeriesChartSettings,
yAxisList: TimeSeriesChartYAxis[], yAxisList: TimeSeriesChartYAxis[],
dataItems: TimeSeriesChartDataItem[], thresholdDataItems: TimeSeriesChartThresholdItem[], dataItems: TimeSeriesChartDataItem[],
darkMode: boolean): EChartsOption => { darkMode: boolean): EChartsOption => {
options.darkMode = darkMode; options.darkMode = darkMode;
if (Array.isArray(options.yAxis)) { if (Array.isArray(options.yAxis)) {
@ -1171,7 +1248,7 @@ export const updateDarkMode = (options: EChartsOption, settings: TimeSeriesChart
} else { } else {
if (item.barRenderContext?.labelOption?.show) { if (item.barRenderContext?.labelOption?.show) {
const barSettings = item.dataKey.settings as BarSeriesSettings; 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'); darkMode, 'series.label');
} }
} }
@ -1215,7 +1292,10 @@ const createTimeSeriesChartSeries = (item: TimeSeriesChartDataItem,
const lineSeriesOption = seriesOption as LineSeriesOption; const lineSeriesOption = seriesOption as LineSeriesOption;
lineSeriesOption.type = 'line'; lineSeriesOption.type = 'line';
lineSeriesOption.label = createSeriesLabelOption(item, lineSettings.showPointLabel, 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.step = lineSettings.step ? lineSettings.stepType : false;
lineSeriesOption.smooth = lineSettings.smooth; lineSeriesOption.smooth = lineSettings.smooth;
if (lineSettings.smooth) { if (lineSettings.smooth) {
@ -1229,7 +1309,7 @@ const createTimeSeriesChartSeries = (item: TimeSeriesChartDataItem,
lineSeriesOption.areaStyle = {}; lineSeriesOption.areaStyle = {};
if (lineSettings.fillAreaSettings.type === SeriesFillType.opacity) { if (lineSettings.fillAreaSettings.type === SeriesFillType.opacity) {
lineSeriesOption.areaStyle.opacity = lineSettings.fillAreaSettings.opacity; lineSeriesOption.areaStyle.opacity = lineSettings.fillAreaSettings.opacity;
} else { } else if (lineSettings.fillAreaSettings.type === SeriesFillType.gradient) {
lineSeriesOption.areaStyle.opacity = 1; lineSeriesOption.areaStyle.opacity = 1;
lineSeriesOption.areaStyle.color = createLinearOpacityGradient(seriesColor, lineSettings.fillAreaSettings.gradient); lineSeriesOption.areaStyle.color = createLinearOpacityGradient(seriesColor, lineSettings.fillAreaSettings.gradient);
} }
@ -1256,9 +1336,12 @@ const createTimeSeriesChartSeries = (item: TimeSeriesChartDataItem,
} }
item.barRenderContext.visualSettings = barVisualSettings; item.barRenderContext.visualSettings = barVisualSettings;
item.barRenderContext.labelOption = createSeriesLabelOption(item, barSettings.showLabel, 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) => barSeriesOption.renderItem = (params, api) =>
renderTimeSeriesBar(params, api, item.barRenderContext); renderTimeSeriesBar(params, api, item.barRenderContext);
barSeriesOption.labelLayout = barSettings.labelLayout;
} }
} }
seriesOption.data = item.data; seriesOption.data = item.data;
@ -1266,30 +1349,63 @@ const createTimeSeriesChartSeries = (item: TimeSeriesChartDataItem,
}; };
const createSeriesLabelOption = (item: TimeSeriesChartDataItem, show: boolean, 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 => { darkMode: boolean): SeriesLabelOption => {
let labelStyle: ComponentStyle = {}; let labelStyle: ComponentStyle = {};
if (show) { if (show) {
labelStyle = createChartTextStyle(labelFont, labelColor, darkMode, 'series.label'); labelStyle = createChartTextStyle(labelFont, labelColor, darkMode, 'series.label', labelColorFill);
} }
return { let formatter: LabelFormatterCallback;
show, if (isFunction(labelFormatter)) {
position, formatter = labelFormatter as LabelFormatterCallback;
formatter: (params): string => { } 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); const value = formatValue(params.value[1], item.decimals, item.units, false);
return `{value|${value}}`; return `{value|${value}}`;
}, };
}
const labelOption: SeriesLabelOption = {
show,
position,
formatter,
rich: { rich: {
value: labelStyle 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); const style = textStyle(font);
delete style.lineHeight; delete style.lineHeight;
style.fontSize = font.size; 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; 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 { import {
AxisPosition, AxisPosition,
calculateThresholdsOffset, calculateThresholdsOffset,
createTimeSeriesVisualMapOption,
createTimeSeriesXAxisOption, createTimeSeriesXAxisOption,
createTimeSeriesYAxis, createTimeSeriesYAxis,
defaultTimeSeriesChartYAxisSettings, defaultTimeSeriesChartYAxisSettings,
@ -31,7 +32,6 @@ import {
TimeSeriesChartNoAggregationBarWidthStrategy, TimeSeriesChartNoAggregationBarWidthStrategy,
TimeSeriesChartSeriesType, TimeSeriesChartSeriesType,
TimeSeriesChartSettings, TimeSeriesChartSettings,
TimeSeriesChartShape,
TimeSeriesChartThreshold, TimeSeriesChartThreshold,
timeSeriesChartThresholdDefaultSettings, timeSeriesChartThresholdDefaultSettings,
TimeSeriesChartThresholdItem, TimeSeriesChartThresholdItem,
@ -48,10 +48,11 @@ import {
calculateYAxisWidth, calculateYAxisWidth,
ECharts, ECharts,
echartsModule, echartsModule,
EChartsOption, EChartsOption, EChartsShape,
echartsTooltipFormatter, echartsTooltipFormatter,
EChartsTooltipTrigger, EChartsTooltipTrigger,
getAxisExtent, getAxisExtent,
getFocusedSeriesIndex,
measureXAxisNameHeight, measureXAxisNameHeight,
measureYAxisNameWidth, measureYAxisNameWidth,
toNamedData toNamedData
@ -60,7 +61,7 @@ import { DateFormatProcessor } from '@shared/models/widget-settings.models';
import { isDefinedAndNotNull, isEqual, mergeDeep } from '@core/utils'; import { isDefinedAndNotNull, isEqual, mergeDeep } from '@core/utils';
import { DataKey, Datasource, DatasourceType, widgetType } from '@shared/models/widget.models'; import { DataKey, Datasource, DatasourceType, widgetType } from '@shared/models/widget.models';
import * as echarts from 'echarts/core'; 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 { Renderer2 } from '@angular/core';
import { CustomSeriesOption, LineSeriesOption } from 'echarts/charts'; import { CustomSeriesOption, LineSeriesOption } from 'echarts/charts';
import { BehaviorSubject } from 'rxjs'; import { BehaviorSubject } from 'rxjs';
@ -86,7 +87,7 @@ export class TbTimeSeriesChart {
settings.type = TimeSeriesChartSeriesType.line; settings.type = TimeSeriesChartSeriesType.line;
settings.lineSettings.showLine = false; settings.lineSettings.showLine = false;
settings.lineSettings.showPoints = true; settings.lineSettings.showPoints = true;
settings.lineSettings.pointShape = TimeSeriesChartShape.circle; settings.lineSettings.pointShape = EChartsShape.circle;
settings.lineSettings.pointSize = 8; settings.lineSettings.pointSize = 8;
} }
return settings; return settings;
@ -107,6 +108,9 @@ export class TbTimeSeriesChart {
private dataItems: TimeSeriesChartDataItem[] = []; private dataItems: TimeSeriesChartDataItem[] = [];
private thresholdItems: TimeSeriesChartThresholdItem[] = []; private thresholdItems: TimeSeriesChartThresholdItem[] = [];
private hasVisualMap = false;
private visualMapSelectedRanges: {[key: number]: boolean};
private timeSeriesChart: ECharts; private timeSeriesChart: ECharts;
private timeSeriesChartOptions: EChartsOption; private timeSeriesChartOptions: EChartsOption;
@ -145,6 +149,7 @@ export class TbTimeSeriesChart {
this.setupYAxes(); this.setupYAxes();
this.setupData(); this.setupData();
this.setupThresholds(); this.setupThresholds();
this.setupVisualMap();
if (this.settings.showTooltip && this.settings.tooltipShowDate) { if (this.settings.showTooltip && this.settings.tooltipShowDate) {
this.tooltipDateFormat = DateFormatProcessor.fromSettings(this.ctx.$injector, this.settings.tooltipDateFormat); this.tooltipDateFormat = DateFormatProcessor.fromSettings(this.ctx.$injector, this.settings.tooltipDateFormat);
} }
@ -186,6 +191,9 @@ export class TbTimeSeriesChart {
} else { } else {
this.timeSeriesChartOptions.tooltip[0].axisPointer.type = 'shadow'; 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.barRenderSharedContext.timeInterval = this.ctx.timeWindow.interval;
this.updateSeriesData(true); this.updateSeriesData(true);
if (this.highlightedDataKey) { 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 { public destroy(): void {
if (this.shapeResize$) { if (this.shapeResize$) {
this.shapeResize$.disconnect(); this.shapeResize$.disconnect();
@ -284,8 +302,7 @@ export class TbTimeSeriesChart {
this.darkMode = darkMode; this.darkMode = darkMode;
if (this.timeSeriesChart) { if (this.timeSeriesChart) {
this.timeSeriesChartOptions = updateDarkMode(this.timeSeriesChartOptions, this.timeSeriesChartOptions = updateDarkMode(this.timeSeriesChartOptions,
this.settings, this.yAxisList, this.dataItems, this.settings, this.yAxisList, this.dataItems, darkMode);
this.thresholdItems, darkMode);
this.timeSeriesChart.setOption(this.timeSeriesChartOptions); this.timeSeriesChart.setOption(this.timeSeriesChartOptions);
} }
} }
@ -300,6 +317,8 @@ export class TbTimeSeriesChart {
const targetBarWidth = noAggregationBarWidthSettings.strategy === TimeSeriesChartNoAggregationBarWidthStrategy.group ? const targetBarWidth = noAggregationBarWidthSettings.strategy === TimeSeriesChartNoAggregationBarWidthStrategy.group ?
noAggregationBarWidthSettings.groupWidth : noAggregationBarWidthSettings.barWidth; noAggregationBarWidthSettings.groupWidth : noAggregationBarWidthSettings.barWidth;
this.barRenderSharedContext = { this.barRenderSharedContext = {
barGap: this.settings.barWidthSettings.barGap,
intervalGap: this.settings.barWidthSettings.intervalGap,
timeInterval: this.ctx.timeWindow?.interval, timeInterval: this.ctx.timeWindow?.interval,
noAggregationBarWidthStrategy: noAggregationBarWidthSettings.strategy, noAggregationBarWidthStrategy: noAggregationBarWidthSettings.strategy,
noAggregationWidthRelative: targetBarWidth.relative, noAggregationWidthRelative: targetBarWidth.relative,
@ -313,7 +332,10 @@ export class TbTimeSeriesChart {
timeSeriesChartKeyDefaultSettings, dataKey.settings); timeSeriesChartKeyDefaultSettings, dataKey.settings);
if ((keySettings.type === TimeSeriesChartSeriesType.line && keySettings.lineSettings.showPointLabel && if ((keySettings.type === TimeSeriesChartSeriesType.line && keySettings.lineSettings.showPointLabel &&
keySettings.lineSettings.pointLabelPosition === SeriesLabelPosition.top) || 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; this.topPointLabels = true;
} }
dataKey.settings = keySettings; dataKey.settings = keySettings;
@ -346,6 +368,11 @@ export class TbTimeSeriesChart {
for (const thresholdSettings of this.settings.thresholds) { for (const thresholdSettings of this.settings.thresholds) {
const threshold = mergeDeep<TimeSeriesChartThreshold>({} as TimeSeriesChartThreshold, const threshold = mergeDeep<TimeSeriesChartThreshold>({} as TimeSeriesChartThreshold,
timeSeriesChartThresholdDefaultSettings, thresholdSettings); timeSeriesChartThresholdDefaultSettings, thresholdSettings);
if (!this.topPointLabels) {
if (threshold.showLabel && !threshold.labelPosition.endsWith('Bottom')) {
this.topPointLabels = true;
}
}
let latestDataKey: DataKey = null; let latestDataKey: DataKey = null;
let entityDataKey: DataKey = null; let entityDataKey: DataKey = null;
let value = 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 { private nextComponentId(): string {
return (this.componentIndexCounter++) + ''; return (this.componentIndexCounter++) + '';
@ -492,8 +528,9 @@ export class TbTimeSeriesChart {
}, },
formatter: (params: CallbackDataParams[]) => formatter: (params: CallbackDataParams[]) =>
this.settings.showTooltip ? echartsTooltipFormatter(this.renderer, this.tooltipDateFormat, this.settings.showTooltip ? echartsTooltipFormatter(this.renderer, this.tooltipDateFormat,
this.settings, params, 0, '', -1, this.dataItems, this.settings, params, 0, '',
this.noAggregation ? null : this.ctx.timeWindow.interval) : undefined, this.settings.tooltipShowFocusedSeries ? getFocusedSeriesIndex(this.timeSeriesChart) : -1,
this.dataItems, this.noAggregation ? null : this.ctx.timeWindow.interval) : undefined,
padding: [8, 12], padding: [8, 12],
backgroundColor: this.settings.tooltipBackgroundColor, backgroundColor: this.settings.tooltipBackgroundColor,
borderWidth: 0, borderWidth: 0,
@ -533,6 +570,10 @@ export class TbTimeSeriesChart {
animationEasingUpdate: this.settings.animation.animationEasingUpdate, animationEasingUpdate: this.settings.animation.animationEasingUpdate,
animationDelayUpdate: this.settings.animation.animationDelayUpdate 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; this.timeSeriesChartOptions.xAxis[0].tbTimeWindow = this.ctx.defaultSubscription.timeWindow;
@ -542,7 +583,7 @@ export class TbTimeSeriesChart {
} }
this.timeSeriesChart.setOption(this.timeSeriesChartOptions); this.timeSeriesChart.setOption(this.timeSeriesChartOptions);
this.updateAxes(); this.updateAxes(false);
if (this.settings.dataZoom) { if (this.settings.dataZoom) {
this.timeSeriesChart.on('datazoom', () => { this.timeSeriesChart.on('datazoom', () => {
@ -567,7 +608,7 @@ export class TbTimeSeriesChart {
this.barRenderSharedContext, this.darkMode); this.barRenderSharedContext, this.darkMode);
} }
private updateAxes() { private updateAxes(lazy = true) {
const leftAxisList = this.yAxisList.filter(axis => axis.option.position === 'left'); const leftAxisList = this.yAxisList.filter(axis => axis.option.position === 'left');
let res = this.updateYAxisOffset(leftAxisList); let res = this.updateYAxisOffset(leftAxisList);
let leftOffset = res.offset + (!res.offset && this.settings.dataZoom ? 5 : 0); let leftOffset = res.offset + (!res.offset && this.settings.dataZoom ? 5 : 0);
@ -617,7 +658,7 @@ export class TbTimeSeriesChart {
} }
if (changed) { if (changed) {
this.timeSeriesChartOptions.yAxis = this.yAxisList.map(axis => axis.option); 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) { if (this.yAxisList.length) {
const extent = getAxisExtent(this.timeSeriesChart, this.yAxisList[0].id); const extent = getAxisExtent(this.timeSeriesChart, this.yAxisList[0].id);
@ -696,7 +737,7 @@ export class TbTimeSeriesChart {
private minTopOffset(): number { private minTopOffset(): number {
const showTickLabels = const showTickLabels =
!!this.yAxisList.find(yAxis => yAxis.settings.show && yAxis.settings.showTickLabels); !!this.yAxisList.find(yAxis => yAxis.settings.show && yAxis.settings.showTickLabels);
return (this.topPointLabels) ? 20 : return (this.topPointLabels) ? 25 :
(showTickLabels ? 10 : 5); (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"> <ng-container [formGroup]="barChartWidgetSettingsForm">
<div class="tb-form-panel"> <div class="tb-form-panel">
<div class="tb-form-panel-title" translate>widgets.bar-chart.bar-chart-card-style</div> <div class="tb-form-panel-title" translate>widgets.bar-chart.bar-chart-style</div>
<div class="tb-form-row space-between"> <div class="tb-form-row">
<mat-slide-toggle class="mat-slide fixed-title-width" formControlName="showBarLabel"> <mat-slide-toggle class="mat-slide" formControlName="dataZoom">
{{ 'widgets.bar-chart.label-on-bar' | translate }} {{ 'widgets.time-series-chart.data-zoom' | translate }}
</mat-slide-toggle> </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>
<div class="tb-form-row space-between"> <div class="tb-form-panel">
<mat-slide-toggle class="mat-slide fixed-title-width" formControlName="showBarValue"> <div class="tb-form-panel-title" translate>widgets.bar-chart.bar-appearance</div>
{{ 'widgets.bar-chart.value-on-bar' | translate }} <div class="tb-form-row space-between">
</mat-slide-toggle> <mat-slide-toggle class="mat-slide fixed-title-width" formControlName="showBarLabel">
<div fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px"> {{ 'widgets.bar-chart.label-on-bar' | translate }}
<tb-font-settings formControlName="barValueFont" </mat-slide-toggle>
disabledLineHeight <div fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px">
forceSizeUnit="px" <tb-font-settings formControlName="barLabelFont"
previewText="45"> disabledLineHeight
</tb-font-settings> forceSizeUnit="px"
<tb-color-input asBoxInput previewText="Humidity">
colorClearButton </tb-font-settings>
formControlName="barValueColor"> <tb-color-input asBoxInput
</tb-color-input> 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>
<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> </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"> <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 class="tb-settings" [expanded]="barChartWidgetSettingsForm.get('showLegend').value" [disabled]="!barChartWidgetSettingsForm.get('showLegend').value">
<mat-expansion-panel-header fxLayout="row wrap"> <mat-expansion-panel-header fxLayout="row wrap">
@ -149,10 +209,22 @@
</ng-template> </ng-template>
</mat-expansion-panel> </mat-expansion-panel>
</div> </div>
<div class="tb-form-row space-between"> <tb-time-series-chart-animation-settings
<div>{{ 'widgets.background.background' | translate }}</div> formControlName="animation">
<tb-background-settings formControlName="background"> </tb-time-series-chart-animation-settings>
</tb-background-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>
</div> </div>
</ng-container> </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 { Component, Injector } from '@angular/core';
import { import {
Datasource,
legendPositions, legendPositions,
legendPositionTranslationMap, legendPositionTranslationMap,
WidgetSettings, WidgetSettings,
@ -37,6 +38,15 @@ import {
}) })
export class BarChartWithLabelsWidgetSettingsComponent extends WidgetSettingsComponent { 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; legendPositions = legendPositions;
legendPositionTranslationMap = legendPositionTranslationMap; legendPositionTranslationMap = legendPositionTranslationMap;
@ -64,12 +74,26 @@ export class BarChartWithLabelsWidgetSettingsComponent extends WidgetSettingsCom
protected onSettingsSet(settings: WidgetSettings) { protected onSettingsSet(settings: WidgetSettings) {
this.barChartWidgetSettingsForm = this.fb.group({ this.barChartWidgetSettingsForm = this.fb.group({
dataZoom: [settings.dataZoom, []],
showBarLabel: [settings.showBarLabel, []], showBarLabel: [settings.showBarLabel, []],
barLabelFont: [settings.barLabelFont, []], barLabelFont: [settings.barLabelFont, []],
barLabelColor: [settings.barLabelColor, []], barLabelColor: [settings.barLabelColor, []],
showBarValue: [settings.showBarValue, []], showBarValue: [settings.showBarValue, []],
barValueFont: [settings.barValueFont, []], barValueFont: [settings.barValueFont, []],
barValueColor: [settings.barValueColor, []], 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, []], showLegend: [settings.showLegend, []],
legendPosition: [settings.legendPosition, []], legendPosition: [settings.legendPosition, []],
@ -88,17 +112,19 @@ export class BarChartWithLabelsWidgetSettingsComponent extends WidgetSettingsCom
tooltipBackgroundColor: [settings.tooltipBackgroundColor, []], tooltipBackgroundColor: [settings.tooltipBackgroundColor, []],
tooltipBackgroundBlur: [settings.tooltipBackgroundBlur, []], tooltipBackgroundBlur: [settings.tooltipBackgroundBlur, []],
background: [settings.background, []] background: [settings.background, []],
padding: [settings.padding, []]
}); });
} }
protected validatorTriggers(): string[] { protected validatorTriggers(): string[] {
return ['showBarLabel', 'showBarValue', 'showLegend', 'showTooltip', 'tooltipShowDate']; return ['showBarLabel', 'showBarValue', 'showBarBorder', 'showLegend', 'showTooltip', 'tooltipShowDate'];
} }
protected updateValidators(emitEvent: boolean) { protected updateValidators(emitEvent: boolean) {
const showBarLabel: boolean = this.barChartWidgetSettingsForm.get('showBarLabel').value; const showBarLabel: boolean = this.barChartWidgetSettingsForm.get('showBarLabel').value;
const showBarValue: boolean = this.barChartWidgetSettingsForm.get('showBarValue').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 showLegend: boolean = this.barChartWidgetSettingsForm.get('showLegend').value;
const showTooltip: boolean = this.barChartWidgetSettingsForm.get('showTooltip').value; const showTooltip: boolean = this.barChartWidgetSettingsForm.get('showTooltip').value;
const tooltipShowDate: boolean = this.barChartWidgetSettingsForm.get('tooltipShowDate').value; const tooltipShowDate: boolean = this.barChartWidgetSettingsForm.get('tooltipShowDate').value;
@ -119,6 +145,12 @@ export class BarChartWithLabelsWidgetSettingsComponent extends WidgetSettingsCom
this.barChartWidgetSettingsForm.get('barValueColor').disable(); this.barChartWidgetSettingsForm.get('barValueColor').disable();
} }
if (showBarBorder) {
this.barChartWidgetSettingsForm.get('barBorderWidth').enable();
} else {
this.barChartWidgetSettingsForm.get('barBorderWidth').disable();
}
if (showLegend) { if (showLegend) {
this.barChartWidgetSettingsForm.get('legendPosition').enable(); this.barChartWidgetSettingsForm.get('legendPosition').enable();
this.barChartWidgetSettingsForm.get('legendLabelFont').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"> <ng-container [formGroup]="rangeChartWidgetSettingsForm">
<div class="tb-form-panel"> <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"> <div class="tb-form-row">
<mat-slide-toggle class="mat-slide" formControlName="dataZoom"> <mat-slide-toggle class="mat-slide" formControlName="dataZoom">
{{ 'widgets.range-chart.data-zoom' | translate }} {{ 'widgets.range-chart.data-zoom' | translate }}
</mat-slide-toggle> </mat-slide-toggle>
</div> </div>
<div class="tb-form-row space-between"> <div class="tb-form-panel">
<div>{{ 'widgets.range-chart.range-colors' | translate }}</div> <div class="tb-form-panel-title" translate>widgets.range-chart.range-chart-appearance</div>
<tb-color-range-settings formControlName="rangeColors"> <div class="tb-form-row space-between">
</tb-color-range-settings> <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>
<div class="tb-form-row space-between"> <div class="tb-form-panel">
<div>{{ 'widgets.range-chart.out-of-range-color' | translate }}</div> <div class="tb-form-panel-title" translate>widgets.time-series-chart.axis.y-axis</div>
<tb-color-input asBoxInput <tb-time-series-chart-axis-settings
colorClearButton formControlName="yAxis"
formControlName="outOfRangeColor"> axisType="yAxis"
</tb-color-input> hideUnits
hideDecimals>
</tb-time-series-chart-axis-settings>
</div> </div>
<div class="tb-form-row"> <div class="tb-form-panel">
<mat-slide-toggle class="mat-slide" formControlName="fillArea"> <div class="tb-form-panel-title" translate>widgets.time-series-chart.axis.x-axis</div>
{{ 'widgets.range-chart.fill-area' | translate }} <tb-time-series-chart-axis-settings
</mat-slide-toggle> formControlName="xAxis"
axisType="xAxis">
</tb-time-series-chart-axis-settings>
</div> </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"> <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 class="tb-settings" [expanded]="rangeChartWidgetSettingsForm.get('showLegend').value" [disabled]="!rangeChartWidgetSettingsForm.get('showLegend').value">
<mat-expansion-panel-header fxLayout="row wrap"> <mat-expansion-panel-header fxLayout="row wrap">
@ -139,10 +285,22 @@
</ng-template> </ng-template>
</mat-expansion-panel> </mat-expansion-panel>
</div> </div>
<div class="tb-form-row space-between"> <tb-time-series-chart-animation-settings
<div>{{ 'widgets.background.background' | translate }}</div> formControlName="animation">
<tb-background-settings formControlName="background"> </tb-time-series-chart-animation-settings>
</tb-background-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>
</div> </div>
</ng-container> </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 { Component, Injector } from '@angular/core';
import { import {
Datasource,
legendPositions, legendPositions,
legendPositionTranslationMap, legendPositionTranslationMap,
WidgetSettings, WidgetSettings,
WidgetSettingsComponent WidgetSettingsComponent
} from '@shared/models/widget.models'; } from '@shared/models/widget.models';
import { UntypedFormBuilder, UntypedFormGroup } from '@angular/forms'; import { UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms';
import { Store } from '@ngrx/store'; import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state'; import { AppState } from '@core/core.state';
import { formatValue } from '@core/utils'; import { formatValue } from '@core/utils';
import { rangeChartDefaultSettings } from '@home/components/widget/lib/chart/range-chart-widget.models'; import { rangeChartDefaultSettings } from '@home/components/widget/lib/chart/range-chart-widget.models';
import { DateFormatProcessor, DateFormatSettings } from '@shared/models/widget-settings.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({ @Component({
selector: 'tb-range-chart-widget-settings', selector: 'tb-range-chart-widget-settings',
@ -35,12 +42,39 @@ import { DateFormatProcessor, DateFormatSettings } from '@shared/models/widget-s
}) })
export class RangeChartWidgetSettingsComponent extends WidgetSettingsComponent { 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; legendPositions = legendPositions;
legendPositionTranslationMap = legendPositionTranslationMap; legendPositionTranslationMap = legendPositionTranslationMap;
rangeChartWidgetSettingsForm: UntypedFormGroup; rangeChartWidgetSettingsForm: UntypedFormGroup;
pointLabelPreviewFn = this._pointLabelPreviewFn.bind(this);
tooltipValuePreviewFn = this._tooltipValuePreviewFn.bind(this); tooltipValuePreviewFn = this._tooltipValuePreviewFn.bind(this);
tooltipDatePreviewFn = this._tooltipDatePreviewFn.bind(this); tooltipDatePreviewFn = this._tooltipDatePreviewFn.bind(this);
@ -64,7 +98,34 @@ export class RangeChartWidgetSettingsComponent extends WidgetSettingsComponent {
dataZoom: [settings.dataZoom, []], dataZoom: [settings.dataZoom, []],
rangeColors: [settings.rangeColors, []], rangeColors: [settings.rangeColors, []],
outOfRangeColor: [settings.outOfRangeColor, []], outOfRangeColor: [settings.outOfRangeColor, []],
showRangeThresholds: [settings.showRangeThresholds, []],
rangeThreshold: [settings.rangeThreshold, []],
fillArea: [settings.fillArea, []], 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, []], showLegend: [settings.showLegend, []],
legendPosition: [settings.legendPosition, []], legendPosition: [settings.legendPosition, []],
@ -83,19 +144,75 @@ export class RangeChartWidgetSettingsComponent extends WidgetSettingsComponent {
tooltipBackgroundColor: [settings.tooltipBackgroundColor, []], tooltipBackgroundColor: [settings.tooltipBackgroundColor, []],
tooltipBackgroundBlur: [settings.tooltipBackgroundBlur, []], tooltipBackgroundBlur: [settings.tooltipBackgroundBlur, []],
background: [settings.background, []] background: [settings.background, []],
padding: [settings.padding, []]
}); });
} }
protected validatorTriggers(): string[] { protected validatorTriggers(): string[] {
return ['showLegend', 'showTooltip', 'tooltipShowDate']; return ['showRangeThresholds', 'fillArea', 'showLine', 'step', 'showPointLabel', 'enablePointLabelBackground',
'showLegend', 'showTooltip', 'tooltipShowDate'];
} }
protected updateValidators(emitEvent: boolean) { 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 showLegend: boolean = this.rangeChartWidgetSettingsForm.get('showLegend').value;
const showTooltip: boolean = this.rangeChartWidgetSettingsForm.get('showTooltip').value; const showTooltip: boolean = this.rangeChartWidgetSettingsForm.get('showTooltip').value;
const tooltipShowDate: boolean = this.rangeChartWidgetSettingsForm.get('tooltipShowDate').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) { if (showLegend) {
this.rangeChartWidgetSettingsForm.get('legendPosition').enable(); this.rangeChartWidgetSettingsForm.get('legendPosition').enable();
this.rangeChartWidgetSettingsForm.get('legendLabelFont').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 { private _tooltipValuePreviewFn(): string {
const units: string = this.widgetConfig.config.units; const units: string = this.widgetConfig.config.units;
const decimals: number = this.widgetConfig.config.decimals; const decimals: number = this.widgetConfig.config.decimals;
@ -148,5 +271,4 @@ export class RangeChartWidgetSettingsComponent extends WidgetSettingsComponent {
processor.update(Date.now()); processor.update(Date.now());
return processor.formatted; 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> </tb-color-input>
</div> </div>
</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 <tb-time-series-chart-fill-settings
formControlName="backgroundSettings" formControlName="backgroundSettings"
title="widgets.time-series-chart.series.background" 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, []], labelPosition: [null, []],
labelFont: [null, []], labelFont: [null, []],
labelColor: [null, []], labelColor: [null, []],
enableLabelBackground: [null, []],
labelBackground: [null, []],
backgroundSettings: [null, []] backgroundSettings: [null, []]
}); });
this.barSettingsFormGroup.valueChanges.subscribe(() => { this.barSettingsFormGroup.valueChanges.subscribe(() => {
this.updateModel(); this.updateModel();
}); });
merge(this.barSettingsFormGroup.get('showBorder').valueChanges, merge(this.barSettingsFormGroup.get('showBorder').valueChanges,
this.barSettingsFormGroup.get('showLabel').valueChanges) this.barSettingsFormGroup.get('showLabel').valueChanges,
this.barSettingsFormGroup.get('enableLabelBackground').valueChanges)
.subscribe(() => { .subscribe(() => {
this.updateValidators(); this.updateValidators();
}); });
@ -116,6 +119,7 @@ export class TimeSeriesChartBarSettingsComponent implements OnInit, ControlValue
private updateValidators() { private updateValidators() {
const showBorder: boolean = this.barSettingsFormGroup.get('showBorder').value; const showBorder: boolean = this.barSettingsFormGroup.get('showBorder').value;
const showLabel: boolean = this.barSettingsFormGroup.get('showLabel').value; const showLabel: boolean = this.barSettingsFormGroup.get('showLabel').value;
const enableLabelBackground: boolean = this.barSettingsFormGroup.get('enableLabelBackground').value;
if (showBorder) { if (showBorder) {
this.barSettingsFormGroup.get('borderWidth').enable({emitEvent: false}); this.barSettingsFormGroup.get('borderWidth').enable({emitEvent: false});
} else { } else {
@ -125,10 +129,18 @@ export class TimeSeriesChartBarSettingsComponent implements OnInit, ControlValue
this.barSettingsFormGroup.get('labelPosition').enable({emitEvent: false}); this.barSettingsFormGroup.get('labelPosition').enable({emitEvent: false});
this.barSettingsFormGroup.get('labelFont').enable({emitEvent: false}); this.barSettingsFormGroup.get('labelFont').enable({emitEvent: false});
this.barSettingsFormGroup.get('labelColor').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 { } else {
this.barSettingsFormGroup.get('labelPosition').disable({emitEvent: false}); this.barSettingsFormGroup.get('labelPosition').disable({emitEvent: false});
this.barSettingsFormGroup.get('labelFont').disable({emitEvent: false}); this.barSettingsFormGroup.get('labelFont').disable({emitEvent: false});
this.barSettingsFormGroup.get('labelColor').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> </tb-color-input>
</div> </div>
</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 class="tb-form-row space-between column-xs">
<div translate>widgets.time-series-chart.series.point.point-shape</div> <div translate>widgets.time-series-chart.series.point.point-shape</div>
<mat-form-field class="medium-width" appearance="outline" subscriptSizing="dynamic"> <mat-form-field class="medium-width" appearance="outline" subscriptSizing="dynamic">
<mat-select formControlName="pointShape"> <mat-select formControlName="pointShape">
<mat-option *ngFor="let shape of timeSeriesChartShapes" [value]="shape"> <mat-option *ngFor="let shape of echartsShapes" [value]="shape">
{{ timeSeriesChartShapeTranslations.get(shape) | translate }} {{ echartsShapeTranslations.get(shape) | translate }}
</mat-option> </mat-option>
</mat-select> </mat-select>
</mat-form-field> </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, lineSeriesStepTypeTranslations,
seriesLabelPositions, seriesLabelPositions,
seriesLabelPositionTranslations, seriesLabelPositionTranslations,
timeSeriesChartShapes, TimeSeriesChartType,
timeSeriesChartShapeTranslations, TimeSeriesChartType,
timeSeriesLineTypes, timeSeriesLineTypes,
timeSeriesLineTypeTranslations timeSeriesLineTypeTranslations
} from '@home/components/widget/lib/chart/time-series-chart.models'; } 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 { Store } from '@ngrx/store';
import { AppState } from '@core/core.state'; import { AppState } from '@core/core.state';
import { merge } from 'rxjs'; import { merge } from 'rxjs';
@ -67,9 +67,9 @@ export class TimeSeriesChartLineSettingsComponent implements OnInit, ControlValu
seriesLabelPositionTranslations = seriesLabelPositionTranslations; seriesLabelPositionTranslations = seriesLabelPositionTranslations;
timeSeriesChartShapes = timeSeriesChartShapes; echartsShapes = echartsShapes;
timeSeriesChartShapeTranslations = timeSeriesChartShapeTranslations; echartsShapeTranslations = echartsShapeTranslations;
pointLabelPreviewFn = this._pointLabelPreviewFn.bind(this); pointLabelPreviewFn = this._pointLabelPreviewFn.bind(this);
@ -103,6 +103,8 @@ export class TimeSeriesChartLineSettingsComponent implements OnInit, ControlValu
pointLabelPosition: [null, []], pointLabelPosition: [null, []],
pointLabelFont: [null, []], pointLabelFont: [null, []],
pointLabelColor: [null, []], pointLabelColor: [null, []],
enablePointLabelBackground: [null, []],
pointLabelBackground: [null, []],
pointShape: [null, []], pointShape: [null, []],
pointSize: [null, [Validators.min(0)]], pointSize: [null, [Validators.min(0)]],
fillAreaSettings: [null, []] fillAreaSettings: [null, []]
@ -112,7 +114,8 @@ export class TimeSeriesChartLineSettingsComponent implements OnInit, ControlValu
}); });
merge(this.lineSettingsFormGroup.get('showLine').valueChanges, merge(this.lineSettingsFormGroup.get('showLine').valueChanges,
this.lineSettingsFormGroup.get('step').valueChanges, this.lineSettingsFormGroup.get('step').valueChanges,
this.lineSettingsFormGroup.get('showPointLabel').valueChanges) this.lineSettingsFormGroup.get('showPointLabel').valueChanges,
this.lineSettingsFormGroup.get('enablePointLabelBackground').valueChanges)
.subscribe(() => { .subscribe(() => {
this.updateValidators(); this.updateValidators();
}); });
@ -147,6 +150,7 @@ export class TimeSeriesChartLineSettingsComponent implements OnInit, ControlValu
const showLine: boolean = this.lineSettingsFormGroup.get('showLine').value; const showLine: boolean = this.lineSettingsFormGroup.get('showLine').value;
const step: boolean = this.lineSettingsFormGroup.get('step').value; const step: boolean = this.lineSettingsFormGroup.get('step').value;
const showPointLabel: boolean = this.lineSettingsFormGroup.get('showPointLabel').value; const showPointLabel: boolean = this.lineSettingsFormGroup.get('showPointLabel').value;
const enablePointLabelBackground: boolean = this.lineSettingsFormGroup.get('enablePointLabelBackground').value;
if (showLine) { if (showLine) {
this.lineSettingsFormGroup.get('step').enable({emitEvent: false}); this.lineSettingsFormGroup.get('step').enable({emitEvent: false});
if (step) { if (step) {
@ -169,10 +173,18 @@ export class TimeSeriesChartLineSettingsComponent implements OnInit, ControlValu
this.lineSettingsFormGroup.get('pointLabelPosition').enable({emitEvent: false}); this.lineSettingsFormGroup.get('pointLabelPosition').enable({emitEvent: false});
this.lineSettingsFormGroup.get('pointLabelFont').enable({emitEvent: false}); this.lineSettingsFormGroup.get('pointLabelFont').enable({emitEvent: false});
this.lineSettingsFormGroup.get('pointLabelColor').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 { } else {
this.lineSettingsFormGroup.get('pointLabelPosition').disable({emitEvent: false}); this.lineSettingsFormGroup.get('pointLabelPosition').disable({emitEvent: false});
this.lineSettingsFormGroup.get('pointLabelFont').disable({emitEvent: false}); this.lineSettingsFormGroup.get('pointLabelFont').disable({emitEvent: false});
this.lineSettingsFormGroup.get('pointLabelColor').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-select>
</mat-form-field> </mat-form-field>
</div> </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> <div translate>widget-config.units-short</div>
<tb-unit-input <tb-unit-input
formControlName="units"> formControlName="units">
</tb-unit-input> </tb-unit-input>
</div> </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> <div translate>widget-config.decimals-short</div>
<mat-form-field appearance="outline" class="number" subscriptSizing="dynamic"> <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 }}"> <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() @coerceBoolean()
advanced = false; advanced = false;
@Input()
@coerceBoolean()
hideUnits = false;
@Input()
@coerceBoolean()
hideDecimals = false;
private modelValue: TimeSeriesChartXAxisSettings | TimeSeriesChartYAxisSettings; private modelValue: TimeSeriesChartXAxisSettings | TimeSeriesChartYAxisSettings;
private propagateChange = null; 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 class="tb-form-row space-between column-xs">
<div translate>widgets.time-series-chart.series.opacity</div> <div translate>widgets.time-series-chart.series.opacity</div>
<mat-form-field class="medium-width number" appearance="outline" subscriptSizing="dynamic"> <mat-form-field class="medium-width number" appearance="outline" subscriptSizing="dynamic">
<input matInput type="number" formControlName="opacity" min="0" max="100" <input matInput type="number" formControlName="opacity" min="0" max="1"
step="1" placeholder="{{ 'widget-config.set' | translate }}"/> step="0.1" placeholder="{{ 'widget-config.set' | translate }}"/>
</mat-form-field> </mat-form-field>
</div> </div>
</ng-container> </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({ @Component({
selector: 'tb-time-series-chart-fill-settings', selector: 'tb-time-series-chart-fill-settings',
templateUrl: './time-series-chart-fill-settings.component.html', templateUrl: './time-series-chart-fill-settings.component.html',
styleUrls: ['./../widget-settings.scss'], styleUrls: ['./../../widget-settings.scss'],
providers: [ providers: [
{ {
provide: NG_VALUE_ACCESSOR, provide: NG_VALUE_ACCESSOR,
@ -73,7 +73,7 @@ export class TimeSeriesChartFillSettingsComponent implements OnInit, ControlValu
ngOnInit(): void { ngOnInit(): void {
this.fillSettingsFormGroup = this.fb.group({ this.fillSettingsFormGroup = this.fb.group({
type: [null, []], type: [null, []],
opacity: [null, [Validators.min(0), Validators.max(100)]], opacity: [null, [Validators.min(0), Validators.max(1)]],
gradient: this.fb.group({ gradient: this.fb.group({
start: [null, [Validators.min(0), Validators.max(100)]], start: [null, [Validators.min(0), Validators.max(100)]],
end: [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"> [formControl]="entityKeyFormControl">
</tb-data-key-input> </tb-data-key-input>
</div> </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-form-field class="tb-inline-field fixed-height" appearance="outline" subscriptSizing="dynamic">
<mat-select formControlName="yAxisId"> <mat-select formControlName="yAxisId">
<mat-option *ngFor="let yAxis of yAxisIds" [value]="yAxis"> <mat-option *ngFor="let yAxis of yAxisIds" [value]="yAxis">
@ -98,14 +98,13 @@
</mat-form-field> </mat-form-field>
</div> </div>
<div class="tb-form-table-row-cell-buttons"> <div class="tb-form-table-row-cell-buttons">
<button type="button" <tb-time-series-chart-threshold-settings
mat-icon-button [widgetConfig]="widgetConfig"
#matButton [yAxisIds]="yAxisIds"
(click)="editThreshold($event, matButton)" [hideYAxis]="hideYAxis"
matTooltip="{{ 'widgets.time-series-chart.threshold.threshold-settings' | translate }}" icon="settings"
matTooltipPosition="above"> [formControl]="thresholdSettingsFormControl">
<mat-icon>settings</mat-icon> </tb-time-series-chart-threshold-settings>
</button>
<button type="button" <button type="button"
mat-icon-button mat-icon-button
(click)="thresholdRemoved.emit()" (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, Component,
EventEmitter, EventEmitter,
forwardRef, forwardRef,
Input, OnChanges, Input,
OnChanges,
OnInit, OnInit,
Output, Output,
Renderer2, SimpleChanges, SimpleChanges,
ViewContainerRef,
ViewEncapsulation ViewEncapsulation
} from '@angular/core'; } from '@angular/core';
import { import {
@ -36,7 +36,8 @@ import {
} from '@angular/forms'; } from '@angular/forms';
import { import {
TimeSeriesChartThreshold, TimeSeriesChartThreshold,
TimeSeriesChartThresholdType, TimeSeriesChartYAxisId, TimeSeriesChartThresholdType,
TimeSeriesChartYAxisId,
timeSeriesThresholdTypes, timeSeriesThresholdTypes,
timeSeriesThresholdTypeTranslations timeSeriesThresholdTypeTranslations
} from '@home/components/widget/lib/chart/time-series-chart.models'; } 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 { DataKey, Datasource, DatasourceType, WidgetConfig } from '@shared/models/widget.models';
import { DataKeysCallbacks } from '@home/components/widget/config/data-keys.component.models'; import { DataKeysCallbacks } from '@home/components/widget/config/data-keys.component.models';
import { DataKeyType } from '@shared/models/telemetry/telemetry.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 { deepClone } from '@core/utils';
import { import { coerceBoolean } from '@shared/decorators/coercion';
TimeSeriesChartThresholdSettingsPanelComponent
} from '@home/components/widget/lib/settings/common/chart/time-series-chart-threshold-settings-panel.component';
@Component({ @Component({
selector: 'tb-time-series-chart-threshold-row', selector: 'tb-time-series-chart-threshold-row',
@ -101,6 +98,10 @@ export class TimeSeriesChartThresholdRowComponent implements ControlValueAccesso
@Input() @Input()
yAxisIds: TimeSeriesChartYAxisId[]; yAxisIds: TimeSeriesChartYAxisId[];
@Input()
@coerceBoolean()
hideYAxis = false;
@Output() @Output()
thresholdRemoved = new EventEmitter(); thresholdRemoved = new EventEmitter();
@ -112,12 +113,11 @@ export class TimeSeriesChartThresholdRowComponent implements ControlValueAccesso
entityKeyFormControl: UntypedFormControl; entityKeyFormControl: UntypedFormControl;
thresholdSettingsFormControl: UntypedFormControl;
private propagateChange = (_val: any) => {}; private propagateChange = (_val: any) => {};
constructor(private fb: UntypedFormBuilder, constructor(private fb: UntypedFormBuilder,
private popoverService: TbPopoverService,
private renderer: Renderer2,
private viewContainerRef: ViewContainerRef,
private thresholdsPanel: TimeSeriesChartThresholdsPanelComponent, private thresholdsPanel: TimeSeriesChartThresholdsPanelComponent,
private cd: ChangeDetectorRef) { private cd: ChangeDetectorRef) {
} }
@ -134,6 +134,7 @@ export class TimeSeriesChartThresholdRowComponent implements ControlValueAccesso
}); });
this.latestKeyFormControl = this.fb.control(null, [Validators.required]); this.latestKeyFormControl = this.fb.control(null, [Validators.required]);
this.entityKeyFormControl = 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.thresholdFormGroup.valueChanges.subscribe(
() => this.updateModel() () => this.updateModel()
); );
@ -143,6 +144,18 @@ export class TimeSeriesChartThresholdRowComponent implements ControlValueAccesso
this.entityKeyFormControl.valueChanges.subscribe( this.entityKeyFormControl.valueChanges.subscribe(
() => this.updateModel() () => 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.thresholdFormGroup.get('type').valueChanges.subscribe(() => {
this.updateValidators(); this.updateValidators();
}); });
@ -175,8 +188,10 @@ export class TimeSeriesChartThresholdRowComponent implements ControlValueAccesso
this.thresholdFormGroup.disable({emitEvent: false}); this.thresholdFormGroup.disable({emitEvent: false});
this.latestKeyFormControl.disable({emitEvent: false}); this.latestKeyFormControl.disable({emitEvent: false});
this.entityKeyFormControl.disable({emitEvent: false}); this.entityKeyFormControl.disable({emitEvent: false});
this.thresholdSettingsFormControl.disable({emitEvent: false});
} else { } else {
this.thresholdFormGroup.enable({emitEvent: false}); this.thresholdFormGroup.enable({emitEvent: false});
this.thresholdSettingsFormControl.enable({emitEvent: false});
this.updateValidators(); this.updateValidators();
} }
} }
@ -205,45 +220,12 @@ export class TimeSeriesChartThresholdRowComponent implements ControlValueAccesso
name: value.entityKey name: value.entityKey
}, {emitEvent: false}); }, {emitEvent: false});
} }
this.thresholdSettingsFormControl.patchValue(deepClone(this.modelValue),
{emitEvent: false});
this.updateValidators(); this.updateValidators();
this.cd.markForCheck(); 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() { private updateValidators() {
const type: TimeSeriesChartThresholdType = this.thresholdFormGroup.get('type').value; const type: TimeSeriesChartThresholdType = this.thresholdFormGroup.get('type').value;
if (type === TimeSeriesChartThresholdType.constant) { if (type === TimeSeriesChartThresholdType.constant) {
@ -282,6 +264,8 @@ export class TimeSeriesChartThresholdRowComponent implements ControlValueAccesso
this.modelValue.entityKey = entityKey?.name; this.modelValue.entityKey = entityKey?.name;
this.modelValue.entityKeyType = (entityKey?.type as any); this.modelValue.entityKeyType = (entityKey?.type as any);
} }
this.thresholdSettingsFormControl.patchValue(deepClone(this.modelValue),
{emitEvent: false});
this.propagateChange(this.modelValue); 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-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-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> <div translate>widgets.time-series-chart.axis.y-axis</div>
<mat-form-field class="medium-width" appearance="outline" subscriptSizing="dynamic"> <mat-form-field class="medium-width" appearance="outline" subscriptSizing="dynamic">
<mat-select formControlName="yAxisId"> <mat-select formControlName="yAxisId">
@ -64,6 +64,15 @@
</tb-color-input> </tb-color-input>
</div> </div>
</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">
<div class="tb-form-panel-title" translate>widgets.time-series-chart.threshold.line-appearance</div> <div class="tb-form-panel-title" translate>widgets.time-series-chart.threshold.line-appearance</div>
<div class="tb-form-row space-between"> <div class="tb-form-row space-between">
@ -94,8 +103,8 @@
<div fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px"> <div fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px">
<mat-form-field class="flex" appearance="outline" subscriptSizing="dynamic"> <mat-form-field class="flex" appearance="outline" subscriptSizing="dynamic">
<mat-select formControlName="startSymbol"> <mat-select formControlName="startSymbol">
<mat-option *ngFor="let shape of timeSeriesChartShapes" [value]="shape"> <mat-option *ngFor="let shape of echartsShapes" [value]="shape">
{{ timeSeriesChartShapeTranslations.get(shape) | translate }} {{ echartsShapeTranslations.get(shape) | translate }}
</mat-option> </mat-option>
</mat-select> </mat-select>
</mat-form-field> </mat-form-field>
@ -111,8 +120,8 @@
<div fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px"> <div fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px">
<mat-form-field class="flex" appearance="outline" subscriptSizing="dynamic"> <mat-form-field class="flex" appearance="outline" subscriptSizing="dynamic">
<mat-select formControlName="endSymbol"> <mat-select formControlName="endSymbol">
<mat-option *ngFor="let shape of timeSeriesChartShapes" [value]="shape"> <mat-option *ngFor="let shape of echartsShapes" [value]="shape">
{{ timeSeriesChartShapeTranslations.get(shape) | translate }} {{ echartsShapeTranslations.get(shape) | translate }}
</mat-option> </mat-option>
</mat-select> </mat-select>
</mat-form-field> </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 { TbPopoverComponent } from '@shared/components/popover.component';
import { UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms'; import { UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms';
import { import {
TimeSeriesChartShape, TimeSeriesChartThreshold,
timeSeriesChartShapes, TimeSeriesChartYAxisId,
timeSeriesChartShapeTranslations,
TimeSeriesChartThreshold, TimeSeriesChartYAxisId,
timeSeriesLineTypes, timeSeriesLineTypes,
timeSeriesLineTypeTranslations, timeSeriesLineTypeTranslations,
timeSeriesThresholdLabelPositions, timeSeriesThresholdLabelPositions,
timeSeriesThresholdLabelPositionTranslations timeSeriesThresholdLabelPositionTranslations
} from '@home/components/widget/lib/chart/time-series-chart.models'; } 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 { merge } from 'rxjs';
import { WidgetConfig } from '@shared/models/widget.models'; import { WidgetConfig } from '@shared/models/widget.models';
import { formatValue, isDefinedAndNotNull } from '@core/utils'; import { formatValue, isDefinedAndNotNull } from '@core/utils';
import { coerceBoolean } from '@shared/decorators/coercion';
@Component({ @Component({
selector: 'tb-time-series-chart-threshold-settings-panel', selector: 'tb-time-series-chart-threshold-settings-panel',
@ -44,9 +48,9 @@ export class TimeSeriesChartThresholdSettingsPanelComponent implements OnInit {
timeSeriesLineTypeTranslations = timeSeriesLineTypeTranslations; timeSeriesLineTypeTranslations = timeSeriesLineTypeTranslations;
timeSeriesChartShapes = timeSeriesChartShapes; echartsShapes = echartsShapes;
timeSeriesChartShapeTranslations = timeSeriesChartShapeTranslations; echartsShapeTranslations = echartsShapeTranslations;
timeSeriesThresholdLabelPositions = timeSeriesThresholdLabelPositions; timeSeriesThresholdLabelPositions = timeSeriesThresholdLabelPositions;
@ -66,6 +70,13 @@ export class TimeSeriesChartThresholdSettingsPanelComponent implements OnInit {
@Input() @Input()
popover: TbPopoverComponent<TimeSeriesChartThresholdSettingsPanelComponent>; popover: TbPopoverComponent<TimeSeriesChartThresholdSettingsPanelComponent>;
@Input()
@coerceBoolean()
hideYAxis = false;
@Input()
panelTitle = 'widgets.time-series-chart.threshold.threshold-settings';
@Output() @Output()
thresholdSettingsApplied = new EventEmitter<Partial<TimeSeriesChartThreshold>>(); thresholdSettingsApplied = new EventEmitter<Partial<TimeSeriesChartThreshold>>();
@ -90,10 +101,13 @@ export class TimeSeriesChartThresholdSettingsPanelComponent implements OnInit {
showLabel: [this.thresholdSettings.showLabel, []], showLabel: [this.thresholdSettings.showLabel, []],
labelPosition: [this.thresholdSettings.labelPosition, []], labelPosition: [this.thresholdSettings.labelPosition, []],
labelFont: [this.thresholdSettings.labelFont, []], 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, merge(this.thresholdSettingsFormGroup.get('showLabel').valueChanges,
this.thresholdSettingsFormGroup.get('enableLabelBackground').valueChanges,
this.thresholdSettingsFormGroup.get('startSymbol').valueChanges, this.thresholdSettingsFormGroup.get('startSymbol').valueChanges,
this.thresholdSettingsFormGroup.get('endSymbol').valueChanges).subscribe(() => { this.thresholdSettingsFormGroup.get('endSymbol').valueChanges).subscribe(() => {
this.updateValidators(); this.updateValidators();
@ -112,23 +126,32 @@ export class TimeSeriesChartThresholdSettingsPanelComponent implements OnInit {
private updateValidators() { private updateValidators() {
const showLabel: boolean = this.thresholdSettingsFormGroup.get('showLabel').value; const showLabel: boolean = this.thresholdSettingsFormGroup.get('showLabel').value;
const startSymbol: TimeSeriesChartShape = this.thresholdSettingsFormGroup.get('startSymbol').value; const enableLabelBackground: boolean = this.thresholdSettingsFormGroup.get('enableLabelBackground').value;
const endSymbol: TimeSeriesChartShape = this.thresholdSettingsFormGroup.get('endSymbol').value; const startSymbol: EChartsShape = this.thresholdSettingsFormGroup.get('startSymbol').value;
const endSymbol: EChartsShape = this.thresholdSettingsFormGroup.get('endSymbol').value;
if (showLabel) { if (showLabel) {
this.thresholdSettingsFormGroup.get('labelPosition').enable({emitEvent: false}); this.thresholdSettingsFormGroup.get('labelPosition').enable({emitEvent: false});
this.thresholdSettingsFormGroup.get('labelFont').enable({emitEvent: false}); this.thresholdSettingsFormGroup.get('labelFont').enable({emitEvent: false});
this.thresholdSettingsFormGroup.get('labelColor').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 { } else {
this.thresholdSettingsFormGroup.get('labelPosition').disable({emitEvent: false}); this.thresholdSettingsFormGroup.get('labelPosition').disable({emitEvent: false});
this.thresholdSettingsFormGroup.get('labelFont').disable({emitEvent: false}); this.thresholdSettingsFormGroup.get('labelFont').disable({emitEvent: false});
this.thresholdSettingsFormGroup.get('labelColor').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}); this.thresholdSettingsFormGroup.get('startSymbolSize').disable({emitEvent: false});
} else { } else {
this.thresholdSettingsFormGroup.get('startSymbolSize').enable({emitEvent: false}); this.thresholdSettingsFormGroup.get('startSymbolSize').enable({emitEvent: false});
} }
if (endSymbol === TimeSeriesChartShape.none) { if (endSymbol === EChartsShape.none) {
this.thresholdSettingsFormGroup.get('endSymbolSize').disable({emitEvent: false}); this.thresholdSettingsFormGroup.get('endSymbolSize').disable({emitEvent: false});
} else { } else {
this.thresholdSettingsFormGroup.get('endSymbolSize').enable({emitEvent: false}); 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">
<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-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-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-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-units-header" translate>widget-config.units-short</div>
<div class="tb-form-table-header-cell tb-decimals-header" translate>widget-config.decimals-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"> <div *ngFor="let thresholdControl of thresholdsFormArray().controls; trackBy: trackByThreshold; let $index = index; let $last = last">
<tb-time-series-chart-threshold-row fxFlex <tb-time-series-chart-threshold-row fxFlex
[formControl]="thresholdControl" [formControl]="thresholdControl"
[hideYAxis]="hideYAxis"
[yAxisIds]="yAxisIds" [yAxisIds]="yAxisIds"
(thresholdRemoved)="removeThreshold($index)"> (thresholdRemoved)="removeThreshold($index)">
</tb-time-series-chart-threshold-row> </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 { DataKeysCallbacks } from '@home/components/widget/config/data-keys.component.models';
import { DataKey, Datasource, WidgetConfig } from '@shared/models/widget.models'; import { DataKey, Datasource, WidgetConfig } from '@shared/models/widget.models';
import { DataKeyType } from '@shared/models/telemetry/telemetry.models'; import { DataKeyType } from '@shared/models/telemetry/telemetry.models';
import { coerceBoolean } from '@shared/decorators/coercion';
@Component({ @Component({
selector: 'tb-time-series-chart-thresholds-panel', selector: 'tb-time-series-chart-thresholds-panel',
@ -77,6 +78,10 @@ export class TimeSeriesChartThresholdsPanelComponent implements ControlValueAcce
@Input() @Input()
yAxisIds: TimeSeriesChartYAxisId[]; yAxisIds: TimeSeriesChartYAxisId[];
@Input()
@coerceBoolean()
hideYAxis = false;
thresholdsFormGroup: UntypedFormGroup; thresholdsFormGroup: UntypedFormGroup;
private propagateChange = (_val: any) => {}; 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. 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 fxLayout="row" fxLayoutAlign="space-between center">
<div fxFlex translate>widgets.time-series-chart.no-aggregation-bar-width-strategy</div> <div fxFlex translate>widgets.time-series-chart.no-aggregation-bar-width-strategy</div>
<tb-toggle-select formControlName="strategy" selectMediaBreakpoint="xs"> <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 timeSeriesChartNoAggregationBarWidthStrategyTranslations
} from '@home/components/widget/lib/chart/time-series-chart.models'; } from '@home/components/widget/lib/chart/time-series-chart.models';
import { merge } from 'rxjs'; import { merge } from 'rxjs';
import { coerceBoolean } from '@shared/decorators/coercion';
@Component({ @Component({
selector: 'tb-time-series-no-aggregation-bar-width-settings', selector: 'tb-time-series-no-aggregation-bar-width-settings',
@ -53,6 +54,10 @@ export class TimeSeriesNoAggregationBarWidthSettingsComponent implements OnInit,
@Input() @Input()
disabled: boolean; disabled: boolean;
@Input()
@coerceBoolean()
stroked = false;
private modelValue: TimeSeriesChartNoAggregationBarWidthSettings; private modelValue: TimeSeriesChartNoAggregationBarWidthSettings;
private propagateChange = null; 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 { import {
AutoDateFormatSettingsComponent AutoDateFormatSettingsComponent
} from '@home/components/widget/lib/settings/common/auto-date-format-settings.component'; } 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({ @NgModule({
declarations: [ declarations: [
@ -174,6 +180,8 @@ import {
TimeSeriesChartYAxisRowComponent, TimeSeriesChartYAxisRowComponent,
TimeSeriesChartYAxisSettingsPanelComponent, TimeSeriesChartYAxisSettingsPanelComponent,
TimeSeriesChartAnimationSettingsComponent, TimeSeriesChartAnimationSettingsComponent,
TimeSeriesChartFillSettingsComponent,
TimeSeriesChartThresholdSettingsComponent,
DataKeyInputComponent, DataKeyInputComponent,
EntityAliasInputComponent EntityAliasInputComponent
], ],
@ -227,6 +235,8 @@ import {
TimeSeriesChartYAxisRowComponent, TimeSeriesChartYAxisRowComponent,
TimeSeriesChartYAxisSettingsPanelComponent, TimeSeriesChartYAxisSettingsPanelComponent,
TimeSeriesChartAnimationSettingsComponent, TimeSeriesChartAnimationSettingsComponent,
TimeSeriesChartFillSettingsComponent,
TimeSeriesChartThresholdSettingsComponent,
DataKeyInputComponent, DataKeyInputComponent,
EntityAliasInputComponent EntityAliasInputComponent
], ],

5
ui-ngx/src/app/modules/home/components/widget/lib/settings/widget-settings.module.ts

@ -336,9 +336,6 @@ import {
import { import {
TimeSeriesChartLineSettingsComponent TimeSeriesChartLineSettingsComponent
} from '@home/components/widget/lib/settings/chart/time-series-chart-line-settings.component'; } 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 { import {
TimeSeriesChartBarSettingsComponent TimeSeriesChartBarSettingsComponent
} from '@home/components/widget/lib/settings/chart/time-series-chart-bar-settings.component'; } from '@home/components/widget/lib/settings/chart/time-series-chart-bar-settings.component';
@ -467,7 +464,6 @@ import {
TimeSeriesChartKeySettingsComponent, TimeSeriesChartKeySettingsComponent,
TimeSeriesChartLineSettingsComponent, TimeSeriesChartLineSettingsComponent,
TimeSeriesChartBarSettingsComponent, TimeSeriesChartBarSettingsComponent,
TimeSeriesChartFillSettingsComponent,
TimeSeriesChartWidgetSettingsComponent TimeSeriesChartWidgetSettingsComponent
], ],
imports: [ imports: [
@ -596,7 +592,6 @@ import {
TimeSeriesChartKeySettingsComponent, TimeSeriesChartKeySettingsComponent,
TimeSeriesChartLineSettingsComponent, TimeSeriesChartLineSettingsComponent,
TimeSeriesChartBarSettingsComponent, TimeSeriesChartBarSettingsComponent,
TimeSeriesChartFillSettingsComponent,
TimeSeriesChartWidgetSettingsComponent TimeSeriesChartWidgetSettingsComponent
] ]
}) })

15
ui-ngx/src/assets/locale/locale.constant-en_US.json

@ -5423,7 +5423,7 @@
"bar-appearance": "Bar appearance", "bar-appearance": "Bar appearance",
"label-on-bar": "Label on bar", "label-on-bar": "Label on bar",
"value-on-bar": "Value on bar", "value-on-bar": "Value on bar",
"bar-chart-card-style": "Bar chart card style" "bar-chart-style": "Bar chart style"
}, },
"battery-level": { "battery-level": {
"layout": "Layout", "layout": "Layout",
@ -6173,10 +6173,14 @@
"range-chart": { "range-chart": {
"chart": "Chart", "chart": "Chart",
"data-zoom": "Data zoom", "data-zoom": "Data zoom",
"range-chart-appearance": "Range chart appearance",
"range-colors": "Range colors", "range-colors": "Range colors",
"out-of-range-color": "Out of range color", "out-of-range-color": "Out of range color",
"show-range-thresholds": "Show range thresholds",
"range-thresholds-settings": "Range thresholds settings",
"fill-area": "Fill area", "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": { "rpc": {
"value-settings": "Value settings", "value-settings": "Value settings",
@ -6770,7 +6774,8 @@
"label-position-inside-middle-bottom": "Inside middle bottom", "label-position-inside-middle-bottom": "Inside middle bottom",
"label-position-inside-end": "Inside end", "label-position-inside-end": "Inside end",
"label-position-inside-end-top": "Inside end top", "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": { "axis": {
"axes": "Axes", "axes": "Axes",
@ -6851,6 +6856,7 @@
"show-points": "Show points", "show-points": "Show points",
"point-label": "Point label", "point-label": "Point label",
"point-label-hint": "Display label with value over the series point.", "point-label-hint": "Display label with value over the series point.",
"point-label-background": "Point label background",
"point-shape": "Point shape", "point-shape": "Point shape",
"point-size": "Point size" "point-size": "Point size"
}, },
@ -6859,7 +6865,8 @@
"border-width": "Border width", "border-width": "Border width",
"border-radius": "Border radius", "border-radius": "Border radius",
"label": "Label", "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", "bar-appearance":"Wygląd słupka",
"label-on-bar":"Etykieta na słupku", "label-on-bar":"Etykieta na słupku",
"value-on-bar":"Wartość 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":{ "battery-level":{
"layout":"Układ", "layout":"Układ",
@ -5924,7 +5924,7 @@
"range-colors":"Kolory zakresu", "range-colors":"Kolory zakresu",
"out-of-range-color":"Kolor poza zakresem", "out-of-range-color":"Kolor poza zakresem",
"fill-area":"Wypełnij obszar", "fill-area":"Wypełnij obszar",
"range-chart-card-style":"Styl karty wykresu zakresu" "range-chart-style":"Styl wykresu zakresu"
}, },
"rpc":{ "rpc":{
"value-settings":"Ustawienia wartości", "value-settings":"Ustawienia wartości",

2
ui-ngx/src/assets/locale/locale.constant-zh_CN.json

@ -5491,7 +5491,7 @@
"range-colors": "范围颜色", "range-colors": "范围颜色",
"out-of-range-color": "超出范围颜色", "out-of-range-color": "超出范围颜色",
"fill-area": "填充区域", "fill-area": "填充区域",
"range-chart-card-style": "范围图表卡片样式" "range-chart-style": "范围图样式"
}, },
"rpc": { "rpc": {
"value-settings": "值设置", "value-settings": "值设置",

Loading…
Cancel
Save