Browse Source

UI: Implement Time series chart widget config.

pull/10315/head
Igor Kulikov 2 years ago
parent
commit
0ebd22be2d
  1. 10
      application/src/main/data/json/system/widget_types/time_series_chart.json
  2. 3
      ui-ngx/src/app/modules/home/components/dashboard-page/add-widget-dialog.component.ts
  3. 3
      ui-ngx/src/app/modules/home/components/dashboard-page/edit-widget.component.ts
  4. 12
      ui-ngx/src/app/modules/home/components/widget/config/basic/basic-widget-config.module.ts
  5. 3
      ui-ngx/src/app/modules/home/components/widget/config/basic/cards/aggregated-data-keys-panel.component.ts
  6. 236
      ui-ngx/src/app/modules/home/components/widget/config/basic/chart/time-series-chart-basic-config.component.html
  7. 314
      ui-ngx/src/app/modules/home/components/widget/config/basic/chart/time-series-chart-basic-config.component.ts
  8. 15
      ui-ngx/src/app/modules/home/components/widget/config/basic/common/data-key-row.component.html
  9. 18
      ui-ngx/src/app/modules/home/components/widget/config/basic/common/data-key-row.component.scss
  10. 44
      ui-ngx/src/app/modules/home/components/widget/config/basic/common/data-key-row.component.ts
  11. 2
      ui-ngx/src/app/modules/home/components/widget/config/basic/common/data-keys-panel.component.html
  12. 4
      ui-ngx/src/app/modules/home/components/widget/config/basic/common/data-keys-panel.component.scss
  13. 13
      ui-ngx/src/app/modules/home/components/widget/config/basic/common/data-keys-panel.component.ts
  14. 1
      ui-ngx/src/app/modules/home/components/widget/config/data-key-config.component.html
  15. 27
      ui-ngx/src/app/modules/home/components/widget/config/data-key-config.component.ts
  16. 5
      ui-ngx/src/app/modules/home/components/widget/config/data-keys.component.models.ts
  17. 15
      ui-ngx/src/app/modules/home/components/widget/config/data-keys.component.ts
  18. 3
      ui-ngx/src/app/modules/home/components/widget/config/datasource.component.html
  19. 6
      ui-ngx/src/app/modules/home/components/widget/config/datasource.component.ts
  20. 9
      ui-ngx/src/app/modules/home/components/widget/config/datasources.component.ts
  21. 12
      ui-ngx/src/app/modules/home/components/widget/config/widget-config.component.models.ts
  22. 7
      ui-ngx/src/app/modules/home/components/widget/lib/cards/aggregated-value-card-widget.component.ts
  23. 7
      ui-ngx/src/app/modules/home/components/widget/lib/cards/value-chart-card-widget.component.ts
  24. 9
      ui-ngx/src/app/modules/home/components/widget/lib/chart/echarts-widget.models.ts
  25. 316
      ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart.models.ts
  26. 24
      ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart.ts
  27. 2
      ui-ngx/src/app/modules/home/components/widget/lib/maps/map-widget2.ts
  28. 36
      ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/time-series-chart-key-settings.component.html
  29. 82
      ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/time-series-chart-key-settings.component.ts
  30. 7
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/legend-config.component.html
  31. 28
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/legend-config.component.ts
  32. 12
      ui-ngx/src/app/modules/home/components/widget/lib/settings/widget-settings.module.ts
  33. 9
      ui-ngx/src/app/modules/home/components/widget/widget-component.service.ts
  34. 9
      ui-ngx/src/app/modules/home/components/widget/widget-config.component.ts
  35. 2
      ui-ngx/src/app/modules/home/models/widget-component.models.ts
  36. 2
      ui-ngx/src/app/shared/models/widget-settings.models.ts
  37. 2
      ui-ngx/src/app/shared/models/widget.models.ts
  38. 23
      ui-ngx/src/assets/locale/locale.constant-en_US.json
  39. 21
      ui-ngx/src/form.scss

10
application/src/main/data/json/system/widget_types/time_series_chart.json

File diff suppressed because one or more lines are too long

3
ui-ngx/src/app/modules/home/components/dashboard-page/add-widget-dialog.component.ts

