Browse Source

UI: Implement bar chart with labels widget. Improve echarts initialization. Improve timewindow component to set preferred aggregation interval for selected timewindow. Fix data aggregator to schedule interval with maximum allowed setTimeout delay.

pull/9960/head
Igor Kulikov 3 years ago
parent
commit
f7fc127e36
  1. 2
      application/src/main/data/json/system/widget_bundles/charts.json
  2. 20
      application/src/main/data/json/system/widget_types/bar_chart_with_labels.json
  3. 6
      ui-ngx/src/app/core/api/data-aggregator.ts
  4. 12
      ui-ngx/src/app/modules/home/components/widget/config/basic/basic-widget-config.module.ts
  5. 248
      ui-ngx/src/app/modules/home/components/widget/config/basic/chart/bar-chart-with-labels-basic-config.component.html
  6. 323
      ui-ngx/src/app/modules/home/components/widget/config/basic/chart/bar-chart-with-labels-basic-config.component.ts
  7. 16
      ui-ngx/src/app/modules/home/components/widget/config/basic/chart/doughnut-basic-config.component.html
  8. 8
      ui-ngx/src/app/modules/home/components/widget/config/basic/chart/doughnut-basic-config.component.ts
  9. 14
      ui-ngx/src/app/modules/home/components/widget/config/basic/chart/range-chart-basic-config.component.html
  10. 10
      ui-ngx/src/app/modules/home/components/widget/config/basic/indicator/signal-strength-basic-config.component.html
  11. 309
      ui-ngx/src/app/modules/home/components/widget/lib/chart/bar-chart-with-labels-widget.component.ts
  12. 19
      ui-ngx/src/app/modules/home/components/widget/lib/chart/bar-chart-with-labels-widget.models.ts
  13. 22
      ui-ngx/src/app/modules/home/components/widget/lib/chart/doughnut-widget.component.ts
  14. 23
      ui-ngx/src/app/modules/home/components/widget/lib/chart/doughnut-widget.models.ts
  15. 214
      ui-ngx/src/app/modules/home/components/widget/lib/chart/echarts-widget.models.ts
  16. 127
      ui-ngx/src/app/modules/home/components/widget/lib/chart/range-chart-widget.component.ts
  17. 16
      ui-ngx/src/app/modules/home/components/widget/lib/chart/range-chart-widget.models.ts
  18. 150
      ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/bar-chart-with-labels-widget-settings.component.html
  19. 170
      ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/bar-chart-with-labels-widget-settings.component.ts
  20. 16
      ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/doughnut-widget-settings.component.html
  21. 13
      ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/doughnut-widget-settings.component.ts
  22. 12
      ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/range-chart-widget-settings.component.html
  23. 13
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/font-settings-panel.component.ts
  24. 8
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/font-settings.component.ts
  25. 10
      ui-ngx/src/app/modules/home/components/widget/lib/settings/indicator/signal-strength-widget-settings.component.html
  26. 12
      ui-ngx/src/app/modules/home/components/widget/lib/settings/widget-settings.module.ts
  27. 12
      ui-ngx/src/app/shared/components/time/timeinterval.component.ts
  28. 35
      ui-ngx/src/assets/locale/locale.constant-en_US.json
  29. 29
      ui-ngx/src/assets/locale/locale.constant-zh_CN.json

2
application/src/main/data/json/system/widget_bundles/charts.json

@ -11,8 +11,8 @@
"charts.basic_timeseries", "charts.basic_timeseries",
"charts.state_chart", "charts.state_chart",
"range_chart", "range_chart",
"charts.timeseries_bars_flot",
"bar_chart_with_labels", "bar_chart_with_labels",
"charts.timeseries_bars_flot",
"cards.aggregated_value_card", "cards.aggregated_value_card",
"charts.bars", "charts.bars",
"charts.pie", "charts.pie",

20
application/src/main/data/json/system/widget_types/bar_chart_with_labels.json

File diff suppressed because one or more lines are too long

6
ui-ngx/src/app/core/api/data-aggregator.ts

