Browse Source

UI: Implement 'Simple Value and chart card' widget.

pull/9447/head
Igor Kulikov 3 years ago
parent
commit
f97350cd8a
  1. 1
      application/src/main/data/json/system/widget_bundles/cards.json
  2. 27
      application/src/main/data/json/system/widget_types/simple_value_and_chart_card.json
  3. 12
      ui-ngx/src/app/modules/home/components/widget/config/basic/basic-widget-config.module.ts
  4. 134
      ui-ngx/src/app/modules/home/components/widget/config/basic/cards/value-chart-card-basic-config.component.html
  5. 250
      ui-ngx/src/app/modules/home/components/widget/config/basic/cards/value-chart-card-basic-config.component.ts
  6. 2
      ui-ngx/src/app/modules/home/components/widget/config/timewindow-config-panel.component.ts
  7. 25
      ui-ngx/src/app/modules/home/components/widget/lib/cards/value-chart-card-widget.component.html
  8. 58
      ui-ngx/src/app/modules/home/components/widget/lib/cards/value-chart-card-widget.component.scss
  9. 247
      ui-ngx/src/app/modules/home/components/widget/lib/cards/value-chart-card-widget.component.ts
  10. 77
      ui-ngx/src/app/modules/home/components/widget/lib/cards/value-chart-card-widget.models.ts
  11. 54
      ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/value-chart-card-widget-settings.component.html
  12. 98
      ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/value-chart-card-widget-settings.component.ts
  13. 12
      ui-ngx/src/app/modules/home/components/widget/lib/settings/widget-settings.module.ts
  14. 7
      ui-ngx/src/app/modules/home/components/widget/widget-components.module.ts
  15. 10
      ui-ngx/src/assets/locale/locale.constant-en_US.json
  16. 21
      ui-ngx/src/assets/widget/value-chart-card/left-layout.svg
  17. 30
      ui-ngx/src/assets/widget/value-chart-card/right-layout.svg

1
application/src/main/data/json/system/widget_bundles/cards.json

@ -11,6 +11,7 @@
"cards.value_card",
"cards.horizontal_value_card",
"cards.aggregated_value_card",
"simple_value_and_chart_card",
"cards.label_widget",
"cards.dashboard_state_widget",
"cards.qr_code",

27
application/src/main/data/json/system/widget_types/simple_value_and_chart_card.json

