UI: Implement time series comparison widget settings.
This commit is contained in:
		
							parent
							
								
									95ac620977
								
							
						
					
					
						commit
						82ab52889b
					
				@ -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,
 | 
			
		||||
 | 
			
		||||
@ -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>
 | 
			
		||||
@ -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;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@ -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);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -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>
 | 
			
		||||
@ -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;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@ -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);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -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">
 | 
			
		||||
 | 
			
		||||
@ -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;
 | 
			
		||||
  }
 | 
			
		||||
@ -135,6 +143,7 @@ export class TimeSeriesChartBasicConfigComponent extends BasicWidgetConfigCompon
 | 
			
		||||
      comparisonEnabled: [settings.comparisonEnabled, []],
 | 
			
		||||
      timeForComparison: [settings.timeForComparison, []],
 | 
			
		||||
      comparisonCustomIntervalValue: [settings.comparisonCustomIntervalValue, [Validators.min(0)]],
 | 
			
		||||
      comparisonXAxis: [settings.comparisonXAxis, []],
 | 
			
		||||
 | 
			
		||||
      thresholds: [settings.thresholds, []],
 | 
			
		||||
 | 
			
		||||
@ -187,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 {
 | 
			
		||||
@ -209,6 +219,7 @@ export class TimeSeriesChartBasicConfigComponent extends BasicWidgetConfigCompon
 | 
			
		||||
    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;
 | 
			
		||||
 | 
			
		||||
@ -254,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();
 | 
			
		||||
@ -345,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) {
 | 
			
		||||
@ -381,4 +416,6 @@ export class TimeSeriesChartBasicConfigComponent extends BasicWidgetConfigCompon
 | 
			
		||||
    processor.update(Date.now());
 | 
			
		||||
    return processor.formatted;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  protected readonly DatasourceType = DatasourceType;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -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>
 | 
			
		||||
 | 
			
		||||
@ -96,6 +96,10 @@ export class DataKeysPanelComponent implements ControlValueAccessor, OnInit, OnC
 | 
			
		||||
  @Input()
 | 
			
		||||
  deviceId: string;
 | 
			
		||||
 | 
			
		||||
  @Input()
 | 
			
		||||
  @coerceBoolean()
 | 
			
		||||
  hidePanel = false;
 | 
			
		||||
 | 
			
		||||
  @Input()
 | 
			
		||||
  @coerceBoolean()
 | 
			
		||||
  hideDataKeyColor = false;
 | 
			
		||||
 | 
			
		||||
@ -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);
 | 
			
		||||
 | 
			
		||||
@ -71,6 +71,7 @@ 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',
 | 
			
		||||
@ -932,6 +933,7 @@ export interface TimeSeriesChartXAxis extends TimeSeriesChartAxis {
 | 
			
		||||
export const createTimeSeriesYAxis = (units: string,
 | 
			
		||||
                                      decimals: number,
 | 
			
		||||
                                      settings: TimeSeriesChartYAxisSettings,
 | 
			
		||||
                                      utils: UtilsService,
 | 
			
		||||
                                      darkMode: boolean): TimeSeriesChartYAxis => {
 | 
			
		||||
  const yAxisTickLabelStyle = createChartTextStyle(settings.tickLabelFont,
 | 
			
		||||
    settings.tickLabelColor, darkMode, 'axis.tickLabel');
 | 
			
		||||
@ -986,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: {
 | 
			
		||||
@ -1044,6 +1046,7 @@ 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');
 | 
			
		||||
@ -1060,7 +1063,7 @@ export const createTimeSeriesXAxis = (id: string,
 | 
			
		||||
      scale: true,
 | 
			
		||||
      position: settings.position,
 | 
			
		||||
      id,
 | 
			
		||||
      name: settings.label,
 | 
			
		||||
      name: utils.customTranslation(settings.label, settings.label),
 | 
			
		||||
      nameLocation: 'middle',
 | 
			
		||||
      nameTextStyle: {
 | 
			
		||||
        color: xAxisNameStyle.color,
 | 
			
		||||
 | 
			
		||||
@ -170,7 +170,7 @@ export class TbTimeSeriesChart {
 | 
			
		||||
    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');
 | 
			
		||||
@ -503,12 +503,12 @@ export class TbTimeSeriesChart {
 | 
			
		||||
 | 
			
		||||
  private setupXAxes(): void {
 | 
			
		||||
    const mainXAxis = createTimeSeriesXAxis('main', this.settings.xAxis, this.ctx.defaultSubscription.timeWindow.minTime,
 | 
			
		||||
      this.ctx.defaultSubscription.timeWindow.maxTime, this.ctx.date, this.darkMode);
 | 
			
		||||
      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.darkMode);
 | 
			
		||||
        this.ctx.date, this.ctx.utilsService, this.darkMode);
 | 
			
		||||
      this.xAxisList.push(comparisonXAxis);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
@ -526,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);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@ -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>
 | 
			
		||||
 | 
			
		||||
@ -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 {
 | 
			
		||||
@ -103,12 +106,14 @@ export class TimeSeriesChartKeySettingsComponent extends WidgetSettingsComponent
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  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 {
 | 
			
		||||
@ -122,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});
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -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">
 | 
			
		||||
 | 
			
		||||
@ -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();
 | 
			
		||||
 | 
			
		||||
@ -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>
 | 
			
		||||
@ -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);
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -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>
 | 
			
		||||
@ -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;
 | 
			
		||||
@ -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);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@ -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(
 | 
			
		||||
 | 
			
		||||
@ -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>
 | 
			
		||||
 | 
			
		||||
@ -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;
 | 
			
		||||
 | 
			
		||||
@ -98,6 +98,10 @@ export class DataKeyInputComponent implements ControlValueAccessor, OnInit, OnCh
 | 
			
		||||
  @coerceBoolean()
 | 
			
		||||
  editable = true;
 | 
			
		||||
 | 
			
		||||
  @Input()
 | 
			
		||||
  @coerceBoolean()
 | 
			
		||||
  removable = true;
 | 
			
		||||
 | 
			
		||||
  @Input()
 | 
			
		||||
  datasourceType: DatasourceType;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -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,
 | 
			
		||||
 | 
			
		||||
@ -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;
 | 
			
		||||
 | 
			
		||||
@ -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",
 | 
			
		||||
 | 
			
		||||
@ -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…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user