@ -136,6 +136,8 @@ const none: AggFunction = (aggData: AggData, value?: any) => {
aggData.aggValue = value; aggData.aggValue = value;
}; };
const MAX_INTERVAL_TIMEOUT = Math.pow(2,31)-1;
export class DataAggregator { export class DataAggregator {
constructor(private onDataCb: onAggregatedData, constructor(private onDataCb: onAggregatedData,
@ -217,7 +219,7 @@ export class DataAggregator {
this.aggregationTimeout = this.isLatestDataAgg ? 1000 : Math.max(this.subsTw.aggregation.interval, 1000); this.aggregationTimeout = this.isLatestDataAgg ? 1000 : Math.max(this.subsTw.aggregation.interval, 1000);
this.resetPending = true; this.resetPending = true;
this.updatedData = false; this.updatedData = false;
this.intervalTimeoutHandle = setTimeout(this.onInterval.bind(this), this.aggregationTimeout); this.intervalTimeoutHandle = setTimeout(this.onInterval.bind(this), Math.min(this.aggregationTimeout, MAX_INTERVAL_TIMEOUT));
} }
public destroy() { public destroy() {
@ -313,7 +315,7 @@ export class DataAggregator {
this.updatedData = false; this.updatedData = false;
} }
if (!history) { if (!history) {
this.intervalTimeoutHandle = setTimeout(this.onInterval.bind(this), intervalTimeout); this.intervalTimeoutHandle = setTimeout(this.onInterval.bind(this), Math.min(intervalTimeout, MAX_INTERVAL_TIMEOUT));
} }
} }

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

@ -88,6 +88,9 @@ import {
import { import {
RangeChartBasicConfigComponent RangeChartBasicConfigComponent
} from '@home/components/widget/config/basic/chart/range-chart-basic-config.component'; } from '@home/components/widget/config/basic/chart/range-chart-basic-config.component';
import {
BarChartWithLabelsBasicConfigComponent
} from '@home/components/widget/config/basic/chart/bar-chart-with-labels-basic-config.component';
@NgModule({ @NgModule({
declarations: [ declarations: [
@ -115,7 +118,8 @@ import {
CompassGaugeBasicConfigComponent, CompassGaugeBasicConfigComponent,
LiquidLevelCardBasicConfigComponent, LiquidLevelCardBasicConfigComponent,
DoughnutBasicConfigComponent, DoughnutBasicConfigComponent,
RangeChartBasicConfigComponent RangeChartBasicConfigComponent,
BarChartWithLabelsBasicConfigComponent
], ],
imports: [ imports: [
CommonModule, CommonModule,
@ -147,7 +151,8 @@ import {
CompassGaugeBasicConfigComponent, CompassGaugeBasicConfigComponent,
LiquidLevelCardBasicConfigComponent, LiquidLevelCardBasicConfigComponent,
DoughnutBasicConfigComponent, DoughnutBasicConfigComponent,
RangeChartBasicConfigComponent RangeChartBasicConfigComponent,
BarChartWithLabelsBasicConfigComponent
] ]
}) })
export class BasicWidgetConfigModule { export class BasicWidgetConfigModule {
@ -173,5 +178,6 @@ export const basicWidgetConfigComponentsMap: {[key: string]: Type<IBasicWidgetCo
'tb-compass-gauge-basic-config': CompassGaugeBasicConfigComponent, 'tb-compass-gauge-basic-config': CompassGaugeBasicConfigComponent,
'tb-liquid-level-card-basic-config': LiquidLevelCardBasicConfigComponent, 'tb-liquid-level-card-basic-config': LiquidLevelCardBasicConfigComponent,
'tb-doughnut-basic-config': DoughnutBasicConfigComponent, 'tb-doughnut-basic-config': DoughnutBasicConfigComponent,
'tb-range-chart-basic-config': RangeChartBasicConfigComponent 'tb-range-chart-basic-config': RangeChartBasicConfigComponent,
'tb-bar-chart-with-labels-basic-config': BarChartWithLabelsBasicConfigComponent
}; };

248
ui-ngx/src/app/modules/home/components/widget/config/basic/chart/bar-chart-with-labels-basic-config.component.html

@ -0,0 +1,248 @@
<!--
Copyright © 2016-2023 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]="barChartWidgetConfigForm">
<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 }}"
hideSourceSelection
hideUnits
hideDecimals
hideDataKeyUnits
hideDataKeyDecimals
[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" 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]="barChartWidgetConfigForm.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]="barChartWidgetConfigForm.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.bar-chart.bar-appearance</div>
<div class="tb-form-row space-between">
<mat-slide-toggle class="mat-slide fixed-title-width" formControlName="showBarLabel">
{{ 'widgets.bar-chart.label-on-bar' | translate }}
</mat-slide-toggle>
<div fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px">
<tb-font-settings formControlName="barLabelFont"
disabledLineHeight
forceSizeUnit="px"
previewText="Humidity">
</tb-font-settings>
<tb-color-input asBoxInput
colorClearButton
formControlName="barLabelColor">
</tb-color-input>
</div>
</div>
<div class="tb-form-row space-between">
<mat-slide-toggle class="mat-slide fixed-title-width" formControlName="showBarValue">
{{ 'widgets.bar-chart.value-on-bar' | translate }}
</mat-slide-toggle>
<div fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px">
<tb-font-settings formControlName="barValueFont"
disabledLineHeight
forceSizeUnit="px"
previewText="45">
</tb-font-settings>
<tb-color-input asBoxInput
colorClearButton
formControlName="barValueColor">
</tb-color-input>
</div>
</div>
<div class="tb-form-row space-between">
<div translate>widget-config.units-short</div>
<tb-unit-input
formControlName="units">
</tb-unit-input>
</div>
<div class="tb-form-row space-between">
<div translate>widget-config.decimals-short</div>
<mat-form-field appearance="outline" class="number" subscriptSizing="dynamic">
<input matInput formControlName="decimals" type="number" min="0" max="15" step="1" placeholder="{{ 'widget-config.set' | translate }}">
</mat-form-field>
</div>
</div>
<div class="tb-form-panel tb-slide-toggle">
<mat-expansion-panel class="tb-settings" [expanded]="barChartWidgetConfigForm.get('showLegend').value" [disabled]="!barChartWidgetConfigForm.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.position' | translate }}</div>
<mat-form-field class="medium-width" appearance="outline" subscriptSizing="dynamic">
<mat-select formControlName="legendPosition">
<mat-option *ngFor="let pos of legendPositions" [value]="pos">
{{ legendPositionTranslationMap.get(pos) | translate }}
</mat-option>
</mat-select>
</mat-form-field>
</div>
<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="Humidity">
</tb-font-settings>
<tb-color-input asBoxInput
colorClearButton
formControlName="legendLabelColor">
</tb-color-input>
</div>
</div>
</ng-template>
</mat-expansion-panel>
</div>
<div class="tb-form-panel tb-slide-toggle">
<mat-expansion-panel class="tb-settings" [expanded]="barChartWidgetConfigForm.get('showTooltip').value" [disabled]="!barChartWidgetConfigForm.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.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 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>

323
ui-ngx/src/app/modules/home/components/widget/config/basic/chart/bar-chart-with-labels-basic-config.component.ts

@ -0,0 +1,323 @@
///
/// Copyright © 2016-2023 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 } from '@core/utils';
import {
cssSizeToStrSize,
DateFormatProcessor,
DateFormatSettings,
resolveCssSize
} from '@shared/models/widget-settings.models';
import {
barChartWithLabelsDefaultSettings,
BarChartWithLabelsWidgetSettings
} from '@home/components/widget/lib/chart/bar-chart-with-labels-widget.models';
@Component({
selector: 'tb-bar-chart-with-labels-basic-config',
templateUrl: './bar-chart-with-labels-basic-config.component.html',
styleUrls: ['../basic-config.scss']
})
export class BarChartWithLabelsBasicConfigComponent extends BasicWidgetConfigComponent {
public get datasource(): Datasource {
const datasources: Datasource[] = this.barChartWidgetConfigForm.get('datasources').value;
if (datasources && datasources.length) {
return datasources[0];
} else {
return null;
}
}
legendPositions = legendPositions;
legendPositionTranslationMap = legendPositionTranslationMap;
barChartWidgetConfigForm: 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.barChartWidgetConfigForm;
}
protected defaultDataKeys(configData: WidgetConfigComponentData): DataKey[] {
return [{ name: 'temperature', label: 'Temperature', type: DataKeyType.timeseries }];
}
protected onConfigSet(configData: WidgetConfigComponentData) {
const settings: BarChartWithLabelsWidgetSettings = {...barChartWithLabelsDefaultSettings, ...(configData.config.settings || {})};
const iconSize = resolveCssSize(configData.config.iconSize);
this.barChartWidgetConfigForm = this.fb.group({
timewindowConfig: [getTimewindowConfig(configData.config), []],
datasources: [configData.config.datasources, []],
series: [this.getSeries(configData.config.datasources), []],
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, []],
showBarLabel: [settings.showBarLabel, []],
barLabelFont: [settings.barLabelFont, []],
barLabelColor: [settings.barLabelColor, []],
showBarValue: [settings.showBarValue, []],
barValueFont: [settings.barValueFont, []],
barValueColor: [settings.barValueColor, []],
units: [configData.config.units, []],
decimals: [configData.config.decimals, []],
showLegend: [settings.showLegend, []],
legendPosition: [settings.legendPosition, []],
legendLabelFont: [settings.legendLabelFont, []],
legendLabelColor: [settings.legendLabelColor, []],
showTooltip: [settings.showTooltip, []],
tooltipValueFont: [settings.tooltipValueFont, []],
tooltipValueColor: [settings.tooltipValueColor, []],
tooltipShowDate: [settings.tooltipShowDate, []],
tooltipDateFormat: [settings.tooltipDateFormat, []],
tooltipDateFont: [settings.tooltipDateFont, []],
tooltipDateColor: [settings.tooltipDateColor, []],
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.showBarLabel = config.showBarLabel;
this.widgetConfig.config.settings.barLabelFont = config.barLabelFont;
this.widgetConfig.config.settings.barLabelColor = config.barLabelColor;
this.widgetConfig.config.settings.showBarValue = config.showBarValue;
this.widgetConfig.config.settings.barValueFont = config.barValueFont;
this.widgetConfig.config.settings.barValueColor = config.barValueColor;
this.widgetConfig.config.units = config.units;
this.widgetConfig.config.decimals = config.decimals;
this.widgetConfig.config.settings.showLegend = config.showLegend;
this.widgetConfig.config.settings.legendPosition = config.legendPosition;
this.widgetConfig.config.settings.legendLabelFont = config.legendLabelFont;
this.widgetConfig.config.settings.legendLabelColor = config.legendLabelColor;
this.widgetConfig.config.settings.showTooltip = config.showTooltip;
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.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', 'showBarLabel', 'showBarValue', 'showLegend', 'showTooltip', 'tooltipShowDate'];
}
protected updateValidators(emitEvent: boolean, trigger?: string) {
const showTitle: boolean = this.barChartWidgetConfigForm.get('showTitle').value;
const showIcon: boolean = this.barChartWidgetConfigForm.get('showIcon').value;
const showBarLabel: boolean = this.barChartWidgetConfigForm.get('showBarLabel').value;
const showBarValue: boolean = this.barChartWidgetConfigForm.get('showBarValue').value;
const showLegend: boolean = this.barChartWidgetConfigForm.get('showLegend').value;
const showTooltip: boolean = this.barChartWidgetConfigForm.get('showTooltip').value;
const tooltipShowDate: boolean = this.barChartWidgetConfigForm.get('tooltipShowDate').value;
if (showTitle) {
this.barChartWidgetConfigForm.get('title').enable();
this.barChartWidgetConfigForm.get('titleFont').enable();
this.barChartWidgetConfigForm.get('titleColor').enable();
this.barChartWidgetConfigForm.get('showIcon').enable({emitEvent: false});
if (showIcon) {
this.barChartWidgetConfigForm.get('iconSize').enable();
this.barChartWidgetConfigForm.get('iconSizeUnit').enable();
this.barChartWidgetConfigForm.get('icon').enable();
this.barChartWidgetConfigForm.get('iconColor').enable();
} else {
this.barChartWidgetConfigForm.get('iconSize').disable();
this.barChartWidgetConfigForm.get('iconSizeUnit').disable();
this.barChartWidgetConfigForm.get('icon').disable();
this.barChartWidgetConfigForm.get('iconColor').disable();
}
} else {
this.barChartWidgetConfigForm.get('title').disable();
this.barChartWidgetConfigForm.get('titleFont').disable();
this.barChartWidgetConfigForm.get('titleColor').disable();
this.barChartWidgetConfigForm.get('showIcon').disable({emitEvent: false});
this.barChartWidgetConfigForm.get('iconSize').disable();
this.barChartWidgetConfigForm.get('iconSizeUnit').disable();
this.barChartWidgetConfigForm.get('icon').disable();
this.barChartWidgetConfigForm.get('iconColor').disable();
}
if (showBarLabel) {
this.barChartWidgetConfigForm.get('barLabelFont').enable();
this.barChartWidgetConfigForm.get('barLabelColor').enable();
} else {
this.barChartWidgetConfigForm.get('barLabelFont').disable();
this.barChartWidgetConfigForm.get('barLabelColor').disable();
}
if (showBarValue) {
this.barChartWidgetConfigForm.get('barValueFont').enable();
this.barChartWidgetConfigForm.get('barValueColor').enable();
} else {
this.barChartWidgetConfigForm.get('barValueFont').disable();
this.barChartWidgetConfigForm.get('barValueColor').disable();
}
if (showLegend) {
this.barChartWidgetConfigForm.get('legendPosition').enable();
this.barChartWidgetConfigForm.get('legendLabelFont').enable();
this.barChartWidgetConfigForm.get('legendLabelColor').enable();
} else {
this.barChartWidgetConfigForm.get('legendPosition').disable();
this.barChartWidgetConfigForm.get('legendLabelFont').disable();
this.barChartWidgetConfigForm.get('legendLabelColor').disable();
}
if (showTooltip) {
this.barChartWidgetConfigForm.get('tooltipValueFont').enable();
this.barChartWidgetConfigForm.get('tooltipValueColor').enable();
this.barChartWidgetConfigForm.get('tooltipShowDate').enable({emitEvent: false});
this.barChartWidgetConfigForm.get('tooltipBackgroundColor').enable();
this.barChartWidgetConfigForm.get('tooltipBackgroundBlur').enable();
if (tooltipShowDate) {
this.barChartWidgetConfigForm.get('tooltipDateFormat').enable();
this.barChartWidgetConfigForm.get('tooltipDateFont').enable();
this.barChartWidgetConfigForm.get('tooltipDateColor').enable();
} else {
this.barChartWidgetConfigForm.get('tooltipDateFormat').disable();
this.barChartWidgetConfigForm.get('tooltipDateFont').disable();
this.barChartWidgetConfigForm.get('tooltipDateColor').disable();
}
} else {
this.barChartWidgetConfigForm.get('tooltipValueFont').disable();
this.barChartWidgetConfigForm.get('tooltipValueColor').disable();
this.barChartWidgetConfigForm.get('tooltipShowDate').disable({emitEvent: false});
this.barChartWidgetConfigForm.get('tooltipDateFormat').disable();
this.barChartWidgetConfigForm.get('tooltipDateFont').disable();
this.barChartWidgetConfigForm.get('tooltipDateColor').disable();
this.barChartWidgetConfigForm.get('tooltipBackgroundColor').disable();
this.barChartWidgetConfigForm.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 {
const units: string = this.barChartWidgetConfigForm.get('units').value;
const decimals: number = this.barChartWidgetConfigForm.get('decimals').value;
return formatValue(22, decimals, units, false);
}
private _tooltipDatePreviewFn(): string {
const dateFormat: DateFormatSettings = this.barChartWidgetConfigForm.get('tooltipDateFormat').value;
const processor = DateFormatProcessor.fromSettings(this.$injector, dateFormat);
processor.update(Date.now());
return processor.formatted;
}
}

16
ui-ngx/src/app/modules/home/components/widget/config/basic/chart/doughnut-basic-config.component.html

@ -145,14 +145,14 @@
<div>{{ 'legend.position' | translate }}</div> <div>{{ 'legend.position' | translate }}</div>
<mat-form-field class="medium-width" appearance="outline" subscriptSizing="dynamic"> <mat-form-field class="medium-width" appearance="outline" subscriptSizing="dynamic">
<mat-select formControlName="legendPosition"> <mat-select formControlName="legendPosition">
<mat-option *ngFor="let pos of doughnutLegendPositions" [value]="pos"> <mat-option *ngFor="let pos of legendPositions" [value]="pos">
{{ doughnutLegendPositionTranslationMap.get(pos) | translate }} {{ legendPositionTranslationMap.get(pos) | translate }}
</mat-option> </mat-option>
</mat-select> </mat-select>
</mat-form-field> </mat-form-field>
</div> </div>
<div class="tb-form-row space-between"> <div class="tb-form-row space-between">
<div>{{ 'widgets.doughnut.legend-label' | translate }}</div> <div>{{ 'legend.label' | translate }}</div>
<div fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px"> <div fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px">
<tb-font-settings formControlName="legendLabelFont" <tb-font-settings formControlName="legendLabelFont"
previewText="Wind power"> previewText="Wind power">
@ -164,7 +164,7 @@
</div> </div>
</div> </div>
<div class="tb-form-row space-between"> <div class="tb-form-row space-between">
<div>{{ 'widgets.doughnut.legend-value' | translate }}</div> <div>{{ 'legend.value' | translate }}</div>
<div fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px"> <div fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px">
<tb-font-settings formControlName="legendValueFont" <tb-font-settings formControlName="legendValueFont"
[previewText]="valuePreviewFn"> [previewText]="valuePreviewFn">
@ -184,13 +184,13 @@
<mat-panel-title> <mat-panel-title>
<mat-slide-toggle class="mat-slide" formControlName="showTooltip" (click)="$event.stopPropagation()" <mat-slide-toggle class="mat-slide" formControlName="showTooltip" (click)="$event.stopPropagation()"
fxLayoutAlign="center"> fxLayoutAlign="center">
{{ 'widgets.doughnut.tooltip' | translate }} {{ 'widget-config.tooltip' | translate }}
</mat-slide-toggle> </mat-slide-toggle>
</mat-panel-title> </mat-panel-title>
</mat-expansion-panel-header> </mat-expansion-panel-header>
<ng-template matExpansionPanelContent> <ng-template matExpansionPanelContent>
<div class="tb-form-row space-between column-xs"> <div class="tb-form-row space-between column-xs">
<div>{{ 'widgets.doughnut.tooltip-value' | translate }}</div> <div>{{ 'tooltip.value' | translate }}</div>
<div fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px"> <div fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px">
<mat-form-field class="medium-width" appearance="outline" subscriptSizing="dynamic"> <mat-form-field class="medium-width" appearance="outline" subscriptSizing="dynamic">
<mat-select formControlName="tooltipValueType"> <mat-select formControlName="tooltipValueType">
@ -213,14 +213,14 @@
</div> </div>
</div> </div>
<div class="tb-form-row space-between"> <div class="tb-form-row space-between">
<div>{{ 'widgets.doughnut.tooltip-background-color' | translate }}</div> <div>{{ 'tooltip.background-color' | translate }}</div>
<tb-color-input asBoxInput <tb-color-input asBoxInput
colorClearButton colorClearButton
formControlName="tooltipBackgroundColor"> formControlName="tooltipBackgroundColor">
</tb-color-input> </tb-color-input>
</div> </div>
<div class="tb-form-row space-between"> <div class="tb-form-row space-between">
<div>{{ 'widgets.doughnut.tooltip-background-blur' | translate }}</div> <div>{{ 'tooltip.background-blur' | translate }}</div>
<mat-form-field appearance="outline" class="number" subscriptSizing="dynamic"> <mat-form-field appearance="outline" class="number" subscriptSizing="dynamic">
<input matInput formControlName="tooltipBackgroundBlur" type="number" min="0" step="1" placeholder="{{ 'widget-config.set' | translate }}"> <input matInput formControlName="tooltipBackgroundBlur" type="number" min="0" step="1" placeholder="{{ 'widget-config.set' | translate }}">
<div matSuffix>px</div> <div matSuffix>px</div>

8
ui-ngx/src/app/modules/home/components/widget/config/basic/chart/doughnut-basic-config.component.ts

@ -25,6 +25,8 @@ import {
Datasource, Datasource,
datasourcesHasAggregation, datasourcesHasAggregation,
datasourcesHasOnlyComparisonAggregation, datasourcesHasOnlyComparisonAggregation,
legendPositions,
legendPositionTranslationMap,
WidgetConfig WidgetConfig
} from '@shared/models/widget.models'; } from '@shared/models/widget.models';
import { WidgetConfigComponent } from '@home/components/widget/widget-config.component'; import { WidgetConfigComponent } from '@home/components/widget/widget-config.component';
@ -40,8 +42,6 @@ import {
doughnutLayoutImages, doughnutLayoutImages,
doughnutLayouts, doughnutLayouts,
doughnutLayoutTranslations, doughnutLayoutTranslations,
doughnutLegendPositions,
doughnutLegendPositionTranslations,
DoughnutTooltipValueType, DoughnutTooltipValueType,
doughnutTooltipValueTypes, doughnutTooltipValueTypes,
doughnutTooltipValueTypeTranslations, doughnutTooltipValueTypeTranslations,
@ -83,9 +83,9 @@ export class DoughnutBasicConfigComponent extends BasicWidgetConfigComponent {
doughnutLayoutImageMap: Map<DoughnutLayout, string>; doughnutLayoutImageMap: Map<DoughnutLayout, string>;
doughnutLegendPositions = doughnutLegendPositions; legendPositions = legendPositions;
doughnutLegendPositionTranslationMap = doughnutLegendPositionTranslations; legendPositionTranslationMap = legendPositionTranslationMap;
doughnutTooltipValueTypes = doughnutTooltipValueTypes; doughnutTooltipValueTypes = doughnutTooltipValueTypes;

14
ui-ngx/src/app/modules/home/components/widget/config/basic/chart/range-chart-basic-config.component.html

@ -49,7 +49,7 @@
</div> </div>
<div class="tb-form-row column-xs"> <div class="tb-form-row column-xs">
<mat-slide-toggle class="mat-slide fixed-title-width" formControlName="showIcon"> <mat-slide-toggle class="mat-slide fixed-title-width" formControlName="showIcon">
{{ 'widgets.value-chart-card.icon' | translate }} {{ 'widget-config.card-icon' | translate }}
</mat-slide-toggle> </mat-slide-toggle>
<div fxFlex fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px"> <div fxFlex fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px">
<mat-form-field appearance="outline" class="flex number" subscriptSizing="dynamic"> <mat-form-field appearance="outline" class="flex number" subscriptSizing="dynamic">
@ -127,7 +127,7 @@
</mat-form-field> </mat-form-field>
</div> </div>
<div class="tb-form-row space-between"> <div class="tb-form-row space-between">
<div>{{ 'widgets.range-chart.legend-label' | translate }}</div> <div>{{ 'legend.label' | translate }}</div>
<div fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px"> <div fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px">
<tb-font-settings formControlName="legendLabelFont" <tb-font-settings formControlName="legendLabelFont"
previewText="20 - 30"> previewText="20 - 30">
@ -147,13 +147,13 @@
<mat-panel-title> <mat-panel-title>
<mat-slide-toggle class="mat-slide" formControlName="showTooltip" (click)="$event.stopPropagation()" <mat-slide-toggle class="mat-slide" formControlName="showTooltip" (click)="$event.stopPropagation()"
fxLayoutAlign="center"> fxLayoutAlign="center">
{{ 'widgets.range-chart.tooltip' | translate }} {{ 'widget-config.tooltip' | translate }}
</mat-slide-toggle> </mat-slide-toggle>
</mat-panel-title> </mat-panel-title>
</mat-expansion-panel-header> </mat-expansion-panel-header>
<ng-template matExpansionPanelContent> <ng-template matExpansionPanelContent>
<div class="tb-form-row space-between"> <div class="tb-form-row space-between">
<div>{{ 'widgets.range-chart.tooltip-value' | translate }}</div> <div>{{ 'tooltip.value' | translate }}</div>
<div fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px"> <div fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px">
<tb-font-settings formControlName="tooltipValueFont" <tb-font-settings formControlName="tooltipValueFont"
[previewText]="tooltipValuePreviewFn"> [previewText]="tooltipValuePreviewFn">
@ -166,7 +166,7 @@
</div> </div>
<div class="tb-form-row column-xs"> <div class="tb-form-row column-xs">
<mat-slide-toggle class="mat-slide fixed-title-width" formControlName="tooltipShowDate"> <mat-slide-toggle class="mat-slide fixed-title-width" formControlName="tooltipShowDate">
{{ 'widgets.range-chart.tooltip-date' | translate }} {{ 'tooltip.date' | translate }}
</mat-slide-toggle> </mat-slide-toggle>
<div fxFlex.gt-xs fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px"> <div fxFlex.gt-xs fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px">
<tb-date-format-select fxFlex excludeLastUpdateAgo formControlName="tooltipDateFormat"></tb-date-format-select> <tb-date-format-select fxFlex excludeLastUpdateAgo formControlName="tooltipDateFormat"></tb-date-format-select>
@ -180,14 +180,14 @@
</div> </div>
</div> </div>
<div class="tb-form-row space-between"> <div class="tb-form-row space-between">
<div>{{ 'widgets.range-chart.tooltip-background-color' | translate }}</div> <div>{{ 'tooltip.background-color' | translate }}</div>
<tb-color-input asBoxInput <tb-color-input asBoxInput
colorClearButton colorClearButton
formControlName="tooltipBackgroundColor"> formControlName="tooltipBackgroundColor">
</tb-color-input> </tb-color-input>
</div> </div>
<div class="tb-form-row space-between"> <div class="tb-form-row space-between">
<div>{{ 'widgets.range-chart.tooltip-background-blur' | translate }}</div> <div>{{ 'tooltip.background-blur' | translate }}</div>
<mat-form-field appearance="outline" class="number" subscriptSizing="dynamic"> <mat-form-field appearance="outline" class="number" subscriptSizing="dynamic">
<input matInput formControlName="tooltipBackgroundBlur" type="number" min="0" step="1" placeholder="{{ 'widget-config.set' | translate }}"> <input matInput formControlName="tooltipBackgroundBlur" type="number" min="0" step="1" placeholder="{{ 'widget-config.set' | translate }}">
<div matSuffix>px</div> <div matSuffix>px</div>

10
ui-ngx/src/app/modules/home/components/widget/config/basic/indicator/signal-strength-basic-config.component.html

@ -117,14 +117,14 @@
<mat-panel-title> <mat-panel-title>
<mat-slide-toggle class="mat-slide" formControlName="showTooltip" (click)="$event.stopPropagation()" <mat-slide-toggle class="mat-slide" formControlName="showTooltip" (click)="$event.stopPropagation()"
fxLayoutAlign="center"> fxLayoutAlign="center">
{{ 'widgets.signal-strength.tooltip' | translate }} {{ 'widget-config.tooltip' | translate }}
</mat-slide-toggle> </mat-slide-toggle>
</mat-panel-title> </mat-panel-title>
</mat-expansion-panel-header> </mat-expansion-panel-header>
<ng-template matExpansionPanelContent> <ng-template matExpansionPanelContent>
<div class="tb-form-row"> <div class="tb-form-row">
<mat-slide-toggle class="mat-slide fixed-title-width" formControlName="showTooltipValue"> <mat-slide-toggle class="mat-slide fixed-title-width" formControlName="showTooltipValue">
{{ 'widgets.signal-strength.value' | translate }} {{ 'tooltip.value' | translate }}
</mat-slide-toggle> </mat-slide-toggle>
<div fxFlex fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px"> <div fxFlex fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px">
<tb-unit-input class="flex" formControlName="units"></tb-unit-input> <tb-unit-input class="flex" formControlName="units"></tb-unit-input>
@ -144,7 +144,7 @@
</div> </div>
<div class="tb-form-row column-xs"> <div class="tb-form-row column-xs">
<mat-slide-toggle class="mat-slide fixed-title-width" formControlName="showTooltipDate"> <mat-slide-toggle class="mat-slide fixed-title-width" formControlName="showTooltipDate">
{{ 'widgets.signal-strength.date' | translate }} {{ 'tooltip.date' | translate }}
</mat-slide-toggle> </mat-slide-toggle>
<div fxFlex.gt-xs fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px"> <div fxFlex.gt-xs fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px">
<tb-date-format-select fxFlex formControlName="tooltipDateFormat"></tb-date-format-select> <tb-date-format-select fxFlex formControlName="tooltipDateFormat"></tb-date-format-select>
@ -158,13 +158,13 @@
</div> </div>
</div> </div>
<div class="tb-form-row space-between"> <div class="tb-form-row space-between">
<div>{{ 'widgets.signal-strength.background-color' | translate }}</div> <div>{{ 'tooltip.background-color' | translate }}</div>
<tb-color-input asBoxInput <tb-color-input asBoxInput
formControlName="tooltipBackgroundColor"> formControlName="tooltipBackgroundColor">
</tb-color-input> </tb-color-input>
</div> </div>
<div class="tb-form-row space-between"> <div class="tb-form-row space-between">
<div translate>widgets.signal-strength.background-blur</div> <div translate>tooltip.background-blur</div>
<mat-form-field appearance="outline" class="number" subscriptSizing="dynamic"> <mat-form-field appearance="outline" class="number" subscriptSizing="dynamic">
<input matInput formControlName="tooltipBackgroundBlur" type="number" min="0" step="1" <input matInput formControlName="tooltipBackgroundBlur" type="number" min="0" step="1"
placeholder="{{ 'widget-config.set' | translate }}"> placeholder="{{ 'widget-config.set' | translate }}">

309
ui-ngx/src/app/modules/home/components/widget/lib/chart/bar-chart-with-labels-widget.component.ts

@ -36,14 +36,8 @@ import {
textStyle textStyle
} from '@shared/models/widget-settings.models'; } from '@shared/models/widget-settings.models';
import { ResizeObserver } from '@juggle/resize-observer'; import { ResizeObserver } from '@juggle/resize-observer';
import * as echarts from 'echarts/core';
import { Axis } from 'echarts';
import { formatValue } from '@core/utils'; import { formatValue } from '@core/utils';
import { GridComponent, GridComponentOption, TooltipComponent, TooltipComponentOption } from 'echarts/components'; import { DataKey } from '@shared/models/widget.models';
import { LabelLayout } from 'echarts/features';
import { BarChart, BarSeriesOption} from 'echarts/charts';
import { CanvasRenderer } from 'echarts/renderers';
import { DataKey, DataSet } from '@shared/models/widget.models';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
import { ImagePipe } from '@shared/pipe/image.pipe'; import { ImagePipe } from '@shared/pipe/image.pipe';
import { DomSanitizer } from '@angular/platform-browser'; import { DomSanitizer } from '@angular/platform-browser';
@ -51,40 +45,19 @@ import {
barChartWithLabelsDefaultSettings, barChartWithLabelsDefaultSettings,
BarChartWithLabelsWidgetSettings BarChartWithLabelsWidgetSettings
} from '@home/components/widget/lib/chart/bar-chart-with-labels-widget.models'; } from '@home/components/widget/lib/chart/bar-chart-with-labels-widget.models';
import { LabelLayoutOptionCallback } from 'echarts/types/dist/shared';
import BarSeriesModel from 'echarts/types/src/chart/bar/BarSeries';
const axisGetBandWidth = Axis.prototype.getBandWidth;
Axis.prototype.getBandWidth = function(){
const series: any[] = this.model?.parentModel?.getSeries();
if (this.scale.type === 'time' && series?.length && series[0].type === 'series.bar') {
const barWidth = (series[0] as BarSeriesModel).getData().getLayout('size');
return barWidth * (series.length + 1);
} else {
return axisGetBandWidth.call(this);
}
};
echarts.use([
TooltipComponent,
GridComponent,
BarChart,
LabelLayout,
CanvasRenderer
]);
type EChartsOption = echarts.ComposeOption<
| TooltipComponentOption
| GridComponentOption
| BarSeriesOption
>;
type ECharts = echarts.ECharts;
type NamedDataSet = {name: string; value: [number, any]}[]; import * as echarts from 'echarts/core';
import { CustomSeriesOption } from 'echarts/charts';
import { CallbackDataParams, CustomSeriesRenderItem, LabelLayoutOptionCallback } from 'echarts/types/dist/shared';
type BarLabelOption = NonNullable<BarSeriesOption['label']>; import {
ECharts,
echartsModule,
EChartsOption,
echartsTooltipFormatter,
NamedDataSet,
toNamedData
} from '@home/components/widget/lib/chart/echarts-widget.models';
interface BarChartDataItem { interface BarChartDataItem {
id: string; id: string;
@ -100,17 +73,6 @@ interface BarChartLegendItem {
enabled: boolean; enabled: boolean;
} }
const toNamedData = (data: DataSet): NamedDataSet => {
if (!data?.length) {
return [];
} else {
return data.map(d => ({
name: d[0] + '',
value: d
}));
}
};
@Component({ @Component({
selector: 'tb-bar-chart-with-labels-widget', selector: 'tb-bar-chart-with-labels-widget',
templateUrl: './bar-chart-with-labels-widget.component.html', templateUrl: './bar-chart-with-labels-widget.component.html',
@ -153,7 +115,7 @@ export class BarChartWithLabelsWidgetComponent implements OnInit, OnDestroy, Aft
private tooltipDateFormat: DateFormatProcessor; private tooltipDateFormat: DateFormatProcessor;
private barLabelOptions: BarLabelOption; private barRenderItem: CustomSeriesRenderItem;
private barLabelLayoutCallback: LabelLayoutOptionCallback; private barLabelLayoutCallback: LabelLayoutOptionCallback;
constructor(private imagePipe: ImagePipe, constructor(private imagePipe: ImagePipe,
@ -216,41 +178,85 @@ export class BarChartWithLabelsWidgetComponent implements OnInit, OnDestroy, Aft
const barValueStyle: ComponentStyle = textStyle(this.settings.barValueFont); const barValueStyle: ComponentStyle = textStyle(this.settings.barValueFont);
delete barValueStyle.lineHeight; delete barValueStyle.lineHeight;
barValueStyle.fontSize = this.settings.barValueFont.size; barValueStyle.fontSize = this.settings.barValueFont.size;
barValueStyle.color = this.settings.barValueColor; barValueStyle.fill = this.settings.barValueColor;
const barLabelStyle: ComponentStyle = textStyle(this.settings.barLabelFont); const barLabelStyle: ComponentStyle = textStyle(this.settings.barLabelFont);
delete barLabelStyle.lineHeight; delete barLabelStyle.lineHeight;
barLabelStyle.fontSize = this.settings.barLabelFont.size; barLabelStyle.fontSize = this.settings.barLabelFont.size;
barLabelStyle.color = this.settings.barLabelColor; barLabelStyle.fill = this.settings.barLabelColor;
this.barLabelOptions = { this.barRenderItem = (params, api) => {
show: this.settings.showBarLabel || this.settings.showBarValue,
position: 'insideBottom', const interval = this.ctx.defaultSubscription.timeWindow.interval;
distance: 15, const enabledDataItems = this.dataItems.filter(d => d.enabled);
align: 'left', const barInterval = interval / (enabledDataItems.length + 1);
verticalAlign: 'middle', const intervalGap = barInterval / 2;
rotate: 90,
formatter: (params) => { const index = enabledDataItems.findIndex(d => d.id === params.seriesId);
const parts: string[] = []; const time = api.value(0) as number;
if (this.settings.showBarValue) { const value = api.value(1);
const value = formatValue(params.value[1], this.decimals, '', false); const start = time - interval / 2;
parts.push(`{value|${value}}`); const startTime = start + intervalGap + barInterval * index;
} const delta = barInterval;
if (this.settings.showBarLabel) { const lowerLeft = api.coord([startTime, value]);
parts.push(`{label|${params.seriesName}}`); const height = api.size([delta, value])[1];
} const width = api.size([delta, 10])[0];
return parts.join(' ');
}, const coordSys: {x: number; y: number; width: number; height: number} = params.coordSys as any;
rich: {
value: barValueStyle, const rectShape = echarts.graphic.clipRectByRect({
label: barLabelStyle x: lowerLeft[0],
y: lowerLeft[1],
width,
height
}, {
x: coordSys.x,
y: coordSys.y,
width: coordSys.width,
height: coordSys.height
});
const zeroPos = api.coord([0, 0]);
const labelParts: string[] = [];
if (this.settings.showBarValue) {
const labelValue = formatValue(value, this.decimals, '', false);
labelParts.push(`{value|${labelValue}}`);
} }
if (this.settings.showBarLabel) {
labelParts.push(`{label|${params.seriesName}}`);
}
const barLabel = labelParts.join(' ');
return rectShape && {
type: 'rect',
id: time + '',
shape: rectShape,
style: {
fill: api.visual('color'),
text: barLabel,
textPosition: 'insideBottom',
textRotation: Math.PI / 2,
textDistance: 15,
textStrokeWidth: 0,
textAlign: 'left',
textVerticalAlign: 'middle',
rich: {
value: barValueStyle,
label: barLabelStyle
}
},
focus: 'series',
transition: 'all',
enterFrom: {
style: { opacity: 0 },
shape: { height: 0, y: zeroPos[1] }
}
};
}; };
this.barLabelLayoutCallback = (params) => { this.barLabelLayoutCallback = (params) => {
if (params.rect.width - params.labelRect.width < 2) { if (params.rect.width - params.labelRect.width < 2) {
return { return {
y: '-100%' y: '1000%',
}; };
} else { } else {
return { return {
@ -288,38 +294,44 @@ export class BarChartWithLabelsWidgetComponent implements OnInit, OnDestroy, Aft
public onDataUpdated() { public onDataUpdated() {
let minTime = this.ctx.defaultSubscription.timeWindow.minTime; let minTime = this.ctx.defaultSubscription.timeWindow.minTime;
let maxTime = this.ctx.defaultSubscription.timeWindow.maxTime;
let dataMin = Number.MAX_VALUE;
let dataMax = Number.MIN_VALUE;
for (const item of this.dataItems) { for (const item of this.dataItems) {
const datasourceData = this.ctx.data ? this.ctx.data.find(d => d.dataKey === item.dataKey) : null; const datasourceData = this.ctx.data ? this.ctx.data.find(d => d.dataKey === item.dataKey) : null;
const namedData = datasourceData?.data ? toNamedData(datasourceData.data) : []; item.data = datasourceData?.data ? toNamedData(datasourceData.data) : [];
item.data = namedData;
if (datasourceData.data.length) { if (datasourceData.data.length) {
minTime = Math.min(datasourceData.data[0][0], minTime); dataMin = Math.min(datasourceData.data[0][0], dataMin);
dataMax = Math.max(datasourceData.data[datasourceData.data.length-1][0], dataMax);
} }
} }
if (dataMin !== Number.MAX_VALUE) {
minTime = dataMin - this.ctx.defaultSubscription.timeWindow.interval / 2;
}
if (dataMax !== Number.MIN_VALUE) {
dataMax = dataMax + this.ctx.defaultSubscription.timeWindow.interval / 2;
maxTime = Math.max(dataMax, maxTime);
}
if (this.barChart) { if (this.barChart) {
(this.barChartOptions.xAxis as any).min = minTime; (this.barChartOptions.xAxis as any).min = minTime;
(this.barChartOptions.xAxis as any).max = this.ctx.defaultSubscription.timeWindow.maxTime; (this.barChartOptions.xAxis as any).max = maxTime;
(this.barChartOptions.xAxis as any).tbTimewindowInterval = this.ctx.defaultSubscription.timeWindow.interval;
this.barChartOptions.series = this.updateSeries(); this.barChartOptions.series = this.updateSeries();
this.barChart.setOption(this.barChartOptions); this.barChart.setOption(this.barChartOptions);
} }
} }
private updateSeries(): Array<BarSeriesOption> { private updateSeries(): Array<CustomSeriesOption> {
const series: Array<BarSeriesOption> = []; const series: Array<CustomSeriesOption> = [];
for (const item of this.dataItems) { for (const item of this.dataItems) {
if (item.enabled) { if (item.enabled) {
const seriesOption: BarSeriesOption = { const seriesOption: CustomSeriesOption = {
type: 'bar', type: 'custom',
barGap: 0,
barMaxWidth: 40,
id: item.id, id: item.id,
name: item.dataKey.label, name: item.dataKey.label,
label: this.barLabelOptions,
color: item.dataKey.color, color: item.dataKey.color,
data: item.data, data: item.data,
emphasis: { renderItem: this.barRenderItem,
focus: 'series'
},
labelLayout: this.barLabelLayoutCallback labelLayout: this.barLabelLayoutCallback
}; };
series.push(seriesOption); series.push(seriesOption);
@ -328,6 +340,7 @@ export class BarChartWithLabelsWidgetComponent implements OnInit, OnDestroy, Aft
return series; return series;
} }
public onLegendItemEnter(item: BarChartLegendItem) { public onLegendItemEnter(item: BarChartLegendItem) {
this.barChart.dispatchAction({ this.barChart.dispatchAction({
type: 'highlight', type: 'highlight',
@ -347,26 +360,26 @@ export class BarChartWithLabelsWidgetComponent implements OnInit, OnDestroy, Aft
const dataItem = this.dataItems.find(d => d.id === item.id); const dataItem = this.dataItems.find(d => d.id === item.id);
if (dataItem) { if (dataItem) {
dataItem.enabled = enable; dataItem.enabled = enable;
if (!enable) {
this.barChart.dispatchAction({
type: 'downplay',
seriesId: item.id
});
}
this.barChartOptions.series = this.updateSeries(); this.barChartOptions.series = this.updateSeries();
this.barChart.setOption(this.barChartOptions, true); this.barChart.setOption(this.barChartOptions, {replaceMerge: ['series']});
item.enabled = enable; item.enabled = enable;
if (enable) { if (enable) {
this.barChart.dispatchAction({ this.barChart.dispatchAction({
type: 'highlight', type: 'highlight',
seriesId: item.id seriesId: item.id
}); });
} else {
for (const otherItem of this.legendItems.filter(i => i.id !== item.id)) {
this.barChart.dispatchAction({
type: 'highlight',
seriesId: otherItem.id
});
}
} }
} }
} }
private drawChart() { private drawChart() {
echartsModule.init();
this.barChart = echarts.init(this.chartShape.nativeElement, null, { this.barChart = echarts.init(this.chartShape.nativeElement, null, {
renderer: 'canvas', renderer: 'canvas',
}); });
@ -383,6 +396,7 @@ export class BarChartWithLabelsWidgetComponent implements OnInit, OnDestroy, Aft
}, },
xAxis: { xAxis: {
type: 'time', type: 'time',
scale: true,
axisTick: { axisTick: {
show: false show: false
}, },
@ -393,17 +407,19 @@ export class BarChartWithLabelsWidgetComponent implements OnInit, OnDestroy, Aft
axisLine: { axisLine: {
onZero: false onZero: false
}, },
min: this.ctx.defaultSubscription.timeWindow.minTime, min: this.ctx.defaultSubscription.timeWindow.minTime - this.ctx.defaultSubscription.timeWindow.interval / 2,
max: this.ctx.defaultSubscription.timeWindow.maxTime max: this.ctx.defaultSubscription.timeWindow.maxTime + this.ctx.defaultSubscription.timeWindow.interval / 2
}, },
yAxis: { yAxis: {
type: 'value', type: 'value',
axisLabel: { axisLabel: {
formatter: value => formatValue(value, this.decimals, this.units, false) formatter: (value: any) => formatValue(value, this.decimals, this.units, false)
} }
} }
}; };
(this.barChartOptions.xAxis as any).tbTimewindowInterval = this.ctx.defaultSubscription.timeWindow.interval;
this.barChartOptions.series = this.updateSeries(); this.barChartOptions.series = this.updateSeries();
if (this.settings.showTooltip) { if (this.settings.showTooltip) {
@ -412,44 +428,14 @@ export class BarChartWithLabelsWidgetComponent implements OnInit, OnDestroy, Aft
axisPointer: { axisPointer: {
type: 'shadow' type: 'shadow'
}, },
formatter: (params: any[]) => { formatter: (params: CallbackDataParams[]) => {
if (!params.length || !params[0]) {
return null;
}
const tooltipElement: HTMLElement = this.renderer.createElement('div');
this.renderer.setStyle(tooltipElement, 'display', 'flex');
this.renderer.setStyle(tooltipElement, 'flex-direction', 'column');
this.renderer.setStyle(tooltipElement, 'align-items', 'flex-start');
this.renderer.setStyle(tooltipElement, 'gap', '4px');
if (this.settings.tooltipShowDate) {
const dateElement: HTMLElement = this.renderer.createElement('div');
const ts = params[0].value[0];
this.tooltipDateFormat.update(ts);
this.renderer.appendChild(dateElement, this.renderer.createText(this.tooltipDateFormat.formatted));
this.renderer.setStyle(dateElement, 'font-family', this.settings.tooltipDateFont.family);
this.renderer.setStyle(dateElement, 'font-size', this.settings.tooltipDateFont.size + this.settings.tooltipDateFont.sizeUnit);
this.renderer.setStyle(dateElement, 'font-style', this.settings.tooltipDateFont.style);
this.renderer.setStyle(dateElement, 'font-weight', this.settings.tooltipDateFont.weight);
this.renderer.setStyle(dateElement, 'line-height', this.settings.tooltipDateFont.lineHeight);
this.renderer.setStyle(dateElement, 'color', this.settings.tooltipDateColor);
this.renderer.appendChild(tooltipElement, dateElement);
}
let seriesParams = null;
const focusedSeriesIndex = this.focusedSeriesIndex(); const focusedSeriesIndex = this.focusedSeriesIndex();
if (focusedSeriesIndex > -1) { return echartsTooltipFormatter(this.renderer, this.tooltipDateFormat,
seriesParams = params.find(param => param.seriesIndex === focusedSeriesIndex); this.settings, params, this.decimals, this.units, focusedSeriesIndex);
}
if (seriesParams) {
this.renderer.appendChild(tooltipElement, this.constructTooltipSeriesElement(seriesParams));
} else {
for (seriesParams of params) {
this.renderer.appendChild(tooltipElement, this.constructTooltipSeriesElement(seriesParams));
}
}
return tooltipElement;
}, },
padding: [8, 12], padding: [8, 12],
backgroundColor: this.settings.tooltipBackgroundColor, backgroundColor: this.settings.tooltipBackgroundColor,
borderWidth: 0,
extraCssText: `line-height: 1; backdrop-filter: blur(${this.settings.tooltipBackgroundBlur}px);` extraCssText: `line-height: 1; backdrop-filter: blur(${this.settings.tooltipBackgroundBlur}px);`
}; };
} }
@ -463,65 +449,22 @@ export class BarChartWithLabelsWidgetComponent implements OnInit, OnDestroy, Aft
this.onResize(); this.onResize();
} }
private constructTooltipSeriesElement(seriesParams): HTMLElement {
const labelValueElement: HTMLElement = this.renderer.createElement('div');
this.renderer.setStyle(labelValueElement, 'display', 'flex');
this.renderer.setStyle(labelValueElement, 'flex-direction', 'row');
this.renderer.setStyle(labelValueElement, 'align-items', 'center');
this.renderer.setStyle(labelValueElement, 'align-self', 'stretch');
this.renderer.setStyle(labelValueElement, 'gap', '12px');
const labelElement: HTMLElement = this.renderer.createElement('div');
this.renderer.setStyle(labelElement, 'display', 'flex');
this.renderer.setStyle(labelElement, 'align-items', 'center');
this.renderer.setStyle(labelElement, 'gap', '8px');
this.renderer.appendChild(labelValueElement, labelElement);
const circleElement: HTMLElement = this.renderer.createElement('div');
this.renderer.setStyle(circleElement, 'width', '8px');
this.renderer.setStyle(circleElement, 'height', '8px');
this.renderer.setStyle(circleElement, 'border-radius', '50%');
this.renderer.setStyle(circleElement, 'background', seriesParams.color);
this.renderer.appendChild(labelElement, circleElement);
const labelTextElement: HTMLElement = this.renderer.createElement('div');
this.renderer.appendChild(labelTextElement, this.renderer.createText(seriesParams.seriesName));
this.renderer.setStyle(labelTextElement, 'font-family', 'Roboto');
this.renderer.setStyle(labelTextElement, 'font-size', '12px');
this.renderer.setStyle(labelTextElement, 'font-style', 'normal');
this.renderer.setStyle(labelTextElement, 'font-weight', '400');
this.renderer.setStyle(labelTextElement, 'line-height', '16px');
this.renderer.setStyle(labelTextElement, 'letter-spacing', '0.4px');
this.renderer.setStyle(labelTextElement, 'color', 'rgba(0, 0, 0, 0.76)');
this.renderer.appendChild(labelElement, labelTextElement);
const valueElement: HTMLElement = this.renderer.createElement('div');
const value = formatValue(seriesParams.value[1], this.decimals, this.units, false);
this.renderer.appendChild(valueElement, this.renderer.createText(value));
this.renderer.setStyle(valueElement, 'flex', '1');
this.renderer.setStyle(valueElement, 'text-align', 'end');
this.renderer.setStyle(valueElement, 'font-family', this.settings.tooltipValueFont.family);
this.renderer.setStyle(valueElement, 'font-size', this.settings.tooltipValueFont.size + this.settings.tooltipValueFont.sizeUnit);
this.renderer.setStyle(valueElement, 'font-style', this.settings.tooltipValueFont.style);
this.renderer.setStyle(valueElement, 'font-weight', this.settings.tooltipValueFont.weight);
this.renderer.setStyle(valueElement, 'line-height', this.settings.tooltipValueFont.lineHeight);
this.renderer.setStyle(valueElement, 'color', this.settings.tooltipValueColor);
this.renderer.appendChild(labelValueElement, valueElement);
return labelValueElement;
}
private focusedSeriesIndex(): number { private focusedSeriesIndex(): number {
let index = - 1; let index = - 1;
// @ts-ignore const views: any[] = (this.barChart as any)._chartsViews;
const views: any[] = this.barChart._chartsViews;
if (views) { if (views) {
const hasBlurredView = !!views.find(view => { const hasBlurredView = !!views.find(view => {
const graphicEls: any[] = view._data._graphicEls; const graphicEls: any[] = view._data._graphicEls;
return !!graphicEls.find(el => el.currentStates.includes('blur')); return !!graphicEls.find(el => el?.currentStates.includes('blur'));
}); });
if (hasBlurredView) { if (hasBlurredView) {
const focusedView = views.find(view => { const focusedView = views.find(view => {
const graphicEls: any[] = view._data._graphicEls; const graphicEls: any[] = view._data._graphicEls;
return !!graphicEls.find(el => !el.currentStates.includes('blur')); return !!graphicEls.find(el => !el?.currentStates.includes('blur'));
}); });
if (focusedView) { if (focusedView) {
index = focusedView._model.seriesIndex; index = !!focusedView._model ?
focusedView._model.seriesIndex : (!!focusedView.__model ? focusedView.__model.seriesIndex : -1);
} }
} }
} }

19
ui-ngx/src/app/modules/home/components/widget/lib/chart/bar-chart-with-labels-widget.models.ts

@ -14,15 +14,11 @@
/// limitations under the License. /// limitations under the License.
/// ///
import { import { BackgroundSettings, BackgroundType, customDateFormat, Font } from '@shared/models/widget-settings.models';
BackgroundSettings,
BackgroundType, customDateFormat,
DateFormatSettings,
Font
} from '@shared/models/widget-settings.models';
import { LegendPosition } from '@shared/models/widget.models'; import { LegendPosition } from '@shared/models/widget.models';
import { EChartsTooltipWidgetSettings } from '@home/components/widget/lib/chart/echarts-widget.models';
export interface BarChartWithLabelsWidgetSettings { export interface BarChartWithLabelsWidgetSettings extends EChartsTooltipWidgetSettings {
showBarLabel: boolean; showBarLabel: boolean;
barLabelFont: Font; barLabelFont: Font;
barLabelColor: string; barLabelColor: string;
@ -33,15 +29,6 @@ export interface BarChartWithLabelsWidgetSettings {
legendPosition: LegendPosition; legendPosition: LegendPosition;
legendLabelFont: Font; legendLabelFont: Font;
legendLabelColor: string; legendLabelColor: string;
showTooltip: boolean;
tooltipValueFont: Font;
tooltipValueColor: string;
tooltipShowDate: boolean;
tooltipDateFormat: DateFormatSettings;
tooltipDateFont: Font;
tooltipDateColor: string;
tooltipBackgroundColor: string;
tooltipBackgroundBlur: number;
background: BackgroundSettings; background: BackgroundSettings;
} }

22
ui-ngx/src/app/modules/home/components/widget/lib/chart/doughnut-widget.component.ts

@ -30,7 +30,6 @@ import {
import { import {
doughnutDefaultSettings, doughnutDefaultSettings,
DoughnutLayout, DoughnutLayout,
DoughnutLegendPosition,
DoughnutTooltipValueType, DoughnutTooltipValueType,
DoughnutWidgetSettings DoughnutWidgetSettings
} from '@home/components/widget/lib/chart/doughnut-widget.models'; } from '@home/components/widget/lib/chart/doughnut-widget.models';
@ -49,25 +48,11 @@ import { TranslateService } from '@ngx-translate/core';
import { PieDataItemOption } from 'echarts/types/src/chart/pie/PieSeries'; import { PieDataItemOption } from 'echarts/types/src/chart/pie/PieSeries';
import { formatValue, isDefinedAndNotNull, isNumeric } from '@core/utils'; import { formatValue, isDefinedAndNotNull, isNumeric } from '@core/utils';
import { SVG, Svg, Text } from '@svgdotjs/svg.js'; import { SVG, Svg, Text } from '@svgdotjs/svg.js';
import { DataKey } from '@shared/models/widget.models'; import { DataKey, LegendPosition } from '@shared/models/widget.models';
import { TooltipComponent, TooltipComponentOption } from 'echarts/components';
import { PieChart, PieSeriesOption } from 'echarts/charts';
import { SVGRenderer } from 'echarts/renderers';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
import { ImagePipe } from '@shared/pipe/image.pipe'; import { ImagePipe } from '@shared/pipe/image.pipe';
import { DomSanitizer } from '@angular/platform-browser'; import { DomSanitizer } from '@angular/platform-browser';
import { ECharts, echartsModule, EChartsOption } from '@home/components/widget/lib/chart/echarts-widget.models';
echarts.use([
TooltipComponent,
PieChart,
SVGRenderer
]);
type EChartsOption = echarts.ComposeOption<
| TooltipComponentOption
| PieSeriesOption
>;
type ECharts = echarts.ECharts;
const shapeSize = 134; const shapeSize = 134;
const shapeSegmentWidth = 13.4; const shapeSegmentWidth = 13.4;
@ -178,7 +163,7 @@ export class DoughnutWidgetComponent implements OnInit, OnDestroy, AfterViewInit
if (this.showLegend) { if (this.showLegend) {
this.legendItems = []; this.legendItems = [];
this.legendClass = `legend-${this.settings.legendPosition}`; this.legendClass = `legend-${this.settings.legendPosition}`;
this.legendHorizontal = [DoughnutLegendPosition.left, DoughnutLegendPosition.right].includes(this.settings.legendPosition); this.legendHorizontal = [LegendPosition.left, LegendPosition.right].includes(this.settings.legendPosition);
this.legendLabelStyle = textStyle(this.settings.legendLabelFont); this.legendLabelStyle = textStyle(this.settings.legendLabelFont);
this.disabledLegendLabelStyle = textStyle(this.settings.legendLabelFont); this.disabledLegendLabelStyle = textStyle(this.settings.legendLabelFont);
this.legendLabelStyle.color = this.settings.legendLabelColor; this.legendLabelStyle.color = this.settings.legendLabelColor;
@ -387,6 +372,7 @@ export class DoughnutWidgetComponent implements OnInit, OnDestroy, AfterViewInit
} }
private drawDoughnut() { private drawDoughnut() {
echartsModule.init();
const shapeWidth = this.doughnutShape.nativeElement.getBoundingClientRect().width; const shapeWidth = this.doughnutShape.nativeElement.getBoundingClientRect().width;
const shapeHeight = this.doughnutShape.nativeElement.getBoundingClientRect().height; const shapeHeight = this.doughnutShape.nativeElement.getBoundingClientRect().height;
const size = this.settings.autoScale ? shapeSize : Math.min(shapeWidth, shapeHeight); const size = this.settings.autoScale ? shapeSize : Math.min(shapeWidth, shapeHeight);

23
ui-ngx/src/app/modules/home/components/widget/lib/chart/doughnut-widget.models.ts

@ -21,6 +21,7 @@ import {
constantColor, constantColor,
Font Font
} from '@shared/models/widget-settings.models'; } from '@shared/models/widget-settings.models';
import { LegendPosition } from '@shared/models/widget.models';
export enum DoughnutLayout { export enum DoughnutLayout {
default = 'default', default = 'default',
@ -50,24 +51,6 @@ export const horizontalDoughnutLayoutImages = new Map<DoughnutLayout, string>(
] ]
); );
export enum DoughnutLegendPosition {
top = 'top',
bottom = 'bottom',
left = 'left',
right = 'right'
}
export const doughnutLegendPositions = Object.keys(DoughnutLegendPosition) as DoughnutLegendPosition[];
export const doughnutLegendPositionTranslations = new Map<DoughnutLegendPosition, string>(
[
[DoughnutLegendPosition.top, 'widgets.doughnut.legend-position-top'],
[DoughnutLegendPosition.bottom, 'widgets.doughnut.legend-position-bottom'],
[DoughnutLegendPosition.left, 'widgets.doughnut.legend-position-left'],
[DoughnutLegendPosition.right, 'widgets.doughnut.legend-position-right']
]
);
export enum DoughnutTooltipValueType { export enum DoughnutTooltipValueType {
absolute = 'absolute', absolute = 'absolute',
percentage = 'percentage' percentage = 'percentage'
@ -90,7 +73,7 @@ export interface DoughnutWidgetSettings {
totalValueFont: Font; totalValueFont: Font;
totalValueColor: ColorSettings; totalValueColor: ColorSettings;
showLegend: boolean; showLegend: boolean;
legendPosition: DoughnutLegendPosition; legendPosition: LegendPosition;
legendLabelFont: Font; legendLabelFont: Font;
legendLabelColor: string; legendLabelColor: string;
legendValueFont: Font; legendValueFont: Font;
@ -120,7 +103,7 @@ export const doughnutDefaultSettings = (horizontal: boolean): DoughnutWidgetSett
}, },
totalValueColor: constantColor('rgba(0, 0, 0, 0.87)'), totalValueColor: constantColor('rgba(0, 0, 0, 0.87)'),
showLegend: true, showLegend: true,
legendPosition: horizontal ? DoughnutLegendPosition.right : DoughnutLegendPosition.bottom, legendPosition: horizontal ? LegendPosition.right : LegendPosition.bottom,
legendLabelFont: { legendLabelFont: {
family: 'Roboto', family: 'Roboto',
size: 12, size: 12,

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

@ -0,0 +1,214 @@
///
/// Copyright © 2016-2023 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 * as echarts from 'echarts/core';
import { Axis } from 'echarts';
import AxisModel from 'echarts/types/src/coord/cartesian/AxisModel';
import { formatValue, isNumber } from '@core/utils';
import TimeScale from 'echarts/types/src/scale/Time';
import {
DataZoomComponent, DataZoomComponentOption,
GridComponent, GridComponentOption,
MarkLineComponent, MarkLineComponentOption,
TooltipComponent, TooltipComponentOption,
VisualMapComponent, VisualMapComponentOption
} from 'echarts/components';
import {
BarChart,
LineChart,
CustomChart,
CustomSeriesOption,
LineSeriesOption,
BarSeriesOption, PieSeriesOption, PieChart
} from 'echarts/charts';
import { LabelLayout } from 'echarts/features';
import { CanvasRenderer, SVGRenderer } from 'echarts/renderers';
import { DataSet } from '@shared/models/widget.models';
import { CallbackDataParams } from 'echarts/types/dist/shared';
import { Renderer2 } from '@angular/core';
import { DateFormatProcessor, DateFormatSettings, Font } from '@shared/models/widget-settings.models';
class EChartsModule {
private initialized = false;
init() {
if (!this.initialized) {
const axisGetBandWidth = Axis.prototype.getBandWidth;
Axis.prototype.getBandWidth = function(){
const model: AxisModel = this.model;
const axisOption = model.option;
const tbTimewindowInterval = (axisOption as any).tbTimewindowInterval;
if (this.scale.type === 'time' && isNumber(tbTimewindowInterval)) {
const timeScale: TimeScale = this.scale;
const axisExtent: [number, number] = this._extent;
const dataExtent = timeScale.getExtent();
const size = Math.abs(axisExtent[1] - axisExtent[0]);
return tbTimewindowInterval * (size / (dataExtent[1] - dataExtent[0]));
} else {
return axisGetBandWidth.call(this);
}
};
echarts.use([
TooltipComponent,
GridComponent,
VisualMapComponent,
DataZoomComponent,
MarkLineComponent,
LineChart,
BarChart,
PieChart,
CustomChart,
LabelLayout,
CanvasRenderer,
SVGRenderer
]);
this.initialized = true;
}
}
}
export const echartsModule = new EChartsModule();
export type EChartsOption = echarts.ComposeOption<
| TooltipComponentOption
| GridComponentOption
| VisualMapComponentOption
| DataZoomComponentOption
| MarkLineComponentOption
| LineSeriesOption
| CustomSeriesOption
| BarSeriesOption
| PieSeriesOption
>;
export type ECharts = echarts.ECharts;
export type NamedDataSet = {name: string; value: [number, any]}[];
export const toNamedData = (data: DataSet): NamedDataSet => {
if (!data?.length) {
return [];
} else {
return data.map(d => ({
name: d[0] + '',
value: d
}));
}
};
export interface EChartsTooltipWidgetSettings {
showTooltip: boolean;
tooltipValueFont: Font;
tooltipValueColor: string;
tooltipShowDate: boolean;
tooltipDateFormat: DateFormatSettings;
tooltipDateFont: Font;
tooltipDateColor: string;
tooltipBackgroundColor: string;
tooltipBackgroundBlur: number;
}
export const echartsTooltipFormatter = (renderer: Renderer2,
tooltipDateFormat: DateFormatProcessor,
settings: EChartsTooltipWidgetSettings,
params: CallbackDataParams[],
decimals: number,
units: string,
focusedSeriesIndex: number): null | HTMLElement => {
if (!params.length || !params[0]) {
return null;
}
const tooltipElement: HTMLElement = renderer.createElement('div');
renderer.setStyle(tooltipElement, 'display', 'flex');
renderer.setStyle(tooltipElement, 'flex-direction', 'column');
renderer.setStyle(tooltipElement, 'align-items', 'flex-start');
renderer.setStyle(tooltipElement, 'gap', '4px');
if (settings.tooltipShowDate) {
const dateElement: HTMLElement = renderer.createElement('div');
const ts = params[0].value[0];
tooltipDateFormat.update(ts);
renderer.appendChild(dateElement, renderer.createText(tooltipDateFormat.formatted));
renderer.setStyle(dateElement, 'font-family', settings.tooltipDateFont.family);
renderer.setStyle(dateElement, 'font-size', settings.tooltipDateFont.size + settings.tooltipDateFont.sizeUnit);
renderer.setStyle(dateElement, 'font-style', settings.tooltipDateFont.style);
renderer.setStyle(dateElement, 'font-weight', settings.tooltipDateFont.weight);
renderer.setStyle(dateElement, 'line-height', settings.tooltipDateFont.lineHeight);
renderer.setStyle(dateElement, 'color', settings.tooltipDateColor);
renderer.appendChild(tooltipElement, dateElement);
}
let seriesParams: CallbackDataParams = null;
if (focusedSeriesIndex > -1) {
seriesParams = params.find(param => param.seriesIndex === focusedSeriesIndex);
}
if (seriesParams) {
renderer.appendChild(tooltipElement, constructEchartsTooltipSeriesElement(renderer, settings, seriesParams, decimals, units));
} else {
for (seriesParams of params) {
renderer.appendChild(tooltipElement, constructEchartsTooltipSeriesElement(renderer, settings, seriesParams, decimals, units));
}
}
return tooltipElement;
};
const constructEchartsTooltipSeriesElement = (renderer: Renderer2,
settings: EChartsTooltipWidgetSettings,
seriesParams: CallbackDataParams,
decimals: number,
units: string): HTMLElement => {
const labelValueElement: HTMLElement = renderer.createElement('div');
renderer.setStyle(labelValueElement, 'display', 'flex');
renderer.setStyle(labelValueElement, 'flex-direction', 'row');
renderer.setStyle(labelValueElement, 'align-items', 'center');
renderer.setStyle(labelValueElement, 'align-self', 'stretch');
renderer.setStyle(labelValueElement, 'gap', '12px');
const labelElement: HTMLElement = renderer.createElement('div');
renderer.setStyle(labelElement, 'display', 'flex');
renderer.setStyle(labelElement, 'align-items', 'center');
renderer.setStyle(labelElement, 'gap', '8px');
renderer.appendChild(labelValueElement, labelElement);
const circleElement: HTMLElement = renderer.createElement('div');
renderer.setStyle(circleElement, 'width', '8px');
renderer.setStyle(circleElement, 'height', '8px');
renderer.setStyle(circleElement, 'border-radius', '50%');
renderer.setStyle(circleElement, 'background', seriesParams.color);
renderer.appendChild(labelElement, circleElement);
const labelTextElement: HTMLElement = renderer.createElement('div');
renderer.appendChild(labelTextElement, renderer.createText(seriesParams.seriesName));
renderer.setStyle(labelTextElement, 'font-family', 'Roboto');
renderer.setStyle(labelTextElement, 'font-size', '12px');
renderer.setStyle(labelTextElement, 'font-style', 'normal');
renderer.setStyle(labelTextElement, 'font-weight', '400');
renderer.setStyle(labelTextElement, 'line-height', '16px');
renderer.setStyle(labelTextElement, 'letter-spacing', '0.4px');
renderer.setStyle(labelTextElement, 'color', 'rgba(0, 0, 0, 0.76)');
renderer.appendChild(labelElement, labelTextElement);
const valueElement: HTMLElement = renderer.createElement('div');
const value = formatValue(seriesParams.value[1], decimals, units, false);
renderer.appendChild(valueElement, renderer.createText(value));
renderer.setStyle(valueElement, 'flex', '1');
renderer.setStyle(valueElement, 'text-align', 'end');
renderer.setStyle(valueElement, 'font-family', settings.tooltipValueFont.family);
renderer.setStyle(valueElement, 'font-size', settings.tooltipValueFont.size + settings.tooltipValueFont.sizeUnit);
renderer.setStyle(valueElement, 'font-style', settings.tooltipValueFont.style);
renderer.setStyle(valueElement, 'font-weight', settings.tooltipValueFont.weight);
renderer.setStyle(valueElement, 'line-height', settings.tooltipValueFont.lineHeight);
renderer.setStyle(valueElement, 'color', settings.tooltipValueColor);
renderer.appendChild(labelValueElement, valueElement);
return labelValueElement;
};

127
ui-ngx/src/app/modules/home/components/widget/lib/chart/range-chart-widget.component.ts

@ -32,7 +32,8 @@ import {
backgroundStyle, backgroundStyle,
ColorRange, ColorRange,
ComponentStyle, ComponentStyle,
DateFormatProcessor, filterIncludingColorRanges, DateFormatProcessor,
filterIncludingColorRanges,
getDataKey, getDataKey,
overlayStyle, overlayStyle,
sortedColorRange, sortedColorRange,
@ -41,46 +42,18 @@ import {
import { ResizeObserver } from '@juggle/resize-observer'; import { ResizeObserver } from '@juggle/resize-observer';
import * as echarts from 'echarts/core'; import * as echarts from 'echarts/core';
import { formatValue, isDefinedAndNotNull, isNumber } from '@core/utils'; import { formatValue, isDefinedAndNotNull, isNumber } from '@core/utils';
import {
DataZoomComponent,
DataZoomComponentOption,
GridComponent,
GridComponentOption,
MarkLineComponent,
MarkLineComponentOption,
TooltipComponent,
TooltipComponentOption,
VisualMapComponent,
VisualMapComponentOption
} from 'echarts/components';
import { LineChart, LineSeriesOption, } from 'echarts/charts';
import { CanvasRenderer } from 'echarts/renderers';
import { rangeChartDefaultSettings, RangeChartWidgetSettings } from './range-chart-widget.models'; import { rangeChartDefaultSettings, RangeChartWidgetSettings } from './range-chart-widget.models';
import { DataSet } from '@shared/models/widget.models';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
import { ImagePipe } from '@shared/pipe/image.pipe'; import { ImagePipe } from '@shared/pipe/image.pipe';
import { DomSanitizer } from '@angular/platform-browser'; import { DomSanitizer } from '@angular/platform-browser';
import {
echarts.use([ ECharts,
TooltipComponent, echartsModule,
GridComponent, EChartsOption,
VisualMapComponent, echartsTooltipFormatter,
DataZoomComponent, toNamedData
MarkLineComponent, } from '@home/components/widget/lib/chart/echarts-widget.models';
LineChart, import { CallbackDataParams } from 'echarts/types/dist/shared';
CanvasRenderer
]);
type EChartsOption = echarts.ComposeOption<
| TooltipComponentOption
| GridComponentOption
| VisualMapComponentOption
| DataZoomComponentOption
| MarkLineComponentOption
| LineSeriesOption
>;
type ECharts = echarts.ECharts;
interface VisualPiece { interface VisualPiece {
lt?: number; lt?: number;
@ -180,17 +153,6 @@ const toRangeItems = (colorRanges: Array<ColorRange>): RangeItem[] => {
return rangeItems; return rangeItems;
}; };
const toNamedData = (data: DataSet): {name: string; value: [number, any]}[] => {
if (!data?.length) {
return [];
} else {
return data.map(d => ({
name: d[0] + '',
value: d
}));
}
};
const getMarkPoints = (ranges: Array<RangeItem>): number[] => { const getMarkPoints = (ranges: Array<RangeItem>): number[] => {
const points = new Set<number>(); const points = new Set<number>();
for (const range of ranges) { for (const range of ranges) {
@ -346,6 +308,7 @@ export class RangeChartWidgetComponent implements OnInit, OnDestroy, AfterViewIn
} }
private drawChart() { private drawChart() {
echartsModule.init();
const dataKey = getDataKey(this.ctx.datasources); const dataKey = getDataKey(this.ctx.datasources);
this.rangeChart = echarts.init(this.chartShape.nativeElement, null, { this.rangeChart = echarts.init(this.chartShape.nativeElement, null, {
renderer: 'canvas', renderer: 'canvas',
@ -379,7 +342,7 @@ export class RangeChartWidgetComponent implements OnInit, OnDestroy, AfterViewIn
yAxis: { yAxis: {
type: 'value', type: 'value',
axisLabel: { axisLabel: {
formatter: value => formatValue(value, this.decimals, this.units, false) formatter: (value: any) => formatValue(value, this.decimals, this.units, false)
} }
}, },
series: [{ series: [{
@ -442,71 +405,11 @@ export class RangeChartWidgetComponent implements OnInit, OnDestroy, AfterViewIn
if (this.settings.showTooltip) { if (this.settings.showTooltip) {
this.rangeChartOptions.tooltip = { this.rangeChartOptions.tooltip = {
trigger: 'axis', trigger: 'axis',
formatter: (params) => { formatter: (params: CallbackDataParams[]) => echartsTooltipFormatter(this.renderer, this.tooltipDateFormat,
if (!params.length || !params[0]) { this.settings, params, this.decimals, this.units, 0),
return null;
}
const seriesParams = params[0];
const value = formatValue(seriesParams.value[1], this.decimals, this.units, false);
const tooltipElement: HTMLElement = this.renderer.createElement('div');
this.renderer.setStyle(tooltipElement, 'display', 'flex');
this.renderer.setStyle(tooltipElement, 'flex-direction', 'column');
this.renderer.setStyle(tooltipElement, 'align-items', 'flex-start');
this.renderer.setStyle(tooltipElement, 'gap', '4px');
if (this.settings.tooltipShowDate) {
const dateElement: HTMLElement = this.renderer.createElement('div');
const ts = seriesParams.value[0];
this.tooltipDateFormat.update(ts);
this.renderer.appendChild(dateElement, this.renderer.createText(this.tooltipDateFormat.formatted));
this.renderer.setStyle(dateElement, 'font-family', this.settings.tooltipDateFont.family);
this.renderer.setStyle(dateElement, 'font-size', this.settings.tooltipDateFont.size + this.settings.tooltipDateFont.sizeUnit);
this.renderer.setStyle(dateElement, 'font-style', this.settings.tooltipDateFont.style);
this.renderer.setStyle(dateElement, 'font-weight', this.settings.tooltipDateFont.weight);
this.renderer.setStyle(dateElement, 'line-height', this.settings.tooltipDateFont.lineHeight);
this.renderer.setStyle(dateElement, 'color', this.settings.tooltipDateColor);
this.renderer.appendChild(tooltipElement, dateElement);
}
const labelValueElement: HTMLElement = this.renderer.createElement('div');
this.renderer.setStyle(labelValueElement, 'display', 'flex');
this.renderer.setStyle(labelValueElement, 'flex-direction', 'row');
this.renderer.setStyle(labelValueElement, 'align-items', 'center');
this.renderer.setStyle(labelValueElement, 'align-self', 'stretch');
this.renderer.setStyle(labelValueElement, 'gap', '12px');
this.renderer.appendChild(tooltipElement, labelValueElement);
const labelElement: HTMLElement = this.renderer.createElement('div');
this.renderer.setStyle(labelElement, 'display', 'flex');
this.renderer.setStyle(labelElement, 'align-items', 'center');
this.renderer.setStyle(labelElement, 'gap', '8px');
this.renderer.appendChild(labelValueElement, labelElement);
const circleElement: HTMLElement = this.renderer.createElement('div');
this.renderer.setStyle(circleElement, 'width', '8px');
this.renderer.setStyle(circleElement, 'height', '8px');
this.renderer.setStyle(circleElement, 'border-radius', '50%');
this.renderer.setStyle(circleElement, 'background', seriesParams.color);
this.renderer.appendChild(labelElement, circleElement);
const labelTextElement: HTMLElement = this.renderer.createElement('div');
this.renderer.appendChild(labelTextElement, this.renderer.createText(seriesParams.seriesName));
this.renderer.setStyle(labelTextElement, 'font-family', 'Roboto');
this.renderer.setStyle(labelTextElement, 'font-size', '12px');
this.renderer.setStyle(labelTextElement, 'font-style', 'normal');
this.renderer.setStyle(labelTextElement, 'font-weight', '400');
this.renderer.setStyle(labelTextElement, 'line-height', '16px');
this.renderer.setStyle(labelTextElement, 'letter-spacing', '0.4px');
this.renderer.setStyle(labelTextElement, 'color', 'rgba(0, 0, 0, 0.76)');
this.renderer.appendChild(labelElement, labelTextElement);
const valueElement: HTMLElement = this.renderer.createElement('div');
this.renderer.appendChild(valueElement, this.renderer.createText(value));
this.renderer.setStyle(valueElement, 'font-family', this.settings.tooltipValueFont.family);
this.renderer.setStyle(valueElement, 'font-size', this.settings.tooltipValueFont.size + this.settings.tooltipValueFont.sizeUnit);
this.renderer.setStyle(valueElement, 'font-style', this.settings.tooltipValueFont.style);
this.renderer.setStyle(valueElement, 'font-weight', this.settings.tooltipValueFont.weight);
this.renderer.setStyle(valueElement, 'line-height', this.settings.tooltipValueFont.lineHeight);
this.renderer.setStyle(valueElement, 'color', this.settings.tooltipValueColor);
this.renderer.appendChild(labelValueElement, valueElement);
return tooltipElement;
},
padding: [8, 12], padding: [8, 12],
backgroundColor: this.settings.tooltipBackgroundColor, backgroundColor: this.settings.tooltipBackgroundColor,
borderWidth: 0,
extraCssText: `line-height: 1; backdrop-filter: blur(${this.settings.tooltipBackgroundBlur}px);` extraCssText: `line-height: 1; backdrop-filter: blur(${this.settings.tooltipBackgroundBlur}px);`
}; };
} }

16
ui-ngx/src/app/modules/home/components/widget/lib/chart/range-chart-widget.models.ts

@ -18,12 +18,13 @@ import {
BackgroundSettings, BackgroundSettings,
BackgroundType, BackgroundType,
ColorRange, ColorRange,
DateFormatSettings, Font,
Font, simpleDateFormat simpleDateFormat
} from '@shared/models/widget-settings.models'; } from '@shared/models/widget-settings.models';
import { LegendPosition } from '@shared/models/widget.models'; import { LegendPosition } from '@shared/models/widget.models';
import { EChartsTooltipWidgetSettings } from '@home/components/widget/lib/chart/echarts-widget.models';
export interface RangeChartWidgetSettings { export interface RangeChartWidgetSettings extends EChartsTooltipWidgetSettings {
dataZoom: boolean; dataZoom: boolean;
rangeColors: Array<ColorRange>; rangeColors: Array<ColorRange>;
outOfRangeColor: string; outOfRangeColor: string;
@ -32,15 +33,6 @@ export interface RangeChartWidgetSettings {
legendPosition: LegendPosition; legendPosition: LegendPosition;
legendLabelFont: Font; legendLabelFont: Font;
legendLabelColor: string; legendLabelColor: string;
showTooltip: boolean;
tooltipValueFont: Font;
tooltipValueColor: string;
tooltipShowDate: boolean;
tooltipDateFormat: DateFormatSettings;
tooltipDateFont: Font;
tooltipDateColor: string;
tooltipBackgroundColor: string;
tooltipBackgroundBlur: number;
background: BackgroundSettings; background: BackgroundSettings;
} }

150
ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/bar-chart-with-labels-widget-settings.component.html

@ -0,0 +1,150 @@
<!--
Copyright © 2016-2023 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]="barChartWidgetSettingsForm">
<div class="tb-form-panel">
<div class="tb-form-panel-title" translate>widgets.bar-chart.bar-chart-card-style</div>
<div class="tb-form-row space-between">
<mat-slide-toggle class="mat-slide fixed-title-width" formControlName="showBarLabel">
{{ 'widgets.bar-chart.label-on-bar' | translate }}
</mat-slide-toggle>
<div fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px">
<tb-font-settings formControlName="barLabelFont"
disabledLineHeight
forceSizeUnit="px"
previewText="Humidity">
</tb-font-settings>
<tb-color-input asBoxInput
colorClearButton
formControlName="barLabelColor">
</tb-color-input>
</div>
</div>
<div class="tb-form-row space-between">
<mat-slide-toggle class="mat-slide fixed-title-width" formControlName="showBarValue">
{{ 'widgets.bar-chart.value-on-bar' | translate }}
</mat-slide-toggle>
<div fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px">
<tb-font-settings formControlName="barValueFont"
disabledLineHeight
forceSizeUnit="px"
previewText="45">
</tb-font-settings>
<tb-color-input asBoxInput
colorClearButton
formControlName="barValueColor">
</tb-color-input>
</div>
</div>
<div class="tb-form-panel tb-slide-toggle">
<mat-expansion-panel class="tb-settings" [expanded]="barChartWidgetSettingsForm.get('showLegend').value" [disabled]="!barChartWidgetSettingsForm.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.position' | translate }}</div>
<mat-form-field class="medium-width" appearance="outline" subscriptSizing="dynamic">
<mat-select formControlName="legendPosition">
<mat-option *ngFor="let pos of legendPositions" [value]="pos">
{{ legendPositionTranslationMap.get(pos) | translate }}
</mat-option>
</mat-select>
</mat-form-field>
</div>
<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="20 - 30">
</tb-font-settings>
<tb-color-input asBoxInput
colorClearButton
formControlName="legendLabelColor">
</tb-color-input>
</div>
</div>
</ng-template>
</mat-expansion-panel>
</div>
<div class="tb-form-panel tb-slide-toggle">
<mat-expansion-panel class="tb-settings" [expanded]="barChartWidgetSettingsForm.get('showTooltip').value" [disabled]="!barChartWidgetSettingsForm.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.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 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-row space-between">
<div>{{ 'widgets.background.background' | translate }}</div>
<tb-background-settings formControlName="background">
</tb-background-settings>
</div>
</div>
</ng-container>

170
ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/bar-chart-with-labels-widget-settings.component.ts

@ -0,0 +1,170 @@
///
/// Copyright © 2016-2023 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 {
legendPositions,
legendPositionTranslationMap,
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 { formatValue } from '@core/utils';
import { DateFormatProcessor, DateFormatSettings } from '@shared/models/widget-settings.models';
import {
barChartWithLabelsDefaultSettings
} from '@home/components/widget/lib/chart/bar-chart-with-labels-widget.models';
@Component({
selector: 'tb-bar-chart-with-labels-widget-settings',
templateUrl: './bar-chart-with-labels-widget-settings.component.html',
styleUrls: []
})
export class BarChartWithLabelsWidgetSettingsComponent extends WidgetSettingsComponent {
legendPositions = legendPositions;
legendPositionTranslationMap = legendPositionTranslationMap;
barChartWidgetSettingsForm: UntypedFormGroup;
tooltipValuePreviewFn = this._tooltipValuePreviewFn.bind(this);
tooltipDatePreviewFn = this._tooltipDatePreviewFn.bind(this);
constructor(protected store: Store<AppState>,
private $injector: Injector,
private fb: UntypedFormBuilder) {
super(store);
}
protected settingsForm(): UntypedFormGroup {
return this.barChartWidgetSettingsForm;
}
protected defaultSettings(): WidgetSettings {
return {...barChartWithLabelsDefaultSettings};
}
protected onSettingsSet(settings: WidgetSettings) {
this.barChartWidgetSettingsForm = this.fb.group({
showBarLabel: [settings.showBarLabel, []],
barLabelFont: [settings.barLabelFont, []],
barLabelColor: [settings.barLabelColor, []],
showBarValue: [settings.showBarValue, []],
barValueFont: [settings.barValueFont, []],
barValueColor: [settings.barValueColor, []],
showLegend: [settings.showLegend, []],
legendPosition: [settings.legendPosition, []],
legendLabelFont: [settings.legendLabelFont, []],
legendLabelColor: [settings.legendLabelColor, []],
showTooltip: [settings.showTooltip, []],
tooltipValueFont: [settings.tooltipValueFont, []],
tooltipValueColor: [settings.tooltipValueColor, []],
tooltipShowDate: [settings.tooltipShowDate, []],
tooltipDateFormat: [settings.tooltipDateFormat, []],
tooltipDateFont: [settings.tooltipDateFont, []],
tooltipDateColor: [settings.tooltipDateColor, []],
tooltipBackgroundColor: [settings.tooltipBackgroundColor, []],
tooltipBackgroundBlur: [settings.tooltipBackgroundBlur, []],
background: [settings.background, []]
});
}
protected validatorTriggers(): string[] {
return ['showBarLabel', 'showBarValue', 'showLegend', 'showTooltip', 'tooltipShowDate'];
}
protected updateValidators(emitEvent: boolean) {
const showBarLabel: boolean = this.barChartWidgetSettingsForm.get('showBarLabel').value;
const showBarValue: boolean = this.barChartWidgetSettingsForm.get('showBarValue').value;
const showLegend: boolean = this.barChartWidgetSettingsForm.get('showLegend').value;
const showTooltip: boolean = this.barChartWidgetSettingsForm.get('showTooltip').value;
const tooltipShowDate: boolean = this.barChartWidgetSettingsForm.get('tooltipShowDate').value;
if (showBarLabel) {
this.barChartWidgetSettingsForm.get('barLabelFont').enable();
this.barChartWidgetSettingsForm.get('barLabelColor').enable();
} else {
this.barChartWidgetSettingsForm.get('barLabelFont').disable();
this.barChartWidgetSettingsForm.get('barLabelColor').disable();
}
if (showBarValue) {
this.barChartWidgetSettingsForm.get('barValueFont').enable();
this.barChartWidgetSettingsForm.get('barValueColor').enable();
} else {
this.barChartWidgetSettingsForm.get('barValueFont').disable();
this.barChartWidgetSettingsForm.get('barValueColor').disable();
}
if (showLegend) {
this.barChartWidgetSettingsForm.get('legendPosition').enable();
this.barChartWidgetSettingsForm.get('legendLabelFont').enable();
this.barChartWidgetSettingsForm.get('legendLabelColor').enable();
} else {
this.barChartWidgetSettingsForm.get('legendPosition').disable();
this.barChartWidgetSettingsForm.get('legendLabelFont').disable();
this.barChartWidgetSettingsForm.get('legendLabelColor').disable();
}
if (showTooltip) {
this.barChartWidgetSettingsForm.get('tooltipValueFont').enable();
this.barChartWidgetSettingsForm.get('tooltipValueColor').enable();
this.barChartWidgetSettingsForm.get('tooltipShowDate').enable({emitEvent: false});
this.barChartWidgetSettingsForm.get('tooltipBackgroundColor').enable();
this.barChartWidgetSettingsForm.get('tooltipBackgroundBlur').enable();
if (tooltipShowDate) {
this.barChartWidgetSettingsForm.get('tooltipDateFormat').enable();
this.barChartWidgetSettingsForm.get('tooltipDateFont').enable();
this.barChartWidgetSettingsForm.get('tooltipDateColor').enable();
} else {
this.barChartWidgetSettingsForm.get('tooltipDateFormat').disable();
this.barChartWidgetSettingsForm.get('tooltipDateFont').disable();
this.barChartWidgetSettingsForm.get('tooltipDateColor').disable();
}
} else {
this.barChartWidgetSettingsForm.get('tooltipValueFont').disable();
this.barChartWidgetSettingsForm.get('tooltipValueColor').disable();
this.barChartWidgetSettingsForm.get('tooltipShowDate').disable({emitEvent: false});
this.barChartWidgetSettingsForm.get('tooltipDateFormat').disable();
this.barChartWidgetSettingsForm.get('tooltipDateFont').disable();
this.barChartWidgetSettingsForm.get('tooltipDateColor').disable();
this.barChartWidgetSettingsForm.get('tooltipBackgroundColor').disable();
this.barChartWidgetSettingsForm.get('tooltipBackgroundBlur').disable();
}
}
private _tooltipValuePreviewFn(): string {
const units: string = this.widgetConfig.config.units;
const decimals: number = this.widgetConfig.config.decimals;
return formatValue(22, decimals, units, false);
}
private _tooltipDatePreviewFn(): string {
const dateFormat: DateFormatSettings = this.barChartWidgetSettingsForm.get('tooltipDateFormat').value;
const processor = DateFormatProcessor.fromSettings(this.$injector, dateFormat);
processor.update(Date.now());
return processor.formatted;
}
}

16
ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/doughnut-widget-settings.component.html

@ -70,14 +70,14 @@
<div>{{ 'legend.position' | translate }}</div> <div>{{ 'legend.position' | translate }}</div>
<mat-form-field class="medium-width" appearance="outline" subscriptSizing="dynamic"> <mat-form-field class="medium-width" appearance="outline" subscriptSizing="dynamic">
<mat-select formControlName="legendPosition"> <mat-select formControlName="legendPosition">
<mat-option *ngFor="let pos of doughnutLegendPositions" [value]="pos"> <mat-option *ngFor="let pos of legendPositions" [value]="pos">
{{ doughnutLegendPositionTranslationMap.get(pos) | translate }} {{ legendPositionTranslationMap.get(pos) | translate }}
</mat-option> </mat-option>
</mat-select> </mat-select>
</mat-form-field> </mat-form-field>
</div> </div>
<div class="tb-form-row space-between"> <div class="tb-form-row space-between">
<div>{{ 'widgets.doughnut.legend-label' | translate }}</div> <div>{{ 'legend.label' | translate }}</div>
<div fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px"> <div fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px">
<tb-font-settings formControlName="legendLabelFont" <tb-font-settings formControlName="legendLabelFont"
previewText="Wind power"> previewText="Wind power">
@ -89,7 +89,7 @@
</div> </div>
</div> </div>
<div class="tb-form-row space-between"> <div class="tb-form-row space-between">
<div>{{ 'widgets.doughnut.legend-value' | translate }}</div> <div>{{ 'legend.value' | translate }}</div>
<div fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px"> <div fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px">
<tb-font-settings formControlName="legendValueFont" <tb-font-settings formControlName="legendValueFont"
[previewText]="valuePreviewFn"> [previewText]="valuePreviewFn">
@ -109,13 +109,13 @@
<mat-panel-title> <mat-panel-title>
<mat-slide-toggle class="mat-slide" formControlName="showTooltip" (click)="$event.stopPropagation()" <mat-slide-toggle class="mat-slide" formControlName="showTooltip" (click)="$event.stopPropagation()"
fxLayoutAlign="center"> fxLayoutAlign="center">
{{ 'widgets.doughnut.tooltip' | translate }} {{ 'widget-config.tooltip' | translate }}
</mat-slide-toggle> </mat-slide-toggle>
</mat-panel-title> </mat-panel-title>
</mat-expansion-panel-header> </mat-expansion-panel-header>
<ng-template matExpansionPanelContent> <ng-template matExpansionPanelContent>
<div class="tb-form-row space-between column-xs"> <div class="tb-form-row space-between column-xs">
<div>{{ 'widgets.doughnut.tooltip-value' | translate }}</div> <div>{{ 'tooltip.value' | translate }}</div>
<div fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px"> <div fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px">
<mat-form-field class="medium-width" appearance="outline" subscriptSizing="dynamic"> <mat-form-field class="medium-width" appearance="outline" subscriptSizing="dynamic">
<mat-select formControlName="tooltipValueType"> <mat-select formControlName="tooltipValueType">
@ -138,14 +138,14 @@
</div> </div>
</div> </div>
<div class="tb-form-row space-between"> <div class="tb-form-row space-between">
<div>{{ 'widgets.doughnut.tooltip-background-color' | translate }}</div> <div>{{ 'tooltip.background-color' | translate }}</div>
<tb-color-input asBoxInput <tb-color-input asBoxInput
colorClearButton colorClearButton
formControlName="tooltipBackgroundColor"> formControlName="tooltipBackgroundColor">
</tb-color-input> </tb-color-input>
</div> </div>
<div class="tb-form-row space-between"> <div class="tb-form-row space-between">
<div>{{ 'widgets.doughnut.tooltip-background-blur' | translate }}</div> <div>{{ 'tooltip.background-blur' | translate }}</div>
<mat-form-field appearance="outline" class="number" subscriptSizing="dynamic"> <mat-form-field appearance="outline" class="number" subscriptSizing="dynamic">
<input matInput formControlName="tooltipBackgroundBlur" type="number" min="0" step="1" placeholder="{{ 'widget-config.set' | translate }}"> <input matInput formControlName="tooltipBackgroundBlur" type="number" min="0" step="1" placeholder="{{ 'widget-config.set' | translate }}">
<div matSuffix>px</div> <div matSuffix>px</div>

13
ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/doughnut-widget-settings.component.ts

@ -15,7 +15,12 @@
/// ///
import { Component } from '@angular/core'; import { Component } from '@angular/core';
import { WidgetSettings, WidgetSettingsComponent } from '@shared/models/widget.models'; import {
legendPositions,
legendPositionTranslationMap,
WidgetSettings,
WidgetSettingsComponent
} from '@shared/models/widget.models';
import { UntypedFormBuilder, UntypedFormGroup } from '@angular/forms'; import { UntypedFormBuilder, UntypedFormGroup } from '@angular/forms';
import { Store } from '@ngrx/store'; import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state'; import { AppState } from '@core/core.state';
@ -26,8 +31,6 @@ import {
doughnutLayoutImages, doughnutLayoutImages,
doughnutLayouts, doughnutLayouts,
doughnutLayoutTranslations, doughnutLayoutTranslations,
doughnutLegendPositions,
doughnutLegendPositionTranslations,
DoughnutTooltipValueType, DoughnutTooltipValueType,
doughnutTooltipValueTypes, doughnutTooltipValueTypes,
doughnutTooltipValueTypeTranslations, doughnutTooltipValueTypeTranslations,
@ -55,9 +58,9 @@ export class DoughnutWidgetSettingsComponent extends WidgetSettingsComponent {
doughnutLayoutImageMap: Map<DoughnutLayout, string>; doughnutLayoutImageMap: Map<DoughnutLayout, string>;
doughnutLegendPositions = doughnutLegendPositions; legendPositions = legendPositions;
doughnutLegendPositionTranslationMap = doughnutLegendPositionTranslations; legendPositionTranslationMap = legendPositionTranslationMap;
doughnutTooltipValueTypes = doughnutTooltipValueTypes; doughnutTooltipValueTypes = doughnutTooltipValueTypes;

12
ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/range-chart-widget-settings.component.html

@ -62,7 +62,7 @@
</mat-form-field> </mat-form-field>
</div> </div>
<div class="tb-form-row space-between"> <div class="tb-form-row space-between">
<div>{{ 'widgets.range-chart.legend-label' | translate }}</div> <div>{{ 'legend.label' | translate }}</div>
<div fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px"> <div fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px">
<tb-font-settings formControlName="legendLabelFont" <tb-font-settings formControlName="legendLabelFont"
previewText="20 - 30"> previewText="20 - 30">
@ -82,13 +82,13 @@
<mat-panel-title> <mat-panel-title>
<mat-slide-toggle class="mat-slide" formControlName="showTooltip" (click)="$event.stopPropagation()" <mat-slide-toggle class="mat-slide" formControlName="showTooltip" (click)="$event.stopPropagation()"
fxLayoutAlign="center"> fxLayoutAlign="center">
{{ 'widgets.range-chart.tooltip' | translate }} {{ 'widget-config.tooltip' | translate }}
</mat-slide-toggle> </mat-slide-toggle>
</mat-panel-title> </mat-panel-title>
</mat-expansion-panel-header> </mat-expansion-panel-header>
<ng-template matExpansionPanelContent> <ng-template matExpansionPanelContent>
<div class="tb-form-row space-between"> <div class="tb-form-row space-between">
<div>{{ 'widgets.range-chart.tooltip-value' | translate }}</div> <div>{{ 'tooltip.value' | translate }}</div>
<div fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px"> <div fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px">
<tb-font-settings formControlName="tooltipValueFont" <tb-font-settings formControlName="tooltipValueFont"
[previewText]="tooltipValuePreviewFn"> [previewText]="tooltipValuePreviewFn">
@ -101,7 +101,7 @@
</div> </div>
<div class="tb-form-row column-xs"> <div class="tb-form-row column-xs">
<mat-slide-toggle class="mat-slide fixed-title-width" formControlName="tooltipShowDate"> <mat-slide-toggle class="mat-slide fixed-title-width" formControlName="tooltipShowDate">
{{ 'widgets.range-chart.tooltip-date' | translate }} {{ 'tooltip.date' | translate }}
</mat-slide-toggle> </mat-slide-toggle>
<div fxFlex.gt-xs fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px"> <div fxFlex.gt-xs fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px">
<tb-date-format-select fxFlex excludeLastUpdateAgo formControlName="tooltipDateFormat"></tb-date-format-select> <tb-date-format-select fxFlex excludeLastUpdateAgo formControlName="tooltipDateFormat"></tb-date-format-select>
@ -115,14 +115,14 @@
</div> </div>
</div> </div>
<div class="tb-form-row space-between"> <div class="tb-form-row space-between">
<div>{{ 'widgets.range-chart.tooltip-background-color' | translate }}</div> <div>{{ 'tooltip.background-color' | translate }}</div>
<tb-color-input asBoxInput <tb-color-input asBoxInput
colorClearButton colorClearButton
formControlName="tooltipBackgroundColor"> formControlName="tooltipBackgroundColor">
</tb-color-input> </tb-color-input>
</div> </div>
<div class="tb-form-row space-between"> <div class="tb-form-row space-between">
<div>{{ 'widgets.range-chart.tooltip-background-blur' | translate }}</div> <div>{{ 'tooltip.background-blur' | translate }}</div>
<mat-form-field appearance="outline" class="number" subscriptSizing="dynamic"> <mat-form-field appearance="outline" class="number" subscriptSizing="dynamic">
<input matInput formControlName="tooltipBackgroundBlur" type="number" min="0" step="1" placeholder="{{ 'widget-config.set' | translate }}"> <input matInput formControlName="tooltipBackgroundBlur" type="number" min="0" step="1" placeholder="{{ 'widget-config.set' | translate }}">
<div matSuffix>px</div> <div matSuffix>px</div>

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

@ -27,7 +27,7 @@ import {
import { PageComponent } from '@shared/components/page.component'; import { PageComponent } from '@shared/components/page.component';
import { import {
commonFonts, commonFonts,
ComponentStyle, ComponentStyle, cssUnit,
Font, Font,
fontStyles, fontStyles,
fontStyleTranslations, fontStyleTranslations,
@ -73,6 +73,9 @@ export class FontSettingsPanelComponent extends PageComponent implements OnInit
@coerceBoolean() @coerceBoolean()
disabledLineHeight = false; disabledLineHeight = false;
@Input()
forceSizeUnit: cssUnit;
@Input() @Input()
popover: TbPopoverComponent<FontSettingsPanelComponent>; popover: TbPopoverComponent<FontSettingsPanelComponent>;
@ -106,7 +109,8 @@ export class FontSettingsPanelComponent extends PageComponent implements OnInit
this.fontFormGroup = this.fb.group( this.fontFormGroup = this.fb.group(
{ {
size: [{value: this.font?.size, disabled: this.autoScale}, [Validators.min(0)]], size: [{value: this.font?.size, disabled: this.autoScale}, [Validators.min(0)]],
sizeUnit: [{ value: (this.font?.sizeUnit || 'px'), disabled: this.autoScale}, []], sizeUnit: [{ value: (!!this.forceSizeUnit ?
this.forceSizeUnit : (this.font?.sizeUnit || 'px')), disabled: this.autoScale || !!this.forceSizeUnit}, []],
family: [this.font?.family, []], family: [this.font?.family, []],
weight: [this.font?.weight, []], weight: [this.font?.weight, []],
style: [this.font?.style, []], style: [this.font?.style, []],
@ -150,11 +154,14 @@ export class FontSettingsPanelComponent extends PageComponent implements OnInit
} }
clearFont() { clearFont() {
this.fontFormGroup.reset({sizeUnit: 'px'}); this.fontFormGroup.reset({sizeUnit: this.forceSizeUnit || 'px'});
this.fontFormGroup.markAsDirty(); this.fontFormGroup.markAsDirty();
} }
private updatePreviewStyle(font: Font) { private updatePreviewStyle(font: Font) {
if (!!this.forceSizeUnit) {
font = {...font, ...{sizeUnit: this.forceSizeUnit}};
}
this.previewStyle = {...(this.initialPreviewStyle || {}), ...textStyle(font)}; this.previewStyle = {...(this.initialPreviewStyle || {}), ...textStyle(font)};
} }

8
ui-ngx/src/app/modules/home/components/widget/lib/settings/common/font-settings.component.ts

@ -16,7 +16,7 @@
import { Component, forwardRef, Input, OnInit, Renderer2, ViewContainerRef } from '@angular/core'; import { Component, forwardRef, Input, OnInit, Renderer2, ViewContainerRef } from '@angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
import { ComponentStyle, Font } from '@shared/models/widget-settings.models'; import { ComponentStyle, cssUnit, Font } from '@shared/models/widget-settings.models';
import { MatButton } from '@angular/material/button'; import { MatButton } from '@angular/material/button';
import { TbPopoverService } from '@shared/components/popover.service'; import { TbPopoverService } from '@shared/components/popover.service';
import { FontSettingsPanelComponent } from '@home/components/widget/lib/settings/common/font-settings-panel.component'; import { FontSettingsPanelComponent } from '@home/components/widget/lib/settings/common/font-settings-panel.component';
@ -58,6 +58,9 @@ export class FontSettingsComponent implements OnInit, ControlValueAccessor {
@coerceBoolean() @coerceBoolean()
disabledLineHeight = false; disabledLineHeight = false;
@Input()
forceSizeUnit: cssUnit;
private modelValue: Font; private modelValue: Font;
private propagateChange = null; private propagateChange = null;
@ -97,7 +100,8 @@ export class FontSettingsComponent implements OnInit, ControlValueAccessor {
initialPreviewStyle: this.initialPreviewStyle, initialPreviewStyle: this.initialPreviewStyle,
clearButton: this.clearButton, clearButton: this.clearButton,
autoScale: this.autoScale, autoScale: this.autoScale,
disabledLineHeight: this.disabledLineHeight disabledLineHeight: this.disabledLineHeight,
forceSizeUnit: this.forceSizeUnit
}; };
if (isDefinedAndNotNull(this.previewText)) { if (isDefinedAndNotNull(this.previewText)) {
const previewText = typeof this.previewText === 'string' ? this.previewText : this.previewText(); const previewText = typeof this.previewText === 'string' ? this.previewText : this.previewText();

10
ui-ngx/src/app/modules/home/components/widget/lib/settings/indicator/signal-strength-widget-settings.component.html

@ -70,14 +70,14 @@
<mat-panel-title> <mat-panel-title>
<mat-slide-toggle class="mat-slide" formControlName="showTooltip" (click)="$event.stopPropagation()" <mat-slide-toggle class="mat-slide" formControlName="showTooltip" (click)="$event.stopPropagation()"
fxLayoutAlign="center"> fxLayoutAlign="center">
{{ 'widgets.signal-strength.tooltip' | translate }} {{ 'widget-config.tooltip' | translate }}
</mat-slide-toggle> </mat-slide-toggle>
</mat-panel-title> </mat-panel-title>
</mat-expansion-panel-header> </mat-expansion-panel-header>
<ng-template matExpansionPanelContent> <ng-template matExpansionPanelContent>
<div class="tb-form-row space-between"> <div class="tb-form-row space-between">
<mat-slide-toggle class="mat-slide" formControlName="showTooltipValue"> <mat-slide-toggle class="mat-slide" formControlName="showTooltipValue">
{{ 'widgets.signal-strength.value' | translate }} {{ 'tooltip.value' | translate }}
</mat-slide-toggle> </mat-slide-toggle>
<div fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px"> <div fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px">
<tb-font-settings formControlName="tooltipValueFont" <tb-font-settings formControlName="tooltipValueFont"
@ -91,7 +91,7 @@
</div> </div>
<div class="tb-form-row column-xs"> <div class="tb-form-row column-xs">
<mat-slide-toggle class="mat-slide fixed-title-width" formControlName="showTooltipDate"> <mat-slide-toggle class="mat-slide fixed-title-width" formControlName="showTooltipDate">
{{ 'widgets.signal-strength.date' | translate }} {{ 'tooltip.date' | translate }}
</mat-slide-toggle> </mat-slide-toggle>
<div fxFlex.gt-xs fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px"> <div fxFlex.gt-xs fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px">
<tb-date-format-select fxFlex formControlName="tooltipDateFormat"></tb-date-format-select> <tb-date-format-select fxFlex formControlName="tooltipDateFormat"></tb-date-format-select>
@ -105,13 +105,13 @@
</div> </div>
</div> </div>
<div class="tb-form-row space-between"> <div class="tb-form-row space-between">
<div>{{ 'widgets.signal-strength.background-color' | translate }}</div> <div>{{ 'tooltip.background-color' | translate }}</div>
<tb-color-input asBoxInput <tb-color-input asBoxInput
formControlName="tooltipBackgroundColor"> formControlName="tooltipBackgroundColor">
</tb-color-input> </tb-color-input>
</div> </div>
<div class="tb-form-row space-between"> <div class="tb-form-row space-between">
<div translate>widgets.signal-strength.background-blur</div> <div translate>tooltip.background-blur</div>
<mat-form-field appearance="outline" class="number" subscriptSizing="dynamic"> <mat-form-field appearance="outline" class="number" subscriptSizing="dynamic">
<input matInput formControlName="tooltipBackgroundBlur" type="number" min="0" step="1" <input matInput formControlName="tooltipBackgroundBlur" type="number" min="0" step="1"
placeholder="{{ 'widget-config.set' | translate }}"> placeholder="{{ 'widget-config.set' | translate }}">

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

@ -308,6 +308,9 @@ import {
import { import {
RangeChartWidgetSettingsComponent RangeChartWidgetSettingsComponent
} from '@home/components/widget/lib/settings/chart/range-chart-widget-settings.component'; } from '@home/components/widget/lib/settings/chart/range-chart-widget-settings.component';
import {
BarChartWithLabelsWidgetSettingsComponent
} from '@home/components/widget/lib/settings/chart/bar-chart-with-labels-widget-settings.component';
@NgModule({ @NgModule({
declarations: [ declarations: [
@ -420,7 +423,8 @@ import {
ProgressBarWidgetSettingsComponent, ProgressBarWidgetSettingsComponent,
LiquidLevelCardWidgetSettingsComponent, LiquidLevelCardWidgetSettingsComponent,
DoughnutWidgetSettingsComponent, DoughnutWidgetSettingsComponent,
RangeChartWidgetSettingsComponent RangeChartWidgetSettingsComponent,
BarChartWithLabelsWidgetSettingsComponent
], ],
imports: [ imports: [
CommonModule, CommonModule,
@ -538,7 +542,8 @@ import {
ProgressBarWidgetSettingsComponent, ProgressBarWidgetSettingsComponent,
LiquidLevelCardWidgetSettingsComponent, LiquidLevelCardWidgetSettingsComponent,
DoughnutWidgetSettingsComponent, DoughnutWidgetSettingsComponent,
RangeChartWidgetSettingsComponent RangeChartWidgetSettingsComponent,
BarChartWithLabelsWidgetSettingsComponent
] ]
}) })
export class WidgetSettingsModule { export class WidgetSettingsModule {
@ -622,5 +627,6 @@ export const widgetSettingsComponentsMap: {[key: string]: Type<IWidgetSettingsCo
'tb-progress-bar-widget-settings': ProgressBarWidgetSettingsComponent, 'tb-progress-bar-widget-settings': ProgressBarWidgetSettingsComponent,
'tb-liquid-level-card-widget-settings': LiquidLevelCardWidgetSettingsComponent, 'tb-liquid-level-card-widget-settings': LiquidLevelCardWidgetSettingsComponent,
'tb-doughnut-widget-settings': DoughnutWidgetSettingsComponent, 'tb-doughnut-widget-settings': DoughnutWidgetSettingsComponent,
'tb-range-chart-widget-settings': RangeChartWidgetSettingsComponent 'tb-range-chart-widget-settings': RangeChartWidgetSettingsComponent,
'tb-bar-chart-with-labels-widget-settings': BarChartWithLabelsWidgetSettingsComponent
}; };

12
ui-ngx/src/app/shared/components/time/timeinterval.component.ts

@ -54,7 +54,7 @@ export class TimeintervalComponent implements OnInit, ControlValueAccessor {
if (typeof maxValueData !== 'undefined' && maxValueData !== this.maxValue) { if (typeof maxValueData !== 'undefined' && maxValueData !== this.maxValue) {
this.maxValue = maxValueData; this.maxValue = maxValueData;
this.minValue = Math.min(this.minValue, this.maxValue); this.minValue = Math.min(this.minValue, this.maxValue);
this.updateView(); this.updateView(true);
} }
} }
@ -138,7 +138,7 @@ export class TimeintervalComponent implements OnInit, ControlValueAccessor {
this.secs = intervalSeconds % 60; this.secs = intervalSeconds % 60;
} }
boundInterval() { boundInterval(updateToPreferred = false) {
const min = this.timeService.boundMinInterval(this.minValue); const min = this.timeService.boundMinInterval(this.minValue);
const max = this.timeService.boundMaxInterval(this.maxValue); const max = this.timeService.boundMaxInterval(this.maxValue);
this.intervals = this.timeService.getIntervals(this.minValue, this.maxValue); this.intervals = this.timeService.getIntervals(this.minValue, this.maxValue);
@ -146,8 +146,8 @@ export class TimeintervalComponent implements OnInit, ControlValueAccessor {
let newIntervalMs = this.modelValue; let newIntervalMs = this.modelValue;
if (newIntervalMs < min) { if (newIntervalMs < min) {
newIntervalMs = min; newIntervalMs = min;
} else if (newIntervalMs > max) { } else if (newIntervalMs >= max && updateToPreferred) {
newIntervalMs = max; newIntervalMs = this.timeService.boundMaxInterval(max / 7);
} }
if (!this.advanced) { if (!this.advanced) {
newIntervalMs = this.timeService.boundToPredefinedInterval(min, max, newIntervalMs); newIntervalMs = this.timeService.boundToPredefinedInterval(min, max, newIntervalMs);
@ -159,7 +159,7 @@ export class TimeintervalComponent implements OnInit, ControlValueAccessor {
} }
} }
updateView() { updateView(updateToPreferred = false) {
if (!this.rendered) { if (!this.rendered) {
return; return;
} }
@ -178,7 +178,7 @@ export class TimeintervalComponent implements OnInit, ControlValueAccessor {
} }
this.modelValue = value; this.modelValue = value;
this.propagateChange(this.modelValue); this.propagateChange(this.modelValue);
this.boundInterval(); this.boundInterval(updateToPreferred);
} }
calculateIntervalMs(): number { calculateIntervalMs(): number {

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

@ -3159,7 +3159,9 @@
"weeks": "(week ago)", "weeks": "(week ago)",
"months": "(month ago)", "months": "(month ago)",
"years": "(year ago)" "years": "(year ago)"
} },
"label": "Label",
"value": "Value"
}, },
"login": { "login": {
"login": "Login", "login": "Login",
@ -4276,6 +4278,12 @@
"displayTypePrefix": "Display Realtime/History prefix", "displayTypePrefix": "Display Realtime/History prefix",
"preview": "Preview" "preview": "Preview"
}, },
"tooltip": {
"value": "Value",
"date": "Date",
"background-color": "Background color",
"background-blur": "Background blur"
},
"unit": { "unit": {
"millimeter": "Millimeter", "millimeter": "Millimeter",
"centimeter": "Centimeter", "centimeter": "Centimeter",
@ -5152,6 +5160,12 @@
"blur": "Blur", "blur": "Blur",
"preview": "Preview" "preview": "Preview"
}, },
"bar-chart": {
"bar-appearance": "Bar appearance",
"label-on-bar": "Label on bar",
"value-on-bar": "Value on bar",
"bar-chart-card-style": "Bar chart card style"
},
"battery-level": { "battery-level": {
"layout": "Layout", "layout": "Layout",
"layout-vertical-solid": "Vertical. Solid", "layout-vertical-solid": "Vertical. Solid",
@ -5177,9 +5191,6 @@
"date": "Date", "date": "Date",
"active-bars-color": "Active signal bars color", "active-bars-color": "Active signal bars color",
"inactive-bars-color": "Inactive signal bars color", "inactive-bars-color": "Inactive signal bars color",
"tooltip": "Tooltip",
"background-color": "Background color",
"background-blur": "Background blur",
"signal-strength-card-style": "Signal strength card style" "signal-strength-card-style": "Signal strength card style"
}, },
"chart": { "chart": {
@ -5422,18 +5433,8 @@
"clockwise-layout": "Clockwise layout", "clockwise-layout": "Clockwise layout",
"sort-series": "Sort series by label", "sort-series": "Sort series by label",
"central-total-value": "Central total value", "central-total-value": "Central total value",
"legend-position-top": "Top",
"legend-position-bottom": "Bottom",
"legend-position-left": "Left",
"legend-position-right": "Right",
"legend-label": "Label",
"legend-value": "Value",
"tooltip": "Tooltip",
"tooltip-value": "Value",
"tooltip-value-type-absolute": "Absolute", "tooltip-value-type-absolute": "Absolute",
"tooltip-value-type-percentage": "Percentage", "tooltip-value-type-percentage": "Percentage",
"tooltip-background-color": "Background color",
"tooltip-background-blur": "Background blur",
"doughnut-card-style": "Doughnut card style" "doughnut-card-style": "Doughnut card style"
}, },
"entities-hierarchy": { "entities-hierarchy": {
@ -5915,12 +5916,6 @@
"range-colors": "Range colors", "range-colors": "Range colors",
"out-of-range-color": "Out of range color", "out-of-range-color": "Out of range color",
"fill-area": "Fill area", "fill-area": "Fill area",
"legend-label": "Label",
"tooltip": "Tooltip",
"tooltip-value": "Value",
"tooltip-date": "Date",
"tooltip-background-color": "Background color",
"tooltip-background-blur": "Background blur",
"range-chart-card-style": "Range chart card style" "range-chart-card-style": "Range chart card style"
}, },
"rpc": { "rpc": {

29
ui-ngx/src/assets/locale/locale.constant-zh_CN.json

@ -3087,7 +3087,9 @@
"weeks": "(一周前)", "weeks": "(一周前)",
"months": "(一个月前)", "months": "(一个月前)",
"years": "(一年前)" "years": "(一年前)"
} },
"label": "标签",
"value": "值"
}, },
"login": { "login": {
"login": "登录", "login": "登录",
@ -4197,6 +4199,12 @@
"displayTypePrefix": "显示实时/历史前缀", "displayTypePrefix": "显示实时/历史前缀",
"preview": "预览" "preview": "预览"
}, },
"tooltip": {
"value": "值",
"date": "日期",
"background-color": "背景颜色",
"background-blur": "背景模糊"
},
"unit": { "unit": {
"millimeter": "mm", "millimeter": "mm",
"centimeter": "cm", "centimeter": "cm",
@ -5082,9 +5090,6 @@
"date": "日期", "date": "日期",
"active-bars-color": "活动信号条颜色", "active-bars-color": "活动信号条颜色",
"inactive-bars-color": "非活动信号条颜色", "inactive-bars-color": "非活动信号条颜色",
"tooltip": "文字提示",
"background-color": "背景颜色",
"background-blur": "背景模糊",
"signal-strength-card-style": "信号强度卡片样式" "signal-strength-card-style": "信号强度卡片样式"
}, },
"chart": { "chart": {
@ -5327,18 +5332,8 @@
"clockwise-layout": "顺时针布局", "clockwise-layout": "顺时针布局",
"sort-series": "按标签对系列排序", "sort-series": "按标签对系列排序",
"central-total-value": "中央总计值", "central-total-value": "中央总计值",
"legend-position-top": "顶部",
"legend-position-bottom": "底部",
"legend-position-left": "左侧",
"legend-position-right": "右侧",
"legend-label": "标签",
"legend-value": "值",
"tooltip": "文字提示",
"tooltip-value": "值",
"tooltip-value-type-absolute": "绝对值", "tooltip-value-type-absolute": "绝对值",
"tooltip-value-type-percentage": "百分比", "tooltip-value-type-percentage": "百分比",
"tooltip-background-color": "背景颜色",
"tooltip-background-blur": "背景模糊",
"doughnut-card-style": "圆环样式" "doughnut-card-style": "圆环样式"
}, },
"entities-hierarchy": { "entities-hierarchy": {
@ -5820,12 +5815,6 @@
"range-colors": "范围颜色", "range-colors": "范围颜色",
"out-of-range-color": "超出范围颜色", "out-of-range-color": "超出范围颜色",
"fill-area": "填充区域", "fill-area": "填充区域",
"legend-label": "标签",
"tooltip": "文字提示",
"tooltip-value": "值",
"tooltip-date": "日期",
"tooltip-background-color": "背景颜色",
"tooltip-background-blur": "背景模糊",
"range-chart-card-style": "范围图表卡片样式" "range-chart-card-style": "范围图表卡片样式"
}, },
"rpc": { "rpc": {

Loading…
Cancel
Save