Browse Source

Merge pull request #10562 from thingsboard/feature/time-series-comparison

Comparison support for new time series charts
pull/10579/head
Igor Kulikov 2 years ago
committed by GitHub
parent
commit
b453bc6ced
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 9
      ui-ngx/src/app/core/api/widget-api.models.ts
  2. 14
      ui-ngx/src/app/core/api/widget-subscription.ts
  3. 8
      ui-ngx/src/app/modules/home/components/widget/config/basic/basic-widget-config.module.ts
  4. 37
      ui-ngx/src/app/modules/home/components/widget/config/basic/chart/comparison-key-row.component.html
  5. 54
      ui-ngx/src/app/modules/home/components/widget/config/basic/chart/comparison-key-row.component.scss
  6. 131
      ui-ngx/src/app/modules/home/components/widget/config/basic/chart/comparison-key-row.component.ts
  7. 38
      ui-ngx/src/app/modules/home/components/widget/config/basic/chart/comparison-keys-table.component.html
  8. 53
      ui-ngx/src/app/modules/home/components/widget/config/basic/chart/comparison-keys-table.component.scss
  9. 110
      ui-ngx/src/app/modules/home/components/widget/config/basic/chart/comparison-keys-table.component.ts
  10. 87
      ui-ngx/src/app/modules/home/components/widget/config/basic/chart/time-series-chart-basic-config.component.html
  11. 48
      ui-ngx/src/app/modules/home/components/widget/config/basic/chart/time-series-chart-basic-config.component.ts
  12. 6
      ui-ngx/src/app/modules/home/components/widget/config/basic/common/data-keys-panel.component.html
  13. 4
      ui-ngx/src/app/modules/home/components/widget/config/basic/common/data-keys-panel.component.ts
  14. 2
      ui-ngx/src/app/modules/home/components/widget/dynamic-widget.component.ts
  15. 219
      ui-ngx/src/app/modules/home/components/widget/lib/chart/echarts-widget.models.ts
  16. 203
      ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart.models.ts
  17. 146
      ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart.ts
  18. 12
      ui-ngx/src/app/modules/home/components/widget/lib/flot-widget.models.ts
  19. 29
      ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/time-series-chart-key-settings.component.html
  20. 24
      ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/time-series-chart-key-settings.component.ts
  21. 40
      ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/time-series-chart-widget-settings.component.html
  22. 20
      ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/time-series-chart-widget-settings.component.ts
  23. 28
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-axis-settings-button.component.html
  24. 108
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-axis-settings-button.component.ts
  25. 16
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-axis-settings-panel.component.html
  26. 8
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-axis-settings-panel.component.scss
  27. 37
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-axis-settings-panel.component.ts
  28. 14
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-y-axis-row.component.ts
  29. 4
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/data-key-input.component.html
  30. 7
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/data-key-input.component.scss
  31. 4
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/data-key-input.component.ts
  32. 13
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/widget-settings-common.module.ts
  33. 2
      ui-ngx/src/app/modules/home/models/widget-component.models.ts
  34. 16
      ui-ngx/src/app/shared/models/widget.models.ts
  35. 10
      ui-ngx/src/assets/locale/locale.constant-en_US.json
  36. 12
      ui-ngx/src/form.scss

9
ui-ngx/src/app/core/api/widget-api.models.ts