@ -29,6 +29,7 @@ import { WidgetConfigComponentData, WidgetInfo } from '@home/models/widget-compo
import { isDefined, isDefinedAndNotNull, isString } from '@core/utils';
import { TranslateService } from '@ngx-translate/core';
import { WidgetConfigComponent } from '@home/components/widget/widget-config.component';
import { DataKeySettingsFunction } from '@home/components/widget/config/data-keys.component.models';
export interface AddWidgetDialogData {
dashboard: Dashboard;
@ -97,6 +98,7 @@ export class AddWidgetDialogComponent extends DialogComponent<AddWidgetDialogCom
const rawDataKeySettingsSchema = widgetInfo.typeDataKeySettingsSchema || widgetInfo.dataKeySettingsSchema;
const rawLatestDataKeySettingsSchema = widgetInfo.typeLatestDataKeySettingsSchema || widgetInfo.latestDataKeySettingsSchema;
const typeParameters = widgetInfo.typeParameters;
const dataKeySettingsFunction: DataKeySettingsFunction = typeParameters?.dataKeySettingsFunction;
const actionSources = widgetInfo.actionSources;
const isDataEnabled = isDefined(widgetInfo.typeParameters) ? !widgetInfo.typeParameters.useCustomDatasources : true;
let settingsSchema;
@ -129,6 +131,7 @@ export class AddWidgetDialogComponent extends DialogComponent<AddWidgetDialogCom
settingsSchema,
dataKeySettingsSchema,
latestDataKeySettingsSchema,
dataKeySettingsFunction,
settingsDirective: widgetInfo.settingsDirective,
dataKeySettingsDirective: widgetInfo.dataKeySettingsDirective,
latestDataKeySettingsDirective: widgetInfo.latestDataKeySettingsDirective,

3
ui-ngx/src/app/modules/home/components/dashboard-page/edit-widget.component.ts

@ -27,6 +27,7 @@ import { WidgetConfigComponentData } from '../../models/widget-component.models'
import { isDefined, isDefinedAndNotNull, isString } from '@core/utils';
import { UntypedFormBuilder, UntypedFormGroup } from '@angular/forms';
import { WidgetConfigComponent } from '@home/components/widget/widget-config.component';
import { DataKeySettingsFunction } from '@home/components/widget/config/data-keys.component.models';
@Component({
selector: 'tb-edit-widget',
@ -133,6 +134,7 @@ export class EditWidgetComponent extends PageComponent implements OnInit, OnChan
const rawDataKeySettingsSchema = widgetInfo.typeDataKeySettingsSchema || widgetInfo.dataKeySettingsSchema;
const rawLatestDataKeySettingsSchema = widgetInfo.typeLatestDataKeySettingsSchema || widgetInfo.latestDataKeySettingsSchema;
const typeParameters = widgetInfo.typeParameters;
const dataKeySettingsFunction: DataKeySettingsFunction = typeParameters?.dataKeySettingsFunction;
const actionSources = widgetInfo.actionSources;
const isDataEnabled = isDefined(widgetInfo.typeParameters) ? !widgetInfo.typeParameters.useCustomDatasources : true;
let settingsSchema;
@ -165,6 +167,7 @@ export class EditWidgetComponent extends PageComponent implements OnInit, OnChan
settingsSchema,
dataKeySettingsSchema,
latestDataKeySettingsSchema,
dataKeySettingsFunction,
settingsDirective: widgetInfo.settingsDirective,
dataKeySettingsDirective: widgetInfo.dataKeySettingsDirective,
latestDataKeySettingsDirective: widgetInfo.latestDataKeySettingsDirective,

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

@ -107,6 +107,9 @@ import { SliderBasicConfigComponent } from '@home/components/widget/config/basic
import {
ToggleButtonBasicConfigComponent
} from '@home/components/widget/config/basic/button/toggle-button-basic-config.component';
import {
TimeSeriesChartBasicConfigComponent
} from '@home/components/widget/config/basic/chart/time-series-chart-basic-config.component';
@NgModule({
declarations: [
@ -141,7 +144,8 @@ import {
CommandButtonBasicConfigComponent,
PowerButtonBasicConfigComponent,
SliderBasicConfigComponent,
ToggleButtonBasicConfigComponent
ToggleButtonBasicConfigComponent,
TimeSeriesChartBasicConfigComponent
],
imports: [
CommonModule,
@ -180,7 +184,8 @@ import {
CommandButtonBasicConfigComponent,
PowerButtonBasicConfigComponent,
SliderBasicConfigComponent,
ToggleButtonBasicConfigComponent
ToggleButtonBasicConfigComponent,
TimeSeriesChartBasicConfigComponent
]
})
export class BasicWidgetConfigModule {
@ -213,5 +218,6 @@ export const basicWidgetConfigComponentsMap: {[key: string]: Type<IBasicWidgetCo
'tb-command-button-basic-config': CommandButtonBasicConfigComponent,
'tb-power-button-basic-config': PowerButtonBasicConfigComponent,
'tb-slider-basic-config': SliderBasicConfigComponent,
'tb-toggle-button-basic-config': ToggleButtonBasicConfigComponent
'tb-toggle-button-basic-config': ToggleButtonBasicConfigComponent,
'tb-time-series-chart-basic-config': TimeSeriesChartBasicConfigComponent
};

3
ui-ngx/src/app/modules/home/components/widget/config/basic/cards/aggregated-data-keys-panel.component.ts

@ -152,7 +152,8 @@ export class AggregatedDataKeysPanelComponent implements ControlValueAccessor, O
}
addKey() {
const dataKey = this.callbacks.generateDataKey(this.keyName, this.dataKeyType, null);
const dataKey = this.callbacks.generateDataKey(this.keyName, this.dataKeyType, null,
true,null);
dataKey.decimals = 0;
dataKey.settings = {...aggregatedValueCardDefaultKeySettings};
const keysArray = this.keysListFormGroup.get('keys') as UntypedFormArray;

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

@ -0,0 +1,236 @@
<!--
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.
-->
<ng-container [formGroup]="timeSeriesChartWidgetConfigForm">
<tb-timewindow-config-panel formControlName="timewindowConfig">
</tb-timewindow-config-panel>
<tb-datasources
[configMode]="basicMode"
hideDatasourceLabel
hideDataKeys
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 }}"
showTimeSeriesType
hideSourceSelection
[datasourceType]="datasource?.type"
[deviceId]="datasource?.deviceId"
[entityAliasId]="datasource?.entityAliasId"
formControlName="series">
</tb-data-keys-panel>
<div class="tb-form-panel">
<div class="tb-form-panel-title">TODO: Thresholds</div>
</div>
<div class="tb-form-panel">
<div class="tb-form-panel-title" translate>widget-config.appearance</div>
<div class="tb-form-row column-xs">
<mat-slide-toggle class="mat-slide fixed-title-width" formControlName="showTitle">
{{ 'widget-config.title' | translate }}
</mat-slide-toggle>
<div fxFlex fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px">
<mat-form-field class="flex" appearance="outline" subscriptSizing="dynamic">
<input matInput formControlName="title" placeholder="{{ 'widget-config.set' | translate }}">
</mat-form-field>
<tb-font-settings formControlName="titleFont"
clearButton
[previewText]="timeSeriesChartWidgetConfigForm.get('title').value"
[initialPreviewStyle]="widgetConfig.config.titleStyle">
</tb-font-settings>
<tb-color-input asBoxInput
colorClearButton
formControlName="titleColor">
</tb-color-input>
</div>
</div>
<div class="tb-form-row column-xs">
<mat-slide-toggle class="mat-slide fixed-title-width" formControlName="showIcon">
{{ 'widget-config.card-icon' | translate }}
</mat-slide-toggle>
<div fxFlex fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px">
<mat-form-field appearance="outline" class="flex number" subscriptSizing="dynamic">
<input matInput type="number" min="0" formControlName="iconSize" placeholder="{{ 'widget-config.set' | translate }}">
</mat-form-field>
<tb-css-unit-select fxFlex formControlName="iconSizeUnit"></tb-css-unit-select>
<tb-material-icon-select asBoxInput
iconClearButton
[color]="timeSeriesChartWidgetConfigForm.get('iconColor').value"
formControlName="icon">
</tb-material-icon-select>
<tb-color-input asBoxInput
colorClearButton
formControlName="iconColor">
</tb-color-input>
</div>
</div>
</div>
<div class="tb-form-panel">
<div class="tb-form-panel-title" translate>widgets.time-series-chart.chart</div>
<div class="tb-form-row">
<mat-slide-toggle class="mat-slide" formControlName="dataZoom">
{{ 'widgets.time-series-chart.data-zoom' | translate }}
</mat-slide-toggle>
</div>
<div class="tb-form-row">
<mat-slide-toggle class="mat-slide" formControlName="stack">
<div tb-hint-tooltip-icon="{{'widgets.time-series-chart.stack-mode-hint' | translate}}">
{{ 'widgets.time-series-chart.stack-mode' | translate }}
</div>
</mat-slide-toggle>
</div>
</div>
<div class="tb-form-panel">
<div class="tb-form-panel-title" translate>widgets.time-series-chart.axes</div>
<div class="tb-form-panel stroked">
TODO: Y Axis
</div>
<div class="tb-form-panel stroked">
TODO: X Axis
</div>
</div>
<div class="tb-form-panel tb-slide-toggle">
<mat-expansion-panel class="tb-settings" [expanded]="timeSeriesChartWidgetConfigForm.get('showLegend').value"
[disabled]="!timeSeriesChartWidgetConfigForm.get('showLegend').value">
<mat-expansion-panel-header fxLayout="row wrap">
<mat-panel-title>
<mat-slide-toggle class="mat-slide" formControlName="showLegend" (click)="$event.stopPropagation()"
fxLayoutAlign="center">
{{ 'widget-config.legend' | translate }}
</mat-slide-toggle>
</mat-panel-title>
</mat-expansion-panel-header>
<ng-template matExpansionPanelContent>
<div class="tb-form-row space-between">
<div>{{ 'legend.label' | translate }}</div>
<div fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px">
<tb-font-settings formControlName="legendLabelFont"
previewText="Temperature">
</tb-font-settings>
<tb-color-input asBoxInput
colorClearButton
formControlName="legendLabelColor">
</tb-color-input>
</div>
</div>
<tb-legend-config hideDirection
formControlName="legendConfig">
</tb-legend-config>
</ng-template>
</mat-expansion-panel>
</div>
<div class="tb-form-panel tb-slide-toggle">
<mat-expansion-panel class="tb-settings" [expanded]="timeSeriesChartWidgetConfigForm.get('showTooltip').value"
[disabled]="!timeSeriesChartWidgetConfigForm.get('showTooltip').value">
<mat-expansion-panel-header fxLayout="row wrap">
<mat-panel-title>
<mat-slide-toggle class="mat-slide" formControlName="showTooltip" (click)="$event.stopPropagation()"
fxLayoutAlign="center">
{{ 'widget-config.tooltip' | translate }}
</mat-slide-toggle>
</mat-panel-title>
</mat-expansion-panel-header>
<ng-template matExpansionPanelContent>
<div class="tb-form-row space-between">
<div>{{ 'tooltip.trigger' | translate }}</div>
<mat-chip-listbox class="center-stretch" formControlName="tooltipTrigger">
<mat-chip-option [selectable]="timeSeriesChartWidgetConfigForm.get('tooltipTrigger').value !== EChartsTooltipTrigger.point"
[value]="EChartsTooltipTrigger.point">{{ 'tooltip.trigger-point' | translate }}</mat-chip-option>
<mat-chip-option [selectable]="timeSeriesChartWidgetConfigForm.get('tooltipTrigger').value !== EChartsTooltipTrigger.axis"
[value]="EChartsTooltipTrigger.axis">{{ 'tooltip.trigger-axis' | translate }}</mat-chip-option>
</mat-chip-listbox>
</div>
<div class="tb-form-row space-between">
<div>{{ 'tooltip.value' | translate }}</div>
<div fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px">
<tb-font-settings formControlName="tooltipValueFont"
[previewText]="tooltipValuePreviewFn">
</tb-font-settings>
<tb-color-input asBoxInput
colorClearButton
formControlName="tooltipValueColor">
</tb-color-input>
</div>
</div>
<div class="tb-form-row column-xs">
<mat-slide-toggle class="mat-slide fixed-title-width" formControlName="tooltipShowDate">
{{ 'tooltip.date' | translate }}
</mat-slide-toggle>
<div fxFlex.gt-xs fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px">
<tb-date-format-select fxFlex excludeLastUpdateAgo formControlName="tooltipDateFormat"></tb-date-format-select>
<tb-font-settings formControlName="tooltipDateFont"
[previewText]="tooltipDatePreviewFn">
</tb-font-settings>
<tb-color-input asBoxInput
colorClearButton
formControlName="tooltipDateColor">
</tb-color-input>
</div>
</div>
<div class="tb-form-row">
<mat-slide-toggle class="mat-slide" formControlName="tooltipDateInterval">
<div tb-hint-tooltip-icon="{{'tooltip.show-date-time-interval-hint' | translate}}">
{{ 'tooltip.show-date-time-interval' | translate }}
</div>
</mat-slide-toggle>
</div>
<div class="tb-form-row space-between">
<div>{{ 'tooltip.background-color' | translate }}</div>
<tb-color-input asBoxInput
colorClearButton
formControlName="tooltipBackgroundColor">
</tb-color-input>
</div>
<div class="tb-form-row space-between">
<div>{{ 'tooltip.background-blur' | translate }}</div>
<mat-form-field appearance="outline" class="number" subscriptSizing="dynamic">
<input matInput formControlName="tooltipBackgroundBlur" type="number" min="0" step="1" placeholder="{{ 'widget-config.set' | translate }}">
<div matSuffix>px</div>
</mat-form-field>
</div>
</ng-template>
</mat-expansion-panel>
</div>
<div class="tb-form-panel">
<div class="tb-form-panel-title" translate>widget-config.card-appearance</div>
<div class="tb-form-row space-between">
<div>{{ 'widgets.background.background' | translate }}</div>
<tb-background-settings formControlName="background">
</tb-background-settings>
</div>
<div class="tb-form-row space-between column-lt-md">
<div translate>widget-config.show-card-buttons</div>
<mat-chip-listbox multiple formControlName="cardButtons">
<mat-chip-option value="fullscreen">{{ 'fullscreen.fullscreen' | translate }}</mat-chip-option>
</mat-chip-listbox>
</div>
<div class="tb-form-row space-between">
<div>{{ 'widget-config.card-border-radius' | translate }}</div>
<mat-form-field appearance="outline" subscriptSizing="dynamic">
<input matInput formControlName="borderRadius" placeholder="{{ 'widget-config.set' | translate }}">
</mat-form-field>
</div>
</div>
<tb-widget-actions-panel
formControlName="actions">
</tb-widget-actions-panel>
</ng-container>

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

@ -0,0 +1,314 @@
///
/// 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, Injector } from '@angular/core';
import { UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms';
import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state';
import { BasicWidgetConfigComponent } from '@home/components/widget/config/widget-config.component.models';
import { WidgetConfigComponentData } from '@home/models/widget-component.models';
import {
DataKey,
Datasource,
legendPositions,
legendPositionTranslationMap,
WidgetConfig,
} from '@shared/models/widget.models';
import { WidgetConfigComponent } from '@home/components/widget/widget-config.component';
import { DataKeyType } from '@shared/models/telemetry/telemetry.models';
import {
getTimewindowConfig,
setTimewindowConfig
} from '@home/components/widget/config/timewindow-config-panel.component';
import { formatValue, isUndefined, mergeDeep } from '@core/utils';
import {
cssSizeToStrSize,
DateFormatProcessor,
DateFormatSettings,
resolveCssSize
} from '@shared/models/widget-settings.models';
import {
timeSeriesChartWidgetDefaultSettings,
TimeSeriesChartWidgetSettings
} from '@home/components/widget/lib/chart/time-series-chart-widget.models';
import { ValueType } from '@shared/models/constants';
import { EChartsTooltipTrigger } from '@home/components/widget/lib/chart/echarts-widget.models';
@Component({
selector: 'tb-time-series-chart-basic-config',
templateUrl: './time-series-chart-basic-config.component.html',
styleUrls: ['../basic-config.scss']
})
export class TimeSeriesChartBasicConfigComponent extends BasicWidgetConfigComponent {
public get datasource(): Datasource {
const datasources: Datasource[] = this.timeSeriesChartWidgetConfigForm.get('datasources').value;
if (datasources && datasources.length) {
return datasources[0];
} else {
return null;
}
}
EChartsTooltipTrigger = EChartsTooltipTrigger;
legendPositions = legendPositions;
legendPositionTranslationMap = legendPositionTranslationMap;
timeSeriesChartWidgetConfigForm: UntypedFormGroup;
tooltipValuePreviewFn = this._tooltipValuePreviewFn.bind(this);
tooltipDatePreviewFn = this._tooltipDatePreviewFn.bind(this);
constructor(protected store: Store<AppState>,
protected widgetConfigComponent: WidgetConfigComponent,
private $injector: Injector,
private fb: UntypedFormBuilder) {
super(store, widgetConfigComponent);
}
protected configForm(): UntypedFormGroup {
return this.timeSeriesChartWidgetConfigForm;
}
protected defaultDataKeys(configData: WidgetConfigComponentData): DataKey[] {
return [{ name: 'temperature', label: 'Temperature', type: DataKeyType.timeseries, units: '°C', decimals: 0 }];
}
protected onConfigSet(configData: WidgetConfigComponentData) {
const settings: TimeSeriesChartWidgetSettings = mergeDeep<TimeSeriesChartWidgetSettings>({} as TimeSeriesChartWidgetSettings,
timeSeriesChartWidgetDefaultSettings, configData.config.settings as TimeSeriesChartWidgetSettings);
const iconSize = resolveCssSize(configData.config.iconSize);
this.timeSeriesChartWidgetConfigForm = this.fb.group({
timewindowConfig: [getTimewindowConfig(configData.config), []],
datasources: [configData.config.datasources, []],
series: [this.getSeries(configData.config.datasources), []],
thresholds: [settings.thresholds, []],
showTitle: [configData.config.showTitle, []],
title: [configData.config.title, []],
titleFont: [configData.config.titleFont, []],
titleColor: [configData.config.titleColor, []],
showIcon: [configData.config.showTitleIcon, []],
iconSize: [iconSize[0], [Validators.min(0)]],
iconSizeUnit: [iconSize[1], []],
icon: [configData.config.titleIcon, []],
iconColor: [configData.config.iconColor, []],
dataZoom: [settings.dataZoom, []],
stack: [settings.stack, []],
yAxis: [settings.yAxis, []],
xAxis: [settings.xAxis, []],
showLegend: [settings.showLegend, []],
legendLabelFont: [settings.legendLabelFont, []],
legendLabelColor: [settings.legendLabelColor, []],
legendConfig: [settings.legendConfig, []],
showTooltip: [settings.showTooltip, []],
tooltipTrigger: [settings.tooltipTrigger, []],
tooltipValueFont: [settings.tooltipValueFont, []],
tooltipValueColor: [settings.tooltipValueColor, []],
tooltipShowDate: [settings.tooltipShowDate, []],
tooltipDateFormat: [settings.tooltipDateFormat, []],
tooltipDateFont: [settings.tooltipDateFont, []],
tooltipDateColor: [settings.tooltipDateColor, []],
tooltipDateInterval: [settings.tooltipDateInterval, []],
tooltipBackgroundColor: [settings.tooltipBackgroundColor, []],
tooltipBackgroundBlur: [settings.tooltipBackgroundBlur, []],
background: [settings.background, []],
cardButtons: [this.getCardButtons(configData.config), []],
borderRadius: [configData.config.borderRadius, []],
actions: [configData.config.actions || {}, []]
});
}
protected prepareOutputConfig(config: any): WidgetConfigComponentData {
setTimewindowConfig(this.widgetConfig.config, config.timewindowConfig);
this.widgetConfig.config.datasources = config.datasources;
this.setSeries(config.series, this.widgetConfig.config.datasources);
this.widgetConfig.config.showTitle = config.showTitle;
this.widgetConfig.config.title = config.title;
this.widgetConfig.config.titleFont = config.titleFont;
this.widgetConfig.config.titleColor = config.titleColor;
this.widgetConfig.config.showTitleIcon = config.showIcon;
this.widgetConfig.config.iconSize = cssSizeToStrSize(config.iconSize, config.iconSizeUnit);
this.widgetConfig.config.titleIcon = config.icon;
this.widgetConfig.config.iconColor = config.iconColor;
this.widgetConfig.config.settings = this.widgetConfig.config.settings || {};
this.widgetConfig.config.settings.thresholds = config.thresholds;
this.widgetConfig.config.settings.dataZoom = config.dataZoom;
this.widgetConfig.config.settings.stack = config.stack;
this.widgetConfig.config.settings.yAxis = config.yAxis;
this.widgetConfig.config.settings.xAxis = config.xAxis;
this.widgetConfig.config.settings.showLegend = config.showLegend;
this.widgetConfig.config.settings.legendLabelFont = config.legendLabelFont;
this.widgetConfig.config.settings.legendLabelColor = config.legendLabelColor;
this.widgetConfig.config.settings.legendConfig = config.legendConfig;
this.widgetConfig.config.settings.showTooltip = config.showTooltip;
this.widgetConfig.config.settings.tooltipTrigger = config.tooltipTrigger;
this.widgetConfig.config.settings.tooltipValueFont = config.tooltipValueFont;
this.widgetConfig.config.settings.tooltipValueColor = config.tooltipValueColor;
this.widgetConfig.config.settings.tooltipShowDate = config.tooltipShowDate;
this.widgetConfig.config.settings.tooltipDateFormat = config.tooltipDateFormat;
this.widgetConfig.config.settings.tooltipDateFont = config.tooltipDateFont;
this.widgetConfig.config.settings.tooltipDateColor = config.tooltipDateColor;
this.widgetConfig.config.settings.tooltipDateInterval = config.tooltipDateInterval;
this.widgetConfig.config.settings.tooltipBackgroundColor = config.tooltipBackgroundColor;
this.widgetConfig.config.settings.tooltipBackgroundBlur = config.tooltipBackgroundBlur;
this.widgetConfig.config.settings.background = config.background;
this.setCardButtons(config.cardButtons, this.widgetConfig.config);
this.widgetConfig.config.borderRadius = config.borderRadius;
this.widgetConfig.config.actions = config.actions;
return this.widgetConfig;
}
protected validatorTriggers(): string[] {
return ['showTitle', 'showIcon', 'showLegend', 'showTooltip', 'tooltipShowDate'];
}
protected updateValidators(emitEvent: boolean, trigger?: string) {
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 (showTitle) {
this.timeSeriesChartWidgetConfigForm.get('title').enable();
this.timeSeriesChartWidgetConfigForm.get('titleFont').enable();
this.timeSeriesChartWidgetConfigForm.get('titleColor').enable();
this.timeSeriesChartWidgetConfigForm.get('showIcon').enable({emitEvent: false});
if (showIcon) {
this.timeSeriesChartWidgetConfigForm.get('iconSize').enable();
this.timeSeriesChartWidgetConfigForm.get('iconSizeUnit').enable();
this.timeSeriesChartWidgetConfigForm.get('icon').enable();
this.timeSeriesChartWidgetConfigForm.get('iconColor').enable();
} else {
this.timeSeriesChartWidgetConfigForm.get('iconSize').disable();
this.timeSeriesChartWidgetConfigForm.get('iconSizeUnit').disable();
this.timeSeriesChartWidgetConfigForm.get('icon').disable();
this.timeSeriesChartWidgetConfigForm.get('iconColor').disable();
}
} else {
this.timeSeriesChartWidgetConfigForm.get('title').disable();
this.timeSeriesChartWidgetConfigForm.get('titleFont').disable();
this.timeSeriesChartWidgetConfigForm.get('titleColor').disable();
this.timeSeriesChartWidgetConfigForm.get('showIcon').disable({emitEvent: false});
this.timeSeriesChartWidgetConfigForm.get('iconSize').disable();
this.timeSeriesChartWidgetConfigForm.get('iconSizeUnit').disable();
this.timeSeriesChartWidgetConfigForm.get('icon').disable();
this.timeSeriesChartWidgetConfigForm.get('iconColor').disable();
}
if (showLegend) {
this.timeSeriesChartWidgetConfigForm.get('legendLabelFont').enable();
this.timeSeriesChartWidgetConfigForm.get('legendLabelColor').enable();
this.timeSeriesChartWidgetConfigForm.get('legendConfig').enable();
} else {
this.timeSeriesChartWidgetConfigForm.get('legendLabelFont').disable();
this.timeSeriesChartWidgetConfigForm.get('legendLabelColor').disable();
this.timeSeriesChartWidgetConfigForm.get('legendConfig').disable();
}
if (showTooltip) {
this.timeSeriesChartWidgetConfigForm.get('tooltipTrigger').enable();
this.timeSeriesChartWidgetConfigForm.get('tooltipValueFont').enable();
this.timeSeriesChartWidgetConfigForm.get('tooltipValueColor').enable();
this.timeSeriesChartWidgetConfigForm.get('tooltipShowDate').enable({emitEvent: false});
this.timeSeriesChartWidgetConfigForm.get('tooltipBackgroundColor').enable();
this.timeSeriesChartWidgetConfigForm.get('tooltipBackgroundBlur').enable();
if (tooltipShowDate) {
this.timeSeriesChartWidgetConfigForm.get('tooltipDateFormat').enable();
this.timeSeriesChartWidgetConfigForm.get('tooltipDateFont').enable();
this.timeSeriesChartWidgetConfigForm.get('tooltipDateColor').enable();
this.timeSeriesChartWidgetConfigForm.get('tooltipDateInterval').enable();
} else {
this.timeSeriesChartWidgetConfigForm.get('tooltipDateFormat').disable();
this.timeSeriesChartWidgetConfigForm.get('tooltipDateFont').disable();
this.timeSeriesChartWidgetConfigForm.get('tooltipDateColor').disable();
this.timeSeriesChartWidgetConfigForm.get('tooltipDateInterval').disable();
}
} else {
this.timeSeriesChartWidgetConfigForm.get('tooltipValueFont').disable();
this.timeSeriesChartWidgetConfigForm.get('tooltipValueColor').disable();
this.timeSeriesChartWidgetConfigForm.get('tooltipShowDate').disable({emitEvent: false});
this.timeSeriesChartWidgetConfigForm.get('tooltipDateFormat').disable();
this.timeSeriesChartWidgetConfigForm.get('tooltipDateFont').disable();
this.timeSeriesChartWidgetConfigForm.get('tooltipDateColor').disable();
this.timeSeriesChartWidgetConfigForm.get('tooltipDateInterval').disable();
this.timeSeriesChartWidgetConfigForm.get('tooltipBackgroundColor').disable();
this.timeSeriesChartWidgetConfigForm.get('tooltipBackgroundBlur').disable();
}
}
private getSeries(datasources?: Datasource[]): DataKey[] {
if (datasources && datasources.length) {
return datasources[0].dataKeys || [];
}
return [];
}
private setSeries(series: DataKey[], datasources?: Datasource[]) {
if (datasources && datasources.length) {
datasources[0].dataKeys = series;
}
}
private getCardButtons(config: WidgetConfig): string[] {
const buttons: string[] = [];
if (isUndefined(config.enableFullscreen) || config.enableFullscreen) {
buttons.push('fullscreen');
}
return buttons;
}
private setCardButtons(buttons: string[], config: WidgetConfig) {
config.enableFullscreen = buttons.includes('fullscreen');
}
private _tooltipValuePreviewFn(): string {
return formatValue(22, 0, '°C', false);
}
private _tooltipDatePreviewFn(): string {
const dateFormat: DateFormatSettings = this.timeSeriesChartWidgetConfigForm.get('tooltipDateFormat').value;
const processor = DateFormatProcessor.fromSettings(this.$injector, dateFormat);
processor.update(Date.now());
return processor.formatted;
}
protected readonly ValueType = ValueType;
}

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

@ -158,6 +158,21 @@
<div *ngIf="singleRow" matSuffix fxHide.lt-md translate>widget-config.decimals-suffix</div>
</mat-form-field>
</div>
<div *ngIf="showTimeSeriesType" class="tb-time-series-type-field">
<mat-form-field *ngIf="displayUnitsOrDigits" class="tb-inline-field fixed-height" appearance="outline" subscriptSizing="dynamic">
<mat-select formControlName="timeSeriesType" panelClass="tb-time-series-type-field">
<mat-select-trigger>
<tb-icon *ngIf="keyRowFormGroup.get('timeSeriesType').value">
{{ timeSeriesChartSeriesTypeIcons.get(keyRowFormGroup.get('timeSeriesType').value) }}
</tb-icon>
</mat-select-trigger>
<mat-option *ngFor="let type of timeSeriesChartSeriesTypes" [value]="type" class="flex">
<tb-icon>{{ timeSeriesChartSeriesTypeIcons.get(type) }}</tb-icon>
<span>{{ timeSeriesChartSeriesTypeTranslations.get(type) | translate }}</span>
</mat-option>
</mat-select>
</mat-form-field>
</div>
<div *ngIf="!singleRow" class="tb-form-table-row-cell-buttons">
<div fxHide.lt-lg class="tb-settings-button">
<button *ngIf="modelValue"

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

@ -59,7 +59,7 @@
min-width: 100px;
}
.tb-color-field, .tb-units-field, .tb-decimals-field {
.tb-color-field, .tb-units-field, .tb-decimals-field, .tb-time-series-type-field {
display: flex;
flex-direction: row;
place-content: center;
@ -81,6 +81,14 @@
min-width: 60px;
}
.tb-time-series-type-field {
width: 60px;
min-width: 60px;
.mat-icon {
color: rgba(0,0,0,0.76);
}
}
.tb-label-field, .tb-units-field, .tb-color-field, .tb-decimals-field {
display: none;
@media #{$mat-gt-sm} {
@ -113,3 +121,11 @@
}
}
}
.tb-time-series-type-field {
.mat-mdc-option:not(.mdc-list-item--selected) {
.mat-icon {
color: rgba(0,0,0,0.54);
}
}
}

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

@ -52,7 +52,7 @@ import { DataKeyType } from '@shared/models/telemetry/telemetry.models';
import { AggregationType } from '@shared/models/time/time.models';
import { COMMA, ENTER, SEMICOLON } from '@angular/cdk/keycodes';
import { MatChipGrid, MatChipInputEvent } from '@angular/material/chips';
import { DataKeysCallbacks } from '@home/components/widget/config/data-keys.component.models';
import { DataKeysCallbacks, DataKeySettingsFunction } from '@home/components/widget/config/data-keys.component.models';
import { MatAutocomplete, MatAutocompleteTrigger } from '@angular/material/autocomplete';
import { Observable, of } from 'rxjs';
import { filter, map, mergeMap, publishReplay, refCount, share, tap } from 'rxjs/operators';
@ -68,6 +68,13 @@ import { IAliasController } from '@core/api/widget-api.models';
import { coerceBoolean } from '@shared/decorators/coercion';
import { alarmFields } from '@shared/models/alarm.models';
import { UtilsService } from '@core/services/utils.service';
import {
TimeSeriesChartKeySettings,
TimeSeriesChartSeriesType,
timeSeriesChartSeriesTypeIcons,
timeSeriesChartSeriesTypes,
timeSeriesChartSeriesTypeTranslations
} from '@home/components/widget/lib/chart/time-series-chart.models';
export const dataKeyValid = (key: DataKey): boolean => !!key && !!key.type && !!key.name;
@ -99,6 +106,10 @@ export class DataKeyRowComponent implements ControlValueAccessor, OnInit, OnChan
dataKeyTypes = DataKeyType;
widgetTypes = widgetType;
timeSeriesChartSeriesTypes = timeSeriesChartSeriesTypes;
timeSeriesChartSeriesTypeTranslations = timeSeriesChartSeriesTypeTranslations;
timeSeriesChartSeriesTypeIcons = timeSeriesChartSeriesTypeIcons;
separatorKeysCodes: number[] = [ENTER, COMMA, SEMICOLON];
@ViewChild('keyInput') keyInput: ElementRef<HTMLInputElement>;
@ -150,6 +161,10 @@ export class DataKeyRowComponent implements ControlValueAccessor, OnInit, OnChan
@coerceBoolean()
hideDecimals = false;
@Input()
@coerceBoolean()
showTimeSeriesType = false;
@Input()
@coerceBoolean()
singleRow = true;
@ -220,6 +235,10 @@ export class DataKeyRowComponent implements ControlValueAccessor, OnInit, OnChan
return this.widgetConfigComponent.modelValue?.latestDataKeySettingsDirective;
}
get dataKeySettingsFunction(): DataKeySettingsFunction {
return this.widgetConfigComponent.modelValue?.dataKeySettingsFunction;
}
get isEntityDatasource(): boolean {
return [DatasourceType.device, DatasourceType.entity].includes(this.datasourceType);
}
@ -272,6 +291,9 @@ export class DataKeyRowComponent implements ControlValueAccessor, OnInit, OnChan
() => this.clearKeySearchCache()
);
}
if (this.showTimeSeriesType) {
this.keyRowFormGroup.addControl('timeSeriesType', this.fb.control(null));
}
this.keyRowFormGroup.valueChanges.subscribe(
() => this.updateModel()
);
@ -353,6 +375,13 @@ export class DataKeyRowComponent implements ControlValueAccessor, OnInit, OnChan
latest: (value as any)?.latest
}, {emitEvent: false});
}
if (this.showTimeSeriesType) {
const settings = value?.settings as TimeSeriesChartKeySettings;
const timeSeriesType = settings?.type || TimeSeriesChartSeriesType.line;
this.keyRowFormGroup.patchValue({
timeSeriesType
}, {emitEvent: false});
}
this.keysFormControl.patchValue(this.modelValue ? [this.modelValue] : [], {emitEvent: false});
this.cd.markForCheck();
}
@ -413,6 +442,9 @@ export class DataKeyRowComponent implements ControlValueAccessor, OnInit, OnChan
this.keyRowFormGroup.get('color').patchValue(this.modelValue.color, {emitEvent: false});
this.keyRowFormGroup.get('units').patchValue(this.modelValue.units, {emitEvent: false});
this.keyRowFormGroup.get('decimals').patchValue(this.modelValue.decimals, {emitEvent: false});
if (this.showTimeSeriesType) {
this.keyRowFormGroup.get('timeSeriesType').patchValue(this.modelValue.settings?.type, {emitEvent: false});
}
this.updateModel();
this.cd.markForCheck();
}
@ -497,10 +529,14 @@ export class DataKeyRowComponent implements ControlValueAccessor, OnInit, OnChan
}
private addKeyFromChipValue(chip: DataKey) {
this.modelValue = this.callbacks.generateDataKey(chip.name, chip.type, this.dataKeySettingsSchema);
this.modelValue = this.callbacks.generateDataKey(chip.name, chip.type, this.dataKeySettingsSchema, this.isLatestDataKeys,
this.dataKeySettingsFunction);
if (!this.keyRowFormGroup.get('label').value) {
this.keyRowFormGroup.get('label').patchValue(this.modelValue.label, {emitEvent: false});
}
if (this.showTimeSeriesType) {
this.keyRowFormGroup.get('timeSeriesType').patchValue(this.modelValue.settings?.type, {emitEvent: false});
}
this.updateModel();
this.clearKeyChip('', false);
}
@ -516,6 +552,10 @@ export class DataKeyRowComponent implements ControlValueAccessor, OnInit, OnChan
if (this.modelValue !== null) {
const value: DataKey = this.keyRowFormGroup.value;
this.modelValue = {...this.modelValue, ...value};
if (this.showTimeSeriesType) {
this.modelValue.settings.type = (this.modelValue as any).timeSeriesType;
delete (this.modelValue as any).timeSeriesType;
}
}
this.propagateChange(this.modelValue);
}

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

