Browse Source

UI: Multiple Y axes configuration support for time series chart.

pull/10356/head
Igor Kulikov 3 years ago
parent
commit
c688f516c7
  1. 15
      ui-ngx/src/app/modules/home/components/widget/config/basic/chart/time-series-chart-basic-config.component.html
  2. 39
      ui-ngx/src/app/modules/home/components/widget/config/basic/chart/time-series-chart-basic-config.component.ts
  3. 39
      ui-ngx/src/app/modules/home/components/widget/config/basic/common/data-key-row.component.html
  4. 17
      ui-ngx/src/app/modules/home/components/widget/config/basic/common/data-key-row.component.scss
  5. 46
      ui-ngx/src/app/modules/home/components/widget/config/basic/common/data-key-row.component.ts
  6. 5
      ui-ngx/src/app/modules/home/components/widget/config/basic/common/data-keys-panel.component.html
  7. 15
      ui-ngx/src/app/modules/home/components/widget/config/basic/common/data-keys-panel.component.scss
  8. 8
      ui-ngx/src/app/modules/home/components/widget/config/basic/common/data-keys-panel.component.ts
  9. 30
      ui-ngx/src/app/modules/home/components/widget/lib/cards/aggregated-value-card-widget.component.ts
  10. 11
      ui-ngx/src/app/modules/home/components/widget/lib/cards/value-chart-card-widget.component.ts
  11. 216
      ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart.models.ts
  12. 102
      ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart.ts
  13. 12
      ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/time-series-chart-key-settings.component.html
  14. 12
      ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/time-series-chart-key-settings.component.ts
  15. 15
      ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/time-series-chart-widget-settings.component.html
  16. 37
      ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/time-series-chart-widget-settings.component.ts
  17. 18
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-axis-settings.component.html
  18. 18
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-axis-settings.component.ts
  19. 28
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-threshold-row.component.html
  20. 14
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-threshold-row.component.scss
  21. 32
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-threshold-row.component.ts
  22. 10
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-threshold-settings-panel.component.html
  23. 6
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-threshold-settings-panel.component.ts
  24. 2
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-thresholds-panel.component.html
  25. 11
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-thresholds-panel.component.scss
  26. 5
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-thresholds-panel.component.ts
  27. 63
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-y-axes-panel.component.html
  28. 87
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-y-axes-panel.component.scss
  29. 180
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-y-axes-panel.component.ts
  30. 72
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-y-axis-row.component.html
  31. 95
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-y-axis-row.component.scss
  32. 201
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-y-axis-row.component.ts
  33. 43
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-y-axis-settings-panel.component.html
  34. 49
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-y-axis-settings-panel.component.scss
  35. 66
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-y-axis-settings-panel.component.ts
  36. 15
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/widget-settings-common.module.ts
  37. 19
      ui-ngx/src/app/shared/models/common.ts
  38. 10
      ui-ngx/src/assets/locale/locale.constant-en_US.json

15
ui-ngx/src/app/modules/home/components/widget/config/basic/chart/time-series-chart-basic-config.component.html

@ -25,6 +25,10 @@
forceSingleDatasource forceSingleDatasource
formControlName="datasources"> formControlName="datasources">
</tb-datasources> </tb-datasources>
<tb-time-series-chart-y-axes-panel
formControlName="yAxes"
(axisRemoved)="yAxisRemoved($event)">
</tb-time-series-chart-y-axes-panel>
<tb-data-keys-panel <tb-data-keys-panel
panelTitle="{{ 'widgets.chart.series' | translate }}" panelTitle="{{ 'widgets.chart.series' | translate }}"
addKeyTitle="{{ 'widgets.chart.add-series' | translate }}" addKeyTitle="{{ 'widgets.chart.add-series' | translate }}"
@ -32,6 +36,8 @@
removeKeyTitle="{{ 'widgets.chart.remove-series' | translate }}" removeKeyTitle="{{ 'widgets.chart.remove-series' | translate }}"
noKeysText="{{ 'widgets.chart.no-series' | translate }}" noKeysText="{{ 'widgets.chart.no-series' | translate }}"
requiredKeysText="{{ 'widgets.chart.no-series-error' | translate }}" requiredKeysText="{{ 'widgets.chart.no-series-error' | translate }}"
timeSeriesChart
[yAxisIds]="yAxisIds"
[showTimeSeriesType]="chartType === TimeSeriesChartType.default" [showTimeSeriesType]="chartType === TimeSeriesChartType.default"
hideSourceSelection hideSourceSelection
[datasourceType]="datasource?.type" [datasourceType]="datasource?.type"
@ -44,7 +50,8 @@
[aliasController]="aliasController" [aliasController]="aliasController"
[dataKeyCallbacks]="callbacks" [dataKeyCallbacks]="callbacks"
[datasource]="datasource" [datasource]="datasource"
[widgetConfig]="widgetConfig?.config"> [widgetConfig]="widgetConfig?.config"
[yAxisIds]="yAxisIds">
</tb-time-series-chart-thresholds-panel> </tb-time-series-chart-thresholds-panel>
<div class="tb-form-panel"> <div class="tb-form-panel">
<div class="tb-form-panel-title" translate>widget-config.appearance</div> <div class="tb-form-panel-title" translate>widget-config.appearance</div>
@ -104,11 +111,7 @@
</div> </div>
</div> </div>
<div class="tb-form-panel"> <div class="tb-form-panel">
<div class="tb-form-panel-title" translate>widgets.time-series-chart.axis.axes</div> <div class="tb-form-panel-title" translate>widgets.time-series-chart.axis.x-axis</div>
<tb-time-series-chart-axis-settings
formControlName="yAxis"
axisType="yAxis">
</tb-time-series-chart-axis-settings>
<tb-time-series-chart-axis-settings <tb-time-series-chart-axis-settings
formControlName="xAxis" formControlName="xAxis"
axisType="xAxis"> axisType="xAxis">

39
ui-ngx/src/app/modules/home/components/widget/config/basic/chart/time-series-chart-basic-config.component.ts

@ -45,7 +45,11 @@ import {
TimeSeriesChartWidgetSettings TimeSeriesChartWidgetSettings
} from '@home/components/widget/lib/chart/time-series-chart-widget.models'; } from '@home/components/widget/lib/chart/time-series-chart-widget.models';
import { EChartsTooltipTrigger } from '@home/components/widget/lib/chart/echarts-widget.models'; import { EChartsTooltipTrigger } from '@home/components/widget/lib/chart/echarts-widget.models';
import { TimeSeriesChartType } from '@home/components/widget/lib/chart/time-series-chart.models'; import {
TimeSeriesChartKeySettings, TimeSeriesChartThreshold,
TimeSeriesChartType, TimeSeriesChartYAxes,
TimeSeriesChartYAxisId
} from '@home/components/widget/lib/chart/time-series-chart.models';
@Component({ @Component({
selector: 'tb-time-series-chart-basic-config', selector: 'tb-time-series-chart-basic-config',
@ -63,6 +67,11 @@ export class TimeSeriesChartBasicConfigComponent extends BasicWidgetConfigCompon
} }
} }
public get yAxisIds(): TimeSeriesChartYAxisId[] {
const yAxes: TimeSeriesChartYAxes = this.timeSeriesChartWidgetConfigForm.get('yAxes').value;
return Object.keys(yAxes);
}
TimeSeriesChartType = TimeSeriesChartType; TimeSeriesChartType = TimeSeriesChartType;
EChartsTooltipTrigger = EChartsTooltipTrigger; EChartsTooltipTrigger = EChartsTooltipTrigger;
@ -86,6 +95,15 @@ export class TimeSeriesChartBasicConfigComponent extends BasicWidgetConfigCompon
super(store, widgetConfigComponent); super(store, widgetConfigComponent);
} }
public yAxisRemoved(yAxisId: TimeSeriesChartYAxisId): void {
if (this.widgetConfig.config.datasources && this.widgetConfig.config.datasources.length > 1) {
for (let i = 1; i < this.widgetConfig.config.datasources.length; i++) {
const datasource = this.widgetConfig.config.datasources[i];
this.removeYaxisId(datasource.dataKeys, yAxisId);
}
}
}
protected configForm(): UntypedFormGroup { protected configForm(): UntypedFormGroup {
return this.timeSeriesChartWidgetConfigForm; return this.timeSeriesChartWidgetConfigForm;
} }
@ -109,6 +127,8 @@ export class TimeSeriesChartBasicConfigComponent extends BasicWidgetConfigCompon
this.timeSeriesChartWidgetConfigForm = this.fb.group({ this.timeSeriesChartWidgetConfigForm = this.fb.group({
timewindowConfig: [getTimewindowConfig(configData.config), []], timewindowConfig: [getTimewindowConfig(configData.config), []],
datasources: [configData.config.datasources, []], datasources: [configData.config.datasources, []],
yAxes: [settings.yAxes, []],
series: [this.getSeries(configData.config.datasources), []], series: [this.getSeries(configData.config.datasources), []],
thresholds: [settings.thresholds, []], thresholds: [settings.thresholds, []],
@ -126,7 +146,6 @@ export class TimeSeriesChartBasicConfigComponent extends BasicWidgetConfigCompon
dataZoom: [settings.dataZoom, []], dataZoom: [settings.dataZoom, []],
stack: [settings.stack, []], stack: [settings.stack, []],
yAxis: [settings.yAxis, []],
xAxis: [settings.xAxis, []], xAxis: [settings.xAxis, []],
noAggregationBarWidthSettings: [settings.noAggregationBarWidthSettings, []], noAggregationBarWidthSettings: [settings.noAggregationBarWidthSettings, []],
@ -180,7 +199,7 @@ export class TimeSeriesChartBasicConfigComponent extends BasicWidgetConfigCompon
this.widgetConfig.config.settings.dataZoom = config.dataZoom; this.widgetConfig.config.settings.dataZoom = config.dataZoom;
this.widgetConfig.config.settings.stack = config.stack; this.widgetConfig.config.settings.stack = config.stack;
this.widgetConfig.config.settings.yAxis = config.yAxis; this.widgetConfig.config.settings.yAxes = config.yAxes;
this.widgetConfig.config.settings.xAxis = config.xAxis; this.widgetConfig.config.settings.xAxis = config.xAxis;
this.widgetConfig.config.settings.noAggregationBarWidthSettings = config.noAggregationBarWidthSettings; this.widgetConfig.config.settings.noAggregationBarWidthSettings = config.noAggregationBarWidthSettings;
@ -303,6 +322,20 @@ export class TimeSeriesChartBasicConfigComponent extends BasicWidgetConfigCompon
} }
} }
private removeYaxisId(series: DataKey[], yAxisId: TimeSeriesChartYAxisId): boolean {
let changed = false;
if (series) {
series.forEach(key => {
const keySettings = ((key.settings || {}) as TimeSeriesChartKeySettings);
if (keySettings.yAxisId === yAxisId) {
keySettings.yAxisId = 'default';
changed = true;
}
});
}
return changed;
}
private getCardButtons(config: WidgetConfig): string[] { private getCardButtons(config: WidgetConfig): string[] {
const buttons: string[] = []; const buttons: string[] = [];
if (isUndefined(config.enableFullscreen) || config.enableFullscreen) { if (isUndefined(config.enableFullscreen) || config.enableFullscreen) {

39
ui-ngx/src/app/modules/home/components/widget/config/basic/common/data-key-row.component.html

@ -38,6 +38,30 @@
<mat-form-field *ngIf="!hideDataKeyLabel" class="tb-inline-field tb-label-field" appearance="outline" subscriptSizing="dynamic"> <mat-form-field *ngIf="!hideDataKeyLabel" class="tb-inline-field tb-label-field" appearance="outline" subscriptSizing="dynamic">
<input matInput formControlName="label" placeholder="{{ 'widget-config.set' | translate }}"> <input matInput formControlName="label" placeholder="{{ 'widget-config.set' | translate }}">
</mat-form-field> </mat-form-field>
<div *ngIf="timeSeriesChart && showTimeSeriesType" class="tb-time-series-type-field">
<mat-form-field *ngIf="displayUnitsOrDigits" class="tb-inline-field fixed-height" appearance="outline" subscriptSizing="dynamic">
<mat-select formControlName="timeSeriesType" panelClass="tb-time-series-type-field">
<mat-select-trigger>
<tb-icon *ngIf="keyRowFormGroup.get('timeSeriesType').value">
{{ timeSeriesChartSeriesTypeIcons.get(keyRowFormGroup.get('timeSeriesType').value) }}
</tb-icon>
</mat-select-trigger>
<mat-option *ngFor="let type of timeSeriesChartSeriesTypes" [value]="type" class="flex">
<tb-icon>{{ timeSeriesChartSeriesTypeIcons.get(type) }}</tb-icon>
<span>{{ timeSeriesChartSeriesTypeTranslations.get(type) | translate }}</span>
</mat-option>
</mat-select>
</mat-form-field>
</div>
<div *ngIf="timeSeriesChart" class="tb-y-axis-field">
<mat-form-field *ngIf="displayUnitsOrDigits" class="tb-inline-field fixed-height" appearance="outline" subscriptSizing="dynamic">
<mat-select formControlName="yAxis">
<mat-option *ngFor="let yAxis of yAxisIds" [value]="yAxis">
{{ yAxis }}
</mat-option>
</mat-select>
</mat-form-field>
</div>
<div *ngIf="!hideDataKeyColor" class="tb-color-field"> <div *ngIf="!hideDataKeyColor" class="tb-color-field">
<tb-color-input asBoxInput <tb-color-input asBoxInput
formControlName="color"> formControlName="color">
@ -54,21 +78,6 @@
<div *ngIf="singleRow" matSuffix fxHide.lt-md translate>widget-config.decimals-suffix</div> <div *ngIf="singleRow" matSuffix fxHide.lt-md translate>widget-config.decimals-suffix</div>
</mat-form-field> </mat-form-field>
</div> </div>
<div *ngIf="showTimeSeriesType" class="tb-time-series-type-field">
<mat-form-field *ngIf="displayUnitsOrDigits" class="tb-inline-field fixed-height" appearance="outline" subscriptSizing="dynamic">
<mat-select formControlName="timeSeriesType" panelClass="tb-time-series-type-field">
<mat-select-trigger>
<tb-icon *ngIf="keyRowFormGroup.get('timeSeriesType').value">
{{ timeSeriesChartSeriesTypeIcons.get(keyRowFormGroup.get('timeSeriesType').value) }}
</tb-icon>
</mat-select-trigger>
<mat-option *ngFor="let type of timeSeriesChartSeriesTypes" [value]="type" class="flex">
<tb-icon>{{ timeSeriesChartSeriesTypeIcons.get(type) }}</tb-icon>
<span>{{ timeSeriesChartSeriesTypeTranslations.get(type) | translate }}</span>
</mat-option>
</mat-select>
</mat-form-field>
</div>
<div *ngIf="!singleRow" class="tb-form-table-row-cell-buttons"> <div *ngIf="!singleRow" class="tb-form-table-row-cell-buttons">
<div fxHide.lt-lg class="tb-settings-button"> <div fxHide.lt-lg class="tb-settings-button">
<button *ngIf="modelValue" <button *ngIf="modelValue"

17
ui-ngx/src/app/modules/home/components/widget/config/basic/common/data-key-row.component.scss

@ -57,6 +57,14 @@
min-width: 60px; min-width: 60px;
} }
.tb-y-axis-field {
width: 100px;
min-width: 100px;
.mat-mdc-form-field {
width: 100%;
}
}
.tb-time-series-type-field { .tb-time-series-type-field {
width: 60px; width: 60px;
min-width: 60px; min-width: 60px;
@ -72,7 +80,16 @@
} }
} }
.tb-y-axis-field {
@media #{$mat-md} {
display: none;
}
}
.tb-time-series-type-field { .tb-time-series-type-field {
@media #{$mat-xs} {
display: none;
}
@media #{$mat-md} { @media #{$mat-md} {
display: none; display: none;
} }