@ -30,7 +30,12 @@ import {
import { TimeService } from '../services/time.service';
import { DeviceService } from '../http/device.service';
import { UtilsService } from '@core/services/utils.service';
import { SubscriptionTimewindow, Timewindow, WidgetTimewindow } from '@shared/models/time/time.models';
import {
ComparisonDuration,
SubscriptionTimewindow,
Timewindow,
WidgetTimewindow
} from '@shared/models/time/time.models';
import { EntityType } from '@shared/models/entity-type.models';
import { HttpErrorResponse } from '@angular/common/http';
import { RafService } from '@core/services/raf.service';
@ -265,7 +270,7 @@ export interface WidgetSubscriptionOptions {
onTimewindowChangeFunction?: (timewindow: Timewindow) => Timewindow;
legendConfig?: LegendConfig;
comparisonEnabled?: boolean;
timeForComparison?: moment_.unitOfTime.DurationConstructor;
timeForComparison?: ComparisonDuration;
comparisonCustomIntervalValue?: number;
decimals?: number;
units?: string;

14
ui-ngx/src/app/core/api/widget-subscription.ts

@ -23,13 +23,13 @@ import {
WidgetSubscriptionOptions
} from '@core/api/widget-api.models';
import {
DataKey,
DataKey, DataKeySettingsWithComparison,
DataSet,
DataSetHolder,
Datasource,
DatasourceData,
datasourcesHasAggregation,
DatasourceType,
DatasourceType, isDataKeySettingsWithComparison,
LegendConfig,
LegendData,
LegendKey,
@ -513,7 +513,7 @@ export class WidgetSubscription implements IWidgetSubscription {
this.configuredDatasources.forEach((datasource, datasourceIndex) => {
const additionalDataKeys: DataKey[] = [];
datasource.dataKeys.forEach((dataKey, dataKeyIndex) => {
if (dataKey.settings.comparisonSettings && dataKey.settings.comparisonSettings.showValuesForComparison) {
if (isDataKeySettingsWithComparison(dataKey.settings) && dataKey.settings.comparisonSettings.showValuesForComparison) {
const additionalDataKey = deepClone(dataKey);
additionalDataKey.isAdditional = true;
additionalDataKey.origDataKeyIndex = dataKeyIndex;
@ -1468,11 +1468,12 @@ export class WidgetSubscription implements IWidgetSubscription {
if (datasource.isAdditional) {
const origDatasource = this.datasourcePages[datasource.origDatasourceIndex].data[dIndex];
datasource.dataKeys.forEach((dataKey) => {
if (dataKey.settings.comparisonSettings.color) {
const settings: DataKeySettingsWithComparison = dataKey.settings;
if (settings.comparisonSettings.color) {
dataKey.color = dataKey.settings.comparisonSettings.color;
}
const origDataKey = origDatasource.dataKeys[dataKey.origDataKeyIndex];
origDataKey.settings.comparisonSettings.color = dataKey.color;
(origDataKey.settings as DataKeySettingsWithComparison).comparisonSettings.color = dataKey.color;
});
}
});
@ -1523,7 +1524,8 @@ export class WidgetSubscription implements IWidgetSubscription {
const formattedData = flatFormattedData(formattedDataArray);
datasource.dataKeys.forEach((dataKey) => {
if (this.comparisonEnabled && dataKey.isAdditional && dataKey.settings.comparisonSettings.comparisonValuesLabel) {
if (this.comparisonEnabled && dataKey.isAdditional && isDataKeySettingsWithComparison(dataKey.settings) &&
dataKey.settings.comparisonSettings.comparisonValuesLabel) {
dataKey.label = createLabelFromPattern(dataKey.settings.comparisonSettings.comparisonValuesLabel, formattedData);
} else {
if (this.comparisonEnabled && dataKey.isAdditional) {

8
ui-ngx/src/app/modules/home/components/widget/config/basic/basic-widget-config.module.ts

@ -110,6 +110,10 @@ import {
import {
TimeSeriesChartBasicConfigComponent
} from '@home/components/widget/config/basic/chart/time-series-chart-basic-config.component';
import { ComparisonKeyRowComponent } from '@home/components/widget/config/basic/chart/comparison-key-row.component';
import {
ComparisonKeysTableComponent
} from '@home/components/widget/config/basic/chart/comparison-keys-table.component';
@NgModule({
declarations: [
@ -145,7 +149,9 @@ import {
PowerButtonBasicConfigComponent,
SliderBasicConfigComponent,
ToggleButtonBasicConfigComponent,
TimeSeriesChartBasicConfigComponent
TimeSeriesChartBasicConfigComponent,
ComparisonKeyRowComponent,
ComparisonKeysTableComponent
],
imports: [
CommonModule,

37
ui-ngx/src/app/modules/home/components/widget/config/basic/chart/comparison-key-row.component.html

@ -0,0 +1,37 @@
<!--
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]="keyRowFormGroup" class="tb-form-table-row tb-comparison-key-row">
<mat-checkbox class="tb-show-field" formControlName="showValuesForComparison"></mat-checkbox>
<tb-data-key-input
[editable]="false"
[removable]="false"
[datasourceType]="datasourceType"
[formControl]="keyFormControl">
</tb-data-key-input>
<div class="tb-label-field">
<mat-form-field class="tb-inline-field" appearance="outline" subscriptSizing="dynamic">
<input matInput formControlName="comparisonValuesLabel" placeholder="{{ 'widgets.time-series-chart.comparison.comparison-values-label-auto' | translate }}">
</mat-form-field>
</div>
<div class="tb-color-field">
<tb-color-input asBoxInput
colorClearButton
formControlName="color">
</tb-color-input>
</div>
</div>

54
ui-ngx/src/app/modules/home/components/widget/config/basic/chart/comparison-key-row.component.scss

@ -0,0 +1,54 @@
/**
* 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-comparison-key-row {
.tb-show-field {
width: 40px;
min-width: 40px;
}
.tb-data-key-input {
flex: 1;
@media #{$mat-gt-xs} {
min-width: 100px;
flex: 1 1 40%;
}
}
.tb-label-field, .tb-color-field {
display: flex;
flex-direction: row;
place-content: center;
align-items: center;
.tb-inline-field {
flex: 1;
}
}
.tb-label-field {
flex: 1;
@media #{$mat-gt-xs} {
min-width: 150px;
flex: 1 1 60%;
}
}
.tb-color-field {
width: 40px;
min-width: 40px;
}
}

131
ui-ngx/src/app/modules/home/components/widget/config/basic/chart/comparison-key-row.component.ts

@ -0,0 +1,131 @@
///
/// 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, forwardRef, Input, OnInit, ViewEncapsulation } from '@angular/core';
import {
ControlValueAccessor,
NG_VALUE_ACCESSOR,
UntypedFormBuilder,
UntypedFormControl,
UntypedFormGroup
} from '@angular/forms';
import {
DataKey,
DataKeyComparisonSettings,
DataKeySettingsWithComparison,
DatasourceType
} from '@shared/models/widget.models';
import { deepClone } from '@core/utils';
@Component({
selector: 'tb-comparison-key-row',
templateUrl: './comparison-key-row.component.html',
styleUrls: ['./comparison-key-row.component.scss', '../../data-keys.component.scss'],
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => ComparisonKeyRowComponent),
multi: true
}
],
encapsulation: ViewEncapsulation.None
})
export class ComparisonKeyRowComponent implements ControlValueAccessor, OnInit {
@Input()
disabled: boolean;
@Input()
datasourceType: DatasourceType;
keyFormControl: UntypedFormControl;
keyRowFormGroup: UntypedFormGroup;
modelValue: DataKey;
private propagateChange = (_val: any) => {};
constructor(private fb: UntypedFormBuilder,
private cd: ChangeDetectorRef) {
}
ngOnInit() {
this.keyFormControl = this.fb.control(null, []);
this.keyRowFormGroup = this.fb.group({
showValuesForComparison: [null, []],
comparisonValuesLabel: [null, []],
color: [null, []]
});
this.keyRowFormGroup.valueChanges.subscribe(
() => this.updateModel()
);
this.keyRowFormGroup.get('showValuesForComparison').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.keyFormControl.disable({emitEvent: false});
this.keyRowFormGroup.disable({emitEvent: false});
} else {
this.keyFormControl.enable({emitEvent: false});
this.keyRowFormGroup.enable({emitEvent: false});
this.updateValidators();
}
}
writeValue(value: DataKey): void {
this.modelValue = value;
const comparisonSettings = (value?.settings as DataKeySettingsWithComparison)?.comparisonSettings;
this.keyRowFormGroup.patchValue(
comparisonSettings, {emitEvent: false}
);
this.keyFormControl.patchValue(deepClone(this.modelValue), {emitEvent: false});
this.updateValidators();
this.cd.markForCheck();
}
private updateValidators() {
const showValuesForComparison: boolean = this.keyRowFormGroup.get('showValuesForComparison').value;
if (showValuesForComparison) {
this.keyFormControl.enable({emitEvent: false});
this.keyRowFormGroup.get('comparisonValuesLabel').enable({emitEvent: false});
this.keyRowFormGroup.get('color').enable({emitEvent: false});
} else {
this.keyFormControl.disable({emitEvent: false});
this.keyRowFormGroup.get('comparisonValuesLabel').disable({emitEvent: false});
this.keyRowFormGroup.get('color').disable({emitEvent: false});
}
}
private updateModel() {
const comparisonSettings: DataKeyComparisonSettings = this.keyRowFormGroup.value;
if (!this.modelValue.settings) {
this.modelValue.settings = {};
}
this.modelValue.settings.comparisonSettings = comparisonSettings;
this.propagateChange(this.modelValue);
}
}

38
ui-ngx/src/app/modules/home/components/widget/config/basic/chart/comparison-keys-table.component.html

@ -0,0 +1,38 @@
<!--
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-comparison-keys-table tb-form-table">
<div class="tb-form-table-header">
<div class="tb-form-table-header-cell tb-show-header" translate>widgets.time-series-chart.comparison.show</div>
<div class="tb-form-table-header-cell tb-key-header" translate>datakey.key</div>
<div class="tb-form-table-header-cell tb-label-header" translate>datakey.label</div>
<div class="tb-form-table-header-cell tb-color-header" translate>datakey.color</div>
</div>
<div *ngIf="keysFormArray().controls.length; else noKeys" class="tb-form-table-body">
<div *ngFor="let keyControl of keysFormArray().controls; trackBy: trackByKey; let $index = index;">
<tb-comparison-key-row
fxFlex
[datasourceType]="datasourceType"
[formControl]="keyControl">
</tb-comparison-key-row>
</div>
</div>
</div>
<ng-template #noKeys>
<span fxLayoutAlign="center center"
class="tb-prompt">{{ 'widgets.chart.no-series' | translate }}</span>
</ng-template>

53
ui-ngx/src/app/modules/home/components/widget/config/basic/chart/comparison-keys-table.component.scss

@ -0,0 +1,53 @@
/**
* 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-comparison-keys-table {
.tb-form-table-header-cell {
&.tb-show-header {
width: 40px;
min-width: 40px;
}
&.tb-key-header {
flex: 1;
@media #{$mat-gt-xs} {
min-width: 100px;
flex: 1 1 40%;
}
}
&.tb-label-header {
flex: 1;
@media #{$mat-gt-xs} {
min-width: 150px;
flex: 1 1 60%;
}
}
&.tb-color-header {
width: 40px;
min-width: 40px;
}
}
.tb-form-table-body {
tb-comparison-key-row {
overflow: hidden;
}
}
}

110
ui-ngx/src/app/modules/home/components/widget/config/basic/chart/comparison-keys-table.component.ts

@ -0,0 +1,110 @@
///
/// Copyright © 2016-2024 The Thingsboard Authors
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
import { Component, forwardRef, Input, OnInit, ViewEncapsulation } from '@angular/core';
import {
AbstractControl,
ControlValueAccessor,
NG_VALUE_ACCESSOR,
UntypedFormArray,
UntypedFormBuilder,
UntypedFormGroup
} from '@angular/forms';
import { DataKey, DatasourceType } from '@shared/models/widget.models';
@Component({
selector: 'tb-comparison-keys-table',
templateUrl: './comparison-keys-table.component.html',
styleUrls: ['./comparison-keys-table.component.scss'],
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => ComparisonKeysTableComponent),
multi: true
}
],
encapsulation: ViewEncapsulation.None
})
export class ComparisonKeysTableComponent implements ControlValueAccessor, OnInit {
@Input()
disabled: boolean;
@Input()
datasourceType: DatasourceType;
keysListFormGroup: UntypedFormGroup;
get noKeys(): boolean {
const keys: DataKey[] = this.keysListFormGroup.get('keys').value;
return keys.length === 0;
}
private propagateChange = (_val: any) => {};
constructor(private fb: UntypedFormBuilder) {
}
ngOnInit() {
this.keysListFormGroup = this.fb.group({
keys: [this.fb.array([]), []]
});
this.keysListFormGroup.valueChanges.subscribe(
() => {
const keys: DataKey[] = this.keysListFormGroup.get('keys').value;
this.propagateChange(keys);
}
);
}
registerOnChange(fn: any): void {
this.propagateChange = fn;
}
registerOnTouched(_fn: any): void {
}
setDisabledState(isDisabled: boolean): void {
if (isDisabled) {
this.keysListFormGroup.disable({emitEvent: false});
} else {
this.keysListFormGroup.enable({emitEvent: false});
}
}
writeValue(value: DataKey[] | undefined): void {
this.keysListFormGroup.setControl('keys', this.prepareKeysFormArray(value), {emitEvent: false});
}
keysFormArray(): UntypedFormArray {
return this.keysListFormGroup.get('keys') as UntypedFormArray;
}
trackByKey(_index: number, keyControl: AbstractControl): any {
return keyControl;
}
private prepareKeysFormArray(keys: DataKey[] | undefined): UntypedFormArray {
const keysControls: Array<AbstractControl> = [];
if (keys) {
keys.forEach((key) => {
keysControls.push(this.fb.control(key, []));
});
}
return this.fb.array(keysControls);
}
}

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

@ -25,22 +25,77 @@
forceSingleDatasource
formControlName="datasources">
</tb-datasources>
<tb-data-keys-panel
panelTitle="{{ 'widgets.chart.series' | translate }}"
addKeyTitle="{{ 'widgets.chart.add-series' | translate }}"
keySettingsTitle="{{ 'widgets.chart.series-settings' | translate }}"
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"
[deviceId]="datasource?.deviceId"
[entityAliasId]="datasource?.entityAliasId"
formControlName="series">
</tb-data-keys-panel>
<div class="tb-form-panel">
<div fxLayout="row" fxLayoutAlign="space-between center">
<div class="tb-form-panel-title">{{ 'widgets.chart.series' | translate }}</div>
<tb-toggle-select [ngModel]="seriesMode" (ngModelChange)="seriesModeChange($event)"
[ngModelOptions]="{ standalone: true }">
<tb-toggle-option value="series">{{ 'widgets.chart.series' | translate }}</tb-toggle-option>
<tb-toggle-option value="comparison">{{ 'widgets.time-series-chart.comparison.comparison' | translate }}</tb-toggle-option>
</tb-toggle-select>
</div>
<tb-data-keys-panel
*ngIf="seriesMode === 'series'"
hidePanel
panelTitle="{{ 'widgets.chart.series' | translate }}"
addKeyTitle="{{ 'widgets.chart.add-series' | translate }}"
keySettingsTitle="{{ 'widgets.chart.series-settings' | translate }}"
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"
[deviceId]="datasource?.deviceId"
[entityAliasId]="datasource?.entityAliasId"
formControlName="series">
</tb-data-keys-panel>
<div *ngIf="seriesMode === 'comparison'" class="tb-form-row no-border no-padding column-xs">
<mat-slide-toggle class="mat-slide fixed-title-width" formControlName="comparisonEnabled">
{{ 'widgets.time-series-chart.comparison.comparison' | translate }}
</mat-slide-toggle>
<div fxFlex fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px">
<mat-form-field class="flex" appearance="outline" subscriptSizing="dynamic">
<mat-select formControlName="timeForComparison">
<mat-option [value]="'previousInterval'">
{{ 'widgets.chart.time-for-comparison-previous-interval' | translate }}
</mat-option>
<mat-option [value]="'days'">
{{ 'widgets.chart.time-for-comparison-days' | translate }}
</mat-option>
<mat-option [value]="'weeks'">
{{ 'widgets.chart.time-for-comparison-weeks' | translate }}
</mat-option>
<mat-option [value]="'months'">
{{ 'widgets.chart.time-for-comparison-months' | translate }}
</mat-option>
<mat-option [value]="'years'">
{{ 'widgets.chart.time-for-comparison-years' | translate }}
</mat-option>
<mat-option [value]="'customInterval'">
{{ 'widgets.chart.time-for-comparison-custom-interval' | translate }}
</mat-option>
</mat-select>
</mat-form-field>
<mat-form-field [fxShow]="timeSeriesChartWidgetConfigForm.get('timeForComparison').value === 'customInterval'"
appearance="outline" class="number flex-lt-md" subscriptSizing="dynamic">
<input matInput formControlName="comparisonCustomIntervalValue" type="number" min="0" placeholder="{{ 'widget-config.set' | translate }}">
</mat-form-field>
<tb-time-series-chart-axis-settings-button
axisType="xAxis"
panelTitle="{{ 'widgets.time-series-chart.axis.comparison-x-axis-settings' | translate }}"
formControlName="comparisonXAxis">
</tb-time-series-chart-axis-settings-button>
</div>
</div>
<tb-comparison-keys-table
*ngIf="seriesMode === 'comparison'"
[datasourceType]="datasource?.type"
formControlName="series">
</tb-comparison-keys-table>
</div>
<tb-time-series-chart-states-panel
*ngIf="chartType === TimeSeriesChartType.state"
formControlName="states">

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

@ -23,6 +23,7 @@ import { WidgetConfigComponentData } from '@home/models/widget-component.models'
import {
DataKey,
Datasource,
DatasourceType,
legendPositions,
legendPositionTranslationMap,
WidgetConfig,
@ -89,6 +90,8 @@ export class TimeSeriesChartBasicConfigComponent extends BasicWidgetConfigCompon
chartType: TimeSeriesChartType = TimeSeriesChartType.default;
seriesMode = 'series';
constructor(protected store: Store<AppState>,
protected widgetConfigComponent: WidgetConfigComponent,
private $injector: Injector,
@ -105,6 +108,11 @@ export class TimeSeriesChartBasicConfigComponent extends BasicWidgetConfigCompon
}
}
seriesModeChange(seriesMode: string) {
this.seriesMode = seriesMode;
this.updateSeriesState();
}
protected configForm(): UntypedFormGroup {
return this.timeSeriesChartWidgetConfigForm;
}
@ -131,6 +139,12 @@ export class TimeSeriesChartBasicConfigComponent extends BasicWidgetConfigCompon
yAxes: [settings.yAxes, []],
series: [this.getSeries(configData.config.datasources), []],
comparisonEnabled: [settings.comparisonEnabled, []],
timeForComparison: [settings.timeForComparison, []],
comparisonCustomIntervalValue: [settings.comparisonCustomIntervalValue, [Validators.min(0)]],
comparisonXAxis: [settings.comparisonXAxis, []],
thresholds: [settings.thresholds, []],
showTitle: [configData.config.showTitle, []],
@ -182,6 +196,7 @@ export class TimeSeriesChartBasicConfigComponent extends BasicWidgetConfigCompon
if (this.chartType === TimeSeriesChartType.state) {
this.timeSeriesChartWidgetConfigForm.addControl('states', this.fb.control(settings.states, []));
}
this.timeSeriesChartWidgetConfigForm.get('comparisonEnabled').valueChanges.subscribe(() => this.updateSeriesState());
}
protected prepareOutputConfig(config: any): WidgetConfigComponentData {
@ -201,6 +216,11 @@ export class TimeSeriesChartBasicConfigComponent extends BasicWidgetConfigCompon
this.widgetConfig.config.settings = this.widgetConfig.config.settings || {};
this.widgetConfig.config.settings.comparisonEnabled = config.comparisonEnabled;
this.widgetConfig.config.settings.timeForComparison = config.timeForComparison;
this.widgetConfig.config.settings.comparisonCustomIntervalValue = config.comparisonCustomIntervalValue;
this.widgetConfig.config.settings.comparisonXAxis = config.comparisonXAxis;
this.widgetConfig.config.settings.thresholds = config.thresholds;
this.widgetConfig.config.settings.dataZoom = config.dataZoom;
@ -245,16 +265,27 @@ export class TimeSeriesChartBasicConfigComponent extends BasicWidgetConfigCompon
}
protected validatorTriggers(): string[] {
return ['showTitle', 'showIcon', 'showLegend', 'showTooltip', 'tooltipShowDate'];
return ['comparisonEnabled', 'showTitle', 'showIcon', 'showLegend', 'showTooltip', 'tooltipShowDate'];
}
protected updateValidators(emitEvent: boolean, trigger?: string) {
const comparisonEnabled: boolean = this.timeSeriesChartWidgetConfigForm.get('comparisonEnabled').value;
const showTitle: boolean = this.timeSeriesChartWidgetConfigForm.get('showTitle').value;
const showIcon: boolean = this.timeSeriesChartWidgetConfigForm.get('showIcon').value;
const showLegend: boolean = this.timeSeriesChartWidgetConfigForm.get('showLegend').value;
const showTooltip: boolean = this.timeSeriesChartWidgetConfigForm.get('showTooltip').value;
const tooltipShowDate: boolean = this.timeSeriesChartWidgetConfigForm.get('tooltipShowDate').value;
if (comparisonEnabled) {
this.timeSeriesChartWidgetConfigForm.get('timeForComparison').enable();
this.timeSeriesChartWidgetConfigForm.get('comparisonCustomIntervalValue').enable();
this.timeSeriesChartWidgetConfigForm.get('comparisonXAxis').enable();
} else {
this.timeSeriesChartWidgetConfigForm.get('timeForComparison').disable();
this.timeSeriesChartWidgetConfigForm.get('comparisonCustomIntervalValue').disable();
this.timeSeriesChartWidgetConfigForm.get('comparisonXAxis').disable();
}
if (showTitle) {
this.timeSeriesChartWidgetConfigForm.get('title').enable();
this.timeSeriesChartWidgetConfigForm.get('titleFont').enable();
@ -336,6 +367,19 @@ export class TimeSeriesChartBasicConfigComponent extends BasicWidgetConfigCompon
}
}
private updateSeriesState() {
if (this.seriesMode === 'series') {
this.timeSeriesChartWidgetConfigForm.get('series').enable({emitEvent: false});
} else {
const comparisonEnabled = this.timeSeriesChartWidgetConfigForm.get('comparisonEnabled').value;
if (comparisonEnabled) {
this.timeSeriesChartWidgetConfigForm.get('series').enable({emitEvent: false});
} else {
this.timeSeriesChartWidgetConfigForm.get('series').disable({emitEvent: false});
}
}
}
private removeYaxisId(series: DataKey[], yAxisId: TimeSeriesChartYAxisId): boolean {
let changed = false;
if (series) {
@ -372,4 +416,6 @@ export class TimeSeriesChartBasicConfigComponent extends BasicWidgetConfigCompon
processor.update(Date.now());
return processor.formatted;
}
protected readonly DatasourceType = DatasourceType;
}

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

@ -15,8 +15,10 @@
limitations under the License.
-->
<div class="tb-form-panel tb-data-keys-panel">
<div class="tb-form-panel-title">{{ panelTitle }}</div>
<div class="tb-form-panel tb-data-keys-panel"
[class.no-border]="hidePanel"
[class.no-padding]="hidePanel">
<div *ngIf="!hidePanel" class="tb-form-panel-title">{{ panelTitle }}</div>
<div class="tb-form-table">
<div class="tb-form-table-header no-padding-right">
<div *ngIf="hasAdditionalLatestDataKeys" class="tb-form-table-header-cell tb-source-header" translate>datakey.source</div>

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

@ -96,6 +96,10 @@ export class DataKeysPanelComponent implements ControlValueAccessor, OnInit, OnC
@Input()
deviceId: string;
@Input()
@coerceBoolean()
hidePanel = false;
@Input()
@coerceBoolean()
hideDataKeyColor = false;

2
ui-ngx/src/app/modules/home/components/widget/dynamic-widget.component.ts

@ -49,6 +49,7 @@ import { TbInject } from '@shared/decorators/tb-inject';
import { MillisecondsToTimeStringPipe } from '@shared/pipe/milliseconds-to-time-string.pipe';
import { UserSettingsService } from '@core/http/user-settings.service';
import { ImagePipe } from '@shared/pipe/image.pipe';
import { UtilsService } from '@core/services/utils.service';
@Directive()
// eslint-disable-next-line @angular-eslint/directive-class-suffix
@ -86,6 +87,7 @@ export class DynamicWidgetComponent extends PageComponent implements IDynamicWid
this.ctx.customDialog = $injector.get(CustomDialogService);
this.ctx.resourceService = $injector.get(ResourceService);
this.ctx.userSettingsService = $injector.get(UserSettingsService);
this.ctx.utilsService = $injector.get(UtilsService);
this.ctx.telemetryWsService = $injector.get(TelemetryWebsocketService);
this.ctx.date = $injector.get(DatePipe);
this.ctx.imagePipe = $injector.get(ImagePipe);

219
ui-ngx/src/app/modules/home/components/widget/lib/chart/echarts-widget.models.ts

@ -115,6 +115,7 @@ export type EChartsSeriesItem = {
decimals?: number;
latestData?: FormattedData;
tooltipValueFormatFunction?: EChartsTooltipValueFormatFunction;
comparisonItem?: boolean;
};
export enum EChartsShape {
@ -200,9 +201,9 @@ export const timeAxisBandWidthCalculator: TimeAxisBandWidthCalculator = (model)
}
};
export const getXAxis = (chart: ECharts): Axis2D => {
export const getAxis = (chart: ECharts, mainType: string, axisId: string): Axis2D => {
const model: GlobalModel = (chart as any).getModel();
const models = model.queryComponents({mainType: 'xAxis'});
const models = model.queryComponents({mainType, id: axisId});
if (models?.length) {
const axisModel = models[0] as AxisModel;
return axisModel.axis;
@ -210,27 +211,20 @@ export const getXAxis = (chart: ECharts): Axis2D => {
return null;
};
export const getYAxis = (chart: ECharts, axisId: string): Axis2D => {
const model: GlobalModel = (chart as any).getModel();
const models = model.queryComponents({mainType: 'yAxis', id: axisId});
if (models?.length) {
const axisModel = models[0] as AxisModel;
return axisModel.axis;
}
return null;
};
export const calculateYAxisWidth = (chart: ECharts, axisId: string): number => {
const axis = getYAxis(chart, axisId);
return calculateAxisSize(axis);
export const calculateAxisSize = (chart: ECharts, mainType: string, axisId: string): number => {
const axis = getAxis(chart, mainType, axisId);
return _calculateAxisSize(axis);
};
export const calculateXAxisHeight = (chart: ECharts): number => {
const axis = getXAxis(chart);
return calculateAxisSize(axis);
export const measureAxisNameSize = (chart: ECharts, mainType: string, axisId: string, name: string): number => {
const axis = getAxis(chart, mainType, axisId);
if (axis) {
return axis.model.getModel('nameTextStyle').getTextRect(name).height;
}
return 0;
};
const calculateAxisSize = (axis: Axis2D): number => {
const _calculateAxisSize = (axis: Axis2D): number => {
let size = 0;
if (axis && axis.model.option.show) {
const labelUnionRect = estimateLabelUnionRect(axis);
@ -247,22 +241,6 @@ const calculateAxisSize = (axis: Axis2D): number => {
return size;
};
export const measureYAxisNameWidth = (chart: ECharts, axisId: string, name: string): number => {
const axis = getYAxis(chart, axisId);
if (axis) {
return axis.model.getModel('nameTextStyle').getTextRect(name).height;
}
return 0;
};
export const measureXAxisNameHeight = (chart: ECharts, name: string): number => {
const axis = getXAxis(chart);
if (axis) {
return axis.model.getModel('nameTextStyle').getTextRect(name).height;
}
return 0;
};
const measureSymbolOffset = (symbol: string, symbolSize: any): number => {
if (isNumber(symbolSize)) {
if (symbol) {
@ -280,7 +258,7 @@ const measureSymbolOffset = (symbol: string, symbolSize: any): number => {
export const measureThresholdOffset = (chart: ECharts, axisId: string, thresholdId: string, value: any): [number, number] => {
const offset: [number, number] = [0,0];
const axis = getYAxis(chart, axisId);
const axis = getAxis(chart, 'yAxis', axisId);
if (axis && !axis.scale.isBlank()) {
const extent = axis.scale.getExtent();
const model: GlobalModel = (chart as any).getModel();
@ -352,7 +330,7 @@ export const measureThresholdOffset = (chart: ECharts, axisId: string, threshold
};
export const getAxisExtent = (chart: ECharts, axisId: string): [number, number] => {
const axis = getYAxis(chart, axisId);
const axis = getAxis(chart, 'yAxis', axisId);
if (axis) {
return axis.scale.getExtent();
}
@ -503,43 +481,72 @@ export const echartsTooltipFormatter = (renderer: Renderer2,
focusedSeriesIndex: number,
series?: EChartsSeriesItem[],
interval?: Interval): null | HTMLElement => {
if (!params || Array.isArray(params) && !params[0]) {
return null;
}
const firstParam = Array.isArray(params) ? params[0] : params;
if (!firstParam.value) {
const tooltipParams = mapTooltipParams(params, series, focusedSeriesIndex);
if (!tooltipParams.items.length && !tooltipParams.comparisonItems.length) {
return null;
}
const tooltipElement: HTMLElement = renderer.createElement('div');
renderer.setStyle(tooltipElement, 'display', 'flex');
renderer.setStyle(tooltipElement, 'flex-direction', 'column');
renderer.setStyle(tooltipElement, 'align-items', 'flex-start');
renderer.setStyle(tooltipElement, 'gap', '4px');
if (settings.tooltipShowDate) {
const dateElement: HTMLElement = renderer.createElement('div');
let dateText: string;
const startTs = firstParam.value[2];
const endTs = firstParam.value[3];
if (settings.tooltipDateInterval && startTs && endTs && (endTs - 1) > startTs) {
const startDateText = tooltipDateFormat.update(startTs, interval);
const endDateText = tooltipDateFormat.update(endTs - 1, interval);
if (startDateText === endDateText) {
dateText = startDateText;
} else {
dateText = startDateText + ' - ' + endDateText;
}
} else {
const ts = firstParam.value[0];
dateText = tooltipDateFormat.update(ts, interval);
renderer.setStyle(tooltipElement, 'gap', '16px');
buildItemsTooltip(tooltipElement, tooltipParams.items, renderer, tooltipDateFormat, settings, valueFormatFunction, interval);
buildItemsTooltip(tooltipElement, tooltipParams.comparisonItems, renderer, tooltipDateFormat, settings, valueFormatFunction, interval);
return tooltipElement;
};
interface TooltipItem {
param: CallbackDataParams;
dataItem: EChartsSeriesItem;
}
interface TooltipParams {
items: TooltipItem[];
comparisonItems: TooltipItem[];
}
const buildItemsTooltip = (tooltipElement: HTMLElement,
items: TooltipItem[],
renderer: Renderer2,
tooltipDateFormat: DateFormatProcessor,
settings: EChartsTooltipWidgetSettings,
valueFormatFunction: EChartsTooltipValueFormatFunction,
interval?: Interval) => {
if (items.length) {
const tooltipItemsElement: HTMLElement = renderer.createElement('div');
renderer.setStyle(tooltipItemsElement, 'display', 'flex');
renderer.setStyle(tooltipItemsElement, 'flex-direction', 'column');
renderer.setStyle(tooltipItemsElement, 'align-items', 'flex-start');
renderer.setStyle(tooltipItemsElement, 'gap', '4px');
renderer.appendChild(tooltipElement, tooltipItemsElement);
if (settings.tooltipShowDate) {
renderer.appendChild(tooltipItemsElement,
constructEchartsTooltipDateElement(renderer, tooltipDateFormat, settings, items[0].param, interval));
}
for (const item of items) {
renderer.appendChild(tooltipItemsElement,
constructEchartsTooltipSeriesElement(renderer, settings, item, valueFormatFunction));
}
renderer.appendChild(dateElement, renderer.createText(dateText));
renderer.setStyle(dateElement, 'font-family', settings.tooltipDateFont.family);
renderer.setStyle(dateElement, 'font-size', settings.tooltipDateFont.size + settings.tooltipDateFont.sizeUnit);
renderer.setStyle(dateElement, 'font-style', settings.tooltipDateFont.style);
renderer.setStyle(dateElement, 'font-weight', settings.tooltipDateFont.weight);
renderer.setStyle(dateElement, 'line-height', settings.tooltipDateFont.lineHeight);
renderer.setStyle(dateElement, 'color', settings.tooltipDateColor);
renderer.appendChild(tooltipElement, dateElement);
}
};
const mapTooltipParams = (params: CallbackDataParams[] | CallbackDataParams,
series?: EChartsSeriesItem[],
focusedSeriesIndex?: number): TooltipParams => {
const result: TooltipParams = {
items: [],
comparisonItems: []
};
if (!params || Array.isArray(params) && !params[0]) {
return result;
}
const firstParam = Array.isArray(params) ? params[0] : params;
if (!firstParam.value) {
return result;
}
let seriesParams: CallbackDataParams = null;
if (Array.isArray(params) && focusedSeriesIndex > -1) {
@ -548,22 +555,63 @@ export const echartsTooltipFormatter = (renderer: Renderer2,
seriesParams = params;
}
if (seriesParams) {
renderer.appendChild(tooltipElement,
constructEchartsTooltipSeriesElement(renderer, settings, seriesParams, valueFormatFunction, series));
appendTooltipItem(result, seriesParams, series);
} else if (Array.isArray(params)) {
for (seriesParams of params) {
renderer.appendChild(tooltipElement,
constructEchartsTooltipSeriesElement(renderer, settings, seriesParams, valueFormatFunction, series));
appendTooltipItem(result, seriesParams, series);
}
}
return tooltipElement;
return result;
};
const appendTooltipItem = (tooltipParams: TooltipParams, seriesParams: CallbackDataParams, series?: EChartsSeriesItem[]) => {
const dataItem = series?.find(s => s.id === seriesParams.seriesId);
const tooltipItem: TooltipItem = {
param: seriesParams,
dataItem
};
if (dataItem?.comparisonItem) {
tooltipParams.comparisonItems.push(tooltipItem);
} else {
tooltipParams.items.push(tooltipItem);
}
};
const constructEchartsTooltipDateElement = (renderer: Renderer2,
tooltipDateFormat: DateFormatProcessor,
settings: EChartsTooltipWidgetSettings,
param: CallbackDataParams,
interval?: Interval): HTMLElement => {
const dateElement: HTMLElement = renderer.createElement('div');
let dateText: string;
const startTs = param.value[2];
const endTs = param.value[3];
if (settings.tooltipDateInterval && startTs && endTs && (endTs - 1) > startTs) {
const startDateText = tooltipDateFormat.update(startTs, interval);
const endDateText = tooltipDateFormat.update(endTs - 1, interval);
if (startDateText === endDateText) {
dateText = startDateText;
} else {
dateText = startDateText + ' - ' + endDateText;
}
} else {
const ts = param.value[0];
dateText = tooltipDateFormat.update(ts, interval);
}
renderer.appendChild(dateElement, renderer.createText(dateText));
renderer.setStyle(dateElement, 'font-family', settings.tooltipDateFont.family);
renderer.setStyle(dateElement, 'font-size', settings.tooltipDateFont.size + settings.tooltipDateFont.sizeUnit);
renderer.setStyle(dateElement, 'font-style', settings.tooltipDateFont.style);
renderer.setStyle(dateElement, 'font-weight', settings.tooltipDateFont.weight);
renderer.setStyle(dateElement, 'line-height', settings.tooltipDateFont.lineHeight);
renderer.setStyle(dateElement, 'color', settings.tooltipDateColor);
return dateElement;
};
const constructEchartsTooltipSeriesElement = (renderer: Renderer2,
settings: EChartsTooltipWidgetSettings,
seriesParams: CallbackDataParams,
valueFormatFunction: EChartsTooltipValueFormatFunction,
series?: EChartsSeriesItem[]): HTMLElement => {
item: TooltipItem,
valueFormatFunction: EChartsTooltipValueFormatFunction): HTMLElement => {
const labelValueElement: HTMLElement = renderer.createElement('div');
renderer.setStyle(labelValueElement, 'display', 'flex');
renderer.setStyle(labelValueElement, 'flex-direction', 'row');
@ -579,10 +627,10 @@ const constructEchartsTooltipSeriesElement = (renderer: Renderer2,
renderer.setStyle(circleElement, 'width', '8px');
renderer.setStyle(circleElement, 'height', '8px');
renderer.setStyle(circleElement, 'border-radius', '50%');
renderer.setStyle(circleElement, 'background', seriesParams.color);
renderer.setStyle(circleElement, 'background', item.param.color);
renderer.appendChild(labelElement, circleElement);
const labelTextElement: HTMLElement = renderer.createElement('div');
renderer.appendChild(labelTextElement, renderer.createText(seriesParams.seriesName));
renderer.appendChild(labelTextElement, renderer.createText(item.param.seriesName));
renderer.setStyle(labelTextElement, 'font-family', 'Roboto');
renderer.setStyle(labelTextElement, 'font-size', '12px');
renderer.setStyle(labelTextElement, 'font-style', 'normal');
@ -596,21 +644,18 @@ const constructEchartsTooltipSeriesElement = (renderer: Renderer2,
let latestData: FormattedData;
let units = '';
let decimals = 0;
if (series) {
const item = series.find(s => s.id === seriesParams.seriesId);
if (item) {
if (item.tooltipValueFormatFunction) {
formatFunction = item.tooltipValueFormatFunction;
}
latestData = item.latestData;
units = item.units;
decimals = item.decimals;
if (item.dataItem) {
if (item.dataItem.tooltipValueFormatFunction) {
formatFunction = item.dataItem.tooltipValueFormatFunction;
}
latestData = item.dataItem.latestData;
units = item.dataItem.units;
decimals = item.dataItem.decimals;
}
if (!latestData) {
latestData = {} as FormattedData;
}
const value = formatFunction(seriesParams.value[1], latestData, units, decimals);
const value = formatFunction(item.param.value[1], latestData, units, decimals);
renderer.appendChild(valueElement, renderer.createText(value));
renderer.setStyle(valueElement, 'flex', '1');
renderer.setStyle(valueElement, 'text-align', 'end');

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

@ -62,13 +62,16 @@ import {
BarVisualSettings,
renderTimeSeriesBar
} from '@home/components/widget/lib/chart/time-series-chart-bar.models';
import { DataKey } from '@shared/models/widget.models';
import { DataKey, DataKeySettingsWithComparison, WidgetComparisonSettings } from '@shared/models/widget.models';
import { DataKeyType } from '@shared/models/telemetry/telemetry.models';
import { TbColorScheme } from '@shared/models/color.models';
import { AbstractControl, ValidationErrors } from '@angular/forms';
import { MarkLine2DDataItemOption } from 'echarts/types/src/component/marker/MarkLineModel';
import { DatePipe } from '@angular/common';
import { BuiltinTextPosition } from 'zrender/src/core/types';
import { CartesianAxisOption } from 'echarts/types/src/coord/cartesian/AxisModel';
import { WidgetTimewindow } from '@shared/models/time/time.models';
import { UtilsService } from '@core/services/utils.service';
export enum TimeSeriesChartType {
default = 'default',
@ -692,7 +695,11 @@ export const timeSeriesChartStateValidator = (control: AbstractControl): Validat
return null;
};
export interface TimeSeriesChartSettings extends EChartsTooltipWidgetSettings {
export interface TimeSeriesChartComparisonSettings extends WidgetComparisonSettings {
comparisonXAxis?: TimeSeriesChartXAxisSettings;
}
export interface TimeSeriesChartSettings extends EChartsTooltipWidgetSettings, TimeSeriesChartComparisonSettings {
thresholds: TimeSeriesChartThreshold[];
darkMode: boolean;
dataZoom: boolean;
@ -750,7 +757,13 @@ export const timeSeriesChartDefaultSettings: TimeSeriesChartSettings = {
tooltipDateColor: 'rgba(0, 0, 0, 0.76)',
tooltipDateInterval: true,
tooltipBackgroundColor: 'rgba(255, 255, 255, 0.76)',
tooltipBackgroundBlur: 4
tooltipBackgroundBlur: 4,
comparisonEnabled: false,
timeForComparison: 'previousInterval',
comparisonCustomIntervalValue: 7200000,
comparisonXAxis: mergeDeep({} as TimeSeriesChartXAxisSettings,
defaultTimeSeriesChartXAxisSettings,
{ position: AxisPosition.top } as TimeSeriesChartXAxisSettings)
};
export interface SeriesFillSettings {
@ -798,7 +811,7 @@ export interface BarSeriesSettings {
backgroundSettings: SeriesFillSettings;
}
export interface TimeSeriesChartKeySettings {
export interface TimeSeriesChartKeySettings extends DataKeySettingsWithComparison {
yAxisId: TimeSeriesChartYAxisId;
showInLegend: boolean;
dataHiddenByDefault: boolean;
@ -870,10 +883,16 @@ export const timeSeriesChartKeyDefaultSettings: TimeSeriesChartKeySettings = {
end: 0
}
}
},
comparisonSettings: {
showValuesForComparison: false,
comparisonValuesLabel: '',
color: ''
}
};
export interface TimeSeriesChartDataItem extends EChartsSeriesItem {
xAxisIndex: number;
yAxisId: TimeSeriesChartYAxisId;
yAxisIndex: number;
option?: LineSeriesOption | CustomSeriesOption;
@ -894,16 +913,27 @@ export interface TimeSeriesChartThresholdItem {
option?: LineSeriesOption;
}
export interface TimeSeriesChartYAxis {
export interface TimeSeriesChartAxis {
id: string;
settings: TimeSeriesChartAxisSettings;
option: CartesianAxisOption;
}
export interface TimeSeriesChartYAxis extends TimeSeriesChartAxis {
decimals: number;
settings: TimeSeriesChartYAxisSettings;
option: YAXisOption & ValueAxisBaseOption;
}
export interface TimeSeriesChartXAxis extends TimeSeriesChartAxis {
settings: TimeSeriesChartXAxisSettings;
option: XAXisOption;
}
export const createTimeSeriesYAxis = (units: string,
decimals: number,
settings: TimeSeriesChartYAxisSettings,
utils: UtilsService,
darkMode: boolean): TimeSeriesChartYAxis => {
const yAxisTickLabelStyle = createChartTextStyle(settings.tickLabelFont,
settings.tickLabelColor, darkMode, 'axis.tickLabel');
@ -944,6 +974,7 @@ export const createTimeSeriesYAxis = (units: string,
decimals,
settings,
option: {
mainType: 'yAxis',
show: settings.show,
type: 'value',
position: settings.position,
@ -957,7 +988,7 @@ export const createTimeSeriesYAxis = (units: string,
splitNumber,
interval,
ticksGenerator,
name: settings.label,
name: utils.customTranslation(settings.label, settings.label),
nameLocation: 'middle',
nameRotate: settings.position === AxisPosition.left ? 90 : -90,
nameTextStyle: {
@ -1011,75 +1042,88 @@ export const createTimeSeriesYAxis = (units: string,
};
};
export const createTimeSeriesXAxisOption = (settings: TimeSeriesChartXAxisSettings,
min: number, max: number,
datePipe: DatePipe,
darkMode: boolean): XAXisOption => {
export const createTimeSeriesXAxis = (id: string,
settings: TimeSeriesChartXAxisSettings,
min: number, max: number,
datePipe: DatePipe,
utils: UtilsService,
darkMode: boolean): TimeSeriesChartXAxis => {
const xAxisTickLabelStyle = createChartTextStyle(settings.tickLabelFont,
settings.tickLabelColor, darkMode, 'axis.tickLabel');
const xAxisNameStyle = createChartTextStyle(settings.labelFont,
settings.labelColor, darkMode, 'axis.label');
const ticksFormat = mergeDeep({}, defaultXAxisTicksFormat, settings.ticksFormat);
return {
show: settings.show,
type: 'time',
scale: true,
position: settings.position,
name: settings.label,
nameLocation: 'middle',
nameTextStyle: {
color: xAxisNameStyle.color,
fontStyle: xAxisNameStyle.fontStyle,
fontWeight: xAxisNameStyle.fontWeight,
fontFamily: xAxisNameStyle.fontFamily,
fontSize: xAxisNameStyle.fontSize
},
axisTick: {
show: settings.showTicks,
lineStyle: {
color: prepareChartThemeColor(settings.ticksColor, darkMode, 'axis.ticks')
}
},
axisLabel: {
show: settings.showTickLabels,
color: xAxisTickLabelStyle.color,
fontStyle: xAxisTickLabelStyle.fontStyle,
fontWeight: xAxisTickLabelStyle.fontWeight,
fontFamily: xAxisTickLabelStyle.fontFamily,
fontSize: xAxisTickLabelStyle.fontSize,
hideOverlap: true,
/** Min/Max time label always visible **/
/* alignMinLabel: 'left',
alignMaxLabel: 'right',
showMinLabel: true,
showMaxLabel: true, */
formatter: (value: number, _index: number, extra: {level: number}) => {
const unit = tsToFormatTimeUnit(value);
const format = ticksFormat[unit];
const formatted = datePipe.transform(value, format);
if (extra.level > 0) {
return `{primary|${formatted}}`;
} else {
return formatted;
id,
settings,
option: {
mainType: 'xAxis',
show: settings.show,
type: 'time',
scale: true,
position: settings.position,
id,
name: utils.customTranslation(settings.label, settings.label),
nameLocation: 'middle',
nameTextStyle: {
color: xAxisNameStyle.color,
fontStyle: xAxisNameStyle.fontStyle,
fontWeight: xAxisNameStyle.fontWeight,
fontFamily: xAxisNameStyle.fontFamily,
fontSize: xAxisNameStyle.fontSize
},
axisPointer: {
shadowStyle: {
color: id === 'main' ? 'rgba(210,219,238,0.2)' : 'rgba(150,150,150,0.1)'
}
}
},
axisLine: {
show: settings.showLine,
onZero: false,
lineStyle: {
color: prepareChartThemeColor(settings.lineColor, darkMode, 'axis.line')
}
},
splitLine: {
show: settings.showSplitLines,
lineStyle: {
color: prepareChartThemeColor(settings.splitLinesColor, darkMode, 'axis.splitLine')
}
},
min,
max,
bandWidthCalculator: timeAxisBandWidthCalculator
},
axisTick: {
show: settings.showTicks,
lineStyle: {
color: prepareChartThemeColor(settings.ticksColor, darkMode, 'axis.ticks')
}
},
axisLabel: {
show: settings.showTickLabels,
color: xAxisTickLabelStyle.color,
fontStyle: xAxisTickLabelStyle.fontStyle,
fontWeight: xAxisTickLabelStyle.fontWeight,
fontFamily: xAxisTickLabelStyle.fontFamily,
fontSize: xAxisTickLabelStyle.fontSize,
hideOverlap: true,
/** Min/Max time label always visible **/
/* alignMinLabel: 'left',
alignMaxLabel: 'right',
showMinLabel: true,
showMaxLabel: true, */
formatter: (value: number, _index: number, extra: {level: number}) => {
const unit = tsToFormatTimeUnit(value);
const format = ticksFormat[unit];
const formatted = datePipe.transform(value, format);
if (extra.level > 0) {
return `{primary|${formatted}}`;
} else {
return formatted;
}
}
},
axisLine: {
show: settings.showLine,
onZero: false,
lineStyle: {
color: prepareChartThemeColor(settings.lineColor, darkMode, 'axis.line')
}
},
splitLine: {
show: settings.showSplitLines,
lineStyle: {
color: prepareChartThemeColor(settings.splitLinesColor, darkMode, 'axis.splitLine')
}
},
min,
max,
bandWidthCalculator: timeAxisBandWidthCalculator
}
};
};
@ -1098,6 +1142,13 @@ export const createTimeSeriesVisualMapOption = (settings: TimeSeriesChartVisualM
} : undefined
});
export const updateXAxisTimeWindow = (option: XAXisOption,
timeWindow: WidgetTimewindow) => {
option.min = timeWindow.minTime;
option.max = timeWindow.maxTime;
(option as any).tbTimeWindow = timeWindow;
};
export const generateChartData = (dataItems: TimeSeriesChartDataItem[],
thresholdItems: TimeSeriesChartThresholdItem[],
stack: boolean,
@ -1262,6 +1313,7 @@ const generateChartSeries = (dataItems: TimeSeriesChartDataItem[],
};
export const updateDarkMode = (options: EChartsOption, settings: TimeSeriesChartSettings,
xAxisList: TimeSeriesChartXAxis[],
yAxisList: TimeSeriesChartYAxis[],
dataItems: TimeSeriesChartDataItem[],
darkMode: boolean): EChartsOption => {
@ -1278,12 +1330,14 @@ export const updateDarkMode = (options: EChartsOption, settings: TimeSeriesChart
}
}
if (Array.isArray(options.xAxis)) {
for (const xAxis of options.xAxis) {
xAxis.nameTextStyle.color = prepareChartThemeColor(settings.xAxis.labelColor, darkMode, 'axis.label');
xAxis.axisLabel.color = prepareChartThemeColor(settings.xAxis.tickLabelColor, darkMode, 'axis.tickLabel');
xAxis.axisLine.lineStyle.color = prepareChartThemeColor(settings.xAxis.lineColor, darkMode, 'axis.line');
xAxis.axisTick.lineStyle.color = prepareChartThemeColor(settings.xAxis.ticksColor, darkMode, 'axis.ticks');
xAxis.splitLine.lineStyle.color = prepareChartThemeColor(settings.xAxis.splitLinesColor, darkMode, 'axis.splitLine');
for (let i = 0; i < options.xAxis.length; i++) {
const xAxis = options.xAxis[i];
const xAxisSettings = xAxisList[i].settings;
xAxis.nameTextStyle.color = prepareChartThemeColor(xAxisSettings.labelColor, darkMode, 'axis.label');
xAxis.axisLabel.color = prepareChartThemeColor(xAxisSettings.tickLabelColor, darkMode, 'axis.tickLabel');
xAxis.axisLine.lineStyle.color = prepareChartThemeColor(xAxisSettings.lineColor, darkMode, 'axis.line');
xAxis.axisTick.lineStyle.color = prepareChartThemeColor(xAxisSettings.ticksColor, darkMode, 'axis.ticks');
xAxis.splitLine.lineStyle.color = prepareChartThemeColor(xAxisSettings.splitLinesColor, darkMode, 'axis.splitLine');
}
}
for (const item of dataItems) {
@ -1322,6 +1376,7 @@ const createTimeSeriesChartSeries = (item: TimeSeriesChartDataItem,
seriesOption = {
id: item.id,
dataGroupId: item.id,
xAxisIndex: item.xAxisIndex,
yAxisIndex: item.yAxisIndex,
name: item.dataKey.label,
color: seriesColor,

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

@ -16,16 +16,16 @@
import { WidgetContext } from '@home/models/widget-component.models';
import {
AxisPosition,
calculateThresholdsOffset,
createTimeSeriesVisualMapOption,
createTimeSeriesXAxisOption,
createTimeSeriesXAxis,
createTimeSeriesYAxis,
defaultTimeSeriesChartYAxisSettings,
generateChartData,
LineSeriesStepType,
parseThresholdData,
SeriesLabelPosition,
TimeSeriesChartAxis,
TimeSeriesChartDataItem,
timeSeriesChartDefaultSettings,
timeSeriesChartKeyDefaultSettings,
@ -38,16 +38,17 @@ import {
TimeSeriesChartThresholdItem,
TimeSeriesChartThresholdType,
TimeSeriesChartType,
TimeSeriesChartXAxis,
TimeSeriesChartYAxis,
TimeSeriesChartYAxisId,
TimeSeriesChartYAxisSettings,
updateDarkMode
updateDarkMode,
updateXAxisTimeWindow
} from '@home/components/widget/lib/chart/time-series-chart.models';
import { ResizeObserver } from '@juggle/resize-observer';
import {
adjustTimeAxisExtentToData,
calculateXAxisHeight,
calculateYAxisWidth,
calculateAxisSize,
createTooltipValueFormatFunction,
ECharts,
echartsModule,
@ -58,8 +59,7 @@ import {
EChartsTooltipValueFormatFunction,
getAxisExtent,
getFocusedSeriesIndex,
measureXAxisNameHeight,
measureYAxisNameWidth,
measureAxisNameSize,
toNamedData
} from '@home/components/widget/lib/chart/echarts-widget.models';
import { DateFormatProcessor } from '@shared/models/widget-settings.models';
@ -120,6 +120,10 @@ export class TbTimeSeriesChart {
private readonly settings: TimeSeriesChartSettings;
private readonly comparisonEnabled: boolean;
private readonly stackMode: boolean;
private xAxisList: TimeSeriesChartXAxis[] = [];
private yAxisList: TimeSeriesChartYAxis[] = [];
private dataItems: TimeSeriesChartDataItem[] = [];
private thresholdItems: TimeSeriesChartThresholdItem[] = [];
@ -163,13 +167,16 @@ export class TbTimeSeriesChart {
this.settings = mergeDeep({} as TimeSeriesChartSettings,
timeSeriesChartDefaultSettings,
this.inputSettings as TimeSeriesChartSettings);
this.comparisonEnabled = !!this.ctx.defaultSubscription.comparisonEnabled;
this.stackMode = !this.comparisonEnabled && this.settings.stack;
if (this.settings.states && this.settings.states.length) {
this.stateValueConverter = new TimeSeriesChartStateValueConverter(this.ctx.dashboard.utils, this.settings.states);
this.stateValueConverter = new TimeSeriesChartStateValueConverter(this.ctx.utilsService, this.settings.states);
this.tooltipValueFormatFunction = this.stateValueConverter.tooltipFormatter;
}
const $dashboardPageElement = this.ctx.$containerParent.parents('.tb-dashboard-page');
const dashboardPageElement = $dashboardPageElement.length ? $($dashboardPageElement[$dashboardPageElement.length-1]) : null;
this.darkMode = this.settings.darkMode || dashboardPageElement?.hasClass('dark');
this.setupXAxes();
this.setupYAxes();
this.setupData();
this.setupThresholds();
@ -216,14 +223,16 @@ export class TbTimeSeriesChart {
}
this.onResize();
if (this.timeSeriesChart) {
this.timeSeriesChartOptions.xAxis[0].min = this.ctx.defaultSubscription.timeWindow.minTime;
this.timeSeriesChartOptions.xAxis[0].max = this.ctx.defaultSubscription.timeWindow.maxTime;
this.timeSeriesChartOptions.xAxis[0].tbTimeWindow = this.ctx.defaultSubscription.timeWindow;
updateXAxisTimeWindow(this.xAxisList[0].option, this.ctx.defaultSubscription.timeWindow);
if (this.noAggregation) {
this.timeSeriesChartOptions.tooltip[0].axisPointer.type = 'line';
} else {
this.timeSeriesChartOptions.tooltip[0].axisPointer.type = 'shadow';
}
if (this.comparisonEnabled) {
updateXAxisTimeWindow(this.xAxisList[1].option, this.ctx.defaultSubscription.comparisonTimeWindow);
}
this.timeSeriesChartOptions.xAxis = this.xAxisList.map(axis => axis.option);
if (this.hasVisualMap) {
(this.timeSeriesChartOptions.visualMap as PiecewiseVisualMapOption).selected = this.visualMapSelectedRanges;
}
@ -300,7 +309,7 @@ export class TbTimeSeriesChart {
this.timeSeriesChartOptions.yAxis = this.yAxisList.map(axis => axis.option);
mergeList.push('yAxis');
}
this.timeSeriesChart.setOption(this.timeSeriesChartOptions, this.settings.stack ? {notMerge: true} : {replaceMerge: mergeList});
this.timeSeriesChart.setOption(this.timeSeriesChartOptions, this.stackMode ? {notMerge: true} : {replaceMerge: mergeList});
this.updateAxes();
dataKey.hidden = !enable;
if (enable) {
@ -343,7 +352,7 @@ export class TbTimeSeriesChart {
this.darkMode = darkMode;
if (this.timeSeriesChart) {
this.timeSeriesChartOptions = updateDarkMode(this.timeSeriesChartOptions,
this.settings, this.yAxisList, this.dataItems, darkMode);
this.settings, this.xAxisList, this.yAxisList, this.dataItems, darkMode);
this.timeSeriesChart.setOption(this.timeSeriesChartOptions);
}
}
@ -392,12 +401,16 @@ export class TbTimeSeriesChart {
if (!Object.keys(this.settings.yAxes).includes(yAxisId)) {
yAxisId = 'default';
}
const comparisonItem = this.comparisonEnabled && dataKey.isAdditional;
const xAxisIndex = comparisonItem ? 1 : 0;
this.dataItems.push({
id: this.nextComponentId(),
units,
decimals,
xAxisIndex,
yAxisId,
yAxisIndex: this.getYAxisIndex(yAxisId),
comparisonItem,
datasource,
dataKey,
data: namedData,
@ -488,6 +501,18 @@ export class TbTimeSeriesChart {
this.subscribeForEntityThresholds(thresholdDatasources);
}
private setupXAxes(): void {
const mainXAxis = createTimeSeriesXAxis('main', this.settings.xAxis, this.ctx.defaultSubscription.timeWindow.minTime,
this.ctx.defaultSubscription.timeWindow.maxTime, this.ctx.date, this.ctx.utilsService, this.darkMode);
this.xAxisList.push(mainXAxis);
if (this.comparisonEnabled) {
const comparisonXAxis = createTimeSeriesXAxis('comparison', this.settings.comparisonXAxis,
this.ctx.defaultSubscription.comparisonTimeWindow.minTime, this.ctx.defaultSubscription.comparisonTimeWindow.maxTime,
this.ctx.date, this.ctx.utilsService, this.darkMode);
this.xAxisList.push(comparisonXAxis);
}
}
private setupYAxes(): void {
const yAxisSettingsList = Object.values(this.settings.yAxes);
yAxisSettingsList.sort((a1, a2) => a1.order - a2.order);
@ -501,7 +526,7 @@ export class TbTimeSeriesChart {
axisSettings.ticksGenerator = this.stateValueConverter.ticksGenerator;
axisSettings.ticksFormatter = this.stateValueConverter.ticksFormatter;
}
const yAxis = createTimeSeriesYAxis(units, decimals, axisSettings, this.darkMode);
const yAxis = createTimeSeriesYAxis(units, decimals, axisSettings, this.ctx.utilsService, this.darkMode);
this.yAxisList.push(yAxis);
}
}
@ -592,10 +617,7 @@ export class TbTimeSeriesChart {
right: this.settings.dataZoom ? 5 : 0,
bottom: this.minBottomOffset()
}],
xAxis: [
createTimeSeriesXAxisOption(this.settings.xAxis, this.ctx.defaultSubscription.timeWindow.minTime,
this.ctx.defaultSubscription.timeWindow.maxTime, this.ctx.date, this.darkMode)
],
xAxis: this.xAxisList.map(axis => axis.option),
yAxis: this.yAxisList.map(axis => axis.option),
dataZoom: [
{
@ -655,10 +677,10 @@ export class TbTimeSeriesChart {
private updateSeries(): void {
this.timeSeriesChartOptions.series = generateChartData(this.dataItems, this.thresholdItems,
this.settings.stack,
this.stackMode,
this.noAggregation,
this.barRenderSharedContext, this.darkMode);
if (this.stateData) {
if (this.stateData && !this.comparisonEnabled) {
adjustTimeAxisExtentToData(this.timeSeriesChartOptions.xAxis[0], this.dataItems,
this.ctx.defaultSubscription.timeWindow.minTime,
this.ctx.defaultSubscription.timeWindow.maxTime);
@ -667,37 +689,26 @@ export class TbTimeSeriesChart {
private updateAxes(lazy = true) {
const leftAxisList = this.yAxisList.filter(axis => axis.option.position === 'left');
let res = this.updateYAxisOffset(leftAxisList);
let res = this.updateAxisOffset(leftAxisList);
let leftOffset = res.offset + (!res.offset && this.settings.dataZoom ? 5 : 0);
let changed = res.changed;
const rightAxisList = this.yAxisList.filter(axis => axis.option.position === 'right');
res = this.updateYAxisOffset(rightAxisList);
res = this.updateAxisOffset(rightAxisList);
let rightOffset = res.offset + (!res.offset && this.settings.dataZoom ? 5 : 0);
changed = changed || res.changed;
let bottomOffset = this.minBottomOffset();
const minTopOffset = this.minTopOffset();
let topOffset = minTopOffset;
if (this.timeSeriesChartOptions.xAxis[0].show) {
const xAxisHeight = calculateXAxisHeight(this.timeSeriesChart);
if (this.timeSeriesChartOptions.xAxis[0].position === AxisPosition.bottom) {
bottomOffset += xAxisHeight;
} else {
topOffset = Math.max(minTopOffset, xAxisHeight);
}
if (this.settings.xAxis.label) {
const nameHeight = measureXAxisNameHeight(this.timeSeriesChart, this.timeSeriesChartOptions.xAxis[0].name);
if (this.timeSeriesChartOptions.xAxis[0].position === AxisPosition.bottom) {
bottomOffset += nameHeight;
} else {
topOffset = Math.max(minTopOffset, xAxisHeight + nameHeight);
}
const nameGap = xAxisHeight;
if (this.timeSeriesChartOptions.xAxis[0].nameGap !== nameGap) {
this.timeSeriesChartOptions.xAxis[0].nameGap = nameGap;
changed = true;
}
}
}
const topAxisList = this.xAxisList.filter(axis => axis.option.position === 'top');
res = this.updateAxisOffset(topAxisList);
const topOffset = Math.max(res.offset, minTopOffset);
changed = changed || res.changed;
const bottomAxisList = this.xAxisList.filter(axis => axis.option.position === 'bottom');
res = this.updateAxisOffset(bottomAxisList);
bottomOffset += res.offset;
changed = changed || res.changed;
const thresholdsOffset = calculateThresholdsOffset(this.timeSeriesChart, this.thresholdItems, this.yAxisList);
leftOffset = Math.max(leftOffset, thresholdsOffset[0]);
@ -715,6 +726,7 @@ export class TbTimeSeriesChart {
}
if (changed) {
this.timeSeriesChartOptions.yAxis = this.yAxisList.map(axis => axis.option);
this.timeSeriesChartOptions.xAxis = this.xAxisList.map(axis => axis.option);
this.timeSeriesChart.setOption(this.timeSeriesChartOptions, {replaceMerge: ['yAxis', 'xAxis', 'grid'], lazyUpdate: lazy});
}
if (this.yAxisList.length) {
@ -730,45 +742,45 @@ export class TbTimeSeriesChart {
}
}
private updateYAxisOffset(axisList: TimeSeriesChartYAxis[]): {offset: number; changed: boolean} {
private updateAxisOffset(axisList: TimeSeriesChartAxis[]): {offset: number; changed: boolean} {
const result = {offset: 0, changed: false};
let width = 0;
for (const yAxis of axisList) {
const newWidth = calculateYAxisWidth(this.timeSeriesChart, yAxis.id);
if (width && newWidth) {
let size = 0;
for (const axis of axisList) {
const newSize = calculateAxisSize(this.timeSeriesChart, axis.option.mainType, axis.id);
if (size && newSize) {
result.offset += 5;
}
width = newWidth;
const showLine = !!width && yAxis.settings.showLine;
if (yAxis.option.axisLine.show !== showLine) {
yAxis.option.axisLine.show = showLine;
size = newSize;
const showLine = !!size && axis.settings.showLine;
if (axis.option.axisLine.show !== showLine) {
axis.option.axisLine.show = showLine;
result.changed = true;
}
if (yAxis.option.offset !== result.offset) {
yAxis.option.offset = result.offset;
if (axis.option.offset !== result.offset) {
axis.option.offset = result.offset;
result.changed = true;
}
if (yAxis.settings.label) {
if (!width) {
if (yAxis.option.name) {
yAxis.option.name = null;
if (axis.settings.label) {
if (!size) {
if (axis.option.name) {
axis.option.name = null;
result.changed = true;
}
} else {
if (!yAxis.option.name) {
yAxis.option.name = yAxis.settings.label;
if (!axis.option.name) {
axis.option.name = axis.settings.label;
result.changed = true;
}
const nameGap = width;
if (yAxis.option.nameGap !== nameGap) {
yAxis.option.nameGap = nameGap;
const nameGap = size;
if (axis.option.nameGap !== nameGap) {
axis.option.nameGap = nameGap;
result.changed = true;
}
const nameWidth = measureYAxisNameWidth(this.timeSeriesChart, yAxis.id, yAxis.settings.label);
result.offset += nameWidth;
const nameSize = measureAxisNameSize(this.timeSeriesChart, axis.option.mainType, axis.id, axis.settings.label);
result.offset += nameSize;
}
}
result.offset += width;
result.offset += size;
}
return result;
}

12
ui-ngx/src/app/modules/home/components/widget/lib/flot-widget.models.ts

@ -18,11 +18,10 @@
/// <reference path="../../../../../../../src/typings/jquery.flot.typings.d.ts" />
import {
DataKey,
DataKey, DataKeySettingsWithComparison,
Datasource,
DatasourceData,
FormattedData,
JsonSettingsSchema,
LegendConfig
} from '@shared/models/widget.models';
import { DataKeyType } from '@shared/models/telemetry/telemetry.models';
@ -219,13 +218,7 @@ export interface TbFlotKeyThreshold {
color: string;
}
export interface TbFlotKeyComparisonSettings {
showValuesForComparison: boolean;
comparisonValuesLabel: string;
color: string;
}
export interface TbFlotKeySettings {
export interface TbFlotKeySettings extends DataKeySettingsWithComparison {
excludeFromStacking: boolean;
hideDataByDefault: boolean;
disableDataHiding: boolean;
@ -249,7 +242,6 @@ export interface TbFlotKeySettings {
axisPosition: TbFlotYAxisPosition;
axisTicksFormatter: string;
thresholds: TbFlotKeyThreshold[];
comparisonSettings: TbFlotKeyComparisonSettings;
}
export interface TbFlotLatestKeySettings {

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

@ -66,6 +66,35 @@
helpId="widget/lib/flot/tooltip_value_format_fn">
</tb-js-func>
</div>
<div *ngIf="comparisonEnabled" class="tb-form-panel tb-slide-toggle" formGroupName="comparisonSettings">
<div class="tb-form-panel-title" translate>widgets.time-series-chart.comparison.settings</div>
<mat-expansion-panel class="tb-settings" [expanded]="timeSeriesChartKeySettingsForm.get('comparisonSettings.showValuesForComparison').value"
[disabled]="!timeSeriesChartKeySettingsForm.get('comparisonSettings.showValuesForComparison').value">
<mat-expansion-panel-header fxLayout="row wrap">
<mat-panel-title>
<mat-slide-toggle class="mat-slide" formControlName="showValuesForComparison" (click)="$event.stopPropagation()"
fxLayoutAlign="center">
{{ 'widgets.time-series-chart.comparison.show-values-for-comparison' | translate }}
</mat-slide-toggle>
</mat-panel-title>
</mat-expansion-panel-header>
<ng-template matExpansionPanelContent>
<div class="tb-form-row">
<div class="fixed-title-width" translate>widgets.time-series-chart.comparison.comparison-values-label</div>
<mat-form-field fxFlex appearance="outline" subscriptSizing="dynamic">
<input matInput formControlName="comparisonValuesLabel" placeholder="{{ 'widgets.time-series-chart.comparison.comparison-values-label-auto' | translate }}">
</mat-form-field>
</div>
<div class="tb-form-row space-between">
<div>{{ 'widgets.time-series-chart.comparison.comparison-data-color' | translate }}</div>
<tb-color-input asBoxInput
colorClearButton
formControlName="color">
</tb-color-input>
</div>
</ng-template>
</mat-expansion-panel>
</div>
</ng-container>
<ng-template #chartTypeTitle>
<div class="tb-form-panel-title">{{ timeSeriesChartTypeTranslations.get(chartType) | translate }}</div>

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

@ -54,6 +54,8 @@ export class TimeSeriesChartKeySettingsComponent extends WidgetSettingsComponent
yAxisIds: TimeSeriesChartYAxisId[];
comparisonEnabled: boolean;
functionScopeVariables = this.widgetService.getWidgetScopeVariables();
constructor(protected store: Store<AppState>,
@ -73,6 +75,7 @@ export class TimeSeriesChartKeySettingsComponent extends WidgetSettingsComponent
}
const widgetSettings = (widgetConfig.config?.settings || {}) as TimeSeriesChartWidgetSettings;
this.yAxisIds = widgetSettings.yAxes ? Object.keys(widgetSettings.yAxes) : ['default'];
this.comparisonEnabled = !!widgetSettings.comparisonEnabled;
}
protected defaultSettings(): WidgetSettings {
@ -94,16 +97,23 @@ export class TimeSeriesChartKeySettingsComponent extends WidgetSettingsComponent
lineSettings: [seriesSettings.lineSettings, []],
barSettings: [seriesSettings.barSettings, []],
tooltipValueFormatter: [seriesSettings.tooltipValueFormatter, []],
comparisonSettings: this.fb.group({
showValuesForComparison: [seriesSettings.comparisonSettings?.showValuesForComparison, []],
comparisonValuesLabel: [seriesSettings.comparisonSettings?.comparisonValuesLabel, []],
color: [seriesSettings.comparisonSettings?.color, []]
})
});
}
protected validatorTriggers(): string[] {
return ['showInLegend', 'type'];
return ['showInLegend', 'type', 'comparisonSettings.showValuesForComparison'];
}
protected updateValidators(_emitEvent: boolean) {
const showInLegend: boolean = this.timeSeriesChartKeySettingsForm.get('showInLegend').value;
const type: TimeSeriesChartSeriesType = this.timeSeriesChartKeySettingsForm.get('type').value;
const showValuesForComparison: boolean =
this.timeSeriesChartKeySettingsForm.get('comparisonSettings').get('showValuesForComparison').value;
if (showInLegend) {
this.timeSeriesChartKeySettingsForm.get('dataHiddenByDefault').enable();
} else {
@ -117,5 +127,17 @@ export class TimeSeriesChartKeySettingsComponent extends WidgetSettingsComponent
this.timeSeriesChartKeySettingsForm.get('lineSettings').disable();
this.timeSeriesChartKeySettingsForm.get('barSettings').enable();
}
if (this.comparisonEnabled) {
this.timeSeriesChartKeySettingsForm.get('comparisonSettings').enable({emitEvent: false});
if (showValuesForComparison) {
this.timeSeriesChartKeySettingsForm.get('comparisonSettings').get('comparisonValuesLabel').enable();
this.timeSeriesChartKeySettingsForm.get('comparisonSettings').get('color').enable();
} else {
this.timeSeriesChartKeySettingsForm.get('comparisonSettings').get('comparisonValuesLabel').disable();
this.timeSeriesChartKeySettingsForm.get('comparisonSettings').get('color').disable();
}
} else {
this.timeSeriesChartKeySettingsForm.get('comparisonSettings').disable({emitEvent: false});
}
}
}

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

@ -16,6 +16,46 @@
-->
<ng-container [formGroup]="timeSeriesChartWidgetSettingsForm">
<div class="tb-form-panel">
<div class="tb-form-row no-border no-padding column-xs">
<mat-slide-toggle class="mat-slide fixed-title-width" formControlName="comparisonEnabled">
{{ 'widgets.time-series-chart.comparison.comparison' | translate }}
</mat-slide-toggle>
<div fxFlex fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px">
<mat-form-field class="flex" appearance="outline" subscriptSizing="dynamic">
<mat-select formControlName="timeForComparison">
<mat-option [value]="'previousInterval'">
{{ 'widgets.chart.time-for-comparison-previous-interval' | translate }}
</mat-option>
<mat-option [value]="'days'">
{{ 'widgets.chart.time-for-comparison-days' | translate }}
</mat-option>
<mat-option [value]="'weeks'">
{{ 'widgets.chart.time-for-comparison-weeks' | translate }}
</mat-option>
<mat-option [value]="'months'">
{{ 'widgets.chart.time-for-comparison-months' | translate }}
</mat-option>
<mat-option [value]="'years'">
{{ 'widgets.chart.time-for-comparison-years' | translate }}
</mat-option>
<mat-option [value]="'customInterval'">
{{ 'widgets.chart.time-for-comparison-custom-interval' | translate }}
</mat-option>
</mat-select>
</mat-form-field>
<mat-form-field [fxShow]="timeSeriesChartWidgetSettingsForm.get('timeForComparison').value === 'customInterval'"
appearance="outline" class="number flex-lt-md" subscriptSizing="dynamic">
<input matInput formControlName="comparisonCustomIntervalValue" type="number" min="0" placeholder="{{ 'widget-config.set' | translate }}">
</mat-form-field>
<tb-time-series-chart-axis-settings-button
axisType="xAxis"
panelTitle="{{ 'widgets.time-series-chart.axis.comparison-x-axis-settings' | translate }}"
formControlName="comparisonXAxis">
</tb-time-series-chart-axis-settings-button>
</div>
</div>
</div>
<tb-time-series-chart-states-panel
*ngIf="chartType === TimeSeriesChartType.state"
formControlName="states">

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

@ -23,7 +23,7 @@ import {
WidgetSettings,
WidgetSettingsComponent
} from '@shared/models/widget.models';
import { UntypedFormBuilder, UntypedFormGroup } from '@angular/forms';
import { UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms';
import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state';
import { formatValue, isDefinedAndNotNull, mergeDeep } from '@core/utils';
@ -114,6 +114,11 @@ export class TimeSeriesChartWidgetSettingsComponent extends WidgetSettingsCompon
protected onSettingsSet(settings: WidgetSettings) {
this.timeSeriesChartWidgetSettingsForm = this.fb.group({
comparisonEnabled: [settings.comparisonEnabled, []],
timeForComparison: [settings.timeForComparison, []],
comparisonCustomIntervalValue: [settings.comparisonCustomIntervalValue, [Validators.min(0)]],
comparisonXAxis: [settings.comparisonXAxis, []],
yAxes: [settings.yAxes, []],
thresholds: [settings.thresholds, []],
@ -154,14 +159,25 @@ export class TimeSeriesChartWidgetSettingsComponent extends WidgetSettingsCompon
}
protected validatorTriggers(): string[] {
return ['showLegend', 'showTooltip', 'tooltipShowDate'];
return ['comparisonEnabled', 'showLegend', 'showTooltip', 'tooltipShowDate'];
}
protected updateValidators(emitEvent: boolean) {
const comparisonEnabled: boolean = this.timeSeriesChartWidgetSettingsForm.get('comparisonEnabled').value;
const showLegend: boolean = this.timeSeriesChartWidgetSettingsForm.get('showLegend').value;
const showTooltip: boolean = this.timeSeriesChartWidgetSettingsForm.get('showTooltip').value;
const tooltipShowDate: boolean = this.timeSeriesChartWidgetSettingsForm.get('tooltipShowDate').value;
if (comparisonEnabled) {
this.timeSeriesChartWidgetSettingsForm.get('timeForComparison').enable();
this.timeSeriesChartWidgetSettingsForm.get('comparisonCustomIntervalValue').enable();
this.timeSeriesChartWidgetSettingsForm.get('comparisonXAxis').enable();
} else {
this.timeSeriesChartWidgetSettingsForm.get('timeForComparison').disable();
this.timeSeriesChartWidgetSettingsForm.get('comparisonCustomIntervalValue').disable();
this.timeSeriesChartWidgetSettingsForm.get('comparisonXAxis').disable();
}
if (showLegend) {
this.timeSeriesChartWidgetSettingsForm.get('legendLabelFont').enable();
this.timeSeriesChartWidgetSettingsForm.get('legendLabelColor').enable();

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

@ -0,0 +1,28 @@
<!--
Copyright © 2016-2024 The Thingsboard Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<button type="button"
mat-stroked-button
color="primary"
[disabled]="disabled"
#matButton
(click)="openAxisSettingsPopup($event, matButton)"
matTooltip="{{ panelTitle }}"
matTooltipPosition="above">
{{ (axisType === 'xAxis' ? 'widgets.time-series-chart.axis.x-axis' : 'widgets.time-series-chart.axis.y-axis') | translate }}
<tb-icon matButtonIcon>settings</tb-icon>
</button>

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

@ -0,0 +1,108 @@
///
/// Copyright © 2016-2024 The Thingsboard Authors
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
import { Component, forwardRef, Input, OnInit, Renderer2, ViewContainerRef } from '@angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
import { MatButton } from '@angular/material/button';
import { TbPopoverService } from '@shared/components/popover.service';
import { coerceBoolean } from '@shared/decorators/coercion';
import { TimeSeriesChartAxisSettings } from '@home/components/widget/lib/chart/time-series-chart.models';
import {
TimeSeriesChartAxisSettingsPanelComponent
} from '@home/components/widget/lib/settings/common/chart/time-series-chart-axis-settings-panel.component';
@Component({
selector: 'tb-time-series-chart-axis-settings-button',
templateUrl: './time-series-chart-axis-settings-button.component.html',
styleUrls: [],
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => TimeSeriesChartAxisSettingsButtonComponent),
multi: true
}
]
})
export class TimeSeriesChartAxisSettingsButtonComponent implements OnInit, ControlValueAccessor {
@Input()
disabled: boolean;
@Input()
axisType: 'xAxis' | 'yAxis' = 'xAxis';
@Input()
panelTitle: string;
@Input()
@coerceBoolean()
advanced = false;
private modelValue: TimeSeriesChartAxisSettings;
private propagateChange = null;
constructor(private popoverService: TbPopoverService,
private renderer: Renderer2,
private viewContainerRef: ViewContainerRef) {}
ngOnInit(): void {
}
registerOnChange(fn: any): void {
this.propagateChange = fn;
}
registerOnTouched(_fn: any): void {
}
setDisabledState(isDisabled: boolean): void {
this.disabled = isDisabled;
}
writeValue(value: TimeSeriesChartAxisSettings): void {
this.modelValue = value;
}
openAxisSettingsPopup($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 = {
axisSettings: this.modelValue,
axisType: this.axisType,
panelTitle: this.panelTitle,
advanced: this.advanced
};
const axisSettingsPanelPopover = this.popoverService.displayPopover(trigger, this.renderer,
this.viewContainerRef, TimeSeriesChartAxisSettingsPanelComponent, ['leftOnly', 'leftTopOnly', 'leftBottomOnly'], true, null,
ctx,
{},
{}, {}, true);
axisSettingsPanelPopover.tbComponentRef.instance.popover = axisSettingsPanelPopover;
axisSettingsPanelPopover.tbComponentRef.instance.axisSettingsApplied.subscribe((axisSettings) => {
axisSettingsPanelPopover.hide();
this.modelValue = axisSettings;
this.propagateChange(this.modelValue);
});
}
}
}

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

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

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

@ -15,7 +15,7 @@
*/
@import '../../../../../../../../../scss/constants';
.tb-y-axis-settings-panel {
.tb-axis-settings-panel {
width: 530px;
display: flex;
flex-direction: column;
@ -23,7 +23,7 @@
@media #{$mat-lt-md} {
width: 90vw;
}
.tb-y-axis-settings-panel-content {
.tb-axis-settings-panel-content {
display: flex;
flex-direction: column;
gap: 16px;
@ -31,14 +31,14 @@
margin: -10px;
padding: 10px;
}
.tb-y-axis-settings-title {
.tb-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 {
.tb-axis-settings-panel-buttons {
height: 40px;
display: flex;
flex-direction: row;

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

@ -17,40 +17,49 @@
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 {
TimeSeriesChartAxisSettings,
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',
selector: 'tb-time-series-chart-axis-settings-panel',
templateUrl: './time-series-chart-axis-settings-panel.component.html',
providers: [],
styleUrls: ['./time-series-chart-y-axis-settings-panel.component.scss'],
styleUrls: ['./time-series-chart-axis-settings-panel.component.scss'],
encapsulation: ViewEncapsulation.None
})
export class TimeSeriesChartYAxisSettingsPanelComponent implements OnInit {
export class TimeSeriesChartAxisSettingsPanelComponent implements OnInit {
@Input()
yAxisSettings: TimeSeriesChartYAxisSettings;
axisType: 'xAxis' | 'yAxis' = 'xAxis';
@Input()
panelTitle: string;
@Input()
axisSettings: TimeSeriesChartAxisSettings;
@Input()
@coerceBoolean()
advanced = false;
@Input()
popover: TbPopoverComponent<TimeSeriesChartYAxisSettingsPanelComponent>;
popover: TbPopoverComponent<TimeSeriesChartAxisSettingsPanelComponent>;
@Output()
yAxisSettingsApplied = new EventEmitter<TimeSeriesChartYAxisSettings>();
axisSettingsApplied = new EventEmitter<TimeSeriesChartAxisSettings>();
yAxisSettingsFormGroup: UntypedFormGroup;
axisSettingsFormGroup: UntypedFormGroup;
constructor(private fb: UntypedFormBuilder) {
}
ngOnInit(): void {
this.yAxisSettingsFormGroup = this.fb.group(
this.axisSettingsFormGroup = this.fb.group(
{
yAxis: [this.yAxisSettings, []]
axis: [this.axisSettings, []]
}
);
}
@ -59,8 +68,8 @@ export class TimeSeriesChartYAxisSettingsPanelComponent implements OnInit {
this.popover?.hide();
}
applyYAxisSettings() {
const yAxisSettings = this.yAxisSettingsFormGroup.get('yAxis').getRawValue();
this.yAxisSettingsApplied.emit(yAxisSettings);
applyAxisSettings() {
const axisSettings = this.axisSettingsFormGroup.get('axis').getRawValue();
this.axisSettingsApplied.emit(axisSettings);
}
}

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

@ -36,9 +36,10 @@ 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';
TimeSeriesChartAxisSettingsPanelComponent
} from '@home/components/widget/lib/settings/common/chart/time-series-chart-axis-settings-panel.component';
import { deepClone } from '@core/utils';
import { TranslateService } from '@ngx-translate/core';
@Component({
selector: 'tb-time-series-chart-y-axis-row',
@ -76,6 +77,7 @@ export class TimeSeriesChartYAxisRowComponent implements ControlValueAccessor, O
private propagateChange = (_val: any) => {};
constructor(private fb: UntypedFormBuilder,
private translate: TranslateService,
private popoverService: TbPopoverService,
private renderer: Renderer2,
private viewContainerRef: ViewContainerRef,
@ -143,16 +145,18 @@ export class TimeSeriesChartYAxisRowComponent implements ControlValueAccessor, O
this.popoverService.hidePopover(trigger);
} else {
const ctx: any = {
yAxisSettings: deepClone(this.modelValue),
axisType: 'yAxis',
panelTitle: this.translate.instant('widgets.time-series-chart.axis.y-axis-settings'),
axisSettings: deepClone(this.modelValue),
advanced: this.advanced
};
const yAxisSettingsPanelPopover = this.popoverService.displayPopover(trigger, this.renderer,
this.viewContainerRef, TimeSeriesChartYAxisSettingsPanelComponent, ['leftOnly', 'leftTopOnly', 'leftBottomOnly'], true, null,
this.viewContainerRef, TimeSeriesChartAxisSettingsPanelComponent, ['leftOnly', 'leftTopOnly', 'leftBottomOnly'], true, null,
ctx,
{},
{}, {}, true);
yAxisSettingsPanelPopover.tbComponentRef.instance.popover = yAxisSettingsPanelPopover;
yAxisSettingsPanelPopover.tbComponentRef.instance.yAxisSettingsApplied.subscribe((yAxisSettings) => {
yAxisSettingsPanelPopover.tbComponentRef.instance.axisSettingsApplied.subscribe((yAxisSettings) => {
yAxisSettingsPanelPopover.hide();
this.modelValue = {...this.modelValue, ...yAxisSettings};
this.axisFormGroup.patchValue(

4
ui-ngx/src/app/modules/home/components/widget/lib/settings/common/data-key-input.component.html

@ -19,6 +19,7 @@
[class.tb-suffix-absolute]="!keysFormControl.value?.length">
<mat-chip-grid #chipList [formControl]="keysFormControl">
<mat-chip-row class="tb-datakey-chip" *ngIf="modelValue?.type"
[removable]="removable"
(removed)="removeKey()">
<div fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="4px" class="tb-attribute-chip">
<div class="tb-chip-labels">
@ -49,7 +50,8 @@
(click)="editKey()" mat-icon-button class="tb-mat-24">
<mat-icon class="tb-mat-18">edit</mat-icon>
</button>
<button matChipRemove
<button *ngIf="removable"
matChipRemove
type="button"
mat-icon-button class="tb-mat-24">
<mat-icon class="tb-mat-18">close</mat-icon>

7
ui-ngx/src/app/modules/home/components/widget/lib/settings/common/data-key-input.component.scss

@ -16,6 +16,13 @@
.tb-data-key-input {
.mat-mdc-form-field.tb-inline-field.tb-key-field {
width: 100%;
&.mat-form-field-appearance-fill {
.mdc-text-field--filled.mdc-text-field--disabled {
&:before {
opacity: 0;
}
}
}
.mat-mdc-text-field-wrapper:not(.mdc-text-field--outlined) {
padding-left: 8px;
padding-right: 0;

4
ui-ngx/src/app/modules/home/components/widget/lib/settings/common/data-key-input.component.ts

@ -98,6 +98,10 @@ export class DataKeyInputComponent implements ControlValueAccessor, OnInit, OnCh
@coerceBoolean()
editable = true;
@Input()
@coerceBoolean()
removable = true;
@Input()
datasourceType: DatasourceType;

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

@ -116,8 +116,8 @@ 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';
TimeSeriesChartAxisSettingsPanelComponent
} from '@home/components/widget/lib/settings/common/chart/time-series-chart-axis-settings-panel.component';
import {
TimeSeriesChartAnimationSettingsComponent
} from '@home/components/widget/lib/settings/common/chart/time-series-chart-animation-settings.component';
@ -139,6 +139,9 @@ import {
import {
TimeSeriesChartStatesPanelComponent
} from '@home/components/widget/lib/settings/common/chart/time-series-chart-states-panel.component';
import {
TimeSeriesChartAxisSettingsButtonComponent
} from '@home/components/widget/lib/settings/common/chart/time-series-chart-axis-settings-button.component';
@NgModule({
declarations: [
@ -184,7 +187,8 @@ import {
TimeSeriesNoAggregationBarWidthSettingsComponent,
TimeSeriesChartYAxesPanelComponent,
TimeSeriesChartYAxisRowComponent,
TimeSeriesChartYAxisSettingsPanelComponent,
TimeSeriesChartAxisSettingsPanelComponent,
TimeSeriesChartAxisSettingsButtonComponent,
TimeSeriesChartAnimationSettingsComponent,
TimeSeriesChartFillSettingsComponent,
TimeSeriesChartThresholdSettingsComponent,
@ -241,7 +245,8 @@ import {
TimeSeriesNoAggregationBarWidthSettingsComponent,
TimeSeriesChartYAxesPanelComponent,
TimeSeriesChartYAxisRowComponent,
TimeSeriesChartYAxisSettingsPanelComponent,
TimeSeriesChartAxisSettingsPanelComponent,
TimeSeriesChartAxisSettingsButtonComponent,
TimeSeriesChartAnimationSettingsComponent,
TimeSeriesChartFillSettingsComponent,
TimeSeriesChartThresholdSettingsComponent,

2
ui-ngx/src/app/modules/home/models/widget-component.models.ts

@ -103,6 +103,7 @@ import { UserId } from '@shared/models/id/user-id';
import { UserSettingsService } from '@core/http/user-settings.service';
import { DynamicComponentModule } from '@core/services/dynamic-component-factory.service';
import { DataKeySettingsFunction } from '@home/components/widget/config/data-keys.component.models';
import { UtilsService } from '@core/services/utils.service';
export interface IWidgetAction {
name: string;
@ -194,6 +195,7 @@ export class WidgetContext {
customDialog: CustomDialogService;
resourceService: ResourceService;
userSettingsService: UserSettingsService;
utilsService: UtilsService;
telemetryWsService: TelemetryWebsocketService;
telemetrySubscribers?: TelemetrySubscriber[];
date: DatePipe;

16
ui-ngx/src/app/shared/models/widget.models.ts

@ -21,7 +21,6 @@ import { AggregationType, ComparisonDuration, Timewindow } from '@shared/models/
import { EntityType } from '@shared/models/entity-type.models';
import { DataKeyType } from './telemetry/telemetry.models';
import { EntityId } from '@shared/models/id/entity-id';
import * as moment_ from 'moment';
import {
AlarmFilter,
AlarmFilterConfig,
@ -710,10 +709,23 @@ export const defaultWidgetAction = (setEntityId = true): WidgetAction => ({
export interface WidgetComparisonSettings {
comparisonEnabled?: boolean;
timeForComparison?: moment_.unitOfTime.DurationConstructor;
timeForComparison?: ComparisonDuration;
comparisonCustomIntervalValue?: number;
}
export interface DataKeyComparisonSettings {
showValuesForComparison: boolean;
comparisonValuesLabel: string;
color: string;
}
export interface DataKeySettingsWithComparison {
comparisonSettings?: DataKeyComparisonSettings;
}
export const isDataKeySettingsWithComparison = (settings: any): settings is DataKeySettingsWithComparison =>
'comparisonSettings' in settings;
export interface WidgetSettings {
[key: string]: any;
}

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

@ -6749,6 +6749,15 @@
"bar-width": "Bar width",
"bar-width-relative": "Percentage of time window",
"bar-width-absolute": "Absolute (ms)",
"comparison": {
"comparison": "Comparison",
"show": "Show",
"settings": "Comparison settings",
"show-values-for-comparison": "Show historical data for comparison",
"comparison-values-label": "Comparison key label",
"comparison-values-label-auto": "Auto",
"comparison-data-color": "Comparison data color"
},
"threshold": {
"thresholds": "Thresholds",
"source": "Source",
@ -6802,6 +6811,7 @@
"x-axis": "X axis",
"y-axis": "Y axis",
"y-axis-settings": "Y axis settings",
"comparison-x-axis-settings": "Comparison X axis settings",
"remove-y-axis": "Remove Y axis",
"id": "Id",
"label": "Label",

12
ui-ngx/src/form.scss

@ -216,6 +216,18 @@
flex: 1;
width: auto;
}
&.flex-xs {
@media #{$mat-xs} {
width: auto;
flex: 1;
}
}
&.flex-lt-md {
@media #{$mat-lt-md} {
width: auto;
flex: 1;
}
}
}
.fixed-title-width {
min-width: 200px;

Loading…
Cancel
Save