UI: Implement time series comparison widget settings.

This commit is contained in:
Igor Kulikov 2024-04-12 17:34:43 +03:00
parent 95ac620977
commit 82ab52889b
31 changed files with 890 additions and 65 deletions

View File

@ -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,

View File

@ -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>

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

@ -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>

View File

@ -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;
}
}
}

View File

@ -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);
}
}

View File

@ -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">

View File

@ -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;
}

View File

@ -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>

View File

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

View File

@ -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);

View File

@ -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,

View File

@ -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);
}
}

View File

@ -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>

View File

@ -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});
}
}
}

View File

@ -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">

View File

@ -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();

View File

@ -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>

View File

@ -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);
});
}
}
}

View File

@ -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>

View File

@ -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;

View File

@ -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);
}
}

View File

@ -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(

View File

@ -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>

View File

@ -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;

View File

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

View File

@ -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,

View File

@ -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;

View File

@ -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",

View File

@ -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;