46
ui-ngx/src/app/modules/home/components/widget/config/basic/common/data-key-row.component.ts

@ -19,9 +19,9 @@ import {
Component, Component,
EventEmitter, EventEmitter,
forwardRef, forwardRef,
Input, Input, OnChanges,
OnInit, OnInit,
Output, Output, SimpleChanges,
ViewEncapsulation ViewEncapsulation
} from '@angular/core'; } from '@angular/core';
import { import {
@ -60,7 +60,7 @@ import {
TimeSeriesChartSeriesType, TimeSeriesChartSeriesType,
timeSeriesChartSeriesTypeIcons, timeSeriesChartSeriesTypeIcons,
timeSeriesChartSeriesTypes, timeSeriesChartSeriesTypes,
timeSeriesChartSeriesTypeTranslations timeSeriesChartSeriesTypeTranslations, TimeSeriesChartYAxisId
} from '@home/components/widget/lib/chart/time-series-chart.models'; } from '@home/components/widget/lib/chart/time-series-chart.models';
export const dataKeyValid = (key: DataKey): boolean => !!key && !!key.type && !!key.name; export const dataKeyValid = (key: DataKey): boolean => !!key && !!key.type && !!key.name;
@ -88,7 +88,7 @@ export const dataKeyRowValidator = (control: AbstractControl): ValidationErrors
], ],
encapsulation: ViewEncapsulation.None encapsulation: ViewEncapsulation.None
}) })
export class DataKeyRowComponent implements ControlValueAccessor, OnInit { export class DataKeyRowComponent implements ControlValueAccessor, OnInit, OnChanges {
timeSeriesChartSeriesTypes = timeSeriesChartSeriesTypes; timeSeriesChartSeriesTypes = timeSeriesChartSeriesTypes;
timeSeriesChartSeriesTypeTranslations = timeSeriesChartSeriesTypeTranslations; timeSeriesChartSeriesTypeTranslations = timeSeriesChartSeriesTypeTranslations;
@ -138,10 +138,17 @@ export class DataKeyRowComponent implements ControlValueAccessor, OnInit {
@coerceBoolean() @coerceBoolean()
hideDecimals = false; hideDecimals = false;
@Input()
@coerceBoolean()
timeSeriesChart = false;
@Input() @Input()
@coerceBoolean() @coerceBoolean()
showTimeSeriesType = false; showTimeSeriesType = false;
@Input()
yAxisIds: TimeSeriesChartYAxisId[];
@Input() @Input()
@coerceBoolean() @coerceBoolean()
singleRow = true; singleRow = true;
@ -237,7 +244,8 @@ export class DataKeyRowComponent implements ControlValueAccessor, OnInit {
if (this.hasAdditionalLatestDataKeys) { if (this.hasAdditionalLatestDataKeys) {
this.keyRowFormGroup.addControl('latest', this.fb.control(false)); this.keyRowFormGroup.addControl('latest', this.fb.control(false));
} }
if (this.showTimeSeriesType) { if (this.timeSeriesChart) {
this.keyRowFormGroup.addControl('yAxis', this.fb.control(null));
this.keyRowFormGroup.addControl('timeSeriesType', this.fb.control(null)); this.keyRowFormGroup.addControl('timeSeriesType', this.fb.control(null));
} }
merge(this.keyFormControl.valueChanges, this.keyRowFormGroup.valueChanges).subscribe( merge(this.keyFormControl.valueChanges, this.keyRowFormGroup.valueChanges).subscribe(
@ -245,6 +253,20 @@ export class DataKeyRowComponent implements ControlValueAccessor, OnInit {
); );
} }
ngOnChanges(changes: SimpleChanges): void {
for (const propName of Object.keys(changes)) {
const change = changes[propName];
if (!change.firstChange && change.currentValue !== change.previousValue) {
if (this.timeSeriesChart && ['yAxisIds'].includes(propName)) {
if (this.modelValue?.settings?.yAxisId &&
!this.yAxisIds.includes(this.modelValue.settings.yAxisId)) {
this.keyRowFormGroup.patchValue({yAxis: 'default'}, {emitEvent: true});
}
}
}
}
}
registerOnChange(fn: any): void { registerOnChange(fn: any): void {
this.propagateChange = fn; this.propagateChange = fn;
} }
@ -278,10 +300,12 @@ export class DataKeyRowComponent implements ControlValueAccessor, OnInit {
latest: (value as any)?.latest latest: (value as any)?.latest
}, {emitEvent: false}); }, {emitEvent: false});
} }
if (this.showTimeSeriesType) { if (this.timeSeriesChart) {
const settings = value?.settings as TimeSeriesChartKeySettings; const settings = value?.settings as TimeSeriesChartKeySettings;
const yAxis = settings?.yAxisId || 'default';
const timeSeriesType = settings?.type || TimeSeriesChartSeriesType.line; const timeSeriesType = settings?.type || TimeSeriesChartSeriesType.line;
this.keyRowFormGroup.patchValue({ this.keyRowFormGroup.patchValue({
yAxis,
timeSeriesType timeSeriesType
}, {emitEvent: false}); }, {emitEvent: false});
} }
@ -319,7 +343,8 @@ export class DataKeyRowComponent implements ControlValueAccessor, OnInit {
this.keyRowFormGroup.get('color').patchValue(this.modelValue.color, {emitEvent: false}); this.keyRowFormGroup.get('color').patchValue(this.modelValue.color, {emitEvent: false});
this.keyRowFormGroup.get('units').patchValue(this.modelValue.units, {emitEvent: false}); this.keyRowFormGroup.get('units').patchValue(this.modelValue.units, {emitEvent: false});
this.keyRowFormGroup.get('decimals').patchValue(this.modelValue.decimals, {emitEvent: false}); this.keyRowFormGroup.get('decimals').patchValue(this.modelValue.decimals, {emitEvent: false});
if (this.showTimeSeriesType) { if (this.timeSeriesChart) {
this.keyRowFormGroup.get('yAxis').patchValue(this.modelValue.settings?.yAxisId, {emitEvent: false});
this.keyRowFormGroup.get('timeSeriesType').patchValue(this.modelValue.settings?.type, {emitEvent: false}); this.keyRowFormGroup.get('timeSeriesType').patchValue(this.modelValue.settings?.type, {emitEvent: false});
} }
this.keyFormControl.patchValue(deepClone(this.modelValue), {emitEvent: false}); this.keyFormControl.patchValue(deepClone(this.modelValue), {emitEvent: false});
@ -335,7 +360,8 @@ export class DataKeyRowComponent implements ControlValueAccessor, OnInit {
if (!this.keyRowFormGroup.get('label').value) { if (!this.keyRowFormGroup.get('label').value) {
this.keyRowFormGroup.get('label').patchValue(key.label, {emitEvent: false}); this.keyRowFormGroup.get('label').patchValue(key.label, {emitEvent: false});
} }
if (this.showTimeSeriesType) { if (this.timeSeriesChart) {
this.keyRowFormGroup.get('yAxis').patchValue(key.settings?.yAxisId, {emitEvent: false});
this.keyRowFormGroup.get('timeSeriesType').patchValue(key.settings?.type, {emitEvent: false}); this.keyRowFormGroup.get('timeSeriesType').patchValue(key.settings?.type, {emitEvent: false});
} }
return key; return key;
@ -346,8 +372,10 @@ export class DataKeyRowComponent implements ControlValueAccessor, OnInit {
if (this.modelValue !== null) { if (this.modelValue !== null) {
const value: DataKey = this.keyRowFormGroup.value; const value: DataKey = this.keyRowFormGroup.value;
this.modelValue = {...this.modelValue, ...value}; this.modelValue = {...this.modelValue, ...value};
if (this.showTimeSeriesType) { if (this.timeSeriesChart) {
this.modelValue.settings.yAxisId = (this.modelValue as any).yAxis;
this.modelValue.settings.type = (this.modelValue as any).timeSeriesType; this.modelValue.settings.type = (this.modelValue as any).timeSeriesType;
delete (this.modelValue as any).yAxis;
delete (this.modelValue as any).timeSeriesType; delete (this.modelValue as any).timeSeriesType;
} }
} }

5
ui-ngx/src/app/modules/home/components/widget/config/basic/common/data-keys-panel.component.html

@ -22,10 +22,11 @@
<div *ngIf="hasAdditionalLatestDataKeys" class="tb-form-table-header-cell tb-source-header" translate>datakey.source</div> <div *ngIf="hasAdditionalLatestDataKeys" class="tb-form-table-header-cell tb-source-header" translate>datakey.source</div>
<div class="tb-form-table-header-cell tb-key-header" translate>datakey.key</div> <div class="tb-form-table-header-cell tb-key-header" translate>datakey.key</div>
<div class="tb-form-table-header-cell tb-label-header" translate>datakey.label</div> <div class="tb-form-table-header-cell tb-label-header" translate>datakey.label</div>
<div *ngIf="timeSeriesChart && showTimeSeriesType" class="tb-form-table-header-cell tb-time-series-type-header" translate>widgets.time-series-chart.series.type</div>
<div *ngIf="timeSeriesChart" class="tb-form-table-header-cell tb-y-axis-header" translate>widgets.time-series-chart.axis.y-axis</div>
<div *ngIf="!hideDataKeyColor" class="tb-form-table-header-cell tb-color-header" translate>datakey.color</div> <div *ngIf="!hideDataKeyColor" class="tb-form-table-header-cell tb-color-header" translate>datakey.color</div>
<div *ngIf="!hideUnits" class="tb-form-table-header-cell tb-units-header" translate>widget-config.units-short</div> <div *ngIf="!hideUnits" class="tb-form-table-header-cell tb-units-header" translate>widget-config.units-short</div>
<div *ngIf="!hideDecimals" class="tb-form-table-header-cell tb-decimals-header" translate>widget-config.decimals-short</div> <div *ngIf="!hideDecimals" class="tb-form-table-header-cell tb-decimals-header" translate>widget-config.decimals-short</div>
<div *ngIf="showTimeSeriesType" class="tb-form-table-header-cell tb-time-series-type-header" translate>widgets.time-series-chart.series.type</div>
<div class="tb-form-table-header-cell tb-actions-header"></div> <div class="tb-form-table-header-cell tb-actions-header"></div>
</div> </div>
<div *ngIf="keysFormArray().controls.length; else noKeys" class="tb-form-table-body tb-drop-list" <div *ngIf="keysFormArray().controls.length; else noKeys" class="tb-form-table-body tb-drop-list"
@ -44,7 +45,9 @@
[hasAdditionalLatestDataKeys]="hasAdditionalLatestDataKeys" [hasAdditionalLatestDataKeys]="hasAdditionalLatestDataKeys"
[hideDataKeyColor]="hideDataKeyColor" [hideDataKeyColor]="hideDataKeyColor"
[hideDecimals]="hideDecimals" [hideDecimals]="hideDecimals"
[timeSeriesChart]="timeSeriesChart"
[showTimeSeriesType]="showTimeSeriesType" [showTimeSeriesType]="showTimeSeriesType"
[yAxisIds]="yAxisIds"
[hideUnits]="hideUnits" [hideUnits]="hideUnits"
[hideDataKeyDecimals]="hideDataKeyDecimals" [hideDataKeyDecimals]="hideDataKeyDecimals"
[hideDataKeyUnits]="hideDataKeyUnits" [hideDataKeyUnits]="hideDataKeyUnits"

15
ui-ngx/src/app/modules/home/components/widget/config/basic/common/data-keys-panel.component.scss

@ -50,6 +50,11 @@
min-width: 60px; min-width: 60px;
} }
&.tb-y-axis-header {
width: 100px;
min-width: 100px;
}
&.tb-time-series-type-header { &.tb-time-series-type-header {
width: 60px; width: 60px;
min-width: 60px; min-width: 60px;
@ -70,7 +75,17 @@
display: block; display: block;
} }
} }
&.tb-y-axis-header {
@media #{$mat-md} {
display: none;
}
}
&.tb-time-series-type-header { &.tb-time-series-type-header {
@media #{$mat-xs} {
display: none;
}
@media #{$mat-md} { @media #{$mat-md} {
display: none; display: none;
} }

8
ui-ngx/src/app/modules/home/components/widget/config/basic/common/data-keys-panel.component.ts

