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

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
} from '@home/components/widget/lib/chart/time-series-chart-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({
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;
EChartsTooltipTrigger = EChartsTooltipTrigger;
@ -86,6 +95,15 @@ export class TimeSeriesChartBasicConfigComponent extends BasicWidgetConfigCompon
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 {
return this.timeSeriesChartWidgetConfigForm;
}
@ -109,6 +127,8 @@ export class TimeSeriesChartBasicConfigComponent extends BasicWidgetConfigCompon
this.timeSeriesChartWidgetConfigForm = this.fb.group({
timewindowConfig: [getTimewindowConfig(configData.config), []],
datasources: [configData.config.datasources, []],
yAxes: [settings.yAxes, []],
series: [this.getSeries(configData.config.datasources), []],
thresholds: [settings.thresholds, []],
@ -126,7 +146,6 @@ export class TimeSeriesChartBasicConfigComponent extends BasicWidgetConfigCompon
dataZoom: [settings.dataZoom, []],
stack: [settings.stack, []],
yAxis: [settings.yAxis, []],
xAxis: [settings.xAxis, []],
noAggregationBarWidthSettings: [settings.noAggregationBarWidthSettings, []],
@ -180,7 +199,7 @@ export class TimeSeriesChartBasicConfigComponent extends BasicWidgetConfigCompon
this.widgetConfig.config.settings.dataZoom = config.dataZoom;
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.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[] {
const buttons: string[] = [];
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">
<input matInput formControlName="label" placeholder="{{ 'widget-config.set' | translate }}">
</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">
<tb-color-input asBoxInput
formControlName="color">
@ -54,21 +78,6 @@
<div *ngIf="singleRow" matSuffix fxHide.lt-md translate>widget-config.decimals-suffix</div>
</mat-form-field>
</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 fxHide.lt-lg class="tb-settings-button">
<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;
}
.tb-y-axis-field {
width: 100px;
min-width: 100px;
.mat-mdc-form-field {
width: 100%;
}
}
.tb-time-series-type-field {
width: 60px;
min-width: 60px;
@ -72,7 +80,16 @@
}
}
.tb-y-axis-field {
@media #{$mat-md} {
display: none;
}
}
.tb-time-series-type-field {
@media #{$mat-xs} {
display: none;
}
@media #{$mat-md} {
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,
EventEmitter,
forwardRef,
Input,
Input, OnChanges,
OnInit,
Output,
Output, SimpleChanges,
ViewEncapsulation
} from '@angular/core';
import {
@ -60,7 +60,7 @@ import {
TimeSeriesChartSeriesType,
timeSeriesChartSeriesTypeIcons,
timeSeriesChartSeriesTypes,
timeSeriesChartSeriesTypeTranslations
timeSeriesChartSeriesTypeTranslations, TimeSeriesChartYAxisId
} from '@home/components/widget/lib/chart/time-series-chart.models';
export const dataKeyValid = (key: DataKey): boolean => !!key && !!key.type && !!key.name;
@ -88,7 +88,7 @@ export const dataKeyRowValidator = (control: AbstractControl): ValidationErrors
],
encapsulation: ViewEncapsulation.None
})
export class DataKeyRowComponent implements ControlValueAccessor, OnInit {
export class DataKeyRowComponent implements ControlValueAccessor, OnInit, OnChanges {
timeSeriesChartSeriesTypes = timeSeriesChartSeriesTypes;
timeSeriesChartSeriesTypeTranslations = timeSeriesChartSeriesTypeTranslations;
@ -138,10 +138,17 @@ export class DataKeyRowComponent implements ControlValueAccessor, OnInit {
@coerceBoolean()
hideDecimals = false;
@Input()
@coerceBoolean()
timeSeriesChart = false;
@Input()
@coerceBoolean()
showTimeSeriesType = false;
@Input()
yAxisIds: TimeSeriesChartYAxisId[];
@Input()
@coerceBoolean()
singleRow = true;
@ -237,7 +244,8 @@ export class DataKeyRowComponent implements ControlValueAccessor, OnInit {
if (this.hasAdditionalLatestDataKeys) {
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));
}
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 {
this.propagateChange = fn;
}
@ -278,10 +300,12 @@ export class DataKeyRowComponent implements ControlValueAccessor, OnInit {
latest: (value as any)?.latest
}, {emitEvent: false});
}
if (this.showTimeSeriesType) {
if (this.timeSeriesChart) {
const settings = value?.settings as TimeSeriesChartKeySettings;
const yAxis = settings?.yAxisId || 'default';
const timeSeriesType = settings?.type || TimeSeriesChartSeriesType.line;
this.keyRowFormGroup.patchValue({
yAxis,
timeSeriesType
}, {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('units').patchValue(this.modelValue.units, {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.keyFormControl.patchValue(deepClone(this.modelValue), {emitEvent: false});
@ -335,7 +360,8 @@ export class DataKeyRowComponent implements ControlValueAccessor, OnInit {
if (!this.keyRowFormGroup.get('label').value) {
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});
}
return key;
@ -346,8 +372,10 @@ export class DataKeyRowComponent implements ControlValueAccessor, OnInit {
if (this.modelValue !== null) {
const value: DataKey = this.keyRowFormGroup.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;
delete (this.modelValue as any).yAxis;
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 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 *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="!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="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>
<div *ngIf="keysFormArray().controls.length; else noKeys" class="tb-form-table-body tb-drop-list"
@ -44,7 +45,9 @@
[hasAdditionalLatestDataKeys]="hasAdditionalLatestDataKeys"
[hideDataKeyColor]="hideDataKeyColor"
[hideDecimals]="hideDecimals"
[timeSeriesChart]="timeSeriesChart"
[showTimeSeriesType]="showTimeSeriesType"
[yAxisIds]="yAxisIds"
[hideUnits]="hideUnits"
[hideDataKeyDecimals]="hideDataKeyDecimals"
[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;
}
&.tb-y-axis-header {
width: 100px;
min-width: 100px;
}
&.tb-time-series-type-header {
width: 60px;
min-width: 60px;
@ -70,7 +75,17 @@
display: block;
}
}
&.tb-y-axis-header {
@media #{$mat-md} {
display: none;
}
}
&.tb-time-series-type-header {
@media #{$mat-xs} {
display: none;
}
@media #{$mat-md} {
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 { DataKeysCallbacks, DataKeySettingsFunction } from '@home/components/widget/config/data-keys.component.models';
import { coerceBoolean } from '@shared/decorators/coercion';
import { TimeSeriesChartYAxisId } from '@home/components/widget/lib/chart/time-series-chart.models';
@Component({
selector: 'tb-data-keys-panel',
@ -119,10 +120,17 @@ export class DataKeysPanelComponent implements ControlValueAccessor, OnInit, OnC
@coerceBoolean()
hideSourceSelection = false;
@Input()
@coerceBoolean()
timeSeriesChart = false;
@Input()
@coerceBoolean()
showTimeSeriesType = false;
@Input()
yAxisIds: TimeSeriesChartYAxisId[];
dataKeyType: DataKeyType;
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 { TbTimeSeriesChart } from '@home/components/widget/lib/chart/time-series-chart';
import {
TimeSeriesChartAxisSettings,
TimeSeriesChartKeySettings,
TimeSeriesChartSeriesType,
TimeSeriesChartSettings
TimeSeriesChartSettings, TimeSeriesChartYAxisSettings
} from '@home/components/widget/lib/chart/time-series-chart.models';
import { DeepPartial } from '@shared/models/common';
const valuesLayoutHeight = 66;
const valuesLayoutVerticalPadding = 16;
@ -172,25 +174,27 @@ export class AggregatedValueCardWidgetComponent implements OnInit, AfterViewInit
ngAfterViewInit(): void {
if (this.showChart && this.ctx.datasources?.length) {
const settings: TimeSeriesChartSettings = {
const settings: DeepPartial<TimeSeriesChartSettings> = {
dataZoom: false,
xAxis: {
show: false
},
yAxis: {
show: true,
showLine: false,
showTicks: false,
showTickLabels: false,
showSplitLines: true,
min: 'dataMin',
max: 'dataMax',
intervalCalculator:
'var scale = axis.scale; return !scale.isBlank() ? ((scale.getExtent()[1] - scale.getExtent()[0]) / 2) : undefined;'
yAxes: {
default: {
show: true,
showLine: false,
showTicks: false,
showTickLabels: false,
showSplitLines: true,
min: 'dataMin',
max: 'dataMax',
intervalCalculator:
'var scale = axis.scale; return !scale.isBlank() ? ((scale.getExtent()[1] - scale.getExtent()[0]) / 2) : undefined;'
}
},
tooltipDateInterval: false,
tooltipDateFormat: simpleDateFormat('dd MMM yyyy HH:mm:ss')
} as TimeSeriesChartSettings;
};
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,
TimeSeriesChartSettings
} from '@home/components/widget/lib/chart/time-series-chart.models';
import { DeepPartial } from '@shared/models/common';
const layoutHeight = 56;
const valueRelativeWidth = 0.35;
@ -162,17 +163,19 @@ export class ValueChartCardWidgetComponent implements OnInit, AfterViewInit, OnD
}
public ngAfterViewInit() {
const settings: TimeSeriesChartSettings = {
const settings: DeepPartial<TimeSeriesChartSettings> = {
dataZoom: false,
xAxis: {
show: false
},
yAxis: {
show: false
yAxes: {
default: {
show: false,
}
},
tooltipDateInterval: false,
tooltipDateFormat: simpleDateFormat('dd MMM yyyy HH:mm:ss')
} as TimeSeriesChartSettings;
};
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 { XAXisOption, YAXisOption } from 'echarts/types/dist/shared';
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 tinycolor from 'tinycolor2';
import Axis2D from 'echarts/types/src/coord/cartesian/Axis2D';
@ -289,13 +297,82 @@ export interface TimeSeriesChartAxisSettings {
splitLinesColor: string;
}
export type TimeSeriesChartYAxisId = 'default' | string;
export interface TimeSeriesChartYAxisSettings extends TimeSeriesChartAxisSettings {
id?: TimeSeriesChartYAxisId;
order?: number;
units?: string;
decimals?: number;
min?: number | string;
max?: number | string;
intervalCalculator?: 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 {
type: TimeSeriesChartThresholdType;
value?: number;
@ -304,6 +381,7 @@ export interface TimeSeriesChartThreshold {
entityAlias?: string;
entityKey?: string;
entityKeyType?: DataKeyType.attribute | DataKeyType.timeseries;
yAxisId: TimeSeriesChartYAxisId;
units?: string;
decimals?: number;
lineColor: string;
@ -320,7 +398,7 @@ export interface TimeSeriesChartThreshold {
}
export const timeSeriesChartThresholdValid = (threshold: TimeSeriesChartThreshold): boolean => {
if (!threshold.type) {
if (!threshold.type || !threshold.yAxisId) {
return false;
}
switch (threshold.type) {
@ -355,7 +433,8 @@ export const timeSeriesChartThresholdValidator = (control: AbstractControl): Val
export const timeSeriesChartThresholdDefaultSettings: TimeSeriesChartThreshold = {
type: TimeSeriesChartThresholdType.constant,
units: '',
yAxisId: 'default',
units: null,
decimals: 0,
lineColor: timeSeriesChartColorScheme['threshold.line'].light,
lineType: TimeSeriesChartLineType.solid,
@ -404,13 +483,61 @@ export interface TimeSeriesChartNoAggregationBarWidthSettings {
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 {
thresholds: TimeSeriesChartThreshold[];
darkMode: boolean;
dataZoom: boolean;
stack: boolean;
yAxis: TimeSeriesChartYAxisSettings;
yAxes: TimeSeriesChartYAxes;
xAxis: TimeSeriesChartAxisSettings;
animation: TimeSeriesChartAnimationSettings;
noAggregationBarWidthSettings: TimeSeriesChartNoAggregationBarWidthSettings;
}
@ -419,36 +546,10 @@ export const timeSeriesChartDefaultSettings: TimeSeriesChartSettings = {
darkMode: false,
dataZoom: true,
stack: false,
yAxis: {
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
yAxes: {
default: mergeDeep({} as TimeSeriesChartYAxisSettings,
defaultTimeSeriesChartYAxisSettings,
{ id: 'default', order: 0 } as TimeSeriesChartYAxisSettings)
},
xAxis: {
show: true,
@ -480,6 +581,16 @@ export const timeSeriesChartDefaultSettings: TimeSeriesChartSettings = {
showSplitLines: true,
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: {
strategy: TimeSeriesChartNoAggregationBarWidthStrategy.group,
groupWidth: {
@ -558,6 +669,7 @@ export interface BarSeriesSettings {
}
export interface TimeSeriesChartKeySettings {
yAxisId: TimeSeriesChartYAxisId;
showInLegend: boolean;
dataHiddenByDefault: boolean;
type: TimeSeriesChartSeriesType;
@ -566,6 +678,7 @@ export interface TimeSeriesChartKeySettings {
}
export const timeSeriesChartKeyDefaultSettings: TimeSeriesChartKeySettings = {
yAxisId: 'default',
showInLegend: true,
dataHiddenByDefault: false,
type: TimeSeriesChartSeriesType.line,
@ -626,6 +739,7 @@ export const timeSeriesChartKeyDefaultSettings: TimeSeriesChartKeySettings = {
};
export interface TimeSeriesChartDataItem extends EChartsSeriesItem {
yAxisId: TimeSeriesChartYAxisId;
yAxisIndex: number;
option?: LineSeriesOption | CustomSeriesOption;
barRenderContext?: BarRenderContext;
@ -635,6 +749,7 @@ type TimeSeriesChartThresholdValue = number | string | (number | string)[];
export interface TimeSeriesChartThresholdItem {
id: string;
yAxisId: TimeSeriesChartYAxisId;
yAxisIndex: number;
latestDataKey?: DataKey;
units?: string;
@ -646,14 +761,16 @@ export interface TimeSeriesChartThresholdItem {
export interface TimeSeriesChartYAxis {
id: string;
units: string;
decimals: number;
settings: TimeSeriesChartYAxisSettings;
option: YAXisOption & ValueAxisBaseOption;
intervalCalculator?: (axis: Axis2D) => number;
ticksFormatter?: (value: any) => string;
}
export const createTimeSeriesYAxis = (axisId: string, units: string,
decimals: number, settings: TimeSeriesChartYAxisSettings,
export const createTimeSeriesYAxis = (units: string,
decimals: number,
settings: TimeSeriesChartYAxisSettings,
darkMode: boolean): TimeSeriesChartYAxis => {
const yAxisTickLabelStyle = createChartTextStyle(settings.tickLabelFont,
settings.tickLabelColor, darkMode, 'axis.tickLabel');
@ -664,13 +781,14 @@ export const createTimeSeriesYAxis = (axisId: string, units: string,
ticksFormatter = parseFunction(settings.ticksFormatter, ['value']);
}
const yAxis: TimeSeriesChartYAxis = {
id: axisId,
units,
id: settings.id,
decimals,
settings,
option: {
show: settings.show,
type: 'value',
position: settings.position,
id: axisId,
id: settings.id,
offset: 0,
alignTicks: true,
scale: true,
@ -843,13 +961,11 @@ const generateChartThresholds = (thresholdItems: TimeSeriesChartThresholdItem[],
id: item.id,
dataGroupId: item.id,
yAxisIndex: item.yAxisIndex,
animation: true,
data: [],
tooltip: {
show: false
},
markLine: {
animation: true,
lineStyle: {
width: item.settings.lineWidth,
color: prepareChartThemeColor(item.settings.lineColor, darkMode, 'threshold.line'),
@ -956,16 +1072,19 @@ const generateChartSeries = (dataItems: TimeSeriesChartDataItem[],
};
export const updateDarkMode = (options: EChartsOption, settings: TimeSeriesChartSettings,
yAxisList: TimeSeriesChartYAxis[],
dataItems: TimeSeriesChartDataItem[], thresholdDataItems: TimeSeriesChartThresholdItem[],
darkMode: boolean): EChartsOption => {
options.darkMode = darkMode;
if (Array.isArray(options.yAxis)) {
for (const yAxis of options.yAxis) {
yAxis.nameTextStyle.color = prepareChartThemeColor(settings.yAxis.labelColor, darkMode, 'axis.label');
yAxis.axisLabel.color = prepareChartThemeColor(settings.yAxis.tickLabelColor, darkMode, 'axis.tickLabel');
yAxis.axisLine.lineStyle.color = prepareChartThemeColor(settings.yAxis.lineColor, darkMode, 'axis.line');
yAxis.axisTick.lineStyle.color = prepareChartThemeColor(settings.yAxis.ticksColor, darkMode, 'axis.ticks');
yAxis.splitLine.lineStyle.color = prepareChartThemeColor(settings.yAxis.splitLinesColor, darkMode, 'axis.splitLine');
for (let i = 0; i < options.yAxis.length; i++) {
const yAxis = options.yAxis[i];
const yAxisSettings = yAxisList[i].settings;
yAxis.nameTextStyle.color = prepareChartThemeColor(yAxisSettings.labelColor, darkMode, 'axis.label');
yAxis.axisLabel.color = prepareChartThemeColor(yAxisSettings.tickLabelColor, darkMode, 'axis.tickLabel');
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)) {
@ -1032,7 +1151,6 @@ const createTimeSeriesChartSeries = (item: TimeSeriesChartDataItem,
emphasis: {
focus: 'series'
},
animation: true,
dimensions: [
{name: 'intervalStart', 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,
calculateThresholdsOffset,
createTimeSeriesXAxisOption,
createTimeSeriesYAxis,
createTimeSeriesYAxis, defaultTimeSeriesChartYAxisSettings,
generateChartData,
parseThresholdData,
SeriesLabelPosition,
@ -29,11 +29,11 @@ import {
TimeSeriesChartKeySettings,
TimeSeriesChartSeriesType,
TimeSeriesChartSettings,
TimeSeriesChartShape,
TimeSeriesChartShape, TimeSeriesChartThreshold, timeSeriesChartThresholdDefaultSettings,
TimeSeriesChartThresholdItem,
TimeSeriesChartThresholdType,
TimeSeriesChartType,
TimeSeriesChartYAxis,
TimeSeriesChartYAxis, TimeSeriesChartYAxisId, TimeSeriesChartYAxisSettings,
updateDarkMode
} from '@home/components/widget/lib/chart/time-series-chart.models';
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 { WidgetSubscriptionOptions } from '@core/api/widget-api.models';
import { DataKeySettingsFunction } from '@home/components/widget/config/data-keys.component.models';
import { DeepPartial } from '@shared/models/common';
export class TbTimeSeriesChart {
@ -94,9 +95,11 @@ export class TbTimeSeriesChart {
private readonly shapeResize$: ResizeObserver;
private readonly settings: TimeSeriesChartSettings;
private yAxisList: TimeSeriesChartYAxis[] = [];
private dataItems: TimeSeriesChartDataItem[] = [];
private thresholdItems: TimeSeriesChartThresholdItem[] = [];
private yAxisList: TimeSeriesChartYAxis[] = [];
private timeSeriesChart: ECharts;
private timeSeriesChartOptions: EChartsOption;
@ -120,13 +123,16 @@ export class TbTimeSeriesChart {
yMax$ = this.yMaxSubject.asObservable();
constructor(private ctx: WidgetContext,
private readonly settings: TimeSeriesChartSettings,
private readonly inputSettings: DeepPartial<TimeSeriesChartSettings>,
private chartElement: HTMLElement,
private renderer: Renderer2,
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.setupYAxes();
this.setupData();
this.setupThresholds();
if (this.settings.showTooltip && this.settings.tooltipShowDate) {
@ -258,7 +264,8 @@ export class TbTimeSeriesChart {
if (this.darkMode !== darkMode) {
this.darkMode = darkMode;
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.timeSeriesChart.setOption(this.timeSeriesChartOptions);
}
@ -287,11 +294,16 @@ export class TbTimeSeriesChart {
const units = dataKey.units && dataKey.units.length ? dataKey.units : this.ctx.units;
const decimals = isDefinedAndNotNull(dataKey.decimals) ? dataKey.decimals :
(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({
id: this.nextComponentId(),
units,
decimals,
yAxisIndex: this.getYAxis(units, decimals),
yAxisId,
yAxisIndex: this.getYAxisIndex(yAxisId),
dataKey,
data: namedData,
enabled: !keySettings.dataHiddenByDefault
@ -303,7 +315,9 @@ export class TbTimeSeriesChart {
private setupThresholds(): void {
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 entityDataKey: DataKey = null;
let value = null;
@ -351,11 +365,16 @@ export class TbTimeSeriesChart {
const units = threshold.units && threshold.units.length ? threshold.units : this.ctx.units;
const decimals = isDefinedAndNotNull(threshold.decimals) ? threshold.decimals :
(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 = {
id: this.nextComponentId(),
units,
decimals,
yAxisIndex: this.getYAxis(units, decimals),
yAxisId,
yAxisIndex: this.getYAxisIndex(yAxisId),
value,
latestDataKey,
settings: threshold
@ -368,17 +387,32 @@ export class TbTimeSeriesChart {
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 {
return (this.componentIndexCounter++) + '';
}
private getYAxis(units: string, decimals: number): number {
let yAxisIndex = this.yAxisList.findIndex(axis => axis.units === units);
private getYAxisIndex(id: TimeSeriesChartYAxisId): number {
let yAxisIndex = this.yAxisList.findIndex(axis => axis.id === id);
if (yAxisIndex === -1) {
const yAxisId = this.yAxisList.length + '';
const yAxis = createTimeSeriesYAxis(yAxisId, units, decimals, this.settings.yAxis, this.darkMode);
this.yAxisList.push(yAxis);
yAxisIndex = this.yAxisList.length - 1;
yAxisIndex = this.yAxisList.findIndex(axis => axis.id === 'default');
if (yAxisIndex === -1 && this.yAxisList.length) {
yAxisIndex = 0;
}
}
return yAxisIndex;
}
@ -460,7 +494,15 @@ export class TbTimeSeriesChart {
realtime: true,
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;
@ -588,7 +630,7 @@ export class TbTimeSeriesChart {
result.offset += 5;
}
width = newWidth;
const showLine = !!width && this.settings.yAxis.showLine;
const showLine = !!width && yAxis.settings.showLine;
if (yAxis.option.axisLine.show !== showLine) {
yAxis.option.axisLine.show = showLine;
result.changed = true;
@ -597,7 +639,7 @@ export class TbTimeSeriesChart {
yAxis.option.offset = result.offset;
result.changed = true;
}
if (this.settings.yAxis.label) {
if (yAxis.settings.label) {
if (!width) {
if (yAxis.option.name) {
yAxis.option.name = null;
@ -605,7 +647,7 @@ export class TbTimeSeriesChart {
}
} else {
if (!yAxis.option.name) {
yAxis.option.name = this.settings.yAxis.label;
yAxis.option.name = yAxis.settings.label;
result.changed = true;
}
const nameGap = width;
@ -613,7 +655,7 @@ export class TbTimeSeriesChart {
yAxis.option.nameGap = nameGap;
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;
}
}
@ -623,15 +665,25 @@ export class TbTimeSeriesChart {
}
private scaleYAxis(yAxis: TimeSeriesChartYAxis): boolean {
const yAxisIndex = this.yAxisList.indexOf(yAxis);
const axisBarDataItems = this.dataItems.filter(d => d.yAxisIndex === yAxisIndex && d.enabled &&
const axisBarDataItems = this.dataItems.filter(d => d.yAxisId === yAxis.id && d.enabled &&
d.data.length && d.dataKey.settings.type === TimeSeriesChartSeriesType.bar);
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 {
let changed = false;
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) {
const axis = getYAxis(this.timeSeriesChart, yAxis.id);
if (axis) {
@ -649,8 +701,10 @@ export class TbTimeSeriesChart {
}
private minTopOffset(): number {
const showTickLabels =
!!this.yAxisList.find(yAxis => yAxis.settings.show && yAxis.settings.showTickLabels);
return (this.topPointLabels) ? 20 :
((this.settings.yAxis.show && this.settings.yAxis.showTickLabels) ? 10 : 5);
(showTickLabels ? 10 : 5);
}
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>
</mat-slide-toggle>
</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 *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>

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,
TimeSeriesChartSeriesType,
timeSeriesChartSeriesTypes,
timeSeriesChartSeriesTypeTranslations, TimeSeriesChartType, timeSeriesChartTypeTranslations
timeSeriesChartSeriesTypeTranslations, TimeSeriesChartType, timeSeriesChartTypeTranslations, TimeSeriesChartYAxisId
} from '@home/components/widget/lib/chart/time-series-chart.models';
import { WidgetConfigComponentData } from '@home/models/widget-component.models';
import { TimeSeriesChartWidgetSettings } from '@home/components/widget/lib/chart/time-series-chart-widget.models';
@Component({
selector: 'tb-time-series-chart-key-settings',
@ -50,6 +51,8 @@ export class TimeSeriesChartKeySettingsComponent extends WidgetSettingsComponent
chartType = TimeSeriesChartType.default;
yAxisIds: TimeSeriesChartYAxisId[];
constructor(protected store: Store<AppState>,
private fb: UntypedFormBuilder) {
super(store);
@ -64,6 +67,8 @@ export class TimeSeriesChartKeySettingsComponent extends WidgetSettingsComponent
if (isDefinedAndNotNull(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 {
@ -73,7 +78,12 @@ export class TimeSeriesChartKeySettingsComponent extends WidgetSettingsComponent
protected onSettingsSet(settings: WidgetSettings) {
const seriesSettings = settings as TimeSeriesChartKeySettings;
let yAxisId = seriesSettings.yAxisId;
if (!this.yAxisIds.includes(yAxisId)) {
yAxisId = 'default';
}
this.timeSeriesChartKeySettingsForm = this.fb.group({
yAxisId: [yAxisId, []],
showInLegend: [seriesSettings.showInLegend, []],
dataHiddenByDefault: [seriesSettings.dataHiddenByDefault, []],
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">
<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
formControlName="thresholds"
[aliasController]="aliasController"
[dataKeyCallbacks]="dataKeyCallbacks"
[datasource]="datasource"
[widgetConfig]="widgetConfig?.config">
[widgetConfig]="widgetConfig?.config"
[yAxisIds]="yAxisIds">
</tb-time-series-chart-thresholds-panel>
<div class="tb-form-panel">
<div class="tb-form-panel-title" translate>widgets.time-series-chart.chart-style</div>
@ -38,12 +44,7 @@
</mat-slide-toggle>
</div>
<div class="tb-form-panel">
<div class="tb-form-panel-title" translate>widgets.time-series-chart.axis.axes</div>
<tb-time-series-chart-axis-settings
formControlName="yAxis"
advanced
axisType="yAxis">
</tb-time-series-chart-axis-settings>
<div class="tb-form-panel-title" translate>widgets.time-series-chart.axis.x-axis</div>
<tb-time-series-chart-axis-settings
formControlName="xAxis"
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 {
DataKey,
Datasource,
legendPositions,
legendPositionTranslationMap,
@ -32,7 +33,11 @@ import {
timeSeriesChartWidgetDefaultSettings,
TimeSeriesChartWidgetSettings
} 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';
@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;
EChartsTooltipTrigger = EChartsTooltipTrigger;
@ -73,6 +83,15 @@ export class TimeSeriesChartWidgetSettingsComponent extends WidgetSettingsCompon
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 {
return this.timeSeriesChartWidgetSettingsForm;
}
@ -91,12 +110,12 @@ export class TimeSeriesChartWidgetSettingsComponent extends WidgetSettingsCompon
protected onSettingsSet(settings: WidgetSettings) {
this.timeSeriesChartWidgetSettingsForm = this.fb.group({
yAxes: [settings.yAxes, []],
thresholds: [settings.thresholds, []],
dataZoom: [settings.dataZoom, []],
stack: [settings.stack, []],
yAxis: [settings.yAxis, []],
xAxis: [settings.xAxis, []],
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 {
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">
<div class="tb-form-panel stroked tb-slide-toggle">
<mat-expansion-panel class="tb-settings" [(expanded)]="settingsExpanded"
[disabled]="!axisSettingsFormGroup.get('show').value">
<mat-expansion-panel class="tb-settings" [(expanded)]="alwaysExpanded || settingsExpanded"
[disabled]="alwaysExpanded || !axisSettingsFormGroup.get('show').value">
<mat-expansion-panel-header fxLayout="row wrap">
<mat-panel-title>
<mat-slide-toggle class="mat-slide" formControlName="show" (click)="$event.stopPropagation()"
fxLayoutAlign="center">
{{ axisTitle | translate }}
{{ 'widgets.time-series-chart.axis.show' | translate }}
</mat-slide-toggle>
</mat-panel-title>
</mat-expansion-panel-header>
@ -56,6 +56,18 @@
</mat-select>
</mat-form-field>
</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-row no-border space-between column-xs">
<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 { ControlValueAccessor, NG_VALUE_ACCESSOR, UntypedFormBuilder, UntypedFormGroup } from '@angular/forms';
import {
ControlValueAccessor,
NG_VALUE_ACCESSOR,
UntypedFormBuilder,
UntypedFormGroup,
Validators
} from '@angular/forms';
import {
AxisPosition,
timeSeriesAxisPositionTranslations,
@ -40,9 +46,11 @@ import { WidgetService } from '@core/http/widget.service';
})
export class TimeSeriesChartAxisSettingsComponent implements OnInit, ControlValueAccessor {
settingsExpanded = false;
@Input()
@coerceBoolean()
alwaysExpanded = false;
axisTitle: string;
settingsExpanded = false;
axisPositions: AxisPosition[];
@ -72,8 +80,6 @@ export class TimeSeriesChartAxisSettingsComponent implements OnInit, ControlValu
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] :
[AxisPosition.left, AxisPosition.right];
@ -94,6 +100,8 @@ export class TimeSeriesChartAxisSettingsComponent implements OnInit, ControlValu
splitLinesColor: [null, []]
});
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('min', 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">
</tb-data-key-input>
</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">
<tb-color-input asBoxInput
colorClearButton
@ -89,17 +98,14 @@
</mat-form-field>
</div>
<div class="tb-form-table-row-cell-buttons">
<div class="tb-settings-button">
<button *ngIf="modelValue"
type="button"
mat-icon-button
#matButton
(click)="editThreshold($event, matButton)"
matTooltip="{{ 'widgets.time-series-chart.threshold.threshold-settings' | translate }}"
matTooltipPosition="above">
<mat-icon>settings</mat-icon>
</button>
</div>
<button type="button"
mat-icon-button
#matButton
(click)="editThreshold($event, matButton)"
matTooltip="{{ 'widgets.time-series-chart.threshold.threshold-settings' | translate }}"
matTooltipPosition="above">
<mat-icon>settings</mat-icon>
</button>
<button type="button"
mat-icon-button
(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 {
display: flex;
flex-direction: row;
@ -77,6 +85,12 @@
min-width: 60px;
}
.tb-y-axis-field {
@media #{$mat-xs} {
display: none;
}
}
.tb-units-field, .tb-color-field, .tb-decimals-field {
display: none;
@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,
EventEmitter,
forwardRef,
Input,
Input, OnChanges,
OnInit,
Output,
Renderer2,
Renderer2, SimpleChanges,
ViewContainerRef,
ViewEncapsulation
} from '@angular/core';
@ -36,7 +36,7 @@ import {
} from '@angular/forms';
import {
TimeSeriesChartThreshold,
TimeSeriesChartThresholdType,
TimeSeriesChartThresholdType, TimeSeriesChartYAxisId,
timeSeriesThresholdTypes,
timeSeriesThresholdTypeTranslations
} from '@home/components/widget/lib/chart/time-series-chart.models';
@ -67,7 +67,7 @@ import {
],
encapsulation: ViewEncapsulation.None
})
export class TimeSeriesChartThresholdRowComponent implements ControlValueAccessor, OnInit {
export class TimeSeriesChartThresholdRowComponent implements ControlValueAccessor, OnInit, OnChanges {
DataKeyType = DataKeyType;
@ -98,6 +98,9 @@ export class TimeSeriesChartThresholdRowComponent implements ControlValueAccesso
@Input()
disabled: boolean;
@Input()
yAxisIds: TimeSeriesChartYAxisId[];
@Output()
thresholdRemoved = new EventEmitter();
@ -124,6 +127,7 @@ export class TimeSeriesChartThresholdRowComponent implements ControlValueAccesso
type: [null, []],
value: [null, [Validators.required]],
entityAlias: [null, [Validators.required]],
yAxisId: [null, [Validators.required]],
lineColor: [null, []],
units: [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 {
this.propagateChange = fn;
}
@ -170,6 +188,7 @@ export class TimeSeriesChartThresholdRowComponent implements ControlValueAccesso
type: value.type,
value: value.value,
entityAlias: value.entityAlias,
yAxisId: value.yAxisId,
lineColor: value.lineColor,
units: value.units,
decimals: value.decimals,
@ -200,7 +219,8 @@ export class TimeSeriesChartThresholdRowComponent implements ControlValueAccesso
} else {
const ctx: any = {
thresholdSettings: deepClone(this.modelValue),
widgetConfig: this.widgetConfig
widgetConfig: this.widgetConfig,
yAxisIds: this.yAxisIds
};
const thresholdSettingsPanelPopover = this.popoverService.displayPopover(trigger, this.renderer,
this.viewContainerRef, TimeSeriesChartThresholdSettingsPanelComponent, ['leftOnly', 'leftTopOnly', 'leftBottomOnly'], true, null,
@ -213,6 +233,7 @@ export class TimeSeriesChartThresholdRowComponent implements ControlValueAccesso
this.modelValue = {...this.modelValue, ...thresholdSettings};
this.thresholdFormGroup.patchValue(
{
yAxisId: this.modelValue.yAxisId,
units: this.modelValue.units,
decimals: this.modelValue.decimals,
lineColor: this.modelValue.lineColor
@ -248,6 +269,7 @@ export class TimeSeriesChartThresholdRowComponent implements ControlValueAccesso
this.modelValue.type = value.type;
this.modelValue.value = value.value;
this.modelValue.entityAlias = value.entityAlias;
this.modelValue.yAxisId = value.yAxisId;
this.modelValue.lineColor = value.lineColor;
this.modelValue.units = value.units;
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-title">{{ 'widgets.time-series-chart.threshold.threshold-settings' | translate }}</div>
<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 translate>widget-config.units-short</div>
<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,
timeSeriesChartShapes,
timeSeriesChartShapeTranslations,
TimeSeriesChartThreshold,
TimeSeriesChartThreshold, TimeSeriesChartYAxisId,
timeSeriesLineTypes,
timeSeriesLineTypeTranslations,
timeSeriesThresholdLabelPositions,
@ -60,6 +60,9 @@ export class TimeSeriesChartThresholdSettingsPanelComponent implements OnInit {
@Input()
widgetConfig: WidgetConfig;
@Input()
yAxisIds: TimeSeriesChartYAxisId[];
@Input()
popover: TbPopoverComponent<TimeSeriesChartThresholdSettingsPanelComponent>;
@ -74,6 +77,7 @@ export class TimeSeriesChartThresholdSettingsPanelComponent implements OnInit {
ngOnInit(): void {
this.thresholdSettingsFormGroup = this.fb.group(
{
yAxisId: [this.thresholdSettings.yAxisId, [Validators.required]],
units: [this.thresholdSettings.units, []],
decimals: [this.thresholdSettings.decimals, [Validators.min(0)]],
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-cell tb-threshold-source-header" translate>widgets.time-series-chart.threshold.source</div>
<div class="tb-form-table-header-cell tb-threshold-key-value-header" translate>widgets.time-series-chart.threshold.key-value</div>
<div class="tb-form-table-header-cell tb-y-axis-header" translate>widgets.time-series-chart.axis.y-axis</div>
<div class="tb-form-table-header-cell tb-color-header" translate>widgets.color.color</div>
<div class="tb-form-table-header-cell tb-units-header" translate>widget-config.units-short</div>
<div class="tb-form-table-header-cell tb-decimals-header" translate>widget-config.decimals-short</div>
@ -30,6 +31,7 @@
<div *ngFor="let thresholdControl of thresholdsFormArray().controls; trackBy: trackByThreshold; let $index = index; let $last = last">
<tb-time-series-chart-threshold-row fxFlex
[formControl]="thresholdControl"
[yAxisIds]="yAxisIds"
(thresholdRemoved)="removeThreshold($index)">
</tb-time-series-chart-threshold-row>
<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 {
width: 80px;
min-width: 80px;
@ -53,6 +58,12 @@
min-width: 80px;
}
&.tb-y-axis-header {
@media #{$mat-xs} {
display: none;
}
}
&.tb-units-header, &.tb-color-header, &.tb-decimals-header {
display: none;
@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,
TimeSeriesChartThresholdType,
timeSeriesChartThresholdValid,
timeSeriesChartThresholdValidator
timeSeriesChartThresholdValidator, TimeSeriesChartYAxisId
} from '@home/components/widget/lib/chart/time-series-chart.models';
import { mergeDeep } from '@core/utils';
import { IAliasController } from '@core/api/widget-api.models';
@ -74,6 +74,9 @@ export class TimeSeriesChartThresholdsPanelComponent implements ControlValueAcce
@Input()
widgetConfig: WidgetConfig;
@Input()
yAxisIds: TimeSeriesChartYAxisId[];
thresholdsFormGroup: UntypedFormGroup;
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 {
TimeSeriesNoAggregationBarWidthSettingsComponent
} 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({
declarations: [
@ -150,6 +159,9 @@ import {
TimeSeriesChartThresholdRowComponent,
TimeSeriesChartThresholdSettingsPanelComponent,
TimeSeriesNoAggregationBarWidthSettingsComponent,
TimeSeriesChartYAxesPanelComponent,
TimeSeriesChartYAxisRowComponent,
TimeSeriesChartYAxisSettingsPanelComponent,
DataKeyInputComponent,
EntityAliasInputComponent
],
@ -197,6 +209,9 @@ import {
TimeSeriesChartThresholdRowComponent,
TimeSeriesChartThresholdSettingsPanelComponent,
TimeSeriesNoAggregationBarWidthSettingsComponent,
TimeSeriesChartYAxesPanelComponent,
TimeSeriesChartYAxisRowComponent,
TimeSeriesChartYAxisSettingsPanelComponent,
DataKeyInputComponent,
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-hint": "Stacks series on the chart. The series with the same unit would be put on top of each other.",
"axes": "Axes",
"y-axes": "Y axes",
"line-type": "Line type",
"line-type-solid": "Solid",
"line-type-dashed": "Dashed",
@ -6670,6 +6671,9 @@
"axes": "Axes",
"x-axis": "X axis",
"y-axis": "Y axis",
"y-axis-settings": "Y axis settings",
"remove-y-axis": "Remove Y axis",
"id": "Id",
"label": "Label",
"position": "Position",
"position-left": "Left",
@ -6686,7 +6690,11 @@
"scale": "Scale",
"scale-min": "min",
"scale-max": "max",
"scale-auto": "Auto"
"scale-auto": "Auto",
"min": "Min",
"max": "Max",
"show": "Show",
"add-y-axis": "Add Y axis"
},
"series": {
"legend-settings": "Legend settings",

Loading…
Cancel
Save