@ -0,0 +1,27 @@
{
"fqn": "simple_value_and_chart_card",
"name": "Simple Value and chart card",
"deprecated": false,
"image": "data:image/png;base64,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",
"description": "Displays a single entity historical telemetry values as a simplified chart. Optionally may display the corresponding latest telemetry value.",
"descriptor": {
"type": "timeseries",
"sizeX": 4.5,
"sizeY": 2,
"resources": [],
"templateHtml": "<tb-value-chart-card-widget \n [ctx]=\"ctx\"\n [widgetTitlePanel]=\"widgetTitlePanel\">\n</tb-value-chart-card-widget>\n",
"templateCss": ".legend {\n font-size: 13px;\n line-height: 10px;\n}\n\n.legend table { \n border-spacing: 0px;\n border-collapse: separate;\n}\n\n.mouse-events .flot-overlay {\n cursor: crosshair; \n}\n\n",
"controllerScript": "self.onInit = function() {\n self.ctx.$scope.valueChartCardWidget.onInit();\n};\n\nself.onDataUpdated = function() {\n self.ctx.$scope.valueChartCardWidget.onDataUpdated();\n};\n\nself.onLatestDataUpdated = function() {\n self.ctx.$scope.valueChartCardWidget.onLatestDataUpdated();\n}\n\nself.onEditModeChanged = function() {\n self.ctx.$scope.valueChartCardWidget.onEditModeChanged();\n}\n\nself.onDestroy = function() {\n self.ctx.$scope.valueChartCardWidget.onDestroy();\n}\n\nself.typeParameters = function() {\n return {\n maxDatasources: 1,\n maxDataKeys: 1,\n singleEntity: true,\n previewWidth: '300px',\n previewHeight: '150px',\n embedTitlePanel: true,\n hasAdditionalLatestDataKeys: true\n };\n}\n",
"settingsSchema": "{}",
"dataKeySettingsSchema": "{}",
"latestDataKeySettingsSchema": "{}",
"settingsDirective": "tb-value-chart-card-widget-settings",
"dataKeySettingsDirective": "",
"latestDataKeySettingsDirective": "",
"hasBasicMode": true,
"basicModeDirective": "tb-value-chart-card-basic-config",
"defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Temperature\",\"color\":\"rgb(63, 82, 221)\",\"settings\":{},\"_hash\":0.8587686344902596,\"funcBody\":\"var value = prevValue + Math.random() * 40 - 20;\\nif (value < -60) {\\n\\tvalue = -60;\\n} else if (value > 60) {\\n\\tvalue = 60;\\n}\\nreturn value;\",\"aggregationType\":null,\"units\":null,\"decimals\":null,\"usePostProcessing\":null,\"postFuncBody\":null}],\"latestDataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Latest\",\"color\":\"rgb(63, 82, 221)\",\"settings\":{},\"_hash\":0.8587686344902596,\"funcBody\":\"var value = prevValue + Math.random() * 40 - 20;\\nif (value < -60) {\\n\\tvalue = -60;\\n} else if (value > 60) {\\n\\tvalue = 60;\\n}\\nreturn value;\",\"aggregationType\":null,\"units\":null,\"decimals\":null,\"usePostProcessing\":null,\"postFuncBody\":null}]}],\"showTitle\":true,\"backgroundColor\":\"rgba(0, 0, 0, 0)\",\"color\":null,\"padding\":\"0\",\"settings\":{\"layout\":\"left\",\"autoScale\":true,\"showValue\":true,\"valueFont\":{\"family\":\"Roboto\",\"size\":28,\"sizeUnit\":\"px\",\"style\":\"normal\",\"weight\":\"500\",\"lineHeight\":\"32px\"},\"valueColor\":{\"type\":\"constant\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"colorFunction\":\"var temperature = value;\\nif (typeof temperature !== undefined) {\\n var percent = (temperature + 60)/120 * 100;\\n return tinycolor.mix('blue', 'red', percent).toHexString();\\n}\\nreturn 'blue';\"},\"background\":{\"type\":\"color\",\"color\":\"#fff\",\"overlay\":{\"enabled\":false,\"color\":\"rgba(255,255,255,0.72)\",\"blur\":3}}},\"title\":\"Simple Value and chart card\",\"dropShadow\":true,\"enableFullscreen\":false,\"titleStyle\":null,\"mobileHeight\":null,\"configMode\":\"basic\",\"actions\":{},\"showTitleIcon\":false,\"titleIcon\":\"thermostat\",\"iconColor\":\"rgba(0, 0, 0, 0.87)\",\"titleFont\":{\"size\":16,\"sizeUnit\":\"px\",\"family\":\"Roboto\",\"weight\":\"500\",\"style\":\"normal\",\"lineHeight\":\"24px\"},\"iconSize\":\"18px\",\"titleTooltip\":\"\",\"widgetStyle\":{},\"widgetCss\":\"\",\"pageSize\":1024,\"noDataDisplayMessage\":\"\",\"useDashboardTimewindow\":true,\"decimals\":0,\"titleColor\":\"rgba(0, 0, 0, 0.87)\",\"borderRadius\":null,\"units\":\"°C\",\"displayTimewindow\":true,\"timewindow\":{\"hideInterval\":false,\"hideLastInterval\":false,\"hideQuickInterval\":false,\"hideAggregation\":false,\"hideAggInterval\":false,\"hideTimezone\":false,\"selectedTab\":1,\"history\":{\"historyType\":2,\"timewindowMs\":60000,\"interval\":43200000,\"fixedTimewindow\":{\"startTimeMs\":1697382151041,\"endTimeMs\":1697468551041},\"quickInterval\":\"CURRENT_MONTH_SO_FAR\"},\"aggregation\":{\"type\":\"AVG\",\"limit\":25000}},\"timewindowStyle\":{\"showIcon\":false,\"iconSize\":\"24px\",\"icon\":\"query_builder\",\"iconPosition\":\"left\",\"font\":{\"size\":12,\"sizeUnit\":\"px\",\"family\":\"Roboto\",\"weight\":\"400\",\"style\":\"normal\",\"lineHeight\":\"16px\"},\"color\":\"rgba(0, 0, 0, 0.38)\",\"displayTypePrefix\":true}}"
},
"externalId": null,
"tags": null
}

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