@ -44,6 +44,7 @@ import { DataKeyType } from '@shared/models/telemetry/telemetry.models';
import { UtilsService } from '@core/services/utils.service'; import { UtilsService } from '@core/services/utils.service';
import { DataKeysCallbacks, DataKeySettingsFunction } from '@home/components/widget/config/data-keys.component.models'; import { DataKeysCallbacks, DataKeySettingsFunction } from '@home/components/widget/config/data-keys.component.models';
import { coerceBoolean } from '@shared/decorators/coercion'; import { coerceBoolean } from '@shared/decorators/coercion';
import { TimeSeriesChartYAxisId } from '@home/components/widget/lib/chart/time-series-chart.models';
@Component({ @Component({
selector: 'tb-data-keys-panel', selector: 'tb-data-keys-panel',
@ -119,10 +120,17 @@ export class DataKeysPanelComponent implements ControlValueAccessor, OnInit, OnC
@coerceBoolean() @coerceBoolean()
hideSourceSelection = false; hideSourceSelection = false;
@Input()
@coerceBoolean()
timeSeriesChart = false;
@Input() @Input()
@coerceBoolean() @coerceBoolean()
showTimeSeriesType = false; showTimeSeriesType = false;
@Input()
yAxisIds: TimeSeriesChartYAxisId[];
dataKeyType: DataKeyType; dataKeyType: DataKeyType;
keysListFormGroup: UntypedFormGroup; keysListFormGroup: UntypedFormGroup;

30
ui-ngx/src/app/modules/home/components/widget/lib/cards/aggregated-value-card-widget.component.ts

@ -54,10 +54,12 @@ import { ImagePipe } from '@shared/pipe/image.pipe';
import { DomSanitizer } from '@angular/platform-browser'; import { DomSanitizer } from '@angular/platform-browser';
import { TbTimeSeriesChart } from '@home/components/widget/lib/chart/time-series-chart'; import { TbTimeSeriesChart } from '@home/components/widget/lib/chart/time-series-chart';
import { import {
TimeSeriesChartAxisSettings,
TimeSeriesChartKeySettings, TimeSeriesChartKeySettings,
TimeSeriesChartSeriesType, TimeSeriesChartSeriesType,
TimeSeriesChartSettings TimeSeriesChartSettings, TimeSeriesChartYAxisSettings
} from '@home/components/widget/lib/chart/time-series-chart.models'; } from '@home/components/widget/lib/chart/time-series-chart.models';
import { DeepPartial } from '@shared/models/common';
const valuesLayoutHeight = 66; const valuesLayoutHeight = 66;
const valuesLayoutVerticalPadding = 16; const valuesLayoutVerticalPadding = 16;
@ -172,25 +174,27 @@ export class AggregatedValueCardWidgetComponent implements OnInit, AfterViewInit
ngAfterViewInit(): void { ngAfterViewInit(): void {
if (this.showChart && this.ctx.datasources?.length) { if (this.showChart && this.ctx.datasources?.length) {
const settings: TimeSeriesChartSettings = { const settings: DeepPartial<TimeSeriesChartSettings> = {
dataZoom: false, dataZoom: false,
xAxis: { xAxis: {
show: false show: false
}, },
yAxis: { yAxes: {
show: true, default: {
showLine: false, show: true,
showTicks: false, showLine: false,
showTickLabels: false, showTicks: false,
showSplitLines: true, showTickLabels: false,
min: 'dataMin', showSplitLines: true,
max: 'dataMax', min: 'dataMin',
intervalCalculator: max: 'dataMax',
'var scale = axis.scale; return !scale.isBlank() ? ((scale.getExtent()[1] - scale.getExtent()[0]) / 2) : undefined;' intervalCalculator:
'var scale = axis.scale; return !scale.isBlank() ? ((scale.getExtent()[1] - scale.getExtent()[0]) / 2) : undefined;'
}
}, },
tooltipDateInterval: false, tooltipDateInterval: false,
tooltipDateFormat: simpleDateFormat('dd MMM yyyy HH:mm:ss') tooltipDateFormat: simpleDateFormat('dd MMM yyyy HH:mm:ss')
} as TimeSeriesChartSettings; };
this.lineChart = new TbTimeSeriesChart(this.ctx, settings, this.chartElement.nativeElement, this.renderer, true); this.lineChart = new TbTimeSeriesChart(this.ctx, settings, this.chartElement.nativeElement, this.renderer, true);

11
ui-ngx/src/app/modules/home/components/widget/lib/cards/value-chart-card-widget.component.ts

@ -57,6 +57,7 @@ import {
TimeSeriesChartSeriesType, TimeSeriesChartSeriesType,
TimeSeriesChartSettings TimeSeriesChartSettings
} from '@home/components/widget/lib/chart/time-series-chart.models'; } from '@home/components/widget/lib/chart/time-series-chart.models';
import { DeepPartial } from '@shared/models/common';
const layoutHeight = 56; const layoutHeight = 56;
const valueRelativeWidth = 0.35; const valueRelativeWidth = 0.35;
@ -162,17 +163,19 @@ export class ValueChartCardWidgetComponent implements OnInit, AfterViewInit, OnD
} }
public ngAfterViewInit() { public ngAfterViewInit() {
const settings: TimeSeriesChartSettings = { const settings: DeepPartial<TimeSeriesChartSettings> = {
dataZoom: false, dataZoom: false,
xAxis: { xAxis: {
show: false show: false
}, },
yAxis: { yAxes: {
show: false default: {
show: false,
}
}, },
tooltipDateInterval: false, tooltipDateInterval: false,
tooltipDateFormat: simpleDateFormat('dd MMM yyyy HH:mm:ss') tooltipDateFormat: simpleDateFormat('dd MMM yyyy HH:mm:ss')
} as TimeSeriesChartSettings; };
this.lineChart = new TbTimeSeriesChart(this.ctx, settings, this.chartElement.nativeElement, this.renderer, false); this.lineChart = new TbTimeSeriesChart(this.ctx, settings, this.chartElement.nativeElement, this.renderer, false);

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

@ -25,7 +25,15 @@ import {
import { ComponentStyle, Font, simpleDateFormat, textStyle } from '@shared/models/widget-settings.models'; import { ComponentStyle, Font, simpleDateFormat, textStyle } from '@shared/models/widget-settings.models';
import { XAXisOption, YAXisOption } from 'echarts/types/dist/shared'; import { XAXisOption, YAXisOption } from 'echarts/types/dist/shared';
import { CustomSeriesOption, LineSeriesOption } from 'echarts/charts'; import { CustomSeriesOption, LineSeriesOption } from 'echarts/charts';
import { formatValue, isDefinedAndNotNull, isUndefined, isUndefinedOrNull, parseFunction } from '@core/utils'; import {
formatValue,
isDefinedAndNotNull,
isNumeric,
isUndefined,
isUndefinedOrNull,
mergeDeep,
parseFunction
} from '@core/utils';
import { LinearGradientObject } from 'zrender/lib/graphic/LinearGradient'; import { LinearGradientObject } from 'zrender/lib/graphic/LinearGradient';
import tinycolor from 'tinycolor2'; import tinycolor from 'tinycolor2';
import Axis2D from 'echarts/types/src/coord/cartesian/Axis2D'; import Axis2D from 'echarts/types/src/coord/cartesian/Axis2D';
@ -289,13 +297,82 @@ export interface TimeSeriesChartAxisSettings {
splitLinesColor: string; splitLinesColor: string;
} }
export type TimeSeriesChartYAxisId = 'default' | string;
export interface TimeSeriesChartYAxisSettings extends TimeSeriesChartAxisSettings { export interface TimeSeriesChartYAxisSettings extends TimeSeriesChartAxisSettings {
id?: TimeSeriesChartYAxisId;
order?: number;
units?: string;
decimals?: number;
min?: number | string; min?: number | string;
max?: number | string; max?: number | string;
intervalCalculator?: string; intervalCalculator?: string;
ticksFormatter?: string; ticksFormatter?: string;
} }
export const timeSeriesChartYAxisValid = (axis: TimeSeriesChartYAxisSettings): boolean =>
!(!axis.id || isUndefinedOrNull(axis.order));
export const timeSeriesChartYAxisValidator = (control: AbstractControl): ValidationErrors | null => {
const axis: TimeSeriesChartYAxisSettings = control.value;
if (!timeSeriesChartYAxisValid(axis)) {
return {
axis: true
};
}
return null;
};
export const getNextTimeSeriesYAxisId = (axes: TimeSeriesChartYAxisSettings[]): TimeSeriesChartYAxisId => {
let id = 0;
for (const axis of axes) {
const existingId = axis.id;
if (existingId.startsWith('axis')) {
const idSuffix = existingId.substring('axis'.length);
if (isNumeric(idSuffix)) {
id = Math.max(id, Number(idSuffix));
}
}
}
return 'axis' + (id+1);
};
export const defaultTimeSeriesChartYAxisSettings: TimeSeriesChartYAxisSettings = {
units: null,
decimals: 0,
show: true,
label: '',
labelFont: {
family: 'Roboto',
size: 12,
sizeUnit: 'px',
style: 'normal',
weight: '600',
lineHeight: '1'
},
labelColor: timeSeriesChartColorScheme['axis.label'].light,
position: AxisPosition.left,
showTickLabels: true,
tickLabelFont: {
family: 'Roboto',
size: 12,
sizeUnit: 'px',
style: 'normal',
weight: '400',
lineHeight: '1'
},
tickLabelColor: timeSeriesChartColorScheme['axis.tickLabel'].light,
ticksFormatter: null,
showTicks: true,
ticksColor: timeSeriesChartColorScheme['axis.ticks'].light,
showLine: true,
lineColor: timeSeriesChartColorScheme['axis.line'].light,
showSplitLines: true,
splitLinesColor: timeSeriesChartColorScheme['axis.splitLine'].light
};
export type TimeSeriesChartYAxes = {[id: TimeSeriesChartYAxisId]: TimeSeriesChartYAxisSettings};
export interface TimeSeriesChartThreshold { export interface TimeSeriesChartThreshold {
type: TimeSeriesChartThresholdType; type: TimeSeriesChartThresholdType;
value?: number; value?: number;
@ -304,6 +381,7 @@ export interface TimeSeriesChartThreshold {
entityAlias?: string; entityAlias?: string;
entityKey?: string; entityKey?: string;
entityKeyType?: DataKeyType.attribute | DataKeyType.timeseries; entityKeyType?: DataKeyType.attribute | DataKeyType.timeseries;
yAxisId: TimeSeriesChartYAxisId;
units?: string; units?: string;
decimals?: number; decimals?: number;
lineColor: string; lineColor: string;
@ -320,7 +398,7 @@ export interface TimeSeriesChartThreshold {
} }
export const timeSeriesChartThresholdValid = (threshold: TimeSeriesChartThreshold): boolean => { export const timeSeriesChartThresholdValid = (threshold: TimeSeriesChartThreshold): boolean => {
if (!threshold.type) { if (!threshold.type || !threshold.yAxisId) {
return false; return false;
} }
switch (threshold.type) { switch (threshold.type) {
@ -355,7 +433,8 @@ export const timeSeriesChartThresholdValidator = (control: AbstractControl): Val
export const timeSeriesChartThresholdDefaultSettings: TimeSeriesChartThreshold = { export const timeSeriesChartThresholdDefaultSettings: TimeSeriesChartThreshold = {
type: TimeSeriesChartThresholdType.constant, type: TimeSeriesChartThresholdType.constant,
units: '', yAxisId: 'default',
units: null,
decimals: 0, decimals: 0,
lineColor: timeSeriesChartColorScheme['threshold.line'].light, lineColor: timeSeriesChartColorScheme['threshold.line'].light,
lineType: TimeSeriesChartLineType.solid, lineType: TimeSeriesChartLineType.solid,
@ -404,13 +483,61 @@ export interface TimeSeriesChartNoAggregationBarWidthSettings {
barWidth?: TimeSeriesChartBarWidth; barWidth?: TimeSeriesChartBarWidth;
} }
export enum TimeSeriesChartAnimationEasing {
linear = 'linear',
quadraticIn = 'quadraticIn',
quadraticOut = 'quadraticOut',
quadraticInOut = 'quadraticInOut',
cubicIn = 'cubicIn',
cubicOut = 'cubicOut',
cubicInOut = 'cubicInOut',
quarticIn = 'quarticIn',
quarticOut = 'quarticOut',
quarticInOut = 'quarticInOut',
quinticIn = 'quinticIn',
quinticOut = 'quinticOut',
quinticInOut = 'quinticInOut',
sinusoidalIn = 'sinusoidalIn',
sinusoidalOut = 'sinusoidalOut',
sinusoidalInOut = 'sinusoidalInOut',
exponentialIn = 'exponentialIn',
exponentialOut = 'exponentialOut',
exponentialInOut = 'exponentialInOut',
circularIn = 'circularIn',
circularOut = 'circularOut',
circularInOut = 'circularInOut',
elasticIn = 'elasticIn',
elasticOut = 'elasticOut',
elasticInOut = 'elasticInOut',
backIn = 'backIn',
backOut = 'backOut',
backInOut = 'backInOut',
bounceIn = 'bounceIn',
bounceOut = 'bounceOut',
bounceInOut = 'bounceInOut'
}
export const timeSeriesChartAnimationEasings = Object.keys(TimeSeriesChartAnimationEasing) as TimeSeriesChartAnimationEasing[];
export interface TimeSeriesChartAnimationSettings {
animation: boolean;
animationThreshold: number;
animationDuration: number;
animationEasing: TimeSeriesChartAnimationEasing;
animationDelay: number;
animationDurationUpdate: number;
animationEasingUpdate: TimeSeriesChartAnimationEasing;
animationDelayUpdate: number;
}
export interface TimeSeriesChartSettings extends EChartsTooltipWidgetSettings { export interface TimeSeriesChartSettings extends EChartsTooltipWidgetSettings {
thresholds: TimeSeriesChartThreshold[]; thresholds: TimeSeriesChartThreshold[];
darkMode: boolean; darkMode: boolean;
dataZoom: boolean; dataZoom: boolean;
stack: boolean; stack: boolean;
yAxis: TimeSeriesChartYAxisSettings; yAxes: TimeSeriesChartYAxes;
xAxis: TimeSeriesChartAxisSettings; xAxis: TimeSeriesChartAxisSettings;
animation: TimeSeriesChartAnimationSettings;
noAggregationBarWidthSettings: TimeSeriesChartNoAggregationBarWidthSettings; noAggregationBarWidthSettings: TimeSeriesChartNoAggregationBarWidthSettings;
} }
@ -419,36 +546,10 @@ export const timeSeriesChartDefaultSettings: TimeSeriesChartSettings = {
darkMode: false, darkMode: false,
dataZoom: true, dataZoom: true,
stack: false, stack: false,
yAxis: { yAxes: {
show: true, default: mergeDeep({} as TimeSeriesChartYAxisSettings,
label: '', defaultTimeSeriesChartYAxisSettings,
labelFont: { { id: 'default', order: 0 } as TimeSeriesChartYAxisSettings)
family: 'Roboto',
size: 12,
sizeUnit: 'px',
style: 'normal',
weight: '600',
lineHeight: '1'
},
labelColor: timeSeriesChartColorScheme['axis.label'].light,
position: AxisPosition.left,
showTickLabels: true,
tickLabelFont: {
family: 'Roboto',
size: 12,
sizeUnit: 'px',
style: 'normal',
weight: '400',
lineHeight: '1'
},
tickLabelColor: timeSeriesChartColorScheme['axis.tickLabel'].light,
ticksFormatter: null,
showTicks: true,
ticksColor: timeSeriesChartColorScheme['axis.ticks'].light,
showLine: true,
lineColor: timeSeriesChartColorScheme['axis.line'].light,
showSplitLines: true,
splitLinesColor: timeSeriesChartColorScheme['axis.splitLine'].light
}, },
xAxis: { xAxis: {
show: true, show: true,
@ -480,6 +581,16 @@ export const timeSeriesChartDefaultSettings: TimeSeriesChartSettings = {
showSplitLines: true, showSplitLines: true,
splitLinesColor: timeSeriesChartColorScheme['axis.splitLine'].light splitLinesColor: timeSeriesChartColorScheme['axis.splitLine'].light
}, },
animation: {
animation: true,
animationThreshold: 2000,
animationDuration: 1000,
animationEasing: TimeSeriesChartAnimationEasing.cubicOut,
animationDelay: 0,
animationDurationUpdate: 300,
animationEasingUpdate: TimeSeriesChartAnimationEasing.cubicOut,
animationDelayUpdate: 0
},
noAggregationBarWidthSettings: { noAggregationBarWidthSettings: {
strategy: TimeSeriesChartNoAggregationBarWidthStrategy.group, strategy: TimeSeriesChartNoAggregationBarWidthStrategy.group,
groupWidth: { groupWidth: {
@ -558,6 +669,7 @@ export interface BarSeriesSettings {
} }
export interface TimeSeriesChartKeySettings { export interface TimeSeriesChartKeySettings {
yAxisId: TimeSeriesChartYAxisId;
showInLegend: boolean; showInLegend: boolean;
dataHiddenByDefault: boolean; dataHiddenByDefault: boolean;
type: TimeSeriesChartSeriesType; type: TimeSeriesChartSeriesType;
@ -566,6 +678,7 @@ export interface TimeSeriesChartKeySettings {
} }
export const timeSeriesChartKeyDefaultSettings: TimeSeriesChartKeySettings = { export const timeSeriesChartKeyDefaultSettings: TimeSeriesChartKeySettings = {
yAxisId: 'default',
showInLegend: true, showInLegend: true,
dataHiddenByDefault: false, dataHiddenByDefault: false,
type: TimeSeriesChartSeriesType.line, type: TimeSeriesChartSeriesType.line,
@ -626,6 +739,7 @@ export const timeSeriesChartKeyDefaultSettings: TimeSeriesChartKeySettings = {
}; };
export interface TimeSeriesChartDataItem extends EChartsSeriesItem { export interface TimeSeriesChartDataItem extends EChartsSeriesItem {
yAxisId: TimeSeriesChartYAxisId;
yAxisIndex: number; yAxisIndex: number;
option?: LineSeriesOption | CustomSeriesOption; option?: LineSeriesOption | CustomSeriesOption;
barRenderContext?: BarRenderContext; barRenderContext?: BarRenderContext;
@ -635,6 +749,7 @@ type TimeSeriesChartThresholdValue = number | string | (number | string)[];
export interface TimeSeriesChartThresholdItem { export interface TimeSeriesChartThresholdItem {
id: string; id: string;
yAxisId: TimeSeriesChartYAxisId;
yAxisIndex: number; yAxisIndex: number;
latestDataKey?: DataKey; latestDataKey?: DataKey;
units?: string; units?: string;
@ -646,14 +761,16 @@ export interface TimeSeriesChartThresholdItem {
export interface TimeSeriesChartYAxis { export interface TimeSeriesChartYAxis {
id: string; id: string;
units: string; decimals: number;
settings: TimeSeriesChartYAxisSettings;
option: YAXisOption & ValueAxisBaseOption; option: YAXisOption & ValueAxisBaseOption;
intervalCalculator?: (axis: Axis2D) => number; intervalCalculator?: (axis: Axis2D) => number;
ticksFormatter?: (value: any) => string; ticksFormatter?: (value: any) => string;
} }
export const createTimeSeriesYAxis = (axisId: string, units: string, export const createTimeSeriesYAxis = (units: string,
decimals: number, settings: TimeSeriesChartYAxisSettings, decimals: number,
settings: TimeSeriesChartYAxisSettings,
darkMode: boolean): TimeSeriesChartYAxis => { darkMode: boolean): TimeSeriesChartYAxis => {
const yAxisTickLabelStyle = createChartTextStyle(settings.tickLabelFont, const yAxisTickLabelStyle = createChartTextStyle(settings.tickLabelFont,
settings.tickLabelColor, darkMode, 'axis.tickLabel'); settings.tickLabelColor, darkMode, 'axis.tickLabel');
@ -664,13 +781,14 @@ export const createTimeSeriesYAxis = (axisId: string, units: string,
ticksFormatter = parseFunction(settings.ticksFormatter, ['value']); ticksFormatter = parseFunction(settings.ticksFormatter, ['value']);
} }
const yAxis: TimeSeriesChartYAxis = { const yAxis: TimeSeriesChartYAxis = {
id: axisId, id: settings.id,
units, decimals,
settings,
option: { option: {
show: settings.show, show: settings.show,
type: 'value', type: 'value',
position: settings.position, position: settings.position,
id: axisId, id: settings.id,
offset: 0, offset: 0,
alignTicks: true, alignTicks: true,
scale: true, scale: true,
@ -843,13 +961,11 @@ const generateChartThresholds = (thresholdItems: TimeSeriesChartThresholdItem[],
id: item.id, id: item.id,
dataGroupId: item.id, dataGroupId: item.id,
yAxisIndex: item.yAxisIndex, yAxisIndex: item.yAxisIndex,
animation: true,
data: [], data: [],
tooltip: { tooltip: {
show: false show: false
}, },
markLine: { markLine: {
animation: true,
lineStyle: { lineStyle: {
width: item.settings.lineWidth, width: item.settings.lineWidth,
color: prepareChartThemeColor(item.settings.lineColor, darkMode, 'threshold.line'), color: prepareChartThemeColor(item.settings.lineColor, darkMode, 'threshold.line'),
@ -956,16 +1072,19 @@ const generateChartSeries = (dataItems: TimeSeriesChartDataItem[],
}; };
export const updateDarkMode = (options: EChartsOption, settings: TimeSeriesChartSettings, export const updateDarkMode = (options: EChartsOption, settings: TimeSeriesChartSettings,
yAxisList: TimeSeriesChartYAxis[],
dataItems: TimeSeriesChartDataItem[], thresholdDataItems: TimeSeriesChartThresholdItem[], dataItems: TimeSeriesChartDataItem[], thresholdDataItems: TimeSeriesChartThresholdItem[],
darkMode: boolean): EChartsOption => { darkMode: boolean): EChartsOption => {
options.darkMode = darkMode; options.darkMode = darkMode;
if (Array.isArray(options.yAxis)) { if (Array.isArray(options.yAxis)) {
for (const yAxis of options.yAxis) { for (let i = 0; i < options.yAxis.length; i++) {
yAxis.nameTextStyle.color = prepareChartThemeColor(settings.yAxis.labelColor, darkMode, 'axis.label'); const yAxis = options.yAxis[i];
yAxis.axisLabel.color = prepareChartThemeColor(settings.yAxis.tickLabelColor, darkMode, 'axis.tickLabel'); const yAxisSettings = yAxisList[i].settings;
yAxis.axisLine.lineStyle.color = prepareChartThemeColor(settings.yAxis.lineColor, darkMode, 'axis.line'); yAxis.nameTextStyle.color = prepareChartThemeColor(yAxisSettings.labelColor, darkMode, 'axis.label');
yAxis.axisTick.lineStyle.color = prepareChartThemeColor(settings.yAxis.ticksColor, darkMode, 'axis.ticks'); yAxis.axisLabel.color = prepareChartThemeColor(yAxisSettings.tickLabelColor, darkMode, 'axis.tickLabel');
yAxis.splitLine.lineStyle.color = prepareChartThemeColor(settings.yAxis.splitLinesColor, darkMode, 'axis.splitLine'); yAxis.axisLine.lineStyle.color = prepareChartThemeColor(yAxisSettings.lineColor, darkMode, 'axis.line');
yAxis.axisTick.lineStyle.color = prepareChartThemeColor(yAxisSettings.ticksColor, darkMode, 'axis.ticks');
yAxis.splitLine.lineStyle.color = prepareChartThemeColor(yAxisSettings.splitLinesColor, darkMode, 'axis.splitLine');
} }
} }
if (Array.isArray(options.xAxis)) { if (Array.isArray(options.xAxis)) {
@ -1032,7 +1151,6 @@ const createTimeSeriesChartSeries = (item: TimeSeriesChartDataItem,
emphasis: { emphasis: {
focus: 'series' focus: 'series'
}, },
animation: true,
dimensions: [ dimensions: [
{name: 'intervalStart', type: 'number'}, {name: 'intervalStart', type: 'number'},
{name: 'intervalEnd', type: 'number'} {name: 'intervalEnd', type: 'number'}

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

@ -19,7 +19,7 @@ import {
AxisPosition, AxisPosition,
calculateThresholdsOffset, calculateThresholdsOffset,
createTimeSeriesXAxisOption, createTimeSeriesXAxisOption,
createTimeSeriesYAxis, createTimeSeriesYAxis, defaultTimeSeriesChartYAxisSettings,
generateChartData, generateChartData,
parseThresholdData, parseThresholdData,
SeriesLabelPosition, SeriesLabelPosition,
@ -29,11 +29,11 @@ import {
TimeSeriesChartKeySettings, TimeSeriesChartKeySettings,
TimeSeriesChartSeriesType, TimeSeriesChartSeriesType,
TimeSeriesChartSettings, TimeSeriesChartSettings,
TimeSeriesChartShape, TimeSeriesChartShape, TimeSeriesChartThreshold, timeSeriesChartThresholdDefaultSettings,
TimeSeriesChartThresholdItem, TimeSeriesChartThresholdItem,
TimeSeriesChartThresholdType, TimeSeriesChartThresholdType,
TimeSeriesChartType, TimeSeriesChartType,
TimeSeriesChartYAxis, TimeSeriesChartYAxis, TimeSeriesChartYAxisId, TimeSeriesChartYAxisSettings,
updateDarkMode updateDarkMode
} from '@home/components/widget/lib/chart/time-series-chart.models'; } from '@home/components/widget/lib/chart/time-series-chart.models';
import { ResizeObserver } from '@juggle/resize-observer'; import { ResizeObserver } from '@juggle/resize-observer';
@ -63,6 +63,7 @@ import { AggregationType } from '@shared/models/time/time.models';
import { DataKeyType } from '@shared/models/telemetry/telemetry.models'; import { DataKeyType } from '@shared/models/telemetry/telemetry.models';
import { WidgetSubscriptionOptions } from '@core/api/widget-api.models'; import { WidgetSubscriptionOptions } from '@core/api/widget-api.models';
import { DataKeySettingsFunction } from '@home/components/widget/config/data-keys.component.models'; import { DataKeySettingsFunction } from '@home/components/widget/config/data-keys.component.models';
import { DeepPartial } from '@shared/models/common';
export class TbTimeSeriesChart { export class TbTimeSeriesChart {
@ -94,9 +95,11 @@ export class TbTimeSeriesChart {
private readonly shapeResize$: ResizeObserver; private readonly shapeResize$: ResizeObserver;
private readonly settings: TimeSeriesChartSettings;
private yAxisList: TimeSeriesChartYAxis[] = [];
private dataItems: TimeSeriesChartDataItem[] = []; private dataItems: TimeSeriesChartDataItem[] = [];
private thresholdItems: TimeSeriesChartThresholdItem[] = []; private thresholdItems: TimeSeriesChartThresholdItem[] = [];
private yAxisList: TimeSeriesChartYAxis[] = [];
private timeSeriesChart: ECharts; private timeSeriesChart: ECharts;
private timeSeriesChartOptions: EChartsOption; private timeSeriesChartOptions: EChartsOption;
@ -120,13 +123,16 @@ export class TbTimeSeriesChart {
yMax$ = this.yMaxSubject.asObservable(); yMax$ = this.yMaxSubject.asObservable();
constructor(private ctx: WidgetContext, constructor(private ctx: WidgetContext,
private readonly settings: TimeSeriesChartSettings, private readonly inputSettings: DeepPartial<TimeSeriesChartSettings>,
private chartElement: HTMLElement, private chartElement: HTMLElement,
private renderer: Renderer2, private renderer: Renderer2,
private autoResize = true) { private autoResize = true) {
this.settings = mergeDeep({} as TimeSeriesChartSettings, timeSeriesChartDefaultSettings, this.settings); this.settings = mergeDeep({} as TimeSeriesChartSettings,
timeSeriesChartDefaultSettings,
this.inputSettings as TimeSeriesChartSettings);
this.darkMode = this.settings.darkMode; this.darkMode = this.settings.darkMode;
this.setupYAxes();
this.setupData(); this.setupData();
this.setupThresholds(); this.setupThresholds();
if (this.settings.showTooltip && this.settings.tooltipShowDate) { if (this.settings.showTooltip && this.settings.tooltipShowDate) {
@ -258,7 +264,8 @@ export class TbTimeSeriesChart {
if (this.darkMode !== darkMode) { if (this.darkMode !== darkMode) {
this.darkMode = darkMode; this.darkMode = darkMode;
if (this.timeSeriesChart) { if (this.timeSeriesChart) {
this.timeSeriesChartOptions = updateDarkMode(this.timeSeriesChartOptions, this.settings, this.dataItems, this.timeSeriesChartOptions = updateDarkMode(this.timeSeriesChartOptions,
this.settings, this.yAxisList, this.dataItems,
this.thresholdItems, darkMode); this.thresholdItems, darkMode);
this.timeSeriesChart.setOption(this.timeSeriesChartOptions); this.timeSeriesChart.setOption(this.timeSeriesChartOptions);
} }
@ -287,11 +294,16 @@ export class TbTimeSeriesChart {
const units = dataKey.units && dataKey.units.length ? dataKey.units : this.ctx.units; const units = dataKey.units && dataKey.units.length ? dataKey.units : this.ctx.units;
const decimals = isDefinedAndNotNull(dataKey.decimals) ? dataKey.decimals : const decimals = isDefinedAndNotNull(dataKey.decimals) ? dataKey.decimals :
(isDefinedAndNotNull(this.ctx.decimals) ? this.ctx.decimals : 2); (isDefinedAndNotNull(this.ctx.decimals) ? this.ctx.decimals : 2);
let yAxisId = keySettings.yAxisId;
if (!Object.keys(this.settings.yAxes).includes(yAxisId)) {
yAxisId = 'default';
}
this.dataItems.push({ this.dataItems.push({
id: this.nextComponentId(), id: this.nextComponentId(),
units, units,
decimals, decimals,
yAxisIndex: this.getYAxis(units, decimals), yAxisId,
yAxisIndex: this.getYAxisIndex(yAxisId),
dataKey, dataKey,
data: namedData, data: namedData,
enabled: !keySettings.dataHiddenByDefault enabled: !keySettings.dataHiddenByDefault
@ -303,7 +315,9 @@ export class TbTimeSeriesChart {
private setupThresholds(): void { private setupThresholds(): void {
const thresholdDatasources: Datasource[] = []; const thresholdDatasources: Datasource[] = [];
for (const threshold of this.settings.thresholds) { for (const thresholdSettings of this.settings.thresholds) {
const threshold = mergeDeep<TimeSeriesChartThreshold>({} as TimeSeriesChartThreshold,
timeSeriesChartThresholdDefaultSettings, thresholdSettings);
let latestDataKey: DataKey = null; let latestDataKey: DataKey = null;
let entityDataKey: DataKey = null; let entityDataKey: DataKey = null;
let value = null; let value = null;
@ -351,11 +365,16 @@ export class TbTimeSeriesChart {
const units = threshold.units && threshold.units.length ? threshold.units : this.ctx.units; const units = threshold.units && threshold.units.length ? threshold.units : this.ctx.units;
const decimals = isDefinedAndNotNull(threshold.decimals) ? threshold.decimals : const decimals = isDefinedAndNotNull(threshold.decimals) ? threshold.decimals :
(isDefinedAndNotNull(this.ctx.decimals) ? this.ctx.decimals : 2); (isDefinedAndNotNull(this.ctx.decimals) ? this.ctx.decimals : 2);
let yAxisId = threshold.yAxisId;
if (!Object.keys(this.settings.yAxes).includes(yAxisId)) {
yAxisId = 'default';
}
const thresholdItem: TimeSeriesChartThresholdItem = { const thresholdItem: TimeSeriesChartThresholdItem = {
id: this.nextComponentId(), id: this.nextComponentId(),
units, units,
decimals, decimals,
yAxisIndex: this.getYAxis(units, decimals), yAxisId,
yAxisIndex: this.getYAxisIndex(yAxisId),
value, value,
latestDataKey, latestDataKey,
settings: threshold settings: threshold
@ -368,17 +387,32 @@ export class TbTimeSeriesChart {
this.subscribeForEntityThresholds(thresholdDatasources); this.subscribeForEntityThresholds(thresholdDatasources);
} }
private setupYAxes(): void {
const yAxisSettingsList = Object.values(this.settings.yAxes);
yAxisSettingsList.sort((a1, a2) => a1.order - a2.order);
for (const yAxisSettings of yAxisSettingsList) {
const axisSettings = mergeDeep<TimeSeriesChartYAxisSettings>({} as TimeSeriesChartYAxisSettings,
defaultTimeSeriesChartYAxisSettings, yAxisSettings);
const units = axisSettings.units && axisSettings.units.length ? axisSettings.units : this.ctx.units;
const decimals = isDefinedAndNotNull(axisSettings.decimals) ? axisSettings.decimals :
(isDefinedAndNotNull(this.ctx.decimals) ? this.ctx.decimals : 2);
const yAxis = createTimeSeriesYAxis(units, decimals, axisSettings, this.darkMode);
this.yAxisList.push(yAxis);
}
}
private nextComponentId(): string { private nextComponentId(): string {
return (this.componentIndexCounter++) + ''; return (this.componentIndexCounter++) + '';
} }
private getYAxis(units: string, decimals: number): number { private getYAxisIndex(id: TimeSeriesChartYAxisId): number {
let yAxisIndex = this.yAxisList.findIndex(axis => axis.units === units); let yAxisIndex = this.yAxisList.findIndex(axis => axis.id === id);
if (yAxisIndex === -1) { if (yAxisIndex === -1) {
const yAxisId = this.yAxisList.length + ''; yAxisIndex = this.yAxisList.findIndex(axis => axis.id === 'default');
const yAxis = createTimeSeriesYAxis(yAxisId, units, decimals, this.settings.yAxis, this.darkMode); if (yAxisIndex === -1 && this.yAxisList.length) {
this.yAxisList.push(yAxis); yAxisIndex = 0;
yAxisIndex = this.yAxisList.length - 1; }
} }
return yAxisIndex; return yAxisIndex;
} }
@ -460,7 +494,15 @@ export class TbTimeSeriesChart {
realtime: true, realtime: true,
bottom: 10 bottom: 10
} }
] ],
animation: this.settings.animation.animation,
animationThreshold: this.settings.animation.animationThreshold,
animationDuration: this.settings.animation.animationDuration,
animationEasing: this.settings.animation.animationEasing,
animationDelay: this.settings.animation.animationDelay,
animationDurationUpdate: this.settings.animation.animationDurationUpdate,
animationEasingUpdate: this.settings.animation.animationEasingUpdate,
animationDelayUpdate: this.settings.animation.animationDelayUpdate
}; };
this.timeSeriesChartOptions.xAxis[0].tbTimeWindow = this.ctx.defaultSubscription.timeWindow; this.timeSeriesChartOptions.xAxis[0].tbTimeWindow = this.ctx.defaultSubscription.timeWindow;
@ -588,7 +630,7 @@ export class TbTimeSeriesChart {
result.offset += 5; result.offset += 5;
} }
width = newWidth; width = newWidth;
const showLine = !!width && this.settings.yAxis.showLine; const showLine = !!width && yAxis.settings.showLine;
if (yAxis.option.axisLine.show !== showLine) { if (yAxis.option.axisLine.show !== showLine) {
yAxis.option.axisLine.show = showLine; yAxis.option.axisLine.show = showLine;
result.changed = true; result.changed = true;
@ -597,7 +639,7 @@ export class TbTimeSeriesChart {
yAxis.option.offset = result.offset; yAxis.option.offset = result.offset;
result.changed = true; result.changed = true;
} }
if (this.settings.yAxis.label) { if (yAxis.settings.label) {
if (!width) { if (!width) {
if (yAxis.option.name) { if (yAxis.option.name) {
yAxis.option.name = null; yAxis.option.name = null;
@ -605,7 +647,7 @@ export class TbTimeSeriesChart {
} }
} else { } else {
if (!yAxis.option.name) { if (!yAxis.option.name) {
yAxis.option.name = this.settings.yAxis.label; yAxis.option.name = yAxis.settings.label;
result.changed = true; result.changed = true;
} }
const nameGap = width; const nameGap = width;
@ -613,7 +655,7 @@ export class TbTimeSeriesChart {
yAxis.option.nameGap = nameGap; yAxis.option.nameGap = nameGap;
result.changed = true; result.changed = true;
} }
const nameWidth = measureYAxisNameWidth(this.timeSeriesChart, yAxis.id, this.settings.yAxis.label); const nameWidth = measureYAxisNameWidth(this.timeSeriesChart, yAxis.id, yAxis.settings.label);
result.offset += nameWidth; result.offset += nameWidth;
} }
} }
@ -623,15 +665,25 @@ export class TbTimeSeriesChart {
} }
private scaleYAxis(yAxis: TimeSeriesChartYAxis): boolean { private scaleYAxis(yAxis: TimeSeriesChartYAxis): boolean {
const yAxisIndex = this.yAxisList.indexOf(yAxis); const axisBarDataItems = this.dataItems.filter(d => d.yAxisId === yAxis.id && d.enabled &&
const axisBarDataItems = this.dataItems.filter(d => d.yAxisIndex === yAxisIndex && d.enabled &&
d.data.length && d.dataKey.settings.type === TimeSeriesChartSeriesType.bar); d.data.length && d.dataKey.settings.type === TimeSeriesChartSeriesType.bar);
return !axisBarDataItems.length; return !axisBarDataItems.length;
} }
private yAxisEmpty(yAxis: TimeSeriesChartYAxis): boolean {
const axisDataItems = this.dataItems.filter(d => d.yAxisId === yAxis.id && d.enabled &&
d.data.length);
return !axisDataItems.length;
}
private calculateYAxisInterval(axisList: TimeSeriesChartYAxis[]): boolean { private calculateYAxisInterval(axisList: TimeSeriesChartYAxis[]): boolean {
let changed = false; let changed = false;
for (const yAxis of axisList) { for (const yAxis of axisList) {
const minInterval = this.yAxisEmpty(yAxis) ? undefined : (1 / Math.pow(10, yAxis.decimals));
if (yAxis.option.minInterval !== minInterval) {
yAxis.option.minInterval = minInterval;
changed = true;
}
if (yAxis.intervalCalculator) { if (yAxis.intervalCalculator) {
const axis = getYAxis(this.timeSeriesChart, yAxis.id); const axis = getYAxis(this.timeSeriesChart, yAxis.id);
if (axis) { if (axis) {
@ -649,8 +701,10 @@ export class TbTimeSeriesChart {
} }
private minTopOffset(): number { private minTopOffset(): number {
const showTickLabels =
!!this.yAxisList.find(yAxis => yAxis.settings.show && yAxis.settings.showTickLabels);
return (this.topPointLabels) ? 20 : return (this.topPointLabels) ? 20 :
((this.settings.yAxis.show && this.settings.yAxis.showTickLabels) ? 10 : 5); (showTickLabels ? 10 : 5);
} }
private minBottomOffset(): number { private minBottomOffset(): number {

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

@ -29,6 +29,18 @@
</div> </div>
</mat-slide-toggle> </mat-slide-toggle>
</div> </div>
<div class="tb-form-panel">
<div class="tb-form-row space-between column-xs">
<div translate>widgets.time-series-chart.axis.y-axis</div>
<mat-form-field class="medium-width" appearance="outline" subscriptSizing="dynamic">
<mat-select formControlName="yAxisId">
<mat-option *ngFor="let yAxis of yAxisIds" [value]="yAxis">
{{ yAxis }}
</mat-option>
</mat-select>
</mat-form-field>
</div>
</div>
<div class="tb-form-panel"> <div class="tb-form-panel">
<div *ngIf="chartType === TimeSeriesChartType.default; else chartTypeTitle" fxLayout="row" fxLayoutAlign="space-between center"> <div *ngIf="chartType === TimeSeriesChartType.default; else chartTypeTitle" fxLayout="row" fxLayoutAlign="space-between center">
<div class="tb-form-panel-title" translate>widgets.time-series-chart.series.series-type</div> <div class="tb-form-panel-title" translate>widgets.time-series-chart.series.series-type</div>

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

@ -25,9 +25,10 @@ import {
TimeSeriesChartKeySettings, TimeSeriesChartKeySettings,
TimeSeriesChartSeriesType, TimeSeriesChartSeriesType,
timeSeriesChartSeriesTypes, timeSeriesChartSeriesTypes,
timeSeriesChartSeriesTypeTranslations, TimeSeriesChartType, timeSeriesChartTypeTranslations timeSeriesChartSeriesTypeTranslations, TimeSeriesChartType, timeSeriesChartTypeTranslations, TimeSeriesChartYAxisId
} from '@home/components/widget/lib/chart/time-series-chart.models'; } from '@home/components/widget/lib/chart/time-series-chart.models';
import { WidgetConfigComponentData } from '@home/models/widget-component.models'; import { WidgetConfigComponentData } from '@home/models/widget-component.models';
import { TimeSeriesChartWidgetSettings } from '@home/components/widget/lib/chart/time-series-chart-widget.models';
@Component({ @Component({
selector: 'tb-time-series-chart-key-settings', selector: 'tb-time-series-chart-key-settings',
@ -50,6 +51,8 @@ export class TimeSeriesChartKeySettingsComponent extends WidgetSettingsComponent
chartType = TimeSeriesChartType.default; chartType = TimeSeriesChartType.default;
yAxisIds: TimeSeriesChartYAxisId[];
constructor(protected store: Store<AppState>, constructor(protected store: Store<AppState>,
private fb: UntypedFormBuilder) { private fb: UntypedFormBuilder) {
super(store); super(store);
@ -64,6 +67,8 @@ export class TimeSeriesChartKeySettingsComponent extends WidgetSettingsComponent
if (isDefinedAndNotNull(params.chartType)) { if (isDefinedAndNotNull(params.chartType)) {
this.chartType = params.chartType; this.chartType = params.chartType;
} }
const widgetSettings = (widgetConfig.config?.settings || {}) as TimeSeriesChartWidgetSettings;
this.yAxisIds = widgetSettings.yAxes ? Object.keys(widgetSettings.yAxes) : ['default'];
} }
protected defaultSettings(): WidgetSettings { protected defaultSettings(): WidgetSettings {
@ -73,7 +78,12 @@ export class TimeSeriesChartKeySettingsComponent extends WidgetSettingsComponent
protected onSettingsSet(settings: WidgetSettings) { protected onSettingsSet(settings: WidgetSettings) {
const seriesSettings = settings as TimeSeriesChartKeySettings; const seriesSettings = settings as TimeSeriesChartKeySettings;
let yAxisId = seriesSettings.yAxisId;
if (!this.yAxisIds.includes(yAxisId)) {
yAxisId = 'default';
}
this.timeSeriesChartKeySettingsForm = this.fb.group({ this.timeSeriesChartKeySettingsForm = this.fb.group({
yAxisId: [yAxisId, []],
showInLegend: [seriesSettings.showInLegend, []], showInLegend: [seriesSettings.showInLegend, []],
dataHiddenByDefault: [seriesSettings.dataHiddenByDefault, []], dataHiddenByDefault: [seriesSettings.dataHiddenByDefault, []],
type: [seriesSettings.type, []], type: [seriesSettings.type, []],

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

@ -16,12 +16,18 @@
--> -->
<ng-container [formGroup]="timeSeriesChartWidgetSettingsForm"> <ng-container [formGroup]="timeSeriesChartWidgetSettingsForm">
<tb-time-series-chart-y-axes-panel
formControlName="yAxes"
(axisRemoved)="yAxisRemoved($event)"
advanced>
</tb-time-series-chart-y-axes-panel>
<tb-time-series-chart-thresholds-panel <tb-time-series-chart-thresholds-panel
formControlName="thresholds" formControlName="thresholds"
[aliasController]="aliasController" [aliasController]="aliasController"
[dataKeyCallbacks]="dataKeyCallbacks" [dataKeyCallbacks]="dataKeyCallbacks"
[datasource]="datasource" [datasource]="datasource"
[widgetConfig]="widgetConfig?.config"> [widgetConfig]="widgetConfig?.config"
[yAxisIds]="yAxisIds">
</tb-time-series-chart-thresholds-panel> </tb-time-series-chart-thresholds-panel>
<div class="tb-form-panel"> <div class="tb-form-panel">
<div class="tb-form-panel-title" translate>widgets.time-series-chart.chart-style</div> <div class="tb-form-panel-title" translate>widgets.time-series-chart.chart-style</div>
@ -38,12 +44,7 @@
</mat-slide-toggle> </mat-slide-toggle>
</div> </div>
<div class="tb-form-panel"> <div class="tb-form-panel">
<div class="tb-form-panel-title" translate>widgets.time-series-chart.axis.axes</div> <div class="tb-form-panel-title" translate>widgets.time-series-chart.axis.x-axis</div>
<tb-time-series-chart-axis-settings
formControlName="yAxis"
advanced
axisType="yAxis">
</tb-time-series-chart-axis-settings>
<tb-time-series-chart-axis-settings <tb-time-series-chart-axis-settings
formControlName="xAxis" formControlName="xAxis"
advanced advanced

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

@ -16,6 +16,7 @@
import { Component, Injector } from '@angular/core'; import { Component, Injector } from '@angular/core';
import { import {
DataKey,
Datasource, Datasource,
legendPositions, legendPositions,
legendPositionTranslationMap, legendPositionTranslationMap,
@ -32,7 +33,11 @@ import {
timeSeriesChartWidgetDefaultSettings, timeSeriesChartWidgetDefaultSettings,
TimeSeriesChartWidgetSettings TimeSeriesChartWidgetSettings
} from '@home/components/widget/lib/chart/time-series-chart-widget.models'; } from '@home/components/widget/lib/chart/time-series-chart-widget.models';
import { TimeSeriesChartType } from '@home/components/widget/lib/chart/time-series-chart.models'; import {
TimeSeriesChartKeySettings,
TimeSeriesChartType, TimeSeriesChartYAxes,
TimeSeriesChartYAxisId
} from '@home/components/widget/lib/chart/time-series-chart.models';
import { WidgetConfigComponentData } from '@home/models/widget-component.models'; import { WidgetConfigComponentData } from '@home/models/widget-component.models';
@Component({ @Component({
@ -51,6 +56,11 @@ export class TimeSeriesChartWidgetSettingsComponent extends WidgetSettingsCompon
} }
} }
public get yAxisIds(): TimeSeriesChartYAxisId[] {
const yAxes: TimeSeriesChartYAxes = this.timeSeriesChartWidgetSettingsForm.get('yAxes').value;
return Object.keys(yAxes);
}
TimeSeriesChartType = TimeSeriesChartType; TimeSeriesChartType = TimeSeriesChartType;
EChartsTooltipTrigger = EChartsTooltipTrigger; EChartsTooltipTrigger = EChartsTooltipTrigger;
@ -73,6 +83,15 @@ export class TimeSeriesChartWidgetSettingsComponent extends WidgetSettingsCompon
super(store); super(store);
} }
public yAxisRemoved(yAxisId: TimeSeriesChartYAxisId): void {
if (this.widgetConfig.config.datasources && this.widgetConfig.config.datasources.length > 1) {
for (let i = 1; i < this.widgetConfig.config.datasources.length; i++) {
const datasource = this.widgetConfig.config.datasources[i];
this.removeYaxisId(datasource.dataKeys, yAxisId);
}
}
}
protected settingsForm(): UntypedFormGroup { protected settingsForm(): UntypedFormGroup {
return this.timeSeriesChartWidgetSettingsForm; return this.timeSeriesChartWidgetSettingsForm;
} }
@ -91,12 +110,12 @@ export class TimeSeriesChartWidgetSettingsComponent extends WidgetSettingsCompon
protected onSettingsSet(settings: WidgetSettings) { protected onSettingsSet(settings: WidgetSettings) {
this.timeSeriesChartWidgetSettingsForm = this.fb.group({ this.timeSeriesChartWidgetSettingsForm = this.fb.group({
yAxes: [settings.yAxes, []],
thresholds: [settings.thresholds, []], thresholds: [settings.thresholds, []],
dataZoom: [settings.dataZoom, []], dataZoom: [settings.dataZoom, []],
stack: [settings.stack, []], stack: [settings.stack, []],
yAxis: [settings.yAxis, []],
xAxis: [settings.xAxis, []], xAxis: [settings.xAxis, []],
noAggregationBarWidthSettings: [settings.noAggregationBarWidthSettings, []], noAggregationBarWidthSettings: [settings.noAggregationBarWidthSettings, []],
@ -173,6 +192,20 @@ export class TimeSeriesChartWidgetSettingsComponent extends WidgetSettingsCompon
} }
} }
private removeYaxisId(series: DataKey[], yAxisId: TimeSeriesChartYAxisId): boolean {
let changed = false;
if (series) {
series.forEach(key => {
const keySettings = ((key.settings || {}) as TimeSeriesChartKeySettings);
if (keySettings.yAxisId === yAxisId) {
keySettings.yAxisId = 'default';
changed = true;
}
});
}
return changed;
}
private _tooltipValuePreviewFn(): string { private _tooltipValuePreviewFn(): string {
return formatValue(22, 0, '°C', false); return formatValue(22, 0, '°C', false);
} }

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

@ -17,13 +17,13 @@
--> -->
<ng-container [formGroup]="axisSettingsFormGroup"> <ng-container [formGroup]="axisSettingsFormGroup">
<div class="tb-form-panel stroked tb-slide-toggle"> <div class="tb-form-panel stroked tb-slide-toggle">
<mat-expansion-panel class="tb-settings" [(expanded)]="settingsExpanded" <mat-expansion-panel class="tb-settings" [(expanded)]="alwaysExpanded || settingsExpanded"
[disabled]="!axisSettingsFormGroup.get('show').value"> [disabled]="alwaysExpanded || !axisSettingsFormGroup.get('show').value">
<mat-expansion-panel-header fxLayout="row wrap"> <mat-expansion-panel-header fxLayout="row wrap">
<mat-panel-title> <mat-panel-title>
<mat-slide-toggle class="mat-slide" formControlName="show" (click)="$event.stopPropagation()" <mat-slide-toggle class="mat-slide" formControlName="show" (click)="$event.stopPropagation()"
fxLayoutAlign="center"> fxLayoutAlign="center">
{{ axisTitle | translate }} {{ 'widgets.time-series-chart.axis.show' | translate }}
</mat-slide-toggle> </mat-slide-toggle>
</mat-panel-title> </mat-panel-title>
</mat-expansion-panel-header> </mat-expansion-panel-header>
@ -56,6 +56,18 @@
</mat-select> </mat-select>
</mat-form-field> </mat-form-field>
</div> </div>
<div *ngIf="axisType === 'yAxis'" class="tb-form-row space-between">
<div translate>widget-config.units-short</div>
<tb-unit-input
formControlName="units">
</tb-unit-input>
</div>
<div *ngIf="axisType === 'yAxis'" class="tb-form-row space-between">
<div 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-panel no-padding stroked"> <div class="tb-form-panel no-padding stroked">
<div class="tb-form-row no-border space-between column-xs"> <div class="tb-form-row no-border space-between column-xs">
<mat-slide-toggle class="mat-slide" formControlName="showTickLabels"> <mat-slide-toggle class="mat-slide" formControlName="showTickLabels">

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

@ -15,7 +15,13 @@
/// ///
import { Component, forwardRef, Input, OnInit } from '@angular/core'; import { Component, forwardRef, Input, OnInit } from '@angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR, UntypedFormBuilder, UntypedFormGroup } from '@angular/forms'; import {
ControlValueAccessor,
NG_VALUE_ACCESSOR,
UntypedFormBuilder,
UntypedFormGroup,
Validators
} from '@angular/forms';
import { import {
AxisPosition, AxisPosition,
timeSeriesAxisPositionTranslations, timeSeriesAxisPositionTranslations,
@ -40,9 +46,11 @@ import { WidgetService } from '@core/http/widget.service';
}) })
export class TimeSeriesChartAxisSettingsComponent implements OnInit, ControlValueAccessor { export class TimeSeriesChartAxisSettingsComponent implements OnInit, ControlValueAccessor {
settingsExpanded = false; @Input()
@coerceBoolean()
alwaysExpanded = false;
axisTitle: string; settingsExpanded = false;
axisPositions: AxisPosition[]; axisPositions: AxisPosition[];
@ -72,8 +80,6 @@ export class TimeSeriesChartAxisSettingsComponent implements OnInit, ControlValu
ngOnInit(): void { ngOnInit(): void {
this.axisTitle = this.axisType === 'xAxis' ? 'widgets.time-series-chart.axis.x-axis' : 'widgets.time-series-chart.axis.y-axis';
this.axisPositions = this.axisType === 'xAxis' ? [AxisPosition.top, AxisPosition.bottom] : this.axisPositions = this.axisType === 'xAxis' ? [AxisPosition.top, AxisPosition.bottom] :
[AxisPosition.left, AxisPosition.right]; [AxisPosition.left, AxisPosition.right];
@ -94,6 +100,8 @@ export class TimeSeriesChartAxisSettingsComponent implements OnInit, ControlValu
splitLinesColor: [null, []] splitLinesColor: [null, []]
}); });
if (this.axisType === 'yAxis') { if (this.axisType === 'yAxis') {
this.axisSettingsFormGroup.addControl('units', this.fb.control(null, []));
this.axisSettingsFormGroup.addControl('decimals', this.fb.control(null, [Validators.min(0)]));
this.axisSettingsFormGroup.addControl('ticksFormatter', this.fb.control(null, [])); this.axisSettingsFormGroup.addControl('ticksFormatter', this.fb.control(null, []));
this.axisSettingsFormGroup.addControl('min', this.fb.control(null, [])); this.axisSettingsFormGroup.addControl('min', this.fb.control(null, []));
this.axisSettingsFormGroup.addControl('max', this.fb.control(null, [])); this.axisSettingsFormGroup.addControl('max', this.fb.control(null, []));

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

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

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

@ -55,6 +55,14 @@
} }
} }
.tb-y-axis-field {
width: 100px;
min-width: 100px;
.mat-mdc-form-field {
width: 100%;
}
}
.tb-color-field, .tb-units-field, .tb-decimals-field { .tb-color-field, .tb-units-field, .tb-decimals-field {
display: flex; display: flex;
flex-direction: row; flex-direction: row;
@ -77,6 +85,12 @@
min-width: 60px; min-width: 60px;
} }
.tb-y-axis-field {
@media #{$mat-xs} {
display: none;
}
}
.tb-units-field, .tb-color-field, .tb-decimals-field { .tb-units-field, .tb-color-field, .tb-decimals-field {
display: none; display: none;
@media #{$mat-gt-md} { @media #{$mat-gt-md} {

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

@ -19,10 +19,10 @@ import {
Component, Component,
EventEmitter, EventEmitter,
forwardRef, forwardRef,
Input, Input, OnChanges,
OnInit, OnInit,
Output, Output,
Renderer2, Renderer2, SimpleChanges,
ViewContainerRef, ViewContainerRef,
ViewEncapsulation ViewEncapsulation
} from '@angular/core'; } from '@angular/core';
@ -36,7 +36,7 @@ import {
} from '@angular/forms'; } from '@angular/forms';
import { import {
TimeSeriesChartThreshold, TimeSeriesChartThreshold,
TimeSeriesChartThresholdType, 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';
@ -67,7 +67,7 @@ import {
], ],
encapsulation: ViewEncapsulation.None encapsulation: ViewEncapsulation.None
}) })
export class TimeSeriesChartThresholdRowComponent implements ControlValueAccessor, OnInit { export class TimeSeriesChartThresholdRowComponent implements ControlValueAccessor, OnInit, OnChanges {
DataKeyType = DataKeyType; DataKeyType = DataKeyType;
@ -98,6 +98,9 @@ export class TimeSeriesChartThresholdRowComponent implements ControlValueAccesso
@Input() @Input()
disabled: boolean; disabled: boolean;
@Input()
yAxisIds: TimeSeriesChartYAxisId[];
@Output() @Output()
thresholdRemoved = new EventEmitter(); thresholdRemoved = new EventEmitter();
@ -124,6 +127,7 @@ export class TimeSeriesChartThresholdRowComponent implements ControlValueAccesso
type: [null, []], type: [null, []],
value: [null, [Validators.required]], value: [null, [Validators.required]],
entityAlias: [null, [Validators.required]], entityAlias: [null, [Validators.required]],
yAxisId: [null, [Validators.required]],
lineColor: [null, []], lineColor: [null, []],
units: [null, []], units: [null, []],
decimals: [null, []] decimals: [null, []]
@ -144,6 +148,20 @@ export class TimeSeriesChartThresholdRowComponent implements ControlValueAccesso
}); });
} }
ngOnChanges(changes: SimpleChanges): void {
for (const propName of Object.keys(changes)) {
const change = changes[propName];
if (!change.firstChange && change.currentValue !== change.previousValue) {
if (['yAxisIds'].includes(propName)) {
if (this.modelValue?.yAxisId &&
!this.yAxisIds.includes(this.modelValue.yAxisId)) {
this.thresholdFormGroup.patchValue({yAxisId: 'default'}, {emitEvent: true});
}
}
}
}
}
registerOnChange(fn: any): void { registerOnChange(fn: any): void {
this.propagateChange = fn; this.propagateChange = fn;
} }
@ -170,6 +188,7 @@ export class TimeSeriesChartThresholdRowComponent implements ControlValueAccesso
type: value.type, type: value.type,
value: value.value, value: value.value,
entityAlias: value.entityAlias, entityAlias: value.entityAlias,
yAxisId: value.yAxisId,
lineColor: value.lineColor, lineColor: value.lineColor,
units: value.units, units: value.units,
decimals: value.decimals, decimals: value.decimals,
@ -200,7 +219,8 @@ export class TimeSeriesChartThresholdRowComponent implements ControlValueAccesso
} else { } else {
const ctx: any = { const ctx: any = {
thresholdSettings: deepClone(this.modelValue), thresholdSettings: deepClone(this.modelValue),
widgetConfig: this.widgetConfig widgetConfig: this.widgetConfig,
yAxisIds: this.yAxisIds
}; };
const thresholdSettingsPanelPopover = this.popoverService.displayPopover(trigger, this.renderer, const thresholdSettingsPanelPopover = this.popoverService.displayPopover(trigger, this.renderer,
this.viewContainerRef, TimeSeriesChartThresholdSettingsPanelComponent, ['leftOnly', 'leftTopOnly', 'leftBottomOnly'], true, null, this.viewContainerRef, TimeSeriesChartThresholdSettingsPanelComponent, ['leftOnly', 'leftTopOnly', 'leftBottomOnly'], true, null,
@ -213,6 +233,7 @@ export class TimeSeriesChartThresholdRowComponent implements ControlValueAccesso
this.modelValue = {...this.modelValue, ...thresholdSettings}; this.modelValue = {...this.modelValue, ...thresholdSettings};
this.thresholdFormGroup.patchValue( this.thresholdFormGroup.patchValue(
{ {
yAxisId: this.modelValue.yAxisId,
units: this.modelValue.units, units: this.modelValue.units,
decimals: this.modelValue.decimals, decimals: this.modelValue.decimals,
lineColor: this.modelValue.lineColor lineColor: this.modelValue.lineColor
@ -248,6 +269,7 @@ export class TimeSeriesChartThresholdRowComponent implements ControlValueAccesso
this.modelValue.type = value.type; this.modelValue.type = value.type;
this.modelValue.value = value.value; this.modelValue.value = value.value;
this.modelValue.entityAlias = value.entityAlias; this.modelValue.entityAlias = value.entityAlias;
this.modelValue.yAxisId = value.yAxisId;
this.modelValue.lineColor = value.lineColor; this.modelValue.lineColor = value.lineColor;
this.modelValue.units = value.units; this.modelValue.units = value.units;
this.modelValue.decimals = value.decimals; this.modelValue.decimals = value.decimals;

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

@ -18,6 +18,16 @@
<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">{{ 'widgets.time-series-chart.threshold.threshold-settings' | 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 translate>widgets.time-series-chart.axis.y-axis</div>
<mat-form-field class="medium-width" appearance="outline" subscriptSizing="dynamic">
<mat-select formControlName="yAxisId">
<mat-option *ngFor="let yAxis of yAxisIds" [value]="yAxis">
{{ yAxis }}
</mat-option>
</mat-select>
</mat-form-field>
</div>
<div class="tb-form-row space-between"> <div 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

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

@ -21,7 +21,7 @@ import {
TimeSeriesChartShape, TimeSeriesChartShape,
timeSeriesChartShapes, timeSeriesChartShapes,
timeSeriesChartShapeTranslations, timeSeriesChartShapeTranslations,
TimeSeriesChartThreshold, TimeSeriesChartThreshold, TimeSeriesChartYAxisId,
timeSeriesLineTypes, timeSeriesLineTypes,
timeSeriesLineTypeTranslations, timeSeriesLineTypeTranslations,
timeSeriesThresholdLabelPositions, timeSeriesThresholdLabelPositions,
@ -60,6 +60,9 @@ export class TimeSeriesChartThresholdSettingsPanelComponent implements OnInit {
@Input() @Input()
widgetConfig: WidgetConfig; widgetConfig: WidgetConfig;
@Input()
yAxisIds: TimeSeriesChartYAxisId[];
@Input() @Input()
popover: TbPopoverComponent<TimeSeriesChartThresholdSettingsPanelComponent>; popover: TbPopoverComponent<TimeSeriesChartThresholdSettingsPanelComponent>;
@ -74,6 +77,7 @@ export class TimeSeriesChartThresholdSettingsPanelComponent implements OnInit {
ngOnInit(): void { ngOnInit(): void {
this.thresholdSettingsFormGroup = this.fb.group( this.thresholdSettingsFormGroup = this.fb.group(
{ {
yAxisId: [this.thresholdSettings.yAxisId, [Validators.required]],
units: [this.thresholdSettings.units, []], units: [this.thresholdSettings.units, []],
decimals: [this.thresholdSettings.decimals, [Validators.min(0)]], decimals: [this.thresholdSettings.decimals, [Validators.min(0)]],
lineColor: [this.thresholdSettings.lineColor, []], lineColor: [this.thresholdSettings.lineColor, []],

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

@ -21,6 +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 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>
@ -30,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"
[yAxisIds]="yAxisIds"
(thresholdRemoved)="removeThreshold($index)"> (thresholdRemoved)="removeThreshold($index)">
</tb-time-series-chart-threshold-row> </tb-time-series-chart-threshold-row>
<mat-divider *ngIf="!$last"></mat-divider> <mat-divider *ngIf="!$last"></mat-divider>

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

@ -33,6 +33,11 @@
} }
} }
&.tb-y-axis-header {
width: 100px;
min-width: 100px;
}
&.tb-units-header { &.tb-units-header {
width: 80px; width: 80px;
min-width: 80px; min-width: 80px;
@ -53,6 +58,12 @@
min-width: 80px; min-width: 80px;
} }
&.tb-y-axis-header {
@media #{$mat-xs} {
display: none;
}
}
&.tb-units-header, &.tb-color-header, &.tb-decimals-header { &.tb-units-header, &.tb-color-header, &.tb-decimals-header {
display: none; display: none;
@media #{$mat-gt-md} { @media #{$mat-gt-md} {

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

@ -31,7 +31,7 @@ import {
timeSeriesChartThresholdDefaultSettings, timeSeriesChartThresholdDefaultSettings,
TimeSeriesChartThresholdType, TimeSeriesChartThresholdType,
timeSeriesChartThresholdValid, timeSeriesChartThresholdValid,
timeSeriesChartThresholdValidator timeSeriesChartThresholdValidator, TimeSeriesChartYAxisId
} from '@home/components/widget/lib/chart/time-series-chart.models'; } from '@home/components/widget/lib/chart/time-series-chart.models';
import { mergeDeep } from '@core/utils'; import { mergeDeep } from '@core/utils';
import { IAliasController } from '@core/api/widget-api.models'; import { IAliasController } from '@core/api/widget-api.models';
@ -74,6 +74,9 @@ export class TimeSeriesChartThresholdsPanelComponent implements ControlValueAcce
@Input() @Input()
widgetConfig: WidgetConfig; widgetConfig: WidgetConfig;
@Input()
yAxisIds: TimeSeriesChartYAxisId[];
thresholdsFormGroup: UntypedFormGroup; thresholdsFormGroup: UntypedFormGroup;
private propagateChange = (_val: any) => {}; private propagateChange = (_val: any) => {};

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

@ -0,0 +1,63 @@
<!--
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.
-->
<div class="tb-form-panel tb-y-axes-panel">
<div class="tb-form-panel-title">{{ 'widgets.time-series-chart.y-axes' | translate }}</div>
<div class="tb-form-table">
<div class="tb-form-table-header no-padding-right">
<div class="tb-form-table-header-cell tb-axis-id-header" translate>widgets.time-series-chart.axis.id</div>
<div class="tb-form-table-header-cell tb-show-header" translate>widgets.time-series-chart.axis.show</div>
<div class="tb-form-table-header-cell tb-label-header" translate>widgets.time-series-chart.axis.label</div>
<div class="tb-form-table-header-cell tb-position-header" translate>widgets.time-series-chart.axis.position</div>
<div class="tb-form-table-header-cell tb-min-header" translate>widgets.time-series-chart.axis.min</div>
<div class="tb-form-table-header-cell tb-max-header" translate>widgets.time-series-chart.axis.max</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-actions-header"></div>
</div>
<div class="tb-form-table-body tb-drop-list"
cdkDropList cdkDropListOrientation="vertical"
[cdkDropListDisabled]="!dragEnabled"
(cdkDropListDropped)="axisDrop($event)">
<div cdkDrag [cdkDragDisabled]="!dragEnabled"
class="tb-draggable-form-table-row"
*ngFor="let axisControl of axesFormArray().controls; trackBy: trackByAxis; let $index = index;">
<tb-time-series-chart-y-axis-row fxFlex
[formControl]="axisControl"
[advanced]="advanced"
(axisRemoved)="removeAxis($index)">
</tb-time-series-chart-y-axis-row>
<div class="tb-form-table-row-cell-buttons">
<button fxHide.lt-lg
mat-icon-button
type="button"
cdkDragHandle
[ngClass]="{'tb-hidden': !dragEnabled}"
matTooltip="{{ 'action.drag' | translate }}"
matTooltipPosition="above">
<mat-icon>drag_indicator</mat-icon>
</button>
</div>
</div>
</div>
</div>
<div>
<button type="button" mat-stroked-button color="primary" (click)="addAxis()">
{{ 'widgets.time-series-chart.axis.add-y-axis' | translate }}
</button>
</div>
</div>

87
ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-y-axes-panel.component.scss

@ -0,0 +1,87 @@
/**
* 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 '../../../../../../../../../scss/constants';
.tb-y-axes-panel {
.tb-form-table-header-cell {
&.tb-axis-id-header {
width: 60px;
min-width: 60px;
}
&.tb-label-header {
flex: 1 1 60%;
min-width: 100px;
}
&.tb-position-header {
flex: 1;
min-width: 60px;
@media #{$mat-gt-md} {
flex: 1 1 40%;
}
}
&.tb-units-header {
width: 80px;
min-width: 80px;
}
&.tb-decimals-header {
width: 60px;
min-width: 60px;
}
&.tb-min-header, &.tb-max-header {
width: 80px;
min-width: 80px;
}
&.tb-show-header {
width: 40px;
min-width: 40px;
}
&.tb-actions-header {
width: 80px;
min-width: 80px;
@media #{$mat-gt-md} {
width: 120px;
min-width: 120px;
}
}
&.tb-label-header, &.tb-units-header, &.tb-decimals-header {
display: none;
@media #{$mat-gt-md} {
display: block;
}
}
&.tb-min-header, &.tb-max-header {
display: none;
@media #{$mat-gt-xs} {
display: block;
}
}
}
.tb-form-table-body {
tb-time-series-chart-y-axis-row {
overflow: hidden;
}
}
}

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

@ -0,0 +1,180 @@
///
/// 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, EventEmitter, forwardRef, Input, OnInit, Output, ViewEncapsulation } from '@angular/core';
import {
AbstractControl,
ControlValueAccessor,
NG_VALIDATORS,
NG_VALUE_ACCESSOR,
UntypedFormArray,
UntypedFormBuilder,
UntypedFormControl,
UntypedFormGroup,
Validator
} from '@angular/forms';
import {
defaultTimeSeriesChartYAxisSettings,
getNextTimeSeriesYAxisId,
TimeSeriesChartYAxes, TimeSeriesChartYAxisId,
TimeSeriesChartYAxisSettings,
timeSeriesChartYAxisValid,
timeSeriesChartYAxisValidator
} from '@home/components/widget/lib/chart/time-series-chart.models';
import { mergeDeep } from '@core/utils';
import { CdkDragDrop } from '@angular/cdk/drag-drop';
import { coerceBoolean } from '@shared/decorators/coercion';
@Component({
selector: 'tb-time-series-chart-y-axes-panel',
templateUrl: './time-series-chart-y-axes-panel.component.html',
styleUrls: ['./time-series-chart-y-axes-panel.component.scss'],
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => TimeSeriesChartYAxesPanelComponent),
multi: true
},
{
provide: NG_VALIDATORS,
useExisting: forwardRef(() => TimeSeriesChartYAxesPanelComponent),
multi: true
}
],
encapsulation: ViewEncapsulation.None
})
export class TimeSeriesChartYAxesPanelComponent implements ControlValueAccessor, OnInit, Validator {
@Input()
disabled: boolean;
@Input()
@coerceBoolean()
advanced = false;
@Output()
axisRemoved = new EventEmitter<TimeSeriesChartYAxisId>();
yAxesFormGroup: UntypedFormGroup;
get dragEnabled(): boolean {
return this.axesFormArray().controls.length > 1;
}
private propagateChange = (_val: any) => {};
constructor(private fb: UntypedFormBuilder) {
}
ngOnInit() {
this.yAxesFormGroup = this.fb.group({
axes: [this.fb.array([]), []]
});
this.yAxesFormGroup.valueChanges.subscribe(
() => {
let axes: TimeSeriesChartYAxisSettings[] = this.yAxesFormGroup.get('axes').value;
for (let i = 0; i < axes.length; i++) {
axes[i].order = i;
}
if (axes) {
axes = axes.filter(axis => timeSeriesChartYAxisValid(axis));
}
const yAxes: TimeSeriesChartYAxes = {};
for (const axis of axes) {
yAxes[axis.id] = axis;
}
this.propagateChange(yAxes);
}
);
}
registerOnChange(fn: any): void {
this.propagateChange = fn;
}
registerOnTouched(fn: any): void {
}
setDisabledState(isDisabled: boolean): void {
this.disabled = isDisabled;
if (isDisabled) {
this.yAxesFormGroup.disable({emitEvent: false});
} else {
this.yAxesFormGroup.enable({emitEvent: false});
}
}
writeValue(value: TimeSeriesChartYAxes | undefined): void {
const yAxes: TimeSeriesChartYAxes = value || {};
if (!yAxes.default) {
yAxes.default = mergeDeep({} as TimeSeriesChartYAxisSettings, defaultTimeSeriesChartYAxisSettings,
{id: 'default', order: 0} as TimeSeriesChartYAxisSettings);
}
const yAxisSettingsList = Object.values(yAxes);
yAxisSettingsList.sort((a1, a2) => a1.order - a2.order);
this.yAxesFormGroup.setControl('axes', this.prepareAxesFormArray(yAxisSettingsList), {emitEvent: false});
}
public validate(c: UntypedFormControl) {
const valid = this.yAxesFormGroup.valid;
return valid ? null : {
yAxes: {
valid: false,
},
};
}
axisDrop(event: CdkDragDrop<string[]>) {
const axesArray = this.yAxesFormGroup.get('axes') as UntypedFormArray;
const axis = axesArray.at(event.previousIndex);
axesArray.removeAt(event.previousIndex);
axesArray.insert(event.currentIndex, axis);
}
axesFormArray(): UntypedFormArray {
return this.yAxesFormGroup.get('axes') as UntypedFormArray;
}
trackByAxis(index: number, axisControl: AbstractControl): any {
return axisControl;
}
removeAxis(index: number) {
const axis =
(this.yAxesFormGroup.get('axes') as UntypedFormArray).at(index).value as TimeSeriesChartYAxisSettings;
(this.yAxesFormGroup.get('axes') as UntypedFormArray).removeAt(index);
this.axisRemoved.emit(axis.id);
}
addAxis() {
const axis = mergeDeep<TimeSeriesChartYAxisSettings>({} as TimeSeriesChartYAxisSettings,
defaultTimeSeriesChartYAxisSettings);
const axes: TimeSeriesChartYAxisSettings[] = this.yAxesFormGroup.get('axes').value;
axis.id = getNextTimeSeriesYAxisId(axes);
axis.order = axes.length;
const axesArray = this.yAxesFormGroup.get('axes') as UntypedFormArray;
const axisControl = this.fb.control(axis, [timeSeriesChartYAxisValidator]);
axesArray.push(axisControl);
}
private prepareAxesFormArray(axes: TimeSeriesChartYAxisSettings[]): UntypedFormArray {
const axesControls: Array<AbstractControl> = [];
axes.forEach((axis) => {
axesControls.push(this.fb.control(axis, [timeSeriesChartYAxisValidator]));
});
return this.fb.array(axesControls);
}
}

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

@ -0,0 +1,72 @@
<!--
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.
-->
<div [formGroup]="axisFormGroup" class="tb-form-table-row tb-axis-row">
<div class="tb-axis-id-field">
{{ modelValue.id }}
</div>
<mat-checkbox class="tb-show-field" formControlName="show"></mat-checkbox>
<mat-form-field class="tb-inline-field tb-label-field" appearance="outline" subscriptSizing="dynamic">
<input matInput formControlName="label" placeholder="{{ 'widget-config.set' | translate }}">
</mat-form-field>
<mat-form-field class="tb-inline-field tb-position-field" appearance="outline" subscriptSizing="dynamic">
<mat-select formControlName="position">
<mat-option *ngFor="let position of axisPositions" [value]="position">
{{ timeSeriesAxisPositionTranslations.get(position) | translate }}
</mat-option>
</mat-select>
</mat-form-field>
<div class="tb-min-field">
<mat-form-field appearance="outline" class="tb-inline-field number" subscriptSizing="dynamic">
<input matInput formControlName="min" type="number" placeholder="{{ 'widgets.time-series-chart.axis.scale-auto' | translate }}">
</mat-form-field>
</div>
<div class="tb-max-field">
<mat-form-field appearance="outline" class="tb-inline-field number" subscriptSizing="dynamic">
<input matInput formControlName="max" type="number" placeholder="{{ 'widgets.time-series-chart.axis.scale-auto' | translate }}">
</mat-form-field>
</div>
<div class="tb-units-field">
<tb-unit-input formControlName="units">
</tb-unit-input>
</div>
<div class="tb-decimals-field">
<mat-form-field appearance="outline" class="tb-inline-field 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-table-row-cell-buttons">
<button type="button"
mat-icon-button
#matButton
(click)="editAxis($event, matButton)"
matTooltip="{{ 'widgets.time-series-chart.axis.y-axis-settings' | translate }}"
matTooltipPosition="above">
<mat-icon>settings</mat-icon>
</button>
<div class="tb-remove-button">
<button *ngIf="modelValue.id !== 'default'"
type="button"
mat-icon-button
(click)="axisRemoved.emit()"
matTooltip="{{ 'widgets.time-series-chart.axis.remove-y-axis' | translate }}"
matTooltipPosition="above">
<mat-icon>delete</mat-icon>
</button>
</div>
</div>
</div>

95
ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-y-axis-row.component.scss

@ -0,0 +1,95 @@
/**
* 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 '../../../../../../../../../scss/constants';
.tb-form-table-row.tb-axis-row {
.tb-axis-id-field {
color: rgba(0, 0, 0, 0.76);
font-weight: 500;
font-size: 14px;
line-height: 20px;
letter-spacing: 0.2px;
width: 60px;
min-width: 60px;
}
.tb-label-field {
flex: 1 1 60%;
min-width: 100px;
}
.tb-position-field {
flex: 1;
min-width: 60px;
@media #{$mat-gt-md} {
flex: 1 1 40%;
}
}
.tb-units-field, .tb-decimals-field, .tb-min-field, .tb-max-field {
display: flex;
flex-direction: row;
place-content: center;
align-items: center;
}
.tb-units-field {
width: 80px;
min-width: 80px;
}
.tb-decimals-field {
width: 60px;
min-width: 60px;
}
.tb-min-field, .tb-max-field {
width: 80px;
min-width: 80px;
}
.tb-show-field {
width: 40px;
min-width: 40px;
}
.tb-label-field {
display: none;
@media #{$mat-gt-md} {
display: block;
}
}
.tb-units-field, .tb-decimals-field {
display: none;
@media #{$mat-gt-md} {
display: flex;
}
}
.tb-min-field, .tb-max-field {
display: none;
@media #{$mat-gt-xs} {
display: flex;
}
}
.tb-remove-button {
width: 40px;
min-width: 40px;
}
}

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

@ -0,0 +1,201 @@
///
/// 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 {
ChangeDetectorRef,
Component,
EventEmitter,
forwardRef,
Input,
OnInit,
Output,
Renderer2,
ViewContainerRef,
ViewEncapsulation
} from '@angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR, UntypedFormBuilder, UntypedFormGroup } from '@angular/forms';
import {
AxisPosition,
timeSeriesAxisPositionTranslations,
TimeSeriesChartYAxisSettings
} from '@home/components/widget/lib/chart/time-series-chart.models';
import { MatButton } from '@angular/material/button';
import { TbPopoverService } from '@shared/components/popover.service';
import { coerceBoolean } from '@shared/decorators/coercion';
import {
TimeSeriesChartYAxisSettingsPanelComponent
} from '@home/components/widget/lib/settings/common/chart/time-series-chart-y-axis-settings-panel.component';
import { deepClone } from '@core/utils';
@Component({
selector: 'tb-time-series-chart-y-axis-row',
templateUrl: './time-series-chart-y-axis-row.component.html',
styleUrls: ['./time-series-chart-y-axis-row.component.scss'],
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => TimeSeriesChartYAxisRowComponent),
multi: true
}
],
encapsulation: ViewEncapsulation.None
})
export class TimeSeriesChartYAxisRowComponent implements ControlValueAccessor, OnInit {
axisPositions = [AxisPosition.left, AxisPosition.right];
timeSeriesAxisPositionTranslations = timeSeriesAxisPositionTranslations;
@Input()
disabled: boolean;
@Input()
@coerceBoolean()
advanced = false;
@Output()
axisRemoved = new EventEmitter();
axisFormGroup: UntypedFormGroup;
modelValue: TimeSeriesChartYAxisSettings;
private propagateChange = (_val: any) => {};
constructor(private fb: UntypedFormBuilder,
private popoverService: TbPopoverService,
private renderer: Renderer2,
private viewContainerRef: ViewContainerRef,
private cd: ChangeDetectorRef) {
}
ngOnInit() {
this.axisFormGroup = this.fb.group({
label: [null, []],
position: [null, []],
units: [null, []],
decimals: [null, []],
min: [null, []],
max: [null, []],
show: [null, []]
});
this.axisFormGroup.valueChanges.subscribe(
() => this.updateModel()
);
this.axisFormGroup.get('show').valueChanges.subscribe(() => {
this.updateValidators();
});
}
registerOnChange(fn: any): void {
this.propagateChange = fn;
}
registerOnTouched(_fn: any): void {
}
setDisabledState(isDisabled: boolean): void {
this.disabled = isDisabled;
if (isDisabled) {
this.axisFormGroup.disable({emitEvent: false});
} else {
this.axisFormGroup.enable({emitEvent: false});
this.updateValidators();
}
}
writeValue(value: TimeSeriesChartYAxisSettings): void {
this.modelValue = value;
this.axisFormGroup.patchValue(
{
label: value.label,
position: value.position,
units: value.units,
decimals: value.decimals,
min: value.min,
max: value.max,
show: value.show,
}, {emitEvent: false}
);
this.updateValidators();
this.cd.markForCheck();
}
editAxis($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 = {
yAxisSettings: deepClone(this.modelValue),
advanced: this.advanced
};
const yAxisSettingsPanelPopover = this.popoverService.displayPopover(trigger, this.renderer,
this.viewContainerRef, TimeSeriesChartYAxisSettingsPanelComponent, ['leftOnly', 'leftTopOnly', 'leftBottomOnly'], true, null,
ctx,
{},
{}, {}, true);
yAxisSettingsPanelPopover.tbComponentRef.instance.popover = yAxisSettingsPanelPopover;
yAxisSettingsPanelPopover.tbComponentRef.instance.yAxisSettingsApplied.subscribe((yAxisSettings) => {
yAxisSettingsPanelPopover.hide();
this.modelValue = {...this.modelValue, ...yAxisSettings};
this.axisFormGroup.patchValue(
{
label: this.modelValue.label,
position: this.modelValue.position,
units: this.modelValue.units,
decimals: this.modelValue.decimals,
min: this.modelValue.min,
max: this.modelValue.max,
show: this.modelValue.show
},
{emitEvent: false});
this.updateValidators();
this.propagateChange(this.modelValue);
});
}
}
private updateValidators() {
const show: boolean = this.axisFormGroup.get('show').value;
if (show) {
this.axisFormGroup.get('label').enable({emitEvent: false});
this.axisFormGroup.get('position').enable({emitEvent: false});
this.axisFormGroup.get('units').enable({emitEvent: false});
this.axisFormGroup.get('decimals').enable({emitEvent: false});
} else {
this.axisFormGroup.get('label').disable({emitEvent: false});
this.axisFormGroup.get('position').disable({emitEvent: false});
this.axisFormGroup.get('units').disable({emitEvent: false});
this.axisFormGroup.get('decimals').disable({emitEvent: false});
}
}
private updateModel() {
const value = this.axisFormGroup.value;
this.modelValue.label = value.label;
this.modelValue.position = value.position;
this.modelValue.units = value.units;
this.modelValue.decimals = value.decimals;
this.modelValue.min = value.min;
this.modelValue.max = value.max;
this.modelValue.show = value.show;
this.propagateChange(this.modelValue);
}
}

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

@ -0,0 +1,43 @@
<!--
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.
-->
<div class="tb-y-axis-settings-panel" [formGroup]="yAxisSettingsFormGroup">
<div class="tb-y-axis-settings-title">{{ 'widgets.time-series-chart.axis.y-axis-settings' | translate }}</div>
<div class="tb-y-axis-settings-panel-content">
<tb-time-series-chart-axis-settings
formControlName="yAxis"
alwaysExpanded
[advanced]="advanced"
axisType="yAxis">
</tb-time-series-chart-axis-settings>
</div>
<div class="tb-y-axis-settings-panel-buttons">
<button mat-button
color="primary"
type="button"
(click)="cancel()">
{{ 'action.cancel' | translate }}
</button>
<button mat-raised-button
color="primary"
type="button"
(click)="applyYAxisSettings()"
[disabled]="yAxisSettingsFormGroup.invalid || !yAxisSettingsFormGroup.dirty">
{{ 'action.apply' | translate }}
</button>
</div>
</div>

49
ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-y-axis-settings-panel.component.scss

@ -0,0 +1,49 @@
/**
* 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 '../../../../../../../../../scss/constants';
.tb-y-axis-settings-panel {
width: 530px;
display: flex;
flex-direction: column;
gap: 16px;
@media #{$mat-lt-md} {
width: 90vw;
}
.tb-y-axis-settings-panel-content {
display: flex;
flex-direction: column;
gap: 16px;
overflow: auto;
margin: -10px;
padding: 10px;
}
.tb-y-axis-settings-title {
font-size: 16px;
font-weight: 500;
line-height: 24px;
letter-spacing: 0.25px;
color: rgba(0, 0, 0, 0.87);
}
.tb-y-axis-settings-panel-buttons {
height: 40px;
display: flex;
flex-direction: row;
gap: 16px;
justify-content: flex-end;
align-items: flex-end;
}
}

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

@ -0,0 +1,66 @@
///
/// 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, EventEmitter, Input, OnInit, Output, ViewEncapsulation } from '@angular/core';
import { TbPopoverComponent } from '@shared/components/popover.component';
import { UntypedFormBuilder, UntypedFormGroup } from '@angular/forms';
import { TimeSeriesChartYAxisSettings } from '@home/components/widget/lib/chart/time-series-chart.models';
import { coerceBoolean } from '@shared/decorators/coercion';
@Component({
selector: 'tb-time-series-chart-y-axis-settings-panel',
templateUrl: './time-series-chart-y-axis-settings-panel.component.html',
providers: [],
styleUrls: ['./time-series-chart-y-axis-settings-panel.component.scss'],
encapsulation: ViewEncapsulation.None
})
export class TimeSeriesChartYAxisSettingsPanelComponent implements OnInit {
@Input()
yAxisSettings: TimeSeriesChartYAxisSettings;
@Input()
@coerceBoolean()
advanced = false;
@Input()
popover: TbPopoverComponent<TimeSeriesChartYAxisSettingsPanelComponent>;
@Output()
yAxisSettingsApplied = new EventEmitter<TimeSeriesChartYAxisSettings>();
yAxisSettingsFormGroup: UntypedFormGroup;
constructor(private fb: UntypedFormBuilder) {
}
ngOnInit(): void {
this.yAxisSettingsFormGroup = this.fb.group(
{
yAxis: [this.yAxisSettings, []]
}
);
}
cancel() {
this.popover?.hide();
}
applyYAxisSettings() {
const yAxisSettings = this.yAxisSettingsFormGroup.get('yAxis').getRawValue();
this.yAxisSettingsApplied.emit(yAxisSettings);
}
}

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

@ -109,6 +109,15 @@ import {
import { import {
TimeSeriesNoAggregationBarWidthSettingsComponent TimeSeriesNoAggregationBarWidthSettingsComponent
} from '@home/components/widget/lib/settings/common/chart/time-series-no-aggregation-bar-width-settings.component'; } from '@home/components/widget/lib/settings/common/chart/time-series-no-aggregation-bar-width-settings.component';
import {
TimeSeriesChartYAxesPanelComponent
} from '@home/components/widget/lib/settings/common/chart/time-series-chart-y-axes-panel.component';
import {
TimeSeriesChartYAxisRowComponent
} from '@home/components/widget/lib/settings/common/chart/time-series-chart-y-axis-row.component';
import {
TimeSeriesChartYAxisSettingsPanelComponent
} from '@home/components/widget/lib/settings/common/chart/time-series-chart-y-axis-settings-panel.component';
@NgModule({ @NgModule({
declarations: [ declarations: [
@ -150,6 +159,9 @@ import {
TimeSeriesChartThresholdRowComponent, TimeSeriesChartThresholdRowComponent,
TimeSeriesChartThresholdSettingsPanelComponent, TimeSeriesChartThresholdSettingsPanelComponent,
TimeSeriesNoAggregationBarWidthSettingsComponent, TimeSeriesNoAggregationBarWidthSettingsComponent,
TimeSeriesChartYAxesPanelComponent,
TimeSeriesChartYAxisRowComponent,
TimeSeriesChartYAxisSettingsPanelComponent,
DataKeyInputComponent, DataKeyInputComponent,
EntityAliasInputComponent EntityAliasInputComponent
], ],
@ -197,6 +209,9 @@ import {
TimeSeriesChartThresholdRowComponent, TimeSeriesChartThresholdRowComponent,
TimeSeriesChartThresholdSettingsPanelComponent, TimeSeriesChartThresholdSettingsPanelComponent,
TimeSeriesNoAggregationBarWidthSettingsComponent, TimeSeriesNoAggregationBarWidthSettingsComponent,
TimeSeriesChartYAxesPanelComponent,
TimeSeriesChartYAxisRowComponent,
TimeSeriesChartYAxisSettingsPanelComponent,
DataKeyInputComponent, DataKeyInputComponent,
EntityAliasInputComponent EntityAliasInputComponent
], ],

19
ui-ngx/src/app/shared/models/common.ts

@ -0,0 +1,19 @@
///
/// 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.
///
export type DeepPartial<T> = T extends object ? {
[P in keyof T]?: DeepPartial<T[P]>;
} : T;

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

@ -6609,6 +6609,7 @@
"stack-mode": "Stack mode", "stack-mode": "Stack mode",
"stack-mode-hint": "Stacks series on the chart. The series with the same unit would be put on top of each other.", "stack-mode-hint": "Stacks series on the chart. The series with the same unit would be put on top of each other.",
"axes": "Axes", "axes": "Axes",
"y-axes": "Y axes",
"line-type": "Line type", "line-type": "Line type",
"line-type-solid": "Solid", "line-type-solid": "Solid",
"line-type-dashed": "Dashed", "line-type-dashed": "Dashed",
@ -6670,6 +6671,9 @@
"axes": "Axes", "axes": "Axes",
"x-axis": "X axis", "x-axis": "X axis",
"y-axis": "Y axis", "y-axis": "Y axis",
"y-axis-settings": "Y axis settings",
"remove-y-axis": "Remove Y axis",
"id": "Id",
"label": "Label", "label": "Label",
"position": "Position", "position": "Position",
"position-left": "Left", "position-left": "Left",
@ -6686,7 +6690,11 @@
"scale": "Scale", "scale": "Scale",
"scale-min": "min", "scale-min": "min",
"scale-max": "max", "scale-max": "max",
"scale-auto": "Auto" "scale-auto": "Auto",
"min": "Min",
"max": "Max",
"show": "Show",
"add-y-axis": "Add Y axis"
}, },
"series": { "series": {
"legend-settings": "Legend settings", "legend-settings": "Legend settings",

Loading…
Cancel
Save