committed by
GitHub
33 changed files with 2013 additions and 274 deletions
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -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> |
|||
@ -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; |
|||
} |
|||
} |
|||
@ -0,0 +1,36 @@ |
|||
<!-- |
|||
|
|||
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. |
|||
|
|||
--> |
|||
<div class="tb-bar-chart-panel" [style]="backgroundStyle$ | async"> |
|||
<div class="tb-bar-chart-overlay" [style]="overlayStyle"></div> |
|||
<ng-container *ngTemplateOutlet="widgetTitlePanel"></ng-container> |
|||
<div class="tb-bar-chart-content" [class]="legendClass"> |
|||
<div #chartShape class="tb-bar-chart-shape"> |
|||
</div> |
|||
<div *ngIf="showLegend" class="tb-bar-chart-legend"> |
|||
<div class="tb-bar-chart-legend-item" *ngFor="let legendItem of legendItems" |
|||
(mouseenter)="onLegendItemEnter(legendItem)" |
|||
(mouseleave)="onLegendItemLeave(legendItem)" |
|||
(click)="toggleLegendItem(legendItem)"> |
|||
<div class="tb-bar-chart-legend-item-label"> |
|||
<div class="tb-bar-chart-legend-item-label-circle" [style]="{background: legendItem.enabled ? legendItem.color : null}"></div> |
|||
<div [style]="legendItem.enabled ? legendLabelStyle : disabledLegendLabelStyle">{{ legendItem.label }}</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
@ -0,0 +1,105 @@ |
|||
/** |
|||
* 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. |
|||
*/ |
|||
|
|||
.tb-bar-chart-panel { |
|||
width: 100%; |
|||
height: 100%; |
|||
position: relative; |
|||
display: flex; |
|||
flex-direction: column; |
|||
gap: 16px; |
|||
padding: 20px 24px 24px 24px; |
|||
> div:not(.tb-bar-chart-overlay) { |
|||
z-index: 1; |
|||
} |
|||
.tb-bar-chart-overlay { |
|||
position: absolute; |
|||
top: 12px; |
|||
left: 12px; |
|||
bottom: 12px; |
|||
right: 12px; |
|||
} |
|||
div.tb-widget-title { |
|||
padding: 0; |
|||
} |
|||
.tb-bar-chart-content { |
|||
flex: 1; |
|||
min-width: 0; |
|||
min-height: 0; |
|||
display: flex; |
|||
flex-direction: column; |
|||
gap: 16px; |
|||
&.legend-top { |
|||
flex-direction: column-reverse; |
|||
} |
|||
&.legend-right { |
|||
flex-direction: row; |
|||
} |
|||
&.legend-left { |
|||
flex-direction: row-reverse; |
|||
} |
|||
.tb-bar-chart-shape { |
|||
flex: 1; |
|||
min-width: 0; |
|||
min-height: 0; |
|||
display: flex; |
|||
align-items: center; |
|||
justify-content: center; |
|||
} |
|||
.tb-bar-chart-legend { |
|||
display: flex; |
|||
justify-content: center; |
|||
align-items: center; |
|||
align-self: stretch; |
|||
flex-wrap: wrap; |
|||
column-gap: 24px; |
|||
row-gap: 8px; |
|||
.tb-bar-chart-legend-item { |
|||
display: flex; |
|||
flex-direction: column; |
|||
justify-content: center; |
|||
align-items: flex-start; |
|||
user-select: none; |
|||
cursor: pointer; |
|||
.tb-bar-chart-legend-item-label { |
|||
display: flex; |
|||
align-items: center; |
|||
gap: 4px; |
|||
color: #ccc; |
|||
.tb-bar-chart-legend-item-label-circle { |
|||
width: 8px; |
|||
height: 8px; |
|||
border-radius: 50%; |
|||
background-color: #ccc; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
&.legend-right, &.legend-left { |
|||
gap: 24px; |
|||
.tb-bar-chart-legend { |
|||
flex-direction: column-reverse; |
|||
justify-content: flex-end; |
|||
align-items: stretch; |
|||
.tb-bar-chart-legend-item { |
|||
flex-direction: row; |
|||
justify-content: space-between; |
|||
align-items: center; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,483 @@ |
|||
///
|
|||
/// 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 { |
|||
AfterViewInit, |
|||
ChangeDetectorRef, |
|||
Component, |
|||
ElementRef, |
|||
Input, |
|||
OnDestroy, |
|||
OnInit, |
|||
Renderer2, |
|||
TemplateRef, |
|||
ViewChild, |
|||
ViewEncapsulation |
|||
} from '@angular/core'; |
|||
import { WidgetContext } from '@home/models/widget-component.models'; |
|||
import { |
|||
backgroundStyle, |
|||
ComponentStyle, |
|||
DateFormatProcessor, |
|||
overlayStyle, |
|||
textStyle |
|||
} from '@shared/models/widget-settings.models'; |
|||
import { ResizeObserver } from '@juggle/resize-observer'; |
|||
import { formatValue } from '@core/utils'; |
|||
import { DataKey } from '@shared/models/widget.models'; |
|||
import { Observable } from 'rxjs'; |
|||
import { ImagePipe } from '@shared/pipe/image.pipe'; |
|||
import { DomSanitizer } from '@angular/platform-browser'; |
|||
import { |
|||
barChartWithLabelsDefaultSettings, |
|||
BarChartWithLabelsWidgetSettings |
|||
} from '@home/components/widget/lib/chart/bar-chart-with-labels-widget.models'; |
|||
|
|||
import * as echarts from 'echarts/core'; |
|||
import { CustomSeriesOption } from 'echarts/charts'; |
|||
import { CallbackDataParams, CustomSeriesRenderItem, LabelLayoutOptionCallback } from 'echarts/types/dist/shared'; |
|||
|
|||
import { |
|||
ECharts, |
|||
echartsModule, |
|||
EChartsOption, |
|||
echartsTooltipFormatter, |
|||
NamedDataSet, |
|||
toNamedData |
|||
} from '@home/components/widget/lib/chart/echarts-widget.models'; |
|||
|
|||
interface BarChartDataItem { |
|||
id: string; |
|||
dataKey: DataKey; |
|||
data: NamedDataSet; |
|||
enabled: boolean; |
|||
} |
|||
|
|||
interface BarChartLegendItem { |
|||
id: string; |
|||
color: string; |
|||
label: string; |
|||
enabled: boolean; |
|||
} |
|||
|
|||
@Component({ |
|||
selector: 'tb-bar-chart-with-labels-widget', |
|||
templateUrl: './bar-chart-with-labels-widget.component.html', |
|||
styleUrls: ['./bar-chart-with-labels-widget.component.scss'], |
|||
encapsulation: ViewEncapsulation.None |
|||
}) |
|||
export class BarChartWithLabelsWidgetComponent implements OnInit, OnDestroy, AfterViewInit { |
|||
|
|||
@ViewChild('chartShape', {static: false}) |
|||
chartShape: ElementRef<HTMLElement>; |
|||
|
|||
settings: BarChartWithLabelsWidgetSettings; |
|||
|
|||
@Input() |
|||
ctx: WidgetContext; |
|||
|
|||
@Input() |
|||
widgetTitlePanel: TemplateRef<any>; |
|||
|
|||
showLegend: boolean; |
|||
legendClass: string; |
|||
|
|||
backgroundStyle$: Observable<ComponentStyle>; |
|||
overlayStyle: ComponentStyle = {}; |
|||
|
|||
legendItems: BarChartLegendItem[]; |
|||
legendLabelStyle: ComponentStyle; |
|||
disabledLegendLabelStyle: ComponentStyle; |
|||
|
|||
private shapeResize$: ResizeObserver; |
|||
|
|||
private decimals = 0; |
|||
private units = ''; |
|||
|
|||
private dataItems: BarChartDataItem[] = []; |
|||
|
|||
private drawChartPending = false; |
|||
private barChart: ECharts; |
|||
private barChartOptions: EChartsOption; |
|||
|
|||
private tooltipDateFormat: DateFormatProcessor; |
|||
|
|||
private barRenderItem: CustomSeriesRenderItem; |
|||
private barLabelLayoutCallback: LabelLayoutOptionCallback; |
|||
|
|||
constructor(private imagePipe: ImagePipe, |
|||
private sanitizer: DomSanitizer, |
|||
private renderer: Renderer2, |
|||
private cd: ChangeDetectorRef) { |
|||
} |
|||
|
|||
ngOnInit(): void { |
|||
this.ctx.$scope.barChartWidget = this; |
|||
this.settings = {...barChartWithLabelsDefaultSettings, ...this.ctx.settings}; |
|||
|
|||
this.decimals = this.ctx.decimals; |
|||
this.units = this.ctx.units; |
|||
|
|||
this.backgroundStyle$ = backgroundStyle(this.settings.background, this.imagePipe, this.sanitizer); |
|||
this.overlayStyle = overlayStyle(this.settings.background.overlay); |
|||
|
|||
this.showLegend = this.settings.showLegend; |
|||
|
|||
if (this.showLegend) { |
|||
this.legendItems = []; |
|||
this.legendClass = `legend-${this.settings.legendPosition}`; |
|||
this.legendLabelStyle = textStyle(this.settings.legendLabelFont); |
|||
this.disabledLegendLabelStyle = textStyle(this.settings.legendLabelFont); |
|||
this.legendLabelStyle.color = this.settings.legendLabelColor; |
|||
} |
|||
let counter = 0; |
|||
if (this.ctx.datasources.length) { |
|||
for (const datasource of this.ctx.datasources) { |
|||
const dataKeys = datasource.dataKeys; |
|||
for (const dataKey of dataKeys) { |
|||
const id = counter++; |
|||
const datasourceData = this.ctx.data ? this.ctx.data.find(d => d.dataKey === dataKey) : null; |
|||
const namedData = datasourceData?.data ? toNamedData(datasourceData.data) : []; |
|||
this.dataItems.push({ |
|||
id: id+'', |
|||
dataKey, |
|||
data: namedData, |
|||
enabled: true |
|||
}); |
|||
if (this.showLegend) { |
|||
this.legendItems.push( |
|||
{ |
|||
id: id+'', |
|||
label: dataKey.label, |
|||
color: dataKey.color, |
|||
enabled: true |
|||
} |
|||
); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
if (this.settings.showTooltip && this.settings.tooltipShowDate) { |
|||
this.tooltipDateFormat = DateFormatProcessor.fromSettings(this.ctx.$injector, this.settings.tooltipDateFormat); |
|||
} |
|||
|
|||
const barValueStyle: ComponentStyle = textStyle(this.settings.barValueFont); |
|||
delete barValueStyle.lineHeight; |
|||
barValueStyle.fontSize = this.settings.barValueFont.size; |
|||
barValueStyle.fill = this.settings.barValueColor; |
|||
|
|||
const barLabelStyle: ComponentStyle = textStyle(this.settings.barLabelFont); |
|||
delete barLabelStyle.lineHeight; |
|||
barLabelStyle.fontSize = this.settings.barLabelFont.size; |
|||
barLabelStyle.fill = this.settings.barLabelColor; |
|||
|
|||
this.barRenderItem = (params, api) => { |
|||
|
|||
const interval = this.ctx.defaultSubscription.timeWindow.interval; |
|||
const enabledDataItems = this.dataItems.filter(d => d.enabled); |
|||
const barInterval = interval / (enabledDataItems.length + 1); |
|||
const intervalGap = barInterval / 2; |
|||
|
|||
const index = enabledDataItems.findIndex(d => d.id === params.seriesId); |
|||
const time = api.value(0) as number; |
|||
const value = api.value(1); |
|||
const start = time - interval / 2; |
|||
const startTime = start + intervalGap + barInterval * index; |
|||
const delta = barInterval; |
|||
const lowerLeft = api.coord([startTime, value]); |
|||
const height = api.size([delta, value])[1]; |
|||
const width = api.size([delta, 10])[0]; |
|||
|
|||
const coordSys: {x: number; y: number; width: number; height: number} = params.coordSys as any; |
|||
|
|||
const rectShape = echarts.graphic.clipRectByRect({ |
|||
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) => { |
|||
if (params.rect.width - params.labelRect.width < 2) { |
|||
return { |
|||
y: '1000%', |
|||
}; |
|||
} else { |
|||
return { |
|||
hideOverlap: true |
|||
}; |
|||
} |
|||
}; |
|||
} |
|||
|
|||
ngAfterViewInit() { |
|||
if (this.drawChartPending) { |
|||
this.drawChart(); |
|||
} |
|||
} |
|||
|
|||
ngOnDestroy() { |
|||
if (this.shapeResize$) { |
|||
this.shapeResize$.disconnect(); |
|||
} |
|||
if (this.barChart) { |
|||
this.barChart.dispose(); |
|||
} |
|||
} |
|||
|
|||
public onInit() { |
|||
const borderRadius = this.ctx.$widgetElement.css('borderRadius'); |
|||
this.overlayStyle = {...this.overlayStyle, ...{borderRadius}}; |
|||
if (this.chartShape) { |
|||
this.drawChart(); |
|||
} else { |
|||
this.drawChartPending = true; |
|||
} |
|||
this.cd.detectChanges(); |
|||
} |
|||
|
|||
public onDataUpdated() { |
|||
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) { |
|||
const datasourceData = this.ctx.data ? this.ctx.data.find(d => d.dataKey === item.dataKey) : null; |
|||
item.data = datasourceData?.data ? toNamedData(datasourceData.data) : []; |
|||
if (datasourceData.data.length) { |
|||
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) { |
|||
(this.barChartOptions.xAxis as any).min = minTime; |
|||
(this.barChartOptions.xAxis as any).max = maxTime; |
|||
(this.barChartOptions.xAxis as any).tbTimewindowInterval = this.ctx.defaultSubscription.timeWindow.interval; |
|||
this.barChartOptions.series = this.updateSeries(); |
|||
this.barChart.setOption(this.barChartOptions); |
|||
} |
|||
} |
|||
|
|||
private updateSeries(): Array<CustomSeriesOption> { |
|||
const series: Array<CustomSeriesOption> = []; |
|||
for (const item of this.dataItems) { |
|||
if (item.enabled) { |
|||
const seriesOption: CustomSeriesOption = { |
|||
type: 'custom', |
|||
id: item.id, |
|||
name: item.dataKey.label, |
|||
color: item.dataKey.color, |
|||
data: item.data, |
|||
renderItem: this.barRenderItem, |
|||
labelLayout: this.barLabelLayoutCallback |
|||
}; |
|||
series.push(seriesOption); |
|||
} |
|||
} |
|||
return series; |
|||
} |
|||
|
|||
|
|||
public onLegendItemEnter(item: BarChartLegendItem) { |
|||
this.barChart.dispatchAction({ |
|||
type: 'highlight', |
|||
seriesId: item.id |
|||
}); |
|||
} |
|||
|
|||
public onLegendItemLeave(item: BarChartLegendItem) { |
|||
this.barChart.dispatchAction({ |
|||
type: 'downplay', |
|||
seriesId: item.id |
|||
}); |
|||
} |
|||
|
|||
public toggleLegendItem(item: BarChartLegendItem) { |
|||
const enable = !item.enabled; |
|||
const dataItem = this.dataItems.find(d => d.id === item.id); |
|||
if (dataItem) { |
|||
dataItem.enabled = enable; |
|||
if (!enable) { |
|||
this.barChart.dispatchAction({ |
|||
type: 'downplay', |
|||
seriesId: item.id |
|||
}); |
|||
} |
|||
this.barChartOptions.series = this.updateSeries(); |
|||
this.barChart.setOption(this.barChartOptions, {replaceMerge: ['series']}); |
|||
item.enabled = enable; |
|||
if (enable) { |
|||
this.barChart.dispatchAction({ |
|||
type: 'highlight', |
|||
seriesId: item.id |
|||
}); |
|||
} |
|||
} |
|||
} |
|||
|
|||
private drawChart() { |
|||
echartsModule.init(); |
|||
this.barChart = echarts.init(this.chartShape.nativeElement, null, { |
|||
renderer: 'canvas', |
|||
}); |
|||
this.barChartOptions = { |
|||
tooltip: { |
|||
trigger: 'none' |
|||
}, |
|||
grid: { |
|||
containLabel: true, |
|||
top: '30', |
|||
left: 0, |
|||
right: 0, |
|||
bottom: 0 |
|||
}, |
|||
xAxis: { |
|||
type: 'time', |
|||
scale: true, |
|||
axisTick: { |
|||
show: false |
|||
}, |
|||
axisLabel: { |
|||
hideOverlap: true, |
|||
fontSize: 10 |
|||
}, |
|||
axisLine: { |
|||
onZero: false |
|||
}, |
|||
min: this.ctx.defaultSubscription.timeWindow.minTime - this.ctx.defaultSubscription.timeWindow.interval / 2, |
|||
max: this.ctx.defaultSubscription.timeWindow.maxTime + this.ctx.defaultSubscription.timeWindow.interval / 2 |
|||
}, |
|||
yAxis: { |
|||
type: 'value', |
|||
axisLabel: { |
|||
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(); |
|||
|
|||
if (this.settings.showTooltip) { |
|||
this.barChartOptions.tooltip = { |
|||
trigger: 'axis', |
|||
axisPointer: { |
|||
type: 'shadow' |
|||
}, |
|||
formatter: (params: CallbackDataParams[]) => { |
|||
const focusedSeriesIndex = this.focusedSeriesIndex(); |
|||
return echartsTooltipFormatter(this.renderer, this.tooltipDateFormat, |
|||
this.settings, params, this.decimals, this.units, focusedSeriesIndex); |
|||
}, |
|||
padding: [8, 12], |
|||
backgroundColor: this.settings.tooltipBackgroundColor, |
|||
borderWidth: 0, |
|||
extraCssText: `line-height: 1; backdrop-filter: blur(${this.settings.tooltipBackgroundBlur}px);` |
|||
}; |
|||
} |
|||
|
|||
this.barChart.setOption(this.barChartOptions); |
|||
|
|||
this.shapeResize$ = new ResizeObserver(() => { |
|||
this.onResize(); |
|||
}); |
|||
this.shapeResize$.observe(this.chartShape.nativeElement); |
|||
this.onResize(); |
|||
} |
|||
|
|||
private focusedSeriesIndex(): number { |
|||
let index = - 1; |
|||
const views: any[] = (this.barChart as any)._chartsViews; |
|||
if (views) { |
|||
const hasBlurredView = !!views.find(view => { |
|||
const graphicEls: any[] = view._data._graphicEls; |
|||
return !!graphicEls.find(el => el?.currentStates.includes('blur')); |
|||
}); |
|||
if (hasBlurredView) { |
|||
const focusedView = views.find(view => { |
|||
const graphicEls: any[] = view._data._graphicEls; |
|||
return !!graphicEls.find(el => !el?.currentStates.includes('blur')); |
|||
}); |
|||
if (focusedView) { |
|||
index = !!focusedView._model ? |
|||
focusedView._model.seriesIndex : (!!focusedView.__model ? focusedView.__model.seriesIndex : -1); |
|||
} |
|||
} |
|||
} |
|||
return index; |
|||
} |
|||
|
|||
private onResize() { |
|||
const width = this.barChart.getWidth(); |
|||
const height = this.barChart.getHeight(); |
|||
const shapeWidth = this.chartShape.nativeElement.offsetWidth; |
|||
const shapeHeight = this.chartShape.nativeElement.offsetHeight; |
|||
if (width !== shapeWidth || height !== shapeHeight) { |
|||
this.barChart.resize(); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,99 @@ |
|||
///
|
|||
/// 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 { BackgroundSettings, BackgroundType, customDateFormat, Font } from '@shared/models/widget-settings.models'; |
|||
import { LegendPosition } from '@shared/models/widget.models'; |
|||
import { EChartsTooltipWidgetSettings } from '@home/components/widget/lib/chart/echarts-widget.models'; |
|||
|
|||
export interface BarChartWithLabelsWidgetSettings extends EChartsTooltipWidgetSettings { |
|||
showBarLabel: boolean; |
|||
barLabelFont: Font; |
|||
barLabelColor: string; |
|||
showBarValue: boolean; |
|||
barValueFont: Font; |
|||
barValueColor: string; |
|||
showLegend: boolean; |
|||
legendPosition: LegendPosition; |
|||
legendLabelFont: Font; |
|||
legendLabelColor: string; |
|||
background: BackgroundSettings; |
|||
} |
|||
|
|||
export const barChartWithLabelsDefaultSettings: BarChartWithLabelsWidgetSettings = { |
|||
showBarLabel: true, |
|||
barLabelFont: { |
|||
family: 'Roboto', |
|||
size: 12, |
|||
sizeUnit: 'px', |
|||
style: 'normal', |
|||
weight: '400', |
|||
lineHeight: '12px' |
|||
}, |
|||
barLabelColor: 'rgba(0, 0, 0, 0.54)', |
|||
showBarValue: true, |
|||
barValueFont: { |
|||
family: 'Roboto', |
|||
size: 12, |
|||
sizeUnit: 'px', |
|||
style: 'normal', |
|||
weight: '700', |
|||
lineHeight: '12px' |
|||
}, |
|||
barValueColor: 'rgba(0, 0, 0, 0.76)', |
|||
showLegend: true, |
|||
legendPosition: LegendPosition.top, |
|||
legendLabelFont: { |
|||
family: 'Roboto', |
|||
size: 12, |
|||
sizeUnit: 'px', |
|||
style: 'normal', |
|||
weight: '400', |
|||
lineHeight: '16px' |
|||
}, |
|||
legendLabelColor: 'rgba(0, 0, 0, 0.76)', |
|||
showTooltip: true, |
|||
tooltipValueFont: { |
|||
family: 'Roboto', |
|||
size: 12, |
|||
sizeUnit: 'px', |
|||
style: 'normal', |
|||
weight: '500', |
|||
lineHeight: '16px' |
|||
}, |
|||
tooltipValueColor: 'rgba(0, 0, 0, 0.76)', |
|||
tooltipShowDate: true, |
|||
tooltipDateFormat: customDateFormat('MMMM y'), |
|||
tooltipDateFont: { |
|||
family: 'Roboto', |
|||
size: 11, |
|||
sizeUnit: 'px', |
|||
style: 'normal', |
|||
weight: '400', |
|||
lineHeight: '16px' |
|||
}, |
|||
tooltipDateColor: 'rgba(0, 0, 0, 0.76)', |
|||
tooltipBackgroundColor: 'rgba(255, 255, 255, 0.76)', |
|||
tooltipBackgroundBlur: 4, |
|||
background: { |
|||
type: BackgroundType.color, |
|||
color: '#fff', |
|||
overlay: { |
|||
enabled: false, |
|||
color: 'rgba(255,255,255,0.72)', |
|||
blur: 3 |
|||
} |
|||
} |
|||
}; |
|||
@ -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; |
|||
}; |
|||
@ -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> |
|||
@ -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; |
|||
} |
|||
|
|||
} |
|||
Loading…
Reference in new issue