@ -64,6 +64,9 @@ import {
import {
SignalStrengthBasicConfigComponent
} from '@home/components/widget/config/basic/indicator/signal-strength-basic-config.component';
import {
ValueChartCardBasicConfigComponent
} from '@home/components/widget/config/basic/cards/value-chart-card-basic-config.component';
@NgModule({
declarations: [
@ -83,7 +86,8 @@ import {
EntityCountBasicConfigComponent,
BatteryLevelBasicConfigComponent,
WindSpeedDirectionBasicConfigComponent,
SignalStrengthBasicConfigComponent
SignalStrengthBasicConfigComponent,
ValueChartCardBasicConfigComponent
],
imports: [
CommonModule,
@ -107,7 +111,8 @@ import {
EntityCountBasicConfigComponent,
BatteryLevelBasicConfigComponent,
WindSpeedDirectionBasicConfigComponent,
SignalStrengthBasicConfigComponent
SignalStrengthBasicConfigComponent,
ValueChartCardBasicConfigComponent
]
})
export class BasicWidgetConfigModule {
@ -125,5 +130,6 @@ export const basicWidgetConfigComponentsMap: {[key: string]: Type<IBasicWidgetCo
'tb-entity-count-basic-config': EntityCountBasicConfigComponent,
'tb-battery-level-basic-config': BatteryLevelBasicConfigComponent,
'tb-wind-speed-direction-basic-config': WindSpeedDirectionBasicConfigComponent,
'tb-signal-strength-basic-config': SignalStrengthBasicConfigComponent
'tb-signal-strength-basic-config': SignalStrengthBasicConfigComponent,
'tb-value-chart-card-basic-config': ValueChartCardBasicConfigComponent
};

134
ui-ngx/src/app/modules/home/components/widget/config/basic/cards/value-chart-card-basic-config.component.html

@ -0,0 +1,134 @@
<!--
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]="valueChartCardWidgetConfigForm">
<tb-timewindow-config-panel formControlName="timewindowConfig">
</tb-timewindow-config-panel>
<tb-datasources
[configMode]="basicMode"
hideDataKeyLabel
hideDataKeyColor
hideDataKeyUnits
hideDataKeyDecimals
hideLatestDataKeys
formControlName="datasources">
</tb-datasources>
<div class="tb-form-panel">
<div class="tb-form-panel-title" translate>widget-config.appearance</div>
<tb-image-cards-select rowHeight="2:1"
cols="2"
label="{{ 'widgets.value-chart-card.layout' | translate }}" formControlName="layout">
<tb-image-cards-select-option *ngFor="let layout of valueChartCardLayouts"
[value]="layout"
[image]="valueChartCardLayoutImageMap.get(layout)">
{{ valueChartCardLayoutTranslationMap.get(layout) | translate }}
</tb-image-cards-select-option>
</tb-image-cards-select>
<div class="tb-form-row">
<mat-slide-toggle class="mat-slide" formControlName="autoScale">
{{ 'widgets.value-chart-card.auto-scale' | translate }}
</mat-slide-toggle>
</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]="valueChartCardWidgetConfigForm.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">
{{ 'widgets.value-chart-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]="valueChartCardWidgetConfigForm.get('iconColor').value"
formControlName="icon">
</tb-material-icon-select>
<tb-color-input asBoxInput
colorClearButton
formControlName="iconColor">
</tb-color-input>
</div>
</div>
<div class="tb-form-row">
<mat-slide-toggle class="mat-slide fixed-title-width" formControlName="showValue">
{{ 'widgets.value-chart-card.value' | translate }}
</mat-slide-toggle>
<div fxFlex fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px">
<tb-unit-input class="flex" formControlName="units"></tb-unit-input>
<mat-form-field appearance="outline" class="flex number" subscriptSizing="dynamic">
<input matInput formControlName="decimals" type="number" min="0" max="15" step="1" placeholder="{{ 'widget-config.set' | translate }}">
<div matSuffix fxHide.lt-md translate>widget-config.decimals-suffix</div>
</mat-form-field>
<tb-font-settings formControlName="valueFont"
[autoScale]="valueChartCardWidgetConfigForm.get('autoScale').value"
[previewText]="valuePreviewFn">
</tb-font-settings>
<tb-color-settings formControlName="valueColor" settingsKey="{{'widgets.value-chart-card.value' | translate }}">
</tb-color-settings>
</div>
</div>
<div class="tb-form-row space-between">
<div>{{ 'widgets.value-chart-card.chart' | translate }}</div>
<tb-color-input asBoxInput
colorClearButton
formControlName="chartColor">
</tb-color-input>
</div>
</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>

250
ui-ngx/src/app/modules/home/components/widget/config/basic/cards/value-chart-card-basic-config.component.ts

@ -0,0 +1,250 @@
///
/// 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 } 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, 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, getDataKey, resolveCssSize } from '@shared/models/widget-settings.models';
import {
valueCartCardLayouts,
valueChartCardDefaultSettings,
valueChartCardLayoutImages,
valueChartCardLayoutTranslations,
ValueChartCardWidgetSettings
} from '@home/components/widget/lib/cards/value-chart-card-widget.models';
@Component({
selector: 'tb-value-chart-card-basic-config',
templateUrl: './value-chart-card-basic-config.component.html',
styleUrls: ['../basic-config.scss']
})
export class ValueChartCardBasicConfigComponent extends BasicWidgetConfigComponent {
public get datasource(): Datasource {
const datasources: Datasource[] = this.valueChartCardWidgetConfigForm.get('datasources').value;
if (datasources && datasources.length) {
return datasources[0];
} else {
return null;
}
}
valueChartCardLayouts = valueCartCardLayouts;
valueChartCardLayoutTranslationMap = valueChartCardLayoutTranslations;
valueChartCardLayoutImageMap = valueChartCardLayoutImages;
valueChartCardWidgetConfigForm: UntypedFormGroup;
valuePreviewFn = this._valuePreviewFn.bind(this);
constructor(protected store: Store<AppState>,
protected widgetConfigComponent: WidgetConfigComponent,
private fb: UntypedFormBuilder) {
super(store, widgetConfigComponent);
}
protected configForm(): UntypedFormGroup {
return this.valueChartCardWidgetConfigForm;
}
protected setupDefaults(configData: WidgetConfigComponentData) {
this.setupDefaultDatasource(configData, [
{ name: 'temperature', label: 'Temperature', type: DataKeyType.timeseries, color: 'rgba(63, 82, 221, 1)'}
],
[{ name: 'temperature', label: 'Latest', type: DataKeyType.timeseries}]
);
}
protected onConfigSet(configData: WidgetConfigComponentData) {
const settings: ValueChartCardWidgetSettings = {...valueChartCardDefaultSettings, ...(configData.config.settings || {})};
const dataKey = getDataKey(configData.config.datasources);
const iconSize = resolveCssSize(configData.config.iconSize);
this.valueChartCardWidgetConfigForm = this.fb.group({
timewindowConfig: [getTimewindowConfig(configData.config), []],
datasources: [configData.config.datasources, []],
layout: [settings.layout, []],
autoScale: [settings.autoScale, []],
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, []],
showValue: [settings.showValue, []],
units: [configData.config.units, []],
decimals: [configData.config.decimals, []],
valueFont: [settings.valueFont, []],
valueColor: [settings.valueColor, []],
chartColor: [dataKey?.color, []],
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.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.layout = config.layout;
this.widgetConfig.config.settings.autoScale = config.autoScale;
this.widgetConfig.config.settings.showValue = config.showValue;
this.widgetConfig.config.units = config.units;
this.widgetConfig.config.decimals = config.decimals;
this.widgetConfig.config.settings.valueFont = config.valueFont;
this.widgetConfig.config.settings.valueColor = config.valueColor;
const dataKey = getDataKey(this.widgetConfig.config.datasources);
if (dataKey) {
dataKey.color = config.chartColor;
this.updateLatestValues(dataKey, this.widgetConfig.config.datasources);
}
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', 'showValue'];
}
protected updateValidators(emitEvent: boolean, trigger?: string) {
const showTitle: boolean = this.valueChartCardWidgetConfigForm.get('showTitle').value;
const showIcon: boolean = this.valueChartCardWidgetConfigForm.get('showIcon').value;
const showValue: boolean = this.valueChartCardWidgetConfigForm.get('showValue').value;
if (showTitle) {
this.valueChartCardWidgetConfigForm.get('title').enable();
this.valueChartCardWidgetConfigForm.get('titleFont').enable();
this.valueChartCardWidgetConfigForm.get('titleColor').enable();
this.valueChartCardWidgetConfigForm.get('showIcon').enable({emitEvent: false});
if (showIcon) {
this.valueChartCardWidgetConfigForm.get('iconSize').enable();
this.valueChartCardWidgetConfigForm.get('iconSizeUnit').enable();
this.valueChartCardWidgetConfigForm.get('icon').enable();
this.valueChartCardWidgetConfigForm.get('iconColor').enable();
} else {
this.valueChartCardWidgetConfigForm.get('iconSize').disable();
this.valueChartCardWidgetConfigForm.get('iconSizeUnit').disable();
this.valueChartCardWidgetConfigForm.get('icon').disable();
this.valueChartCardWidgetConfigForm.get('iconColor').disable();
}
} else {
this.valueChartCardWidgetConfigForm.get('title').disable();
this.valueChartCardWidgetConfigForm.get('titleFont').disable();
this.valueChartCardWidgetConfigForm.get('titleColor').disable();
this.valueChartCardWidgetConfigForm.get('showIcon').disable({emitEvent: false});
this.valueChartCardWidgetConfigForm.get('iconSize').disable();
this.valueChartCardWidgetConfigForm.get('iconSizeUnit').disable();
this.valueChartCardWidgetConfigForm.get('icon').disable();
this.valueChartCardWidgetConfigForm.get('iconColor').disable();
}
if (showValue) {
this.valueChartCardWidgetConfigForm.get('units').enable();
this.valueChartCardWidgetConfigForm.get('decimals').enable();
this.valueChartCardWidgetConfigForm.get('valueFont').enable();
this.valueChartCardWidgetConfigForm.get('valueColor').enable();
} else {
this.valueChartCardWidgetConfigForm.get('units').disable();
this.valueChartCardWidgetConfigForm.get('decimals').disable();
this.valueChartCardWidgetConfigForm.get('valueFont').disable();
this.valueChartCardWidgetConfigForm.get('valueColor').disable();
}
}
private updateLatestValues(sourceDataKey: DataKey, datasources?: Datasource[]) {
if (datasources && datasources.length) {
let latestDataKeys = datasources[0].latestDataKeys;
if (!latestDataKeys) {
latestDataKeys = [];
datasources[0].latestDataKeys = latestDataKeys;
}
let dataKey: DataKey;
if (!latestDataKeys.length) {
dataKey = {...sourceDataKey};
latestDataKeys.push(dataKey);
} else {
dataKey = latestDataKeys[0];
dataKey = {...dataKey, ...sourceDataKey};
latestDataKeys[0] = dataKey;
}
dataKey.label = 'Latest';
dataKey.units = null;
dataKey.decimals = null;
}
}
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 _valuePreviewFn(): string {
const units: string = this.widgetConfig.config.units;
const decimals: number = this.widgetConfig.config.decimals;
return formatValue(22, decimals, units, true);
}
}

2
ui-ngx/src/app/modules/home/components/widget/config/timewindow-config-panel.component.ts

@ -92,7 +92,7 @@ export class TimewindowConfigPanelComponent implements ControlValueAccessor, OnI
timewindowStyle: [null, []]
});
this.timewindowConfig.valueChanges.subscribe(
(val) => this.propagateChange(val)
() => this.propagateChange(this.timewindowConfig.getRawValue())
);
this.timewindowConfig.get('useDashboardTimewindow').valueChanges.subscribe(() => {
this.updateTimewindowConfigEnabledState();

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

@ -0,0 +1,25 @@
<!--
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-value-chart-card-panel" [style]="backgroundStyle">
<div class="tb-value-chart-card-overlay" [style]="overlayStyle"></div>
<ng-container *ngTemplateOutlet="widgetTitlePanel"></ng-container>
<div #valueChartCardContent class="tb-value-chart-card-content" [class]="layout">
<div *ngIf="showValue" class="tb-value-chart-card-value" #valueChartCardValue [style]="valueStyle" [style.color]="valueColor.color">{{ valueText }}</div>
<div class="tb-value-chart-card-chart" #chartElement></div>
</div>
</div>

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

@ -0,0 +1,58 @@
/**
* 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-value-chart-card-panel {
width: 100%;
height: 100%;
position: relative;
display: flex;
flex-direction: column;
gap: 8px;
padding: 20px 24px 24px 24px;
> div:not(.tb-value-chart-card-overlay) {
z-index: 1;
}
div.tb-widget-title {
padding: 0;
}
.tb-value-chart-card-overlay {
position: absolute;
top: 12px;
left: 12px;
bottom: 12px;
right: 12px;
}
.tb-value-chart-card-content {
min-height: 0;
flex: 1;
display: flex;
align-items: flex-end;
gap: 16px;
position: relative;
&.left {
flex-direction: row;
}
&.right {
flex-direction: row-reverse;
}
.tb-value-chart-card-value {
white-space: nowrap;
}
.tb-value-chart-card-chart {
flex: 1;
height: 100%;
}
}
}

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

@ -0,0 +1,247 @@
///
/// 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 { formatValue, isDefinedAndNotNull } from '@core/utils';
import {
backgroundStyle,
ColorProcessor,
ComponentStyle,
getDataKey,
overlayStyle, resolveCssSize,
textStyle
} from '@shared/models/widget-settings.models';
import { WidgetComponent } from '@home/components/widget/widget.component';
import { ResizeObserver } from '@juggle/resize-observer';
import {
valueChartCardDefaultSettings,
ValueChartCardLayout,
ValueChartCardWidgetSettings
} from '@home/components/widget/lib/cards/value-chart-card-widget.models';
import { TbFlot } from '@home/components/widget/lib/flot-widget';
import { DataKey } from '@shared/models/widget.models';
import { TbFlotKeySettings, TbFlotSettings } from '@home/components/widget/lib/flot-widget.models';
import { getTsValueByLatestDataKey } from '@home/components/widget/lib/cards/aggregated-value-card.models';
const layoutWidth = 218;
const layoutValueWidth = 68;
const valueRelativeWidth = layoutValueWidth / layoutWidth;
@Component({
selector: 'tb-value-chart-card-widget',
templateUrl: './value-chart-card-widget.component.html',
styleUrls: ['./value-chart-card-widget.component.scss'],
encapsulation: ViewEncapsulation.None
})
export class ValueChartCardWidgetComponent implements OnInit, AfterViewInit, OnDestroy {
@ViewChild('chartElement', {static: false})
chartElement: ElementRef;
@ViewChild('valueChartCardContent', {static: false})
valueChartCardContent: ElementRef<HTMLElement>;
@ViewChild('valueChartCardValue', {static: false})
valueChartCardValue: ElementRef<HTMLElement>;
settings: ValueChartCardWidgetSettings;
@Input()
ctx: WidgetContext;
@Input()
widgetTitlePanel: TemplateRef<any>;
layout: ValueChartCardLayout;
showValue = true;
valueText = 'N/A';
valueStyle: ComponentStyle = {};
valueColor: ColorProcessor;
backgroundStyle: ComponentStyle = {};
overlayStyle: ComponentStyle = {};
private flot: TbFlot;
private flotDataKey: DataKey;
private valueKey: DataKey;
private contentResize$: ResizeObserver;
private decimals = 0;
private units = '';
constructor(private renderer: Renderer2,
private widgetComponent: WidgetComponent,
private cd: ChangeDetectorRef) {
}
ngOnInit(): void {
this.ctx.$scope.valueChartCardWidget = this;
this.settings = {...valueChartCardDefaultSettings, ...this.ctx.settings};
if (this.showValue) {
this.decimals = this.ctx.decimals;
this.units = this.ctx.units;
const dataKey = getDataKey(this.ctx.datasources);
if (dataKey?.name && this.ctx.defaultSubscription.firstDatasource?.latestDataKeys?.length) {
const dataKeys = this.ctx.defaultSubscription.firstDatasource?.latestDataKeys;
this.valueKey = dataKeys?.find(k => k.name === dataKey.name);
if (isDefinedAndNotNull(this.valueKey?.decimals)) {
this.decimals = this.valueKey.decimals;
}
if (this.valueKey?.units) {
this.units = dataKey.units;
}
}
}
this.layout = this.settings.layout;
this.showValue = this.settings.showValue;
this.valueStyle = textStyle(this.settings.valueFont, '0.25px');
this.valueColor = ColorProcessor.fromSettings(this.settings.valueColor);
this.backgroundStyle = backgroundStyle(this.settings.background);
this.overlayStyle = overlayStyle(this.settings.background.overlay);
if (this.ctx.defaultSubscription.firstDatasource?.dataKeys?.length) {
this.flotDataKey = this.ctx.defaultSubscription.firstDatasource?.dataKeys[0];
this.flotDataKey.settings = {
fillLines: false,
showLines: true,
lineWidth: 2
} as TbFlotKeySettings;
}
}
public ngAfterViewInit() {
const settings = {
shadowSize: 0,
enableSelection: false,
smoothLines: true,
grid: {
tickColor: 'rgba(0,0,0,0.12)',
horizontalLines: false,
verticalLines: false,
outlineWidth: 0,
minBorderMargin: 0,
margin: 0
},
yaxis: {
showLabels: false,
tickGenerator: 'return [(axis.max + axis.min) / 2];'
},
xaxis: {
showLabels: false
}
} as TbFlotSettings;
this.flot = new TbFlot(this.ctx, 'line', $(this.chartElement.nativeElement), settings);
this.contentResize$ = new ResizeObserver(() => {
this.onResize();
});
this.contentResize$.observe(this.valueChartCardContent.nativeElement);
this.onResize();
}
ngOnDestroy() {
if (this.contentResize$) {
this.contentResize$.disconnect();
}
}
public onInit() {
const borderRadius = this.ctx.$widgetElement.css('borderRadius');
this.overlayStyle = {...this.overlayStyle, ...{borderRadius}};
this.cd.detectChanges();
}
public onDataUpdated() {
this.flot.update();
}
public onLatestDataUpdated() {
if (this.showValue && this.valueKey) {
const tsValue = getTsValueByLatestDataKey(this.ctx.latestData, this.valueKey);
let value;
if (tsValue) {
value = tsValue[1];
this.valueText = formatValue(value, this.decimals, this.units, true);
} else {
this.valueText = 'N/A';
}
this.valueColor.update(value);
this.cd.detectChanges();
setTimeout(() => {
this.onResize(false);
}, 0);
}
}
public onEditModeChanged() {
this.flot.checkMouseEvents();
}
public onDestroy() {
this.flot.destroy();
}
private onResize(fitMaxWidth = true) {
if (this.settings.autoScale && this.showValue) {
const contentWidth = this.valueChartCardContent.nativeElement.getBoundingClientRect().width;
const contentHeight = this.valueChartCardContent.nativeElement.getBoundingClientRect().height;
const targetValueWidth = valueRelativeWidth * contentWidth;
this.setValueFontSize(targetValueWidth, contentHeight, fitMaxWidth);
}
this.flot.resize();
}
private setValueFontSize(maxWidth: number, maxHeight: number, fitMaxWidth = true) {
const fontSize = getComputedStyle(this.valueChartCardValue.nativeElement).fontSize;
let valueFontSize = resolveCssSize(fontSize)[0];
this.renderer.setStyle(this.valueChartCardValue.nativeElement, 'fontSize', valueFontSize + 'px');
this.renderer.setStyle(this.valueChartCardValue.nativeElement, 'lineHeight', '1');
let valueWidth = this.valueChartCardValue.nativeElement.getBoundingClientRect().width;
while (fitMaxWidth && valueWidth < maxWidth) {
valueFontSize++;
this.renderer.setStyle(this.valueChartCardValue.nativeElement, 'fontSize', valueFontSize + 'px');
valueWidth = this.valueChartCardValue.nativeElement.getBoundingClientRect().width;
}
let valueHeight = this.valueChartCardValue.nativeElement.getBoundingClientRect().height;
while ((valueWidth > maxWidth || valueHeight > maxHeight) && valueFontSize > 6) {
valueFontSize--;
this.renderer.setStyle(this.valueChartCardValue.nativeElement, 'fontSize', valueFontSize + 'px');
valueWidth = this.valueChartCardValue.nativeElement.getBoundingClientRect().width;
valueHeight = this.valueChartCardValue.nativeElement.getBoundingClientRect().height;
}
}
}

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

@ -0,0 +1,77 @@
///
/// 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,
ColorSettings,
constantColor,
Font
} from '@shared/models/widget-settings.models';
export enum ValueChartCardLayout {
left = 'left',
right = 'right'
}
export const valueCartCardLayouts = Object.keys(ValueChartCardLayout) as ValueChartCardLayout[];
export const valueChartCardLayoutTranslations = new Map<ValueChartCardLayout, string>(
[
[ValueChartCardLayout.left, 'widgets.value-chart-card.layout-left'],
[ValueChartCardLayout.right, 'widgets.value-chart-card.layout-right']
]
);
export const valueChartCardLayoutImages = new Map<ValueChartCardLayout, string>(
[
[ValueChartCardLayout.left, 'assets/widget/value-chart-card/left-layout.svg'],
[ValueChartCardLayout.right, 'assets/widget/value-chart-card/right-layout.svg']
]
);
export interface ValueChartCardWidgetSettings {
layout: ValueChartCardLayout;
autoScale: boolean;
showValue: boolean;
valueFont: Font;
valueColor: ColorSettings;
background: BackgroundSettings;
}
export const valueChartCardDefaultSettings: ValueChartCardWidgetSettings = {
layout: ValueChartCardLayout.left,
autoScale: true,
showValue: true,
valueFont: {
family: 'Roboto',
size: 28,
sizeUnit: 'px',
style: 'normal',
weight: '500',
lineHeight: '32px'
},
valueColor: constantColor('rgba(0, 0, 0, 0.87)'),
background: {
type: BackgroundType.color,
color: '#fff',
overlay: {
enabled: false,
color: 'rgba(255,255,255,0.72)',
blur: 3
}
}
};

54
ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/value-chart-card-widget-settings.component.html

@ -0,0 +1,54 @@
<!--
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]="valueChartCardWidgetSettingsForm">
<div class="tb-form-panel">
<div class="tb-form-panel-title" translate>widgets.value-chart-card.value-chart-card-style</div>
<tb-image-cards-select rowHeight="2:1"
cols="2"
label="{{ 'widgets.value-chart-card.layout' | translate }}" formControlName="layout">
<tb-image-cards-select-option *ngFor="let layout of valueChartCardLayouts"
[value]="layout"
[image]="valueChartCardLayoutImageMap.get(layout)">
{{ valueChartCardLayoutTranslationMap.get(layout) | translate }}
</tb-image-cards-select-option>
</tb-image-cards-select>
<div class="tb-form-row">
<mat-slide-toggle class="mat-slide" formControlName="autoScale">
{{ 'widgets.value-chart-card.auto-scale' | translate }}
</mat-slide-toggle>
</div>
<div class="tb-form-row space-between">
<mat-slide-toggle class="mat-slide" formControlName="showValue">
{{ 'widgets.value-chart-card.value' | translate }}
</mat-slide-toggle>
<div fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px">
<tb-font-settings formControlName="valueFont"
[autoScale]="valueChartCardWidgetSettingsForm.get('autoScale').value"
[previewText]="valuePreviewFn">
</tb-font-settings>
<tb-color-settings formControlName="valueColor" settingsKey="{{'widgets.value-chart-card.value' | translate }}">
</tb-color-settings>
</div>
</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>

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

@ -0,0 +1,98 @@
///
/// 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 { 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 {
valueCartCardLayouts,
valueChartCardDefaultSettings,
valueChartCardLayoutImages,
valueChartCardLayoutTranslations
} from '@home/components/widget/lib/cards/value-chart-card-widget.models';
@Component({
selector: 'tb-value-chart-card-widget-settings',
templateUrl: './value-chart-card-widget-settings.component.html',
styleUrls: []
})
export class ValueChartCardWidgetSettingsComponent extends WidgetSettingsComponent {
valueChartCardLayouts = valueCartCardLayouts;
valueChartCardLayoutTranslationMap = valueChartCardLayoutTranslations;
valueChartCardLayoutImageMap = valueChartCardLayoutImages;
valueChartCardWidgetSettingsForm: UntypedFormGroup;
valuePreviewFn = this._valuePreviewFn.bind(this);
constructor(protected store: Store<AppState>,
private $injector: Injector,
private fb: UntypedFormBuilder) {
super(store);
}
protected settingsForm(): UntypedFormGroup {
return this.valueChartCardWidgetSettingsForm;
}
protected defaultSettings(): WidgetSettings {
return {...valueChartCardDefaultSettings};
}
protected onSettingsSet(settings: WidgetSettings) {
this.valueChartCardWidgetSettingsForm = this.fb.group({
layout: [settings.layout, []],
autoScale: [settings.autoScale, []],
showValue: [settings.showValue, []],
valueFont: [settings.valueFont, []],
valueColor: [settings.valueColor, []],
background: [settings.background, []]
});
}
protected validatorTriggers(): string[] {
return ['showValue'];
}
protected updateValidators(emitEvent: boolean) {
const showValue: boolean = this.valueChartCardWidgetSettingsForm.get('showValue').value;
if (showValue) {
this.valueChartCardWidgetSettingsForm.get('valueFont').enable();
this.valueChartCardWidgetSettingsForm.get('valueColor').enable();
} else {
this.valueChartCardWidgetSettingsForm.get('valueFont').disable();
this.valueChartCardWidgetSettingsForm.get('valueColor').disable();
}
this.valueChartCardWidgetSettingsForm.get('valueFont').updateValueAndValidity({emitEvent});
this.valueChartCardWidgetSettingsForm.get('valueColor').updateValueAndValidity({emitEvent});
}
private _valuePreviewFn(): string {
const units: string = this.widgetConfig.config.units;
const decimals: number = this.widgetConfig.config.decimals;
return formatValue(22, decimals, units, true);
}
}

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

@ -294,6 +294,9 @@ import {
import {
SignalStrengthWidgetSettingsComponent
} from '@home/components/widget/lib/settings/indicator/signal-strength-widget-settings.component';
import {
ValueChartCardWidgetSettingsComponent
} from '@home/components/widget/lib/settings/cards/value-chart-card-widget-settings.component';
@NgModule({
declarations: [
@ -402,7 +405,8 @@ import {
EntityCountWidgetSettingsComponent,
BatteryLevelWidgetSettingsComponent,
WindSpeedDirectionWidgetSettingsComponent,
SignalStrengthWidgetSettingsComponent
SignalStrengthWidgetSettingsComponent,
ValueChartCardWidgetSettingsComponent
],
imports: [
CommonModule,
@ -516,7 +520,8 @@ import {
EntityCountWidgetSettingsComponent,
BatteryLevelWidgetSettingsComponent,
WindSpeedDirectionWidgetSettingsComponent,
SignalStrengthWidgetSettingsComponent
SignalStrengthWidgetSettingsComponent,
ValueChartCardWidgetSettingsComponent
]
})
export class WidgetSettingsModule {
@ -595,5 +600,6 @@ export const widgetSettingsComponentsMap: {[key: string]: Type<IWidgetSettingsCo
'tb-entity-count-widget-settings': EntityCountWidgetSettingsComponent,
'tb-battery-level-widget-settings': BatteryLevelWidgetSettingsComponent,
'tb-wind-speed-direction-widget-settings': WindSpeedDirectionWidgetSettingsComponent,
'tb-signal-strength-widget-settings': SignalStrengthWidgetSettingsComponent
'tb-signal-strength-widget-settings': SignalStrengthWidgetSettingsComponent,
'tb-value-chart-card-widget-settings': ValueChartCardWidgetSettingsComponent
};

7
ui-ngx/src/app/modules/home/components/widget/widget-components.module.ts

@ -62,6 +62,7 @@ import {
WindSpeedDirectionWidgetComponent
} from '@home/components/widget/lib/weather/wind-speed-direction-widget.component';
import { SignalStrengthWidgetComponent } from '@home/components/widget/lib/indicator/signal-strength-widget.component';
import { ValueChartCardWidgetComponent } from '@home/components/widget/lib/cards/value-chart-card-widget.component';
@NgModule({
declarations:
@ -98,7 +99,8 @@ import { SignalStrengthWidgetComponent } from '@home/components/widget/lib/indic
CountWidgetComponent,
BatteryLevelWidgetComponent,
WindSpeedDirectionWidgetComponent,
SignalStrengthWidgetComponent
SignalStrengthWidgetComponent,
ValueChartCardWidgetComponent
],
imports: [
CommonModule,
@ -139,7 +141,8 @@ import { SignalStrengthWidgetComponent } from '@home/components/widget/lib/indic
CountWidgetComponent,
BatteryLevelWidgetComponent,
WindSpeedDirectionWidgetComponent,
SignalStrengthWidgetComponent
SignalStrengthWidgetComponent,
ValueChartCardWidgetComponent
],
providers: [
{provide: WIDGET_COMPONENTS_MODULE_TOKEN, useValue: WidgetComponentsModule }

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

@ -6086,6 +6086,16 @@
"aggregated-value-card-style": "Aggregated value card style",
"auto-scale": "Auto scale"
},
"value-chart-card": {
"layout": "Layout",
"layout-left": "Left",
"layout-right": "Right",
"auto-scale": "Auto scale",
"icon": "Icon",
"value": "Value",
"chart": "Chart",
"value-chart-card-style": "Value chart card style"
},
"alarm-count": {
"alarm-count-card-style": "Alarm count card style"
},

21
ui-ngx/src/assets/widget/value-chart-card/left-layout.svg

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 18 KiB

30
ui-ngx/src/assets/widget/value-chart-card/right-layout.svg

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 18 KiB

Loading…
Cancel
Save