@ -25,6 +25,7 @@
<div *ngIf="!hideDataKeyColor" class="tb-form-table-header-cell tb-color-header" translate>datakey.color</div>
<div *ngIf="!hideUnits" class="tb-form-table-header-cell tb-units-header" translate>widget-config.units-short</div>
<div *ngIf="!hideDecimals" class="tb-form-table-header-cell tb-decimals-header" translate>widget-config.decimals-short</div>
<div *ngIf="showTimeSeriesType" class="tb-form-table-header-cell tb-time-series-type-header" translate>widgets.time-series-chart.series.type</div>
<div class="tb-form-table-header-cell tb-actions-header"></div>
</div>
<div *ngIf="keysFormArray().controls.length; else noKeys" class="tb-form-table-body tb-drop-list"
@ -43,6 +44,7 @@
[hasAdditionalLatestDataKeys]="hasAdditionalLatestDataKeys"
[hideDataKeyColor]="hideDataKeyColor"
[hideDecimals]="hideDecimals"
[showTimeSeriesType]="showTimeSeriesType"
[hideUnits]="hideUnits"
[hideDataKeyDecimals]="hideDataKeyDecimals"
[hideDataKeyUnits]="hideDataKeyUnits"

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

@ -43,6 +43,10 @@
width: 60px;
min-width: 60px;
}
&.tb-time-series-type-header {
width: 60px;
min-width: 60px;
}
&.tb-actions-header {
width: 40px;
min-width: 40px;

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

@ -42,7 +42,7 @@ import { dataKeyRowValidator, dataKeyValid } from '@home/components/widget/confi
import { CdkDragDrop } from '@angular/cdk/drag-drop';
import { DataKeyType } from '@shared/models/telemetry/telemetry.models';
import { UtilsService } from '@core/services/utils.service';
import { DataKeysCallbacks } from '@home/components/widget/config/data-keys.component.models';
import { DataKeysCallbacks, DataKeySettingsFunction } from '@home/components/widget/config/data-keys.component.models';
import { coerceBoolean } from '@shared/decorators/coercion';
@Component({
@ -119,6 +119,10 @@ export class DataKeysPanelComponent implements ControlValueAccessor, OnInit, OnC
@coerceBoolean()
hideSourceSelection = false;
@Input()
@coerceBoolean()
showTimeSeriesType = false;
dataKeyType: DataKeyType;
keysListFormGroup: UntypedFormGroup;
@ -142,6 +146,10 @@ export class DataKeysPanelComponent implements ControlValueAccessor, OnInit, OnC
return this.widgetConfigComponent.modelValue?.dataKeySettingsSchema;
}
get dataKeySettingsFunction(): DataKeySettingsFunction {
return this.widgetConfigComponent.modelValue?.dataKeySettingsFunction;
}
get dragEnabled(): boolean {
return this.keysFormArray().controls.length > 1;
}
@ -256,7 +264,8 @@ export class DataKeysPanelComponent implements ControlValueAccessor, OnInit, OnC
}
addKey() {
const dataKey = this.callbacks.generateDataKey('', null, this.datakeySettingsSchema);
const dataKey = this.callbacks.generateDataKey('', null, this.datakeySettingsSchema,
false, this.dataKeySettingsFunction);
dataKey.label = '';
dataKey.decimals = 0;
if (this.hasAdditionalLatestDataKeys) {

1
ui-ngx/src/app/modules/home/components/widget/config/data-key-config.component.html

@ -197,6 +197,7 @@
[dashboard]="dashboard"
[aliasController]="aliasController"
[widget]="widget"
[widgetConfig]="widgetConfig"
formControlName="settings">
</tb-widget-settings>
</div>

27
ui-ngx/src/app/modules/home/components/widget/config/data-key-config.component.ts

@ -40,7 +40,7 @@ import { UtilsService } from '@core/services/utils.service';
import { TranslateService } from '@ngx-translate/core';
import { MatDialog } from '@angular/material/dialog';
import { EntityService } from '@core/http/entity.service';
import { DataKeysCallbacks } from '@home/components/widget/config/data-keys.component.models';
import { DataKeysCallbacks, DataKeySettingsFunction } from '@home/components/widget/config/data-keys.component.models';
import { DataKeyType } from '@shared/models/telemetry/telemetry.models';
import { Observable, of } from 'rxjs';
import { map, mergeMap, publishReplay, refCount, tap } from 'rxjs/operators';
@ -51,8 +51,10 @@ import { WidgetService } from '@core/http/widget.service';
import { Dashboard } from '@shared/models/dashboard.models';
import { IAliasController } from '@core/api/widget-api.models';
import { aggregationTranslations, AggregationType, ComparisonDuration } from '@shared/models/time/time.models';
import { genNextLabel } from '@core/utils';
import { genNextLabel, isDefinedAndNotNull } from '@core/utils';
import { coerceBoolean } from '@shared/decorators/coercion';
import { WidgetConfigComponentData } from '@home/models/widget-component.models';
import { WidgetComponentService } from '@home/components/widget/widget-component.service';
@Component({
selector: 'tb-data-key-config',
@ -155,6 +157,8 @@ export class DataKeyConfigComponent extends PageComponent implements OnInit, Con
modelValue: DataKey;
widgetConfig: WidgetConfigComponentData;
private propagateChange = null;
public dataKeyFormGroup: UntypedFormGroup;
@ -180,12 +184,31 @@ export class DataKeyConfigComponent extends PageComponent implements OnInit, Con
private dialog: MatDialog,
private translate: TranslateService,
private widgetService: WidgetService,
private widgetComponentService: WidgetComponentService,
private fb: UntypedFormBuilder) {
super(store);
this.functionScopeVariables = this.widgetService.getWidgetScopeVariables();
}
ngOnInit(): void {
const widgetInfo = this.widgetComponentService.getInstantWidgetInfo(this.widget);
const typeParameters = widgetInfo.typeParameters;
const dataKeySettingsFunction: DataKeySettingsFunction = typeParameters?.dataKeySettingsFunction;
this.widgetConfig = {
widgetName: widgetInfo.widgetName,
config: this.widget.config,
widgetType: this.widget.type,
typeParameters,
dataKeySettingsFunction,
settingsDirective: widgetInfo.settingsDirective,
dataKeySettingsDirective: widgetInfo.dataKeySettingsDirective,
latestDataKeySettingsDirective: widgetInfo.latestDataKeySettingsDirective,
hasBasicMode: isDefinedAndNotNull(widgetInfo.hasBasicMode) ? widgetInfo.hasBasicMode : false,
basicModeDirective: widgetInfo.basicModeDirective
} as WidgetConfigComponentData;
this.alarmKeys = [];
for (const name of Object.keys(alarmFields)) {
this.alarmKeys.push({

5
ui-ngx/src/app/modules/home/components/widget/config/data-keys.component.models.ts

@ -18,8 +18,11 @@ import { DataKeyType } from '@shared/models/telemetry/telemetry.models';
import { DataKey, JsonSettingsSchema } from '@shared/models/widget.models';
import { Observable } from 'rxjs';
export type DataKeySettingsFunction = (key: DataKey, isLatestDataKey: boolean) => any;
export interface DataKeysCallbacks {
generateDataKey: (chip: any, type: DataKeyType, datakeySettingsSchema: JsonSettingsSchema) => DataKey;
generateDataKey: (chip: any, type: DataKeyType, datakeySettingsSchema: JsonSettingsSchema,
isLatestDataKey: boolean, dataKeySettingsFunction: DataKeySettingsFunction) => DataKey;
fetchEntityKeys: (entityAliasId: string, types: Array<DataKeyType>) => Observable<Array<DataKey>>;
fetchEntityKeysForDevice: (deviceId: string, types: Array<DataKeyType>) => Observable<Array<DataKey>>;
}

15
ui-ngx/src/app/modules/home/components/widget/config/data-keys.component.ts

@ -52,7 +52,7 @@ import { coerceBooleanProperty } from '@angular/cdk/coercion';
import { DataKeyType } from '@shared/models/telemetry/telemetry.models';
import { DataKey, DatasourceType, JsonSettingsSchema, Widget, widgetType } from '@shared/models/widget.models';
import { IAliasController } from '@core/api/widget-api.models';
import { DataKeysCallbacks } from './data-keys.component.models';
import { DataKeysCallbacks, DataKeySettingsFunction } from './data-keys.component.models';
import { alarmFields } from '@shared/models/alarm.models';
import { UtilsService } from '@core/services/utils.service';
import { ErrorStateMatcher } from '@angular/material/core';
@ -139,6 +139,10 @@ export class DataKeysComponent implements ControlValueAccessor, OnInit, OnChange
@Input()
optDataKeys: boolean;
@Input()
@coerceBoolean()
latestDataKeys = false;
@Input()
@coerceBoolean()
simpleDataKeysLabel = false;
@ -149,6 +153,9 @@ export class DataKeysComponent implements ControlValueAccessor, OnInit, OnChange
@Input()
datakeySettingsSchema: JsonSettingsSchema;
@Input()
datakeySettingsFunction: DataKeySettingsFunction;
@Input()
dataKeySettingsDirective: string;
@ -361,7 +368,8 @@ export class DataKeysComponent implements ControlValueAccessor, OnInit, OnChange
if (this.widgetType === widgetType.alarm) {
this.keys = this.utils.getDefaultAlarmDataKeys();
} else if (this.isCountDatasource) {
this.keys = [this.callbacks.generateDataKey('count', DataKeyType.count, this.datakeySettingsSchema)];
this.keys = [this.callbacks.generateDataKey('count', DataKeyType.count, this.datakeySettingsSchema,
this.latestDataKeys, this.datakeySettingsFunction)];
} else {
this.keys = [];
}
@ -447,7 +455,8 @@ export class DataKeysComponent implements ControlValueAccessor, OnInit, OnChange
}
private addFromChipValue(chip: DataKey) {
const key = this.callbacks.generateDataKey(chip.name, chip.type, this.datakeySettingsSchema);
const key = this.callbacks.generateDataKey(chip.name, chip.type, this.datakeySettingsSchema, this.latestDataKeys,
this.datakeySettingsFunction);
this.addKey(key);
}

3
ui-ngx/src/app/modules/home/components/widget/config/datasource.component.html

@ -72,6 +72,7 @@
[aliasController]="aliasController"
[datakeySettingsSchema]="dataKeySettingsSchema"
[dataKeySettingsDirective]="dataKeySettingsDirective"
[datakeySettingsFunction]="dataKeySettingsFunction"
[dashboard]="dashboard"
[widget]="widget"
[callbacks]="dataKeysCallbacks"
@ -82,10 +83,12 @@
<tb-data-keys *ngIf="hasAdditionalLatestDataKeys" class="tb-data-keys" fxFlex
[widgetType]="widgetTypes.latest"
[datasourceType]="datasourceFormGroup.get('type').value"
latestDataKeys
[optDataKeys]="true"
[aliasController]="aliasController"
[datakeySettingsSchema]="latestDataKeySettingsSchema"
[dataKeySettingsDirective]="latestDataKeySettingsDirective"
[datakeySettingsFunction]="dataKeySettingsFunction"
[dashboard]="dashboard"
[widget]="widget"
[callbacks]="dataKeysCallbacks"

6
ui-ngx/src/app/modules/home/components/widget/config/datasource.component.ts

@ -39,7 +39,7 @@ import { WidgetConfigComponent } from '@home/components/widget/widget-config.com
import { IAliasController } from '@core/api/widget-api.models';
import { EntityAliasSelectCallbacks } from '@home/components/alias/entity-alias-select.component.models';
import { FilterSelectCallbacks } from '@home/components/filter/filter-select.component.models';
import { DataKeysCallbacks } from '@home/components/widget/config/data-keys.component.models';
import { DataKeysCallbacks, DataKeySettingsFunction } from '@home/components/widget/config/data-keys.component.models';
import { EntityType } from '@shared/models/entity-type.models';
import { DatasourcesComponent } from '@home/components/widget/config/datasources.component';
@ -119,6 +119,10 @@ export class DatasourceComponent implements ControlValueAccessor, OnInit, Valida
return this.widgetConfigComponent.modelValue?.latestDataKeySettingsDirective;
}
public get dataKeySettingsFunction(): DataKeySettingsFunction {
return this.widgetConfigComponent.modelValue?.dataKeySettingsFunction;
}
public get dashboard(): Dashboard {
return this.widgetConfigComponent.dashboard;
}

9
ui-ngx/src/app/modules/home/components/widget/config/datasources.component.ts

@ -39,7 +39,7 @@ import { CdkDragDrop } from '@angular/cdk/drag-drop';
import { deepClone } from '@core/utils';
import { DataKeyType } from '@shared/models/telemetry/telemetry.models';
import { UtilsService } from '@core/services/utils.service';
import { DataKeysCallbacks } from '@home/components/widget/config/data-keys.component.models';
import { DataKeysCallbacks, DataKeySettingsFunction } from '@home/components/widget/config/data-keys.component.models';
import { TranslateService } from '@ngx-translate/core';
import { coerceBoolean } from '@shared/decorators/coercion';
@ -337,7 +337,8 @@ export class DatasourcesComponent implements ControlValueAccessor, OnInit, Valid
let newDatasource: Datasource;
if (this.widgetConfigComponent.functionsOnly) {
newDatasource = deepClone(this.utils.getDefaultDatasource(this.dataKeySettingsSchema.schema));
newDatasource.dataKeys = [this.dataKeysCallbacks.generateDataKey('Sin', DataKeyType.function, this.dataKeySettingsSchema)];
newDatasource.dataKeys = [this.dataKeysCallbacks.generateDataKey('Sin', DataKeyType.function, this.dataKeySettingsSchema,
false, this.dataKeySettingsFunction)];
} else {
const type = this.basicMode ? this.datasourcesMode : DatasourceType.entity;
newDatasource = { type,
@ -354,6 +355,10 @@ export class DatasourcesComponent implements ControlValueAccessor, OnInit, Valid
return this.widgetConfigComponent.modelValue?.dataKeySettingsSchema;
}
private get dataKeySettingsFunction(): DataKeySettingsFunction {
return this.widgetConfigComponent.modelValue?.dataKeySettingsFunction;
}
private get dataKeysCallbacks(): DataKeysCallbacks {
return this.widgetConfigComponent.widgetConfigCallbacks;
}

12
ui-ngx/src/app/modules/home/components/widget/config/widget-config.component.models.ts

@ -23,9 +23,8 @@ import { PageComponent } from '@shared/components/page.component';
import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state';
import { AbstractControl, UntypedFormGroup } from '@angular/forms';
import { DataKey, DatasourceType, KeyInfo, WidgetConfigMode, widgetType } from '@shared/models/widget.models';
import { DataKey, DatasourceType, WidgetConfigMode, widgetType } from '@shared/models/widget.models';
import { WidgetConfigComponent } from '@home/components/widget/widget-config.component';
import { DataKeyType } from '@shared/models/telemetry/telemetry.models';
import { isDefinedAndNotNull } from '@core/utils';
import { IAliasController } from '@core/api/widget-api.models';
@ -187,22 +186,23 @@ export abstract class BasicWidgetConfigComponent extends PageComponent implement
if (keys && keys.length) {
dataKeys.length = 0;
keys.forEach(key => {
const dataKey = this.constructDataKey(configData, key);
const dataKey = this.constructDataKey(configData, key, false);
dataKeys.push(dataKey);
});
}
if (latestKeys && latestKeys.length) {
latestDataKeys.length = 0;
latestKeys.forEach(key => {
const dataKey = this.constructDataKey(configData, key);
const dataKey = this.constructDataKey(configData, key, true);
latestDataKeys.push(dataKey);
});
}
}
protected constructDataKey(configData: WidgetConfigComponentData, key: DataKey): DataKey {
protected constructDataKey(configData: WidgetConfigComponentData, key: DataKey, isLatestKey: boolean): DataKey {
const dataKey =
this.widgetConfigComponent.widgetConfigCallbacks.generateDataKey(key.name, key.type, configData.dataKeySettingsSchema);
this.widgetConfigComponent.widgetConfigCallbacks.generateDataKey(key.name, key.type,
configData.dataKeySettingsSchema, isLatestKey, configData.dataKeySettingsFunction);
if (key.label) {
dataKey.label = key.label;
}

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

@ -148,13 +148,14 @@ export class AggregatedValueCardWidgetComponent implements OnInit, AfterViewInit
if (this.ctx.defaultSubscription.firstDatasource?.dataKeys?.length) {
this.lineChartDataKey = this.ctx.defaultSubscription.firstDatasource?.dataKeys[0];
this.lineChartDataKey.settings = {
showPointLabel: false,
type: TimeSeriesChartSeriesType.line,
lineSettings: {
smooth: false,
showLine: true,
step: false,
smooth: false,
lineWidth: 2,
showPoints: false
showPoints: false,
showPointLabel: false
}
} as TimeSeriesChartKeySettings;
}

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

@ -148,13 +148,14 @@ export class ValueChartCardWidgetComponent implements OnInit, AfterViewInit, OnD
if (this.ctx.defaultSubscription.firstDatasource?.dataKeys?.length) {
this.lineChartDataKey = this.ctx.defaultSubscription.firstDatasource?.dataKeys[0];
this.lineChartDataKey.settings = {
showPointLabel: false,
type: TimeSeriesChartSeriesType.line,
lineSettings: {
smooth: true,
showLine: true,
step: false,
smooth: true,
lineWidth: 2,
showPoints: false
showPoints: false,
showPointLabel: false
}
} as TimeSeriesChartKeySettings;
}

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

@ -44,7 +44,7 @@ import {
} from 'echarts/charts';
import { LabelLayout } from 'echarts/features';
import { CanvasRenderer, SVGRenderer } from 'echarts/renderers';
import { DataEntry, DataKey, DataSet } from '@shared/models/widget.models';
import { DataEntry, DataKey, DataSet, LegendDirection } from '@shared/models/widget.models';
import {
calculateAggIntervalWithWidgetTimeWindow,
IntervalMath,
@ -304,6 +304,13 @@ export enum EChartsTooltipTrigger {
axis = 'axis'
}
export const tooltipTriggerTranslationMap = new Map<EChartsTooltipTrigger, string>(
[
[ EChartsTooltipTrigger.point, 'tooltip.trigger-point' ],
[ EChartsTooltipTrigger.axis, 'tooltip.trigger-axis' ]
]
);
export interface EChartsTooltipWidgetSettings {
showTooltip: boolean;
tooltipTrigger?: EChartsTooltipTrigger;

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

@ -40,11 +40,42 @@ import {
import { DataKey } from '@shared/models/widget.models';
import { DataKeyType } from '@shared/models/telemetry/telemetry.models';
import { TbColorScheme } from '@shared/models/color.models';
import { DoughnutLayout } from '@home/components/widget/lib/chart/doughnut-widget.models';
export enum PointLabelPosition {
top = 'top',
bottom = 'bottom'
}
const timeSeriesChartColorScheme: TbColorScheme = {
'threshold.line': {
light: 'rgba(0, 0, 0, 0.76)',
dark: '#eee'
},
'threshold.label': {
light: 'rgba(0, 0, 0, 0.76)',
dark: '#eee'
},
'axis.line': {
light: 'rgba(0, 0, 0, 0.54)',
dark: '#B9B8CE'
},
'axis.label': {
light: 'rgba(0, 0, 0, 0.54)',
dark: '#B9B8CE'
},
'axis.ticks': {
light: 'rgba(0, 0, 0, 0.54)',
dark: '#B9B8CE'
},
'axis.tickLabel': {
light: 'rgba(0, 0, 0, 0.54)',
dark: '#B9B8CE'
},
'axis.splitLine': {
light: 'rgba(0, 0, 0, 0.12)',
dark: '#484753'
},
'series.label': {
light: 'rgba(0, 0, 0, 0.76)',
dark: '#eee'
}
};
export enum AxisPosition {
left = 'left',
@ -86,19 +117,63 @@ export enum ThresholdLabelPosition {
insideEndBottom = 'insideEndBottom'
}
export enum TimeSeriesChartThresholdType {
constant = 'constant',
latestKey = 'latestKey',
entity = 'entity'
}
export enum SeriesFillType {
none = 'none',
opacity = 'opacity',
gradient = 'gradient'
}
export enum SeriesLabelPosition {
top = 'top',
bottom = 'bottom'
}
export enum LineSeriesStepType {
start = 'start',
middle = 'middle',
end = 'end'
}
export enum TimeSeriesChartSeriesType {
line = 'line',
bar = 'bar'
}
export const timeSeriesChartSeriesTypes = Object.keys(TimeSeriesChartSeriesType) as TimeSeriesChartSeriesType[];
export const timeSeriesChartSeriesTypeTranslations = new Map<TimeSeriesChartSeriesType, string>(
[
[TimeSeriesChartSeriesType.line, 'widgets.time-series-chart.series.type-line'],
[TimeSeriesChartSeriesType.bar, 'widgets.time-series-chart.series.type-bar']
]
);
export const timeSeriesChartSeriesTypeIcons = new Map<TimeSeriesChartSeriesType, string>(
[
[TimeSeriesChartSeriesType.line, 'mdi:chart-line'],
[TimeSeriesChartSeriesType.bar, 'mdi:chart-bar']
]
);
export interface TimeSeriesChartAxisSettings {
show: boolean;
position: AxisPosition;
label?: string;
labelFont?: Font;
labelColor?: string;
showLine: boolean;
lineColor: string;
showTicks: boolean;
ticksColor: string;
position: AxisPosition;
showTickLabels: boolean;
tickLabelFont: Font;
tickLabelColor: string;
showTicks: boolean;
ticksColor: string;
showLine: boolean;
lineColor: string;
showSplitLines: boolean;
splitLinesColor: string;
}
@ -109,24 +184,19 @@ export interface TimeSeriesChartYAxisSettings extends TimeSeriesChartAxisSetting
intervalCalculator?: string;
}
export enum TimeSeriesChartThresholdType {
constant = 'constant',
latestKey = 'latestKey',
entity = 'entity'
}
export interface TimeSeriesChartThreshold {
type: TimeSeriesChartThresholdType;
value?: number;
latestKeyName?: string;
latestKey?: string;
latestKeyType?: DataKeyType.attribute | DataKeyType.timeseries;
entityAlias?: string;
entityKeyType?: DataKeyType.attribute | DataKeyType.timeseries;
entityKey?: string;
entityKeyType?: DataKeyType.attribute | DataKeyType.timeseries;
units?: string;
decimals?: number;
lineWidth: number;
lineType: TimeSeriesChartLineType;
lineColor: string;
lineType: TimeSeriesChartLineType;
lineWidth: number;
startSymbol: TimeSeriesChartShape;
startSymbolSize: number;
endSymbol: TimeSeriesChartShape;
@ -137,48 +207,13 @@ export interface TimeSeriesChartThreshold {
labelColor: string;
}
const timeSeriesChartColorScheme: TbColorScheme = {
'threshold.line': {
light: 'rgba(0, 0, 0, 0.76)',
dark: '#eee'
},
'threshold.label': {
light: 'rgba(0, 0, 0, 0.76)',
dark: '#eee'
},
'axis.line': {
light: 'rgba(0, 0, 0, 0.54)',
dark: '#B9B8CE'
},
'axis.label': {
light: 'rgba(0, 0, 0, 0.54)',
dark: '#B9B8CE'
},
'axis.ticks': {
light: 'rgba(0, 0, 0, 0.54)',
dark: '#B9B8CE'
},
'axis.tickLabel': {
light: 'rgba(0, 0, 0, 0.54)',
dark: '#B9B8CE'
},
'axis.splitLine': {
light: 'rgba(0, 0, 0, 0.12)',
dark: '#484753'
},
'series.pointLabel': {
light: 'rgba(0, 0, 0, 0.76)',
dark: '#eee'
}
};
export const timeSeriesChartThresholdDefaultSettings: TimeSeriesChartThreshold = {
type: TimeSeriesChartThresholdType.constant,
units: '',
decimals: 0,
lineWidth: 1,
lineType: TimeSeriesChartLineType.solid,
lineColor: timeSeriesChartColorScheme['threshold.line'].light,
lineType: TimeSeriesChartLineType.solid,
lineWidth: 1,
startSymbol: TimeSeriesChartShape.none,
startSymbolSize: 5,
endSymbol: TimeSeriesChartShape.arrow,
@ -197,22 +232,21 @@ export const timeSeriesChartThresholdDefaultSettings: TimeSeriesChartThreshold =
};
export interface TimeSeriesChartSettings extends EChartsTooltipWidgetSettings {
thresholds: TimeSeriesChartThreshold[];
darkMode: boolean;
dataZoom: boolean;
stack: boolean;
thresholds: TimeSeriesChartThreshold[];
xAxis: TimeSeriesChartAxisSettings;
yAxis: TimeSeriesChartYAxisSettings;
xAxis: TimeSeriesChartAxisSettings;
}
export const timeSeriesChartDefaultSettings: TimeSeriesChartSettings = {
thresholds: [],
darkMode: false,
dataZoom: true,
stack: false,
thresholds: [],
xAxis: {
yAxis: {
show: true,
position: AxisPosition.bottom,
label: '',
labelFont: {
family: 'Roboto',
@ -223,26 +257,26 @@ export const timeSeriesChartDefaultSettings: TimeSeriesChartSettings = {
lineHeight: '1'
},
labelColor: timeSeriesChartColorScheme['axis.label'].light,
showLine: true,
lineColor: timeSeriesChartColorScheme['axis.line'].light,
showTicks: true,
ticksColor: timeSeriesChartColorScheme['axis.ticks'].light,
position: AxisPosition.left,
showTickLabels: true,
tickLabelFont: {
family: 'Roboto',
size: 10,
size: 12,
sizeUnit: 'px',
style: 'normal',
weight: '400',
lineHeight: '1'
},
tickLabelColor: timeSeriesChartColorScheme['axis.tickLabel'].light,
showTicks: true,
ticksColor: timeSeriesChartColorScheme['axis.ticks'].light,
showLine: true,
lineColor: timeSeriesChartColorScheme['axis.line'].light,
showSplitLines: true,
splitLinesColor: timeSeriesChartColorScheme['axis.splitLine'].light
},
yAxis: {
xAxis: {
show: true,
position: AxisPosition.left,
label: '',
labelFont: {
family: 'Roboto',
@ -253,20 +287,21 @@ export const timeSeriesChartDefaultSettings: TimeSeriesChartSettings = {
lineHeight: '1'
},
labelColor: timeSeriesChartColorScheme['axis.label'].light,
showLine: true,
lineColor: timeSeriesChartColorScheme['axis.line'].light,
showTicks: true,
ticksColor: timeSeriesChartColorScheme['axis.ticks'].light,
position: AxisPosition.bottom,
showTickLabels: true,
tickLabelFont: {
family: 'Roboto',
size: 12,
size: 10,
sizeUnit: 'px',
style: 'normal',
weight: '400',
lineHeight: '1'
},
tickLabelColor: timeSeriesChartColorScheme['axis.tickLabel'].light,
showTicks: true,
ticksColor: timeSeriesChartColorScheme['axis.ticks'].light,
showLine: true,
lineColor: timeSeriesChartColorScheme['axis.line'].light,
showSplitLines: true,
splitLinesColor: timeSeriesChartColorScheme['axis.splitLine'].light
},
@ -282,7 +317,6 @@ export const timeSeriesChartDefaultSettings: TimeSeriesChartSettings = {
},
tooltipValueColor: 'rgba(0, 0, 0, 0.76)',
tooltipShowDate: true,
tooltipDateInterval: true,
tooltipDateFormat: simpleDateFormat('dd MMM yyyy HH:mm:ss'),
tooltipDateFont: {
family: 'Roboto',
@ -293,16 +327,11 @@ export const timeSeriesChartDefaultSettings: TimeSeriesChartSettings = {
lineHeight: '16px'
},
tooltipDateColor: 'rgba(0, 0, 0, 0.76)',
tooltipDateInterval: true,
tooltipBackgroundColor: 'rgba(255, 255, 255, 0.76)',
tooltipBackgroundBlur: 4
};
export enum SeriesFillType {
none = 'none',
opacity = 'opacity',
gradient = 'gradient'
}
export interface SeriesFillSettings {
type: SeriesFillType;
opacity: number;
@ -313,36 +342,36 @@ export interface SeriesFillSettings {
}
export interface LineSeriesSettings {
step: false | 'start' | 'end' | 'middle';
smooth: boolean;
showLine: boolean;
lineWidth: number;
step: boolean;
stepType: LineSeriesStepType;
smooth: boolean;
lineType: TimeSeriesChartLineType;
fillAreaSettings: SeriesFillSettings;
lineWidth: number;
showPoints: boolean;
showPointLabel: boolean;
pointLabelPosition: SeriesLabelPosition;
pointLabelFont: Font;
pointLabelColor: string;
pointShape: TimeSeriesChartShape;
pointSize: number;
fillAreaSettings: SeriesFillSettings;
}
export interface BarSeriesSettings {
showBorder: boolean;
borderWidth: number;
borderRadius: number;
showLabel: boolean;
labelPosition: SeriesLabelPosition;
labelFont: Font;
labelColor: string;
backgroundSettings: SeriesFillSettings;
}
export enum TimeSeriesChartSeriesType {
line = 'line',
bar = 'bar'
}
export interface TimeSeriesChartKeySettings {
dataHiddenByDefault: boolean;
showInLegend: boolean;
showPointLabel: boolean;
pointLabelPosition: PointLabelPosition;
pointLabelFont: Font;
pointLabelColor: string;
dataHiddenByDefault: boolean;
type: TimeSeriesChartSeriesType;
lineSettings: LineSeriesSettings;
barSettings: BarSeriesSettings;
@ -351,24 +380,28 @@ export interface TimeSeriesChartKeySettings {
export const timeSeriesChartKeyDefaultSettings: TimeSeriesChartKeySettings = {
showInLegend: true,
dataHiddenByDefault: false,
showPointLabel: false,
pointLabelPosition: PointLabelPosition.top,
pointLabelFont: {
family: 'Roboto',
size: 11,
sizeUnit: 'px',
style: 'normal',
weight: '400',
lineHeight: '1'
},
pointLabelColor: timeSeriesChartColorScheme['series.pointLabel'].light,
type: TimeSeriesChartSeriesType.line,
lineSettings: {
showLine: true,
step: false,
stepType: LineSeriesStepType.start,
smooth: false,
showLine: true,
lineWidth: 2,
lineType: TimeSeriesChartLineType.solid,
lineWidth: 2,
showPoints: false,
showPointLabel: false,
pointLabelPosition: SeriesLabelPosition.top,
pointLabelFont: {
family: 'Roboto',
size: 11,
sizeUnit: 'px',
style: 'normal',
weight: '400',
lineHeight: '1'
},
pointLabelColor: timeSeriesChartColorScheme['series.label'].light,
pointShape: TimeSeriesChartShape.emptyCircle,
pointSize: 4,
fillAreaSettings: {
type: SeriesFillType.none,
opacity: 0.4,
@ -376,15 +409,23 @@ export const timeSeriesChartKeyDefaultSettings: TimeSeriesChartKeySettings = {
start: 100,
end: 0
}
},
showPoints: false,
pointShape: TimeSeriesChartShape.emptyCircle,
pointSize: 4
}
},
barSettings: {
showBorder: false,
borderWidth: 2,
borderRadius: 0,
showLabel: false,
labelPosition: SeriesLabelPosition.top,
labelFont: {
family: 'Roboto',
size: 11,
sizeUnit: 'px',
style: 'normal',
weight: '400',
lineHeight: '1'
},
labelColor: timeSeriesChartColorScheme['series.label'].light,
backgroundSettings: {
type: SeriesFillType.none,
opacity: 0.4,
@ -707,21 +748,23 @@ export const updateDarkMode = (options: EChartsOption, settings: TimeSeriesChart
}
for (const item of dataItems) {
if (item.dataKey.settings.type === TimeSeriesChartSeriesType.line) {
const lineSettings = item.dataKey.settings as LineSeriesSettings;
if (item.option.label?.show) {
item.option.label.rich.value.color = prepareChartThemeColor(item.dataKey.settings.pointLabelColor, darkMode, 'series.pointLabel');
item.option.label.rich.value.color = prepareChartThemeColor(lineSettings.pointLabelColor, darkMode, 'series.label');
}
if (Array.isArray(options.series)) {
const series = options.series.find(s => s.id === item.id);
if (series) {
if (series.label?.show) {
series.label.rich.value.color = prepareChartThemeColor(item.dataKey.settings.pointLabelColor, darkMode, 'series.pointLabel');
series.label.rich.value.color = prepareChartThemeColor(lineSettings.pointLabelColor, darkMode, 'series.label');
}
}
}
} else {
if (item.barRenderContext?.labelOption?.show) {
item.barRenderContext.labelOption.rich.value.color = prepareChartThemeColor(item.dataKey.settings.pointLabelColor,
darkMode, 'series.pointLabel');
const barSettings = item.dataKey.settings as BarSeriesSettings;
item.barRenderContext.labelOption.rich.value.color = prepareChartThemeColor(barSettings.labelColor,
darkMode, 'series.label');
}
}
}
@ -747,21 +790,6 @@ const createTimeSeriesChartSeries = (item: TimeSeriesChartDataItem,
const dataKey = item.dataKey;
const settings: TimeSeriesChartKeySettings = dataKey.settings;
const seriesColor = item.dataKey.color;
let pointLabelStyle: ComponentStyle = {};
if (settings.showPointLabel) {
pointLabelStyle = createChartTextStyle(settings.pointLabelFont, settings.pointLabelColor, darkMode, 'series.pointLabel');
}
const label: SeriesLabelOption = {
show: settings.showPointLabel,
position: settings.pointLabelPosition,
formatter: (params): string => {
const value = formatValue(params.value[1], item.decimals, item.units, false);
return `{value|${value}}`;
},
rich: {
value: pointLabelStyle
}
};
seriesOption = {
id: item.id,
dataGroupId: item.id,
@ -788,8 +816,9 @@ const createTimeSeriesChartSeries = (item: TimeSeriesChartDataItem,
const lineSettings = settings.lineSettings;
const lineSeriesOption = seriesOption as LineSeriesOption;
lineSeriesOption.type = 'line';
lineSeriesOption.label = label;
lineSeriesOption.step = lineSettings.step;
lineSeriesOption.label = createSeriesLabelOption(item, lineSettings.showPointLabel,
lineSettings.pointLabelFont, lineSettings.pointLabelColor, lineSettings.pointLabelPosition, darkMode);
lineSeriesOption.step = lineSettings.step ? lineSettings.stepType : false;
lineSeriesOption.smooth = lineSettings.smooth;
lineSeriesOption.lineStyle = {
width: lineSettings.showLine ? lineSettings.lineWidth : 0,
@ -825,7 +854,8 @@ const createTimeSeriesChartSeries = (item: TimeSeriesChartDataItem,
barVisualSettings.color = createLinearOpacityGradient(seriesColor, barSettings.backgroundSettings.gradient);
}
item.barRenderContext.visualSettings = barVisualSettings;
item.barRenderContext.labelOption = label;
item.barRenderContext.labelOption = createSeriesLabelOption(item, barSettings.showLabel,
barSettings.labelFont, barSettings.labelColor, barSettings.labelPosition, darkMode);
barSeriesOption.renderItem = (params, api) =>
renderTimeSeriesBar(params, api, item.barRenderContext);
}
@ -834,6 +864,26 @@ const createTimeSeriesChartSeries = (item: TimeSeriesChartDataItem,
return seriesOption;
};
const createSeriesLabelOption = (item: TimeSeriesChartDataItem, show: boolean,
labelFont: Font, labelColor: string, position: SeriesLabelPosition,
darkMode: boolean): SeriesLabelOption => {
let labelStyle: ComponentStyle = {};
if (show) {
labelStyle = createChartTextStyle(labelFont, labelColor, darkMode, 'series.label');
}
return {
show,
position,
formatter: (params): string => {
const value = formatValue(params.value[1], item.decimals, item.units, false);
return `{value|${value}}`;
},
rich: {
value: labelStyle
}
};
};
const createChartTextStyle = (font: Font, color: string, darkMode: boolean, colorKey?: string): ComponentStyle => {
const style = textStyle(font);
delete style.lineHeight;

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

@ -21,6 +21,7 @@ import {
createTimeSeriesXAxisOption,
createTimeSeriesYAxis,
generateChartData,
parseThresholdData, SeriesLabelPosition,
TimeSeriesChartDataItem,
timeSeriesChartDefaultSettings,
timeSeriesChartKeyDefaultSettings,
@ -30,8 +31,6 @@ import {
TimeSeriesChartThresholdItem,
TimeSeriesChartThresholdType,
TimeSeriesChartYAxis,
parseThresholdData,
PointLabelPosition,
updateDarkMode
} from '@home/components/widget/lib/chart/time-series-chart.models';
import { ResizeObserver } from '@juggle/resize-observer';
@ -60,9 +59,20 @@ import { BehaviorSubject } from 'rxjs';
import { AggregationType } from '@shared/models/time/time.models';
import { DataKeyType } from '@shared/models/telemetry/telemetry.models';
import { WidgetSubscriptionOptions } from '@core/api/widget-api.models';
import { DataKeySettingsFunction } from '@home/components/widget/config/data-keys.component.models';
export class TbTimeSeriesChart {
public static dataKeySettings(): DataKeySettingsFunction {
return (key, isLatestDataKey) => {
if (!isLatestDataKey) {
return mergeDeep<TimeSeriesChartKeySettings>({} as TimeSeriesChartKeySettings,
timeSeriesChartKeyDefaultSettings);
}
return null;
};
}
private readonly shapeResize$: ResizeObserver;
private dataItems: TimeSeriesChartDataItem[] = [];
@ -247,7 +257,10 @@ export class TbTimeSeriesChart {
for (const dataKey of dataKeys) {
const keySettings = mergeDeep<TimeSeriesChartKeySettings>({} as TimeSeriesChartKeySettings,
timeSeriesChartKeyDefaultSettings, dataKey.settings);
if (keySettings.showPointLabel && keySettings.pointLabelPosition === PointLabelPosition.top) {
if ((keySettings.type === TimeSeriesChartSeriesType.line && keySettings.lineSettings.showPointLabel &&
keySettings.lineSettings.pointLabelPosition === SeriesLabelPosition.top) ||
(keySettings.type === TimeSeriesChartSeriesType.bar && keySettings.barSettings.showLabel &&
keySettings.barSettings.labelPosition === SeriesLabelPosition.top)) {
this.topPointLabels = true;
}
dataKey.settings = keySettings;
@ -280,8 +293,9 @@ export class TbTimeSeriesChart {
if (this.ctx.datasources.length) {
for (const datasource of this.ctx.datasources) {
latestDataKey = datasource.latestDataKeys?.find(d =>
(d.type === DataKeyType.function && d.label === threshold.latestKeyName) ||
(d.type !== DataKeyType.function && d.name === threshold.latestKeyName));
(d.type === DataKeyType.function && d.label === threshold.latestKey) ||
(d.type !== DataKeyType.function && d.name === threshold.latestKey &&
d.type === threshold.latestKeyType));
if (latestDataKey) {
break;
}

2
ui-ngx/src/app/modules/home/components/widget/lib/maps/map-widget2.ts

@ -298,6 +298,6 @@ export class MapWidgetController implements MapWidgetInterface {
}
}
export let TbMapWidgetV2: MapWidgetStaticInterface = MapWidgetController;
export const TbMapWidgetV2: MapWidgetStaticInterface = MapWidgetController;

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

@ -0,0 +1,36 @@
<!--
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.
-->
<ng-container [formGroup]="timeSeriesChartKeySettingsForm">
<div class="tb-form-panel">
<div class="tb-form-panel-title" translate>widgets.time-series-chart.series.legend-settings</div>
<mat-slide-toggle class="mat-slide" formControlName="showInLegend">
<div tb-hint-tooltip-icon="{{'widgets.time-series-chart.series.show-in-legend-hint' | translate}}">
{{ 'widgets.time-series-chart.series.show-in-legend' | translate }}
</div>
</mat-slide-toggle>
<mat-slide-toggle class="mat-slide" formControlName="dataHiddenByDefault">
<div tb-hint-tooltip-icon="{{'widgets.time-series-chart.series.hidden-by-default-hint' | translate}}">
{{ 'widgets.time-series-chart.series.hidden-by-default' | translate }}
</div>
</mat-slide-toggle>
</div>
<div class="tb-form-panel">
<div class="tb-form-panel-title" translate>widgets.time-series-chart.series.series-type</div>
TODO:
</div>
</ng-container>

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

@ -0,0 +1,82 @@
///
/// 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 } from '@angular/core';
import { WidgetSettings, WidgetSettingsComponent } from '@shared/models/widget.models';
import { UntypedFormBuilder, UntypedFormGroup } from '@angular/forms';
import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state';
import { mergeDeep } from '@core/utils';
import {
timeSeriesChartKeyDefaultSettings,
TimeSeriesChartKeySettings
} from '@home/components/widget/lib/chart/time-series-chart.models';
import { WidgetConfigComponentData } from '@home/models/widget-component.models';
@Component({
selector: 'tb-time-series-chart-key-settings',
templateUrl: './time-series-chart-key-settings.component.html',
styleUrls: ['./../widget-settings.scss']
})
export class TimeSeriesChartKeySettingsComponent extends WidgetSettingsComponent {
timeSeriesChartKeySettingsForm: UntypedFormGroup;
constructor(protected store: Store<AppState>,
private fb: UntypedFormBuilder) {
super(store);
}
protected settingsForm(): UntypedFormGroup {
return this.timeSeriesChartKeySettingsForm;
}
protected onWidgetConfigSet(widgetConfig: WidgetConfigComponentData) {
const params = widgetConfig.typeParameters as any;
// const timeSeriesChartType = params.timeSeriesChartType;
}
protected defaultSettings(): WidgetSettings {
return mergeDeep<TimeSeriesChartKeySettings>({} as TimeSeriesChartKeySettings,
timeSeriesChartKeyDefaultSettings);
}
protected onSettingsSet(settings: WidgetSettings) {
const seriesSettings = settings as TimeSeriesChartKeySettings;
this.timeSeriesChartKeySettingsForm = this.fb.group({
showInLegend: [seriesSettings.showInLegend, []],
dataHiddenByDefault: [seriesSettings.dataHiddenByDefault, []],
type: [seriesSettings.type, []],
lineSettings: [settings.lineSettings, []],
barSettings: [settings.barSettings, []]
});
}
protected validatorTriggers(): string[] {
return ['showInLegend'];
}
protected updateValidators(_emitEvent: boolean) {
const showInLegend: boolean = this.timeSeriesChartKeySettingsForm.get('showInLegend').value;
if (showInLegend) {
this.timeSeriesChartKeySettingsForm.get('dataHiddenByDefault').enable();
} else {
this.timeSeriesChartKeySettingsForm.get('dataHiddenByDefault').patchValue(false, {emitEvent: false});
this.timeSeriesChartKeySettingsForm.get('dataHiddenByDefault').disable();
}
}
}

7
ui-ngx/src/app/modules/home/components/widget/lib/settings/common/legend-config.component.html

@ -16,7 +16,7 @@
-->
<ng-container [formGroup]="legendConfigForm">
<div class="tb-form-row space-between">
<div *ngIf="!hideDirection" class="tb-form-row space-between">
<div>{{ 'legend.direction' | translate }}</div>
<mat-form-field class="medium-width" appearance="outline" subscriptSizing="dynamic">
<mat-select formControlName="direction">
@ -31,8 +31,9 @@
<mat-form-field class="medium-width" appearance="outline" subscriptSizing="dynamic">
<mat-select formControlName="position">
<mat-option *ngFor="let pos of legendPositions" [value]="pos"
[disabled]="legendConfigForm.get('direction').value === legendDirection.row &&
(pos === legendPosition.left || pos === legendPosition.right)">
[disabled]="!hideDirection &&
legendConfigForm.get('direction').value === legendDirection.row &&
(pos === legendPosition.left || pos === legendPosition.right)">
{{ legendPositionTranslations.get(legendPosition[pos]) | translate }}
</mat-option>
</mat-select>

28
ui-ngx/src/app/modules/home/components/widget/lib/settings/common/legend-config.component.ts

@ -25,6 +25,7 @@ import {
legendPositionTranslationMap
} from '@shared/models/widget.models';
import { Subscription } from 'rxjs';
import { coerceBoolean } from '@shared/decorators/coercion';
// @dynamic
@Component({
@ -43,6 +44,10 @@ export class LegendConfigComponent implements OnInit, OnDestroy, ControlValueAcc
@Input() disabled: boolean;
@Input()
@coerceBoolean()
hideDirection = false;
legendConfigForm: UntypedFormGroup;
legendDirection = LegendDirection;
legendDirections = Object.keys(LegendDirection);
@ -60,15 +65,17 @@ export class LegendConfigComponent implements OnInit, OnDestroy, ControlValueAcc
ngOnInit(): void {
this.legendConfigForm = this.fb.group({
direction: [null, []],
position: [null, []],
showValues: [[], []],
sortDataKeys: [null, []]
});
this.legendSettingsFormDirectionChanges$ = this.legendConfigForm.get('direction').valueChanges
if (!this.hideDirection) {
this.legendConfigForm.addControl('direction', this.fb.control([null, []]));
this.legendSettingsFormDirectionChanges$ = this.legendConfigForm.get('direction').valueChanges
.subscribe((direction: LegendDirection) => {
this.onDirectionChanged(direction);
});
}
this.legendSettingsFormChanges$ = this.legendConfigForm.valueChanges.subscribe(
() => this.legendConfigUpdated()
);
@ -114,23 +121,30 @@ export class LegendConfigComponent implements OnInit, OnDestroy, ControlValueAcc
writeValue(legendConfig: LegendConfig): void {
if (legendConfig) {
this.legendConfigForm.patchValue({
direction: legendConfig.direction,
const value: any = {
position: legendConfig.position,
showValues: this.getShowValues(legendConfig),
sortDataKeys: isDefined(legendConfig.sortDataKeys) ? legendConfig.sortDataKeys : false
}, {emitEvent: false});
};
if (!this.hideDirection) {
value.direction = legendConfig.direction;
}
this.legendConfigForm.patchValue(value, {emitEvent: false});
}
if (!this.hideDirection) {
this.onDirectionChanged(legendConfig?.direction);
}
this.onDirectionChanged(legendConfig.direction);
}
private legendConfigUpdated() {
const configValue = this.legendConfigForm.value;
const legendConfig: Partial<LegendConfig> = {
direction: configValue.direction,
position: configValue.position,
sortDataKeys: configValue.sortDataKeys
};
if (!this.hideDirection) {
legendConfig.direction = configValue.direction;
}
this.setShowValues(configValue.showValues, legendConfig);
this.propagateChange(legendConfig);
}

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

@ -330,6 +330,9 @@ import {
import {
ToggleButtonWidgetSettingsComponent
} from '@home/components/widget/lib/settings/button/toggle-button-widget-settings.component';
import {
TimeSeriesChartKeySettingsComponent
} from '@home/components/widget/lib/settings/chart/time-series-chart-key-settings.component';
@NgModule({
declarations: [
@ -448,7 +451,8 @@ import {
CommandButtonWidgetSettingsComponent,
PowerButtonWidgetSettingsComponent,
SliderWidgetSettingsComponent,
ToggleButtonWidgetSettingsComponent
ToggleButtonWidgetSettingsComponent,
TimeSeriesChartKeySettingsComponent
],
imports: [
CommonModule,
@ -572,7 +576,8 @@ import {
CommandButtonWidgetSettingsComponent,
PowerButtonWidgetSettingsComponent,
SliderWidgetSettingsComponent,
ToggleButtonWidgetSettingsComponent
ToggleButtonWidgetSettingsComponent,
TimeSeriesChartKeySettingsComponent
]
})
export class WidgetSettingsModule {
@ -663,5 +668,6 @@ export const widgetSettingsComponentsMap: {[key: string]: Type<IWidgetSettingsCo
'tb-command-button-widget-settings': CommandButtonWidgetSettingsComponent,
'tb-power-button-widget-settings': PowerButtonWidgetSettingsComponent,
'tb-slider-widget-settings': SliderWidgetSettingsComponent,
'tb-toggle-button-widget-settings': ToggleButtonWidgetSettingsComponent
'tb-toggle-button-widget-settings': ToggleButtonWidgetSettingsComponent,
'tb-time-series-chart-key-settings': TimeSeriesChartKeySettingsComponent
};

9
ui-ngx/src/app/modules/home/components/widget/widget-component.service.ts

@ -55,6 +55,7 @@ import { HOME_COMPONENTS_MODULE_TOKEN } from '@home/components/tokens';
import { widgetSettingsComponentsMap } from '@home/components/widget/lib/settings/widget-settings.module';
import { basicWidgetConfigComponentsMap } from '@home/components/widget/config/basic/basic-widget-config.module';
import { IBasicWidgetConfigComponent } from '@home/components/widget/config/widget-config.component.models';
import { TbTimeSeriesChart } from '@home/components/widget/lib/chart/time-series-chart';
@Injectable()
export class WidgetComponentService {
@ -148,6 +149,11 @@ export class WidgetComponentService {
(window as any).TbFlot = mod.TbFlot;
}))
);
widgetModulesTasks.push(from(import('@home/components/widget/lib/chart/time-series-chart')).pipe(
tap((mod) => {
(window as any).TbTimeSeriesChart = mod.TbTimeSeriesChart;
}))
);
widgetModulesTasks.push(from(import('@home/components/widget/lib/analogue-compass')).pipe(
tap((mod) => {
(window as any).TbAnalogueCompass = mod.TbAnalogueCompass;
@ -577,6 +583,9 @@ export class WidgetComponentService {
if (!isFunction(result.typeParameters.defaultLatestDataKeysFunction)) {
result.typeParameters.defaultLatestDataKeysFunction = null;
}
if (!isFunction(result.typeParameters.dataKeySettingsFunction)) {
result.typeParameters.dataKeySettingsFunction = null;
}
if (isUndefined(result.typeParameters.displayRpcMessageToast)) {
result.typeParameters.displayRpcMessageToast = true;
}

9
ui-ngx/src/app/modules/home/components/widget/widget-config.component.ts

@ -84,6 +84,7 @@ import { coerceBoolean } from '@shared/decorators/coercion';
import { basicWidgetConfigComponentsMap } from '@home/components/widget/config/basic/basic-widget-config.module';
import { TimewindowConfigData } from '@home/components/widget/config/timewindow-config-panel.component';
import Timeout = NodeJS.Timeout;
import { DataKeySettingsFunction } from '@home/components/widget/config/data-keys.component.models';
const emptySettingsSchema: JsonSchema = {
type: 'object',
@ -734,7 +735,8 @@ export class WidgetConfigComponent extends PageComponent implements OnInit, OnDe
}
}
public generateDataKey(chip: any, type: DataKeyType, datakeySettingsSchema: JsonSettingsSchema): DataKey {
public generateDataKey(chip: any, type: DataKeyType, datakeySettingsSchema: JsonSettingsSchema,
isLatestDataKey: boolean, dataKeySettingsFunction: DataKeySettingsFunction): DataKey {
if (isObject(chip)) {
(chip as DataKey)._hash = Math.random();
return chip;
@ -767,6 +769,11 @@ export class WidgetConfigComponent extends PageComponent implements OnInit, OnDe
}
if (datakeySettingsSchema && isDefined(datakeySettingsSchema.schema)) {
result.settings = this.utils.generateObjectFromJsonSchema(datakeySettingsSchema.schema);
} else if (dataKeySettingsFunction) {
const settings = dataKeySettingsFunction(result, isLatestDataKey);
if (settings) {
result.settings = settings;
}
}
return result;
}

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

@ -102,6 +102,7 @@ import { ImagePipe, MillisecondsToTimeStringPipe, TelemetrySubscriber } from '@a
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';
export interface IWidgetAction {
name: string;
@ -549,6 +550,7 @@ export interface WidgetConfigComponentData {
settingsSchema: JsonSettingsSchema;
dataKeySettingsSchema: JsonSettingsSchema;
latestDataKeySettingsSchema: JsonSettingsSchema;
dataKeySettingsFunction: DataKeySettingsFunction;
settingsDirective: string;
dataKeySettingsDirective: string;
latestDataKeySettingsDirective: string;

2
ui-ngx/src/app/shared/models/widget-settings.models.ts

@ -348,7 +348,7 @@ export const customDateFormat = (format: string): DateFormatSettings => ({
custom: true
});
export const dateFormats = ['MMM dd yyyy HH:mm', 'dd MMM yyyy HH:mm', 'yyyy MMM dd HH:mm',
export const dateFormats = ['MMM dd yyyy HH:mm', 'dd MMM yyyy HH:mm', 'dd MMM yyyy HH:mm:ss', 'yyyy MMM dd HH:mm',
'MM/dd/yyyy HH:mm', 'dd/MM/yyyy HH:mm', 'yyyy/MM/dd HH:mm:ss', 'yyyy-MM-dd HH:mm:ss', 'yyyy-MM-dd HH:mm:ss.SSS']
.map(f => simpleDateFormat(f)).concat([lastUpdateAgoDateFormat(), customDateFormat('EEE, MMMM dd, yyyy')]);

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

@ -43,6 +43,7 @@ import { WidgetConfigComponentData } from '@home/models/widget-component.models'
import { ComponentStyle, Font, TimewindowStyle } from '@shared/models/widget-settings.models';
import { NULL_UUID } from '@shared/models/id/has-uuid';
import { HasTenantId } from '@shared/models/entity.models';
import { DataKeySettingsFunction } from '@home/components/widget/config/data-keys.component.models';
export enum widgetType {
timeseries = 'timeseries',
@ -184,6 +185,7 @@ export interface WidgetTypeParameters {
hideDataSettings?: boolean;
defaultDataKeysFunction?: (configComponent: any, configData: any) => DataKey[];
defaultLatestDataKeysFunction?: (configComponent: any, configData: any) => DataKey[];
dataKeySettingsFunction?: DataKeySettingsFunction;
displayRpcMessageToast?: boolean;
}

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

@ -4334,8 +4334,13 @@
"preview": "Preview"
},
"tooltip": {
"trigger": "Trigger",
"trigger-point": "Point",
"trigger-axis": "Axis",
"value": "Value",
"date": "Date",
"show-date-time-interval": "Show date time interval",
"show-date-time-interval-hint": "Show date time interval according to the data aggregation.",
"background-color": "Background color",
"background-blur": "Background blur"
},
@ -6590,6 +6595,24 @@
"table-tabs": "Table tabs",
"show-cell-actions-menu-mobile": "Show cell actions dropdown menu in mobile mode"
},
"time-series-chart": {
"chart": "Chart",
"data-zoom": "Data zoom",
"stack-mode": "Stack mode",
"stack-mode-hint": "Stacks series on the chart. The series with the same unit would be put on top of each other.",
"axes": "Axes",
"series": {
"legend-settings": "Legend settings",
"show-in-legend": "Show in legend",
"show-in-legend-hint": "Show series name and data in legend.",
"hidden-by-default": "Hidden by default",
"hidden-by-default-hint": "Make series hidden in legend by default.",
"series-type": "Series type",
"type": "Type",
"type-line": "Line",
"type-bar": "Bar"
}
},
"wind-speed-direction": {
"layout": "Layout",
"layout-default": "Default",

21
ui-ngx/src/form.scss

@ -467,6 +467,17 @@
}
}
}
&.fixed-height {
.mat-mdc-form-field-infix {
max-height: 40px;
.mat-mdc-select {
max-height: 24px;
.mat-mdc-select-trigger {
max-height: 24px;
}
}
}
}
}
.tb-form-table {
@ -670,4 +681,14 @@
}
}
}
.mat-mdc-option {
&.flex {
.mdc-list-item__primary-text {
display: flex;
align-items: center;
justify-content: flex-start;
}
}
}
}

Loading…
Cancel
Save